Compare commits

..

100 Commits

Author SHA1 Message Date
Auxilor
55fc6d762f Updated folia scheduler 2023-05-09 13:48:57 +01:00
Auxilor
b9c5eb2b4e Merge branch 'master' into folia
# Conflicts:
#	eco-core/core-plugin/build.gradle
2023-05-09 13:00:09 +01:00
Auxilor
37e271c96c More optimisations to EcoConfig 2023-05-04 14:32:52 +01:00
Auxilor
3dad48e24d Updated to 6.57.2 2023-05-03 23:45:01 +01:00
Auxilor
ae77e4810b Digsusting hacks to optimise eval pipeline 2023-05-03 23:44:53 +01:00
Auxilor
3d50e37c37 Merge branch 'master' into develop 2023-05-03 23:01:41 +01:00
Auxilor
421fd3bd04 Finally removed LegacyMySQLDataHandler 2023-05-03 16:03:36 +01:00
Auxilor
5ecae0a366 Updated to 6.57.1 2023-05-03 14:19:01 +01:00
Auxilor
5de4914fd7 Fixed expression loading, improved hash codes down evaluation pipeline 2023-05-03 14:18:50 +01:00
Auxilor
0f9bf094ae Ignore case 2023-05-02 18:47:54 +01:00
Auxilor
e67680f397 Improved PlaceholderAPI 2023-05-02 17:39:15 +01:00
Auxilor
73c0a5d655 Fixed more stupidity 2023-05-02 17:17:41 +01:00
Auxilor
220ed26f4a Fixed stupidity 2023-05-02 17:14:03 +01:00
Auxilor
edf2ea41c7 Cleaned up RECIPE ERROR message to be nicer 2023-05-02 16:13:33 +01:00
Auxilor
16859b8ce5 Revert "Temporarily disabled Wolfyscript"
This reverts commit f973281dd9.
2023-05-02 15:01:01 +01:00
Auxilor
10fe7d190a Clean 2023-05-02 14:58:25 +01:00
Auxilor
60a1f2429c Added plugin-version and plugin to extension.yml 2023-05-02 14:26:53 +01:00
Auxilor
cc6dc1e67c Improved PAPI expansion 2023-05-02 13:57:23 +01:00
Auxilor
f4f5941691 Fixed duplicate config placeholder bug 2023-05-02 13:56:10 +01:00
Auxilor
f973281dd9 Temporarily disabled Wolfyscript 2023-05-02 13:55:50 +01:00
Auxilor
8acd76f363 Updated to 6.57.0 2023-05-01 19:53:45 +01:00
Auxilor
4a165a86dc Added PluginLike#getFile 2023-05-01 19:53:37 +01:00
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
Auxilor
58316c2a06 Fixed locale bug with Registry 2023-04-20 20:32:07 +01:00
Auxilor
d96ad10960 Added locker to registry 2023-04-20 19:27:52 +01:00
Auxilor
21283b0928 Added ExternalDataStoreObjectAdapter 2023-04-20 19:19:00 +01:00
Auxilor
2797687f01 Cleanup 2023-04-20 17:54:23 +01:00
Auxilor
9c68d1fbc3 Merge branch 'master' into develop
# Conflicts:
#	eco-core/core-plugin/src/main/kotlin/com/willfp/eco/internal/spigot/integrations/customitems/CustomItemsOraxen.kt
2023-04-20 17:45:06 +01:00
Auxilor
9371d9b88d Upstream 2023-04-20 17:44:58 +01:00
Auxilor
14e2ead488 Fixed javadoc, updated Kingdoms 2023-04-20 17:42:46 +01:00
Auxilor
5473bb8ef8 Cleaned up IntegrationRegistry 2023-04-20 17:34:18 +01:00
Auxilor
90c55849ae Isolated integration registration 2023-04-20 17:24:56 +01:00
Auxilor
9809140cf9 Isolated remaining integrations 2023-04-20 17:23:24 +01:00
Auxilor
49a82dc005 Isolated ShopManager 2023-04-20 17:15:18 +01:00
Auxilor
92dec03b9a Added IntegrationRegistry 2023-04-20 17:09:46 +01:00
Auxilor
7453c70b87 Fixed imports 2023-04-20 16:52:49 +01:00
Auxilor
3038ea43d0 Updated to 6.55.0 2023-04-20 16:51:11 +01:00
Auxilor
3c6ddd8255 Added OutdatedEcoVersionError 2023-04-20 16:50:35 +01:00
Auxilor
925ee04cc1 Added Version as a cross-version compatible version of DefaultArtifactVersion 2023-04-20 16:48:20 +01:00
Auxilor
6f7de8716b Added KRegistrable 2023-04-20 16:41:35 +01:00
Auxilor
a17b951a8b Made registry lockable and iterable 2023-04-20 16:40:06 +01:00
Auxilor
f003ed06a8 Fixed imports 2023-04-19 18:00:19 +01:00
Auxilor
f864953da2 Updated to 6.54.1 2023-04-19 17:38:22 +01:00
Auxilor
6ef31444ac Removed Checkstyle CI, cleaned up Oraxen PR 2023-04-19 17:38:09 +01:00
Will FP
99258116de Merge pull request #264 from MCCasper/master
fix oraxen and SNBT
2023-04-19 12:33:14 -04:00
casper
a59c68102e fix SNBT matching the same tag for diff materials 2023-04-19 11:09:26 -05:00
casper
2482525fe2 load oraxen later 2023-04-18 22:23:49 -05:00
Auxilor
393d0031c7 Added folia support 2023-03-12 12:11:12 +00:00
106 changed files with 3287 additions and 1303 deletions

View File

@@ -1,15 +0,0 @@
name: Check PR Codestyle
on: [ pull_request ]
jobs:
checkstyle:
name: Checkstyle
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: dbelyaev/action-checkstyle@v0.5.1
with:
github_token: ${{ secrets.github_token }}
reporter: github-pr-review
level: warning
checkstyle_config: ../../config/checkstyle/checkstyle.xml

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

