Compare commits

..

44 Commits

Author SHA1 Message Date
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
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
42 changed files with 533 additions and 479 deletions

View File

@@ -135,6 +135,14 @@ public interface Eco {
@NotNull @NotNull
Logger createLogger(@NotNull EcoPlugin plugin); Logger createLogger(@NotNull EcoPlugin plugin);
/**
* Get NOOP logger.
*
* @return The logger.
*/
@NotNull
Logger getNOOPLogger();
/** /**
* Create a PAPI integration. * Create a PAPI integration.
* *
@@ -170,7 +178,7 @@ public interface Eco {
* @return The PluginCommandBase implementation * @return The PluginCommandBase implementation
*/ */
@NotNull @NotNull
PluginCommandBase createPluginCommand(@NotNull CommandBase parentDelegate, PluginCommandBase createPluginCommand(@NotNull PluginCommandBase parentDelegate,
@NotNull EcoPlugin plugin, @NotNull EcoPlugin plugin,
@NotNull String name, @NotNull String name,
@NotNull String permission, @NotNull String permission,
@@ -393,15 +401,6 @@ public interface Eco {
@NotNull @NotNull
ServerProfile getServerProfile(); 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. * Create dummy entity - never spawned, exists purely in code.
* *

View File

@@ -126,7 +126,7 @@ public abstract class EcoPlugin extends JavaPlugin implements PluginLike, Regist
/** /**
* The logger for the plugin. * The logger for the plugin.
*/ */
private final Logger logger; private Logger logger;
/** /**
* If the server is running an outdated version of the plugin. * 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<>(); 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. * Create a new plugin.
* <p> * <p>
@@ -425,7 +430,18 @@ public abstract class EcoPlugin extends JavaPlugin implements PluginLike, Regist
this.loadPluginCommands().forEach(PluginCommand::register); 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()) { if (this.isSupportingExtensions()) {
this.getExtensionLoader().loadExtensions(); this.getExtensionLoader().loadExtensions();
@@ -601,14 +617,30 @@ public abstract class EcoPlugin extends JavaPlugin implements PluginLike, Regist
* Reload the plugin. * Reload the plugin.
*/ */
public final void reload() { 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.getConfigHandler().updateConfigs();
this.getScheduler().cancelAll(); if (cancelTasks) {
this.getScheduler().cancelAll();
}
this.getConfigHandler().callUpdate(); this.getConfigHandler().callUpdate();
this.getConfigHandler().callUpdate(); // Call twice to fix issues this.getConfigHandler().callUpdate(); // Call twice to fix issues
this.handleLifecycle(this.onReload, this::handleReload); this.handleLifecycle(this.onReload, this::handleReload);
if (cancelTasks) {
this.handleLifecycle(this.createTasks, this::createTasks);
}
for (Extension extension : this.extensionLoader.getLoadedExtensions()) { for (Extension extension : this.extensionLoader.getLoadedExtensions()) {
extension.handleReload(); 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. * The plugin-specific code to be executed after the server is up.
* <p> * <p>
@@ -1150,6 +1191,16 @@ public abstract class EcoPlugin extends JavaPlugin implements PluginLike, Regist
return this.getMetadataValueFactory().create(value); 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 @Override
@NotNull @NotNull
public final String getID() { public final String getID() {

View File

@@ -9,6 +9,11 @@ import org.jetbrains.annotations.NotNull;
* Default plugin config.yml. * Default plugin config.yml.
*/ */
public class ConfigYml extends BaseConfig { public class ConfigYml extends BaseConfig {
/**
* The use local storage key.
*/
public static final String KEY_USES_LOCAL_STORAGE = "use-local-storage";
/** /**
* Config.yml. * Config.yml.
* *
@@ -52,4 +57,13 @@ public class ConfigYml extends BaseConfig {
final boolean removeUnused) { final boolean removeUnused) {
super(name, plugin, removeUnused, ConfigType.YAML); 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

@@ -4,6 +4,7 @@ import com.willfp.eco.core.config.interfaces.Config;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.math.BigDecimal;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@@ -49,6 +50,11 @@ public final class PersistentDataKeyType<T> {
*/ */
public static final PersistentDataKeyType<Config> CONFIG = new PersistentDataKeyType<>("CONFIG"); 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. * 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))); 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. * Add a menu close handler.
* *

View File

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

View File

@@ -92,6 +92,15 @@ public interface Slot extends GUIComponent {
return false; 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 @Override
default int getRows() { default int getRows() {
return 1; return 1;

View File

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

View File

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

View File

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

View File

@@ -3,10 +3,12 @@ package com.willfp.eco.core.placeholder.context;
import com.willfp.eco.core.placeholder.InjectablePlaceholder; import com.willfp.eco.core.placeholder.InjectablePlaceholder;
import com.willfp.eco.core.placeholder.PlaceholderInjectable; import com.willfp.eco.core.placeholder.PlaceholderInjectable;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Set; import java.util.Set;
/** /**
@@ -67,4 +69,24 @@ public class MergedInjectableContext implements PlaceholderInjectable {
return injections; 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

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

View File

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

View File

@@ -268,7 +268,7 @@ public final class NumberUtils {
* @deprecated Use {@link #evaluateExpression(String, PlaceholderContext)} instead. * @deprecated Use {@link #evaluateExpression(String, PlaceholderContext)} instead.
*/ */
@Deprecated(since = "6.56.0", forRemoval = true) @Deprecated(since = "6.56.0", forRemoval = true)
@SuppressWarnings("removal") @SuppressWarnings({"removal", "DeprecatedIsStillUsed"})
public static double evaluateExpression(@NotNull final String expression, public static double evaluateExpression(@NotNull final String expression,
@NotNull final com.willfp.eco.core.math.MathContext context) { @NotNull final com.willfp.eco.core.math.MathContext context) {
return evaluateExpression(expression, context.toPlaceholderContext()); return evaluateExpression(expression, context.toPlaceholderContext());

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.integrations.placeholder.PlaceholderManager;
import com.willfp.eco.core.placeholder.context.PlaceholderContext; import com.willfp.eco.core.placeholder.context.PlaceholderContext;
import net.kyori.adventure.text.Component; 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.format.TextDecoration;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
@@ -784,6 +786,127 @@ public final class StringUtils {
return result.toString(); 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. * Options for formatting.
*/ */

View File

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

View File

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

View File

@@ -2,6 +2,32 @@
package com.willfp.eco.util package com.willfp.eco.util
import com.willfp.eco.core.placeholder.context.PlaceholderContext
/** @see NumberUtils.toNumeral */ /** @see NumberUtils.toNumeral */
fun Number.toNumeral(): String = fun Number.toNumeral(): String =
NumberUtils.toNumeral(this.toInt()) 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)

View File

@@ -69,3 +69,15 @@ fun Any?.toNiceString(): String =
/** @see StringUtils.replaceQuickly */ /** @see StringUtils.replaceQuickly */
fun String.replaceQuickly(target: String, replacement: String): String = fun String.replaceQuickly(target: String, replacement: String): String =
StringUtils.replaceQuickly(this, target, replacement) 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( class DelegatedBukkitCommand(
private val delegate: EcoPluginCommand private val delegate: EcoPluginCommand
) : Command(delegate.name), TabCompleter, PluginIdentifiableCommand { ) : Command(
private var _aliases: List<String>? = null delegate.name,
private var _description: String? = null delegate.description ?: "",
"/${delegate.name}",
delegate.aliases
), TabCompleter, PluginIdentifiableCommand {
override fun execute(sender: CommandSender, label: String, args: Array<out String>?): Boolean { override fun execute(sender: CommandSender, label: String, args: Array<out String>?): Boolean {
return delegate.onCommand(sender, this, label, args) return delegate.onCommand(sender, this, label, args)
} }
@@ -36,16 +38,4 @@ class DelegatedBukkitCommand(
override fun getPlugin() = delegate.plugin override fun getPlugin() = delegate.plugin
override fun getPermission() = delegate.permission 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 import org.bukkit.Bukkit
class EcoPluginCommand( class EcoPluginCommand(
parentDelegate: CommandBase, private val parentDelegate: PluginCommandBase,
plugin: EcoPlugin, plugin: EcoPlugin,
name: String, name: String,
permission: String, permission: String,
@@ -39,6 +39,9 @@ class EcoPluginCommand(
Eco.get().syncCommands() Eco.get().syncCommands()
} }
override fun getAliases(): List<String> = parentDelegate.aliases
override fun getDescription(): String? = parentDelegate.description
} }
class EcoSubcommand( class EcoSubcommand(

View File

@@ -7,7 +7,6 @@ import com.willfp.eco.core.placeholder.context.PlaceholderContext
import com.willfp.eco.internal.fast.listView import com.willfp.eco.internal.fast.listView
import com.willfp.eco.util.StringUtils import com.willfp.eco.util.StringUtils
import org.bukkit.configuration.file.YamlConfiguration import org.bukkit.configuration.file.YamlConfiguration
import java.util.Objects
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
@@ -235,22 +234,26 @@ open class EcoConfig(
return false return false
} }
if (configType != other.configType) { // Hey! Don't care. This works.
return false return this.hashCode() == other.hashCode()
}
if (values != other.values) {
return false
}
if (injections != other.injections) {
return false
}
return true
} }
override fun hashCode(): Int { override fun hashCode(): Int {
return Objects.hash(values, configType, injections) /*
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

@@ -59,7 +59,7 @@ class EcoExtensionLoader(
val pluginVersion = Version(extensionYml.getStringOrNull("plugin-version") ?: "0.0.0") val pluginVersion = Version(extensionYml.getStringOrNull("plugin-version") ?: "0.0.0")
val pluginName = extensionYml.getStringOrNull("plugin") val pluginName = extensionYml.getStringOrNull("plugin")
if (pluginName != null && pluginName.equals(this.plugin.description.name, ignoreCase = true)) { if (pluginName != null && !pluginName.equals(this.plugin.description.name, ignoreCase = true)) {
throw ExtensionLoadException("${extensionJar.name} is only compatible with $pluginName!") throw ExtensionLoadException("${extensionJar.name} is only compatible with $pluginName!")
} }

View File

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

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

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

View File

@@ -219,8 +219,6 @@ abstract class EcoSpigotPlugin : EcoPlugin() {
this.logger.info("No conflicts found!") this.logger.info("No conflicts found!")
} }
CollatedRunnable(this)
CustomItemsManager.registerProviders() // Do it again here CustomItemsManager.registerProviders() // Do it again here
// Register events for ShopSellEvent // Register events for ShopSellEvent
@@ -251,14 +249,15 @@ abstract class EcoSpigotPlugin : EcoPlugin() {
Eco.get().adventure?.close() Eco.get().adventure?.close()
} }
override fun handleReload() { override fun createTasks() {
CollatedRunnable(this) CollatedRunnable(this)
this.scheduler.runLater(3) { this.scheduler.runLater(3) {
profileHandler.migrateIfNeeded() profileHandler.migrateIfNeeded()
} }
ProfileSaver(this, profileHandler) ProfileSaver(this, profileHandler).startTicking()
this.scheduler.runTimer( this.scheduler.runTimer(
{ getProxy(PacketHandlerProxy::class.java).clearDisplayFrames() }, { getProxy(PacketHandlerProxy::class.java).clearDisplayFrames() },
this.configYml.getInt("display-frame-ttl").toLong(), this.configYml.getInt("display-frame-ttl").toLong(),
@@ -383,7 +382,7 @@ abstract class EcoSpigotPlugin : EcoPlugin() {
GUIListener(this), GUIListener(this),
ArrowDataListener(this), ArrowDataListener(this),
ArmorChangeEventListeners(this), ArmorChangeEventListeners(this),
DataListener(this), DataListener(this, profileHandler),
PlayerBlockListener(this), PlayerBlockListener(this),
ServerLocking ServerLocking
) )

View File

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

View File

@@ -1,5 +1,6 @@
package com.willfp.eco.internal.spigot.data 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.PlayerProfile
import com.willfp.eco.core.data.Profile import com.willfp.eco.core.data.Profile
import com.willfp.eco.core.data.ServerProfile import com.willfp.eco.core.data.ServerProfile
@@ -11,7 +12,8 @@ import java.util.concurrent.ConcurrentHashMap
abstract class EcoProfile( abstract class EcoProfile(
val data: MutableMap<PersistentDataKey<*>, Any>, val data: MutableMap<PersistentDataKey<*>, Any>,
val uuid: UUID, val uuid: UUID,
private val handler: DataHandler private val handler: DataHandler,
private val localHandler: DataHandler
) : Profile { ) : Profile {
override fun <T : Any> write(key: PersistentDataKey<T>, value: T) { override fun <T : Any> write(key: PersistentDataKey<T>, value: T) {
this.data[key] = value this.data[key] = value
@@ -25,7 +27,12 @@ abstract class EcoProfile(
return this.data[key] as T 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) return read(key)
} }
@@ -49,8 +56,9 @@ abstract class EcoProfile(
class EcoPlayerProfile( class EcoPlayerProfile(
data: MutableMap<PersistentDataKey<*>, Any>, data: MutableMap<PersistentDataKey<*>, Any>,
uuid: UUID, uuid: UUID,
handler: DataHandler handler: DataHandler,
) : EcoProfile(data, uuid, handler), PlayerProfile { localHandler: DataHandler
) : EcoProfile(data, uuid, handler, localHandler), PlayerProfile {
override fun toString(): String { override fun toString(): String {
return "EcoPlayerProfile{uuid=$uuid}" return "EcoPlayerProfile{uuid=$uuid}"
} }
@@ -58,9 +66,13 @@ class EcoPlayerProfile(
class EcoServerProfile( class EcoServerProfile(
data: MutableMap<PersistentDataKey<*>, Any>, data: MutableMap<PersistentDataKey<*>, Any>,
handler: DataHandler handler: DataHandler,
) : EcoProfile(data, serverProfileUUID, handler), ServerProfile { localHandler: DataHandler
) : EcoProfile(data, serverProfileUUID, handler, localHandler), ServerProfile {
override fun toString(): String { override fun toString(): String {
return "EcoServerProfile" return "EcoServerProfile"
} }
} }
private val PersistentDataKey<*>.isLocal: Boolean
get() = 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.PersistentDataKey
import com.willfp.eco.core.data.keys.PersistentDataKeyType import com.willfp.eco.core.data.keys.PersistentDataKeyType
import org.bukkit.NamespacedKey import org.bukkit.NamespacedKey
import java.math.BigDecimal
object KeyRegistry { object KeyRegistry {
private val registry = mutableMapOf<NamespacedKey, PersistentDataKey<*>>() private val registry = mutableMapOf<NamespacedKey, PersistentDataKey<*>>()
@@ -44,6 +45,9 @@ object KeyRegistry {
PersistentDataKeyType.CONFIG -> if (default !is Config) { PersistentDataKeyType.CONFIG -> if (default !is Config) {
throw IllegalArgumentException("Invalid Data Type! Should be 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!") else -> throw NullPointerException("Null value found!")
} }

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.ServerLocking
import com.willfp.eco.internal.spigot.data.storage.DataHandler 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.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.MongoDataHandler
import com.willfp.eco.internal.spigot.data.storage.MySQLDataHandler import com.willfp.eco.internal.spigot.data.storage.MySQLDataHandler
import com.willfp.eco.internal.spigot.data.storage.YamlDataHandler import com.willfp.eco.internal.spigot.data.storage.YamlDataHandler
@@ -24,21 +23,12 @@ class ProfileHandler(
) { ) {
private val loaded = mutableMapOf<UUID, EcoProfile>() private val loaded = mutableMapOf<UUID, EcoProfile>()
private val localHandler = YamlDataHandler(plugin, this)
val handler: DataHandler = when (type) { val handler: DataHandler = when (type) {
HandlerType.YAML -> YamlDataHandler(plugin, this) HandlerType.YAML -> localHandler
HandlerType.MYSQL -> MySQLDataHandler(plugin, this) HandlerType.MYSQL -> MySQLDataHandler(plugin, this)
HandlerType.MONGO -> MongoDataHandler(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? = fun accessLoadedProfile(uuid: UUID): EcoProfile? =
@@ -53,7 +43,7 @@ class ProfileHandler(
val data = mutableMapOf<PersistentDataKey<*>, Any>() val data = mutableMapOf<PersistentDataKey<*>, Any>()
val profile = if (uuid == serverProfileUUID) 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 loaded[uuid] = profile
return profile return profile
@@ -68,7 +58,19 @@ class ProfileHandler(
} }
fun saveKeysFor(uuid: UUID, keys: Set<PersistentDataKey<*>>) { 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) { fun unloadPlayer(uuid: UUID) {
@@ -77,6 +79,10 @@ class ProfileHandler(
fun save() { fun save() {
handler.save() handler.save()
if (localHandler != handler) {
localHandler.save()
}
} }
fun migrateIfNeeded() { fun migrateIfNeeded() {
@@ -90,11 +96,7 @@ class ProfileHandler(
} }
var previousHandlerType = HandlerType.valueOf(plugin.dataYml.getString("previous-handler")) val previousHandlerType = HandlerType.valueOf(plugin.dataYml.getString("previous-handler"))
if (previousHandlerType == HandlerType.MYSQL && !plugin.dataYml.has("new-mysql")) {
previousHandlerType = HandlerType.LEGACY_MYSQL
}
if (previousHandlerType == type) { if (previousHandlerType == type) {
return return
@@ -104,7 +106,6 @@ class ProfileHandler(
HandlerType.YAML -> YamlDataHandler(plugin, this) HandlerType.YAML -> YamlDataHandler(plugin, this)
HandlerType.MYSQL -> MySQLDataHandler(plugin, this) HandlerType.MYSQL -> MySQLDataHandler(plugin, this)
HandlerType.MONGO -> MongoDataHandler(plugin, this) HandlerType.MONGO -> MongoDataHandler(plugin, this)
HandlerType.LEGACY_MYSQL -> LegacyMySQLDataHandler(plugin, this)
} }
ServerLocking.lock("Migrating player data! Check console for more information.") ServerLocking.lock("Migrating player data! Check console for more information.")
@@ -164,5 +165,8 @@ class ProfileHandler(
fun initialize() { fun initialize() {
handler.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. * 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. // Everything below this are methods that are only needed for certain implementations.

View File

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

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

@@ -1,6 +1,5 @@
package com.willfp.eco.internal.spigot.data.storage 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.core.data.keys.PersistentDataKey
import com.willfp.eco.internal.spigot.EcoSpigotPlugin import com.willfp.eco.internal.spigot.EcoSpigotPlugin
import com.willfp.eco.internal.spigot.data.ProfileHandler import com.willfp.eco.internal.spigot.data.ProfileHandler
@@ -51,18 +50,16 @@ class MongoDataHandler(
} }
} }
override fun saveKeysFor(uuid: UUID, keys: Set<PersistentDataKey<*>>) { override fun saveKeysFor(uuid: UUID, keys: Map<PersistentDataKey<*>, Any>) {
val profile = handler.loadGenericProfile(uuid)
scope.launch { scope.launch {
for (key in keys) { for ((key, value) in keys) {
saveKey(profile, uuid, key) saveKey(uuid, key, value)
} }
} }
} }
private suspend fun <T : Any> saveKey(profile: Profile, uuid: UUID, key: PersistentDataKey<T>) { private suspend fun <T : Any> saveKey(uuid: UUID, key: PersistentDataKey<T>, value: Any) {
val data = profile.read(key) val data = value as T
doWrite(uuid, key, data) doWrite(uuid, key, data)
} }
@@ -100,6 +97,18 @@ class MongoDataHandler(
profile 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( 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.select
import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.transactions.transaction
import org.jetbrains.exposed.sql.update import org.jetbrains.exposed.sql.update
import java.math.BigDecimal
import java.util.UUID import java.util.UUID
import java.util.concurrent.Executors import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@@ -34,7 +35,7 @@ Whatever. At least it works.
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
class MySQLDataHandler( class MySQLDataHandler(
private val plugin: EcoSpigotPlugin, plugin: EcoSpigotPlugin,
private val handler: ProfileHandler private val handler: ProfileHandler
) : DataHandler(HandlerType.MYSQL) { ) : DataHandler(HandlerType.MYSQL) {
private val database: Database private val database: Database
@@ -84,6 +85,9 @@ class MySQLDataHandler(
PersistentDataKeyType.BOOLEAN -> data.getBoolOrNull(key.key.toString()) PersistentDataKeyType.BOOLEAN -> data.getBoolOrNull(key.key.toString())
PersistentDataKeyType.STRING_LIST -> data.getStringsOrNull(key.key.toString()) PersistentDataKeyType.STRING_LIST -> data.getStringsOrNull(key.key.toString())
PersistentDataKeyType.CONFIG -> data.getSubsectionOrNull(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 else -> null
} }
@@ -97,16 +101,15 @@ class MySQLDataHandler(
setData(uuid, data) setData(uuid, data)
} }
override fun saveKeysFor(uuid: UUID, keys: Set<PersistentDataKey<*>>) { override fun saveKeysFor(uuid: UUID, keys: Map<PersistentDataKey<*>, Any>) {
val profile = handler.loadGenericProfile(uuid)
executor.submit { executor.submit {
val data = getData(uuid) 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) { private fun setData(uuid: UUID, config: Config) {
executor.submit { executor.submit {
transaction(database) { doSetData(uuid, config)
table.update({ table.id eq uuid }) { }
it[dataColumn] = config.toPlaintext() }
}
private fun doSetData(uuid: UUID, config: Config) {
transaction(database) {
table.update({ table.id eq uuid }) {
it[dataColumn] = config.toPlaintext()
} }
} }
} }
@@ -150,8 +157,15 @@ class MySQLDataHandler(
} }
} }
override fun save() { override fun equals(other: Any?): Boolean {
plugin.dataYml.set("new-mysql", true) if (this === other) {
plugin.dataYml.save() 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 import com.willfp.eco.internal.spigot.data.ProfileHandler
class ProfileSaver( class ProfileSaver(
plugin: EcoPlugin, private val plugin: EcoPlugin,
handler: ProfileHandler private val handler: ProfileHandler
) { ) {
init { fun startTicking() {
val interval = plugin.configYml.getInt("save-interval").toLong() val interval = plugin.configYml.getInt("save-interval").toLong()
plugin.scheduler.runTimer(20, interval) { plugin.scheduler.runTimer(20, interval) {
@@ -18,7 +18,12 @@ class ProfileSaver(
val uuid = iterator.next() val uuid = iterator.next()
iterator.remove() iterator.remove()
val profile = handler.accessLoadedProfile(uuid) ?: continue val profile = handler.accessLoadedProfile(uuid)
if (profile == null) {
iterator.remove()
continue
}
handler.saveKeysFor(uuid, profile.data.keys) handler.saveKeysFor(uuid, profile.data.keys)
} }

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.EcoSpigotPlugin
import com.willfp.eco.internal.spigot.data.ProfileHandler import com.willfp.eco.internal.spigot.data.ProfileHandler
import org.bukkit.NamespacedKey import org.bukkit.NamespacedKey
import java.math.BigDecimal
import java.util.UUID import java.util.UUID
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
@@ -27,6 +28,9 @@ class YamlDataHandler(
PersistentDataKeyType.BOOLEAN -> dataYml.getBoolOrNull("player.$uuid.${key.key}") as T? PersistentDataKeyType.BOOLEAN -> dataYml.getBoolOrNull("player.$uuid.${key.key}") as T?
PersistentDataKeyType.STRING_LIST -> dataYml.getStringsOrNull("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.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 else -> null
} }
@@ -37,15 +41,25 @@ class YamlDataHandler(
doWrite(uuid, key.key, value) doWrite(uuid, key.key, value)
} }
override fun saveKeysFor(uuid: UUID, keys: Set<PersistentDataKey<*>>) { override fun saveKeysFor(uuid: UUID, keys: Map<PersistentDataKey<*>, Any>) {
val profile = handler.loadGenericProfile(uuid) for ((key, value) in keys) {
doWrite(uuid, key.key, value)
for (key in keys) {
doWrite(uuid, key.key, profile.read(key))
} }
} }
private fun doWrite(uuid: UUID, key: NamespacedKey, value: Any) { private fun doWrite(uuid: UUID, key: NamespacedKey, value: Any) {
dataYml.set("player.$uuid.$key", value) 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) { if (delegate is EcoSlot) {
delegate.handleInventoryClick(event, menu) delegate.handleInventoryClick(event, menu)
if (delegate.shouldRenderOnClick()) {
player.renderActiveMenu()
}
} else if (delegate === this) { } else if (delegate === this) {
return return
} else { } 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( @EventHandler(
priority = EventPriority.HIGH priority = EventPriority.HIGH
) )
@@ -94,6 +90,8 @@ class GUIListener(private val plugin: EcoPlugin) : Listener {
if (slot.isCaptive(player, menu)) { if (slot.isCaptive(player, menu)) {
if (!slot.isAllowedCaptive(player, menu, event.oldCursor)) { if (!slot.isAllowedCaptive(player, menu, event.oldCursor)) {
event.isCancelled = true event.isCancelled = true
} else {
player.renderActiveMenu()
} }
} else { } else {
event.isCancelled = true event.isCancelled = true
@@ -126,6 +124,8 @@ class GUIListener(private val plugin: EcoPlugin) : Listener {
if (slot.isCaptive(player, menu)) { if (slot.isCaptive(player, menu)) {
if (!slot.isAllowedCaptive(player, menu, event.currentItem)) { if (!slot.isAllowedCaptive(player, menu, event.currentItem)) {
event.isCancelled = true event.isCancelled = true
} else {
player.renderActiveMenu()
} }
} else { } else {
event.isCancelled = true event.isCancelled = true
@@ -141,18 +141,6 @@ class GUIListener(private val plugin: EcoPlugin) : Listener {
plugin.scheduler.run { MenuHandler.unregisterInventory(event.inventory) } 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( @EventHandler(
priority = EventPriority.HIGHEST priority = EventPriority.HIGHEST
) )

View File

@@ -4,23 +4,21 @@ import com.github.benmanes.caffeine.cache.Cache
import com.github.benmanes.caffeine.cache.Caffeine import com.github.benmanes.caffeine.cache.Caffeine
import com.willfp.eco.core.EcoPlugin import com.willfp.eco.core.EcoPlugin
import com.willfp.eco.core.placeholder.context.PlaceholderContext import com.willfp.eco.core.placeholder.context.PlaceholderContext
import java.util.Objects
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
class DelegatedExpressionHandler( class DelegatedExpressionHandler(
plugin: EcoPlugin, plugin: EcoPlugin,
private val handler: ExpressionHandler private val handler: ExpressionHandler
): 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) .expireAfterWrite(plugin.configYml.getInt("math-cache-ttl").toLong(), TimeUnit.MILLISECONDS)
.build() .build()
override fun evaluate(expression: String, context: PlaceholderContext): Double { override fun evaluate(expression: String, context: PlaceholderContext): Double {
val hash = Objects.hash( // Peak performance (totally not having fun with bitwise operators)
expression, val hash = (((expression.hashCode() shl 5) - expression.hashCode()) xor
context.player?.uniqueId, (context.player?.uniqueId?.hashCode() ?: 0)
context.injectableContext ) xor context.injectableContext.hashCode()
)
return evaluationCache.get(hash) { return evaluationCache.get(hash) {
handler.evaluate(expression, context) handler.evaluate(expression, context)

View File

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