Compare commits

..

53 Commits

Author SHA1 Message Date
Auxilor
43b7c393b9 Cleaned up data desync PR 2023-04-29 15:52:13 +01:00
Auxilor
714952bc45 PR Cleanup 2023-04-29 15:47:42 +01:00
Will FP
322e179b81 Merge pull request #269
Add PlayerPoints to prices system
2023-04-29 15:43:54 +01:00
Auxilor
469be73ada Added option to translate placeholders without a context 2023-04-29 15:03:02 +01:00
Auxilor
7eac60146f Added placeholderContext#copy 2023-04-29 13:44:03 +01:00
Auxilor
ad0223e1bb Cleaned up PlaceholderContext 2023-04-29 13:22:18 +01:00
BuildTools
430117f342 Add PlayerPoints to prices system 2023-04-29 10:20:53 +07:00
Auxilor
fa87cae81e Clarified PlaceholderParser 2023-04-28 19:59:13 +01:00
Auxilor
5695750fc5 Optimised additional player placeholder parsing 2023-04-28 17:05:40 +01:00
Auxilor
45e8a57880 Fixed DynamicInjectablePlaceholder 2023-04-28 16:24:34 +01:00
Auxilor
17fcd2c1b5 Added DynamicPlaceholder and DynamicInjectablePlaceholder 2023-04-28 16:24:18 +01:00
Auxilor
0c1e17c351 Massively optimised placeholder parsing with ListViewOfCollection 2023-04-28 16:11:21 +01:00
Auxilor
9415515849 Simplified enable logs 2023-04-28 14:34:30 +01:00
Auxilor
d0e957ea37 Cleaned up imports 2023-04-28 14:23:44 +01:00
Will FP
69514e2f85 Merge pull request #268
Denizen Integration
2023-04-28 14:22:29 +01:00
FireML
259e35c978 Script Name Alternative 2023-04-28 00:47:01 +01:00
FireML
40aec26f15 Initial Denizen Support 2023-04-27 23:03:34 +01:00
Auxilor
67b2fdd594 Merge branch 'master' into develop
# Conflicts:
#	gradle.properties
2023-04-27 19:27:57 +01:00
Auxilor
83f86983f6 Updated to 6.55.4 2023-04-27 19:22:30 +01:00
Auxilor
3ba98a9a5e Fixed UltraEconomy 2023-04-27 19:22:20 +01:00
Auxilor
f19e565fbe Moved to native fastToDoubleOrNull function 2023-04-27 19:08:10 +01:00
Auxilor
ee3ecb643b Cleanup 2023-04-27 18:55:03 +01:00
Auxilor
3aefb0e481 Switched back to ConcurrentHashMap in EcoConfig 2023-04-27 18:04:02 +01:00
Auxilor
d9eb1e1c26 Merge branch 'fix-data-desync' into develop 2023-04-27 18:02:09 +01:00
Auxilor
70631e67c5 Config injections no longer use a ConcurrentHashMap 2023-04-27 15:51:16 +01:00
Auxilor
82797e7342 Rewrote placeholder parsing 2023-04-27 15:42:46 +01:00
Auxilor
d2917341b1 Removed use-lower-protocollib-priority 2023-04-26 20:31:53 +01:00
Auxilor
760f42be0c Refactor, made math cache TTL configurable 2023-04-26 20:31:44 +01:00
Auxilor
bb01af2ab2 Optimised StringUtils#replaceQuickly 2023-04-26 18:22:56 +01:00
Auxilor
517d890c05 More optimisations to evaluation pipeline 2023-04-26 17:37:31 +01:00
Auxilor
f02fc56778 Added alternate crunch evaluation option 2023-04-26 16:55:40 +01:00
Auxilor
4417b09c14 Various optimisations along the evaluation pipeline 2023-04-26 16:10:04 +01:00
Auxilor
6b37fafa90 Further simplified Config 2023-04-26 15:25:50 +01:00
Auxilor
f8c5c9f06d Revert "Added KPlaceholderContext"
This reverts commit 0fd6cbaa08.
2023-04-26 15:02:05 +01:00
Auxilor
0fd6cbaa08 Added KPlaceholderContext 2023-04-26 14:58:48 +01:00
Auxilor
81afa32eb0 Added placeholderContext kotlin builder 2023-04-26 14:47:40 +01:00
Auxilor
7387fe2332 Improved default config methods 2023-04-26 14:44:13 +01:00
Auxilor
80fa05da98 Added PlaceholderContext#copyWithItem 2023-04-26 14:07:22 +01:00
Auxilor
77754249ad Added SimplePlaceholder and SimpleInjectablePlaceholder 2023-04-26 13:23:29 +01:00
Auxilor
1843cf0f8a PlaceholderManager#translatePlaceholders now includes injections 2023-04-26 13:13:59 +01:00
Auxilor
05eb5ee993 Rewrote more placeholder backend, deprecated MathContext 2023-04-26 13:07:18 +01:00
Auxilor
7bc11ee716 Added new format options 2023-04-25 19:19:43 +01:00
Auxilor
f566aec00e PlaceholderContext now extends MathContext 2023-04-25 19:11:15 +01:00
Auxilor
36d47c55a1 Updated to 6.56.0 2023-04-25 18:57:22 +01:00
Auxilor
40463213ac Added PlaceholderContext as a unified way to parse placeholders 2023-04-25 18:57:06 +01:00
Auxilor
6ec80d30ad Updated to 6.55.3 2023-04-25 10:28:38 +01:00
Auxilor
7ccee60a0c Fixed IntegrationRegistry#executeSafely 2023-04-25 10:28:23 +01:00
Auxilor
0805b48763 Updated to 6.55.2 2023-04-24 22:12:47 +01:00
Auxilor
d737322aaa Merge remote-tracking branch 'origin/master'
# Conflicts:
#	gradle.properties
2023-04-24 22:12:19 +01:00
Auxilor
4ddc150e1c Updated to 6.54.1 2023-04-24 22:11:55 +01:00
Auxilor
0da119d89d Finally reimplemented BlockUtils#getVein 2023-04-24 22:10:42 +01:00
Auxilor
fc0a07d1c5 Config injections now use a ConcurrentHashMap 2023-04-24 22:05:37 +01:00
Cyramek
4ce2850138 fix data desync 2023-04-23 14:35:17 +02:00
59 changed files with 2356 additions and 693 deletions

View File

