Compare commits

..

74 Commits

Author SHA1 Message Date
Auxilor
c8f710161d Merge remote-tracking branch 'origin/master'
# Conflicts:
#	gradle.properties
2023-06-13 15:29:12 +02:00
Auxilor
f840a55734 Fixed 1.20 bugs 2023-06-13 15:28:12 +02:00
Auxilor
36677fe503 Updated to 6.63.1 2023-06-08 21:30:26 +01:00
Auxilor
7e74223352 Added 1.20 support 2023-06-08 21:30:16 +01:00
Auxilor
b6f2b9d4ea Added global %player% placeholder 2023-06-05 14:09:17 +01:00
Auxilor
f320e77008 Updated to 6.64.0 2023-06-05 13:44:50 +01:00
Auxilor
c8b255b358 Added placeholder extension methods 2023-06-05 13:44:42 +01:00
Auxilor
5b5e161062 Added head arg parser 2023-06-05 13:22:21 +01:00
Auxilor
ffa511176f Improved packet event error logging 2023-06-03 15:39:38 +01:00
Auxilor
8515ff2b7b Updated to 6.63.0 2023-06-03 15:13:51 +01:00
Auxilor
74aeaec257 Added NumberUtils.evaluateExpressionOrNull 2023-06-03 15:13:42 +01:00
Auxilor
0ce3d294d1 Updated to 6.62.2 2023-06-03 14:23:33 +01:00
Auxilor
7c7052f5b9 Placeholders with identical patterns will now override previous registrations 2023-06-03 14:23:15 +01:00
Auxilor
a6b7dda82d Added check to prevent !!float in configs 2023-06-03 14:20:59 +01:00
Auxilor
04aaefb9ea Updated to 6.62.1 2023-06-03 14:03:23 +01:00
Auxilor
d2fdb4f8e0 Packet events will no longer kick players if exceptions are thrown 2023-06-03 14:03:14 +01:00
Auxilor
87f90d8b26 Updated to 6.62.0 2023-05-24 14:45:37 +01:00
Auxilor
899d5cc054 Added caching to literal pattern compilation 2023-05-24 14:41:15 +01:00
Auxilor
c1ed771eb3 Updated to 6.61.1 2023-05-22 13:22:03 +01:00
Auxilor
08a4d9d6b1 Changed playerflow to use local server ID 2023-05-22 01:35:23 +01:00
Auxilor
7ef8dcfd64 Merge branch 'develop' 2023-05-21 19:07:56 +01:00
Auxilor
5bedf88b4c Updated lang.yml 2023-05-21 19:03:47 +01:00
Auxilor
1d241651b5 Added config option for playerflow 2023-05-21 17:01:13 +01:00
Auxilor
3ff2bfa412 Updated playerflow URL 2023-05-21 16:58:21 +01:00
Auxilor
3a508c693b Updated to 6.61.0 2023-05-21 16:38:35 +01:00
Auxilor
a4c5ff921e Added playerflow 2023-05-21 16:38:17 +01:00
Auxilor
137e9dc7d6 Added global price display names 2023-05-20 18:01:45 +01:00
Auxilor
710cec4bc1 Merge branch 'develop' 2023-05-19 13:17:05 +01:00
Auxilor
fa0ec7d6b0 Updated to 6.60.4 2023-05-19 13:16:20 +01:00
Auxilor
5093799775 Merge remote-tracking branch 'origin/develop' into develop 2023-05-19 13:15:34 +01:00
Will FP
8535f23ede Merge pull request #274
Fix PvPManager integration
2023-05-19 13:15:24 +01:00
Will FP
8e09ae7f4c Merge pull request #275
Add RoyaleEconomy to prices system
2023-05-19 13:14:54 +01:00
Sen2000
bbc2513b40 Fix RoyaleEconomy integration 2023-05-19 16:54:05 +07:00
Sen2000
c7f8063a3a Add RoyaleEconomy to prices system 2023-05-19 09:11:17 +07:00
ChanceSD
14b0f1be0c Fix PvPManager integration 2023-05-18 22:42:49 +01:00
Auxilor
af20bb315b Updated to 6.60.3 2023-05-18 15:35:54 +01:00
Auxilor
6645e216d5 Fixed stupid profile saver bug 2023-05-18 15:35:47 +01:00
Auxilor
eddf240f0c Updated to 6.60.2 2023-05-18 14:33:21 +01:00
Auxilor
4f406353ba Fixed data bug 2023-05-18 14:33:14 +01:00
Auxilor
095494dd2e Fixed PersistentDataKeyType.BIG_DECIMAL and javadoc 2023-05-17 18:39:48 +01:00
Auxilor
fd92645500 Updated to 6.60.1 2023-05-17 17:27:00 +01:00
Auxilor
1a6ac29083 Fixed PersistentDataKeyType.BIG_DECIMAL 2023-05-17 17:26:48 +01:00
Auxilor
7edc00d459 Added createTasks 2023-05-16 14:50:02 +01:00
Auxilor
a51bad058f Updated to 6.60.0 2023-05-16 14:47:06 +01:00
Auxilor
89ebb8ba59 Updated to 6.59.1 2023-05-15 17:43:03 +01:00
Auxilor
f0ae8f4f84 Fixed DelegatedBukkitCommand (again) 2023-05-15 17:42:49 +01:00
Auxilor
7d6cf78442 Cleanup 2023-05-15 16:38:33 +01:00
Auxilor
780d8f3b86 Fixed DefaultMap 2023-05-15 16:33:58 +01:00
Auxilor
146a0130f9 Fixed DefaultMap 2023-05-15 16:33:32 +01:00
Auxilor
df8c3411cb Updated to 6.59.0 2023-05-15 12:06:56 +01:00
Auxilor
4fc3c22a7d Added PersistentDataKeyType#BIG_DECIMAL 2023-05-15 12:05:37 +01:00
Auxilor
cfc4808bb8 Updated to 6.58.1 2023-05-14 16:36:05 +01:00
Auxilor
4ac6325a41 Fixed T?.toSingletonList() 2023-05-14 16:35:56 +01:00
Auxilor
4aed33751d EcoPlugin#afterLoad now runs 2 ticks after, with a preliminary reload 1 tick after startup to fix load order bugs 2023-05-13 17:43:31 +01:00
Auxilor
3fe1c2c69f Added EcoPlugin#cancelsTasksOnReload 2023-05-13 12:10:08 +01:00
Auxilor
5feaa84b2c Revert "Probably janky solution to command aliases"
This reverts commit 862b588c8d.
2023-05-12 16:57:12 +01:00
Auxilor
d8793bc2bb Fixed command aliases 2023-05-12 16:54:03 +01:00
Auxilor
15c512f3ca Cleanup 2023-05-12 15:40:28 +01:00
Auxilor
15ff2fb1d6 Fixed registry locks 2023-05-12 15:39:51 +01:00
Auxilor
862b588c8d Probably janky solution to command aliases 2023-05-12 15:37:29 +01:00
Auxilor
3c2a99b5f4 Added MenuBuilder#defaultPage 2023-05-12 14:53:44 +01:00
Auxilor
2d23c05c47 Added kotlin extensions to NumberUtils methods 2023-05-12 14:26:00 +01:00
Auxilor
8fc55d3393 Merge remote-tracking branch 'origin/develop' into develop 2023-05-12 13:53:45 +01:00
Auxilor
5900a756e4 Added margin options to line wrapping 2023-05-12 13:53:38 +01:00
Auxilor
c18b85f223 AAAAAAA 2023-05-12 13:53:16 +01:00
Auxilor
85116108c2 Oops 2023-05-12 13:47:47 +01:00
Auxilor
044f141bd0 Added margin options to line wrapping 2023-05-12 13:45:21 +01:00
Auxilor
9ca7f99fdb Added StringUtils#lineWrap kotlin extensions 2023-05-12 13:28:21 +01:00
Auxilor
7aa7770a3e Added StringUtils#lineWrap for lists 2023-05-12 13:27:13 +01:00
Auxilor
10202917fa Added StringUtils#lineWrap to wrap strings while preserving formatting 2023-05-11 17:24:05 +01:00
Auxilor
43df79e3b1 Fixed captive slots not forcing a re-render on shift click 2023-05-10 16:03:29 +01:00
Auxilor
5ef244f0bc Added option to store plugin data locally, even if eco is set to use a database 2023-05-10 15:19:15 +01:00
Auxilor
861f076c11 Updated to 6.58.0 2023-05-10 14:52:52 +01:00
Auxilor
7bed43059f Added Slot#shouldRenderOnClick 2023-05-08 18:39:27 +01:00
80 changed files with 1513 additions and 164 deletions

View File

