Compare commits

...

61 Commits

Author SHA1 Message Date
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
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
50 changed files with 690 additions and 488 deletions

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.
*

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

@@ -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

@@ -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

@@ -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

@@ -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

@@ -268,7 +268,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());

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

@@ -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,32 @@
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)

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

@@ -7,7 +7,6 @@ import com.willfp.eco.core.placeholder.context.PlaceholderContext
import com.willfp.eco.internal.fast.listView
import com.willfp.eco.util.StringUtils
import org.bukkit.configuration.file.YamlConfiguration
import java.util.Objects
import java.util.concurrent.ConcurrentHashMap
@Suppress("UNCHECKED_CAST")
@@ -235,19 +234,8 @@ open class EcoConfig(
return false
}
if (configType != other.configType) {
return false
}
if (values != other.values) {
return false
}
if (injections != other.injections) {
return false
}
return true
// Hey! Don't care. This works.
return this.hashCode() == other.hashCode()
}
override fun hashCode(): Int {
@@ -259,13 +247,13 @@ open class EcoConfig(
var injectionHash = 0
for (injection in injections.values) {
injectionHash = injectionHash * 31 + injection.hashCode()
injections.forEachValue(5) {
injectionHash = injectionHash xor (it.hashCode() shl 5)
}
return Objects.hash(
values,
configType
) + injectionHash
// 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

@@ -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,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.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

@@ -112,11 +112,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 +129,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
@@ -219,8 +222,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 +252,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 +364,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 +393,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

@@ -9,7 +9,6 @@ import com.willfp.eco.internal.spigot.EcoSpigotPlugin
import com.willfp.eco.internal.spigot.ServerLocking
import com.willfp.eco.internal.spigot.data.storage.DataHandler
import com.willfp.eco.internal.spigot.data.storage.HandlerType
import com.willfp.eco.internal.spigot.data.storage.LegacyMySQLDataHandler
import com.willfp.eco.internal.spigot.data.storage.MongoDataHandler
import com.willfp.eco.internal.spigot.data.storage.MySQLDataHandler
import com.willfp.eco.internal.spigot.data.storage.YamlDataHandler
@@ -24,21 +23,12 @@ 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)
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? =
@@ -53,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
@@ -68,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) {
@@ -77,6 +79,10 @@ class ProfileHandler(
fun save() {
handler.save()
if (localHandler != handler) {
localHandler.save()
}
}
fun migrateIfNeeded() {
@@ -90,11 +96,7 @@ class ProfileHandler(
}
var previousHandlerType = HandlerType.valueOf(plugin.dataYml.getString("previous-handler"))
if (previousHandlerType == HandlerType.MYSQL && !plugin.dataYml.has("new-mysql")) {
previousHandlerType = HandlerType.LEGACY_MYSQL
}
val previousHandlerType = HandlerType.valueOf(plugin.dataYml.getString("previous-handler"))
if (previousHandlerType == type) {
return
@@ -104,7 +106,6 @@ class ProfileHandler(
HandlerType.YAML -> YamlDataHandler(plugin, this)
HandlerType.MYSQL -> MySQLDataHandler(plugin, this)
HandlerType.MONGO -> MongoDataHandler(plugin, this)
HandlerType.LEGACY_MYSQL -> LegacyMySQLDataHandler(plugin, this)
}
ServerLocking.lock("Migrating player data! Check console for more information.")
@@ -164,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

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

View File

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

View File

@@ -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
@@ -34,7 +35,7 @@ Whatever. At least it works.
@Suppress("UNCHECKED_CAST")
class MySQLDataHandler(
private val plugin: EcoSpigotPlugin,
plugin: EcoSpigotPlugin,
private val handler: ProfileHandler
) : DataHandler(HandlerType.MYSQL) {
private val database: Database
@@ -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()
}
}
}
@@ -150,8 +157,15 @@ class MySQLDataHandler(
}
}
override fun save() {
plugin.dataYml.set("new-mysql", true)
plugin.dataYml.save()
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

@@ -4,7 +4,6 @@ import com.github.benmanes.caffeine.cache.Cache
import com.github.benmanes.caffeine.cache.Caffeine
import com.willfp.eco.core.EcoPlugin
import com.willfp.eco.core.placeholder.context.PlaceholderContext
import java.util.Objects
import java.util.concurrent.TimeUnit
class DelegatedExpressionHandler(
@@ -16,11 +15,10 @@ class DelegatedExpressionHandler(
.build()
override fun evaluate(expression: String, context: PlaceholderContext): Double {
val hash = Objects.hash(
expression,
context.player?.uniqueId,
context.injectableContext
)
// Peak performance (totally not having fun with bitwise operators)
val hash = (((expression.hashCode() shl 5) - expression.hashCode()) xor
(context.player?.uniqueId?.hashCode() ?: 0)
) xor context.injectableContext.hashCode()
return evaluationCache.get(hash) {
handler.evaluate(expression, context)

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.1
version = 6.61.1
plugin-name = eco
kotlin.code.style = official

BIN
lib/RoyaleEconomyAPI.jar Normal file

Binary file not shown.