@@ -82,6 +82,12 @@ allprojects {
// UltraEconomy
maven("https://repo.techscode.com/repository/maven-releases/")
// PlayerPoints
maven("https://repo.rosewooddev.io/repository/public/")
// Denizen
maven("https://maven.citizensnpcs.co/repo")
}
dependencies {

View File

@@ -25,8 +25,8 @@ import com.willfp.eco.core.gui.menu.MenuType;
import com.willfp.eco.core.gui.slot.SlotBuilder;
import com.willfp.eco.core.gui.slot.functional.SlotProvider;
import com.willfp.eco.core.items.TestableItem;
import com.willfp.eco.core.math.MathContext;
import com.willfp.eco.core.packet.Packet;
import com.willfp.eco.core.placeholder.context.PlaceholderContext;
import com.willfp.eco.core.proxy.ProxyFactory;
import com.willfp.eco.core.scheduling.Scheduler;
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
@@ -531,7 +531,7 @@ public interface Eco {
* @return The value of the expression, or zero if invalid.
*/
double evaluate(@NotNull String expression,
@NotNull MathContext context);
@NotNull PlaceholderContext context);
/**
* Get the menu a player currently has open.
@@ -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.
*

View File

@@ -366,7 +366,6 @@ public abstract class EcoPlugin extends JavaPlugin implements PluginLike, Regist
public final void onEnable() {
super.onEnable();
this.getLogger().info("");
this.getLogger().info("Loading " + this.getColor() + this.getName());
if (this.getResourceId() != 0 && !Eco.get().getEcoPlugin().getConfigYml().getBool("no-update-checker")) {
@@ -406,7 +405,9 @@ public abstract class EcoPlugin extends JavaPlugin implements PluginLike, Regist
this.loadedIntegrations.removeIf(pl -> pl.equalsIgnoreCase(this.getName()));
this.getLogger().info("Loaded integrations: " + String.join(", ", this.getLoadedIntegrations()));
if (!this.getLoadedIntegrations().isEmpty()) {
this.getLogger().info("Loaded integrations: " + String.join(", ", this.getLoadedIntegrations()));
}
Prerequisite.update();
@@ -428,17 +429,19 @@ public abstract class EcoPlugin extends JavaPlugin implements PluginLike, Regist
if (this.isSupportingExtensions()) {
this.getExtensionLoader().loadExtensions();
if (this.getExtensionLoader().getLoadedExtensions().isEmpty()) {
this.getLogger().info("&cNo extensions found");
} else {
this.getLogger().info("Extensions Loaded:");
this.getExtensionLoader().getLoadedExtensions().forEach(extension -> this.getLogger().info("- " + extension.getName() + " v" + extension.getVersion()));
if (!this.getExtensionLoader().getLoadedExtensions().isEmpty()) {
List<String> loadedExtensions = this.getExtensionLoader().getLoadedExtensions().stream().map(
extension -> extension.getName() + " v" + extension.getVersion()
).toList();
this.getLogger().info(
"Loaded extensions: " +
String.join(", ", loadedExtensions)
);
}
}
this.handleLifecycle(this.onEnable, this::handleEnable);
this.getLogger().info("");
}
/**

View File

@@ -6,6 +6,7 @@ import com.willfp.eco.core.config.Configs;
import com.willfp.eco.core.placeholder.AdditionalPlayer;
import com.willfp.eco.core.placeholder.InjectablePlaceholder;
import com.willfp.eco.core.placeholder.PlaceholderInjectable;
import com.willfp.eco.core.placeholder.context.PlaceholderContext;
import com.willfp.eco.util.NumberUtils;
import com.willfp.eco.util.StringUtils;
import org.bukkit.configuration.ConfigurationSection;
@@ -134,7 +135,7 @@ public interface Config extends Cloneable, PlaceholderInjectable {
* @return The computed value, or 0 if not found or invalid.
*/
default int getIntFromExpression(@NotNull String path) {
return getIntFromExpression(path, null);
return getIntFromExpression(path, PlaceholderContext.of(this));
}
/**
@@ -163,6 +164,18 @@ public interface Config extends Cloneable, PlaceholderInjectable {
return Double.valueOf(getDoubleFromExpression(path, player, additionalPlayers)).intValue();
}
/**
* Get a decimal value via a mathematical expression.
*
* @param path The key to fetch the value from.
* @param context The placeholder context.
* @return The computed value, or 0 if not found or invalid.
*/
default int getIntFromExpression(@NotNull String path,
@NotNull PlaceholderContext context) {
return Double.valueOf(getDoubleFromExpression(path, context)).intValue();
}
/**
* Get an integer from config.
@@ -256,6 +269,22 @@ public interface Config extends Cloneable, PlaceholderInjectable {
return getString(path, true, option);
}
/**
* Get a formatted string from config.
*
* @param path The key to fetch the value from.
* @param context The placeholder context.
* @return The found value, or an empty string if not found.
*/
@NotNull
default String getFormattedString(@NotNull String path,
@NotNull PlaceholderContext context) {
return Objects.requireNonNullElse(
getFormattedStringOrNull(path, context),
""
);
}
/**
* Get a string from config.
* <p>
@@ -288,7 +317,7 @@ public interface Config extends Cloneable, PlaceholderInjectable {
* Get a formatted string from config.
*
* @param path The key to fetch the value from.
* @return The found value, or an empty string if not found.
* @return The found value, or null if not found.
*/
@Nullable
default String getFormattedStringOrNull(@NotNull String path) {
@@ -300,7 +329,7 @@ public interface Config extends Cloneable, PlaceholderInjectable {
*
* @param path The key to fetch the value from.
* @param option The format option.
* @return The found value, or an empty string if not found.
* @return The found value, or null if not found.
*/
@Nullable
default String getFormattedStringOrNull(@NotNull String path,
@@ -308,6 +337,25 @@ public interface Config extends Cloneable, PlaceholderInjectable {
return getStringOrNull(path, true, option);
}
/**
* Get a formatted string from config.
*
* @param path The key to fetch the value from.
* @param context The placeholder context.
* @return The found value, or null if not found.
*/
@Nullable
default String getFormattedStringOrNull(@NotNull String path,
@NotNull PlaceholderContext context) {
String nullable = getStringOrNull(path);
if (nullable == null) {
return null;
}
return StringUtils.format(nullable, context.withInjectableContext(this));
}
/**
* Get a string from config.
* <p>
@@ -362,6 +410,24 @@ public interface Config extends Cloneable, PlaceholderInjectable {
return getStrings(path, true, option);
}
/**
* Get a list of strings from config.
* <p>
* Formatted.
*
* @param path The key to fetch the value from.
* @param context The placeholder context.
* @return The found value, or a blank {@link java.util.ArrayList} if not found.
*/
@NotNull
default List<String> getFormattedStrings(@NotNull String path,
@NotNull PlaceholderContext context) {
return Objects.requireNonNullElse(
getFormattedStringsOrNull(path, context),
new ArrayList<>()
);
}
/**
* Get a list of strings from config.
* <p>
@@ -418,6 +484,30 @@ public interface Config extends Cloneable, PlaceholderInjectable {
return getStringsOrNull(path, true, option);
}
/**
* Get a list of strings from config.
* <p>
* Formatted.
*
* @param path The key to fetch the value from.
* @param context The placeholder context.
* @return The found value, or null if not found.
*/
@Nullable
default List<String> getFormattedStringsOrNull(@NotNull String path,
@NotNull PlaceholderContext context) {
List<String> nullable = getStringsOrNull(path);
if (nullable == null) {
return null;
}
return StringUtils.formatList(
nullable,
context.withInjectableContext(this)
);
}
/**
* Get a list of strings from config.
* <p>
@@ -463,7 +553,7 @@ public interface Config extends Cloneable, PlaceholderInjectable {
* @return The computed value, or 0 if not found or invalid.
*/
default double getDoubleFromExpression(@NotNull String path) {
return getDoubleFromExpression(path, null);
return getDoubleFromExpression(path, PlaceholderContext.of(this));
}
/**
@@ -475,21 +565,38 @@ public interface Config extends Cloneable, PlaceholderInjectable {
*/
default double getDoubleFromExpression(@NotNull String path,
@Nullable Player player) {
return NumberUtils.evaluateExpression(this.getString(path), player, this);
return getDoubleFromExpression(path, player, Collections.emptyList());
}
/**
* Get a decimal value via a mathematical expression.
*
* @param path The key to fetch the value from.
* @param player The player to evaluate placeholders with respect to.
* @param path The key to fetch the value from.
* @param player The player to evaluate placeholders with respect to.
* @param additionalPlayers The additional players to evaluate placeholders with respect to.
* @return The computed value, or 0 if not found or invalid.
*/
default double getDoubleFromExpression(@NotNull String path,
@Nullable Player player,
@NotNull Collection<AdditionalPlayer> additionalPlayers) {
return NumberUtils.evaluateExpression(this.getString(path), player, this, additionalPlayers);
return getDoubleFromExpression(path, new PlaceholderContext(
player,
null,
this,
additionalPlayers
));
}
/**
* Get a decimal value via a mathematical expression.
*
* @param path The key to fetch the value from.
* @param context The placeholder context.
* @return The computed value, or 0 if not found or invalid.
*/
default double getDoubleFromExpression(@NotNull String path,
@NotNull PlaceholderContext context) {
return NumberUtils.evaluateExpression(this.getString(path), context.withInjectableContext(this));
}
/**

View File

@@ -114,7 +114,7 @@ public class IntegrationRegistry<T extends Integration> extends Registry<T> {
Eco.get().getEcoPlugin().getLogger().warning("Integration for " + integration.getPluginName() + " threw an exception!");
Eco.get().getEcoPlugin().getLogger().warning("The integration will be disabled.");
e.printStackTrace();
this.remove(integration.getPluginName());
this.remove(integration);
return defaultValue;
}
}

View File

@@ -9,7 +9,7 @@ import java.util.ArrayList;
import java.util.List;
/**
* Wrapper class for placeholder integrations.
* Wrapper class for arguments integrations.
*/
public interface PlaceholderIntegration extends Integration {
/**

View File

@@ -1,21 +1,15 @@
package com.willfp.eco.core.integrations.placeholder;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
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;
import com.willfp.eco.core.placeholder.DynamicPlaceholder;
import com.willfp.eco.core.placeholder.InjectablePlaceholder;
import com.willfp.eco.core.placeholder.Placeholder;
import com.willfp.eco.core.placeholder.PlaceholderInjectable;
import com.willfp.eco.core.placeholder.PlayerDynamicPlaceholder;
import com.willfp.eco.core.placeholder.PlayerPlaceholder;
import com.willfp.eco.core.placeholder.PlayerStaticPlaceholder;
import com.willfp.eco.core.placeholder.PlayerlessPlaceholder;
import com.willfp.eco.core.placeholder.StaticPlaceholder;
import com.willfp.eco.util.StringUtils;
import org.apache.commons.lang.Validate;
import com.willfp.eco.core.placeholder.RegistrablePlaceholder;
import com.willfp.eco.core.placeholder.context.PlaceholderContext;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -23,58 +17,34 @@ 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.Optional;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Class to handle placeholder integrations.
* Class to handle arguments integrations.
*/
public final class PlaceholderManager {
/**
* All registered placeholders.
*/
private static final Map<EcoPlugin, Map<Pattern, Placeholder>> REGISTERED_PLACEHOLDERS = new HashMap<>();
private static final DefaultMap<EcoPlugin, Set<Placeholder>> REGISTERED_PLACEHOLDERS = new DefaultMap<>(HashSet::new);
/**
* All registered placeholder integrations.
* All registered arguments integrations.
*/
private static final Set<PlaceholderIntegration> REGISTERED_INTEGRATIONS = new HashSet<>();
/**
* Placeholder Lookup Cache.
*/
private static final Cache<PlaceholderLookup, Optional<Placeholder>> PLACEHOLDER_LOOKUP_CACHE = Caffeine.newBuilder()
.expireAfterWrite(1, TimeUnit.SECONDS)
.build();
/**
* Placeholder Cache.
*/
private static final LoadingCache<EntryWithPlayer, String> PLACEHOLDER_CACHE = Caffeine.newBuilder()
.expireAfterWrite(50, TimeUnit.MILLISECONDS)
.build(key -> key.entry.getValue(key.player));
/**
* Dynamic Placeholder Cache.
*/
private static final LoadingCache<DynamicEntryWithPlayer, String> DYNAMIC_PLACEHOLDER_CACHE = Caffeine.newBuilder()
.expireAfterWrite(50, TimeUnit.MILLISECONDS)
.build(key -> key.entry.getValue(key.args, key.player));
/**
* The default PlaceholderAPI pattern; brought in for compatibility.
*/
private static final Pattern PATTERN = Pattern.compile("%([^% ]+)%");
/**
* Empty injectable object.
* Empty injectableContext object.
*/
public static final PlaceholderInjectable EMPTY_INJECTABLE = new PlaceholderInjectable() {
@Override
@@ -105,97 +75,68 @@ public final class PlaceholderManager {
}
/**
* Register a placeholder.
* Register a arguments.
*
* @param placeholder The placeholder to register.
* @param placeholder The arguments to register.
* @deprecated Use {@link #registerPlaceholder(RegistrablePlaceholder)} instead.
*/
@Deprecated(since = "6.56.0", forRemoval = true)
public static void registerPlaceholder(@NotNull final Placeholder placeholder) {
if (placeholder instanceof StaticPlaceholder || placeholder instanceof PlayerStaticPlaceholder) {
throw new IllegalArgumentException("Static placeholders cannot be registered!");
if (!(placeholder instanceof RegistrablePlaceholder)) {
throw new IllegalArgumentException("Placeholder must be RegistrablePlaceholder!");
}
Map<Pattern, Placeholder> pluginPlaceholders = REGISTERED_PLACEHOLDERS
.getOrDefault(placeholder.getPlugin(), new HashMap<>());
pluginPlaceholders.put(placeholder.getPattern(), placeholder);
REGISTERED_PLACEHOLDERS.put(placeholder.getPlugin(), pluginPlaceholders);
registerPlaceholder((RegistrablePlaceholder) placeholder);
}
/**
* Get the result of a placeholder with respect to a player.
* Register a arguments.
*
* @param player The player to get the result from.
* @param identifier The placeholder identifier.
* @return The value of the placeholder.
* @deprecated Specify a plugin to get the result from.
* @param placeholder The arguments to register.
*/
@Deprecated(since = "6.52.2", forRemoval = true)
@SuppressWarnings("unused")
public static String getResult(@Nullable final Player player,
@NotNull final String identifier) {
throw new UnsupportedOperationException("Please specify a plugin to get the result from!");
public static void registerPlaceholder(@NotNull final RegistrablePlaceholder placeholder) {
// Storing as immutable set leads to slower times to register placeholders, but much
// faster times to access registrations.
Set<Placeholder> 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 plugin The plugin for the placeholder.
* @return The value of the placeholder.
* @param identifier The placeholder args.
* @param plugin The plugin for the arguments.
* @return The value of the arguments.
*/
@NotNull
public static String getResult(@Nullable final Player player,
@NotNull final String identifier,
@NotNull final EcoPlugin plugin) {
Validate.notNull(plugin, "Plugin cannot be null!");
@Nullable final EcoPlugin plugin) {
return Objects.requireNonNullElse(
getResult(
plugin,
identifier,
new PlaceholderContext(player)
),
""
);
}
// This is really janky, and it sucks, but it works so?
// Compensating for regex being slow so that's why we get it.
Placeholder placeholder = PLACEHOLDER_LOOKUP_CACHE.get(
new PlaceholderLookup(identifier, plugin),
(it) -> {
// I hate the streams API.
return REGISTERED_PLACEHOLDERS
.getOrDefault(plugin, new HashMap<>())
.entrySet()
.stream().filter(entry -> entry.getKey().matcher(identifier).matches())
.map(Map.Entry::getValue)
.findFirst();
}
).orElse(null);
if (placeholder == null) {
return "";
}
/*
This code here is *really* not very good. It's mega externalized logic hacked
together and made worse by the addition of dynamic placeholders. But it works,
and it means I don't have to rewrite the whole placeholder system. So it's
good enough for me.
*/
if (placeholder instanceof PlayerPlaceholder playerPlaceholder) {
if (player == null) {
return "";
} else {
return PLACEHOLDER_CACHE.get(new EntryWithPlayer(playerPlaceholder, player));
}
} else if (placeholder instanceof PlayerlessPlaceholder playerlessPlaceholder) {
return playerlessPlaceholder.getValue();
} else if (placeholder instanceof PlayerDynamicPlaceholder playerDynamicPlaceholder) {
if (player == null) {
return "";
} else {
return DYNAMIC_PLACEHOLDER_CACHE.get(new DynamicEntryWithPlayer(playerDynamicPlaceholder, identifier, player));
}
} else if (placeholder instanceof DynamicPlaceholder dynamicPlaceholder) {
return dynamicPlaceholder.getValue(identifier);
} else {
return "";
}
/**
* Get the result of a placeholder given a plugin and arguments.
*
* @param plugin The plugin for the placeholder.
* @param args The arguments.
* @param context The context.
* @return The value of the arguments.
*/
@Nullable
public static String getResult(@Nullable final EcoPlugin plugin,
@NotNull final String args,
@NotNull final PlaceholderContext context) {
return Eco.get().getPlaceholderValue(plugin, args, context);
}
/**
@@ -204,7 +145,10 @@ public final class PlaceholderManager {
* @param text The text that may contain placeholders to translate.
* @param player The player to translate the placeholders with respect to.
* @return The text, translated.
* @deprecated Use {@link #translatePlaceholders(String, PlaceholderContext)} instead.
*/
@Deprecated(since = "6.56.0", forRemoval = true)
@NotNull
public static String translatePlaceholders(@NotNull final String text,
@Nullable final Player player) {
return translatePlaceholders(text, player, EMPTY_INJECTABLE);
@@ -215,9 +159,12 @@ public final class PlaceholderManager {
*
* @param text The text that may contain placeholders to translate.
* @param player The player to translate the placeholders with respect to.
* @param context The injectable context.
* @param context The injectableContext parseContext.
* @return The text, translated.
* @deprecated Use {@link #translatePlaceholders(String, PlaceholderContext)} instead.
*/
@Deprecated(since = "6.56.0", forRemoval = true)
@NotNull
public static String translatePlaceholders(@NotNull final String text,
@Nullable final Player player,
@NotNull final PlaceholderInjectable context) {
@@ -229,84 +176,50 @@ public final class PlaceholderManager {
*
* @param text The text that may contain placeholders to translate.
* @param player The player to translate the placeholders with respect to.
* @param context The injectable context.
* @param context The injectableContext parseContext.
* @param additionalPlayers Additional players to translate placeholders for.
* @return The text, translated.
* @deprecated Use {@link #translatePlaceholders(String, PlaceholderContext)} instead.
*/
@Deprecated(since = "6.56.0", forRemoval = true)
@NotNull
public static String translatePlaceholders(@NotNull final String text,
@Nullable final Player player,
@NotNull final PlaceholderInjectable context,
@NotNull final Collection<AdditionalPlayer> additionalPlayers) {
String processed = text;
return translatePlaceholders(
text,
new PlaceholderContext(
player,
null,
context,
additionalPlayers
)
);
}
/*
/**
* Translate all placeholders without a placeholder context.
*
* @param text The text that may contain placeholders to translate.
* @return The text, translated.
*/
@NotNull
public static String translatePlaceholders(@NotNull final String text) {
return Eco.get().translatePlaceholders(text, PlaceholderContext.EMPTY);
}
Why am I doing statics at the start, but player statics 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.
*/
for (InjectablePlaceholder injection : context.getPlaceholderInjections()) {
if (injection instanceof StaticPlaceholder placeholder) {
processed = processed.replace("%" + placeholder.getIdentifier() + "%", placeholder.getValue());
} else if (injection instanceof PlayerStaticPlaceholder placeholder && player != null) {
processed = processed.replace("%" + placeholder.getIdentifier() + "%", placeholder.getValue(player));
}
}
// Prevent running 2 scans if there are no additional players.
if (!additionalPlayers.isEmpty()) {
List<String> found = findPlaceholdersIn(text);
for (AdditionalPlayer additionalPlayer : additionalPlayers) {
for (String placeholder : found) {
String prefix = "%" + additionalPlayer.getIdentifier() + "_";
if (placeholder.startsWith(prefix)) {
processed = processed.replace(
placeholder,
translatePlaceholders(
"%" + StringUtils.removePrefix(prefix, placeholder),
additionalPlayer.getPlayer()
)
);
}
}
}
}
// Only run jank code if there are no integrations.
if (REGISTERED_INTEGRATIONS.isEmpty()) {
processed = setWithoutIntegration(processed, player);
}
for (PlaceholderIntegration integration : REGISTERED_INTEGRATIONS) {
processed = integration.translate(processed, player);
}
// DON'T REMOVE THIS, IT'S NOT DUPLICATE CODE.
for (InjectablePlaceholder injection : context.getPlaceholderInjections()) {
// Do I know this is a bad way of doing this? Yes.
if (injection instanceof PlayerStaticPlaceholder placeholder && player != null) {
processed = processed.replace("%" + placeholder.getIdentifier() + "%", placeholder.getValue(player));
}
}
return processed;
/**
* Translate all placeholders in a translation context.
*
* @param text The text that may contain placeholders to translate.
* @param context The translation context.
* @return The text, translated.
*/
@NotNull
public static String translatePlaceholders(@NotNull final String text,
@NotNull final PlaceholderContext context) {
return Eco.get().translatePlaceholders(text, context);
}
/**
@@ -331,108 +244,22 @@ public final class PlaceholderManager {
}
/**
* Set placeholders without any integrations.
* <p>
* This is fallback if for some reason you don't have PAPI installed.
* It's a cut-down version of the actual PAPI code, and I don't
* really know how it works.
* <p>
* Original source
* <a href="https://github.com/PlaceholderAPI/PlaceholderAPI/blob/master/src/main/java/me/clip/placeholderapi/replacer/CharsReplacer.java">here</a>.
* Get all registered placeholder integrations.
*
* @param text The text.
* @param player The player.
* @return The text.
* @return The integrations.
*/
private static String setWithoutIntegration(@NotNull final String text,
@Nullable final Player player) {
char[] chars = text.toCharArray();
StringBuilder builder = new StringBuilder(text.length());
StringBuilder identifier = new StringBuilder();
StringBuilder parameters = new StringBuilder();
for (int i = 0; i < chars.length; i++) {
char currentChar = chars[i];
if (currentChar == '%' && i + 1 < chars.length) {
boolean identified = false;
boolean badPlaceholder = true;
boolean hadSpace = false;
while (true) {
i++;
if (i >= chars.length) {
break;
}
char p = chars[i];
if (p == ' ' && !identified) {
hadSpace = true;
break;
}
if (p == '%') {
badPlaceholder = false;
break;
}
if (p == '_' && !identified) {
identified = true;
} else if (identified) {
parameters.append(p);
} else {
identifier.append(p);
}
}
String pluginName = identifier.toString().toLowerCase();
EcoPlugin plugin = EcoPlugin.getPlugin(pluginName);
String placeholderIdentifier = parameters.toString();
identifier.setLength(0);
parameters.setLength(0);
if (badPlaceholder) {
builder.append('%').append(pluginName);
if (identified) {
builder.append('_').append(placeholderIdentifier);
}
if (hadSpace) {
builder.append(' ');
}
} else {
if (plugin == null) {
builder.append('%').append(pluginName);
if (identified) {
builder.append('_');
}
builder.append(placeholderIdentifier).append('%');
} else {
builder.append(getResult(player, placeholderIdentifier, plugin));
}
}
} else {
builder.append(currentChar);
}
}
return builder.toString();
public static Set<PlaceholderIntegration> getRegisteredIntegrations() {
return Set.copyOf(REGISTERED_INTEGRATIONS);
}
private record PlaceholderLookup(@NotNull String identifier,
@Nullable EcoPlugin plugin) {
}
private record EntryWithPlayer(@NotNull PlayerPlaceholder entry,
@NotNull Player player) {
}
private record DynamicEntryWithPlayer(@NotNull PlayerDynamicPlaceholder entry,
@NotNull String args,
@NotNull Player player) {
/**
* Get all registered placeholders for a plugin.
*
* @param plugin The plugin.
* @return The placeholders.
*/
public static Set<Placeholder> getRegisteredPlaceholders(@NotNull final EcoPlugin plugin) {
return REGISTERED_PLACEHOLDERS.get(plugin);
}
private PlaceholderManager() {

View File

@@ -3,27 +3,25 @@ package com.willfp.eco.core.math;
import com.willfp.eco.core.integrations.placeholder.PlaceholderManager;
import com.willfp.eco.core.placeholder.AdditionalPlayer;
import com.willfp.eco.core.placeholder.PlaceholderInjectable;
import com.willfp.eco.core.placeholder.context.PlaceholderContext;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.Objects;
/**
* Represents a context to do math in.
* Represents a context to parse math in.
*
* @param injectableContext The PlaceholderInjectable context.
* @param player The player.
* @param additionalPlayers The additional players.
* @deprecated Use {@link PlaceholderContext} instead.
*/
public record MathContext(
@NotNull PlaceholderInjectable injectableContext,
@Nullable Player player,
@NotNull Collection<AdditionalPlayer> additionalPlayers
) {
@SuppressWarnings("DeprecatedIsStillUsed")
@Deprecated(since = "6.56.0", forRemoval = true)
public class MathContext {
/**
* Empty math context.
* Returns an empty math parseContext.
*/
public static final MathContext EMPTY = new MathContext(
PlaceholderManager.EMPTY_INJECTABLE,
@@ -32,9 +30,157 @@ public record MathContext(
);
/**
* Create MathContext of a PlaceholderInjectable context.
* The PlaceholderInjectable parse context.
*/
@NotNull
private final PlaceholderInjectable injectableContext;
/**
* The player.
*/
@Nullable
private final Player player;
/**
* The additional players.
*/
@NotNull
private final Collection<AdditionalPlayer> additionalPlayers;
/**
* Constructs a new MathContext with the given parameters.
*
* @param injectableContext The PlaceholderInjectable context.
* @param injectableContext The PlaceholderInjectable parseContext.
* @param player The player.
* @param additionalPlayers The additional players.
*/
public MathContext(@NotNull PlaceholderInjectable injectableContext,
@Nullable Player player,
@NotNull Collection<AdditionalPlayer> additionalPlayers) {
this.injectableContext = injectableContext;
this.player = player;
this.additionalPlayers = additionalPlayers;
}
/**
* Returns the PlaceholderInjectable parse context.
* <p>
* Duplicate method because MathContext used to be a record.
*
* @return The injectable context.
* @deprecated Use {@link #getInjectableContext()} instead.
*/
@Deprecated(since = "6.56.0", forRemoval = true)
public PlaceholderInjectable injectableContext() {
return injectableContext;
}
/**
* Returns the PlaceholderInjectable parse context.
*
* @return The injectable context.
*/
@NotNull
public PlaceholderInjectable getInjectableContext() {
return injectableContext;
}
/**
* Returns the player.
* <p>
* Duplicate method because MathContext used to be a record.
*
* @return The player.
* @deprecated Use {@link #getPlayer()} instead.
*/
@Deprecated(since = "6.56.0", forRemoval = true)
@Nullable
public Player player() {
return player;
}
/**
* Returns the player.
*
* @return The player.
*/
@Nullable
public Player getPlayer() {
return player;
}
/**
* Returns the additional players.
* <p>
* Duplicate method because MathContext used to be a record.
*
* @return The additional players.
* @deprecated Use {@link #getAdditionalPlayers()} instead.
*/
@Deprecated(since = "6.56.0", forRemoval = true)
@NotNull
public Collection<AdditionalPlayer> additionalPlayers() {
return additionalPlayers;
}
/**
* Returns the additional players.
*
* @return The additional players.
*/
@NotNull
public Collection<AdditionalPlayer> getAdditionalPlayers() {
return additionalPlayers;
}
/**
* Convert to PlaceholderContext.
*
* @return The PlaceholderContext.
*/
@NotNull
public PlaceholderContext toPlaceholderContext() {
return new PlaceholderContext(
this.player,
null,
this.injectableContext,
this.additionalPlayers
);
}
@Override
public String toString() {
return "MathContext{" +
"injectableContext=" + injectableContext +
", player=" + player +
", additionalPlayers=" + additionalPlayers +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof MathContext that)) {
return false;
}
return injectableContext.equals(that.injectableContext) &&
Objects.equals(player, that.player) &&
additionalPlayers.equals(that.additionalPlayers);
}
@Override
public int hashCode() {
return Objects.hash(injectableContext, player, additionalPlayers);
}
/**
* Create MathContext of a PlaceholderInjectable parseContext.
*
* @param injectableContext The PlaceholderInjectable parseContext.
* @return The MathContext.
*/
public static MathContext of(@NotNull final PlaceholderInjectable injectableContext) {
@@ -48,16 +194,16 @@ public record MathContext(
/**
* Copy a MathContext with a player.
*
* @param context The context.
* @param context The parseContext.
* @param player The player.
* @return The new MathContext.
*/
public static MathContext copyWithPlayer(@NotNull final MathContext context,
@Nullable final Player player) {
return new MathContext(
context.injectableContext(),
context.injectableContext,
player,
context.additionalPlayers()
context.additionalPlayers
);
}
}

View File

@@ -1,7 +1,7 @@
package com.willfp.eco.core.placeholder;
import com.willfp.eco.core.EcoPlugin;
import com.willfp.eco.core.integrations.placeholder.PlaceholderManager;
import com.willfp.eco.core.placeholder.context.PlaceholderContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -12,24 +12,24 @@ import java.util.regex.Pattern;
/**
* A placeholder that does not require a player and supports dynamic styles.
*/
public final class DynamicPlaceholder implements Placeholder {
public final class DynamicPlaceholder implements RegistrablePlaceholder {
/**
* The placeholder pattern.
* The arguments pattern.
*/
private final Pattern pattern;
/**
* The function to retrieve the output of the placeholder.
* The function to retrieve the output of the arguments.
*/
private final Function<String, String> function;
private final Function<@NotNull String, @Nullable String> function;
/**
* The plugin for the placeholder.
* The plugin for the arguments.
*/
private final EcoPlugin plugin;
/**
* Create a new dynamic placeholder.
* Create a new dynamic arguments.
*
* @param plugin The plugin.
* @param pattern The pattern.
@@ -37,31 +37,33 @@ public final class DynamicPlaceholder implements Placeholder {
*/
public DynamicPlaceholder(@NotNull final EcoPlugin plugin,
@NotNull final Pattern pattern,
@NotNull final Function<String, String> function) {
@NotNull final Function<@NotNull String, @Nullable String> function) {
this.plugin = plugin;
this.pattern = pattern;
this.function = function;
}
/**
* Get the value of the placeholder.
*
* @param args The args.
* @return The value.
*/
@NotNull
public String getValue(@NotNull final String args) {
@Override
@Nullable
public String getValue(@NotNull final String args,
@NotNull final PlaceholderContext context) {
return function.apply(args);
}
/**
* Register the placeholder.
* Get the value of the arguments.
*
* @return The placeholder.
* @param args The args.
* @return The value.
* @deprecated Use {@link #getValue(String, PlaceholderContext)} instead.
*/
public DynamicPlaceholder register() {
PlaceholderManager.registerPlaceholder(this);
return this;
@Deprecated(since = "6.56.0", forRemoval = true)
@NotNull
public String getValue(@NotNull final String args) {
return Objects.requireNonNullElse(
function.apply(args),
""
);
}
@Override
@@ -69,18 +71,17 @@ public final class DynamicPlaceholder implements Placeholder {
return this.plugin;
}
@Override
@Deprecated
public @NotNull String getIdentifier() {
return "dynamic";
}
@NotNull
@Override
public Pattern getPattern() {
return this.pattern;
}
@Override
public @NotNull DynamicPlaceholder register() {
return (DynamicPlaceholder) RegistrablePlaceholder.super.register();
}
@Override
public boolean equals(@Nullable final Object o) {
if (this == o) {
@@ -97,6 +98,6 @@ public final class DynamicPlaceholder implements Placeholder {
@Override
public int hashCode() {
return Objects.hash(this.getIdentifier(), this.getPlugin());
return Objects.hash(this.getPattern(), this.getPlugin());
}
}

View File

@@ -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 sealed interface InjectablePlaceholder extends Placeholder permits PlayerStaticPlaceholder, StaticPlaceholder {
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;
}
}

View File

@@ -1,38 +1,64 @@
package com.willfp.eco.core.placeholder;
import com.willfp.eco.core.EcoPlugin;
import com.willfp.eco.core.placeholder.context.PlaceholderContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.regex.Pattern;
/**
* A placeholder represents a string that can hold a value.
*/
public sealed interface Placeholder permits PlayerPlaceholder, PlayerlessPlaceholder,
DynamicPlaceholder, PlayerDynamicPlaceholder, InjectablePlaceholder {
public interface Placeholder {
/**
* Get the plugin that holds the placeholder.
* Get the plugin that holds the arguments.
*
* @return The plugin.
*/
@NotNull
@Nullable
EcoPlugin getPlugin();
/**
* Get the identifier for the placeholder.
* Get the value of the arguments.
*
* @return The identifier.
* @param args The args.
* @param context The context.
* @return The value.
*/
@NotNull
String getIdentifier();
@Nullable
String getValue(@NotNull String args,
@NotNull PlaceholderContext context);
/**
* Get the pattern for the placeholder.
* Get the pattern for the arguments.
*
* @return The pattern.
*/
@NotNull
default Pattern getPattern() {
return Pattern.compile(this.getIdentifier());
Pattern getPattern();
/**
* Try to translate all instances of this placeholder in text quickly.
*
* @param text The text to translate.
* @param context The context.
* @return The translated text.
*/
default String tryTranslateQuickly(@NotNull final String text,
@NotNull final PlaceholderContext context) {
return text;
}
/**
* Get the identifier for the arguments.
*
* @return The identifier.
* @deprecated Some arguments may not have an identifier. Use {@link #getPattern()} instead.
*/
@Deprecated(since = "6.56.0", forRemoval = true)
@NotNull
default String getIdentifier() {
return this.getPattern().pattern();
}
}

View File

@@ -9,7 +9,7 @@ import java.util.List;
*/
public interface PlaceholderInjectable {
/**
* Inject placeholder.
* Inject arguments.
*
* @param placeholders The placeholders.
*/
@@ -18,7 +18,7 @@ public interface PlaceholderInjectable {
}
/**
* Inject placeholder.
* Inject arguments.
*
* @param placeholders The placeholders.
*/
@@ -29,7 +29,7 @@ public interface PlaceholderInjectable {
/**
* Inject placeholders.
* <p>
* When implementing a PlaceholderInjectable object, override this method.
* If a placeholder already has the same pattern, it should be replaced.
*
* @param placeholders The placeholders.
*/
@@ -43,7 +43,7 @@ public interface PlaceholderInjectable {
/**
* Get injected placeholders.
* <p>
* Override this method in implementations.
* This method should always return an immutable list.
*
* @return Injected placeholders.
*/

View File

@@ -1,7 +1,7 @@
package com.willfp.eco.core.placeholder;
import com.willfp.eco.core.EcoPlugin;
import com.willfp.eco.core.integrations.placeholder.PlaceholderManager;
import com.willfp.eco.core.placeholder.context.PlaceholderContext;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -11,26 +11,26 @@ import java.util.function.BiFunction;
import java.util.regex.Pattern;
/**
* A placeholder that does not require a player and supports dynamic styles.
* A arguments that does not require a player and supports dynamic styles.
*/
public final class PlayerDynamicPlaceholder implements Placeholder {
public final class PlayerDynamicPlaceholder implements RegistrablePlaceholder {
/**
* The placeholder pattern.
* The arguments pattern.
*/
private final Pattern pattern;
/**
* The function to retrieve the output of the placeholder.
* The function to retrieve the output of the arguments.
*/
private final BiFunction<String, Player, String> function;
private final BiFunction<@NotNull String, @NotNull Player, @Nullable String> function;
/**
* The plugin for the placeholder.
* The plugin for the arguments.
*/
private final EcoPlugin plugin;
/**
* Create a new dynamic placeholder.
* Create a new dynamic arguments.
*
* @param plugin The plugin.
* @param pattern The pattern.
@@ -38,33 +38,40 @@ public final class PlayerDynamicPlaceholder implements Placeholder {
*/
public PlayerDynamicPlaceholder(@NotNull final EcoPlugin plugin,
@NotNull final Pattern pattern,
@NotNull final BiFunction<String, Player, String> function) {
@NotNull final BiFunction<@NotNull String, @NotNull Player, @Nullable String> function) {
this.plugin = plugin;
this.pattern = pattern;
this.function = function;
}
/**
* Get the value of the placeholder.
*
* @param args The args.
* @param player The player.
* @return The value.
*/
@NotNull
public String getValue(@NotNull final String args,
@NotNull final Player player) {
@Override
public @Nullable String getValue(@NotNull final String args,
@NotNull final PlaceholderContext context) {
Player player = context.getPlayer();
if (player == null) {
return null;
}
return function.apply(args, player);
}
/**
* Register the placeholder.
* Get the value of the arguments.
*
* @return The placeholder.
* @param args The args.
* @param player The player.
* @return The value.
* @deprecated Use {@link #getValue(String, PlaceholderContext)} instead.
*/
public PlayerDynamicPlaceholder register() {
PlaceholderManager.registerPlaceholder(this);
return this;
@Deprecated(since = "6.56.0", forRemoval = true)
@NotNull
public String getValue(@NotNull final String args,
@NotNull final Player player) {
return Objects.requireNonNullElse(
function.apply(args, player),
""
);
}
@Override
@@ -72,18 +79,17 @@ public final class PlayerDynamicPlaceholder implements Placeholder {
return this.plugin;
}
@Override
@Deprecated
public @NotNull String getIdentifier() {
return "dynamic";
}
@NotNull
@Override
public Pattern getPattern() {
return this.pattern;
}
@Override
public @NotNull PlayerDynamicPlaceholder register() {
return (PlayerDynamicPlaceholder) RegistrablePlaceholder.super.register();
}
@Override
public boolean equals(@Nullable final Object o) {
if (this == o) {
@@ -100,6 +106,6 @@ public final class PlayerDynamicPlaceholder implements Placeholder {
@Override
public int hashCode() {
return Objects.hash(this.getIdentifier(), this.getPlugin());
return Objects.hash(this.getPattern(), this.getPlugin());
}
}

View File

@@ -1,7 +1,7 @@
package com.willfp.eco.core.placeholder;
import com.willfp.eco.core.EcoPlugin;
import com.willfp.eco.core.integrations.placeholder.PlaceholderManager;
import com.willfp.eco.core.placeholder.context.PlaceholderContext;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -11,31 +11,26 @@ import java.util.function.Function;
import java.util.regex.Pattern;
/**
* A placeholder that requires a player.
* A arguments that requires a player.
*/
public final class PlayerPlaceholder implements Placeholder {
public final class PlayerPlaceholder implements RegistrablePlaceholder {
/**
* The name of the placeholder.
*/
private final String identifier;
/**
* The placeholder pattern.
* The arguments pattern.
*/
private final Pattern pattern;
/**
* The function to retrieve the output of the placeholder given a player.
* The function to retrieve the output of the arguments given a player.
*/
private final Function<Player, String> function;
private final Function<@NotNull Player, @Nullable String> function;
/**
* The plugin for the placeholder.
* The plugin for the arguments.
*/
private final EcoPlugin plugin;
/**
* Create a new player placeholder.
* Create a new player arguments.
*
* @param plugin The plugin.
* @param identifier The identifier.
@@ -43,32 +38,38 @@ public final class PlayerPlaceholder implements Placeholder {
*/
public PlayerPlaceholder(@NotNull final EcoPlugin plugin,
@NotNull final String identifier,
@NotNull final Function<Player, String> function) {
@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;
}
/**
* Get the value of the placeholder for a given player.
*
* @param player The player.
* @return The value.
*/
@NotNull
public String getValue(@NotNull final Player player) {
@Override
public @Nullable String getValue(@NotNull final String args,
@NotNull final PlaceholderContext context) {
Player player = context.getPlayer();
if (player == null) {
return null;
}
return function.apply(player);
}
/**
* Register the placeholder.
* Get the value of the arguments for a given player.
*
* @return The placeholder.
* @param player The player.
* @return The value.
* @deprecated Use {@link #getValue(String, PlaceholderContext)} instead.
*/
public PlayerPlaceholder register() {
PlaceholderManager.registerPlaceholder(this);
return this;
@Deprecated(since = "6.56.0", forRemoval = true)
@NotNull
public String getValue(@NotNull final Player player) {
return Objects.requireNonNullElse(
function.apply(player),
""
);
}
@Override
@@ -76,17 +77,17 @@ public final class PlayerPlaceholder implements Placeholder {
return this.plugin;
}
@Override
public @NotNull String getIdentifier() {
return this.identifier;
}
@NotNull
@Override
public Pattern getPattern() {
return this.pattern;
}
@Override
public @NotNull PlayerPlaceholder register() {
return (PlayerPlaceholder) RegistrablePlaceholder.super.register();
}
@Override
public boolean equals(@Nullable final Object o) {
if (this == o) {
@@ -95,12 +96,12 @@ public final class PlayerPlaceholder implements Placeholder {
if (!(o instanceof PlayerPlaceholder that)) {
return false;
}
return Objects.equals(this.getIdentifier(), that.getIdentifier())
return Objects.equals(this.getPattern(), that.getPattern())
&& Objects.equals(this.getPlugin(), that.getPlugin());
}
@Override
public int hashCode() {
return Objects.hash(this.getIdentifier(), this.getPlugin());
return Objects.hash(this.getPattern(), this.getPlugin());
}
}

View File

@@ -1,5 +1,7 @@
package com.willfp.eco.core.placeholder;
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;
@@ -13,47 +15,72 @@ import java.util.regex.Pattern;
*/
public final class PlayerStaticPlaceholder implements InjectablePlaceholder {
/**
* The name of the placeholder.
* The identifier.
*/
private final String identifier;
/**
* The placeholder pattern.
* The arguments pattern.
*/
private final Pattern pattern;
/**
* The function to retrieve the output of the placeholder.
* The function to retrieve the output of the arguments.
*/
private final Function<Player, String> function;
private final Function<@NotNull Player, @Nullable String> function;
/**
* Create a new player placeholder.
* Create a new player arguments.
*
* @param identifier The identifier.
* @param function The function to retrieve the value.
*/
public PlayerStaticPlaceholder(@NotNull final String identifier,
@NotNull final Function<Player, String> function) {
this.identifier = identifier;
this.pattern = Pattern.compile(identifier);
@NotNull final Function<@NotNull Player, @Nullable String> function) {
this.identifier = "%" + identifier + "%";
this.pattern = Pattern.compile(identifier, Pattern.LITERAL);
this.function = function;
}
@Override
public @Nullable String getValue(@NotNull final String args,
@NotNull final PlaceholderContext context) {
Player player = context.getPlayer();
if (player == null) {
return null;
}
return this.getValue(player);
}
/**
* Get the value of the placeholder.
* Get the value of the arguments.
*
* @param player The player.
* @return The value.
* @deprecated Use {@link #getValue(String, PlaceholderContext)} instead.
*/
@Deprecated(since = "6.56.0", forRemoval = true)
@NotNull
public String getValue(@NotNull final Player player) {
return function.apply(player);
return Objects.requireNonNullElse(
function.apply(player),
""
);
}
@Override
public @NotNull String getIdentifier() {
return this.identifier;
public String tryTranslateQuickly(@NotNull final String text,
@NotNull final PlaceholderContext context) {
return StringUtils.replaceQuickly(
text,
this.identifier,
Objects.requireNonNullElse(
this.getValue(identifier, context),
""
)
);
}
@NotNull
@@ -70,11 +97,11 @@ public final class PlayerStaticPlaceholder implements InjectablePlaceholder {
if (!(o instanceof PlayerStaticPlaceholder that)) {
return false;
}
return Objects.equals(this.getIdentifier(), that.getIdentifier());
return Objects.equals(this.getPattern(), that.getPattern());
}
@Override
public int hashCode() {
return Objects.hash(this.getIdentifier());
return Objects.hash(this.getPattern());
}
}

View File

@@ -1,7 +1,7 @@
package com.willfp.eco.core.placeholder;
import com.willfp.eco.core.EcoPlugin;
import com.willfp.eco.core.integrations.placeholder.PlaceholderManager;
import com.willfp.eco.core.placeholder.context.PlaceholderContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -10,31 +10,26 @@ import java.util.function.Supplier;
import java.util.regex.Pattern;
/**
* A placeholder that does not require a player.
* A arguments that does not require a player.
*/
public final class PlayerlessPlaceholder implements Placeholder {
public final class PlayerlessPlaceholder implements RegistrablePlaceholder {
/**
* The placeholder identifier.
*/
private final String identifier;
/**
* The placeholder pattern.
* The arguments pattern.
*/
private final Pattern pattern;
/**
* The function to retrieve the output of the placeholder.
* The function to retrieve the output of the arguments.
*/
private final Supplier<String> function;
private final Supplier<@Nullable String> function;
/**
* The plugin for the placeholder.
* The plugin for the arguments.
*/
private final EcoPlugin plugin;
/**
* Create a new player placeholder.
* Create a new player arguments.
*
* @param plugin The plugin.
* @param identifier The identifier.
@@ -42,30 +37,31 @@ public final class PlayerlessPlaceholder implements Placeholder {
*/
public PlayerlessPlaceholder(@NotNull final EcoPlugin plugin,
@NotNull final String identifier,
@NotNull final Supplier<String> function) {
@NotNull final Supplier<@Nullable String> function) {
this.plugin = plugin;
this.identifier = identifier;
this.pattern = Pattern.compile(identifier);
this.pattern = Pattern.compile(identifier, Pattern.LITERAL);
this.function = function;
}
/**
* Get the value of the placeholder.
*
* @return The value.
*/
public String getValue() {
@Override
public @Nullable String getValue(@NotNull final String args,
@NotNull final PlaceholderContext context) {
return function.get();
}
/**
* Register the placeholder.
* Get the value of the arguments.
*
* @return The placeholder.
* @return The value.
* @deprecated Use {@link #getValue(String, PlaceholderContext)} instead.
*/
public PlayerlessPlaceholder register() {
PlaceholderManager.registerPlaceholder(this);
return this;
@Deprecated(since = "6.56.0", forRemoval = true)
@NotNull
public String getValue() {
return Objects.requireNonNullElse(
this.function.get(),
""
);
}
@Override
@@ -73,17 +69,17 @@ public final class PlayerlessPlaceholder implements Placeholder {
return this.plugin;
}
@Override
public @NotNull String getIdentifier() {
return this.identifier;
}
@NotNull
@Override
public Pattern getPattern() {
return this.pattern;
}
@Override
public @NotNull PlayerlessPlaceholder register() {
return (PlayerlessPlaceholder) RegistrablePlaceholder.super.register();
}
@Override
public boolean equals(@Nullable final Object o) {
if (this == o) {
@@ -92,12 +88,12 @@ public final class PlayerlessPlaceholder implements Placeholder {
if (!(o instanceof PlayerlessPlaceholder that)) {
return false;
}
return Objects.equals(this.getIdentifier(), that.getIdentifier())
return Objects.equals(this.getPattern(), that.getPattern())
&& Objects.equals(this.getPlugin(), that.getPlugin());
}
@Override
public int hashCode() {
return Objects.hash(this.getIdentifier(), this.getPlugin());
return Objects.hash(this.getPattern(), this.getPlugin());
}
}

View File

@@ -0,0 +1,31 @@
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;
/**
* 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.
*
* @return The arguments.
*/
@NotNull
default RegistrablePlaceholder register() {
PlaceholderManager.registerPlaceholder(this);
return this;
}
}

View File

@@ -1,5 +1,7 @@
package com.willfp.eco.core.placeholder;
import com.willfp.eco.core.placeholder.context.PlaceholderContext;
import com.willfp.eco.util.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -8,50 +10,66 @@ import java.util.function.Supplier;
import java.util.regex.Pattern;
/**
* A placeholder that cannot be registered, and exists purely in injection.
* A arguments that cannot be registered, and exists purely in injection.
*/
public final class StaticPlaceholder implements InjectablePlaceholder {
/**
* The name of the placeholder.
* The name of the arguments.
*/
private final String identifier;
/**
* The placeholder pattern.
* The arguments pattern.
*/
private final Pattern pattern;
/**
* The function to retrieve the output of the placeholder.
* The function to retrieve the output of the arguments.
*/
private final Supplier<String> function;
private final Supplier<@Nullable String> function;
/**
* Create a new player placeholder.
* Create a new player arguments.
*
* @param identifier The identifier.
* @param function The function to retrieve the value.
*/
public StaticPlaceholder(@NotNull final String identifier,
@NotNull final Supplier<String> function) {
this.identifier = identifier;
this.pattern = Pattern.compile(identifier);
@NotNull final Supplier<@Nullable String> function) {
this.identifier = "%" + identifier + "%";
this.pattern = Pattern.compile(identifier, Pattern.LITERAL);
this.function = function;
}
/**
* Get the value of the placeholder.
*
* @return The value.
*/
@NotNull
public String getValue() {
@Override
public @Nullable String getValue(@NotNull final String args,
@NotNull final PlaceholderContext context) {
return function.get();
}
/**
* Get the value of the arguments.
*
* @return The value.
* @deprecated Use {@link #getValue(String, PlaceholderContext)} instead.
*/
@Deprecated(since = "6.56.0", forRemoval = true)
@NotNull
public String getValue() {
return Objects.requireNonNullElse(
function.get(),
""
);
}
@Override
public @NotNull String getIdentifier() {
return this.identifier;
public String tryTranslateQuickly(@NotNull final String text,
@NotNull final PlaceholderContext context) {
return StringUtils.replaceQuickly(
text,
this.identifier,
Objects.requireNonNullElse(this.getValue(this.identifier, context), "")
);
}
@NotNull
@@ -68,11 +86,11 @@ public final class StaticPlaceholder implements InjectablePlaceholder {
if (!(o instanceof StaticPlaceholder that)) {
return false;
}
return Objects.equals(this.getIdentifier(), that.getIdentifier());
return Objects.equals(this.getPattern(), that.getPattern());
}
@Override
public int hashCode() {
return Objects.hash(this.getIdentifier());
return Objects.hash(this.getPattern());
}
}

View File

@@ -0,0 +1,70 @@
package com.willfp.eco.core.placeholder.context;
import com.willfp.eco.core.placeholder.InjectablePlaceholder;
import com.willfp.eco.core.placeholder.PlaceholderInjectable;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* A merged injectable context.
*/
public class MergedInjectableContext implements PlaceholderInjectable {
/**
* The base context.
*/
private final PlaceholderInjectable baseContext;
/**
* The additional context.
*/
private final PlaceholderInjectable additionalContext;
/**
* Extra injections.
*/
private final Set<InjectablePlaceholder> extraInjections = new HashSet<>();
/**
* Create a new merged injectable context.
*
* @param baseContext The base context.
* @param additionalContext The additional context.
*/
public MergedInjectableContext(@NotNull final PlaceholderInjectable baseContext,
@NotNull final PlaceholderInjectable additionalContext) {
this.baseContext = baseContext;
this.additionalContext = additionalContext;
}
@Override
public void addInjectablePlaceholder(@NotNull final Iterable<InjectablePlaceholder> placeholders) {
for (InjectablePlaceholder placeholder : placeholders) {
extraInjections.add(placeholder);
}
}
@Override
public void clearInjectedPlaceholders() {
baseContext.clearInjectedPlaceholders();
additionalContext.clearInjectedPlaceholders();
extraInjections.clear();
}
@Override
public @NotNull List<InjectablePlaceholder> getPlaceholderInjections() {
List<InjectablePlaceholder> base = baseContext.getPlaceholderInjections();
List<InjectablePlaceholder> additional = additionalContext.getPlaceholderInjections();
List<InjectablePlaceholder> injections = new ArrayList<>(base.size() + additional.size() + extraInjections.size());
injections.addAll(base);
injections.addAll(additional);
injections.addAll(extraInjections);
return injections;
}
}

View File

@@ -0,0 +1,252 @@
package com.willfp.eco.core.placeholder.context;
import com.willfp.eco.core.placeholder.AdditionalPlayer;
import com.willfp.eco.core.placeholder.InjectablePlaceholder;
import com.willfp.eco.core.placeholder.PlaceholderInjectable;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
/**
* Represents a context to translate placeholders in.
*/
public class PlaceholderContext {
/**
* An empty injectable.
*/
private static final PlaceholderInjectable EMPTY_INJECTABLE = new PlaceholderInjectable() {
@Override
public void addInjectablePlaceholder(@NotNull final Iterable<InjectablePlaceholder> placeholders) {
// Do nothing.
}
@Override
public void clearInjectedPlaceholders() {
// Do nothing.
}
@Override
public @NotNull List<InjectablePlaceholder> getPlaceholderInjections() {
return Collections.emptyList();
}
};
/**
* An empty context.
*/
public static final PlaceholderContext EMPTY = new PlaceholderContext(
null,
null,
null,
Collections.emptyList()
);
/**
* The player.
*/
@Nullable
private final Player player;
/**
* The ItemStack.
*/
@Nullable
private final ItemStack itemStack;
/**
* The PlaceholderInjectable context.
*/
@NotNull
private final PlaceholderInjectable injectableContext;
/**
* The additional players.
*/
@NotNull
private final Collection<AdditionalPlayer> additionalPlayers;
/**
* Create an empty PlaceholderContext.
*/
public PlaceholderContext() {
this(null, null, null, Collections.emptyList());
}
/**
* Create a PlaceholderContext for a player.
*
* @param player The player.
*/
public PlaceholderContext(@Nullable final Player player) {
this(player, null, null, Collections.emptyList());
}
/**
* Constructs a new PlaceholderContext with the given parameters.
*
* @param player The player.
* @param itemStack The ItemStack.
* @param injectableContext The PlaceholderInjectable parseContext.
* @param additionalPlayers The additional players.
*/
public PlaceholderContext(@Nullable final Player player,
@Nullable final ItemStack itemStack,
@Nullable final PlaceholderInjectable injectableContext,
@NotNull final Collection<AdditionalPlayer> additionalPlayers) {
this.player = player;
this.itemStack = itemStack;
this.injectableContext = Objects.requireNonNullElse(injectableContext, EMPTY_INJECTABLE);
this.additionalPlayers = additionalPlayers;
}
/**
* Get the player.
*
* @return The player.
*/
@Nullable
public Player getPlayer() {
return player;
}
/**
* Get the ItemStack.
*
* @return The ItemStack.
*/
@Nullable
public ItemStack getItemStack() {
return itemStack;
}
/**
* Get the PlaceholderInjectable context.
*
* @return The PlaceholderInjectable context.
*/
@NotNull
public PlaceholderInjectable getInjectableContext() {
return injectableContext;
}
/**
* Get the additional players.
*
* @return The additional players.
*/
@NotNull
public Collection<AdditionalPlayer> getAdditionalPlayers() {
return additionalPlayers;
}
/**
* Convert to a {@link com.willfp.eco.core.math.MathContext}.
*
* @return The math context.
* @deprecated MathContext is deprecated, use {@link PlaceholderContext} instead.
*/
@Deprecated(since = "6.56.0", forRemoval = true)
@SuppressWarnings({"removal", "DeprecatedIsStillUsed"})
public com.willfp.eco.core.math.MathContext toMathContext() {
return new com.willfp.eco.core.math.MathContext(this.getInjectableContext(), this.getPlayer(), this.getAdditionalPlayers());
}
/**
* Copy with a player.
*
* @param player The player.
* @return The new context.
*/
public PlaceholderContext copyWithPlayer(@Nullable final Player player) {
return new PlaceholderContext(
player,
this.getItemStack(),
this.getInjectableContext(),
this.getAdditionalPlayers()
);
}
/**
* Copy with an item.
*
* @param itemStack The ItemStack.
* @return The new context.
*/
public PlaceholderContext copyWithItem(@Nullable final ItemStack itemStack) {
return new PlaceholderContext(
this.getPlayer(),
itemStack,
this.getInjectableContext(),
this.getAdditionalPlayers()
);
}
/**
* Copy with an extra injectable context.
*
* @param injectableContext The injectable context to add.
* @return The new context.
*/
public PlaceholderContext withInjectableContext(@NotNull final PlaceholderInjectable injectableContext) {
return new PlaceholderContext(
this.getPlayer(),
this.getItemStack(),
new MergedInjectableContext(this.getInjectableContext(), injectableContext),
this.getAdditionalPlayers()
);
}
@Override
public String toString() {
return "PlaceholderContext{" +
"player=" + player +
", itemStack=" + itemStack +
", injectableContext=" + injectableContext +
", additionalPlayers=" + additionalPlayers +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof PlaceholderContext that)) {
return false;
}
return Objects.equals(
getPlayer(), that.getPlayer())
&& Objects.equals(getItemStack(), that.getItemStack())
&& getInjectableContext().equals(that.getInjectableContext())
&& getAdditionalPlayers().equals(that.getAdditionalPlayers()
);
}
@Override
public int hashCode() {
return Objects.hash(getPlayer(), getItemStack(), getInjectableContext(), getAdditionalPlayers());
}
/**
* Create PlaceholderContext of a PlaceholderInjectable parseContext.
*
* @param injectableContext The PlaceholderInjectable parseContext.
* @return The context.
*/
public static PlaceholderContext of(@NotNull final PlaceholderInjectable injectableContext) {
return new PlaceholderContext(
null,
null,
injectableContext,
Collections.emptyList()
);
}
}

View File

@@ -0,0 +1,19 @@
package com.willfp.eco.core.placeholder.context;
import org.jetbrains.annotations.NotNull;
/**
* A supplier that takes a {@link PlaceholderContext} and returns a value.
*
* @param <T> The type of value to return.
*/
public interface PlaceholderContextSupplier<T> {
/**
* Get the value.
*
* @param context The context.
* @return The value.
*/
@NotNull
T get(@NotNull PlaceholderContext context);
}

View File

@@ -0,0 +1,57 @@
package com.willfp.eco.core.placeholder.templates;
import com.willfp.eco.core.placeholder.InjectablePlaceholder;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
import java.util.regex.Pattern;
/**
* A template class for dynamic placeholders.
*/
public abstract class DynamicInjectablePlaceholder implements InjectablePlaceholder {
/**
* The placeholder pattern.
*/
private final Pattern pattern;
/**
* Create a new dynamic injectable placeholder.
*
* @param pattern The pattern.
*/
protected DynamicInjectablePlaceholder(@NotNull final Pattern pattern) {
this.pattern = pattern;
}
@Override
public @NotNull Pattern getPattern() {
return pattern;
}
@Override
public String toString() {
return "DynamicInjectablePlaceholder{" +
"pattern='" + pattern + '\'' +
'}';
}
@Override
public boolean equals(@NotNull final Object o) {
if (this == o) {
return true;
}
if (!(o instanceof DynamicInjectablePlaceholder that)) {
return false;
}
return Objects.equals(pattern, that.getPattern());
}
@Override
public int hashCode() {
return Objects.hash(pattern);
}
}

View File

@@ -0,0 +1,73 @@
package com.willfp.eco.core.placeholder.templates;
import com.willfp.eco.core.EcoPlugin;
import com.willfp.eco.core.placeholder.RegistrablePlaceholder;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
import java.util.regex.Pattern;
/**
* A template class for dynamic placeholders.
*/
public abstract class DynamicPlaceholder implements RegistrablePlaceholder {
/**
* The plugin.
*/
private final EcoPlugin plugin;
/**
* The placeholder pattern.
*/
private final Pattern pattern;
/**
* Create a new dynamic placeholder.
*
* @param plugin The plugin.
* @param pattern The pattern.
*/
protected DynamicPlaceholder(@NotNull final EcoPlugin plugin,
@NotNull final Pattern pattern) {
this.plugin = plugin;
this.pattern = pattern;
}
@Override
public @NotNull Pattern getPattern() {
return pattern;
}
@NotNull
@Override
public EcoPlugin getPlugin() {
return plugin;
}
@Override
public String toString() {
return "DynamicPlaceholder{" +
"plugin='" + plugin + '\'' +
"pattern='" + pattern + '\'' +
'}';
}
@Override
public boolean equals(@NotNull final Object o) {
if (this == o) {
return true;
}
if (!(o instanceof DynamicPlaceholder that)) {
return false;
}
return Objects.equals(pattern, that.getPattern())
&& Objects.equals(plugin, that.getPlugin());
}
@Override
public int hashCode() {
return Objects.hash(pattern, plugin);
}
}

View File

@@ -0,0 +1,72 @@
package com.willfp.eco.core.placeholder.templates;
import com.willfp.eco.core.placeholder.InjectablePlaceholder;
import com.willfp.eco.core.placeholder.context.PlaceholderContext;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
import java.util.regex.Pattern;
/**
* A template class for simple placeholders.
*/
public abstract class SimpleInjectablePlaceholder implements InjectablePlaceholder {
/**
* The name of the placeholder.
*/
private final String identifier;
/**
* The placeholder pattern.
*/
private final Pattern pattern;
/**
* Create a new simple injectable placeholder.
*
* @param identifier The identifier.
*/
protected SimpleInjectablePlaceholder(@NotNull final String identifier) {
this.identifier = identifier;
this.pattern = Pattern.compile(identifier, Pattern.LITERAL);
}
@Override
public String tryTranslateQuickly(@NotNull final String text,
@NotNull final PlaceholderContext context) {
return text.replace(
"%" + this.identifier + "%",
Objects.requireNonNullElse(this.getValue(this.identifier, context), "")
);
}
@Override
public @NotNull Pattern getPattern() {
return pattern;
}
@Override
public String toString() {
return "SimpleInjectablePlaceholder{" +
"identifier='" + identifier + '\'' +
'}';
}
@Override
public boolean equals(@NotNull final Object o) {
if (this == o) {
return true;
}
if (!(o instanceof SimpleInjectablePlaceholder that)) {
return false;
}
return Objects.equals(identifier, that.identifier);
}
@Override
public int hashCode() {
return Objects.hash(identifier);
}
}

View File

@@ -0,0 +1,89 @@
package com.willfp.eco.core.placeholder.templates;
import com.willfp.eco.core.EcoPlugin;
import com.willfp.eco.core.placeholder.RegistrablePlaceholder;
import com.willfp.eco.core.placeholder.context.PlaceholderContext;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
import java.util.regex.Pattern;
/**
* A template class for simple placeholders.
*/
public abstract class SimplePlaceholder implements RegistrablePlaceholder {
/**
* The plugin.
*/
private final EcoPlugin plugin;
/**
* The name of the placeholder.
*/
private final String identifier;
/**
* The placeholder pattern.
*/
private final Pattern pattern;
/**
* Create a new simple placeholder.
*
* @param plugin The plugin.
* @param identifier The identifier.
*/
protected SimplePlaceholder(@NotNull final EcoPlugin plugin,
@NotNull final String identifier) {
this.plugin = plugin;
this.identifier = identifier;
this.pattern = Pattern.compile(identifier, Pattern.LITERAL);
}
@Override
public String tryTranslateQuickly(@NotNull final String text,
@NotNull final PlaceholderContext context) {
return text.replace(
"%" + this.identifier + "%",
Objects.requireNonNullElse(this.getValue(this.identifier, context), "")
);
}
@Override
public @NotNull Pattern getPattern() {
return pattern;
}
@NotNull
@Override
public EcoPlugin getPlugin() {
return plugin;
}
@Override
public String toString() {
return "SimplePlaceholder{" +
"plugin='" + plugin + '\'' +
"identifier='" + identifier + '\'' +
'}';
}
@Override
public boolean equals(@NotNull final Object o) {
if (this == o) {
return true;
}
if (!(o instanceof SimplePlaceholder that)) {
return false;
}
return Objects.equals(pattern, that.getPattern())
&& Objects.equals(plugin, that.getPlugin());
}
@Override
public int hashCode() {
return Objects.hash(identifier, plugin);
}
}

View File

@@ -1,7 +1,7 @@
package com.willfp.eco.core.price;
import com.willfp.eco.core.config.interfaces.Config;
import com.willfp.eco.core.math.MathContext;
import com.willfp.eco.core.placeholder.context.PlaceholderContext;
import com.willfp.eco.core.price.impl.PriceFree;
import com.willfp.eco.core.serialization.ConfigDeserializer;
import com.willfp.eco.util.NumberUtils;
@@ -168,7 +168,7 @@ public final class ConfiguredPrice implements Price {
Price price = Prices.create(
config.getString("value"),
config.getString("type"),
MathContext.of(config)
PlaceholderContext.of(config)
);
return new ConfiguredPrice(price, formatString);

View File

@@ -1,6 +1,7 @@
package com.willfp.eco.core.price;
import com.willfp.eco.core.math.MathContext;
import com.willfp.eco.core.placeholder.context.PlaceholderContext;
import com.willfp.eco.core.placeholder.context.PlaceholderContextSupplier;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
@@ -10,7 +11,8 @@ import java.util.function.Function;
/**
* Create prices.
* <p>
* You must override one of the create methods.
* Override create(PlaceholderContext, PlaceholderContextSupplier), other methods
* are for backwards compatibility.
*/
public interface PriceFactory {
/**
@@ -27,20 +29,38 @@ public interface PriceFactory {
*
* @param value The value.
* @return The price.
* @deprecated Use {@link #create(PlaceholderContext, PlaceholderContextSupplier)} instead.
*/
@Deprecated(since = "6.56.0", forRemoval = true)
default @NotNull Price create(final double value) {
return create(MathContext.EMPTY, (ctx) -> value);
return create(PlaceholderContext.EMPTY, (ctx) -> value);
}
/**
* Create the price.
*
* @param baseContext The base MathContext.
* @param function The function to use. Should use {@link MathContext#copyWithPlayer(MathContext, Player)} on calls.
* @param function The function to use. Should use {@link com.willfp.eco.core.math.MathContext#copyWithPlayer(com.willfp.eco.core.math.MathContext, Player)} on calls.
* @return The price.
* @deprecated Use {@link #create(PlaceholderContext, PlaceholderContextSupplier)} instead.
*/
@Deprecated(since = "6.56.0", forRemoval = true)
@SuppressWarnings("removal")
default @NotNull Price create(@NotNull final com.willfp.eco.core.math.MathContext baseContext,
@NotNull final Function<com.willfp.eco.core.math.MathContext, Double> function) {
return create(baseContext.toPlaceholderContext(), (PlaceholderContext ctx) -> function.apply(ctx.toMathContext()));
}
/**
* Create the price.
*
* @param baseContext The base PlaceholderContext.
* @param function The function to use. Should use {@link PlaceholderContext#copyWithPlayer(Player)} on calls.
* @return The price.
*/
default @NotNull Price create(@NotNull final MathContext baseContext,
@NotNull final Function<MathContext, Double> function) {
return create(function.apply(baseContext));
@SuppressWarnings("removal")
default @NotNull Price create(@NotNull final PlaceholderContext baseContext,
@NotNull final PlaceholderContextSupplier<Double> function) {
return create(baseContext.toMathContext(), (com.willfp.eco.core.math.MathContext ctx) -> function.get(ctx.toPlaceholderContext()));
}
}

View File

@@ -2,7 +2,8 @@ package com.willfp.eco.core.price;
import com.willfp.eco.core.items.Items;
import com.willfp.eco.core.items.TestableItem;
import com.willfp.eco.core.math.MathContext;
import com.willfp.eco.core.placeholder.context.PlaceholderContext;
import com.willfp.eco.core.placeholder.context.PlaceholderContextSupplier;
import com.willfp.eco.core.price.impl.PriceEconomy;
import com.willfp.eco.core.price.impl.PriceFree;
import com.willfp.eco.core.price.impl.PriceItem;
@@ -13,7 +14,6 @@ import org.jetbrains.annotations.Nullable;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
/**
* Class to manage prices.
@@ -48,7 +48,28 @@ public final class Prices {
@NotNull
public static Price create(@NotNull final String expression,
@Nullable final String priceName) {
return create(expression, priceName, MathContext.EMPTY);
return create(expression, priceName, PlaceholderContext.EMPTY);
}
/**
* Create price from an expression (representing the value),
* and a price name. Uses a context to parse the expression.
* <p>
* Supports items as price names.
*
* @param expression The expression for the value.
* @param priceName The price name.
* @param context The math context to parse the expression.
* @return The price, or free if invalid.
* @deprecated Use {@link #create(String, String, PlaceholderContext)} instead.
*/
@NotNull
@Deprecated(since = "6.56.0", forRemoval = true)
@SuppressWarnings("removal")
public static Price create(@NotNull final String expression,
@Nullable final String priceName,
@NotNull final com.willfp.eco.core.math.MathContext context) {
return create(expression, priceName, context.toPlaceholderContext());
}
/**
@@ -65,8 +86,8 @@ public final class Prices {
@NotNull
public static Price create(@NotNull final String expression,
@Nullable final String priceName,
@NotNull final MathContext context) {
Function<MathContext, Double> function = (ctx) -> NumberUtils.evaluateExpression(
@NotNull final PlaceholderContext context) {
PlaceholderContextSupplier<Double> function = (ctx) -> NumberUtils.evaluateExpression(
expression,
ctx
);

View File

@@ -1,7 +1,8 @@
package com.willfp.eco.core.price.impl;
import com.willfp.eco.core.integrations.economy.EconomyManager;
import com.willfp.eco.core.math.MathContext;
import com.willfp.eco.core.placeholder.context.PlaceholderContext;
import com.willfp.eco.core.placeholder.context.PlaceholderContextSupplier;
import com.willfp.eco.core.price.Price;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
@@ -18,12 +19,12 @@ public final class PriceEconomy implements Price {
/**
* The value of the price.
*/
private final Function<MathContext, Double> function;
private final PlaceholderContextSupplier<Double> function;
/**
* The base math context.
* The base placeholder context.
*/
private final MathContext baseContext;
private final PlaceholderContext baseContext;
/**
* The multipliers.
@@ -36,7 +37,21 @@ public final class PriceEconomy implements Price {
* @param value The value.
*/
public PriceEconomy(final double value) {
this(MathContext.EMPTY, ctx -> value);
this(PlaceholderContext.EMPTY, (PlaceholderContext ctx) -> value);
}
/**
* Create a new economy-based price.
*
* @param baseContext The base context.
* @param function The function.
* @deprecated Use {@link #PriceEconomy(PlaceholderContext, PlaceholderContextSupplier)} instead.
*/
@Deprecated(since = "6.56.0", forRemoval = true)
@SuppressWarnings("removal")
public PriceEconomy(@NotNull final com.willfp.eco.core.math.MathContext baseContext,
@NotNull final Function<com.willfp.eco.core.math.MathContext, Double> function) {
this(baseContext.toPlaceholderContext(), (PlaceholderContext ctx) -> function.apply(ctx.toMathContext()));
}
/**
@@ -45,8 +60,8 @@ public final class PriceEconomy implements Price {
* @param baseContext The base context.
* @param function The function.
*/
public PriceEconomy(@NotNull final MathContext baseContext,
@NotNull final Function<MathContext, Double> function) {
public PriceEconomy(@NotNull final PlaceholderContext baseContext,
@NotNull final PlaceholderContextSupplier<Double> function) {
this.baseContext = baseContext;
this.function = function;
}
@@ -72,7 +87,7 @@ public final class PriceEconomy implements Price {
@Override
public double getValue(@NotNull final Player player,
final double multiplier) {
return this.function.apply(MathContext.copyWithPlayer(baseContext, player)) * getMultiplier(player) * multiplier;
return this.function.get(baseContext.copyWithPlayer(player)) * getMultiplier(player) * multiplier;
}
@Override

View File

@@ -3,7 +3,8 @@ package com.willfp.eco.core.price.impl;
import com.willfp.eco.core.drops.DropQueue;
import com.willfp.eco.core.items.HashedItem;
import com.willfp.eco.core.items.TestableItem;
import com.willfp.eco.core.math.MathContext;
import com.willfp.eco.core.placeholder.context.PlaceholderContext;
import com.willfp.eco.core.placeholder.context.PlaceholderContextSupplier;
import com.willfp.eco.core.price.Price;
import org.bukkit.Material;
import org.bukkit.entity.Player;
@@ -20,14 +21,14 @@ import java.util.function.Function;
*/
public final class PriceItem implements Price {
/**
* The base MathContext.
* The base PlaceholderContext.
*/
private final MathContext baseContext;
private final PlaceholderContext baseContext;
/**
* The amount of items.
*/
private final Function<MathContext, Double> function;
private final PlaceholderContextSupplier<Double> function;
/**
* The item.
@@ -47,7 +48,23 @@ public final class PriceItem implements Price {
*/
public PriceItem(final int amount,
@NotNull final TestableItem item) {
this(MathContext.EMPTY, ctx -> (double) amount, item);
this(PlaceholderContext.EMPTY, (PlaceholderContext ctx) -> (double) amount, item);
}
/**
* Create a new item-based price.
*
* @param baseContext The base MathContext.
* @param function The function to get the amount of items to remove.
* @param item The item.
* @deprecated Use {@link #PriceItem(PlaceholderContext, PlaceholderContextSupplier, TestableItem)} instead.
*/
@Deprecated(since = "6.56.0", forRemoval = true)
@SuppressWarnings("removal")
public PriceItem(@NotNull final com.willfp.eco.core.math.MathContext baseContext,
@NotNull final Function<com.willfp.eco.core.math.MathContext, Double> function,
@NotNull final TestableItem item) {
this(baseContext.toPlaceholderContext(), (PlaceholderContext ctx) -> function.apply(ctx.toMathContext()), item);
}
/**
@@ -57,8 +74,8 @@ public final class PriceItem implements Price {
* @param function The function to get the amount of items to remove.
* @param item The item.
*/
public PriceItem(@NotNull final MathContext baseContext,
@NotNull final Function<MathContext, Double> function,
public PriceItem(@NotNull final PlaceholderContext baseContext,
@NotNull final PlaceholderContextSupplier<Double> function,
@NotNull final TestableItem item) {
this.baseContext = baseContext;
this.function = function;
@@ -137,7 +154,7 @@ public final class PriceItem implements Price {
public double getValue(@NotNull final Player player,
final double multiplier) {
return Math.toIntExact(Math.round(
this.function.apply(MathContext.copyWithPlayer(baseContext, player))
this.function.get(baseContext.copyWithPlayer(player))
* getMultiplier(player) * multiplier
));
}

View File

@@ -8,42 +8,15 @@ import org.bukkit.persistence.PersistentDataType;
import org.jetbrains.annotations.NotNull;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Set;
/**
* Utilities / API methods for blocks.
*/
public final class BlockUtils {
/**
* Max blocks to mine (yes, this is to prevent a stack overflow).
*/
private static final int MAX_BLOCKS = 2500;
private static Set<Block> getNearbyBlocks(@NotNull final Block start,
@NotNull final List<Material> allowedMaterials,
@NotNull final Set<Block> blocks,
final int limit) {
for (BlockFace face : BlockFace.values()) {
Block block = start.getRelative(face);
if (blocks.contains(block)) {
continue;
}
if (allowedMaterials.contains(block.getType())) {
blocks.add(block);
if (blocks.size() > limit || blocks.size() > MAX_BLOCKS) {
return blocks;
}
blocks.addAll(getNearbyBlocks(block, allowedMaterials, blocks, limit));
}
}
return blocks;
}
/**
* Get a set of all blocks in contact with each other of a specific type.
*
@@ -56,7 +29,32 @@ public final class BlockUtils {
public static Set<Block> getVein(@NotNull final Block start,
@NotNull final List<Material> allowedMaterials,
final int limit) {
return getNearbyBlocks(start, allowedMaterials, new HashSet<>(), limit);
Set<Block> blocks = new HashSet<>();
Queue<Block> toProcess = new LinkedList<>();
if (allowedMaterials.contains(start.getType())) {
toProcess.add(start);
}
while (!toProcess.isEmpty() && blocks.size() < limit) {
Block currentBlock = toProcess.poll();
if (blocks.contains(currentBlock)) {
continue;
}
blocks.add(currentBlock);
for (BlockFace face : BlockFace.values()) {
Block adjacentBlock = currentBlock.getRelative(face);
if (!blocks.contains(adjacentBlock) && allowedMaterials.contains(adjacentBlock.getType())) {
toProcess.add(adjacentBlock);
}
}
}
return blocks;
}
/**

View File

@@ -1,10 +1,9 @@
package com.willfp.eco.util;
import com.willfp.eco.core.Eco;
import com.willfp.eco.core.integrations.placeholder.PlaceholderManager;
import com.willfp.eco.core.math.MathContext;
import com.willfp.eco.core.placeholder.AdditionalPlayer;
import com.willfp.eco.core.placeholder.PlaceholderInjectable;
import com.willfp.eco.core.placeholder.context.PlaceholderContext;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -210,7 +209,7 @@ public final class NumberUtils {
* @return The value of the expression, or zero if invalid.
*/
public static double evaluateExpression(@NotNull final String expression) {
return evaluateExpression(expression, MathContext.EMPTY);
return evaluateExpression(expression, PlaceholderContext.EMPTY);
}
/**
@@ -222,7 +221,7 @@ public final class NumberUtils {
*/
public static double evaluateExpression(@NotNull final String expression,
@Nullable final Player player) {
return evaluateExpression(expression, player, PlaceholderManager.EMPTY_INJECTABLE);
return evaluateExpression(expression, player, null);
}
/**
@@ -230,12 +229,12 @@ public final class NumberUtils {
*
* @param expression The expression.
* @param player The player.
* @param context The injectable placeholders.
* @param context The injectableContext placeholders.
* @return The value of the expression, or zero if invalid.
*/
public static double evaluateExpression(@NotNull final String expression,
@Nullable final Player player,
@NotNull final PlaceholderInjectable context) {
@Nullable final PlaceholderInjectable context) {
return evaluateExpression(expression, player, context, new ArrayList<>());
}
@@ -244,30 +243,46 @@ public final class NumberUtils {
*
* @param expression The expression.
* @param player The player.
* @param context The injectable placeholders.
* @param context The injectableContext placeholders.
* @param additionalPlayers Additional players to parse placeholders for.
* @return The value of the expression, or zero if invalid.
*/
public static double evaluateExpression(@NotNull final String expression,
@Nullable final Player player,
@NotNull final PlaceholderInjectable context,
@Nullable final PlaceholderInjectable context,
@NotNull final Collection<AdditionalPlayer> additionalPlayers) {
return Eco.get().evaluate(expression, new MathContext(
context,
return Eco.get().evaluate(expression, new PlaceholderContext(
player,
null,
context,
additionalPlayers
));
}
/**
* Evaluate an expression with respect to a player (for placeholders).
* Evaluate an expression in a context.
*
* @param expression The expression.
* @param context The math context.
* @param context The context.
* @return The value of the expression, or zero if invalid.
* @deprecated Use {@link #evaluateExpression(String, PlaceholderContext)} instead.
*/
@Deprecated(since = "6.56.0", forRemoval = true)
@SuppressWarnings("removal")
public static double evaluateExpression(@NotNull final String expression,
@NotNull final com.willfp.eco.core.math.MathContext context) {
return evaluateExpression(expression, context.toPlaceholderContext());
}
/**
* Evaluate an expression in a context.
*
* @param expression The expression.
* @param context The context.
* @return The value of the expression, or zero if invalid.
*/
public static double evaluateExpression(@NotNull final String expression,
@NotNull final MathContext context) {
@NotNull final PlaceholderContext context) {
return Eco.get().evaluate(expression, context);
}

View File

@@ -8,6 +8,7 @@ import com.google.common.collect.ImmutableMap;
import com.google.gson.JsonSyntaxException;
import com.willfp.eco.core.Eco;
import com.willfp.eco.core.integrations.placeholder.PlaceholderManager;
import com.willfp.eco.core.placeholder.context.PlaceholderContext;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.TextDecoration;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
@@ -200,6 +201,26 @@ public final class StringUtils {
return translated;
}
/**
* Format a list of strings.
* <p>
* Coverts color codes and placeholders.
*
* @param list The messages to format.
* @param context The context.
* @return The message, format.
*/
@NotNull
public static List<String> formatList(@NotNull final List<String> list,
@NotNull PlaceholderContext context) {
List<String> translated = new ArrayList<>();
for (String string : list) {
translated.add(format(string, context));
}
return translated;
}
/**
* Format a string.
* <p>
@@ -242,7 +263,7 @@ public final class StringUtils {
@NotNull
public static String format(@NotNull final String message,
@NotNull final FormatOption option) {
return format(message, null, option);
return format(message, (Player) null, option);
}
/**
@@ -287,7 +308,7 @@ public final class StringUtils {
@NotNull
public static Component formatToComponent(@NotNull final String message,
@NotNull final FormatOption option) {
return formatToComponent(message, null, option);
return formatToComponent(message, (Player) null, option);
}
/**
@@ -321,10 +342,49 @@ public final class StringUtils {
public static String format(@NotNull final String message,
@Nullable final Player player,
@NotNull final FormatOption option) {
String processedMessage = message;
if (option == FormatOption.WITH_PLACEHOLDERS) {
processedMessage = PlaceholderManager.translatePlaceholders(processedMessage, player);
return format(
message,
new PlaceholderContext(player)
);
}
return STRING_FORMAT_CACHE.get(message);
}
/**
* Format a string to a component.
* <p>
* Converts color codes and placeholders if specified.
*
* @param message The message to translate.
* @param context The placeholder context.
* @return The message, formatted, as a component.
* @see StringUtils#format(String, Player)
*/
@NotNull
public static Component formatToComponent(@NotNull final String message,
@NotNull final PlaceholderContext context) {
return toComponent(format(message, context));
}
/**
* Format a string.
* <p>
* Coverts color codes and placeholders if specified.
*
* @param message The message to format.
* @param context The context to translate placeholders with respect to.
* @return The message, formatted.
*/
@NotNull
public static String format(@NotNull final String message,
@NotNull final PlaceholderContext context) {
String processedMessage = message;
processedMessage = PlaceholderManager.translatePlaceholders(
processedMessage,
context
);
return STRING_FORMAT_CACHE.get(processedMessage);
}
@@ -674,6 +734,56 @@ public final class StringUtils {
return builder.toString();
}
/**
* Fast implementation of {@link String#replace(CharSequence, CharSequence)}
*
* @param input The input string.
* @param target The target string.
* @param replacement The replacement string.
* @return The replaced string.
*/
@NotNull
public static String replaceQuickly(@NotNull final String input,
@NotNull final String target,
@NotNull final String replacement) {
int targetLength = target.length();
// Count the number of original occurrences
int count = 0;
for (
int index = input.indexOf(target);
index != -1;
index = input.indexOf(target, index + targetLength)
) {
count++;
}
if (count == 0) {
return input;
}
int replacementLength = replacement.length();
int inputLength = input.length();
// Pre-calculate the final size of the StringBuilder
int newSize = inputLength + (replacementLength - targetLength) * count;
StringBuilder result = new StringBuilder(newSize);
int start = 0;
for (
int index = input.indexOf(target);
index != -1;
index = input.indexOf(target, start)
) {
result.append(input, start, index);
result.append(replacement);
start = index + targetLength;
}
result.append(input, start, inputLength);
return result.toString();
}
/**
* Options for formatting.
*/

View File

@@ -0,0 +1,25 @@
@file:JvmName("PlaceholderContextExtensions")
package com.willfp.eco.core.placeholder.context
import com.willfp.eco.core.placeholder.AdditionalPlayer
import com.willfp.eco.core.placeholder.PlaceholderInjectable
import org.bukkit.entity.Player
import org.bukkit.inventory.ItemStack
/** @see PlaceholderContext */
@JvmOverloads
fun placeholderContext(
player: Player? = null,
item: ItemStack? = null,
injectable: PlaceholderInjectable? = null,
additionalPlayers: Collection<AdditionalPlayer> = emptyList()
): PlaceholderContext = PlaceholderContext(player, item, injectable, additionalPlayers)
/** @see PlaceholderContext */
fun PlaceholderContext.copy(
player: Player? = this.player,
item: ItemStack? = this.itemStack,
injectable: PlaceholderInjectable? = this.injectableContext,
additionalPlayers: Collection<AdditionalPlayer> = this.additionalPlayers
): PlaceholderContext = PlaceholderContext(player, item, injectable, additionalPlayers)

View File

@@ -2,6 +2,7 @@
package com.willfp.eco.util
import com.willfp.eco.core.placeholder.context.PlaceholderContext
import net.kyori.adventure.text.Component
import org.bukkit.entity.Player
@@ -31,6 +32,14 @@ fun String.formatEco(
if (formatPlaceholders) StringUtils.FormatOption.WITH_PLACEHOLDERS else StringUtils.FormatOption.WITHOUT_PLACEHOLDERS
)
/** @see StringUtils.format */
fun String.formatEco(
context: PlaceholderContext
) = StringUtils.format(
this,
context
)
/** @see StringUtils.formatList */
fun List<String>.formatEco(
player: Player? = null,
@@ -41,6 +50,14 @@ fun List<String>.formatEco(
if (formatPlaceholders) StringUtils.FormatOption.WITH_PLACEHOLDERS else StringUtils.FormatOption.WITHOUT_PLACEHOLDERS
)
/** @see StringUtils.formatList */
fun List<String>.formatEco(
context: PlaceholderContext
): List<String> = StringUtils.formatList(
this,
context
)
/** @see StringUtils.splitAround */
fun String.splitAround(separator: String): Array<String> =
StringUtils.splitAround(this, separator)
@@ -48,3 +65,7 @@ fun String.splitAround(separator: String): Array<String> =
/** @see StringUtils.toNiceString */
fun Any?.toNiceString(): String =
StringUtils.toNiceString(this)
/** @see StringUtils.replaceQuickly */
fun String.replaceQuickly(target: String, replacement: String): String =
StringUtils.replaceQuickly(this, target, replacement)

View File

@@ -3,10 +3,13 @@ package com.willfp.eco.internal.config
import com.willfp.eco.core.config.ConfigType
import com.willfp.eco.core.config.interfaces.Config
import com.willfp.eco.core.placeholder.InjectablePlaceholder
import com.willfp.eco.core.placeholder.StaticPlaceholder
import com.willfp.eco.core.placeholder.context.PlaceholderContext
import com.willfp.eco.internal.fast.listView
import com.willfp.eco.util.StringUtils
import org.bukkit.configuration.file.YamlConfiguration
import java.util.Objects
import java.util.concurrent.ConcurrentHashMap
import java.util.regex.Pattern
@Suppress("UNCHECKED_CAST")
open class EcoConfig(
@@ -15,7 +18,7 @@ open class EcoConfig(
private val values = ConcurrentHashMap<String, Any?>()
@Transient
var injections = mutableListOf<InjectablePlaceholder>()
var injections = ConcurrentHashMap<Pattern, InjectablePlaceholder>()
fun init(values: Map<String, Any?>) {
this.values.clear()
@@ -104,12 +107,12 @@ open class EcoConfig(
}
override fun getSubsectionOrNull(path: String): Config? {
return (get(path) as? Config)?.apply { this.addInjectablePlaceholder(injections) }
return (get(path) as? Config)?.apply { this.addInjectablePlaceholder(injections.values) }
}
override fun getSubsectionsOrNull(path: String): List<Config>? {
return getList<Config>(path)
?.map { it.apply { this.addInjectablePlaceholder(injections) } }
?.map { it.apply { this.addInjectablePlaceholder(injections.values) } }
?.toList()
}
@@ -141,9 +144,7 @@ open class EcoConfig(
var string = get(path)?.toString() ?: return null
if (format && option == StringUtils.FormatOption.WITH_PLACEHOLDERS) {
for (injection in placeholderInjections) {
if (injection is StaticPlaceholder) {
string = string.replace("%${injection.identifier}%", injection.value)
}
string = injection.tryTranslateQuickly(string, PlaceholderContext.EMPTY)
}
}
return if (format) StringUtils.format(string, option) else string
@@ -161,9 +162,7 @@ open class EcoConfig(
strings.replaceAll {
var string = it
for (injection in placeholderInjections) {
if (injection is StaticPlaceholder) {
string = string.replace("%${injection.identifier}%", injection.value)
}
string = injection.tryTranslateQuickly(string, PlaceholderContext.EMPTY)
}
string
}
@@ -180,12 +179,13 @@ open class EcoConfig(
}
override fun addInjectablePlaceholder(placeholders: Iterable<InjectablePlaceholder>) {
injections.removeIf { placeholders.any { placeholder -> it.identifier == placeholder.identifier } }
injections.addAll(placeholders)
for (placeholder in placeholders) {
injections[placeholder.pattern] = placeholder
}
}
override fun getPlaceholderInjections(): List<InjectablePlaceholder> {
return injections.toList()
return injections.values.listView() // Faster than .toList()
}
override fun clearInjectedPlaceholders() {
@@ -208,14 +208,6 @@ open class EcoConfig(
return bukkit
}
override fun clone(): Config {
return EcoConfigSection(type, this.values.toMutableMap(), injections)
}
override fun toString(): String {
return this.toPlaintext()
}
private inline fun <reified T> getList(path: String): List<T>? {
val asIterable = get(path) as? Iterable<*> ?: return null
val asList = asIterable.toList()
@@ -226,4 +218,40 @@ open class EcoConfig(
return asList as List<T>
}
override fun clone(): Config {
return EcoConfigSection(type, this.values.toMutableMap(), injections)
}
override fun toString(): String {
return this.toPlaintext()
}
override fun equals(other: Any?): Boolean {
if (this === other) {
return true
}
if (other !is EcoConfig) {
return false
}
if (configType != other.configType) {
return false
}
if (values != other.values) {
return false
}
if (injections != other.injections) {
return false
}
return true
}
override fun hashCode(): Int {
return Objects.hash(values, configType, injections)
}
}

View File

@@ -2,14 +2,16 @@ package com.willfp.eco.internal.config
import com.willfp.eco.core.config.ConfigType
import com.willfp.eco.core.placeholder.InjectablePlaceholder
import java.util.concurrent.ConcurrentHashMap
import java.util.regex.Pattern
class EcoConfigSection(
type: ConfigType,
values: Map<String, Any?> = emptyMap(),
injections: Collection<InjectablePlaceholder> = emptyList()
injections: Map<Pattern, InjectablePlaceholder> = emptyMap()
) : EcoConfig(type) {
init {
this.init(values)
this.injections = injections.toMutableList()
this.injections = ConcurrentHashMap(injections)
}
}

View File

@@ -0,0 +1,48 @@
package com.willfp.eco.internal.fast
class ListViewOfCollection<T>(private val collection: Collection<T>) : List<T> {
/*
The backing list is lazy-loaded because it provides a performance hit
to copy the contents of the contents of the collection into a list.
Since the only required operator for most use-cases is .iterator(),
we can just use the collection's iterator.
*/
private val backingList: List<T> by lazy { collection.toList() }
override val size: Int
get() = collection.size
override fun containsAll(elements: Collection<T>) =
collection.containsAll(elements)
override fun get(index: Int): T =
backingList[index]
override fun indexOf(element: T): Int = backingList.indexOf(element)
override fun contains(element: T): Boolean = collection.contains(element)
override fun isEmpty() =
collection.isEmpty()
override fun iterator() =
collection.iterator()
override fun listIterator() =
backingList.listIterator()
override fun listIterator(index: Int) =
backingList.listIterator(index)
override fun subList(fromIndex: Int, toIndex: Int) =
backingList.subList(fromIndex, toIndex)
override fun lastIndexOf(element: T) =
backingList.lastIndexOf(element)
}
inline fun <reified T> Collection<T>.listView(): List<T> {
return ListViewOfCollection(this)
}

View File

@@ -0,0 +1,44 @@
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<InjectablePlaceholder>?
) {
fun findMatchingPlaceholder(): Placeholder? {
if (plugin != null) {
val pluginPlaceholders = PlaceholderManager.getRegisteredPlaceholders(plugin)
for (placeholder in pluginPlaceholders) {
if (placeholder.matches(this)) {
return placeholder
}
}
}
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()
}
}

View File

@@ -0,0 +1,208 @@
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. This was optimised with ListViewOfCollection,
but it's still best to minimise the memory overhead.
*/
class PlaceholderParser {
private val placeholderRegex = Regex("%([^% ]+)%")
private val placeholderLookupCache = Caffeine.newBuilder()
.expireAfterWrite(1, TimeUnit.SECONDS)
.build<PlaceholderLookup, Optional<Placeholder>>()
fun translatePlacholders(text: String, context: PlaceholderContext): String {
return translatePlacholders(text, context, context.injectableContext.placeholderInjections)
}
private fun translatePlacholders(
text: String,
context: PlaceholderContext,
injections: Collection<InjectablePlaceholder>,
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 = translatePlacholders(
newPlaceholder,
context.copyWithPlayer(additionalPlayer.player),
injections
)
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<InjectablePlaceholder>?,
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<InjectablePlaceholder>
): 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<String>, context: PlaceholderContext): Collection<String> {
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<InjectablePlaceholder>
): 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
}
}

View File

@@ -1,10 +1,10 @@
package com.willfp.eco.internal.price
import com.willfp.eco.core.math.MathContext
import com.willfp.eco.core.placeholder.context.PlaceholderContext
import com.willfp.eco.core.placeholder.context.PlaceholderContextSupplier
import com.willfp.eco.core.price.Price
import com.willfp.eco.core.price.PriceFactory
import com.willfp.eco.core.price.impl.PriceEconomy
import java.util.function.Function
object PriceFactoryEconomy : PriceFactory {
override fun getNames() = listOf(
@@ -12,7 +12,7 @@ object PriceFactoryEconomy : PriceFactory {
"$"
)
override fun create(baseContext: MathContext, function: Function<MathContext, Double>): Price {
override fun create(baseContext: PlaceholderContext, function: PlaceholderContextSupplier<Double>): Price {
return PriceEconomy(baseContext, function)
}
}

View File

@@ -1,11 +1,11 @@
package com.willfp.eco.internal.price
import com.willfp.eco.core.math.MathContext
import com.willfp.eco.core.placeholder.context.PlaceholderContext
import com.willfp.eco.core.placeholder.context.PlaceholderContextSupplier
import com.willfp.eco.core.price.Price
import com.willfp.eco.core.price.PriceFactory
import org.bukkit.entity.Player
import java.util.UUID
import java.util.function.Function
import kotlin.math.roundToInt
object PriceFactoryXP : PriceFactory {
@@ -15,13 +15,13 @@ object PriceFactoryXP : PriceFactory {
"experience"
)
override fun create(baseContext: MathContext, function: Function<MathContext, Double>): Price {
return PriceXP(baseContext) { function.apply(it).roundToInt() }
override fun create(baseContext: PlaceholderContext, function: PlaceholderContextSupplier<Double>): Price {
return PriceXP(baseContext) { function.get(it).roundToInt() }
}
private class PriceXP(
private val baseContext: MathContext,
private val xp: (MathContext) -> Int
private val baseContext: PlaceholderContext,
private val xp: (PlaceholderContext) -> Int
) : Price {
private val multipliers = mutableMapOf<UUID, Double>()
@@ -37,7 +37,7 @@ object PriceFactoryXP : PriceFactory {
}
override fun getValue(player: Player, multiplier: Double): Double {
return xp(MathContext.copyWithPlayer(baseContext, player)) * getMultiplier(player) * multiplier
return xp(baseContext.copyWithPlayer(player)) * getMultiplier(player) * multiplier
}
override fun getMultiplier(player: Player): Double {

View File

@@ -1,11 +1,11 @@
package com.willfp.eco.internal.price
import com.willfp.eco.core.math.MathContext
import com.willfp.eco.core.placeholder.context.PlaceholderContext
import com.willfp.eco.core.placeholder.context.PlaceholderContextSupplier
import com.willfp.eco.core.price.Price
import com.willfp.eco.core.price.PriceFactory
import org.bukkit.entity.Player
import java.util.UUID
import java.util.function.Function
import kotlin.math.roundToInt
object PriceFactoryXPLevels : PriceFactory {
@@ -16,13 +16,13 @@ object PriceFactoryXPLevels : PriceFactory {
"explevels",
)
override fun create(baseContext: MathContext, function: Function<MathContext, Double>): Price {
return PriceXPLevel(baseContext) { function.apply(it).roundToInt() }
override fun create(baseContext: PlaceholderContext, function: PlaceholderContextSupplier<Double>): Price {
return PriceXPLevel(baseContext) { function.get(it).roundToInt() }
}
private class PriceXPLevel(
private val baseContext: MathContext,
private val level: (MathContext) -> Int
private val baseContext: PlaceholderContext,
private val level: (PlaceholderContext) -> Int
) : Price {
private val multipliers = mutableMapOf<UUID, Double>()
@@ -37,7 +37,7 @@ object PriceFactoryXPLevels : PriceFactory {
}
override fun getValue(player: Player, multiplier: Double): Double {
return level(MathContext.copyWithPlayer(baseContext, player)) * getMultiplier(player) * multiplier
return level(baseContext.copyWithPlayer(player)) * getMultiplier(player) * multiplier
}
override fun getMultiplier(player: Player): Double {

View File

@@ -52,12 +52,16 @@ dependencies {
compileOnly("com.github.N0RSKA:ScytherAPI:55a")
compileOnly("com.ticxo.modelengine:api:R3.0.1")
compileOnly("me.TechsCode:UltraEconomyAPI:1.0.0")
compileOnly("org.black_ixx:playerpoints:3.2.5")
compileOnly("com.github.Ssomar-Developement:SCore:3.4.7")
compileOnly("io.lumine:Mythic:5.2.1")
compileOnly("io.lumine:LumineUtils:1.19-SNAPSHOT")
compileOnly("com.SirBlobman.combatlogx:CombatLogX-API:10.0.0.0-SNAPSHOT")
compileOnly("com.github.sirblobman.combatlogx:api:11.0.0.0-SNAPSHOT")
compileOnly("LibsDisguises:LibsDisguises:10.0.26")
compileOnly("com.denizenscript:denizen:1.2.7-SNAPSHOT") {
exclude(group = "*", module = "*")
}
compileOnly(fileTree("../../lib") {
include("*.jar")

View File

@@ -13,8 +13,8 @@ import com.willfp.eco.core.gui.menu.Menu
import com.willfp.eco.core.gui.menu.MenuType
import com.willfp.eco.core.gui.slot.functional.SlotProvider
import com.willfp.eco.core.items.Items
import com.willfp.eco.core.math.MathContext
import com.willfp.eco.core.packet.Packet
import com.willfp.eco.core.placeholder.context.PlaceholderContext
import com.willfp.eco.internal.EcoPropsParser
import com.willfp.eco.internal.command.EcoPluginCommand
import com.willfp.eco.internal.command.EcoSubcommand
@@ -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
@@ -44,7 +45,9 @@ import com.willfp.eco.internal.spigot.data.KeyRegistry
import com.willfp.eco.internal.spigot.data.ProfileHandler
import com.willfp.eco.internal.spigot.data.storage.HandlerType
import com.willfp.eco.internal.spigot.integrations.bstats.MetricHandler
import com.willfp.eco.internal.spigot.math.evaluateExpression
import com.willfp.eco.internal.spigot.math.DelegatedExpressionHandler
import com.willfp.eco.internal.spigot.math.ImmediatePlaceholderTranslationExpressionHandler
import com.willfp.eco.internal.spigot.math.LazyPlaceholderTranslationExpressionHandler
import com.willfp.eco.internal.spigot.proxy.BukkitCommandsProxy
import com.willfp.eco.internal.spigot.proxy.CommonsInitializerProxy
import com.willfp.eco.internal.spigot.proxy.DummyEntityFactoryProxy
@@ -88,6 +91,15 @@ 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(placeholderParser)
else LazyPlaceholderTranslationExpressionHandler(placeholderParser),
)
override fun createScheduler(plugin: EcoPlugin) =
EcoScheduler(plugin)
@@ -312,8 +324,8 @@ class EcoImpl : EcoSpigotPlugin(), Eco {
override fun getTPS() =
getProxy(TPSProxy::class.java).getTPS()
override fun evaluate(expression: String, context: MathContext) =
evaluateExpression(expression, context)
override fun evaluate(expression: String, context: PlaceholderContext) =
crunchHandler.evaluate(expression, context)
override fun getOpenMenu(player: Player) =
player.renderedInventory?.menu
@@ -326,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)
}

View File

@@ -95,6 +95,7 @@ import com.willfp.eco.internal.spigot.integrations.antigrief.AntigriefTowny
import com.willfp.eco.internal.spigot.integrations.antigrief.AntigriefWorldGuard
import com.willfp.eco.internal.spigot.integrations.customentities.CustomEntitiesMythicMobs
import com.willfp.eco.internal.spigot.integrations.customitems.CustomItemsCustomCrafting
import com.willfp.eco.internal.spigot.integrations.customitems.CustomItemsDenizen
import com.willfp.eco.internal.spigot.integrations.customitems.CustomItemsExecutableItems
import com.willfp.eco.internal.spigot.integrations.customitems.CustomItemsHeadDatabase
import com.willfp.eco.internal.spigot.integrations.customitems.CustomItemsItemsAdder
@@ -110,6 +111,7 @@ import com.willfp.eco.internal.spigot.integrations.hologram.HologramHolographicD
import com.willfp.eco.internal.spigot.integrations.mcmmo.McmmoIntegrationImpl
import com.willfp.eco.internal.spigot.integrations.multiverseinventories.MultiverseInventoriesIntegration
import com.willfp.eco.internal.spigot.integrations.placeholder.PlaceholderIntegrationPAPI
import com.willfp.eco.internal.spigot.integrations.price.PriceFactoryPlayerPoints
import com.willfp.eco.internal.spigot.integrations.price.PriceFactoryUltraEconomy
import com.willfp.eco.internal.spigot.integrations.shop.ShopDeluxeSellwands
import com.willfp.eco.internal.spigot.integrations.shop.ShopEconomyShopGUI
@@ -323,6 +325,7 @@ abstract class EcoSpigotPlugin : EcoPlugin() {
},
IntegrationLoader("MythicMobs") { CustomItemsManager.register(CustomItemsMythicMobs(this)) },
IntegrationLoader("Scyther") { CustomItemsManager.register(CustomItemsScyther()) },
IntegrationLoader("Denizen") { CustomItemsManager.register(CustomItemsDenizen()) },
// Shop
IntegrationLoader("ShopGUIPlus") { ShopManager.register(ShopShopGuiPlus()) },
@@ -355,6 +358,7 @@ abstract class EcoSpigotPlugin : EcoPlugin() {
Prices.registerPriceFactory(PriceFactoryUltraEconomy(currency))
}
},
IntegrationLoader("PlayerPoints") { Prices.registerPriceFactory(PriceFactoryPlayerPoints()) },
// Placeholder
IntegrationLoader("PlaceholderAPI") { PlaceholderManager.addIntegration(PlaceholderIntegrationPAPI()) },

View File

@@ -16,9 +16,7 @@ abstract class EcoProfile(
override fun <T : Any> write(key: PersistentDataKey<T>, value: T) {
this.data[key] = value
val changedKeys = CHANGE_MAP[uuid] ?: mutableSetOf()
changedKeys.add(key)
CHANGE_MAP[uuid] = changedKeys
CHANGE_MAP.add(uuid)
}
override fun <T : Any> read(key: PersistentDataKey<T>): T {
@@ -44,7 +42,7 @@ abstract class EcoProfile(
}
companion object {
val CHANGE_MAP: MutableMap<UUID, MutableSet<PersistentDataKey<*>>> = ConcurrentHashMap()
val CHANGE_MAP: MutableSet<UUID> = ConcurrentHashMap.newKeySet()
}
}

View File

@@ -22,7 +22,7 @@ class ProfileHandler(
private val type: HandlerType,
private val plugin: EcoSpigotPlugin
) {
private val loaded = mutableMapOf<UUID, Profile>()
private val loaded = mutableMapOf<UUID, EcoProfile>()
val handler: DataHandler = when (type) {
HandlerType.YAML -> YamlDataHandler(plugin, this)
@@ -41,6 +41,9 @@ class ProfileHandler(
}
}
fun accessLoadedProfile(uuid: UUID): EcoProfile? =
loaded[uuid]
fun loadGenericProfile(uuid: UUID): Profile {
val found = loaded[uuid]
if (found != null) {

View File

@@ -12,10 +12,16 @@ class ProfileSaver(
val interval = plugin.configYml.getInt("save-interval").toLong()
plugin.scheduler.runTimer(20, interval) {
for ((uuid, set) in EcoProfile.CHANGE_MAP) {
handler.saveKeysFor(uuid, set)
val iterator = EcoProfile.CHANGE_MAP.iterator()
while (iterator.hasNext()) {
val uuid = iterator.next()
iterator.remove()
val profile = handler.accessLoadedProfile(uuid) ?: continue
handler.saveKeysFor(uuid, profile.data.keys)
}
EcoProfile.CHANGE_MAP.clear()
}
}
}

View File

@@ -0,0 +1,36 @@
package com.willfp.eco.internal.spigot.integrations.customitems
import com.willfp.eco.core.integrations.customitems.CustomItemsIntegration
import com.willfp.eco.core.items.CustomItem
import com.willfp.eco.core.items.TestableItem
import com.willfp.eco.core.items.provider.ItemProvider
import com.willfp.eco.util.NamespacedKeyUtils
import com.denizenscript.denizen.objects.ItemTag
import com.denizenscript.denizen.scripts.containers.core.ItemScriptHelper
import com.willfp.eco.core.items.Items
import org.bukkit.event.Listener
class CustomItemsDenizen : CustomItemsIntegration, Listener {
override fun registerProvider() {
Items.registerItemProvider(DenizenProvider())
}
override fun getPluginName(): String {
return "Denizen"
}
private class DenizenProvider : ItemProvider("denizen") {
override fun provideForKey(key: String): TestableItem? {
val item = ItemTag.valueOf(key, false) ?: return null
val id = item.scriptName
val namespacedKey = NamespacedKeyUtils.create("denizen", id)
val stack = item.itemStack
return CustomItem(
namespacedKey,
{ id.equals(ItemScriptHelper.getItemScriptNameText(it), ignoreCase = true) },
stack
)
}
}
}

View File

@@ -0,0 +1,53 @@
package com.willfp.eco.internal.spigot.integrations.price
import com.willfp.eco.core.placeholder.context.PlaceholderContext
import com.willfp.eco.core.placeholder.context.PlaceholderContextSupplier
import com.willfp.eco.core.price.Price
import com.willfp.eco.core.price.PriceFactory
import org.black_ixx.playerpoints.PlayerPoints
import org.bukkit.entity.Player
import java.util.UUID
import kotlin.math.roundToInt
class PriceFactoryPlayerPoints : PriceFactory {
override fun getNames() = listOf(
"player_points",
"p_points"
)
override fun create(baseContext: PlaceholderContext, function: PlaceholderContextSupplier<Double>): Price {
return PricePlayerPoints(baseContext) { function.get(it).roundToInt() }
}
private class PricePlayerPoints(
private val baseContext: PlaceholderContext,
private val function: (PlaceholderContext) -> Int
) : Price {
private val api = PlayerPoints.getInstance().api
private val multipliers = mutableMapOf<UUID, Double>()
override fun canAfford(player: Player, multiplier: Double): Boolean {
return api.look(player.uniqueId) >= getValue(player, multiplier)
}
override fun pay(player: Player, multiplier: Double) {
api.take(player.uniqueId, getValue(player, multiplier).roundToInt())
}
override fun giveTo(player: Player, multiplier: Double) {
api.give(player.uniqueId, getValue(player, multiplier).roundToInt())
}
override fun getValue(player: Player, multiplier: Double): Double {
return function(baseContext.copyWithPlayer(player)) * getMultiplier(player) * multiplier
}
override fun getMultiplier(player: Player): Double {
return multipliers[player.uniqueId] ?: 1.0
}
override fun setMultiplier(player: Player, multiplier: Double) {
multipliers[player.uniqueId] = multiplier
}
}
}

View File

@@ -1,6 +1,7 @@
package com.willfp.eco.internal.spigot.integrations.price
import com.willfp.eco.core.math.MathContext
import com.willfp.eco.core.placeholder.context.PlaceholderContext
import com.willfp.eco.core.placeholder.context.PlaceholderContextSupplier
import com.willfp.eco.core.price.Price
import com.willfp.eco.core.price.PriceFactory
import com.willfp.eco.util.toSingletonList
@@ -9,21 +10,20 @@ import me.TechsCode.UltraEconomy.objects.Account
import me.TechsCode.UltraEconomy.objects.Currency
import org.bukkit.entity.Player
import java.util.UUID
import java.util.function.Function
class PriceFactoryUltraEconomy(private val currency: Currency) : PriceFactory {
override fun getNames(): List<String> {
return currency.name.lowercase().toSingletonList()
}
override fun create(baseContext: MathContext, function: Function<MathContext, Double>): Price {
return PriceUltraEconomy(currency, baseContext) { function.apply(it) }
override fun create(baseContext: PlaceholderContext, function: PlaceholderContextSupplier<Double>): Price {
return PriceUltraEconomy(currency, baseContext) { function.get(it) }
}
private class PriceUltraEconomy(
private val currency: Currency,
private val baseContext: MathContext,
private val function: (MathContext) -> Double
private val baseContext: PlaceholderContext,
private val function: (PlaceholderContext) -> Double
) : Price {
private val multipliers = mutableMapOf<UUID, Double>()
private val api = UltraEconomy.getAPI()
@@ -44,7 +44,7 @@ class PriceFactoryUltraEconomy(private val currency: Currency) : PriceFactory {
}
override fun getValue(player: Player, multiplier: Double): Double {
return function(MathContext.copyWithPlayer(baseContext, player)) * getMultiplier(player) * multiplier
return function(baseContext.copyWithPlayer(player)) * getMultiplier(player) * multiplier
}
override fun getMultiplier(player: Player): Double {

View File

@@ -1,53 +0,0 @@
package com.willfp.eco.internal.spigot.math
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.math.MathContext
import com.willfp.eco.core.placeholder.AdditionalPlayer
import com.willfp.eco.core.placeholder.PlaceholderInjectable
import org.bukkit.entity.Player
import redempt.crunch.CompiledExpression
import redempt.crunch.Crunch
import redempt.crunch.data.FastNumberParsing
import redempt.crunch.functional.EvaluationEnvironment
import redempt.crunch.functional.Function
import kotlin.math.max
import kotlin.math.min
private val cache: Cache<String, CompiledExpression> = Caffeine.newBuilder().build()
private val goToZero = Crunch.compileExpression("0")
private val min = Function("min", 2) {
min(it[0], it[1])
}
private val max = Function("max", 2) {
max(it[0], it[1])
}
fun evaluateExpression(expression: String, context: MathContext) =
evaluateExpression(expression, context.player, context.injectableContext, context.additionalPlayers)
.let { if (!it.isFinite()) 0.0 else it } // Fixes NaN bug.
private fun evaluateExpression(
expression: String,
player: Player?,
context: PlaceholderInjectable,
additional: Collection<AdditionalPlayer>
): Double {
val placeholderValues = PlaceholderManager.findPlaceholdersIn(expression)
.map { PlaceholderManager.translatePlaceholders(it, player, context, additional) }
.map { runCatching { FastNumberParsing.parseDouble(it) }.getOrDefault(0.0) }
.toDoubleArray()
val compiled = cache.get(expression) {
val placeholders = PlaceholderManager.findPlaceholdersIn(it)
val env = EvaluationEnvironment()
env.setVariableNames(*placeholders.toTypedArray())
env.addFunctions(min, max)
runCatching { Crunch.compileExpression(expression, env) }.getOrDefault(goToZero)
}
return runCatching { compiled.evaluate(*placeholderValues) }.getOrDefault(0.0)
}

View File

@@ -0,0 +1,30 @@
package com.willfp.eco.internal.spigot.math
import com.github.benmanes.caffeine.cache.Cache
import com.github.benmanes.caffeine.cache.Caffeine
import com.willfp.eco.core.EcoPlugin
import com.willfp.eco.core.placeholder.context.PlaceholderContext
import java.util.Objects
import java.util.concurrent.TimeUnit
class DelegatedExpressionHandler(
plugin: EcoPlugin,
private val handler: ExpressionHandler
): ExpressionHandler {
private val evaluationCache: Cache<Int, Double> = Caffeine.newBuilder()
.expireAfterWrite(plugin.configYml.getInt("math-cache-ttl").toLong(), TimeUnit.MILLISECONDS)
.build()
override fun evaluate(expression: String, context: PlaceholderContext): Double {
val hash = Objects.hash(
expression,
context.player?.uniqueId,
context.injectableContext
)
return evaluationCache.get(hash) {
handler.evaluate(expression, context)
.let { if (!it.isFinite()) 0.0 else it } // Fixes NaN bug.
}
}
}

View File

@@ -0,0 +1,115 @@
package com.willfp.eco.internal.spigot.math
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.functional.EvaluationEnvironment
import redempt.crunch.functional.Function
import java.util.concurrent.TimeUnit
import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
private val goToZero = Crunch.compileExpression("0")
private val min = Function("min", 2) {
min(it[0], it[1])
}
private val max = Function("max", 2) {
max(it[0], it[1])
}
interface ExpressionHandler {
fun evaluate(expression: String, context: PlaceholderContext): Double
}
private fun String.fastToDoubleOrNull(): Double? {
if (isEmpty()) {
return null
}
var idx = 0
val isNegative = this[0] == '-'
if (isNegative) idx++
var integerPart = 0.0
var decimalPart = 0.0
var decimalIdx = -1
while (idx < length) {
when (val char = this[idx]) {
'.' -> {
if (decimalIdx != -1) return null
decimalIdx = idx
}
in '0'..'9' -> {
val number = (char.code - '0'.code).toDouble()
if (decimalIdx != -1) {
decimalPart = decimalPart * 10 + number
} else {
integerPart = integerPart * 10 + number
}
}
else -> return null
}
idx++
}
decimalPart /= 10.0.pow((length - decimalIdx - 1).toDouble())
return if (isNegative) -(integerPart + decimalPart) else integerPart + decimalPart
}
class ImmediatePlaceholderTranslationExpressionHandler(
private val placeholderParser: PlaceholderParser
) : ExpressionHandler {
private val cache: Cache<String, CompiledExpression> = Caffeine.newBuilder()
.expireAfterAccess(500, TimeUnit.MILLISECONDS)
.build()
private val env = EvaluationEnvironment().apply {
addFunctions(min, max)
}
override fun evaluate(expression: String, context: PlaceholderContext): Double {
val translatedExpression = placeholderParser.translatePlacholders(expression, context)
val compiled = cache.get(translatedExpression) {
runCatching { Crunch.compileExpression(translatedExpression, env) }
.getOrDefault(goToZero)
}
return runCatching { compiled.evaluate() }.getOrDefault(0.0)
}
}
class LazyPlaceholderTranslationExpressionHandler(
private val placeholderParser: PlaceholderParser
) : ExpressionHandler {
private val cache: Cache<String, CompiledExpression> = Caffeine.newBuilder()
.build()
override fun evaluate(expression: String, context: PlaceholderContext): Double {
val placeholders = PlaceholderManager.findPlaceholdersIn(expression)
val placeholderValues = placeholderParser.parseIndividualPlaceholders(placeholders, context)
.map { it.fastToDoubleOrNull() ?: 0.0 }
.toDoubleArray()
val compiled = cache.get(expression) {
val env = EvaluationEnvironment()
env.setVariableNames(*placeholders.toTypedArray())
env.addFunctions(min, max)
runCatching { Crunch.compileExpression(expression, env) }.getOrDefault(goToZero)
}
return runCatching { compiled.evaluate(*placeholderValues) }.getOrDefault(0.0)
}
}

View File

@@ -46,11 +46,6 @@ conflicts:
# Disable it if it changes drop mechanics too much for you.
use-fast-collated-drops: true
# Some plugins use their own item display systems (eg Triton)
# And must be run after eco. Don't enable this unless you run a conflicting plugin
# and have been told to enable it.
use-lower-protocollib-priority: false
# Display frames massively optimize PacketWindowItems, however some users have
# reported display bugs by using it. If you have any problems with it, then you
# should disable this option.
@@ -82,4 +77,17 @@ displayed-recipes: true
# If eco plugins should not check for updates; only enable this if you know what you're doing
# as there can be urgent hotfixes that you are then not notified about. If you're confident
# that you can manage updates on your own, turn this on.
no-update-checker: false
no-update-checker: false
# Math expressions are parsed using Crunch, which allows for variables to be used in expressions.
# If this is false, variables will be used to represent placeholders, which leads to fewer
# expression compilations at the expense of slower evaluation times. If this is true, variables
# will instead be translated before compilation to reduce evaluation times at the expense of
# longer compilation times. If your expressions usually contain many variables, then you
# should enable this option as it may improve performance. If you're unsure, leave it disabled.
use-immediate-placeholder-translation-for-math: false
# The time (in milliseconds) for math expressions to be cached for. Higher values will lead to
# faster evaluation times (less CPU usage) at the expense of slightly more memory usage and
# less reactive values.
math-cache-ttl: 200

View File

@@ -185,4 +185,14 @@ dependencies:
required: false
bootstrap: false
- name: UltraEconomy
required: false
bootstrap: false
- name: PlayerPoints
required: false
bootstrap: false
- name: Denizen
required: false
bootstrap: false

View File

@@ -51,3 +51,6 @@ softdepend:
- ModelEngine
- PvPManager
- DeluxeMenus
- UltraEconomy
- PlayerPoints
- Denizen

View File

@@ -1,3 +1,3 @@
version = 6.55.1
version = 6.56.0
plugin-name = eco
kotlin.code.style = official