@@ -27,6 +27,7 @@ dependencies {
implementation(project(path = ":eco-core:core-nms:v1_19_R1", configuration = "reobf"))
implementation(project(path = ":eco-core:core-nms:v1_19_R2", configuration = "reobf"))
implementation(project(path = ":eco-core:core-nms:v1_19_R3", configuration = "reobf"))
implementation(project(path = ":eco-core:core-nms:v1_20_R1", configuration = "reobf"))
}
allprojects {

View File

@@ -135,6 +135,14 @@ public interface Eco {
@NotNull
Logger createLogger(@NotNull EcoPlugin plugin);
/**
* Get NOOP logger.
*
* @return The logger.
*/
@NotNull
Logger getNOOPLogger();
/**
* Create a PAPI integration.
*
@@ -170,7 +178,7 @@ public interface Eco {
* @return The PluginCommandBase implementation
*/
@NotNull
PluginCommandBase createPluginCommand(@NotNull CommandBase parentDelegate,
PluginCommandBase createPluginCommand(@NotNull PluginCommandBase parentDelegate,
@NotNull EcoPlugin plugin,
@NotNull String name,
@NotNull String permission,
@@ -393,15 +401,6 @@ public interface Eco {
@NotNull
ServerProfile getServerProfile();
/**
* Unload a player profile from memory.
* <p>
* This will not save the profile first.
*
* @param uuid The uuid.
*/
void unloadPlayerProfile(@NotNull UUID uuid);
/**
* Create dummy entity - never spawned, exists purely in code.
*
@@ -528,9 +527,10 @@ public interface Eco {
*
* @param expression The expression.
* @param context The context.
* @return The value of the expression, or zero if invalid.
* @return The value of the expression, or null if invalid.
*/
double evaluate(@NotNull String expression,
@Nullable
Double evaluate(@NotNull String expression,
@NotNull PlaceholderContext context);
/**

View File

@@ -126,7 +126,7 @@ public abstract class EcoPlugin extends JavaPlugin implements PluginLike, Regist
/**
* The logger for the plugin.
*/
private final Logger logger;
private Logger logger;
/**
* If the server is running an outdated version of the plugin.
@@ -164,6 +164,11 @@ public abstract class EcoPlugin extends JavaPlugin implements PluginLike, Regist
*/
private final ListMap<LifecyclePosition, Runnable> afterLoad = new ListMap<>();
/**
* The tasks to run on task creation.
*/
private final ListMap<LifecyclePosition, Runnable> createTasks = new ListMap<>();
/**
* Create a new plugin.
* <p>
@@ -425,7 +430,18 @@ public abstract class EcoPlugin extends JavaPlugin implements PluginLike, Regist
this.loadPluginCommands().forEach(PluginCommand::register);
this.getScheduler().runLater(this::afterLoad, 1);
// Run preliminary reload to resolve load order issues
this.getScheduler().runLater(() -> {
Logger before = this.getLogger();
// Temporary silence logger.
this.logger = Eco.get().getNOOPLogger();
this.reload(false);
this.logger = before;
}, 1);
this.getScheduler().runLater(this::afterLoad, 2);
if (this.isSupportingExtensions()) {
this.getExtensionLoader().loadExtensions();
@@ -601,14 +617,30 @@ public abstract class EcoPlugin extends JavaPlugin implements PluginLike, Regist
* Reload the plugin.
*/
public final void reload() {
this.reload(true);
}
/**
* Reload the plugin.
*
* @param cancelTasks If tasks should be cancelled.
*/
public final void reload(final boolean cancelTasks) {
this.getConfigHandler().updateConfigs();
this.getScheduler().cancelAll();
if (cancelTasks) {
this.getScheduler().cancelAll();
}
this.getConfigHandler().callUpdate();
this.getConfigHandler().callUpdate(); // Call twice to fix issues
this.handleLifecycle(this.onReload, this::handleReload);
if (cancelTasks) {
this.handleLifecycle(this.createTasks, this::createTasks);
}
for (Extension extension : this.extensionLoader.getLoadedExtensions()) {
extension.handleReload();
}
@@ -722,6 +754,15 @@ public abstract class EcoPlugin extends JavaPlugin implements PluginLike, Regist
}
/**
* The plugin-specific code to create tasks.
* <p>
* Override when needed.
*/
protected void createTasks() {
}
/**
* The plugin-specific code to be executed after the server is up.
* <p>
@@ -1150,6 +1191,16 @@ public abstract class EcoPlugin extends JavaPlugin implements PluginLike, Regist
return this.getMetadataValueFactory().create(value);
}
/**
* Get if all {@link com.willfp.eco.core.data.keys.PersistentDataKey}'s for this
* plugin should be saved locally (via data.yml.) even if eco is using a database.
*
* @return If using local storage.
*/
public boolean isUsingLocalStorage() {
return this.configYml.isUsingLocalStorage();
}
@Override
@NotNull
public final String getID() {

View File

@@ -37,11 +37,19 @@ public class Prerequisite {
"Requires server to have ProtocolLib"
);
/**
* Requires the server to be running 1.20.
*/
public static final Prerequisite HAS_1_20 = new Prerequisite(
() -> ProxyConstants.NMS_VERSION.contains("20"),
"Requires server to be running 1.20+"
);
/**
* Requires the server to be running 1.19.4.
*/
public static final Prerequisite HAS_1_19_4 = new Prerequisite(
() -> ProxyConstants.NMS_VERSION.contains("19_R3"),
() -> ProxyConstants.NMS_VERSION.contains("19_R3") || HAS_1_20.isMet(),
"Requires server to be running 1.19.4+"
);
@@ -49,7 +57,7 @@ public class Prerequisite {
* Requires the server to be running 1.19.
*/
public static final Prerequisite HAS_1_19 = new Prerequisite(
() -> ProxyConstants.NMS_VERSION.contains("19"),
() -> ProxyConstants.NMS_VERSION.contains("19") || HAS_1_20.isMet(),
"Requires server to be running 1.19+"
);

View File

@@ -9,6 +9,11 @@ import org.jetbrains.annotations.NotNull;
* Default plugin config.yml.
*/
public class ConfigYml extends BaseConfig {
/**
* The use local storage key.
*/
public static final String KEY_USES_LOCAL_STORAGE = "use-local-storage";
/**
* Config.yml.
*
@@ -52,4 +57,13 @@ public class ConfigYml extends BaseConfig {
final boolean removeUnused) {
super(name, plugin, removeUnused, ConfigType.YAML);
}
/**
* Get if the plugin is using local storage.
*
* @return The prefix.
*/
public boolean isUsingLocalStorage() {
return this.getBool(KEY_USES_LOCAL_STORAGE);
}
}

View File

@@ -9,6 +9,22 @@ import org.jetbrains.annotations.NotNull;
* Profiles save automatically, so there is no need to save after changes.
*/
public interface ServerProfile extends Profile {
/**
* Get the server ID.
*
* @return The server ID.
*/
@NotNull
String getServerID();
/**
* Get the local server ID.
*
* @return The local server ID.
*/
@NotNull
String getLocalServerID();
/**
* Load the server profile.
*

View File

@@ -4,6 +4,7 @@ import com.willfp.eco.core.config.interfaces.Config;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@@ -49,6 +50,11 @@ public final class PersistentDataKeyType<T> {
*/
public static final PersistentDataKeyType<Config> CONFIG = new PersistentDataKeyType<>("CONFIG");
/**
* Big Decimal.
*/
public static final PersistentDataKeyType<BigDecimal> BIG_DECIMAL = new PersistentDataKeyType<>("BIG_DECIMAL");
/**
* The name of the key type.
*/

View File

@@ -142,6 +142,26 @@ public interface MenuBuilder extends PageBuilder {
return this.onRender((player, menu) -> menu.setState(player, Page.MAX_PAGE_KEY, pages.apply(player)));
}
/**
* Set the default page.
*
* @param page The page.
* @return The builder.
*/
default MenuBuilder defaultPage(final int page) {
return this.maxPages(player -> page);
}
/**
* Set the default page dynamically for a player.
*
* @param page The default page.
* @return The builder.
*/
default MenuBuilder defaultPage(@NotNull final Function<Player, Integer> page) {
return this.onOpen((player, menu) -> menu.setState(player, Page.PAGE_KEY, page.apply(player)));
}
/**
* Add a menu close handler.
*

View File

@@ -76,6 +76,15 @@ public abstract class CustomSlot implements Slot {
return delegate;
}
@Override
public boolean shouldRenderOnClick() {
if (delegate == null) {
throw new IllegalStateException("Custom Slot was not initialized!");
}
return delegate.shouldRenderOnClick();
}
@Override
public final int getRows() {
return Slot.super.getRows();

View File

@@ -92,6 +92,15 @@ public interface Slot extends GUIComponent {
return false;
}
/**
* If the slot should re-render the menu if clicked.
*
* @return If the slot should re-render.
*/
default boolean shouldRenderOnClick() {
return true;
}
@Override
default int getRows() {
return 1;

View File

@@ -17,6 +17,13 @@ import java.util.function.Supplier;
* @param <T> The type of integration.
*/
public class IntegrationRegistry<T extends Integration> extends Registry<T> {
/**
* Create a new integration registry.
*/
public IntegrationRegistry() {
super();
}
@Override
public @NotNull T register(@NotNull final T element) {
return executeSafely(() -> super.register(element), element);

View File

@@ -98,6 +98,7 @@ public final class PlaceholderManager {
// 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.removeIf(p -> p.getPattern().equals(placeholder.getPattern()));
pluginPlaceholders.add(placeholder);
REGISTERED_PLACEHOLDERS.put(placeholder.getPlugin(), ImmutableSet.copyOf(pluginPlaceholders));
}

View File

@@ -1,7 +1,6 @@
package com.willfp.eco.core.items;
import com.willfp.eco.core.Eco;
import org.bukkit.Bukkit;
import org.bukkit.NamespacedKey;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
@@ -55,7 +54,7 @@ public class CustomItem implements TestableItem {
*/
Eco.get().getEcoPlugin().getScheduler().runLater(() -> {
if (!matches(getItem())) {
Bukkit.getLogger().severe("Item with key " + key + " is invalid!");
Eco.get().getEcoPlugin().getLogger().severe("Item with key " + key + " is invalid!");
}
}, 1);
}

View File

@@ -150,7 +150,7 @@ public class DefaultMap<K, V> implements Map<K, V> {
*/
@NotNull
public static <K, K1, V> DefaultMap<K, Map<K1, V>> createNestedMap() {
return new DefaultMap<>(new HashMap<>());
return new DefaultMap<>(HashMap::new);
}
/**
@@ -163,6 +163,6 @@ public class DefaultMap<K, V> implements Map<K, V> {
*/
@NotNull
public static <K, K1, V> DefaultMap<K, ListMap<K1, V>> createNestedListMap() {
return new DefaultMap<>(new ListMap<>());
return new DefaultMap<>(ListMap::new);
}
}

View File

@@ -2,6 +2,7 @@ package com.willfp.eco.core.placeholder;
import com.willfp.eco.core.EcoPlugin;
import com.willfp.eco.core.placeholder.context.PlaceholderContext;
import com.willfp.eco.util.PatternUtils;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -40,7 +41,7 @@ public final class PlayerPlaceholder implements RegistrablePlaceholder {
@NotNull final String identifier,
@NotNull final Function<@NotNull Player, @Nullable String> function) {
this.plugin = plugin;
this.pattern = Pattern.compile(identifier, Pattern.LITERAL);
this.pattern = PatternUtils.compileLiteral(identifier);
this.function = function;
}

View File

@@ -1,6 +1,7 @@
package com.willfp.eco.core.placeholder;
import com.willfp.eco.core.placeholder.context.PlaceholderContext;
import com.willfp.eco.util.PatternUtils;
import com.willfp.eco.util.StringUtils;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
@@ -38,7 +39,7 @@ public final class PlayerStaticPlaceholder implements InjectablePlaceholder {
public PlayerStaticPlaceholder(@NotNull final String identifier,
@NotNull final Function<@NotNull Player, @Nullable String> function) {
this.identifier = "%" + identifier + "%";
this.pattern = Pattern.compile(identifier, Pattern.LITERAL);
this.pattern = PatternUtils.compileLiteral(identifier);
this.function = function;
}

View File

@@ -2,6 +2,7 @@ package com.willfp.eco.core.placeholder;
import com.willfp.eco.core.EcoPlugin;
import com.willfp.eco.core.placeholder.context.PlaceholderContext;
import com.willfp.eco.util.PatternUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -39,7 +40,7 @@ public final class PlayerlessPlaceholder implements RegistrablePlaceholder {
@NotNull final String identifier,
@NotNull final Supplier<@Nullable String> function) {
this.plugin = plugin;
this.pattern = Pattern.compile(identifier, Pattern.LITERAL);
this.pattern = PatternUtils.compileLiteral(identifier);
this.function = function;
}

View File

@@ -1,6 +1,7 @@
package com.willfp.eco.core.placeholder;
import com.willfp.eco.core.placeholder.context.PlaceholderContext;
import com.willfp.eco.util.PatternUtils;
import com.willfp.eco.util.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -37,7 +38,7 @@ public final class StaticPlaceholder implements InjectablePlaceholder {
public StaticPlaceholder(@NotNull final String identifier,
@NotNull final Supplier<@Nullable String> function) {
this.identifier = "%" + identifier + "%";
this.pattern = Pattern.compile(identifier, Pattern.LITERAL);
this.pattern = PatternUtils.compileLiteral(identifier);
this.function = function;
}

View File

@@ -2,6 +2,7 @@ package com.willfp.eco.core.placeholder.templates;
import com.willfp.eco.core.placeholder.InjectablePlaceholder;
import com.willfp.eco.core.placeholder.context.PlaceholderContext;
import com.willfp.eco.util.PatternUtils;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
@@ -28,7 +29,7 @@ public abstract class SimpleInjectablePlaceholder implements InjectablePlacehold
*/
protected SimpleInjectablePlaceholder(@NotNull final String identifier) {
this.identifier = identifier;
this.pattern = Pattern.compile(identifier, Pattern.LITERAL);
this.pattern = PatternUtils.compileLiteral(identifier);
}
@Override

View File

@@ -3,6 +3,7 @@ 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 com.willfp.eco.util.PatternUtils;
import org.jetbrains.annotations.NotNull;
import java.util.Objects;
@@ -37,7 +38,7 @@ public abstract class SimplePlaceholder implements RegistrablePlaceholder {
@NotNull final String identifier) {
this.plugin = plugin;
this.identifier = identifier;
this.pattern = Pattern.compile(identifier, Pattern.LITERAL);
this.pattern = PatternUtils.compileLiteral(identifier);
}
@Override

View File

@@ -1,5 +1,6 @@
package com.willfp.eco.core.price;
import com.willfp.eco.core.Eco;
import com.willfp.eco.core.config.interfaces.Config;
import com.willfp.eco.core.placeholder.context.PlaceholderContext;
import com.willfp.eco.core.price.impl.PriceFree;
@@ -158,12 +159,27 @@ public final class ConfiguredPrice implements Price {
if (!(
config.has("value")
&& config.has("type")
&& config.has("display")
)) {
return null;
}
String formatString = config.getString("display");
String formatString;
String langConfig = Eco.get().getEcoPlugin().getLangYml()
.getSubsections("price-display")
.stream()
.filter(section -> section.getString("type").equalsIgnoreCase(config.getString("type")))
.findFirst()
.map(section -> section.getString("display"))
.orElse(null);
if (langConfig != null) {
formatString = langConfig;
} else if (config.has("display")) {
formatString = config.getString("display");
} else {
return null;
}
Price price = Prices.create(
config.getString("value"),

View File

@@ -23,7 +23,8 @@ public final class ProxyConstants {
"v1_18_R2",
"v1_19_R1",
"v1_19_R2",
"v1_19_R3"
"v1_19_R3",
"v1_20_R1"
);
private ProxyConstants() {

View File

@@ -9,7 +9,6 @@ import com.willfp.eco.core.items.Items;
import com.willfp.eco.core.recipe.recipes.CraftingRecipe;
import com.willfp.eco.core.recipe.recipes.ShapedCraftingRecipe;
import com.willfp.eco.util.NamespacedKeyUtils;
import org.bukkit.Bukkit;
import org.bukkit.NamespacedKey;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
@@ -127,8 +126,8 @@ public final class Recipes {
}
if (builder.isAir()) {
Bukkit.getLogger().warning("Crafting recipe " + plugin.getID() + ":" + key + " consists only");
Bukkit.getLogger().warning("of air or invalid items! It will not be registered.");
plugin.getLogger().warning("Crafting recipe " + plugin.getID() + ":" + key + " consists only");
plugin.getLogger().warning("of air or invalid items! It will not be registered.");
return null;
}

View File

@@ -149,6 +149,10 @@ public class Registry<T extends Registrable> implements Iterable<T> {
* @param locker The locker.
*/
public void lock(@Nullable final Object locker) {
if (this.isLocked && this.locker != locker) {
throw new IllegalArgumentException("Registry is already locked with a different locker!");
}
this.locker = locker;
isLocked = true;
}
@@ -162,6 +166,8 @@ public class Registry<T extends Registrable> implements Iterable<T> {
if (this.locker != locker) {
throw new IllegalArgumentException("Cannot unlock registry!");
}
this.locker = null;
isLocked = false;
}

View File

@@ -12,6 +12,7 @@ import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
import java.util.concurrent.ThreadLocalRandom;
@@ -251,12 +252,15 @@ public final class NumberUtils {
@Nullable final Player player,
@Nullable final PlaceholderInjectable context,
@NotNull final Collection<AdditionalPlayer> additionalPlayers) {
return Eco.get().evaluate(expression, new PlaceholderContext(
player,
null,
context,
additionalPlayers
));
return evaluateExpression(
expression,
new PlaceholderContext(
player,
null,
context,
additionalPlayers
)
);
}
/**
@@ -268,7 +272,7 @@ public final class NumberUtils {
* @deprecated Use {@link #evaluateExpression(String, PlaceholderContext)} instead.
*/
@Deprecated(since = "6.56.0", forRemoval = true)
@SuppressWarnings("removal")
@SuppressWarnings({"removal", "DeprecatedIsStillUsed"})
public static double evaluateExpression(@NotNull final String expression,
@NotNull final com.willfp.eco.core.math.MathContext context) {
return evaluateExpression(expression, context.toPlaceholderContext());
@@ -283,6 +287,22 @@ public final class NumberUtils {
*/
public static double evaluateExpression(@NotNull final String expression,
@NotNull final PlaceholderContext context) {
return Objects.requireNonNullElse(
evaluateExpressionOrNull(expression, context),
0.0
);
}
/**
* Evaluate an expression in a context.
*
* @param expression The expression.
* @param context The context.
* @return The value of the expression, or zero if invalid.
*/
@Nullable
public static Double evaluateExpressionOrNull(@NotNull final String expression,
@NotNull final PlaceholderContext context) {
return Eco.get().evaluate(expression, context);
}

View File

@@ -0,0 +1,35 @@
package com.willfp.eco.util;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.jetbrains.annotations.NotNull;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
/**
* Utilities / API methods for patterns.
*/
public final class PatternUtils {
/**
* Cache of compiled literal patterns.
*/
private static final Cache<String, Pattern> LITERAL_PATTERN_CACHE = Caffeine.newBuilder()
.expireAfterAccess(1, TimeUnit.MINUTES)
.build();
/**
* Compile a literal pattern.
*
* @param pattern The pattern.
* @return The compiled pattern.
*/
@NotNull
public static Pattern compileLiteral(@NotNull final String pattern) {
return LITERAL_PATTERN_CACHE.get(pattern, (it) -> Pattern.compile(it, Pattern.LITERAL));
}
private PatternUtils() {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
}
}

View File

@@ -10,6 +10,8 @@ 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.JoinConfiguration;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.format.TextDecoration;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
@@ -784,6 +786,127 @@ public final class StringUtils {
return result.toString();
}
/**
* Line wrap a list of strings while preserving formatting.
*
* @param input The input list.
* @param lineLength The length of each line.
* @return The wrapped list.
*/
@NotNull
public static List<String> lineWrap(@NotNull final List<String> input,
final int lineLength) {
return lineWrap(input, lineLength, true);
}
/**
* Line wrap a list of strings while preserving formatting.
*
* @param input The input list.
* @param lineLength The length of each line.
* @param preserveMargin If the string has a margin, add it to the next line.
* @return The wrapped list.
*/
@NotNull
public static List<String> lineWrap(@NotNull final List<String> input,
final int lineLength,
final boolean preserveMargin) {
return input.stream()
.flatMap(line -> lineWrap(line, lineLength, preserveMargin).stream())
.toList();
}
/**
* Line wrap a string while preserving formatting.
*
* @param input The input list.
* @param lineLength The length of each line.
* @return The wrapped list.
*/
@NotNull
public static List<String> lineWrap(@NotNull final String input,
final int lineLength) {
return lineWrap(input, lineLength, true);
}
/**
* Line wrap a string while preserving formatting.
*
* @param input The input string.
* @param lineLength The length of each line.
* @param preserveMargin If the string has a margin, add it to the start of each line.
* @return The wrapped string.
*/
@NotNull
public static List<String> lineWrap(@NotNull final String input,
final int lineLength,
final boolean preserveMargin) {
int margin = preserveMargin ? getMargin(input) : 0;
TextComponent space = Component.text(" ");
Component asComponent = toComponent(input);
// The component contains the text as its children, so the child components
// are accessed like this:
List<TextComponent> children = new ArrayList<>();
if (asComponent instanceof TextComponent) {
children.add((TextComponent) asComponent);
}
for (Component child : asComponent.children()) {
children.add((TextComponent) child);
}
// Start by splitting the component into individual characters.
List<TextComponent> letters = new ArrayList<>();
for (TextComponent child : children) {
for (char c : child.content().toCharArray()) {
letters.add(Component.text(c).mergeStyle(child));
}
}
List<Component> lines = new ArrayList<>();
List<TextComponent> currentLine = new ArrayList<>();
boolean isFirstLine = true;
for (TextComponent letter : letters) {
if (currentLine.size() > lineLength && letter.content().isBlank()) {
lines.add(Component.join(JoinConfiguration.noSeparators(), currentLine));
currentLine.clear();
isFirstLine = false;
} else {
// Add margin if starting a new line.
if (currentLine.isEmpty() && !isFirstLine) {
if (preserveMargin) {
for (int i = 0; i < margin; i++) {
currentLine.add(space);
}
}
}
currentLine.add(letter);
}
}
// Push last line.
lines.add(Component.join(JoinConfiguration.noSeparators(), currentLine));
// Convert back to legacy strings.
return lines.stream().map(StringUtils::toLegacy)
.collect(Collectors.toList());
}
/**
* Get a string's margin.
*
* @param input The input string.
* @return The margin.
*/
public static int getMargin(@NotNull final String input) {
return input.indexOf(input.trim());
}
/**
* Options for formatting.
*/

View File

@@ -8,7 +8,7 @@ package com.willfp.eco.core.map
* @see ListMap
*/
@Suppress("RedundantOverride")
class MutableListMap<K : Any, V : Any> : ListMap<K, V>() {
class MutableListMap<K : Any, V> : ListMap<K, V>() {
/**
* Override with enforced MutableList type.
*/
@@ -18,7 +18,7 @@ class MutableListMap<K : Any, V : Any> : ListMap<K, V>() {
/**
* Override with enforced MutableList type.
*/
override fun getOrDefault(key: K, defaultValue: MutableList<V>?): MutableList<V> {
override fun getOrDefault(key: K, defaultValue: MutableList<V>): MutableList<V> {
return super.getOrDefault(key, defaultValue)
}
}
@@ -29,6 +29,12 @@ class MutableListMap<K : Any, V : Any> : ListMap<K, V>() {
fun <K : Any, V : Any> defaultMap(defaultValue: V) =
DefaultMap<K, V>(defaultValue)
/**
* @see DefaultMap
*/
fun <K : Any, V : Any> defaultMap(defaultValue: () -> V) =
DefaultMap<K, V>(defaultValue())
/**
* @see ListMap
*/
@@ -38,11 +44,13 @@ fun <K : Any, V : Any> listMap() =
/**
* @see DefaultMap.createNestedMap
*/
fun <K : Any, K1 : Any, V : Any> nestedMap() =
fun <K : Any, K1 : Any, V> nestedMap() =
DefaultMap.createNestedMap<K, K1, V>()
/**
* @see DefaultMap.createNestedListMap
*/
fun <K : Any, K1 : Any, V : Any> nestedListMap() =
DefaultMap<K, MutableListMap<K1, V>>(MutableListMap())
fun <K : Any, K1 : Any, V> nestedListMap() =
DefaultMap<K, MutableListMap<K1, V>>() {
MutableListMap()
}

View File

@@ -0,0 +1,14 @@
@file:JvmName("PlaceholderExtensions")
package com.willfp.eco.core.placeholder
import com.willfp.eco.core.integrations.placeholder.PlaceholderManager
import com.willfp.eco.core.placeholder.context.PlaceholderContext
/** @see PlaceholderManager.translatePlaceholders */
fun String.translatePlaceholders(context: PlaceholderContext) =
PlaceholderManager.translatePlaceholders(this, context)
/** @see PlaceholderManager.findPlaceholdersIn */
fun String.findPlaceholders(): List<String> =
PlaceholderManager.findPlaceholdersIn(this)

View File

@@ -15,5 +15,5 @@ fun <T> create2DList(rows: Int, columns: Int): MutableList<MutableList<T>> =
ListUtils.create2DList(rows, columns)
/** @see ListUtils.toSingletonList */
fun <T> T.toSingletonList(): List<T> =
fun <T> T?.toSingletonList(): List<T> =
ListUtils.toSingletonList(this)

View File

@@ -2,6 +2,36 @@
package com.willfp.eco.util
import com.willfp.eco.core.placeholder.context.PlaceholderContext
/** @see NumberUtils.toNumeral */
fun Number.toNumeral(): String =
NumberUtils.toNumeral(this.toInt())
/** @see NumberUtils.fromNumeral */
fun String.parseNumeral(): Int =
NumberUtils.fromNumeral(this)
/** @see NumberUtils.randInt */
fun randInt(min: Int, max: Int) =
NumberUtils.randInt(min, max)
/** @see NumberUtils.randFloat */
fun randDouble(min: Double, max: Double) =
NumberUtils.randFloat(min, max)
/** @see NumberUtils.randFloat */
fun randFloat(min: Float, max: Float) =
NumberUtils.randFloat(min.toDouble(), max.toDouble()).toFloat()
/** @see NumberUtils.evaluateExpression */
fun evaluateExpression(expression: String) =
NumberUtils.evaluateExpression(expression)
/** @see NumberUtils.evaluateExpression */
fun evaluateExpression(expression: String, context: PlaceholderContext) =
NumberUtils.evaluateExpression(expression, context)
/** @see NumberUtils.evaluateExpressionOrNull */
fun evaluateExpressionOrNull(expression: String, context: PlaceholderContext) =
NumberUtils.evaluateExpressionOrNull(expression, context)

View File

@@ -69,3 +69,15 @@ fun Any?.toNiceString(): String =
/** @see StringUtils.replaceQuickly */
fun String.replaceQuickly(target: String, replacement: String): String =
StringUtils.replaceQuickly(this, target, replacement)
/** @see StringUtils.lineWrap */
fun String.lineWrap(width: Int, preserveMargin: Boolean = true): List<String> =
StringUtils.lineWrap(this, width, preserveMargin)
/** @see StringUtils.lineWrap */
fun List<String>.lineWrap(width: Int, preserveMargin: Boolean = true): List<String> =
StringUtils.lineWrap(this, width, preserveMargin)
/** @see StringUtils.getMargin */
val String.margin: Int
get() = StringUtils.getMargin(this)

View File

@@ -8,10 +8,12 @@ import org.bukkit.command.TabCompleter
class DelegatedBukkitCommand(
private val delegate: EcoPluginCommand
) : Command(delegate.name), TabCompleter, PluginIdentifiableCommand {
private var _aliases: List<String>? = null
private var _description: String? = null
) : Command(
delegate.name,
delegate.description ?: "",
"/${delegate.name}",
delegate.aliases
), TabCompleter, PluginIdentifiableCommand {
override fun execute(sender: CommandSender, label: String, args: Array<out String>?): Boolean {
return delegate.onCommand(sender, this, label, args)
}
@@ -36,16 +38,4 @@ class DelegatedBukkitCommand(
override fun getPlugin() = delegate.plugin
override fun getPermission() = delegate.permission
override fun getDescription() = _description ?: delegate.description ?: ""
override fun getAliases(): List<String> = _aliases ?: delegate.aliases
override fun setDescription(description: String): Command {
this._description = description
return this
}
override fun setAliases(aliases: List<String>): Command {
this._aliases = aliases
return this
}
}

View File

@@ -7,7 +7,7 @@ import com.willfp.eco.core.command.PluginCommandBase
import org.bukkit.Bukkit
class EcoPluginCommand(
parentDelegate: CommandBase,
private val parentDelegate: PluginCommandBase,
plugin: EcoPlugin,
name: String,
permission: String,
@@ -39,6 +39,9 @@ class EcoPluginCommand(
Eco.get().syncCommands()
}
override fun getAliases(): List<String> = parentDelegate.aliases
override fun getDescription(): String? = parentDelegate.description
}
class EcoSubcommand(

View File

@@ -30,6 +30,8 @@ internal fun Any?.constrainConfigTypes(type: ConfigType): Any? = when (this) {
this.toMutableList()
}
}
is Float -> this.toDouble() // Should prevent !!float from being written
else -> this
}

View File

@@ -1,12 +1,12 @@
package com.willfp.eco.internal.config
import com.willfp.eco.core.config.interfaces.Config
import org.yaml.snakeyaml.DumperOptions
import org.yaml.snakeyaml.nodes.Node
import org.yaml.snakeyaml.representer.Represent
import org.yaml.snakeyaml.representer.Representer
@Suppress("DEPRECATION")
class EcoRepresenter : Representer() {
class EcoRepresenter : Representer(DumperOptions()) {
init {
multiRepresenters[Config::class.java] = RepresentConfig(multiRepresenters[Map::class.java]!!)
}

View File

@@ -20,7 +20,15 @@ private val listeners = mutableMapOf<PacketPriority, MutableList<RegisteredPacke
fun PacketEvent.handleSend() {
for (priority in PacketPriority.values()) {
for (listener in listeners[priority] ?: continue) {
listener.listener.onSend(this)
try {
listener.listener.onSend(this)
} catch (e: Exception) {
listener.plugin.logger.warning(
"Exception in packet listener ${listener.listener.javaClass.name}" +
" for packet ${packet.handle.javaClass.name}!"
)
e.printStackTrace()
}
}
}
}
@@ -28,7 +36,15 @@ fun PacketEvent.handleSend() {
fun PacketEvent.handleReceive() {
for (priority in PacketPriority.values()) {
for (listener in listeners[priority] ?: continue) {
listener.listener.onReceive(this)
try {
listener.listener.onReceive(this)
} catch (e: Exception) {
listener.plugin.logger.warning(
"Exception in packet listener ${listener.listener.javaClass.name}" +
" for packet ${packet.handle.javaClass.name}!"
)
e.printStackTrace()
}
}
}
}

View File

@@ -34,4 +34,6 @@ open class EcoSlot(
}
override fun getActionableSlot(player: Player, menu: Menu): EcoSlot = this
override fun shouldRenderOnClick() = handlers.values.any { it.isNotEmpty() }
}

View File

@@ -6,6 +6,7 @@ import com.willfp.eco.core.gui.slot.functional.CaptiveFilter
import com.willfp.eco.core.gui.slot.functional.SlotHandler
import com.willfp.eco.core.gui.slot.functional.SlotProvider
import com.willfp.eco.core.gui.slot.functional.SlotUpdater
import com.willfp.eco.core.map.listMap
import org.bukkit.entity.Player
import org.bukkit.event.inventory.ClickType
import java.util.function.Predicate
@@ -15,14 +16,14 @@ class EcoSlotBuilder(private val provider: SlotProvider) : SlotBuilder {
private var captiveFromEmpty = false
private var updater: SlotUpdater = SlotUpdater { player, menu, _ -> provider.provide(player, menu) }
private val handlers = mutableMapOf<ClickType, MutableList<SlotHandler>>()
private val handlers = listMap<ClickType, SlotHandler>()
private var captiveFilter =
CaptiveFilter { _, _, _ -> true }
private var notCaptiveFor: (Player) -> Boolean = { _ -> false}
override fun onClick(type: ClickType, action: SlotHandler): SlotBuilder {
handlers.computeIfAbsent(type) { mutableListOf() } += action
handlers[type] += action
return this
}

View File

@@ -0,0 +1,53 @@
package com.willfp.eco.internal.items
import com.willfp.eco.core.items.args.LookupArgParser
import org.bukkit.Bukkit
import org.bukkit.inventory.ItemStack
import org.bukkit.inventory.meta.ItemMeta
import org.bukkit.inventory.meta.SkullMeta
import java.util.function.Predicate
object ArgParserHead : LookupArgParser {
override fun parseArguments(args: Array<out String>, meta: ItemMeta): Predicate<ItemStack>? {
if (meta !is SkullMeta) {
return null
}
var playerName: String? = null
for (arg in args) {
val argSplit = arg.split(":")
if (!argSplit[0].equals("head", ignoreCase = true)) {
continue
}
if (argSplit.size < 2) {
continue
}
playerName = argSplit[1]
}
playerName ?: return null
@Suppress("DEPRECATION")
val player = Bukkit.getOfflinePlayer(playerName)
meta.owningPlayer = player
return Predicate {
val testMeta = it.itemMeta as? SkullMeta ?: return@Predicate false
testMeta.owningPlayer?.uniqueId == player.uniqueId
}
}
override fun serializeBack(meta: ItemMeta): String? {
if (meta !is SkullMeta) {
return null
}
if (meta.owningPlayer == null) {
return null
}
return "head:${meta.owningPlayer?.name}"
}
}

View File

@@ -0,0 +1,10 @@
package com.willfp.eco.internal.logging
import java.util.logging.LogRecord
import java.util.logging.Logger
object NOOPLogger : Logger("eco_noop", null as String?) {
override fun log(record: LogRecord?) {
return
}
}

View File

@@ -4,8 +4,23 @@ 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.core.placeholder.templates.SimpleInjectablePlaceholder
import java.util.regex.Pattern
/*
A set of global placeholders that are always available.
*/
private val globalPlaceholders = setOf<Placeholder>(
object : SimpleInjectablePlaceholder("player") {
override fun getValue(args: String, context: PlaceholderContext): String? {
return context.player?.name
}
},
)
class PlaceholderLookup(
val args: String,
val plugin: EcoPlugin?,
@@ -29,6 +44,12 @@ class PlaceholderLookup(
}
}
for (placeholder in globalPlaceholders) {
if (placeholder.matches(this)) {
return placeholder
}
}
return null
}

View File

@@ -7,7 +7,6 @@ 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
/*
@@ -23,7 +22,7 @@ class PlaceholderParser {
private val placeholderLookupCache = Caffeine.newBuilder()
.expireAfterWrite(1, TimeUnit.SECONDS)
.build<PlaceholderLookup, Optional<Placeholder>>()
.build<PlaceholderLookup, Placeholder?>()
fun translatePlacholders(text: String, context: PlaceholderContext): String {
return translatePlacholders(text, context, context.injectableContext.placeholderInjections)
@@ -122,10 +121,10 @@ class PlaceholderParser {
val lookup = PlaceholderLookup(args, plugin, injections)
val placeholder = placeholderLookupCache.get(lookup) {
Optional.ofNullable(it.findMatchingPlaceholder())
}.orElse(null) ?: return null
it.findMatchingPlaceholder()
}
return placeholder.getValue(args, context)
return placeholder?.getValue(args, context)
}
private fun translateEcoPlaceholdersIn(

View File

@@ -0,0 +1,39 @@
plugins {
id("io.papermc.paperweight.userdev")
}
group = "com.willfp"
version = rootProject.version
dependencies {
implementation(project(":eco-core:core-nms:nms-common"))
paperweight.paperDevBundle("1.20-R0.1-SNAPSHOT")
implementation("net.kyori:adventure-text-minimessage:4.11.0") {
version {
strictly("4.11.0")
}
exclude(group = "net.kyori", module = "adventure-api")
}
}
tasks {
build {
dependsOn(reobfJar)
}
reobfJar {
mustRunAfter(shadowJar)
}
shadowJar {
relocate(
"com.willfp.eco.internal.spigot.proxy.common",
"com.willfp.eco.internal.spigot.proxy.v1_20_R1.common"
)
relocate(
"net.kyori.adventure.text.minimessage",
"com.willfp.eco.internal.spigot.proxy.v1_20_R1.minimessage"
)
}
}

View File

@@ -0,0 +1,35 @@
package com.willfp.eco.internal.spigot.proxy.v1_20_R1
import com.willfp.eco.core.command.PluginCommandBase
import com.willfp.eco.internal.spigot.proxy.BukkitCommandsProxy
import org.bukkit.Bukkit
import org.bukkit.command.Command
import org.bukkit.command.SimpleCommandMap
import org.bukkit.craftbukkit.v1_20_R1.CraftServer
import java.lang.reflect.Field
class BukkitCommands : BukkitCommandsProxy {
private val knownCommandsField: Field by lazy {
SimpleCommandMap::class.java.getDeclaredField("knownCommands")
.apply {
isAccessible = true
}
}
@Suppress("UNCHECKED_CAST")
private val knownCommands: MutableMap<String, Command>
get() = knownCommandsField.get(getCommandMap()) as MutableMap<String, Command>
override fun getCommandMap(): SimpleCommandMap {
return (Bukkit.getServer() as CraftServer).commandMap
}
override fun syncCommands() {
(Bukkit.getServer() as CraftServer).syncCommands()
}
override fun unregisterCommand(command: PluginCommandBase) {
knownCommands.remove(command.name)
knownCommands.remove("${command.plugin.name.lowercase()}:${command.name}")
}
}

View File

@@ -0,0 +1,159 @@
package com.willfp.eco.internal.spigot.proxy.v1_20_R1
import com.willfp.eco.core.EcoPlugin
import com.willfp.eco.internal.spigot.proxy.CommonsInitializerProxy
import com.willfp.eco.internal.spigot.proxy.common.CommonsProvider
import com.willfp.eco.internal.spigot.proxy.common.packet.PacketInjectorListener
import com.willfp.eco.internal.spigot.proxy.common.toResourceLocation
import net.minecraft.core.registries.BuiltInRegistries
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.Tag
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.item.Item
import org.bukkit.Bukkit
import org.bukkit.Material
import org.bukkit.NamespacedKey
import org.bukkit.craftbukkit.v1_20_R1.CraftServer
import org.bukkit.craftbukkit.v1_20_R1.entity.CraftEntity
import org.bukkit.craftbukkit.v1_20_R1.entity.CraftMob
import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer
import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftItemStack
import org.bukkit.craftbukkit.v1_20_R1.persistence.CraftPersistentDataContainer
import org.bukkit.craftbukkit.v1_20_R1.persistence.CraftPersistentDataTypeRegistry
import org.bukkit.craftbukkit.v1_20_R1.util.CraftMagicNumbers
import org.bukkit.craftbukkit.v1_20_R1.util.CraftNamespacedKey
import org.bukkit.entity.LivingEntity
import org.bukkit.entity.Mob
import org.bukkit.entity.Player
import org.bukkit.inventory.ItemStack
import org.bukkit.persistence.PersistentDataContainer
import java.lang.reflect.Field
class CommonsInitializer : CommonsInitializerProxy {
override fun init(plugin: EcoPlugin) {
CommonsProvider.setIfNeeded(CommonsProviderImpl)
plugin.onEnable {
plugin.eventManager.registerListener(PacketInjectorListener)
}
}
object CommonsProviderImpl : CommonsProvider {
private val cisHandle: Field = CraftItemStack::class.java.getDeclaredField("handle").apply {
isAccessible = true
}
private val pdcRegsitry = Class.forName("org.bukkit.craftbukkit.v1_20_R1.inventory.CraftMetaItem")
.getDeclaredField("DATA_TYPE_REGISTRY")
.apply { isAccessible = true }
.get(null) as CraftPersistentDataTypeRegistry
override val nbtTagString = CraftMagicNumbers.NBT.TAG_STRING
override fun toPathfinderMob(mob: Mob): PathfinderMob? {
val craft = mob as? CraftMob ?: return null
return craft.handle as? PathfinderMob
}
override fun toResourceLocation(namespacedKey: NamespacedKey): ResourceLocation =
CraftNamespacedKey.toMinecraft(namespacedKey)
override fun asNMSStack(itemStack: ItemStack): net.minecraft.world.item.ItemStack {
return if (itemStack !is CraftItemStack) {
CraftItemStack.asNMSCopy(itemStack)
} else {
cisHandle[itemStack] as net.minecraft.world.item.ItemStack? ?: CraftItemStack.asNMSCopy(itemStack)
}
}
override fun asBukkitStack(itemStack: net.minecraft.world.item.ItemStack): ItemStack {
return CraftItemStack.asCraftMirror(itemStack)
}
override fun mergeIfNeeded(itemStack: ItemStack, nmsStack: net.minecraft.world.item.ItemStack) {
if (itemStack !is CraftItemStack) {
itemStack.itemMeta = CraftItemStack.asCraftMirror(nmsStack).itemMeta
}
}
override fun toBukkitEntity(entity: net.minecraft.world.entity.LivingEntity): LivingEntity? =
CraftEntity.getEntity(Bukkit.getServer() as CraftServer, entity) as? LivingEntity
override fun makePdc(tag: CompoundTag, base: Boolean): PersistentDataContainer {
fun emptyPdc(): CraftPersistentDataContainer = CraftPersistentDataContainer(pdcRegsitry)
fun CompoundTag?.toPdc(): PersistentDataContainer {
val pdc = emptyPdc()
this ?: return pdc
val keys = this.allKeys
for (key in keys) {
pdc.put(key, this[key])
}
return pdc
}
return if (base) {
tag.toPdc()
} else {
if (tag.contains("PublicBukkitValues")) {
tag.getCompound("PublicBukkitValues").toPdc()
} else {
emptyPdc()
}
}
}
override fun setPdc(
tag: CompoundTag,
pdc: PersistentDataContainer?,
item: net.minecraft.world.item.ItemStack?
) {
fun CraftPersistentDataContainer.toTag(): CompoundTag {
val compound = CompoundTag()
val rawPublicMap: Map<String, Tag> = this.raw
for ((key, value) in rawPublicMap) {
compound.put(key, value)
}
return compound
}
val container = when (pdc) {
is CraftPersistentDataContainer? -> pdc
else -> null
}
if (item != null) {
if (container != null && !container.isEmpty) {
for (key in tag.allKeys.toSet()) {
tag.remove(key)
}
tag.merge(container.toTag())
} else {
item.tag = null
}
} else {
if (container != null && !container.isEmpty) {
tag.put("PublicBukkitValues", container.toTag())
} else {
tag.remove("PublicBukkitValues")
}
}
}
override fun materialToItem(material: Material): Item =
BuiltInRegistries.ITEM.getOptional(material.key.toResourceLocation())
.orElseThrow { IllegalArgumentException("Material is not item!") }
override fun itemToMaterial(item: Item) =
Material.getMaterial(BuiltInRegistries.ITEM.getKey(item).path.uppercase())
?: throw IllegalArgumentException("Invalid material!")
override fun toNMS(player: Player): ServerPlayer {
return (player as CraftPlayer).handle
}
}
}

View File

@@ -0,0 +1,16 @@
package com.willfp.eco.internal.spigot.proxy.v1_20_R1
import com.willfp.eco.internal.entities.EcoDummyEntity
import com.willfp.eco.internal.spigot.proxy.DummyEntityFactoryProxy
import org.bukkit.Location
import org.bukkit.craftbukkit.v1_20_R1.CraftWorld
import org.bukkit.entity.Entity
import org.bukkit.entity.EntityType
class DummyEntityFactory : DummyEntityFactoryProxy {
override fun createDummyEntity(location: Location): Entity {
val world = location.world as CraftWorld
@Suppress("UsePropertyAccessSyntax")
return EcoDummyEntity(world.createEntity(location, EntityType.ZOMBIE.entityClass).getBukkitEntity())
}
}

View File

@@ -0,0 +1,12 @@
package com.willfp.eco.internal.spigot.proxy.v1_20_R1
import com.willfp.eco.core.entities.ai.EntityController
import com.willfp.eco.internal.spigot.proxy.EntityControllerFactoryProxy
import com.willfp.eco.internal.spigot.proxy.v1_20_R1.entity.EcoEntityController
import org.bukkit.entity.Mob
class EntityControllerFactory : EntityControllerFactoryProxy {
override fun <T : Mob> createEntityController(entity: T): EntityController<T> {
return EcoEntityController(entity)
}
}

View File

@@ -0,0 +1,82 @@
package com.willfp.eco.internal.spigot.proxy.v1_20_R1
import com.willfp.eco.core.data.ExtendedPersistentDataContainer
import com.willfp.eco.internal.spigot.proxy.ExtendedPersistentDataContainerFactoryProxy
import net.minecraft.nbt.Tag
import org.bukkit.Material
import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftItemStack
import org.bukkit.craftbukkit.v1_20_R1.persistence.CraftPersistentDataContainer
import org.bukkit.craftbukkit.v1_20_R1.persistence.CraftPersistentDataTypeRegistry
import org.bukkit.inventory.ItemStack
import org.bukkit.persistence.PersistentDataContainer
import org.bukkit.persistence.PersistentDataType
class ExtendedPersistentDataContainerFactory : ExtendedPersistentDataContainerFactoryProxy {
private val registry: CraftPersistentDataTypeRegistry
init {
/*
Can't grab actual instance since it's in CraftMetaItem (which is package-private)
And getting it would mean more janky reflection
*/
val item = CraftItemStack.asCraftCopy(ItemStack(Material.STONE))
val pdc = item.itemMeta!!.persistentDataContainer
this.registry = CraftPersistentDataContainer::class.java.getDeclaredField("registry")
.apply { isAccessible = true }.get(pdc) as CraftPersistentDataTypeRegistry
}
override fun adapt(pdc: PersistentDataContainer): ExtendedPersistentDataContainer {
return when (pdc) {
is CraftPersistentDataContainer -> EcoPersistentDataContainer(pdc)
else -> throw IllegalArgumentException("Custom PDC instance ims not supported!")
}
}
override fun newPdc(): PersistentDataContainer {
return CraftPersistentDataContainer(registry)
}
inner class EcoPersistentDataContainer(
private val handle: CraftPersistentDataContainer
) : ExtendedPersistentDataContainer {
@Suppress("UNCHECKED_CAST")
private val customDataTags: MutableMap<String, Tag> =
CraftPersistentDataContainer::class.java.getDeclaredField("customDataTags")
.apply { isAccessible = true }.get(handle) as MutableMap<String, Tag>
override fun <T : Any, Z : Any> set(key: String, dataType: PersistentDataType<T, Z>, value: Z) {
customDataTags[key] =
registry.wrap(dataType.primitiveType, dataType.toPrimitive(value, handle.adapterContext))
}
override fun <T : Any, Z : Any> has(key: String, dataType: PersistentDataType<T, Z>): Boolean {
val value = customDataTags[key] ?: return false
return registry.isInstanceOf(dataType.primitiveType, value)
}
override fun <T : Any, Z : Any> get(key: String, dataType: PersistentDataType<T, Z>): Z? {
val value = customDataTags[key] ?: return null
return dataType.fromPrimitive(registry.extract(dataType.primitiveType, value), handle.adapterContext)
}
override fun <T : Any, Z : Any> getOrDefault(
key: String,
dataType: PersistentDataType<T, Z>,
defaultValue: Z
): Z {
return get(key, dataType) ?: defaultValue
}
override fun remove(key: String) {
customDataTags.remove(key)
}
override fun getAllKeys(): MutableSet<String> {
return customDataTags.keys
}
override fun getBase(): PersistentDataContainer {
return handle
}
}
}

View File

@@ -0,0 +1,12 @@
package com.willfp.eco.internal.spigot.proxy.v1_20_R1
import com.willfp.eco.core.fast.FastItemStack
import com.willfp.eco.internal.spigot.proxy.FastItemStackFactoryProxy
import com.willfp.eco.internal.spigot.proxy.common.item.EcoFastItemStack
import org.bukkit.inventory.ItemStack
class FastItemStackFactory : FastItemStackFactoryProxy {
override fun create(itemStack: ItemStack): FastItemStack {
return EcoFastItemStack(itemStack)
}
}

View File

@@ -0,0 +1,33 @@
package com.willfp.eco.internal.spigot.proxy.v1_20_R1
import com.willfp.eco.core.display.Display
import com.willfp.eco.internal.spigot.proxy.MiniMessageTranslatorProxy
import com.willfp.eco.util.toLegacy
import net.kyori.adventure.text.minimessage.MiniMessage
class MiniMessageTranslator : MiniMessageTranslatorProxy {
override fun format(message: String): String {
var mut = message
val startsWithPrefix = mut.startsWith(Display.PREFIX)
if (startsWithPrefix) {
mut = mut.substring(2)
}
mut = mut.replace('§', '&')
val miniMessage = runCatching {
MiniMessage.miniMessage().deserialize(
mut
).toLegacy()
}.getOrNull() ?: mut
mut = if (startsWithPrefix) {
Display.PREFIX + miniMessage
} else {
miniMessage
}
return mut
}
}

View File

@@ -0,0 +1,46 @@
package com.willfp.eco.internal.spigot.proxy.v1_20_R1
import com.willfp.eco.core.EcoPlugin
import com.willfp.eco.core.packet.PacketListener
import com.willfp.eco.internal.spigot.proxy.PacketHandlerProxy
import com.willfp.eco.internal.spigot.proxy.common.packet.display.PacketAutoRecipe
import com.willfp.eco.internal.spigot.proxy.common.packet.display.PacketHeldItemSlot
import com.willfp.eco.internal.spigot.proxy.common.packet.display.PacketOpenWindowMerchant
import com.willfp.eco.internal.spigot.proxy.common.packet.display.PacketSetCreativeSlot
import com.willfp.eco.internal.spigot.proxy.common.packet.display.PacketSetSlot
import com.willfp.eco.internal.spigot.proxy.common.packet.display.PacketWindowItems
import com.willfp.eco.internal.spigot.proxy.common.packet.display.frame.clearFrames
import net.minecraft.network.protocol.Packet
import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer
import org.bukkit.entity.Player
class PacketHandler : PacketHandlerProxy {
override fun sendPacket(player: Player, packet: com.willfp.eco.core.packet.Packet) {
if (player !is CraftPlayer) {
return
}
val handle = packet.handle
if (handle !is Packet<*>) {
return
}
player.handle.connection.send(handle)
}
override fun clearDisplayFrames() {
clearFrames()
}
override fun getPacketListeners(plugin: EcoPlugin): List<PacketListener> {
return listOf(
PacketAutoRecipe(plugin),
PacketHeldItemSlot,
PacketOpenWindowMerchant,
PacketSetCreativeSlot,
PacketSetSlot,
PacketWindowItems(plugin)
)
}
}

View File

@@ -0,0 +1,52 @@
package com.willfp.eco.internal.spigot.proxy.v1_20_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 net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.SnbtPrinterTagVisitor
import net.minecraft.nbt.TagParser
import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftItemStack
import org.bukkit.inventory.ItemStack
class SNBTConverter : SNBTConverterProxy {
override fun fromSNBT(snbt: String): ItemStack? {
val nbt = runCatching { TagParser.parseTag(snbt) }.getOrNull() ?: return null
val nms = net.minecraft.world.item.ItemStack.of(nbt)
return CraftItemStack.asBukkitCopy(nms)
}
override fun toSNBT(itemStack: ItemStack): String {
val nms = CraftItemStack.asNMSCopy(itemStack)
return SnbtPrinterTagVisitor().visit(nms.save(CompoundTag()))
}
override fun makeSNBTTestable(snbt: String): TestableItem {
val nbt = runCatching { TagParser.parseTag(snbt) }.getOrNull() ?: return EmptyTestableItem()
val nms = net.minecraft.world.item.ItemStack.of(nbt)
if (nms == net.minecraft.world.item.ItemStack.EMPTY) {
return EmptyTestableItem()
}
nbt.remove("Count")
return SNBTTestableItem(CraftItemStack.asBukkitCopy(nms), nbt)
}
class SNBTTestableItem(
private val item: ItemStack,
private val tag: CompoundTag
) : TestableItem {
override fun matches(itemStack: ItemStack?): Boolean {
if (itemStack == null) {
return false
}
val nms = CraftItemStack.asNMSCopy(itemStack)
val nmsTag = nms.save(CompoundTag())
nmsTag.remove("Count")
return tag.copy().merge(nmsTag) == nmsTag && itemStack.type == item.type
}
override fun getItem(): ItemStack = item
}
}

View File

@@ -0,0 +1,18 @@
package com.willfp.eco.internal.spigot.proxy.v1_20_R1
import com.willfp.eco.internal.spigot.proxy.SkullProxy
import com.willfp.eco.internal.spigot.proxy.common.texture
import org.bukkit.inventory.meta.SkullMeta
class Skull : SkullProxy {
override fun setSkullTexture(
meta: SkullMeta,
base64: String
) {
meta.texture = base64
}
override fun getSkullTexture(
meta: SkullMeta
): String? = meta.texture
}

View File

@@ -0,0 +1,11 @@
package com.willfp.eco.internal.spigot.proxy.v1_20_R1
import com.willfp.eco.internal.spigot.proxy.TPSProxy
import org.bukkit.Bukkit
import org.bukkit.craftbukkit.v1_20_R1.CraftServer
class TPS : TPSProxy {
override fun getTPS(): Double {
return (Bukkit.getServer() as CraftServer).handle.server.recentTps[0]
}
}

View File

@@ -0,0 +1,95 @@
package com.willfp.eco.internal.spigot.proxy.v1_20_R1.entity
import com.willfp.eco.core.entities.ai.CustomGoal
import com.willfp.eco.core.entities.ai.EntityController
import com.willfp.eco.core.entities.ai.EntityGoal
import com.willfp.eco.core.entities.ai.TargetGoal
import com.willfp.eco.internal.spigot.proxy.common.ai.CustomGoalFactory
import com.willfp.eco.internal.spigot.proxy.common.ai.getGoalFactory
import com.willfp.eco.internal.spigot.proxy.common.toPathfinderMob
import net.minecraft.world.entity.PathfinderMob
import net.minecraft.world.entity.ai.goal.Goal
import org.bukkit.entity.Mob
class EcoEntityController<T : Mob>(
private val handle: T
) : EntityController<T> {
override fun addEntityGoal(priority: Int, goal: EntityGoal<in T>): EntityController<T> {
val nms = getNms() ?: return this
nms.goalSelector.addGoal(
priority,
goal.getGoalFactory()?.create(goal, nms) ?: return this
)
return this
}
override fun removeEntityGoal(goal: EntityGoal<in T>): EntityController<T> {
val nms = getNms() ?: return this
val predicate: (Goal) -> Boolean = if (goal is CustomGoal<*>) {
{ CustomGoalFactory.isGoalOfType(it, goal) }
} else {
{ goal.getGoalFactory()?.isGoalOfType(it) == true }
}
for (wrapped in nms.goalSelector.availableGoals.toSet()) {
if (predicate(wrapped.goal)) {
nms.goalSelector.removeGoal(wrapped.goal)
}
}
return this
}
override fun clearEntityGoals(): EntityController<T> {
val nms = getNms() ?: return this
nms.goalSelector.availableGoals.clear()
return this
}
override fun addTargetGoal(priority: Int, goal: TargetGoal<in T>): EntityController<T> {
val nms = getNms() ?: return this
nms.targetSelector.addGoal(
priority, goal.getGoalFactory()?.create(goal, nms) ?: return this
)
nms.targetSelector
return this
}
override fun removeTargetGoal(goal: TargetGoal<in T>): EntityController<T> {
val nms = getNms() ?: return this
val predicate: (Goal) -> Boolean = if (goal is CustomGoal<*>) {
{ CustomGoalFactory.isGoalOfType(it, goal) }
} else {
{ goal.getGoalFactory()?.isGoalOfType(it) == true }
}
for (wrapped in nms.targetSelector.availableGoals.toSet()) {
if (predicate(wrapped.goal)) {
nms.targetSelector.removeGoal(wrapped.goal)
}
}
return this
}
override fun clearTargetGoals(): EntityController<T> {
val nms = getNms() ?: return this
nms.targetSelector.availableGoals.clear()
return this
}
private fun getNms(): PathfinderMob? {
return handle.toPathfinderMob()
}
override fun getEntity(): T {
return handle
}
}

View File

@@ -37,6 +37,7 @@ import com.willfp.eco.internal.gui.menu.renderedInventory
import com.willfp.eco.internal.gui.slot.EcoSlotBuilder
import com.willfp.eco.internal.integrations.PAPIExpansion
import com.willfp.eco.internal.logging.EcoLogger
import com.willfp.eco.internal.logging.NOOPLogger
import com.willfp.eco.internal.placeholder.PlaceholderParser
import com.willfp.eco.internal.proxy.EcoProxyFactory
import com.willfp.eco.internal.scheduling.EcoScheduler
@@ -125,6 +126,9 @@ class EcoImpl : EcoSpigotPlugin(), Eco {
override fun createLogger(plugin: EcoPlugin) =
EcoLogger(plugin)
override fun getNOOPLogger() =
NOOPLogger
override fun createPAPIIntegration(plugin: EcoPlugin) {
PAPIExpansion(plugin)
}
@@ -184,7 +188,7 @@ class EcoImpl : EcoSpigotPlugin(), Eco {
}
override fun createPluginCommand(
parentDelegate: CommandBase,
parentDelegate: PluginCommandBase,
plugin: EcoPlugin,
name: String,
permission: String,
@@ -258,6 +262,7 @@ class EcoImpl : EcoSpigotPlugin(), Eco {
override fun addNewPlugin(plugin: EcoPlugin) {
loadedEcoPlugins[plugin.name.lowercase()] = plugin
loadedEcoPlugins[plugin.id] = plugin
}
override fun getLoadedPlugins(): List<String> =
@@ -281,9 +286,6 @@ class EcoImpl : EcoSpigotPlugin(), Eco {
override fun loadPlayerProfile(uuid: UUID) =
profileHandler.load(uuid)
override fun unloadPlayerProfile(uuid: UUID) =
profileHandler.unloadPlayer(uuid)
override fun createDummyEntity(location: Location): Entity =
getProxy(DummyEntityFactoryProxy::class.java).createDummyEntity(location)

View File

@@ -45,6 +45,7 @@ import com.willfp.eco.internal.items.ArgParserColor
import com.willfp.eco.internal.items.ArgParserCustomModelData
import com.willfp.eco.internal.items.ArgParserEnchantment
import com.willfp.eco.internal.items.ArgParserFlag
import com.willfp.eco.internal.items.ArgParserHead
import com.willfp.eco.internal.items.ArgParserName
import com.willfp.eco.internal.items.ArgParserTexture
import com.willfp.eco.internal.items.ArgParserUnbreakable
@@ -112,11 +113,13 @@ 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.PriceFactoryRoyaleEconomy
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
import com.willfp.eco.internal.spigot.integrations.shop.ShopShopGuiPlus
import com.willfp.eco.internal.spigot.integrations.shop.ShopZShop
import com.willfp.eco.internal.spigot.metrics.PlayerflowHandler
import com.willfp.eco.internal.spigot.proxy.FastItemStackFactoryProxy
import com.willfp.eco.internal.spigot.proxy.PacketHandlerProxy
import com.willfp.eco.internal.spigot.recipes.CraftingRecipeListener
@@ -127,6 +130,7 @@ import com.willfp.eco.internal.spigot.recipes.stackhandlers.ShapedCraftingRecipe
import com.willfp.eco.internal.spigot.recipes.stackhandlers.ShapelessCraftingRecipeStackHandler
import com.willfp.eco.util.ClassUtils
import me.TechsCode.UltraEconomy.UltraEconomy
import me.qKing12.RoyaleEconomy.MultiCurrency.MultiCurrencyHandler
import net.kyori.adventure.platform.bukkit.BukkitAudiences
import net.milkbowl.vault.economy.Economy
import org.bukkit.Bukkit
@@ -147,6 +151,7 @@ abstract class EcoSpigotPlugin : EcoPlugin() {
Items.registerArgParser(ArgParserFlag)
Items.registerArgParser(ArgParserUnbreakable)
Items.registerArgParser(ArgParserName)
Items.registerArgParser(ArgParserHead)
Entities.registerArgParser(EntityArgParserName)
Entities.registerArgParser(EntityArgParserNoAI)
@@ -219,8 +224,6 @@ abstract class EcoSpigotPlugin : EcoPlugin() {
this.logger.info("No conflicts found!")
}
CollatedRunnable(this)
CustomItemsManager.registerProviders() // Do it again here
// Register events for ShopSellEvent
@@ -251,19 +254,23 @@ abstract class EcoSpigotPlugin : EcoPlugin() {
Eco.get().adventure?.close()
}
override fun handleReload() {
override fun createTasks() {
CollatedRunnable(this)
this.scheduler.runLater(3) {
profileHandler.migrateIfNeeded()
}
ProfileSaver(this, profileHandler)
ProfileSaver(this, profileHandler).startTicking()
this.scheduler.runTimer(
{ getProxy(PacketHandlerProxy::class.java).clearDisplayFrames() },
this.configYml.getInt("display-frame-ttl").toLong(),
this.configYml.getInt("display-frame-ttl").toLong()
)
this.configYml.getInt("display-frame-ttl").toLong(),
) { getProxy(PacketHandlerProxy::class.java).clearDisplayFrames() }
if (this.configYml.getBool("playerflow")) {
PlayerflowHandler(this.scheduler).startTicking()
}
}
override fun handleAfterLoad() {
@@ -359,6 +366,11 @@ abstract class EcoSpigotPlugin : EcoPlugin() {
}
},
IntegrationLoader("PlayerPoints") { Prices.registerPriceFactory(PriceFactoryPlayerPoints()) },
IntegrationLoader("RoyaleEconomy") {
for (currency in MultiCurrencyHandler.getCurrencies()) {
Prices.registerPriceFactory(PriceFactoryRoyaleEconomy(currency))
}
},
// Placeholder
IntegrationLoader("PlaceholderAPI") { PlaceholderManager.addIntegration(PlaceholderIntegrationPAPI()) },
@@ -383,7 +395,7 @@ abstract class EcoSpigotPlugin : EcoPlugin() {
GUIListener(this),
ArrowDataListener(this),
ArmorChangeEventListeners(this),
DataListener(this),
DataListener(this, profileHandler),
PlayerBlockListener(this),
ServerLocking
)

View File

@@ -1,6 +1,5 @@
package com.willfp.eco.internal.spigot.data
import com.willfp.eco.core.Eco
import com.willfp.eco.core.EcoPlugin
import com.willfp.eco.util.PlayerUtils
import org.bukkit.event.EventHandler
@@ -11,11 +10,14 @@ import org.bukkit.event.player.PlayerLoginEvent
import org.bukkit.event.player.PlayerQuitEvent
class DataListener(
private val plugin: EcoPlugin
private val plugin: EcoPlugin,
private val handler: ProfileHandler
) : Listener {
@EventHandler(priority = EventPriority.HIGHEST)
fun onLeave(event: PlayerQuitEvent) {
Eco.get().unloadPlayerProfile(event.player.uniqueId)
val profile = handler.accessLoadedProfile(event.player.uniqueId) ?: return
handler.saveKeysFor(event.player.uniqueId, profile.data.keys)
handler.unloadPlayer(event.player.uniqueId)
}
@EventHandler
@@ -27,6 +29,6 @@ class DataListener(
@EventHandler(priority = EventPriority.LOWEST)
fun onLogin(event: PlayerLoginEvent) {
Eco.get().unloadPlayerProfile(event.player.uniqueId)
handler.unloadPlayer(event.player.uniqueId)
}
}

View File

@@ -1,17 +1,21 @@
package com.willfp.eco.internal.spigot.data
import com.willfp.eco.core.EcoPlugin
import com.willfp.eco.core.data.PlayerProfile
import com.willfp.eco.core.data.Profile
import com.willfp.eco.core.data.ServerProfile
import com.willfp.eco.core.data.keys.PersistentDataKey
import com.willfp.eco.core.data.keys.PersistentDataKeyType
import com.willfp.eco.internal.spigot.data.storage.DataHandler
import com.willfp.eco.util.namespacedKeyOf
import java.util.UUID
import java.util.concurrent.ConcurrentHashMap
abstract class EcoProfile(
val data: MutableMap<PersistentDataKey<*>, Any>,
val uuid: UUID,
private val handler: DataHandler
private val handler: DataHandler,
private val localHandler: DataHandler
) : Profile {
override fun <T : Any> write(key: PersistentDataKey<T>, value: T) {
this.data[key] = value
@@ -25,7 +29,12 @@ abstract class EcoProfile(
return this.data[key] as T
}
this.data[key] = handler.read(uuid, key) ?: key.defaultValue
this.data[key] = if (key.isLocal) {
localHandler.read(uuid, key)
} else {
handler.read(uuid, key)
} ?: key.defaultValue
return read(key)
}
@@ -49,18 +58,51 @@ abstract class EcoProfile(
class EcoPlayerProfile(
data: MutableMap<PersistentDataKey<*>, Any>,
uuid: UUID,
handler: DataHandler
) : EcoProfile(data, uuid, handler), PlayerProfile {
handler: DataHandler,
localHandler: DataHandler
) : EcoProfile(data, uuid, handler, localHandler), PlayerProfile {
override fun toString(): String {
return "EcoPlayerProfile{uuid=$uuid}"
}
}
private val serverIDKey = PersistentDataKey(
namespacedKeyOf("eco", "server_id"),
PersistentDataKeyType.STRING,
""
)
private val localServerIDKey = PersistentDataKey(
namespacedKeyOf("eco", "local_server_id"),
PersistentDataKeyType.STRING,
""
)
class EcoServerProfile(
data: MutableMap<PersistentDataKey<*>, Any>,
handler: DataHandler
) : EcoProfile(data, serverProfileUUID, handler), ServerProfile {
handler: DataHandler,
localHandler: DataHandler
) : EcoProfile(data, serverProfileUUID, handler, localHandler), ServerProfile {
override fun getServerID(): String {
if (this.read(serverIDKey).isBlank()) {
this.write(serverIDKey, UUID.randomUUID().toString())
}
return this.read(serverIDKey)
}
override fun getLocalServerID(): String {
if (this.read(localServerIDKey).isBlank()) {
this.write(localServerIDKey, UUID.randomUUID().toString())
}
return this.read(localServerIDKey)
}
override fun toString(): String {
return "EcoServerProfile"
}
}
private val PersistentDataKey<*>.isLocal: Boolean
get() = this == localServerIDKey || EcoPlugin.getPlugin(this.key.namespace)?.isUsingLocalStorage == true

View File

@@ -4,6 +4,7 @@ import com.willfp.eco.core.config.interfaces.Config
import com.willfp.eco.core.data.keys.PersistentDataKey
import com.willfp.eco.core.data.keys.PersistentDataKeyType
import org.bukkit.NamespacedKey
import java.math.BigDecimal
object KeyRegistry {
private val registry = mutableMapOf<NamespacedKey, PersistentDataKey<*>>()
@@ -44,6 +45,9 @@ object KeyRegistry {
PersistentDataKeyType.CONFIG -> if (default !is Config) {
throw IllegalArgumentException("Invalid Data Type! Should be Config")
}
PersistentDataKeyType.BIG_DECIMAL -> if (default !is BigDecimal) {
throw IllegalArgumentException("Invalid Data Type! Should be BigDecimal")
}
else -> throw NullPointerException("Null value found!")
}

View File

@@ -23,8 +23,10 @@ class ProfileHandler(
) {
private val loaded = mutableMapOf<UUID, EcoProfile>()
private val localHandler = YamlDataHandler(plugin, this)
val handler: DataHandler = when (type) {
HandlerType.YAML -> YamlDataHandler(plugin, this)
HandlerType.YAML -> localHandler
HandlerType.MYSQL -> MySQLDataHandler(plugin, this)
HandlerType.MONGO -> MongoDataHandler(plugin, this)
}
@@ -41,7 +43,7 @@ class ProfileHandler(
val data = mutableMapOf<PersistentDataKey<*>, Any>()
val profile = if (uuid == serverProfileUUID)
EcoServerProfile(data, handler) else EcoPlayerProfile(data, uuid, handler)
EcoServerProfile(data, handler, localHandler) else EcoPlayerProfile(data, uuid, handler, localHandler)
loaded[uuid] = profile
return profile
@@ -56,7 +58,19 @@ class ProfileHandler(
}
fun saveKeysFor(uuid: UUID, keys: Set<PersistentDataKey<*>>) {
handler.saveKeysFor(uuid, keys)
val profile = accessLoadedProfile(uuid) ?: return
val map = mutableMapOf<PersistentDataKey<*>, Any>()
for (key in keys) {
map[key] = profile.data[key] ?: continue
}
handler.saveKeysFor(uuid, map)
// Don't save to local handler if it's the same handler.
if (localHandler != handler) {
localHandler.saveKeysFor(uuid, map)
}
}
fun unloadPlayer(uuid: UUID) {
@@ -65,6 +79,10 @@ class ProfileHandler(
fun save() {
handler.save()
if (localHandler != handler) {
localHandler.save()
}
}
fun migrateIfNeeded() {
@@ -147,5 +165,8 @@ class ProfileHandler(
fun initialize() {
handler.initialize()
if (localHandler != handler) {
localHandler.initialize()
}
}
}

View File

@@ -19,7 +19,7 @@ abstract class DataHandler(
/**
* Save a set of keys for a given UUID.
*/
abstract fun saveKeysFor(uuid: UUID, keys: Set<PersistentDataKey<*>>)
abstract fun saveKeysFor(uuid: UUID, keys: Map<PersistentDataKey<*>, Any>)
// Everything below this are methods that are only needed for certain implementations.

View File

@@ -1,6 +1,5 @@
package com.willfp.eco.internal.spigot.data.storage
import com.willfp.eco.core.data.Profile
import com.willfp.eco.core.data.keys.PersistentDataKey
import com.willfp.eco.internal.spigot.EcoSpigotPlugin
import com.willfp.eco.internal.spigot.data.ProfileHandler
@@ -51,18 +50,16 @@ class MongoDataHandler(
}
}
override fun saveKeysFor(uuid: UUID, keys: Set<PersistentDataKey<*>>) {
val profile = handler.loadGenericProfile(uuid)
override fun saveKeysFor(uuid: UUID, keys: Map<PersistentDataKey<*>, Any>) {
scope.launch {
for (key in keys) {
saveKey(profile, uuid, key)
for ((key, value) in keys) {
saveKey(uuid, key, value)
}
}
}
private suspend fun <T : Any> saveKey(profile: Profile, uuid: UUID, key: PersistentDataKey<T>) {
val data = profile.read(key)
private suspend fun <T : Any> saveKey(uuid: UUID, key: PersistentDataKey<T>, value: Any) {
val data = value as T
doWrite(uuid, key, data)
}
@@ -100,6 +97,18 @@ class MongoDataHandler(
profile
}
}
override fun equals(other: Any?): Boolean {
if (this === other) {
return true
}
return other is MongoDataHandler
}
override fun hashCode(): Int {
return type.hashCode()
}
}
private data class UUIDProfile(

View File

@@ -21,6 +21,7 @@ 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.math.BigDecimal
import java.util.UUID
import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit
@@ -84,6 +85,9 @@ class MySQLDataHandler(
PersistentDataKeyType.BOOLEAN -> data.getBoolOrNull(key.key.toString())
PersistentDataKeyType.STRING_LIST -> data.getStringsOrNull(key.key.toString())
PersistentDataKeyType.CONFIG -> data.getSubsectionOrNull(key.key.toString())
PersistentDataKeyType.BIG_DECIMAL -> if (data.has(key.key.toString()))
BigDecimal(data.getString(key.key.toString())) else null
else -> null
}
@@ -97,16 +101,15 @@ class MySQLDataHandler(
setData(uuid, data)
}
override fun saveKeysFor(uuid: UUID, keys: Set<PersistentDataKey<*>>) {
val profile = handler.loadGenericProfile(uuid)
override fun saveKeysFor(uuid: UUID, keys: Map<PersistentDataKey<*>, Any>) {
executor.submit {
val data = getData(uuid)
for (key in keys) {
data.set(key.key.toString(), profile.read(key))
for ((key, value) in keys) {
data.set(key.key.toString(), value)
}
setData(uuid, data)
doSetData(uuid, data)
}
}
@@ -136,10 +139,14 @@ class MySQLDataHandler(
private fun setData(uuid: UUID, config: Config) {
executor.submit {
transaction(database) {
table.update({ table.id eq uuid }) {
it[dataColumn] = config.toPlaintext()
}
doSetData(uuid, config)
}
}
private fun doSetData(uuid: UUID, config: Config) {
transaction(database) {
table.update({ table.id eq uuid }) {
it[dataColumn] = config.toPlaintext()
}
}
}
@@ -149,4 +156,16 @@ class MySQLDataHandler(
SchemaUtils.createMissingTablesAndColumns(table, withLogs = false)
}
}
override fun equals(other: Any?): Boolean {
if (this === other) {
return true
}
return other is MySQLDataHandler
}
override fun hashCode(): Int {
return type.hashCode()
}
}

View File

@@ -5,10 +5,10 @@ import com.willfp.eco.internal.spigot.data.EcoProfile
import com.willfp.eco.internal.spigot.data.ProfileHandler
class ProfileSaver(
plugin: EcoPlugin,
handler: ProfileHandler
private val plugin: EcoPlugin,
private val handler: ProfileHandler
) {
init {
fun startTicking() {
val interval = plugin.configYml.getInt("save-interval").toLong()
plugin.scheduler.runTimer(20, interval) {

View File

@@ -5,6 +5,7 @@ import com.willfp.eco.core.data.keys.PersistentDataKeyType
import com.willfp.eco.internal.spigot.EcoSpigotPlugin
import com.willfp.eco.internal.spigot.data.ProfileHandler
import org.bukkit.NamespacedKey
import java.math.BigDecimal
import java.util.UUID
@Suppress("UNCHECKED_CAST")
@@ -27,6 +28,9 @@ class YamlDataHandler(
PersistentDataKeyType.BOOLEAN -> dataYml.getBoolOrNull("player.$uuid.${key.key}") as T?
PersistentDataKeyType.STRING_LIST -> dataYml.getStringsOrNull("player.$uuid.${key.key}") as T?
PersistentDataKeyType.CONFIG -> dataYml.getSubsectionOrNull("player.$uuid.${key.key}") as T?
PersistentDataKeyType.BIG_DECIMAL -> (if (dataYml.has(key.key.toString()))
BigDecimal(dataYml.getString(key.key.toString())) else null) as T?
else -> null
}
@@ -37,15 +41,25 @@ class YamlDataHandler(
doWrite(uuid, key.key, value)
}
override fun saveKeysFor(uuid: UUID, keys: Set<PersistentDataKey<*>>) {
val profile = handler.loadGenericProfile(uuid)
for (key in keys) {
doWrite(uuid, key.key, profile.read(key))
override fun saveKeysFor(uuid: UUID, keys: Map<PersistentDataKey<*>, Any>) {
for ((key, value) in keys) {
doWrite(uuid, key.key, value)
}
}
private fun doWrite(uuid: UUID, key: NamespacedKey, value: Any) {
dataYml.set("player.$uuid.$key", value)
}
override fun equals(other: Any?): Boolean {
if (this === other) {
return true
}
return other is YamlDataHandler
}
override fun hashCode(): Int {
return type.hashCode()
}
}

View File

@@ -44,6 +44,10 @@ class GUIListener(private val plugin: EcoPlugin) : Listener {
if (delegate is EcoSlot) {
delegate.handleInventoryClick(event, menu)
if (delegate.shouldRenderOnClick()) {
player.renderActiveMenu()
}
} else if (delegate === this) {
return
} else {
@@ -51,14 +55,6 @@ class GUIListener(private val plugin: EcoPlugin) : Listener {
}
}
@EventHandler(
priority = EventPriority.HIGHEST
)
fun handleRender(event: InventoryClickEvent) {
val player = event.whoClicked as? Player ?: return
player.renderActiveMenu()
}
@EventHandler(
priority = EventPriority.HIGH
)
@@ -94,6 +90,8 @@ class GUIListener(private val plugin: EcoPlugin) : Listener {
if (slot.isCaptive(player, menu)) {
if (!slot.isAllowedCaptive(player, menu, event.oldCursor)) {
event.isCancelled = true
} else {
player.renderActiveMenu()
}
} else {
event.isCancelled = true
@@ -126,6 +124,8 @@ class GUIListener(private val plugin: EcoPlugin) : Listener {
if (slot.isCaptive(player, menu)) {
if (!slot.isAllowedCaptive(player, menu, event.currentItem)) {
event.isCancelled = true
} else {
player.renderActiveMenu()
}
} else {
event.isCancelled = true
@@ -141,18 +141,6 @@ class GUIListener(private val plugin: EcoPlugin) : Listener {
plugin.scheduler.run { MenuHandler.unregisterInventory(event.inventory) }
}
@EventHandler
fun forceRender(event: InventoryClickEvent) {
val player = event.whoClicked as? Player ?: return
player.renderActiveMenu()
}
@EventHandler
fun forceRender(event: InventoryDragEvent) {
val player = event.whoClicked as? Player ?: return
player.renderActiveMenu()
}
@EventHandler(
priority = EventPriority.HIGHEST
)

View File

@@ -27,7 +27,8 @@ class AntigriefPvPManager: AntigriefIntegration {
override fun canInjure(player: Player, victim: LivingEntity): Boolean {
return when(victim) {
is Player -> {
(PvPlayer.get(victim).isInCombat)}
val defender = PvPlayer.get(victim)
(defender.hasPvPEnabled() && !defender.isNewbie || defender.isInCombat)}
else -> true
}
}

View File

@@ -0,0 +1,54 @@
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 com.willfp.eco.util.toSingletonList
import me.qKing12.RoyaleEconomy.MultiCurrency.Currency
import org.bukkit.entity.Player
import java.util.*
class PriceFactoryRoyaleEconomy(private val currency: Currency) : PriceFactory {
override fun getNames(): List<String> {
return currency.currencyId.lowercase().toSingletonList()
}
override fun create(baseContext: PlaceholderContext, function: PlaceholderContextSupplier<Double>): Price {
return PriceRoyaleEconomy(currency, baseContext) { function.get(it) }
}
private class PriceRoyaleEconomy(
private val currency: Currency,
private val baseContext: PlaceholderContext,
private val function: (PlaceholderContext) -> Double
) : Price {
private val multipliers = mutableMapOf<UUID, Double>()
override fun canAfford(player: Player, multiplier: Double): Boolean {
return currency.getAmount(player.uniqueId.toString()) >= getValue(player, multiplier)
}
override fun pay(player: Player, multiplier: Double) {
currency.removeAmount(player.uniqueId.toString(), getValue(player, multiplier))
}
override fun giveTo(player: Player, multiplier: Double) {
currency.addAmount(player.uniqueId.toString(), getValue(player, multiplier))
}
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

@@ -10,11 +10,11 @@ class DelegatedExpressionHandler(
plugin: EcoPlugin,
private val handler: ExpressionHandler
) : ExpressionHandler {
private val evaluationCache: Cache<Int, Double> = Caffeine.newBuilder()
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 {
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)
@@ -22,7 +22,7 @@ class DelegatedExpressionHandler(
return evaluationCache.get(hash) {
handler.evaluate(expression, context)
.let { if (!it.isFinite()) 0.0 else it } // Fixes NaN bug.
.let { if (it?.isFinite() != true) null else it } // Fixes NaN bug.
}
}
}

View File

@@ -14,8 +14,6 @@ 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])
}
@@ -25,8 +23,9 @@ private val max = Function("max", 2) {
}
interface ExpressionHandler {
fun evaluate(expression: String, context: PlaceholderContext): Double
fun evaluate(expression: String, context: PlaceholderContext): Double?
}
private fun String.fastToDoubleOrNull(): Double? {
if (isEmpty()) {
return null
@@ -47,6 +46,7 @@ private fun String.fastToDoubleOrNull(): Double? {
if (decimalIdx != -1) return null
decimalIdx = idx
}
in '0'..'9' -> {
val number = (char.code - '0'.code).toDouble()
if (decimalIdx != -1) {
@@ -55,6 +55,7 @@ private fun String.fastToDoubleOrNull(): Double? {
integerPart = integerPart * 10 + number
}
}
else -> return null
}
@@ -70,7 +71,7 @@ private fun String.fastToDoubleOrNull(): Double? {
class ImmediatePlaceholderTranslationExpressionHandler(
private val placeholderParser: PlaceholderParser
) : ExpressionHandler {
private val cache: Cache<String, CompiledExpression> = Caffeine.newBuilder()
private val cache: Cache<String, CompiledExpression?> = Caffeine.newBuilder()
.expireAfterAccess(500, TimeUnit.MILLISECONDS)
.build()
@@ -78,25 +79,24 @@ class ImmediatePlaceholderTranslationExpressionHandler(
addFunctions(min, max)
}
override fun evaluate(expression: String, context: PlaceholderContext): Double {
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)
runCatching { Crunch.compileExpression(translatedExpression, env) }.getOrNull()
}
return runCatching { compiled.evaluate() }.getOrDefault(0.0)
return runCatching { compiled?.evaluate() }.getOrNull()
}
}
class LazyPlaceholderTranslationExpressionHandler(
private val placeholderParser: PlaceholderParser
) : ExpressionHandler {
private val cache: Cache<String, CompiledExpression> = Caffeine.newBuilder()
private val cache: Cache<String, CompiledExpression?> = Caffeine.newBuilder()
.build()
override fun evaluate(expression: String, context: PlaceholderContext): Double {
override fun evaluate(expression: String, context: PlaceholderContext): Double? {
val placeholders = PlaceholderManager.findPlaceholdersIn(expression)
val placeholderValues = placeholderParser.parseIndividualPlaceholders(placeholders, context)
@@ -107,9 +107,9 @@ class LazyPlaceholderTranslationExpressionHandler(
val env = EvaluationEnvironment()
env.setVariableNames(*placeholders.toTypedArray())
env.addFunctions(min, max)
runCatching { Crunch.compileExpression(expression, env) }.getOrDefault(goToZero)
runCatching { Crunch.compileExpression(expression, env) }.getOrNull()
}
return runCatching { compiled.evaluate(*placeholderValues) }.getOrDefault(0.0)
return runCatching { compiled?.evaluate(*placeholderValues) }.getOrNull()
}
}

View File

@@ -0,0 +1,45 @@
package com.willfp.eco.internal.spigot.metrics
import com.willfp.eco.core.Eco
import com.willfp.eco.core.config.json
import com.willfp.eco.core.data.ServerProfile
import com.willfp.eco.core.scheduling.Scheduler
import org.bukkit.Bukkit
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse
private const val PLAYERFLOW_URL = "https://playerflow.auxilor.io/api/v1/ping"
private val client = HttpClient.newBuilder().build()
class PlayerflowHandler(
private val scheduler: Scheduler
) {
internal fun startTicking() {
scheduler.runAsyncTimer(1200L, 1200L) {
makeRequest()
}
}
private fun makeRequest() {
val body = json {
"uuid" to ServerProfile.load().localServerID
"players" to Bukkit.getOnlinePlayers().size
"plugins" to Eco.get().loadedPlugins
}.toPlaintext()
val request = HttpRequest.newBuilder()
.uri(URI.create(PLAYERFLOW_URL))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build()
try {
client.send(request, HttpResponse.BodyHandlers.ofString())
} catch (e: Exception) {
// Silently fail
}
}
}

View File

@@ -91,3 +91,9 @@ use-immediate-placeholder-translation-for-math: false
# faster evaluation times (less CPU usage) at the expense of slightly more memory usage and
# less reactive values.
math-cache-ttl: 200
# If anonymous usage statistics should be tracked. This is very valuable information as it
# helps understand how eco and other plugins are being used by logging player and server
# counts. This is completely anonymous and no personal information is logged. This data
# is primarily used for optimisation and server insights.
playerflow: true

View File

@@ -1 +1,7 @@
multiple-in-craft: '&l&c! &fThis recipe requires &a%amount%&f of this item.'
multiple-in-craft: '&l&c! &fThis recipe requires &a%amount%&f of this item.'
# Specify default display names for prices made through ConfiguredPrice#create
# These will override any custom configured price display names.
price-display:
- type: example_type
display: "&e%value% Price"

View File

@@ -194,5 +194,9 @@ dependencies:
bootstrap: false
- name: Denizen
required: false
bootstrap: false
- name: RoyaleEconomy
required: false
bootstrap: false

View File

@@ -54,3 +54,4 @@ softdepend:
- UltraEconomy
- PlayerPoints
- Denizen
- RoyaleEconomy

View File

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

BIN
lib/RoyaleEconomyAPI.jar Normal file

Binary file not shown.

View File

@@ -18,6 +18,7 @@ include(":eco-core:core-nms:v1_18_R2")
include(":eco-core:core-nms:v1_19_R1")
include(":eco-core:core-nms:v1_19_R2")
include(":eco-core:core-nms:v1_19_R3")
include(":eco-core:core-nms:v1_20_R1")
include(":eco-core:core-proxy")
include(":eco-core:core-plugin")
include(":eco-core:core-backend")