@@ -19,9 +19,10 @@ import com.willfp.eco.core.proxy.ProxyFactory;
import com.willfp.eco.core.registry.Registrable;
import com.willfp.eco.core.registry.Registry;
import com.willfp.eco.core.scheduling.Scheduler;
import com.willfp.eco.core.version.OutdatedEcoVersionError;
import com.willfp.eco.core.version.Version;
import com.willfp.eco.core.web.UpdateChecker;
import org.apache.commons.lang.Validate;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.bukkit.Bukkit;
import org.bukkit.NamespacedKey;
import org.bukkit.configuration.file.FileConfiguration;
@@ -32,6 +33,7 @@ import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
@@ -347,14 +349,14 @@ public abstract class EcoPlugin extends JavaPlugin implements PluginLike, Regist
they have an outdated version of eco installed.
*/
DefaultArtifactVersion runningVersion = new DefaultArtifactVersion(Eco.get().getEcoPlugin().getDescription().getVersion());
DefaultArtifactVersion requiredVersion = new DefaultArtifactVersion(this.getMinimumEcoVersion());
Version runningVersion = new Version(Eco.get().getEcoPlugin().getDescription().getVersion());
Version requiredVersion = new Version(this.getMinimumEcoVersion());
if (!(runningVersion.compareTo(requiredVersion) > 0 || runningVersion.equals(requiredVersion))) {
this.getLogger().severe("You are running an outdated version of eco!");
this.getLogger().severe("You must be on at least" + this.getMinimumEcoVersion());
this.getLogger().severe("Download the newest version here:");
this.getLogger().severe("https://polymart.org/download/773/recent/JSpprMspkuyecf5y1wQ2Jn8OoLQSQ_IW");
Bukkit.getPluginManager().disablePlugin(this);
throw new OutdatedEcoVersionError("This plugin requires at least eco version " + this.getMinimumEcoVersion() + " to run.");
}
}
@@ -365,13 +367,12 @@ 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")) {
new UpdateChecker(this).getVersion(version -> {
DefaultArtifactVersion currentVersion = new DefaultArtifactVersion(this.getDescription().getVersion());
DefaultArtifactVersion mostRecentVersion = new DefaultArtifactVersion(version);
Version currentVersion = new Version(this.getDescription().getVersion());
Version mostRecentVersion = new Version(version);
if (!(currentVersion.compareTo(mostRecentVersion) > 0 || currentVersion.equals(mostRecentVersion))) {
this.outdated = true;
this.getLogger().warning(this.getName() + " is out of date! (Version " + this.getDescription().getVersion() + ")");
@@ -405,7 +406,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();
@@ -427,17 +430,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("");
}
/**
@@ -1081,7 +1086,7 @@ public abstract class EcoPlugin extends JavaPlugin implements PluginLike, Regist
*
* @return The config handler.
*/
public ConfigHandler getConfigHandler() {
public @NotNull ConfigHandler getConfigHandler() {
return this.configHandler;
}
@@ -1150,4 +1155,9 @@ public abstract class EcoPlugin extends JavaPlugin implements PluginLike, Regist
public final String getID() {
return Registry.tryFitPattern(this.getName());
}
@Override
public @NotNull File getFile() {
return super.getFile();
}
}

View File

@@ -1,6 +1,8 @@
package com.willfp.eco.core;
import com.willfp.eco.core.config.updating.ConfigHandler;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.logging.Logger;
@@ -13,12 +15,13 @@ import java.util.logging.Logger;
*/
public interface PluginLike {
/**
* Get the data folder of the object.
* Get the data folder.
* <p>
* Returns the plugin data folder for a plugin, or the extension's parent plugin's folder
*
* @return The data folder.
*/
@NotNull
File getDataFolder();
/**
@@ -26,6 +29,7 @@ public interface PluginLike {
*
* @return The config handler.
*/
@NotNull
ConfigHandler getConfigHandler();
/**
@@ -33,5 +37,16 @@ public interface PluginLike {
*
* @return The logger.
*/
@NotNull
Logger getLogger();
/**
* Get the actual file.
*
* @return The file, i.e. the jar file.
*/
@Nullable
default File getFile() {
return null;
}
}

View File

@@ -26,7 +26,7 @@ public class Prerequisite {
*/
public static final Prerequisite HAS_PAPER = new Prerequisite(
() -> ClassUtils.exists("com.destroystokyo.paper.event.block.BeaconEffectEvent"),
"Requires server to be running paper (or a fork)"
"Requires server to be running paper"
);
/**
@@ -69,7 +69,7 @@ public class Prerequisite {
@Deprecated(since = "6.49.0", forRemoval = true)
public static final Prerequisite HAS_BUNGEECORD = new Prerequisite(
() -> ClassUtils.exists("net.md_5.bungee.api.event.ServerConnectedEvent"),
"Requires server to be running BungeeCord (or a fork)"
"Requires server to be running BungeeCord"
);
/**
@@ -80,7 +80,15 @@ public class Prerequisite {
@Deprecated(since = "6.49.0", forRemoval = true)
public static final Prerequisite HAS_VELOCITY = new Prerequisite(
() -> ClassUtils.exists("com.velocitypowered.api.event.player.ServerConnectedEvent"),
"Requires server to be running Velocity (or a fork)"
"Requires server to be running Velocity"
);
/**
* Requires the server to be running an implementation of Folia.
*/
public static final Prerequisite HAS_FOLIA = new Prerequisite(
() -> ClassUtils.exists("io.papermc.paper.threadedregions.scheduler.RegionisedScheduler"),
"Requires server to be running Folia!"
);
/**

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

@@ -3,17 +3,26 @@ package com.willfp.eco.core.data;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
/**
* A simple store key-value store for data to be stored outside of plugins.
*/
@SuppressWarnings("unchecked")
public final class ExternalDataStore {
/**
* The store.
*/
private static final HashMap<String, Object> data = new HashMap<>();
private static final Map<String, Object> DATA = new HashMap<>();
/**
* The store adapters.
*/
private static final List<ExternalDataStoreObjectAdapter<?, ?>> STORE_ADAPTERS = new ArrayList<>();
/**
* Put data into the store.
@@ -23,7 +32,29 @@ public final class ExternalDataStore {
*/
public static void put(@NotNull final String key,
@NotNull final Object value) {
data.put(key, value);
doPut(key, value);
}
/**
* Put data into the store.
*
* @param key The key.
* @param value The value.
* @param <A> The stored type.
*/
private static <A> void doPut(@NotNull final String key,
@NotNull final A value) {
Object storedValue = value;
for (ExternalDataStoreObjectAdapter<?, ?> unknownAdapter : STORE_ADAPTERS) {
if (unknownAdapter.getAccessedClass().isInstance(value)) {
ExternalDataStoreObjectAdapter<A, ?> adapter = (ExternalDataStoreObjectAdapter<A, ?>) unknownAdapter;
storedValue = adapter.toStoredObject(value);
break;
}
}
DATA.put(key, storedValue);
}
/**
@@ -37,7 +68,30 @@ public final class ExternalDataStore {
@Nullable
public static <T> T get(@NotNull final String key,
@NotNull final Class<T> clazz) {
Object value = data.get(key);
return doGet(key, clazz);
}
/**
* Get data from the store.
*
* @param key The key.
* @param clazz The class.
* @param <A> The accessed type.
* @param <S> The stored type.
* @return The value.
*/
@Nullable
private static <A, S> A doGet(@NotNull final String key,
@NotNull final Class<A> clazz) {
Object value = DATA.get(key);
for (ExternalDataStoreObjectAdapter<?, ?> unknownAdapter : STORE_ADAPTERS) {
if (unknownAdapter.getStoredClass().isInstance(value) && unknownAdapter.getAccessedClass().equals(clazz)) {
ExternalDataStoreObjectAdapter<A, S> adapter = (ExternalDataStoreObjectAdapter<A, S>) unknownAdapter;
value = adapter.toAccessedObject((S) value);
break;
}
}
if (clazz.isInstance(value)) {
return clazz.cast(value);
@@ -79,6 +133,15 @@ public final class ExternalDataStore {
return get(key, clazz, defaultValue.get());
}
/**
* Register a new adapter.
*
* @param adapter The adapter.
*/
public static void registerAdapter(@NotNull final ExternalDataStoreObjectAdapter<?, ?> adapter) {
STORE_ADAPTERS.add(adapter);
}
private ExternalDataStore() {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
}

View File

@@ -0,0 +1,69 @@
package com.willfp.eco.core.data;
import org.jetbrains.annotations.NotNull;
/**
* An adapter for objects stored in {@link ExternalDataStore}.
*
* @param <A> The accessed class.
* @param <S> The stored class.
*/
public abstract class ExternalDataStoreObjectAdapter<A, S> {
/**
* The class that is accessed (read / written).
*/
private final Class<? extends A> accessedClass;
/**
* The class that is stored internally.
*/
private final Class<? extends S> storedClass;
/**
* Create a new adapter.
*
* @param accessedClass The class that is accessed (read / written).
* @param storedClass The class that is stored internally.
*/
protected ExternalDataStoreObjectAdapter(@NotNull final Class<? extends A> accessedClass,
@NotNull final Class<? extends S> storedClass) {
this.accessedClass = accessedClass;
this.storedClass = storedClass;
}
/**
* Convert an object to the stored object.
*
* @param obj The object.
* @return The stored object.
*/
@NotNull
public abstract S toStoredObject(@NotNull final A obj);
/**
* Convert an object to the accessed object.
*
* @param obj The object.
* @return The accessed object.
*/
@NotNull
public abstract A toAccessedObject(@NotNull final S obj);
/**
* Get the class that is accessed (read / written).
*
* @return The class.
*/
public Class<? extends A> getAccessedClass() {
return accessedClass;
}
/**
* Get the class that is stored internally.
*
* @return The class.
*/
public Class<? extends S> getStoredClass() {
return storedClass;
}
}

View File

@@ -135,20 +135,26 @@ public abstract class Extension implements PluginLike {
}
@Override
public File getDataFolder() {
public @NotNull File getDataFolder() {
return this.plugin.getDataFolder();
}
@Override
public ConfigHandler getConfigHandler() {
public @NotNull ConfigHandler getConfigHandler() {
return this.plugin.getConfigHandler();
}
@Override
public Logger getLogger() {
public @NotNull Logger getLogger() {
return this.plugin.getLogger();
}
@Override
public @NotNull File getFile() {
Validate.notNull(metadata, "Metadata cannot be null!");
return this.metadata.file();
}
/**
* Get the plugin for the extension.
*

View File

@@ -0,0 +1,17 @@
package com.willfp.eco.core.extensions;
import org.jetbrains.annotations.NotNull;
/**
* Generic exception in extension loading.
*/
public class ExtensionLoadException extends RuntimeException {
/**
* Create a new ExtensionLoadException.
*
* @param errorMessage The error message to show.
*/
public ExtensionLoadException(@NotNull final String errorMessage) {
super(errorMessage);
}
}

View File

@@ -1,7 +1,10 @@
package com.willfp.eco.core.extensions;
import com.willfp.eco.core.version.Version;
import org.jetbrains.annotations.NotNull;
import java.io.File;
/**
* The extension's metadata.
* <p>
@@ -13,6 +16,23 @@ import org.jetbrains.annotations.NotNull;
*/
public record ExtensionMetadata(@NotNull String version,
@NotNull String name,
@NotNull String author) {
@NotNull String author,
@NotNull File file,
@NotNull Version minimumPluginVersion) {
/**
* Legacy constructor.
*
* @param version The extension version.
* @param name The extension name.
* @param author The extension's author.
* @deprecated Use {@link ExtensionMetadata#ExtensionMetadata(String, String, String, File, Version)} instead.
*/
@SuppressWarnings("ConstantConditions")
@Deprecated(since = "6.57.0", forRemoval = true)
public ExtensionMetadata(@NotNull String version,
@NotNull String name,
@NotNull String author) {
this(version, name, author, null, null);
throw new UnsupportedOperationException("Legacy constructor is not supported.");
}
}

View File

@@ -7,7 +7,7 @@ import org.jetbrains.annotations.NotNull;
* Missing or invalid extension.yml.
* Invalid filetype.
*/
public class MalformedExtensionException extends RuntimeException {
public class MalformedExtensionException extends ExtensionLoadException {
/**
* Create a new MalformedExtensionException.
*

View File

@@ -1,13 +1,22 @@
package com.willfp.eco.core.integrations;
import com.willfp.eco.core.registry.Registrable;
import com.willfp.eco.core.registry.Registry;
import org.jetbrains.annotations.NotNull;
/**
* Abstract class for integrations.
*/
public interface Integration {
public interface Integration extends Registrable {
/**
* Get the name of integration.
*
* @return The name.
*/
String getPluginName();
@Override
default @NotNull String getID() {
return Registry.tryFitPattern(this.getPluginName());
}
}

View File

@@ -0,0 +1,131 @@
package com.willfp.eco.core.integrations;
import com.willfp.eco.core.Eco;
import com.willfp.eco.core.registry.Registry;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashSet;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
/**
* Registry for integrations.
*
* @param <T> The type of integration.
*/
public class IntegrationRegistry<T extends Integration> extends Registry<T> {
@Override
public @NotNull T register(@NotNull final T element) {
return executeSafely(() -> super.register(element), element);
}
/**
* Iterate over all integrations, safely.
*
* @param action The action to perform.
*/
public void forEachSafely(@NotNull final Consumer<T> action) {
for (T integration : new HashSet<>(this.values())) {
executeSafely(() -> action.accept(integration), integration);
}
}
/**
* If any integrations return true, safely.
*
* @param predicate The predicate to test.
* @return If any integrations return true.
*/
public boolean anySafely(@NotNull final Predicate<T> predicate) {
for (T integration : new HashSet<>(this.values())) {
Boolean result = executeSafely(() -> predicate.test(integration), integration);
if (result != null && result) {
return true;
}
}
return false;
}
/**
* Get the first integration that returns a value, safely.
*
* @param function The function to apply.
* @param defaultValue The default value.
* @param <R> The type of value.
* @return The first value that returns a value.
*/
@NotNull
public <R> R firstSafely(@NotNull final Function<T, R> function,
@NotNull final R defaultValue) {
if (this.isEmpty()) {
return defaultValue;
}
T integration = this.iterator().next();
return executeSafely(() -> function.apply(integration), integration, defaultValue);
}
/**
* Executes a given action safely, catching any exceptions and logging the issue.
*
* @param action The action to execute.
* @param integration The integration to apply the action on.
*/
private void executeSafely(@NotNull final Runnable action,
@NotNull final T integration) {
executeSafely(() -> {
action.run();
return null;
}, integration);
}
/**
* Executes a given action safely, catching any exceptions and logging the issue.
*
* @param action The action to execute.
* @param integration The integration to apply the action on.
* @param <R> The return type of the action.
* @return The result of the action, or null if an exception was thrown.
*/
private <R> R executeSafely(@NotNull final Supplier<R> action,
@NotNull final T integration) {
return executeSafely(action, integration, null);
}
/**
* Executes a given action safely, catching any exceptions and logging the issue.
*
* @param action The action to execute.
* @param integration The integration to apply the action on.
* @param defaultValue The default value to return if an exception is thrown.
* @param <R> The return type of the action.
* @return The result of the action, or the default value if an exception was thrown.
*/
private <R> R executeSafely(@NotNull final Supplier<R> action,
@NotNull final T integration,
@Nullable final R defaultValue) {
try {
return action.get();
} catch (final Exception e) {
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);
return defaultValue;
}
}
/**
* If all integrations return true, safely.
*
* @param predicate The predicate to test.
* @return If all integrations return true.
*/
public boolean allSafely(@NotNull final Predicate<T> predicate) {
return !this.anySafely(predicate.negate());
}
}

View File

@@ -1,11 +1,9 @@
package com.willfp.eco.core.integrations.afk;
import com.willfp.eco.core.integrations.IntegrationRegistry;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.HashSet;
import java.util.Set;
/**
* Class to handle afk integrations.
*/
@@ -13,7 +11,7 @@ public final class AFKManager {
/**
* A set of all registered integrations.
*/
private static final Set<AFKIntegration> REGISTERED = new HashSet<>();
private static final IntegrationRegistry<AFKIntegration> REGISTRY = new IntegrationRegistry<>();
/**
* Register a new integration.
@@ -21,8 +19,7 @@ public final class AFKManager {
* @param integration The integration to register.
*/
public static void register(@NotNull final AFKIntegration integration) {
REGISTERED.removeIf(it -> it.getPluginName().equalsIgnoreCase(integration.getPluginName()));
REGISTERED.add(integration);
REGISTRY.register(integration);
}
/**
@@ -32,13 +29,7 @@ public final class AFKManager {
* @return If afk.
*/
public static boolean isAfk(@NotNull final Player player) {
for (AFKIntegration integration : REGISTERED) {
if (integration.isAfk(player)) {
return true;
}
}
return false;
return REGISTRY.anySafely(integration -> integration.isAfk(player));
}
private AFKManager() {

View File

@@ -1,13 +1,11 @@
package com.willfp.eco.core.integrations.anticheat;
import com.willfp.eco.core.Eco;
import com.willfp.eco.core.integrations.IntegrationRegistry;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.jetbrains.annotations.NotNull;
import java.util.HashSet;
import java.util.Set;
/**
* Class to handle anticheat integrations.
*/
@@ -15,7 +13,7 @@ public final class AnticheatManager {
/**
* A set of all registered anticheats.
*/
private static final Set<AnticheatIntegration> ANTICHEATS = new HashSet<>();
private static final IntegrationRegistry<AnticheatIntegration> REGISTRY = new IntegrationRegistry<>();
/**
* Register a new anticheat.
@@ -26,8 +24,7 @@ public final class AnticheatManager {
if (anticheat instanceof Listener) {
Eco.get().getEcoPlugin().getEventManager().registerListener((Listener) anticheat);
}
ANTICHEATS.removeIf(it -> it.getPluginName().equalsIgnoreCase(anticheat.getPluginName()));
ANTICHEATS.add(anticheat);
REGISTRY.register(anticheat);
}
/**
@@ -36,17 +33,16 @@ public final class AnticheatManager {
* @param player The player to exempt.
*/
public static void exemptPlayer(@NotNull final Player player) {
ANTICHEATS.forEach(anticheat -> anticheat.exempt(player));
REGISTRY.forEachSafely(anticheat -> anticheat.exempt(player));
}
/**
* Unexempt a player from triggering anticheats.
* This is ran a tick after it is called to ensure that there are no event timing conflicts.
*
* @param player The player to remove the exemption.
*/
public static void unexemptPlayer(@NotNull final Player player) {
ANTICHEATS.forEach(anticheat -> anticheat.unexempt(player));
REGISTRY.forEachSafely(anticheat -> anticheat.unexempt(player));
}
private AnticheatManager() {

View File

@@ -1,5 +1,6 @@
package com.willfp.eco.core.integrations.antigrief;
import com.willfp.eco.core.integrations.IntegrationRegistry;
import org.bukkit.Location;
import org.bukkit.block.Block;
import org.bukkit.entity.LivingEntity;
@@ -16,7 +17,7 @@ public final class AntigriefManager {
/**
* Registered antigriefs.
*/
private static final Set<AntigriefIntegration> REGISTERED = new HashSet<>();
private static final IntegrationRegistry<AntigriefIntegration> REGISTRY = new IntegrationRegistry<>();
/**
* Register a new AntiGrief/Land Management integration.
@@ -24,8 +25,7 @@ public final class AntigriefManager {
* @param antigrief The integration to register.
*/
public static void register(@NotNull final AntigriefIntegration antigrief) {
REGISTERED.removeIf(it -> it.getPluginName().equalsIgnoreCase(antigrief.getPluginName()));
REGISTERED.add(antigrief);
REGISTRY.register(antigrief);
}
/**
@@ -34,8 +34,7 @@ public final class AntigriefManager {
* @param antigrief The integration to unregister.
*/
public static void unregister(@NotNull final AntigriefIntegration antigrief) {
REGISTERED.removeIf(it -> it.getPluginName().equalsIgnoreCase(antigrief.getPluginName()));
REGISTERED.remove(antigrief);
REGISTRY.remove(antigrief);
}
/**
@@ -47,7 +46,7 @@ public final class AntigriefManager {
*/
public static boolean canPickupItem(@NotNull final Player player,
@NotNull final Location location) {
return REGISTERED.stream().allMatch(antigriefIntegration -> antigriefIntegration.canPickupItem(player, location));
return REGISTRY.allSafely(integration -> integration.canPickupItem(player, location));
}
/**
@@ -59,7 +58,7 @@ public final class AntigriefManager {
*/
public static boolean canBreakBlock(@NotNull final Player player,
@NotNull final Block block) {
return REGISTERED.stream().allMatch(antigriefIntegration -> antigriefIntegration.canBreakBlock(player, block));
return REGISTRY.allSafely(integration -> integration.canBreakBlock(player, block));
}
/**
@@ -71,7 +70,7 @@ public final class AntigriefManager {
*/
public static boolean canCreateExplosion(@NotNull final Player player,
@NotNull final Location location) {
return REGISTERED.stream().allMatch(antigriefIntegration -> antigriefIntegration.canCreateExplosion(player, location));
return REGISTRY.allSafely(integration -> integration.canCreateExplosion(player, location));
}
/**
@@ -83,7 +82,7 @@ public final class AntigriefManager {
*/
public static boolean canPlaceBlock(@NotNull final Player player,
@NotNull final Block block) {
return REGISTERED.stream().allMatch(antigriefIntegration -> antigriefIntegration.canPlaceBlock(player, block));
return REGISTRY.allSafely(integration -> integration.canPlaceBlock(player, block));
}
/**
@@ -95,7 +94,7 @@ public final class AntigriefManager {
*/
public static boolean canInjure(@NotNull final Player player,
@NotNull final LivingEntity victim) {
return REGISTERED.stream().allMatch(antigriefIntegration -> antigriefIntegration.canInjure(player, victim));
return REGISTRY.allSafely(integration -> integration.canInjure(player, victim));
}
private AntigriefManager() {

View File

@@ -1,10 +1,8 @@
package com.willfp.eco.core.integrations.customentities;
import com.willfp.eco.core.integrations.IntegrationRegistry;
import org.jetbrains.annotations.NotNull;
import java.util.HashSet;
import java.util.Set;
/**
* Class to handle custom entity integrations.
*/
@@ -12,7 +10,7 @@ public final class CustomEntitiesManager {
/**
* A set of all registered integrations.
*/
private static final Set<CustomEntitiesIntegration> REGISTERED = new HashSet<>();
private static final IntegrationRegistry<CustomEntitiesIntegration> REGISTRY = new IntegrationRegistry<>();
/**
* Register a new integration.
@@ -20,8 +18,7 @@ public final class CustomEntitiesManager {
* @param integration The integration to register.
*/
public static void register(@NotNull final CustomEntitiesIntegration integration) {
REGISTERED.removeIf(it -> it.getPluginName().equalsIgnoreCase(integration.getPluginName()));
REGISTERED.add(integration);
REGISTRY.register(integration);
}
/**
@@ -30,9 +27,7 @@ public final class CustomEntitiesManager {
* @see com.willfp.eco.core.entities.Entities
*/
public static void registerAllEntities() {
for (CustomEntitiesIntegration integration : REGISTERED) {
integration.registerAllEntities();
}
REGISTRY.forEachSafely(CustomEntitiesIntegration::registerAllEntities);
}
private CustomEntitiesManager() {

View File

@@ -1,5 +1,6 @@
package com.willfp.eco.core.integrations.customitems;
import com.willfp.eco.core.integrations.IntegrationRegistry;
import org.jetbrains.annotations.NotNull;
import java.util.HashSet;
@@ -12,7 +13,7 @@ public final class CustomItemsManager {
/**
* A set of all registered integrations.
*/
private static final Set<CustomItemsIntegration> REGISTERED = new HashSet<>();
private static final IntegrationRegistry<CustomItemsIntegration> REGISTRY = new IntegrationRegistry<>();
/**
* Register a new integration.
@@ -20,8 +21,7 @@ public final class CustomItemsManager {
* @param integration The integration to register.
*/
public static void register(@NotNull final CustomItemsIntegration integration) {
REGISTERED.removeIf(it -> it.getPluginName().equalsIgnoreCase(integration.getPluginName()));
REGISTERED.add(integration);
REGISTRY.register(integration);
}
/**
@@ -30,9 +30,7 @@ public final class CustomItemsManager {
* @see com.willfp.eco.core.items.Items
*/
public static void registerAllItems() {
for (CustomItemsIntegration customItemsIntegration : REGISTERED) {
customItemsIntegration.registerAllItems();
}
REGISTRY.forEachSafely(CustomItemsIntegration::registerAllItems);
}
/**
@@ -41,9 +39,7 @@ public final class CustomItemsManager {
* @see com.willfp.eco.core.items.Items
*/
public static void registerProviders() {
for (CustomItemsIntegration customItemsIntegration : REGISTERED) {
customItemsIntegration.registerProvider();
}
REGISTRY.forEachSafely(CustomItemsIntegration::registerProvider);
}
private CustomItemsManager() {

View File

@@ -1,11 +1,10 @@
package com.willfp.eco.core.integrations.economy;
import com.willfp.eco.core.integrations.IntegrationRegistry;
import org.bukkit.OfflinePlayer;
import org.jetbrains.annotations.NotNull;
import java.math.BigDecimal;
import java.util.HashSet;
import java.util.Set;
/**
* Class to handle economy.
@@ -14,7 +13,7 @@ public final class EconomyManager {
/**
* A set of all registered integrations.
*/
private static final Set<EconomyIntegration> REGISTERED = new HashSet<>();
private static final IntegrationRegistry<EconomyIntegration> REGISTRY = new IntegrationRegistry<>();
/**
* Register a new integration.
@@ -22,8 +21,7 @@ public final class EconomyManager {
* @param integration The integration to register.
*/
public static void register(@NotNull final EconomyIntegration integration) {
REGISTERED.removeIf(it -> it.getPluginName().equalsIgnoreCase(integration.getPluginName()));
REGISTERED.add(integration);
REGISTRY.register(integration);
}
/**
@@ -32,7 +30,7 @@ public final class EconomyManager {
* @return If any economy.
*/
public static boolean hasRegistrations() {
return !REGISTERED.isEmpty();
return REGISTRY.isNotEmpty();
}
/**
@@ -56,11 +54,10 @@ public final class EconomyManager {
*/
public static boolean hasAmount(@NotNull final OfflinePlayer player,
final BigDecimal amount) {
for (EconomyIntegration integration : REGISTERED) {
return integration.hasAmount(player, amount);
}
return false;
return REGISTRY.firstSafely(
integration -> integration.hasAmount(player, amount),
false
);
}
/**
@@ -84,11 +81,10 @@ public final class EconomyManager {
*/
public static boolean giveMoney(@NotNull final OfflinePlayer player,
@NotNull final BigDecimal amount) {
for (EconomyIntegration integration : REGISTERED) {
return integration.giveMoney(player, amount);
}
return false;
return REGISTRY.firstSafely(
integration -> integration.giveMoney(player, amount),
false
);
}
/**
@@ -112,11 +108,10 @@ public final class EconomyManager {
*/
public static boolean removeMoney(@NotNull final OfflinePlayer player,
@NotNull final BigDecimal amount) {
for (EconomyIntegration integration : REGISTERED) {
return integration.removeMoney(player, amount);
}
return false;
return REGISTRY.firstSafely(
integration -> integration.removeMoney(player, amount),
false
);
}
/**
@@ -136,11 +131,10 @@ public final class EconomyManager {
* @return The balance.
*/
public static BigDecimal getExactBalance(@NotNull final OfflinePlayer player) {
for (EconomyIntegration integration : REGISTERED) {
return integration.getExactBalance(player);
}
return BigDecimal.ZERO;
return REGISTRY.firstSafely(
integration -> integration.getExactBalance(player),
BigDecimal.ZERO
);
}
private EconomyManager() {

View File

@@ -1,12 +1,10 @@
package com.willfp.eco.core.integrations.guidetection;
import com.willfp.eco.core.integrations.IntegrationRegistry;
import com.willfp.eco.util.MenuUtils;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import java.util.HashSet;
import java.util.Set;
/**
* Class to handle GUI detection.
*/
@@ -14,7 +12,7 @@ public final class GUIDetectionManager {
/**
* A set of all registered integrations.
*/
private static final Set<GUIDetectionIntegration> REGISTERED = new HashSet<>();
private static final IntegrationRegistry<GUIDetectionIntegration> REGISTRY = new IntegrationRegistry<>();
/**
* Register a new integration.
@@ -22,8 +20,7 @@ public final class GUIDetectionManager {
* @param integration The integration to register.
*/
public static void register(@NotNull final GUIDetectionIntegration integration) {
REGISTERED.removeIf(it -> it.getPluginName().equalsIgnoreCase(integration.getPluginName()));
REGISTERED.add(integration);
REGISTRY.register(integration);
}
/**
@@ -37,13 +34,7 @@ public final class GUIDetectionManager {
return true;
}
for (GUIDetectionIntegration integration : REGISTERED) {
if (integration.hasGUIOpen(player)) {
return true;
}
}
return false;
return REGISTRY.anySafely(integration -> integration.hasGUIOpen(player));
}
private GUIDetectionManager() {

View File

@@ -1,11 +1,10 @@
package com.willfp.eco.core.integrations.hologram;
import com.willfp.eco.core.integrations.IntegrationRegistry;
import org.bukkit.Location;
import org.jetbrains.annotations.NotNull;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Class to handle hologram integrations.
@@ -14,7 +13,7 @@ public final class HologramManager {
/**
* A set of all registered integrations.
*/
private static final Set<HologramIntegration> REGISTERED = new HashSet<>();
private static final IntegrationRegistry<HologramIntegration> REGISTRY = new IntegrationRegistry<>();
/**
* Register a new integration.
@@ -22,8 +21,7 @@ public final class HologramManager {
* @param integration The integration to register.
*/
public static void register(@NotNull final HologramIntegration integration) {
REGISTERED.removeIf(it -> it.getPluginName().equalsIgnoreCase(integration.getPluginName()));
REGISTERED.add(integration);
REGISTRY.register(integration);
}
/**
@@ -35,11 +33,10 @@ public final class HologramManager {
*/
public static Hologram createHologram(@NotNull final Location location,
@NotNull final List<String> contents) {
for (HologramIntegration integration : REGISTERED) {
return integration.createHologram(location, contents);
}
return new DummyHologram();
return REGISTRY.firstSafely(
integration -> integration.createHologram(location, contents),
new DummyHologram()
);
}
private HologramManager() {

View File

@@ -1,5 +1,6 @@
package com.willfp.eco.core.integrations.mcmmo;
import com.willfp.eco.core.integrations.IntegrationRegistry;
import org.bukkit.block.Block;
import org.bukkit.event.Event;
import org.jetbrains.annotations.NotNull;
@@ -14,7 +15,7 @@ public final class McmmoManager {
/**
* A set of all registered integrations.
*/
private static final Set<McmmoIntegration> REGISTERED = new HashSet<>();
private static final IntegrationRegistry<McmmoIntegration> REGISTERED = new IntegrationRegistry<>();
/**
* Register a new integration.
@@ -22,8 +23,7 @@ public final class McmmoManager {
* @param integration The integration to register.
*/
public static void register(@NotNull final McmmoIntegration integration) {
REGISTERED.removeIf(it -> it.getPluginName().equalsIgnoreCase(integration.getPluginName()));
REGISTERED.add(integration);
REGISTERED.register(integration);
}
/**
@@ -34,13 +34,11 @@ public final class McmmoManager {
*/
public static int getBonusDropCount(@NotNull final Block block) {
int finalValue = 0;
for (McmmoIntegration mcmmoIntegration : REGISTERED) {
finalValue += mcmmoIntegration.getBonusDropCount(block);
}
return finalValue;
}
@@ -51,13 +49,7 @@ public final class McmmoManager {
* @return If the event is fake.
*/
public static boolean isFake(@NotNull final Event event) {
for (McmmoIntegration mcmmoIntegration : REGISTERED) {
if (mcmmoIntegration.isFake(event)) {
return true;
}
}
return false;
return REGISTERED.anySafely(integration -> integration.isFake(event));
}
private McmmoManager() {

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

@@ -1,5 +1,6 @@
package com.willfp.eco.core.integrations.shop;
import com.willfp.eco.core.integrations.IntegrationRegistry;
import com.willfp.eco.core.price.Price;
import com.willfp.eco.core.price.impl.PriceFree;
import org.bukkit.entity.Player;
@@ -13,11 +14,12 @@ import java.util.Set;
/**
* Class to handle shop integrations.
*/
@SuppressWarnings("DeprecatedIsStillUsed")
public final class ShopManager {
/**
* A set of all registered integrations.
*/
private static final Set<ShopIntegration> REGISTERED = new HashSet<>();
private static final IntegrationRegistry<ShopIntegration> REGISTRY = new IntegrationRegistry<>();
/**
* Register a new integration.
@@ -25,17 +27,14 @@ public final class ShopManager {
* @param integration The integration to register.
*/
public static void register(@NotNull final ShopIntegration integration) {
REGISTERED.removeIf(it -> it.getPluginName().equalsIgnoreCase(integration.getPluginName()));
REGISTERED.add(integration);
REGISTRY.register(integration);
}
/**
* Register eco item provider for shop plugins.
*/
public static void registerEcoProvider() {
for (ShopIntegration shopIntegration : REGISTERED) {
shopIntegration.registerEcoProvider();
}
REGISTRY.forEachSafely(ShopIntegration::registerEcoProvider);
}
/**
@@ -51,11 +50,7 @@ public final class ShopManager {
return false;
}
for (ShopIntegration integration : REGISTERED) {
return integration.isSellable(itemStack, player);
}
return false;
return REGISTRY.anySafely(integration -> integration.isSellable(itemStack, player));
}
/**
@@ -74,11 +69,10 @@ public final class ShopManager {
return new PriceFree();
}
for (ShopIntegration integration : REGISTERED) {
return integration.getUnitValue(itemStack, player);
}
return new PriceFree();
return REGISTRY.firstSafely(
integration -> integration.getUnitValue(itemStack, player),
new PriceFree()
);
}
/**
@@ -108,11 +102,10 @@ public final class ShopManager {
return 0.0;
}
for (ShopIntegration shopIntegration : REGISTERED) {
return shopIntegration.getUnitValue(itemStack, player).getValue(player, itemStack.getAmount());
}
return 0.0;
return REGISTRY.firstSafely(
integration -> integration.getUnitValue(itemStack, player).getValue(player, itemStack.getAmount()),
0.0
);
}
/**
@@ -121,7 +114,7 @@ public final class ShopManager {
* @return The integrations.
*/
public static Set<ShopIntegration> getRegisteredIntegrations() {
return new HashSet<>(REGISTERED);
return new HashSet<>(REGISTRY.values());
}
private ShopManager() {

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,92 @@
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 org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
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;
}
@Override
public boolean equals(@Nullable final Object o) {
if (this == o) {
return true;
}
if (!(o instanceof MergedInjectableContext that)) {
return false;
}
return Objects.equals(baseContext, that.baseContext)
&& Objects.equals(additionalContext, that.additionalContext)
&& Objects.equals(extraInjections, that.extraInjections);
}
@Override
public int hashCode() {
return Objects.hash(baseContext, additionalContext, extraInjections);
}
}

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

@@ -127,8 +127,8 @@ public final class Recipes {
}
if (builder.isAir()) {
Bukkit.getLogger().warning("RECIPE ERROR! " + plugin.getName() + ":" + key + " consists only");
Bukkit.getLogger().warning("of air or invalid items! Please change that or disable this recipe.");
Bukkit.getLogger().warning("Crafting recipe " + plugin.getID() + ":" + key + " consists only");
Bukkit.getLogger().warning("of air or invalid items! It will not be registered.");
return null;
}

View File

@@ -5,6 +5,8 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
@@ -14,7 +16,7 @@ import java.util.regex.Pattern;
*
* @param <T> The type of {@link Registrable}.
*/
public class Registry<T extends Registrable> {
public class Registry<T extends Registrable> implements Iterable<T> {
/**
* The ID pattern.
*/
@@ -25,6 +27,17 @@ public class Registry<T extends Registrable> {
*/
private final Map<String, T> registry = new HashMap<>();
/**
* If the registry is locked.
*/
private boolean isLocked = false;
/**
* The locker, used to 'secure' registries and prevent random unlocking.
*/
@Nullable
private Object locker = null;
/**
* Instantiate a new registry.
*/
@@ -40,6 +53,10 @@ public class Registry<T extends Registrable> {
*/
@NotNull
public T register(@NotNull final T element) {
if (this.isLocked) {
throw new IllegalStateException("Cannot add to locked registry! (ID: " + element.getID() + ")");
}
Validate.isTrue(ID_PATTERN.matcher(element.getID()).matches(), "ID must match pattern: " + ID_PATTERN.pattern() + " (was " + element.getID() + ")");
registry.put(element.getID(), element);
@@ -56,6 +73,10 @@ public class Registry<T extends Registrable> {
* @return The element.
*/
public T remove(@NotNull final T element) {
if (this.isLocked) {
throw new IllegalStateException("Cannot remove from locked registry! (ID: " + element.getID() + ")");
}
element.onRemove();
registry.remove(element.getID());
@@ -71,6 +92,10 @@ public class Registry<T extends Registrable> {
*/
@Nullable
public T remove(@NotNull final String id) {
if (this.isLocked) {
throw new IllegalStateException("Cannot remove from locked registry! (ID: " + id + ")");
}
T element = registry.get(id);
if (element != null) {
@@ -109,6 +134,61 @@ public class Registry<T extends Registrable> {
return Set.copyOf(registry.values());
}
/**
* Get if the registry is locked.
*
* @return If the registry is locked.
*/
public boolean isLocked() {
return isLocked;
}
/**
* Lock the registry.
*
* @param locker The locker.
*/
public void lock(@Nullable final Object locker) {
this.locker = locker;
isLocked = true;
}
/**
* Unlock the registry.
*
* @param locker The locker.
*/
public void unlock(@Nullable final Object locker) {
if (this.locker != locker) {
throw new IllegalArgumentException("Cannot unlock registry!");
}
isLocked = false;
}
/**
* Get if the registry is empty.
*
* @return If the registry is empty.
*/
public boolean isEmpty() {
return registry.isEmpty();
}
/**
* Get if the registry is not empty.
*
* @return If the registry is not empty.
*/
public boolean isNotEmpty() {
return !isEmpty();
}
@NotNull
@Override
public Iterator<T> iterator() {
return values().iterator();
}
/**
* Try to fit a string to the ID pattern.
*
@@ -120,6 +200,6 @@ public class Registry<T extends Registrable> {
return string.replace(" ", "_")
.replace(".", "_")
.replace("-", "_")
.toLowerCase();
.toLowerCase(Locale.ENGLISH);
}
}

View File

@@ -1,6 +1,8 @@
package com.willfp.eco.core.scheduling;
import com.willfp.eco.core.EcoPlugin;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.scheduler.BukkitTask;
import org.jetbrains.annotations.NotNull;
@@ -14,9 +16,13 @@ public interface Scheduler {
* @param runnable The lambda to run.
* @param ticksLater The amount of ticks to wait before execution.
* @return The created {@link BukkitTask}.
* @deprecated Does not work with Folia.
*/
BukkitTask runLater(@NotNull Runnable runnable,
long ticksLater);
@Deprecated(since = "6.53.0", forRemoval = true)
default BukkitTask runLater(@NotNull Runnable runnable,
long ticksLater) {
return runLater(new Location(Bukkit.getWorlds().get(0), 0, 0, 0), (int) ticksLater, runnable);
}
/**
* Run the task after a specified tick delay.
@@ -26,10 +32,12 @@ public interface Scheduler {
* @param runnable The lambda to run.
* @param ticksLater The amount of ticks to wait before execution.
* @return The created {@link BukkitTask}.
* @deprecated Does not work with Folia.
*/
@Deprecated(since = "6.53.0", forRemoval = true)
default BukkitTask runLater(long ticksLater,
@NotNull Runnable runnable) {
return runLater(runnable, ticksLater);
return runLater(new Location(Bukkit.getWorlds().get(0), 0, 0, 0), (int) ticksLater, runnable);
}
/**
@@ -39,10 +47,14 @@ public interface Scheduler {
* @param delay The amount of ticks to wait before the first execution.
* @param repeat The amount of ticks to wait between executions.
* @return The created {@link BukkitTask}.
* @deprecated Does not work with Folia.
*/
BukkitTask runTimer(@NotNull Runnable runnable,
long delay,
long repeat);
@Deprecated(since = "6.53.0", forRemoval = true)
default BukkitTask runTimer(@NotNull Runnable runnable,
long delay,
long repeat) {
return runTimer(new Location(Bukkit.getWorlds().get(0), 0, 0, 0), (int) delay, (int) repeat, runnable);
}
/**
* Run the task repeatedly on a timer.
@@ -53,11 +65,13 @@ public interface Scheduler {
* @param delay The amount of ticks to wait before the first execution.
* @param repeat The amount of ticks to wait between executions.
* @return The created {@link BukkitTask}.
* @deprecated Does not work with Folia.
*/
@Deprecated(since = "6.53.0", forRemoval = true)
default BukkitTask runTimer(long delay,
long repeat,
@NotNull Runnable runnable) {
return runTimer(runnable, delay, repeat);
return runTimer(new Location(Bukkit.getWorlds().get(0), 0, 0, 0), (int) delay, (int) repeat, runnable);
}
/**
@@ -67,10 +81,14 @@ public interface Scheduler {
* @param delay The amount of ticks to wait before the first execution.
* @param repeat The amount of ticks to wait between executions.
* @return The created {@link BukkitTask}.
* @deprecated Does not work with Folia.
*/
BukkitTask runAsyncTimer(@NotNull Runnable runnable,
long delay,
long repeat);
@Deprecated(since = "6.53.0", forRemoval = true)
default BukkitTask runAsyncTimer(@NotNull Runnable runnable,
long delay,
long repeat) {
return runTimerAsync((int) delay, (int) repeat, runnable);
}
/**
* Run the task repeatedly and asynchronously on a timer.
@@ -81,11 +99,13 @@ public interface Scheduler {
* @param delay The amount of ticks to wait before the first execution.
* @param repeat The amount of ticks to wait between executions.
* @return The created {@link BukkitTask}.
* @deprecated Does not work with Folia.
*/
@Deprecated(since = "6.53.0", forRemoval = true)
default BukkitTask runAsyncTimer(long delay,
long repeat,
@NotNull Runnable runnable) {
return runAsyncTimer(runnable, delay, repeat);
return runTimerAsync((int) delay, (int) repeat, runnable);
}
/**
@@ -93,28 +113,28 @@ public interface Scheduler {
*
* @param runnable The lambda to run.
* @return The created {@link BukkitTask}.
* @deprecated Does not work with Folia.
*/
BukkitTask run(@NotNull Runnable runnable);
@Deprecated(since = "6.53.0", forRemoval = true)
default BukkitTask run(@NotNull Runnable runnable) {
return run(new Location(Bukkit.getWorlds().get(0), 0, 0, 0), runnable);
}
/**
* Run the task asynchronously.
*
* @param runnable The lambda to run.
* @return The created {@link BukkitTask}.
*/
BukkitTask runAsync(@NotNull Runnable runnable);
/**
* Schedule the task to be ran repeatedly on a timer.
* Schedule the task to be run repeatedly on a timer.
*
* @param runnable The lambda to run.
* @param delay The amount of ticks to wait before the first execution.
* @param repeat The amount of ticks to wait between executions.
* @return The id of the task.
* @deprecated Not needed.
*/
int syncRepeating(@NotNull Runnable runnable,
long delay,
long repeat);
@Deprecated(since = "6.53.0", forRemoval = true)
default int syncRepeating(@NotNull Runnable runnable,
long delay,
long repeat) {
return runTimer(runnable, delay, repeat).getTaskId();
}
/**
* Schedule the task to be ran repeatedly on a timer.
@@ -125,15 +145,73 @@ public interface Scheduler {
* @param delay The amount of ticks to wait before the first execution.
* @param repeat The amount of ticks to wait between executions.
* @return The id of the task.
* @deprecated Not needed.
*/
@Deprecated(since = "6.53.0", forRemoval = true)
default int syncRepeating(long delay,
long repeat,
@NotNull Runnable runnable) {
return syncRepeating(runnable, delay, repeat);
return runTimer(runnable, delay, repeat).getTaskId();
}
/**
* Cancel all running tasks from the linked {@link EcoPlugin}.
*/
void cancelAll();
/**
* Run a task asynchronously.
*
* @param task The lambda to run.
* @return The created {@link BukkitTask}.
*/
BukkitTask runAsync(@NotNull Runnable task);
/**
* Run a task.
*
* @param location The location.
* @param task The task.
* @return The created {@link BukkitTask}.
*/
BukkitTask run(@NotNull Location location,
@NotNull Runnable task);
/**
* Run a task after a delay.
*
* @param location The location.
* @param ticksLater The delay.
* @param task The task.
* @return The created {@link BukkitTask}.
*/
BukkitTask runLater(@NotNull Location location,
int ticksLater,
@NotNull Runnable task);
/**
* Run a task on a timer.
*
* @param location The location.
* @param delay The delay.
* @param repeat The repeat delay.
* @param task The task.
* @return The created {@link BukkitTask}.
*/
BukkitTask runTimer(@NotNull Location location,
int delay,
int repeat,
@NotNull Runnable task);
/**
* Run a task asynchronously on a timer.
*
* @param delay The delay.
* @param repeat The repeat delay.
* @param task The task.
* @return The created {@link BukkitTask}.
*/
BukkitTask runTimerAsync(int delay,
int repeat,
@NotNull Runnable task);
}

View File

@@ -0,0 +1,17 @@
package com.willfp.eco.core.version;
import org.jetbrains.annotations.NotNull;
/**
* An error thrown when eco is outdated.
*/
public class OutdatedEcoVersionError extends Error {
/**
* Create a new OutdatedEcoVersionError.
*
* @param message The message.
*/
public OutdatedEcoVersionError(@NotNull final String message) {
super(message);
}
}

View File

@@ -0,0 +1,53 @@
package com.willfp.eco.core.version;
import org.apache.maven.artifact.versioning.DefaultArtifactVersion;
import org.jetbrains.annotations.NotNull;
/**
* A minimal class to represent a version, uses DefaultArtifactVersion under the hood.
* <p>
* This class exists to resolve issues where 1.17 doesn't include DefaultArtifactVersion.
*/
public class Version implements Comparable<Version> {
/**
* The version.
*/
private final DefaultArtifactVersion version;
/**
* Create a new version.
*
* @param version The version.
*/
public Version(@NotNull final String version) {
this.version = new DefaultArtifactVersion(version);
}
@Override
public int compareTo(@NotNull final Version o) {
return this.version.compareTo(o.version);
}
@Override
public String toString() {
return this.version.toString();
}
@Override
public boolean equals(@NotNull final Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof Version other)) {
return false;
}
return this.version.equals(other.version);
}
@Override
public int hashCode() {
return this.version.hashCode();
}
}

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

@@ -0,0 +1,13 @@
package com.willfp.eco.core.registry
/**
* A registrable that has a string ID, for use with Kotlin.
*/
interface KRegistrable : Registrable {
/**
* The ID of the registrable.
*/
val id: String
override fun getID() = id
}

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,7 +3,8 @@ 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.concurrent.ConcurrentHashMap
@@ -15,7 +16,7 @@ open class EcoConfig(
private val values = ConcurrentHashMap<String, Any?>()
@Transient
var injections = mutableListOf<InjectablePlaceholder>()
var injections = ConcurrentHashMap<String, InjectablePlaceholder>()
fun init(values: Map<String, Any?>) {
this.values.clear()
@@ -104,12 +105,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 +142,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 +160,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 +177,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.pattern()] = placeholder
}
}
override fun getPlaceholderInjections(): List<InjectablePlaceholder> {
return injections.toList()
return injections.values.listView() // Faster than .toList()
}
override fun clearInjectedPlaceholders() {
@@ -208,14 +206,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 +216,44 @@ 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
}
// Hey! Don't care. This works.
return this.hashCode() == other.hashCode()
}
override fun hashCode(): Int {
/*
The keys are completely redundant, as they are only used to prevent
duplicate keys in the map. Therefore, we can ignore them and just
hash the actual placeholder values.
*/
var injectionHash = 0
injections.forEachValue(5) {
injectionHash = injectionHash xor (it.hashCode() shl 5)
}
// hashCode() has to compute extremely quickly, so we're using bitwise, because why not?
// Fucking filthy to use identityHashCode here, but it should be extremely fast
val identityHash = System.identityHashCode(this)
return (identityHash shl 5) - (identityHash xor configType.hashCode()) + injectionHash
}
}

View File

@@ -2,14 +2,15 @@ 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
class EcoConfigSection(
type: ConfigType,
values: Map<String, Any?> = emptyMap(),
injections: Collection<InjectablePlaceholder> = emptyList()
injections: Map<String, InjectablePlaceholder> = emptyMap()
) : EcoConfig(type) {
init {
this.init(values)
this.injections = injections.toMutableList()
this.injections = ConcurrentHashMap(injections)
}
}

View File

@@ -0,0 +1,27 @@
package com.willfp.eco.internal.data
import com.willfp.eco.core.data.ExternalDataStoreObjectAdapter
import com.willfp.eco.core.version.Version
object VersionToStringAdapter: ExternalDataStoreObjectAdapter<Version, String>(
Version::class.java,
String::class.java
) {
override fun toAccessedObject(obj: String): Version = Version(obj)
override fun toStoredObject(obj: Version): String = obj.toString()
}
class MavenVersionToStringAdapter(
className: String
): ExternalDataStoreObjectAdapter<Any, String>(
Class.forName(className),
String::class.java
) {
private val constructor = Class.forName(className)
.getConstructor(String::class.java)
override fun toAccessedObject(obj: String): Any = constructor.newInstance(obj)
override fun toStoredObject(obj: Any): String = obj.toString()
}

View File

@@ -5,9 +5,11 @@ import com.willfp.eco.core.Eco
import com.willfp.eco.core.EcoPlugin
import com.willfp.eco.core.config.toConfig
import com.willfp.eco.core.extensions.Extension
import com.willfp.eco.core.extensions.ExtensionLoadException
import com.willfp.eco.core.extensions.ExtensionLoader
import com.willfp.eco.core.extensions.ExtensionMetadata
import com.willfp.eco.core.extensions.MalformedExtensionException
import com.willfp.eco.core.version.Version
import org.bukkit.configuration.file.YamlConfiguration
import java.io.File
import java.io.InputStreamReader
@@ -32,7 +34,7 @@ class EcoExtensionLoader(
}
runCatching { loadExtension(extensionJar) }.onFailure {
this.plugin.logger.warning(extensionJar.name + " caused an error!")
this.plugin.logger.warning(extensionJar.name + " caused an error: ${it.message ?: "Unknown error"}")
if (Eco.get().ecoPlugin.configYml.getBool("log-full-extension-errors")) {
it.printStackTrace()
}
@@ -54,7 +56,12 @@ class EcoExtensionLoader(
var name = extensionYml.getStringOrNull("name")
var version = extensionYml.getStringOrNull("version")
var author = extensionYml.getStringOrNull("author")
val pluginVersion = Version(extensionYml.getStringOrNull("plugin-version") ?: "0.0.0")
val pluginName = extensionYml.getStringOrNull("plugin")
if (pluginName != null && !pluginName.equals(this.plugin.description.name, ignoreCase = true)) {
throw ExtensionLoadException("${extensionJar.name} is only compatible with $pluginName!")
}
if (mainClass == null) {
throw MalformedExtensionException("Invalid extension.yml found in " + extensionJar.name)
@@ -75,7 +82,11 @@ class EcoExtensionLoader(
author = "Unnamed Author"
}
val metadata = ExtensionMetadata(version, name, author)
if (Version(this.plugin.description.version) < pluginVersion) {
throw ExtensionLoadException("Plugin version is too low for ${extensionJar.name}!")
}
val metadata = ExtensionMetadata(version, name, author, extensionJar, pluginVersion)
val cls: Class<*> = classLoader.loadClass(mainClass)
val extension: Extension = cls.getConstructor(EcoPlugin::class.java).newInstance(this.plugin) as Extension
@@ -85,7 +96,7 @@ class EcoExtensionLoader(
extensions[extension] = classLoader
}
override fun getLoadedExtensions(): MutableSet<Extension> {
override fun getLoadedExtensions(): Set<Extension> {
return ImmutableSet.copyOf(extensions.keys)
}
@@ -97,4 +108,4 @@ class EcoExtensionLoader(
extensions.clear()
}
}
}

View File

@@ -14,4 +14,4 @@ class EcoRunnableFactory(private val plugin: EcoPlugin) : RunnableFactory {
}
}
}
}
}

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

@@ -2,6 +2,7 @@ package com.willfp.eco.internal.integrations
import com.willfp.eco.core.EcoPlugin
import com.willfp.eco.core.integrations.placeholder.PlaceholderManager
import com.willfp.eco.core.placeholder.context.placeholderContext
import me.clip.placeholderapi.expansion.PlaceholderExpansion
import org.bukkit.entity.Player
@@ -33,7 +34,19 @@ class PAPIExpansion(private val plugin: EcoPlugin) : PlaceholderExpansion() {
override fun onPlaceholderRequest(
player: Player?,
identifier: String
): String {
return PlaceholderManager.getResult(player, identifier, plugin)
): String? {
return PlaceholderManager.getResult(
plugin,
identifier,
placeholderContext(
player = player,
item = player?.inventory?.itemInMainHand
)
)
}
override fun getPlaceholders(): List<String> {
return PlaceholderManager.getRegisteredPlaceholders(plugin)
.map { "%${this.plugin.name.lowercase()}_${it.pattern.pattern()}%" }
}
}

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

@@ -1,51 +0,0 @@
package com.willfp.eco.internal.scheduling
import com.willfp.eco.core.EcoPlugin
import com.willfp.eco.core.scheduling.Scheduler
import org.bukkit.Bukkit
import org.bukkit.scheduler.BukkitTask
class EcoScheduler(private val plugin: EcoPlugin) : Scheduler {
override fun runLater(
runnable: Runnable,
ticksLater: Long
): BukkitTask {
return Bukkit.getScheduler().runTaskLater(plugin, runnable, ticksLater)
}
override fun runTimer(
runnable: Runnable,
delay: Long,
repeat: Long
): BukkitTask {
return Bukkit.getScheduler().runTaskTimer(plugin, runnable, delay, repeat)
}
override fun runAsyncTimer(
runnable: Runnable,
delay: Long,
repeat: Long
): BukkitTask {
return Bukkit.getScheduler().runTaskTimerAsynchronously(plugin, runnable, delay, repeat)
}
override fun run(runnable: Runnable): BukkitTask {
return Bukkit.getScheduler().runTask(plugin, runnable)
}
override fun runAsync(runnable: Runnable): BukkitTask {
return Bukkit.getScheduler().runTaskAsynchronously(plugin, runnable)
}
override fun syncRepeating(
runnable: Runnable,
delay: Long,
repeat: Long
): Int {
return Bukkit.getScheduler().scheduleSyncRepeatingTask(plugin, runnable, delay, repeat)
}
override fun cancelAll() {
Bukkit.getScheduler().cancelTasks(plugin)
}
}

View File

@@ -0,0 +1,33 @@
package com.willfp.eco.internal.scheduling
import com.willfp.eco.core.EcoPlugin
import com.willfp.eco.core.scheduling.Scheduler
import org.bukkit.Bukkit
import org.bukkit.Location
import org.bukkit.scheduler.BukkitTask
class EcoSchedulerSpigot(private val plugin: EcoPlugin) : Scheduler {
override fun runLater(location: Location, ticksLater: Int, task: Runnable): BukkitTask {
return Bukkit.getScheduler().runTaskLater(plugin, task, ticksLater.toLong())
}
override fun runTimer(location: Location, delay: Int, repeat: Int, task: Runnable): BukkitTask {
return Bukkit.getScheduler().runTaskTimer(plugin, task, delay.toLong(), repeat.toLong())
}
override fun run(location: Location, task: Runnable): BukkitTask {
return Bukkit.getScheduler().runTask(plugin, task)
}
override fun runAsync(task: Runnable): BukkitTask {
return Bukkit.getScheduler().runTaskAsynchronously(plugin, task)
}
override fun runTimerAsync(delay: Int, repeat: Int, task: Runnable): BukkitTask {
return Bukkit.getScheduler().runTaskTimerAsynchronously(plugin, task, delay.toLong(), repeat.toLong())
}
override fun cancelAll() {
Bukkit.getScheduler().cancelTasks(plugin)
}
}

View File

@@ -0,0 +1,6 @@
group = "com.willfp"
version = rootProject.version
dependencies {
compileOnly("dev.folia:folia-api:1.19.4-R0.1-SNAPSHOT")
}

View File

@@ -0,0 +1,49 @@
package com.willfp.eco.internal.scheduling
import com.willfp.eco.core.EcoPlugin
import com.willfp.eco.core.scheduling.Scheduler
import org.bukkit.Bukkit
import org.bukkit.Location
import org.bukkit.scheduler.BukkitTask
import java.util.concurrent.TimeUnit
class EcoSchedulerFolia(private val plugin: EcoPlugin) : Scheduler {
override fun runLater(runnable: Runnable, ticksLater: Long): BukkitTask {
Bukkit.getGlobalRegionScheduler().runDelayed(plugin, { runnable.run() }, ticksLater)
}
override fun runLater(location: Location, ticksLater: Int, task: Runnable): BukkitTask {
Bukkit.getRegionScheduler().runDelayed(plugin, location, { task.run() }, ticksLater.toLong())
}
override fun runTimer(delay: Long, repeat: Long, runnable: Runnable): BukkitTask {
Bukkit.getGlobalRegionScheduler().runAtFixedRate(plugin, { runnable.run() }, delay, repeat)
}
override fun runTimer(location: Location, delay: Int, repeat: Int, task: Runnable): BukkitTask {
Bukkit.getRegionScheduler().runAtFixedRate(plugin, location, { task.run() }, delay.toLong(), repeat.toLong())
}
override fun run(runnable: Runnable): BukkitTask {
Bukkit.getGlobalRegionScheduler().run(plugin) { runnable.run() }
}
override fun run(location: Location, task: Runnable): BukkitTask {
Bukkit.getRegionScheduler().run(plugin, location) { task.run() }
}
override fun runAsync(task: Runnable): BukkitTask {
Bukkit.getAsyncScheduler().runNow(plugin) { task.run() }
}
override fun runTimerAsync(delay: Int, repeat: Int, task: Runnable): BukkitTask {
Bukkit.getAsyncScheduler()
.runAtFixedRate(plugin, { task.run() }, delay * 50L, repeat * 50L, TimeUnit.MILLISECONDS)
}
override fun cancelAll() {
Bukkit.getScheduler().cancelTasks(plugin)
Bukkit.getAsyncScheduler().cancelTasks(plugin)
Bukkit.getGlobalRegionScheduler().cancelTasks(plugin)
}
}

View File

@@ -45,7 +45,7 @@ class SNBTConverter : SNBTConverterProxy {
val nms = CraftItemStack.asNMSCopy(itemStack)
val nmsTag = nms.save(CompoundTag())
nmsTag.remove("Count")
return tag.copy().merge(nmsTag) == nmsTag
return tag.copy().merge(nmsTag) == nmsTag && itemStack.type == item.type
}
override fun getItem(): ItemStack = item

View File

@@ -45,7 +45,7 @@ class SNBTConverter : SNBTConverterProxy {
val nms = CraftItemStack.asNMSCopy(itemStack)
val nmsTag = nms.save(CompoundTag())
nmsTag.remove("Count")
return tag.copy().merge(nmsTag) == nmsTag
return tag.copy().merge(nmsTag) == nmsTag && itemStack.type == item.type
}
override fun getItem(): ItemStack = item

View File

@@ -45,7 +45,7 @@ class SNBTConverter : SNBTConverterProxy {
val nms = CraftItemStack.asNMSCopy(itemStack)
val nmsTag = nms.save(CompoundTag())
nmsTag.remove("Count")
return tag.copy().merge(nmsTag) == nmsTag
return tag.copy().merge(nmsTag) == nmsTag && itemStack.type == item.type
}
override fun getItem(): ItemStack = item

View File

@@ -3,6 +3,7 @@ package com.willfp.eco.internal.spigot.proxy.v1_19_R1
import com.willfp.eco.core.items.TestableItem
import com.willfp.eco.core.recipe.parts.EmptyTestableItem
import com.willfp.eco.internal.spigot.proxy.SNBTConverterProxy
import com.willfp.eco.internal.spigot.proxy.common.toMaterial
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.SnbtPrinterTagVisitor
import net.minecraft.nbt.TagParser
@@ -44,7 +45,7 @@ class SNBTConverter : SNBTConverterProxy {
val nms = CraftItemStack.asNMSCopy(itemStack)
val nmsTag = nms.save(CompoundTag())
nmsTag.remove("Count")
return tag.copy().merge(nmsTag) == nmsTag
return tag.copy().merge(nmsTag) == nmsTag && itemStack.type == item.type
}
override fun getItem(): ItemStack = item

View File

@@ -44,7 +44,7 @@ class SNBTConverter : SNBTConverterProxy {
val nms = CraftItemStack.asNMSCopy(itemStack)
val nmsTag = nms.save(CompoundTag())
nmsTag.remove("Count")
return tag.copy().merge(nmsTag) == nmsTag
return tag.copy().merge(nmsTag) == nmsTag && itemStack.type == item.type
}
override fun getItem(): ItemStack = item

View File

@@ -44,7 +44,7 @@ class SNBTConverter : SNBTConverterProxy {
val nms = CraftItemStack.asNMSCopy(itemStack)
val nmsTag = nms.save(CompoundTag())
nmsTag.remove("Count")
return tag.copy().merge(nmsTag) == nmsTag
return tag.copy().merge(nmsTag) == nmsTag && itemStack.type == item.type
}
override fun getItem(): ItemStack = item

View File

@@ -37,7 +37,7 @@ dependencies {
compileOnly("com.github.jiangdashao:matrix-api-repo:317d4635fd")
compileOnly("com.gmail.nossr50.mcMMO:mcMMO:2.1.202")
compileOnly("me.clip:placeholderapi:2.10.10")
compileOnly("com.github.oraxen:oraxen:bea381fb82")
compileOnly("com.github.oraxen:oraxen:1.155.0")
compileOnly("com.github.brcdev-minecraft:shopgui-api:3.0.0")
compileOnly("com.github.LoneDev6:API-ItemsAdder:2.4.7")
compileOnly("com.arcaniax:HeadDatabase-API:1.3.0")
@@ -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

@@ -4,6 +4,7 @@ import com.willfp.eco.core.Eco
import com.willfp.eco.core.EcoPlugin
import com.willfp.eco.core.PluginLike
import com.willfp.eco.core.PluginProps
import com.willfp.eco.core.Prerequisite
import com.willfp.eco.core.command.CommandBase
import com.willfp.eco.core.command.PluginCommandBase
import com.willfp.eco.core.config.ConfigType
@@ -13,8 +14,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,14 +38,18 @@ 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.scheduling.EcoSchedulerFolia
import com.willfp.eco.internal.scheduling.EcoSchedulerSpigot
import com.willfp.eco.internal.spigot.data.DataYml
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,8 +93,17 @@ 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)
if (Prerequisite.HAS_FOLIA.isMet) EcoSchedulerFolia(plugin) else EcoSchedulerSpigot(plugin)
override fun createEventManager(plugin: EcoPlugin) =
EcoEventManager(plugin)
@@ -312,8 +326,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 +340,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

@@ -3,6 +3,7 @@ package com.willfp.eco.internal.spigot
import com.willfp.eco.core.Eco
import com.willfp.eco.core.EcoPlugin
import com.willfp.eco.core.Prerequisite
import com.willfp.eco.core.data.ExternalDataStore
import com.willfp.eco.core.entities.Entities
import com.willfp.eco.core.integrations.IntegrationLoader
import com.willfp.eco.core.integrations.afk.AFKManager
@@ -19,6 +20,8 @@ import com.willfp.eco.core.items.Items
import com.willfp.eco.core.packet.PacketListener
import com.willfp.eco.core.particle.Particles
import com.willfp.eco.core.price.Prices
import com.willfp.eco.internal.data.MavenVersionToStringAdapter
import com.willfp.eco.internal.data.VersionToStringAdapter
import com.willfp.eco.internal.entities.EntityArgParserAdult
import com.willfp.eco.internal.entities.EntityArgParserAttackDamage
import com.willfp.eco.internal.entities.EntityArgParserAttackSpeed
@@ -92,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
@@ -107,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
@@ -120,6 +125,7 @@ import com.willfp.eco.internal.spigot.recipes.listeners.ComplexInComplex
import com.willfp.eco.internal.spigot.recipes.listeners.ComplexInVanilla
import com.willfp.eco.internal.spigot.recipes.stackhandlers.ShapedCraftingRecipeStackHandler
import com.willfp.eco.internal.spigot.recipes.stackhandlers.ShapelessCraftingRecipeStackHandler
import com.willfp.eco.util.ClassUtils
import me.TechsCode.UltraEconomy.UltraEconomy
import net.kyori.adventure.platform.bukkit.BukkitAudiences
import net.milkbowl.vault.economy.Economy
@@ -178,6 +184,20 @@ abstract class EcoSpigotPlugin : EcoPlugin() {
SegmentParserUseIfPresent.register()
CustomItemsManager.registerProviders()
ExternalDataStore.registerAdapter(VersionToStringAdapter)
// Handle with shadow.
val className = listOf(
"org",
"apache",
"maven",
"artifact",
"versioning",
"DefaultArtifactVersion"
).joinToString(".")
if (ClassUtils.exists(className)) {
ExternalDataStore.registerAdapter(MavenVersionToStringAdapter(className))
}
}
override fun handleEnable() {
@@ -270,6 +290,7 @@ abstract class EcoSpigotPlugin : EcoPlugin() {
IntegrationLoader("CombatLogX") {
val pluginManager = Bukkit.getPluginManager()
val combatLogXPlugin = pluginManager.getPlugin("CombatLogX") ?: return@IntegrationLoader
@Suppress("DEPRECATION")
val pluginVersion = combatLogXPlugin.description.version
if (pluginVersion.startsWith("10")) {
@@ -294,7 +315,7 @@ abstract class EcoSpigotPlugin : EcoPlugin() {
IntegrationLoader("MythicMobs") { CustomEntitiesManager.register(CustomEntitiesMythicMobs()) },
// Custom Items
IntegrationLoader("Oraxen") { CustomItemsManager.register(CustomItemsOraxen()) },
IntegrationLoader("Oraxen") { CustomItemsManager.register(CustomItemsOraxen(this)) },
IntegrationLoader("ItemsAdder") { CustomItemsManager.register(CustomItemsItemsAdder()) },
IntegrationLoader("HeadDatabase") { CustomItemsManager.register(CustomItemsHeadDatabase(this)) },
IntegrationLoader("ExecutableItems") { CustomItemsManager.register(CustomItemsExecutableItems()) },
@@ -304,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()) },
@@ -336,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

@@ -9,7 +9,6 @@ import com.willfp.eco.internal.spigot.EcoSpigotPlugin
import com.willfp.eco.internal.spigot.ServerLocking
import com.willfp.eco.internal.spigot.data.storage.DataHandler
import com.willfp.eco.internal.spigot.data.storage.HandlerType
import com.willfp.eco.internal.spigot.data.storage.LegacyMySQLDataHandler
import com.willfp.eco.internal.spigot.data.storage.MongoDataHandler
import com.willfp.eco.internal.spigot.data.storage.MySQLDataHandler
import com.willfp.eco.internal.spigot.data.storage.YamlDataHandler
@@ -22,24 +21,16 @@ 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)
HandlerType.MYSQL -> MySQLDataHandler(plugin, this)
HandlerType.MONGO -> MongoDataHandler(plugin, this)
HandlerType.LEGACY_MYSQL -> LegacyMySQLDataHandler(plugin, this)
}
init {
if (handler.type == HandlerType.LEGACY_MYSQL) {
plugin.logger.warning("You're using the legacy MySQL handler!")
plugin.logger.warning("Some features will not work and you may get unfixable errors.")
plugin.logger.warning("Support cannot be given to data issues related to legacy MySQL.")
plugin.logger.warning("Change your data handler to mysql, mongo, or yaml to fix this!")
plugin.logger.warning("This can be done in /plugins/eco/config.yml")
}
}
fun accessLoadedProfile(uuid: UUID): EcoProfile? =
loaded[uuid]
fun loadGenericProfile(uuid: UUID): Profile {
val found = loaded[uuid]
@@ -87,11 +78,7 @@ class ProfileHandler(
}
var previousHandlerType = HandlerType.valueOf(plugin.dataYml.getString("previous-handler"))
if (previousHandlerType == HandlerType.MYSQL && !plugin.dataYml.has("new-mysql")) {
previousHandlerType = HandlerType.LEGACY_MYSQL
}
val previousHandlerType = HandlerType.valueOf(plugin.dataYml.getString("previous-handler"))
if (previousHandlerType == type) {
return
@@ -101,7 +88,6 @@ class ProfileHandler(
HandlerType.YAML -> YamlDataHandler(plugin, this)
HandlerType.MYSQL -> MySQLDataHandler(plugin, this)
HandlerType.MONGO -> MongoDataHandler(plugin, this)
HandlerType.LEGACY_MYSQL -> LegacyMySQLDataHandler(plugin, this)
}
ServerLocking.lock("Migrating player data! Check console for more information.")

View File

@@ -3,6 +3,5 @@ package com.willfp.eco.internal.spigot.data.storage
enum class HandlerType {
YAML,
MYSQL,
MONGO,
LEGACY_MYSQL
MONGO
}

View File

@@ -1,315 +0,0 @@
package com.willfp.eco.internal.spigot.data.storage
import com.github.benmanes.caffeine.cache.Caffeine
import com.google.common.util.concurrent.ThreadFactoryBuilder
import com.willfp.eco.core.Eco
import com.willfp.eco.core.EcoPlugin
import com.willfp.eco.core.data.keys.PersistentDataKey
import com.willfp.eco.core.data.keys.PersistentDataKeyType
import com.willfp.eco.internal.spigot.EcoSpigotPlugin
import com.willfp.eco.internal.spigot.data.ProfileHandler
import com.willfp.eco.internal.spigot.data.serverProfileUUID
import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
import org.jetbrains.exposed.dao.id.UUIDTable
import org.jetbrains.exposed.sql.BooleanColumnType
import org.jetbrains.exposed.sql.Column
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.DoubleColumnType
import org.jetbrains.exposed.sql.IntegerColumnType
import org.jetbrains.exposed.sql.ResultRow
import org.jetbrains.exposed.sql.SchemaUtils
import org.jetbrains.exposed.sql.VarCharColumnType
import org.jetbrains.exposed.sql.insert
import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.transactions.transaction
import org.jetbrains.exposed.sql.update
import java.util.UUID
import java.util.concurrent.Callable
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
/*
The MySQL data handler is hot garbage for several reasons:
- Using MySQL on unstructured data: it's being horrifically misused, but that's just how it has to be.
- Can't remove un-needed keys, there's wasted space in the columns everywhere.
- No native support for the STRING_LIST type, instead it 'serializes' the lists with semicolons as separators.
- General lack of flexibility, it's too rigid.
That's why I added the MongoDB handler, it's far, far better suited for what eco does - use it over
MySQL if you can.
Oh, also - I don't really know how this class works. I've rewritten it and hacked it together several ways
in several sessions, and it's basically complete gibberish to me. Adding the STRING_LIST type is probably
the worst bodge I've shipped in production.
*/
@Suppress("UNCHECKED_CAST")
class LegacyMySQLDataHandler(
plugin: EcoSpigotPlugin,
handler: ProfileHandler
) : DataHandler(HandlerType.LEGACY_MYSQL) {
private val database: Database
private val playerHandler: ImplementedMySQLHandler
private val serverHandler: ImplementedMySQLHandler
init {
val config = HikariConfig()
config.driverClassName = "com.mysql.cj.jdbc.Driver"
config.username = plugin.configYml.getString("mysql.user")
config.password = plugin.configYml.getString("mysql.password")
config.jdbcUrl = "jdbc:mysql://" +
"${plugin.configYml.getString("mysql.host")}:" +
"${plugin.configYml.getString("mysql.port")}/" +
plugin.configYml.getString("mysql.database")
config.maximumPoolSize = plugin.configYml.getInt("mysql.connections")
database = Database.connect(HikariDataSource(config))
playerHandler = ImplementedMySQLHandler(
handler,
UUIDTable("eco_players"),
plugin
)
serverHandler = ImplementedMySQLHandler(
handler,
UUIDTable("eco_server"),
plugin
)
}
override fun <T : Any> read(uuid: UUID, key: PersistentDataKey<T>): T? {
return applyFor(uuid) {
it.read(uuid, key)
}
}
override fun <T : Any> write(uuid: UUID, key: PersistentDataKey<T>, value: T) {
applyFor(uuid) {
it.write(uuid, key, value)
}
}
override fun saveKeysFor(uuid: UUID, keys: Set<PersistentDataKey<*>>) {
applyFor(uuid) {
it.saveKeysForRow(uuid, keys)
}
}
private inline fun <R> applyFor(uuid: UUID, function: (ImplementedMySQLHandler) -> R): R {
return if (uuid == serverProfileUUID) {
function(serverHandler)
} else {
function(playerHandler)
}
}
override fun initialize() {
playerHandler.initialize()
serverHandler.initialize()
}
@Suppress("UNCHECKED_CAST")
private inner class ImplementedMySQLHandler(
private val handler: ProfileHandler,
private val table: UUIDTable,
private val plugin: EcoPlugin
) {
private val rows = Caffeine.newBuilder()
.expireAfterWrite(3, TimeUnit.SECONDS)
.build<UUID, ResultRow>()
private val threadFactory = ThreadFactoryBuilder().setNameFormat("eco-legacy-mysql-thread-%d").build()
private val executor = Executors.newFixedThreadPool(plugin.configYml.getInt("mysql.threads"), threadFactory)
val registeredKeys = mutableSetOf<PersistentDataKey<*>>()
init {
transaction(database) {
SchemaUtils.create(table)
}
}
fun initialize() {
transaction(database) {
SchemaUtils.createMissingTablesAndColumns(table, withLogs = false)
}
}
fun ensureKeyRegistration(key: PersistentDataKey<*>) {
if (table.columns.any { it.name == key.key.toString() }) {
registeredKeys.add(key)
return
}
registerColumn(key)
registeredKeys.add(key)
}
fun <T : Any> write(uuid: UUID, key: PersistentDataKey<T>, value: Any) {
getRow(uuid)
doWrite(uuid, key, key.type.constrainSQLTypes(value))
}
private fun doWrite(uuid: UUID, key: PersistentDataKey<*>, constrainedValue: Any) {
val column: Column<Any> = getColumn(key) as Column<Any>
executor.submit {
transaction(database) {
table.update({ table.id eq uuid }) {
it[column] = constrainedValue
}
}
}
}
fun saveKeysForRow(uuid: UUID, keys: Set<PersistentDataKey<*>>) {
saveRow(uuid, keys)
}
private fun saveRow(uuid: UUID, keys: Set<PersistentDataKey<*>>) {
val profile = handler.loadGenericProfile(uuid)
executor.submit {
transaction(database) {
getRow(uuid)
for (key in keys) {
doWrite(uuid, key, key.type.constrainSQLTypes(profile.read(key)))
}
}
}
}
fun <T> read(uuid: UUID, key: PersistentDataKey<T>): T? {
val doRead = Callable<T?> {
transaction(database) {
val row = getRow(uuid)
val column = getColumn(key)
val raw = row[column]
key.type.fromConstrained(raw)
}
}
ensureKeyRegistration(key) // DON'T DELETE THIS LINE! I know it's covered in getColumn, but I need to do it here as well.
doRead.call()
return if (Eco.get().ecoPlugin.configYml.getBool("mysql.async-reads")) {
executor.submit(doRead).get()
} else {
doRead.call()
}
}
private fun <T> registerColumn(key: PersistentDataKey<T>) {
try {
transaction(database) {
try {
table.apply {
if (table.columns.any { it.name == key.key.toString() }) {
return@apply
}
when (key.type) {
PersistentDataKeyType.INT -> registerColumn<Int>(key.key.toString(), IntegerColumnType())
.default(key.defaultValue as Int)
PersistentDataKeyType.DOUBLE -> registerColumn<Double>(
key.key.toString(),
DoubleColumnType()
).default(key.defaultValue as Double)
PersistentDataKeyType.BOOLEAN -> registerColumn<Boolean>(
key.key.toString(),
BooleanColumnType()
).default(key.defaultValue as Boolean)
PersistentDataKeyType.STRING -> registerColumn<String>(
key.key.toString(),
VarCharColumnType(512)
).default(key.defaultValue as String)
PersistentDataKeyType.STRING_LIST -> registerColumn<String>(
key.key.toString(),
VarCharColumnType(8192)
).default(PersistentDataKeyType.STRING_LIST.constrainSQLTypes(key.defaultValue as List<String>) as String)
PersistentDataKeyType.CONFIG -> throw IllegalArgumentException(
"Config Persistent Data Keys are not supported by the legacy MySQL handler!"
)
else -> throw NullPointerException("Null value found!")
}
}
SchemaUtils.createMissingTablesAndColumns(table, withLogs = false)
} catch (e: Exception) {
plugin.logger.info("MySQL Error 1!")
e.printStackTrace()
// What's that? Two enormous exception catches? That's right! This code sucks.
}
}
} catch (e: Exception) {
plugin.logger.info("MySQL Error 2!")
e.printStackTrace()
// It might fail. Who cares? This is legacy.
}
}
private fun getColumn(key: PersistentDataKey<*>): Column<*> {
ensureKeyRegistration(key)
val name = key.key.toString()
return table.columns.first { it.name == name }
}
private fun getRow(uuid: UUID): ResultRow {
fun select(uuid: UUID): ResultRow? {
return transaction(database) {
table.select { table.id eq uuid }.limit(1).singleOrNull()
}
}
return rows.get(uuid) {
val row = select(uuid)
return@get if (row != null) {
row
} else {
transaction(database) {
table.insert { it[id] = uuid }
}
select(uuid)
}
}
}
}
}
private fun <T> PersistentDataKeyType<T>.constrainSQLTypes(value: Any): Any {
return if (this == PersistentDataKeyType.STRING_LIST) {
@Suppress("UNCHECKED_CAST")
value as List<String>
value.joinToString(separator = ";")
} else {
value
}
}
private fun <T> PersistentDataKeyType<T>.fromConstrained(constrained: Any?): T? {
if (constrained == null) {
return null
}
@Suppress("UNCHECKED_CAST")
return if (this == PersistentDataKeyType.STRING_LIST) {
constrained as String
constrained.split(";").toList()
} else {
constrained
} as T
}

View File

@@ -34,7 +34,7 @@ Whatever. At least it works.
@Suppress("UNCHECKED_CAST")
class MySQLDataHandler(
private val plugin: EcoSpigotPlugin,
plugin: EcoSpigotPlugin,
private val handler: ProfileHandler
) : DataHandler(HandlerType.MYSQL) {
private val database: Database
@@ -149,9 +149,4 @@ class MySQLDataHandler(
SchemaUtils.createMissingTablesAndColumns(table, withLogs = false)
}
}
override fun save() {
plugin.dataYml.set("new-mysql", true)
plugin.dataYml.save()
}
}

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

@@ -1,5 +1,6 @@
package com.willfp.eco.internal.spigot.integrations.customitems
import com.willfp.eco.core.EcoPlugin
import com.willfp.eco.core.integrations.customitems.CustomItemsIntegration
import com.willfp.eco.core.items.CustomItem
import com.willfp.eco.core.items.Items
@@ -7,16 +8,27 @@ import com.willfp.eco.core.items.TestableItem
import com.willfp.eco.core.items.provider.ItemProvider
import com.willfp.eco.util.NamespacedKeyUtils
import io.th0rgal.oraxen.api.OraxenItems
import io.th0rgal.oraxen.api.events.OraxenItemsLoadedEvent
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
class CustomItemsOraxen : CustomItemsIntegration {
class CustomItemsOraxen(
private val plugin: EcoPlugin
) : CustomItemsIntegration, Listener {
override fun registerProvider() {
Items.registerItemProvider(OraxenProvider())
plugin.eventManager.registerListener(this)
}
override fun getPluginName(): String {
return "Oraxen"
}
@EventHandler
@Suppress("UNUSED_PARAMETER")
fun onItemRegister(event: OraxenItemsLoadedEvent) {
Items.registerItemProvider(OraxenProvider())
}
private class OraxenProvider : ItemProvider("oraxen") {
override fun provideForKey(key: String): TestableItem? {
val item = OraxenItems.getItemById(key) ?: return null

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,28 @@
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.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 {
// Peak performance (totally not having fun with bitwise operators)
val hash = (((expression.hashCode() shl 5) - expression.hashCode()) xor
(context.player?.uniqueId?.hashCode() ?: 0)
) xor context.injectableContext.hashCode()
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

Some files were not shown because too many files have changed in this diff Show More