Compare commits

..

58 Commits

Author SHA1 Message Date
Auxilor
dfe2f1361b Updated to 6.41.0 2022-09-14 13:58:10 +01:00
Auxilor
3826f9f713 Added item price API 2022-09-14 13:57:01 +01:00
Auxilor
c2d2303c91 Added scyther integration and BigDecimal economy support 2022-09-14 13:45:32 +01:00
Auxilor
b1158ceb3d Added configurable host to Paste 2022-09-12 14:39:15 +01:00
Auxilor
af4048afbe Updated to 6.40.2 2022-09-12 13:53:51 +01:00
Auxilor
fce12020d5 Fixed bug with old MySQL versions 2022-09-12 13:53:35 +01:00
Auxilor
7e37332b64 Updated to 6.40.1 2022-09-11 10:47:23 +01:00
Auxilor
52dbf9ff52 Codestyle 2022-09-11 10:47:00 +01:00
Auxilor
78b6292367 Merge remote-tracking branch 'origin/master' 2022-09-11 10:46:43 +01:00
Will FP
09705e787b Merge pull request #193
Fix DeluxeCombat and FabledSkyBlock integration
2022-09-11 10:46:44 +01:00
Auxilor
9c86069a85 Added option to disable the update checker 2022-09-11 10:45:40 +01:00
Auxilor
0a7acceb83 Updated to 6.40.0 2022-09-10 19:59:07 +01:00
Auxilor
40aa8b17dd Fixed MySQL changes 2022-09-10 19:58:57 +01:00
Auxilor
71eb386a19 Reworked MySQL data handler and data migration 2022-09-10 19:22:29 +01:00
Kapitowa
c857df2360 fix FabledSkyBlock integration for proper check 2022-09-09 14:45:33 +03:00
Kapitowa
f4fc611f3b Fix when damager with DeluxeCombat protection can cause damage 2022-09-09 05:31:54 +03:00
Auxilor
0d91324d47 Updated crunch 2022-09-06 15:26:46 +01:00
Auxilor
e9dbc3ec73 Updated crunch 2022-09-06 15:11:42 +01:00
Auxilor
493d1b1b6d Updated crunch 2022-09-06 15:11:14 +01:00
Auxilor
68221d5912 Updated crunch 2022-09-06 15:07:29 +01:00
Auxilor
7f2ef4e038 Updated to 6.39.1 2022-09-06 15:06:11 +01:00
Auxilor
be0a19175b Added additional players to config expression getters 2022-09-06 15:05:52 +01:00
Auxilor
5afdcd75f7 Added min and max functions to crunch 2022-09-06 14:49:40 +01:00
Auxilor
a1c0b8c857 Fixed FUUID 2022-09-06 13:25:17 +01:00
Auxilor
0442ccf58f Fixed FactionsUUID jar 2022-09-06 13:23:59 +01:00
Auxilor
1c1a796610 FactionsUUID to local jar 2022-09-06 13:19:49 +01:00
Auxilor
eacb243493 Fixed FactionsUUID 2022-09-06 13:17:55 +01:00
Auxilor
7bbed31d4e Fixed FactionsUUID 2022-09-06 13:17:43 +01:00
Auxilor
58bccf3cd7 Added songoda repo for FabledSkyblock 2022-09-06 13:15:36 +01:00
Auxilor
4502e1e311 Removed ender.zone, using JitPack build instead 2022-09-06 13:14:27 +01:00
Auxilor
054a8d5a5e Updated to 6.39.0 2022-09-06 13:04:55 +01:00
Auxilor
dbdd4785ba Added AdditionalPlayer support to placeholders 2022-09-06 13:04:00 +01:00
Will FP
35f800b62a Merge pull request #178
Added FabledSkyBlock integration in AntigriefManager
2022-09-06 12:50:56 +01:00
Auxilor
591800dba8 Updated to 6.38.3 2022-08-22 14:16:20 +02:00
Auxilor
46673e8d24 Fixed SNBT 2022-08-22 14:16:07 +02:00
Kapitowa
bb7c300074 fix space and fill softdepend 2022-08-02 18:46:09 +03:00
Kapitowa
b003ec96f7 Added FabledSkyBlock integration in AntigriefManager 2022-08-02 18:33:18 +03:00
Will FP
a526f51780 Update README.md 2022-07-28 16:07:08 +01:00
Auxilor
dd14fc666a Updated to 6.38.2 2022-07-27 19:42:19 +01:00
Auxilor
ae0150f012 Bumped ShopGUI+ 2022-07-27 19:42:06 +01:00
Auxilor
04418fa038 Updated ProtocolLib for compatibility 2022-07-27 19:40:08 +01:00
Auxilor
bcb9523315 Updated to 6.38.1 2022-07-25 16:51:55 +01:00
Auxilor
43e7972ca3 Improved menu re-renders 2022-07-25 16:51:38 +01:00
Auxilor
7ea61eb393 Removed source/target compatibility no longer required by kotlin 1.7.x 2022-07-22 14:08:33 +01:00
Auxilor
5245a9b1d8 Updated to 6.38.0 2022-07-22 14:05:53 +01:00
Auxilor
195932463c Added DisplayProperties to DisplayModule 2022-07-22 14:04:42 +01:00
Auxilor
3cf60a7e2c Added MenuUtils#getOpenMenu 2022-07-22 13:54:45 +01:00
Auxilor
0f9f57fca2 Merge remote-tracking branch 'origin/master' 2022-07-22 13:47:03 +01:00
Will FP
fe21616dd5 Merge pull request #161 from Syrent/master
Add MythicMobs 5.X support.
2022-07-22 13:46:53 +01:00
Auxilor
396144abaa Added Vector#isSafeVelocity 2022-07-22 13:46:08 +01:00
Auxilor
50b4fa59ab Added onOpen to menu 2022-07-22 13:42:48 +01:00
Auxilor
a6754379e8 Updated to 6.37.3 2022-07-07 22:55:03 +01:00
Auxilor
bbd0182c2a Fixed weird bug 2022-07-07 22:54:54 +01:00
Auxilor
0370e9f454 Fixed startup order 2022-07-03 16:49:26 +01:00
Auxilor
8d585b58cb Fixed initializing text 2022-07-03 16:45:44 +01:00
Auxilor
0bfbd4c036 Updated to 6.37.2 2022-07-03 16:38:32 +01:00
Auxilor
881839955e Added player health fixer 2022-07-03 16:38:21 +01:00
Syrent
8ffc5f9c0f Add MythicMobs 5.X support. 2022-06-28 08:07:05 +04:30
66 changed files with 1462 additions and 651 deletions

View File

@@ -39,7 +39,7 @@ and many more.
# For developers
## Javadoc
The 6.27.2 Javadoc can be found [here](https://javadoc.jitpack.io/com/willfp/eco/6.27.2/javadoc/)
The 6.38.2 Javadoc can be found [here](https://javadoc.jitpack.io/com/willfp/eco/6.38.2/javadoc/)
## Plugin Information

View File

@@ -4,7 +4,7 @@ buildscript {
}
dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.21")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10")
}
}
@@ -13,7 +13,7 @@ plugins {
id("com.github.johnrengelman.shadow") version "7.1.2"
id("maven-publish")
id("java")
kotlin("jvm") version "1.6.21"
kotlin("jvm") version "1.7.10"
}
dependencies {
@@ -61,7 +61,7 @@ allprojects {
maven("https://maven.enginehub.org/repo/")
// FactionsUUID
maven("https://ci.ender.zone/plugin/repository/everything/")
//maven("https://ci.ender.zone/plugin/repository/everything/")
// NoCheatPlus
maven("https://repo.md-5.net/content/repositories/snapshots/")
@@ -77,12 +77,15 @@ allprojects {
// LibsDisguises
maven("https://repo.md-5.net/content/groups/public/")
// FabledSkyblock
maven("https://repo.songoda.com/repository/public/")
}
dependencies {
// Kotlin
implementation(kotlin("stdlib", version = "1.6.21"))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1")
implementation(kotlin("stdlib", version = "1.7.10"))
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.2")
// Included in spigot jar, no need to move to implementation
compileOnly("org.jetbrains:annotations:23.0.0")
@@ -129,8 +132,6 @@ allprojects {
kotlinOptions {
jvmTarget = "17"
}
targetCompatibility = "17"
sourceCompatibility = "17"
}
shadowJar {

View File

@@ -304,6 +304,11 @@ public abstract class EcoPlugin extends JavaPlugin implements PluginLike {
this.color = props.getColor();
this.supportingExtensions = props.isSupportingExtensions();
this.proxyFactory = this.proxyPackage.equalsIgnoreCase("") ? null : Eco.getHandler().createProxyFactory(this);
this.logger = Eco.getHandler().createLogger(this);
this.getLogger().info("Initializing " + this.getColor() + this.getName());
this.scheduler = Eco.getHandler().createScheduler(this);
this.eventManager = Eco.getHandler().createEventManager(this);
this.namespacedKeyFactory = Eco.getHandler().createNamespacedKeyFactory(this);
@@ -311,16 +316,12 @@ public abstract class EcoPlugin extends JavaPlugin implements PluginLike {
this.runnableFactory = Eco.getHandler().createRunnableFactory(this);
this.extensionLoader = Eco.getHandler().createExtensionLoader(this);
this.configHandler = Eco.getHandler().createConfigHandler(this);
this.logger = Eco.getHandler().createLogger(this);
this.proxyFactory = this.proxyPackage.equalsIgnoreCase("") ? null : Eco.getHandler().createProxyFactory(this);
this.langYml = this.createLangYml();
this.configYml = this.createConfigYml();
Eco.getHandler().addNewPlugin(this);
this.getLogger().info("Initializing " + this.getColor() + this.getName());
/*
The minimum eco version check was moved here because it's very common
to add a lot of code in the constructor of plugins; meaning that the plugin
@@ -349,7 +350,7 @@ public abstract class EcoPlugin extends JavaPlugin implements PluginLike {
this.getLogger().info("");
this.getLogger().info("Loading " + this.getColor() + this.getName());
if (this.getResourceId() != 0) {
if (this.getResourceId() != 0 && !Eco.getHandler().getEcoPlugin().getConfigYml().getBool("no-update-checker")) {
new UpdateChecker(this).getVersion(version -> {
DefaultArtifactVersion currentVersion = new DefaultArtifactVersion(this.getDescription().getVersion());
DefaultArtifactVersion mostRecentVersion = new DefaultArtifactVersion(version);

View File

@@ -41,11 +41,19 @@ public class Prerequisite {
"Requires server to have vault"
);
/**
* Requires the server to be running 1.19.
*/
public static final Prerequisite HAS_1_19 = new Prerequisite(
() -> ProxyConstants.NMS_VERSION.contains("19"),
"Requires server to be running 1.19+"
);
/**
* Requires the server to be running 1.18.
*/
public static final Prerequisite HAS_1_18 = new Prerequisite(
() -> ProxyConstants.NMS_VERSION.contains("18"),
() -> ProxyConstants.NMS_VERSION.contains("18") || HAS_1_19.isMet(),
"Requires server to be running 1.18+"
);

View File

@@ -3,6 +3,7 @@ package com.willfp.eco.core.config.interfaces;
import com.willfp.eco.core.config.BuildableConfig;
import com.willfp.eco.core.config.ConfigType;
import com.willfp.eco.core.config.TransientConfig;
import com.willfp.eco.core.placeholder.AdditionalPlayer;
import com.willfp.eco.core.placeholder.InjectablePlaceholder;
import com.willfp.eco.core.placeholder.PlaceholderInjectable;
import com.willfp.eco.util.NumberUtils;
@@ -14,6 +15,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -77,7 +79,6 @@ public interface Config extends Cloneable, PlaceholderInjectable {
/**
* Get an object from config.
* Default implementations call {@link org.bukkit.configuration.file.YamlConfiguration#get(String)}.
*
* @param path The path.
* @return The object.
@@ -87,7 +88,6 @@ public interface Config extends Cloneable, PlaceholderInjectable {
/**
* Set an object in config.
* Default implementations call {@link org.bukkit.configuration.file.YamlConfiguration#set(String, Object)}
*
* @param path The path.
* @param object The object.
@@ -159,6 +159,20 @@ public interface Config extends Cloneable, PlaceholderInjectable {
return Double.valueOf(getDoubleFromExpression(path, player)).intValue();
}
/**
* Get a decimal value via a mathematical expression.
*
* @param path The key to fetch the value from.
* @param player The player to evaluate placeholders with respect to.
* @param additionalPlayers The additional players to evaluate placeholders with respect to.
* @return The computed value, or 0 if not found or invalid.
*/
default int getIntFromExpression(@NotNull String path,
@Nullable Player player,
@NotNull Collection<AdditionalPlayer> additionalPlayers) {
return Double.valueOf(getDoubleFromExpression(path, player, additionalPlayers)).intValue();
}
/**
* Get an integer from config.
@@ -474,6 +488,20 @@ public interface Config extends Cloneable, PlaceholderInjectable {
return NumberUtils.evaluateExpression(this.getString(path), player, this);
}
/**
* Get a decimal value via a mathematical expression.
*
* @param path The key to fetch the value from.
* @param player The player to evaluate placeholders with respect to.
* @param additionalPlayers The additional players to evaluate placeholders with respect to.
* @return The computed value, or 0 if not found or invalid.
*/
default double getDoubleFromExpression(@NotNull String path,
@Nullable Player player,
@NotNull Collection<AdditionalPlayer> additionalPlayers) {
return NumberUtils.evaluateExpression(this.getString(path), player, this, additionalPlayers);
}
/**
* Get a decimal from config.
*

View File

@@ -21,15 +21,6 @@ public interface KeyRegistry {
*/
void registerKey(@NotNull PersistentDataKey<?> key);
/**
* Get a key's category.
*
* @param key The key.
* @return The category.
*/
@Nullable
KeyCategory getCategory(@NotNull PersistentDataKey<?> key);
/**
* Get all registered keys.
*
@@ -37,15 +28,6 @@ public interface KeyRegistry {
*/
Set<PersistentDataKey<?>> getRegisteredKeys();
/**
* Mark key as category.
*
* @param key The key.
* @param category The category.
*/
void markKeyAs(@NotNull PersistentDataKey<?> key,
@NotNull KeyRegistry.KeyCategory category);
/**
* Get persistent data key from namespaced key.
*
@@ -54,19 +36,4 @@ public interface KeyRegistry {
*/
@Nullable
PersistentDataKey<?> getKeyFrom(@NotNull NamespacedKey namespacedKey);
/**
* Locations for key categorization.
*/
enum KeyCategory {
/**
* Player keys.
*/
PLAYER,
/**
* Server keys.
*/
SERVER
}
}

View File

@@ -83,28 +83,40 @@ public final class PersistentDataKey<T> {
}
/**
* In older eco versions, keys would have to be categorized in order
* to register the columns in the MySQL database. This is no longer needed.
* <p>
* Old description is below:
* <p>
* Categorize key as a server key, will register new column to MySQL
* database immediately rather than waiting for auto-categorization.
* <p>
* This will improve performance.
*
* @return The key.
* @deprecated Not required since the new MySQL data handler was introduced.
*/
@Deprecated(since = "6.40.0", forRemoval = true)
public PersistentDataKey<T> server() {
Eco.getHandler().getKeyRegistry().markKeyAs(this, KeyRegistry.KeyCategory.SERVER);
return this;
}
/**
* In older eco versions, keys would have to be categorized in order
* to register the columns in the MySQL database. This is no longer needed.
* <p>
* Old description is below:
* <p>
* Categorize key as a player key, will register new column to MySQL
* database immediately rather than waiting for auto-categorization.
* <p>
* This will improve performance.
*
* @return The key.
* @deprecated Not required since the new MySQL data handler was introduced.
*/
@Deprecated(since = "6.40.0", forRemoval = true)
public PersistentDataKey<T> player() {
Eco.getHandler().getKeyRegistry().markKeyAs(this, KeyRegistry.KeyCategory.PLAYER);
return this;
}

View File

@@ -64,6 +64,21 @@ public abstract class DisplayModule extends PluginDependent<EcoPlugin> {
// Technically optional.
}
/**
* Display an item.
*
* @param itemStack The item.
* @param player The player.
* @param properties The properties.
* @param args Optional args for display.
*/
public void display(@NotNull final ItemStack itemStack,
@Nullable final Player player,
@NotNull final DisplayProperties properties,
@NotNull final Object... args) {
// Technically optional.
}
/**
* Revert an item.
*

View File

@@ -0,0 +1,18 @@
package com.willfp.eco.core.display;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
/**
* Extra properties passed into {@link DisplayModule}.
*
* @param inInventory If the item was in an inventory.
* @param inGui If the item is assumed to be in a gui. (Not perfectly accurate).
* @param originalItem The original item, not to be modified.
*/
public record DisplayProperties(
boolean inInventory,
boolean inGui,
@NotNull ItemStack originalItem
) {
}

View File

@@ -68,6 +68,14 @@ public interface MenuBuilder {
*/
MenuBuilder onClose(@NotNull CloseHandler action);
/**
* Set the menu open handler.
*
* @param action The handler.
* @return The builder.
*/
MenuBuilder onOpen(@NotNull OpenHandler action);
/**
* Set the action to run on render.
*

View File

@@ -0,0 +1,19 @@
package com.willfp.eco.core.gui.menu;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
/**
* Interface to run on menu open.
*/
@FunctionalInterface
public interface OpenHandler {
/**
* Performs this operation on the given arguments.
*
* @param player The player.
* @param menu The menu.
*/
void handle(@NotNull Player player,
@NotNull Menu menu);
}

View File

@@ -4,6 +4,8 @@ import com.willfp.eco.core.integrations.Integration;
import org.bukkit.OfflinePlayer;
import org.jetbrains.annotations.NotNull;
import java.math.BigDecimal;
/**
* Wrapper class for economy integrations.
*/
@@ -45,4 +47,14 @@ public interface EconomyIntegration extends Integration {
* @return The balance.
*/
double getBalance(@NotNull OfflinePlayer player);
/**
* Get the balance of a player.
*
* @param player The player.
* @return The balance.
*/
default BigDecimal getExactBalance(@NotNull OfflinePlayer player) {
return BigDecimal.valueOf(getBalance(player));
}
}

View File

@@ -3,6 +3,7 @@ package com.willfp.eco.core.integrations.economy;
import org.bukkit.OfflinePlayer;
import org.jetbrains.annotations.NotNull;
import java.math.BigDecimal;
import java.util.HashSet;
import java.util.Set;
@@ -96,6 +97,20 @@ public final class EconomyManager {
return 0;
}
/**
* Get the balance of a player.
*
* @param player The player.
* @return The balance.
*/
public static BigDecimal getExactBalance(@NotNull final OfflinePlayer player) {
for (EconomyIntegration integration : REGISTERED) {
return integration.getExactBalance(player);
}
return BigDecimal.ZERO;
}
private EconomyManager() {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
}

View File

@@ -4,6 +4,7 @@ import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.willfp.eco.core.Eco;
import com.willfp.eco.core.EcoPlugin;
import com.willfp.eco.core.placeholder.AdditionalPlayer;
import com.willfp.eco.core.placeholder.InjectablePlaceholder;
import com.willfp.eco.core.placeholder.Placeholder;
import com.willfp.eco.core.placeholder.PlaceholderInjectable;
@@ -11,11 +12,13 @@ import com.willfp.eco.core.placeholder.PlayerPlaceholder;
import com.willfp.eco.core.placeholder.PlayerStaticPlaceholder;
import com.willfp.eco.core.placeholder.PlayerlessPlaceholder;
import com.willfp.eco.core.placeholder.StaticPlaceholder;
import com.willfp.eco.util.StringUtils;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -23,6 +26,8 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Class to handle placeholder integrations.
@@ -56,11 +61,17 @@ public final class PlaceholderManager {
}
@Override
public @NotNull List<InjectablePlaceholder> getPlaceholderInjections() {
public @NotNull
List<InjectablePlaceholder> getPlaceholderInjections() {
return Collections.emptyList();
}
};
/**
* The default PlaceholderAPI pattern; brought in for compatibility.
*/
private static final Pattern PATTERN = Pattern.compile("[%]([^%]+)[%]");
/**
* Register a new placeholder integration.
*
@@ -192,7 +203,45 @@ public final class PlaceholderManager {
public static String translatePlaceholders(@NotNull final String text,
@Nullable final Player player,
@NotNull final PlaceholderInjectable context) {
return translatePlaceholders(text, player, context, new ArrayList<>());
}
/**
* Translate all placeholders with respect to a player.
*
* @param text The text that may contain placeholders to translate.
* @param player The player to translate the placeholders with respect to.
* @param context The injectable context.
* @param additionalPlayers Additional players to translate placeholders for.
* @return The text, translated.
*/
public static String translatePlaceholders(@NotNull final String text,
@Nullable final Player player,
@NotNull final PlaceholderInjectable context,
@NotNull final Collection<AdditionalPlayer> additionalPlayers) {
String processed = text;
// Prevent running 2 scans if there are no additional players.
if (!additionalPlayers.isEmpty()) {
List<String> found = findPlaceholdersIn(text);
for (AdditionalPlayer additionalPlayer : additionalPlayers) {
for (String placeholder : found) {
String prefix = "%" + additionalPlayer.getIdentifier() + "_";
if (placeholder.startsWith(prefix)) {
processed = processed.replace(
placeholder,
translatePlaceholders(
"%" + StringUtils.removePrefix(prefix, placeholder),
additionalPlayer.getPlayer()
)
);
}
}
}
}
for (PlaceholderIntegration integration : REGISTERED_INTEGRATIONS) {
processed = integration.translate(processed, player);
}
@@ -214,12 +263,21 @@ public final class PlaceholderManager {
* @return The placeholders.
*/
public static List<String> findPlaceholdersIn(@NotNull final String text) {
List<String> found = new ArrayList<>();
Set<String> found = new HashSet<>();
// Mock PAPI for those without it installed
if (REGISTERED_INTEGRATIONS.isEmpty()) {
Matcher matcher = PATTERN.matcher(text);
while (matcher.find()) {
found.add(matcher.group());
}
}
for (PlaceholderIntegration integration : REGISTERED_INTEGRATIONS) {
found.addAll(integration.findPlaceholdersIn(text));
}
return found;
return new ArrayList<>(found);
}
private record EntryWithPlayer(@NotNull PlayerPlaceholder entry,

View File

@@ -1,7 +1,10 @@
package com.willfp.eco.core.integrations.shop;
import com.willfp.eco.core.integrations.Integration;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
@@ -25,4 +28,27 @@ public interface ShopIntegration extends Integration {
// Do nothing unless overridden.
return null;
}
/**
* Get the price of an item.
*
* @param itemStack The item.
* @return The price.
*/
default double getPrice(@NotNull final ItemStack itemStack) {
// Do nothing unless overridden.
return 0.0;
}
/**
* Get the price of an item.
*
* @param itemStack The item.
* @param player The player.
* @return The price.
*/
default double getPrice(@NotNull final ItemStack itemStack,
@NotNull final Player player) {
return getPrice(itemStack);
}
}

View File

@@ -1,9 +1,12 @@
package com.willfp.eco.core.integrations.shop;
import com.willfp.eco.core.EcoPlugin;
import org.bukkit.entity.Player;
import org.bukkit.event.Listener;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashSet;
import java.util.Set;
@@ -52,6 +55,40 @@ public final class ShopManager {
}
}
/**
* Get the price of an item.
*
* @param itemStack The item.
* @return The price.
*/
public static double getItemPrice(@Nullable final ItemStack itemStack) {
return getItemPrice(itemStack, null);
}
/**
* Get the price of an item.
*
* @param itemStack The item.
* @param player The player.
* @return The price.
*/
public static double getItemPrice(@Nullable final ItemStack itemStack,
@Nullable final Player player) {
if (itemStack == null) {
return 0.0;
}
for (ShopIntegration shopIntegration : REGISTERED) {
if (player == null) {
return shopIntegration.getPrice(itemStack);
} else {
return shopIntegration.getPrice(itemStack, player);
}
}
return 0.0;
}
private ShopManager() {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
}

View File

@@ -0,0 +1,49 @@
package com.willfp.eco.core.placeholder;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
/**
* An additional player with an identifier to parse placeholders for the player.
*/
public class AdditionalPlayer {
/**
* The player.
*/
private final Player player;
/**
* The identifier.
*/
private final String identifier;
/**
* Create a new additional player.
*
* @param player The player.
* @param identifier The identifier.
*/
public AdditionalPlayer(@NotNull final Player player,
@NotNull final String identifier) {
this.player = player;
this.identifier = identifier;
}
/**
* Get the player.
*
* @return The player.
*/
public Player getPlayer() {
return player;
}
/**
* Get the identifier.
*
* @return The identifier.
*/
public String getIdentifier() {
return identifier;
}
}

View File

@@ -25,13 +25,30 @@ public class Paste {
*/
private final String contents;
/**
* The host.
*/
private final String host;
/**
* Create a new paste.
*
* @param contents The contents.
*/
public Paste(@NotNull final String contents) {
this(contents, "https://paste.willfp.com");
}
/**
* Create a new paste.
*
* @param contents The contents.
* @param host The host.
*/
public Paste(@NotNull final String contents,
@NotNull final String host) {
this.contents = contents;
this.host = host;
}
/**
@@ -47,7 +64,7 @@ public class Paste {
byte[] postData = URLEncoder.encode(contents, StandardCharsets.UTF_8).getBytes(StandardCharsets.UTF_8);
int postDataLength = postData.length;
String requestURL = "https://paste.willfp.com/documents";
String requestURL = this.host + "/documents";
URL url = new URL(requestURL);
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setDoOutput(true);
@@ -85,14 +102,26 @@ public class Paste {
* @return The paste.
*/
public static Paste getFromHastebin(@NotNull final String token) {
return getFromHastebin(token, "https://paste.willfp.com");
}
/**
* Get paste from hastebin.
*
* @param token The token.
* @param host The host.
* @return The paste.
*/
public static Paste getFromHastebin(@NotNull final String token,
@NotNull final String host) {
try {
StringBuilder result = new StringBuilder();
URL url = new URL("https://paste.willfp.com/raw/" + token);
URL url = new URL(host + "/raw/" + token);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
try (var reader = new BufferedReader(
new InputStreamReader(conn.getInputStream()))) {
for (String line; (line = reader.readLine()) != null;) {
for (String line; (line = reader.readLine()) != null; ) {
result.append(line);
}
}

View File

@@ -1,12 +1,24 @@
package com.willfp.eco.util;
import com.willfp.eco.core.gui.menu.Menu;
import com.willfp.eco.core.tuples.Pair;
import org.apache.commons.lang.Validate;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.function.Function;
/**
* Utilities / API methods for menus.
*/
public final class MenuUtils {
/**
* The menu supplier.
*/
private static Function<Player, Menu> menuGetter = null;
/**
* Convert 0-53 slot to row and column pair.
*
@@ -31,6 +43,29 @@ public final class MenuUtils {
return (column - 1) + ((row - 1) * 9);
}
/**
* Get a player's open menu.
*
* @param player The player.
* @return The menu, or null if none open.
*/
@Nullable
public static Menu getOpenMenu(@NotNull final Player player) {
return menuGetter.apply(player);
}
/**
* Initialize the tps supplier function.
*
* @param function The function.
*/
@ApiStatus.Internal
public static void initialize(@NotNull final Function<Player, Menu> function) {
Validate.isTrue(menuGetter == null, "Already initialized!");
menuGetter = function;
}
private MenuUtils() {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
}

View File

@@ -1,5 +1,6 @@
package com.willfp.eco.util;
import com.willfp.eco.core.placeholder.AdditionalPlayer;
import com.willfp.eco.core.placeholder.InjectablePlaceholder;
import com.willfp.eco.core.placeholder.PlaceholderInjectable;
import com.willfp.eco.core.placeholder.StaticPlaceholder;
@@ -11,6 +12,7 @@ import org.jetbrains.annotations.Nullable;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@@ -263,7 +265,8 @@ public final class NumberUtils {
}
@Override
public @NotNull List<InjectablePlaceholder> getPlaceholderInjections() {
public @NotNull
List<InjectablePlaceholder> getPlaceholderInjections() {
return Collections.emptyList();
}
});
@@ -282,7 +285,7 @@ public final class NumberUtils {
public static double evaluateExpression(@NotNull final String expression,
@Nullable final Player player,
@NotNull final Iterable<StaticPlaceholder> statics) {
return crunch.evaluate(expression, player, new PlaceholderInjectable() {
return evaluateExpression(expression, player, new PlaceholderInjectable() {
@Override
public void clearInjectedPlaceholders() {
// Do nothing.
@@ -304,13 +307,29 @@ public final class NumberUtils {
*
* @param expression The expression.
* @param player The player.
* @param context The injectable placeholders.
* @param context The injectable placeholders.
* @return The value of the expression, or zero if invalid.
*/
public static double evaluateExpression(@NotNull final String expression,
@Nullable final Player player,
@NotNull final PlaceholderInjectable context) {
return crunch.evaluate(expression, player, context);
return evaluateExpression(expression, player, context, new ArrayList<>());
}
/**
* Evaluate an expression with respect to a player (for placeholders).
*
* @param expression The expression.
* @param player The player.
* @param context The injectable placeholders.
* @param additionalPlayers Additional players to parse placeholders for.
* @return The value of the expression, or zero if invalid.
*/
public static double evaluateExpression(@NotNull final String expression,
@Nullable final Player player,
@NotNull final PlaceholderInjectable context,
@NotNull final Collection<AdditionalPlayer> additionalPlayers) {
return crunch.evaluate(expression, player, context, additionalPlayers);
}
/**
@@ -332,14 +351,16 @@ public final class NumberUtils {
/**
* Evaluate an expression.
*
* @param expression The expression.
* @param player The player.
* @param injectable The injectable placeholders.
* @param expression The expression.
* @param player The player.
* @param injectable The injectable placeholders.
* @param additionalPlayers The additional players.
* @return The value of the expression, or zero if invalid.
*/
double evaluate(@NotNull String expression,
@Nullable Player player,
@NotNull PlaceholderInjectable injectable);
@NotNull PlaceholderInjectable injectable,
@NotNull Collection<AdditionalPlayer> additionalPlayers);
}
private NumberUtils() {

View File

@@ -152,6 +152,20 @@ public final class VectorUtils {
return vectors.toArray(new Vector[0]);
}
/**
* Get if a vector is a safe velocity.
*
* @param vec The vector to check.
* @return If safe.
*/
public static boolean isSafeVelocity(@NotNull final Vector vec) {
double x = Math.abs(vec.getX());
double y = Math.abs(vec.getY());
double z = Math.abs(vec.getZ());
return x < 4 && y < 4 && z < 4;
}
private VectorUtils() {
throw new UnsupportedOperationException("This is a utility class and cannot be instantiated");
}

View File

@@ -109,6 +109,10 @@ fun slot(
fun MenuBuilder.onClose(action: (InventoryCloseEvent, Menu) -> Unit): MenuBuilder =
this.onClose { a, b -> action(a, b) }
/** @see MenuBuilder.onOpen */
fun MenuBuilder.onOpen(action: (Player, Menu) -> Unit): MenuBuilder =
this.onOpen { a, b -> action(a, b) }
/** @see MenuBuilder.modify */
fun MenuBuilder.modify(modifier: (MenuBuilder) -> Unit): MenuBuilder =
this.modfiy { modifier(it) }

View File

@@ -3,6 +3,7 @@
package com.willfp.eco.core.integrations.economy
import org.bukkit.OfflinePlayer
import java.math.BigDecimal
/** @see EconomyManager */
var OfflinePlayer.balance: Double
@@ -21,3 +22,21 @@ var OfflinePlayer.balance: Double
EconomyManager.giveMoney(this, -diff)
}
}
/** @see EconomyManager */
var OfflinePlayer.exactBalance: BigDecimal
get() = EconomyManager.getBalance(this).toBigDecimal()
set(value) {
if (value <= BigDecimal.ZERO) {
EconomyManager.removeMoney(this, this.balance)
return
}
val diff = this.exactBalance - value
if (diff > BigDecimal.ZERO) {
EconomyManager.removeMoney(this, diff.toDouble())
} else if (diff < BigDecimal.ZERO) {
EconomyManager.giveMoney(this, -diff.toDouble())
}
}

View File

@@ -0,0 +1,14 @@
@file:JvmName("ShopExtensions")
package com.willfp.eco.core.integrations.shop
import org.bukkit.entity.Player
import org.bukkit.inventory.ItemStack
/** @see ShopManager.getItemPrice **/
val ItemStack.price: Double
get() = ShopManager.getItemPrice(this)
/** @see ShopManager.getItemPrice **/
fun ItemStack.getPrice(player: Player): Double =
ShopManager.getItemPrice(this, player)

View File

@@ -0,0 +1,10 @@
@file:JvmName("MenuUtilsExtensions")
package com.willfp.eco.util
import com.willfp.eco.core.gui.menu.Menu
import org.bukkit.entity.Player
/** @see MenuUtils.getOpenMenu */
val Player.openMenu: Menu?
get() = MenuUtils.getOpenMenu(this)

View File

@@ -11,3 +11,7 @@ val Vector.isFinite: Boolean
/** @see VectorUtils.simplifyVector */
fun Vector.simplify(): Vector =
VectorUtils.simplifyVector(this)
/** @see VectorUtils.isSafeVelocity */
val Vector.isSafeVelocity: Boolean
get() = VectorUtils.isSafeVelocity(this)

View File

@@ -4,6 +4,7 @@ import com.willfp.eco.core.EcoPlugin
import com.willfp.eco.core.display.Display
import com.willfp.eco.core.display.DisplayHandler
import com.willfp.eco.core.display.DisplayModule
import com.willfp.eco.core.display.DisplayProperties
import com.willfp.eco.core.fast.fast
import org.bukkit.NamespacedKey
import org.bukkit.entity.Player
@@ -32,13 +33,21 @@ class EcoDisplayHandler(plugin: EcoPlugin) : DisplayHandler {
}
}
Display.revert(itemStack)
if (!itemStack.hasItemMeta()) {
return itemStack
}
val original = itemStack.clone()
val inventory = player?.openInventory?.topInventory
val inInventory = inventory?.contains(original) ?: false
val props = DisplayProperties(
inInventory,
inInventory && inventory?.holder == null,
original
)
for ((_, modules) in registeredModules) {
for (module in modules) {
@@ -48,6 +57,7 @@ class EcoDisplayHandler(plugin: EcoPlugin) : DisplayHandler {
if (player != null) {
module.display(itemStack, player as Player?, *varargs)
module.display(itemStack, player as Player?, props, *varargs)
}
}
}

View File

@@ -2,6 +2,7 @@ package com.willfp.eco.internal.gui.menu
import com.willfp.eco.core.gui.menu.CloseHandler
import com.willfp.eco.core.gui.menu.Menu
import com.willfp.eco.core.gui.menu.OpenHandler
import com.willfp.eco.core.gui.slot.Slot
import com.willfp.eco.internal.gui.slot.EcoSlot
import com.willfp.eco.util.NamespacedKeyUtils
@@ -19,7 +20,8 @@ class EcoMenu(
val slots: List<MutableList<EcoSlot>>,
private val title: String,
private val onClose: CloseHandler,
private val onRender: (Player, Menu) -> Unit
private val onRender: (Player, Menu) -> Unit,
private val onOpen: OpenHandler
) : Menu {
override fun getSlot(row: Int, column: Int): Slot {
if (row < 1 || row > this.rows) {
@@ -49,6 +51,7 @@ class EcoMenu(
player.openInventory(inventory)
MenuHandler.registerInventory(inventory, this, player)
onOpen.handle(player, this)
inventory.asRenderedInventory()?.generateCaptive()
return inventory
}

View File

@@ -3,6 +3,7 @@ package com.willfp.eco.internal.gui.menu
import com.willfp.eco.core.gui.menu.CloseHandler
import com.willfp.eco.core.gui.menu.Menu
import com.willfp.eco.core.gui.menu.MenuBuilder
import com.willfp.eco.core.gui.menu.OpenHandler
import com.willfp.eco.core.gui.slot.FillerMask
import com.willfp.eco.core.gui.slot.FillerSlot
import com.willfp.eco.core.gui.slot.Slot
@@ -21,6 +22,7 @@ class EcoMenuBuilder(private val rows: Int ) : MenuBuilder {
private var maskSlots: List<MutableList<Slot?>>
private val slots: List<MutableList<Slot?>> = ListUtils.create2DList(rows, 9)
private var onClose = CloseHandler { _, _ -> }
private var onOpen = OpenHandler { _, _ -> }
private var onRender: (Player, Menu) -> Unit = { _, _ -> }
override fun setTitle(title: String): MenuBuilder {
@@ -54,6 +56,11 @@ class EcoMenuBuilder(private val rows: Int ) : MenuBuilder {
return this
}
override fun onOpen(action: OpenHandler): MenuBuilder {
onOpen = action
return this
}
override fun onRender(action: BiConsumer<Player, Menu>): MenuBuilder {
onRender = { a, b -> action.accept(a, b) }
return this
@@ -83,7 +90,7 @@ class EcoMenuBuilder(private val rows: Int ) : MenuBuilder {
finalSlots.add(tempRow)
}
return EcoMenu(rows, finalSlots, title, onClose, onRender)
return EcoMenu(rows, finalSlots, title, onClose, onRender, onOpen)
}
init {

View File

@@ -38,7 +38,7 @@ open class EcoSlot(
}
override fun getItemStack(player: Player): ItemStack {
val menu = player.openInventory.topInventory.getMenu()!!
val menu = player.openInventory.topInventory.getMenu() ?: return ItemStack(Material.AIR)
val prev = provider.provide(player, menu)
return updater.update(player, menu, prev) ?: ItemStack(Material.AIR)
}

View File

@@ -30,10 +30,11 @@ class SNBTConverter : SNBTConverterProxy {
nbt.remove("Count")
return SNBTTestableItem(nbt)
return SNBTTestableItem(CraftItemStack.asBukkitCopy(nms), nbt)
}
class SNBTTestableItem(
private val item: ItemStack,
private val tag: CompoundTag
) : TestableItem {
override fun matches(itemStack: ItemStack?): Boolean {
@@ -47,8 +48,6 @@ class SNBTConverter : SNBTConverterProxy {
return tag.copy().merge(nmsTag) == nmsTag
}
override fun getItem(): ItemStack {
return CraftItemStack.asBukkitCopy(net.minecraft.world.item.ItemStack.of(tag))
}
override fun getItem(): ItemStack = item
}
}

View File

@@ -30,10 +30,11 @@ class SNBTConverter : SNBTConverterProxy {
nbt.remove("Count")
return SNBTTestableItem(nbt)
return SNBTTestableItem(CraftItemStack.asBukkitCopy(nms), nbt)
}
class SNBTTestableItem(
private val item: ItemStack,
private val tag: CompoundTag
) : TestableItem {
override fun matches(itemStack: ItemStack?): Boolean {
@@ -47,8 +48,6 @@ class SNBTConverter : SNBTConverterProxy {
return tag.copy().merge(nmsTag) == nmsTag
}
override fun getItem(): ItemStack {
return CraftItemStack.asBukkitCopy(net.minecraft.world.item.ItemStack.of(tag))
}
override fun getItem(): ItemStack = item
}
}

View File

@@ -30,10 +30,11 @@ class SNBTConverter : SNBTConverterProxy {
nbt.remove("Count")
return SNBTTestableItem(nbt)
return SNBTTestableItem(CraftItemStack.asBukkitCopy(nms), nbt)
}
class SNBTTestableItem(
private val item: ItemStack,
private val tag: CompoundTag
) : TestableItem {
override fun matches(itemStack: ItemStack?): Boolean {
@@ -47,8 +48,6 @@ class SNBTConverter : SNBTConverterProxy {
return tag.copy().merge(nmsTag) == nmsTag
}
override fun getItem(): ItemStack {
return CraftItemStack.asBukkitCopy(net.minecraft.world.item.ItemStack.of(tag))
}
override fun getItem(): ItemStack = item
}
}

View File

@@ -29,11 +29,11 @@ class SNBTConverter : SNBTConverterProxy {
}
nbt.remove("Count")
return SNBTTestableItem(nbt)
return SNBTTestableItem(CraftItemStack.asBukkitCopy(nms), nbt)
}
class SNBTTestableItem(
private val item: ItemStack,
private val tag: CompoundTag
) : TestableItem {
override fun matches(itemStack: ItemStack?): Boolean {
@@ -47,8 +47,6 @@ class SNBTConverter : SNBTConverterProxy {
return tag.copy().merge(nmsTag) == nmsTag
}
override fun getItem(): ItemStack {
return CraftItemStack.asBukkitCopy(net.minecraft.world.item.ItemStack.of(tag))
}
override fun getItem(): ItemStack = item
}
}

View File

@@ -6,7 +6,7 @@ dependencies {
compileOnly project(":eco-core:core-backend")
// Libraries
implementation 'com.github.Redempt:Crunch:1.1.2'
implementation 'com.github.Redempt:Crunch:master-SNAPSHOT'
implementation 'mysql:mysql-connector-java:8.0.25'
implementation 'org.jetbrains.exposed:exposed-core:0.37.3'
implementation 'org.jetbrains.exposed:exposed-dao:0.37.3'
@@ -22,10 +22,9 @@ dependencies {
compileOnly 'io.papermc.paper:paper-api:1.17.1-R0.1-SNAPSHOT'
// Plugin dependencies
compileOnly 'com.comphenix.protocol:ProtocolLib:4.6.1-SNAPSHOT'
compileOnly 'com.comphenix.protocol:ProtocolLib:5.0.0-SNAPSHOT'
compileOnly 'com.sk89q.worldguard:worldguard-bukkit:7.0.7-SNAPSHOT'
compileOnly 'com.github.TechFortress:GriefPrevention:16.17.1'
compileOnly 'com.massivecraft:Factions:1.6.9.5-U0.5.10'
compileOnly 'com.github.cryptomorin:kingdoms:1.12.3'
compileOnly('com.github.TownyAdvanced:Towny:0.97.2.6') {
exclude group: 'com.zaxxer', module: 'HikariCP'
@@ -36,17 +35,19 @@ dependencies {
compileOnly 'com.gmail.nossr50.mcMMO:mcMMO:2.1.202'
compileOnly 'me.clip:placeholderapi:2.10.10'
compileOnly 'com.github.oraxen:oraxen:bd81ace154'
compileOnly 'com.github.brcdev-minecraft:shopgui-api:2.2.0'
compileOnly 'com.github.brcdev-minecraft:shopgui-api:3.0.0'
compileOnly 'com.github.LoneDev6:API-ItemsAdder:2.4.7'
compileOnly 'com.arcaniax:HeadDatabase-API:1.3.0'
compileOnly 'com.gmail.filoghost.holographicdisplays:holographicdisplays-api:2.4.0'
compileOnly 'com.github.EssentialsX:Essentials:2.18.2'
compileOnly 'com.bgsoftware:SuperiorSkyblockAPI:1.8.3'
compileOnly 'com.songoda:skyblock:2.3.30'
compileOnly 'com.github.MilkBowl:VaultAPI:1.7'
compileOnly 'com.github.WhipDevelopment:CrashClaim:f9cd7d92eb'
compileOnly 'com.wolfyscript.wolfyutilities:wolfyutilities:3.16.0.0'
compileOnly 'com.github.decentsoftware-eu:decentholograms:2.1.2'
compileOnly 'com.github.Gypopo:EconomyShopGUI-API:1.1.0'
compileOnly 'com.github.N0RSKA:ScytherAPI:55a'
// MythicMobs
compileOnly 'io.lumine:Mythic:5.0.1'

View File

@@ -16,7 +16,11 @@ object ConflictFinder {
continue
}
val conflict = plugin.getConflict()
val conflict = try {
plugin.getConflict()
} catch (e: Exception) {
continue
} // Really can't be fucked to do this properly.
if (conflict != null) {
conflicts.add(conflict)

View File

@@ -59,17 +59,12 @@ class EcoHandler : EcoSpigotPlugin(), Handler {
private val cleaner = EcoCleaner()
private var adventure: BukkitAudiences? = null
private val keyRegistry = EcoKeyRegistry()
private val playerProfileHandler = EcoProfileHandler(
if (this.configYml.getBool("mysql.enabled")) {
this.configYml.set("mysql.enabled", false)
this.configYml.set("data-handler", "mysql")
HandlerType.MYSQL
} else {
HandlerType.valueOf(
this.configYml.getString("data-handler").uppercase()
)
}, this
HandlerType.valueOf(this.configYml.getString("data-handler").uppercase()),
this
)
private val snbtHandler = EcoSNBTHandler(this)

View File

@@ -38,6 +38,7 @@ import com.willfp.eco.internal.entities.EntityArgParserSilent
import com.willfp.eco.internal.entities.EntityArgParserSize
import com.willfp.eco.internal.entities.EntityArgParserSpawnReinforcements
import com.willfp.eco.internal.entities.EntityArgParserSpeed
import com.willfp.eco.internal.gui.menu.getMenu
import com.willfp.eco.internal.items.ArgParserColor
import com.willfp.eco.internal.items.ArgParserCustomModelData
import com.willfp.eco.internal.items.ArgParserEnchantment
@@ -83,6 +84,7 @@ import com.willfp.eco.internal.spigot.integrations.antigrief.AntigriefCombatLogX
import com.willfp.eco.internal.spigot.integrations.antigrief.AntigriefCombatLogXV11
import com.willfp.eco.internal.spigot.integrations.antigrief.AntigriefCrashClaim
import com.willfp.eco.internal.spigot.integrations.antigrief.AntigriefDeluxeCombat
import com.willfp.eco.internal.spigot.integrations.antigrief.AntigriefFabledSkyBlock
import com.willfp.eco.internal.spigot.integrations.antigrief.AntigriefFactionsUUID
import com.willfp.eco.internal.spigot.integrations.antigrief.AntigriefGriefPrevention
import com.willfp.eco.internal.spigot.integrations.antigrief.AntigriefIridiumSkyblock
@@ -99,6 +101,7 @@ import com.willfp.eco.internal.spigot.integrations.customitems.CustomItemsHeadDa
import com.willfp.eco.internal.spigot.integrations.customitems.CustomItemsItemsAdder
import com.willfp.eco.internal.spigot.integrations.customitems.CustomItemsMythicMobs
import com.willfp.eco.internal.spigot.integrations.customitems.CustomItemsOraxen
import com.willfp.eco.internal.spigot.integrations.customitems.CustomItemsScyther
import com.willfp.eco.internal.spigot.integrations.customrecipes.CustomRecipeCustomCrafting
import com.willfp.eco.internal.spigot.integrations.economy.EconomyVault
import com.willfp.eco.internal.spigot.integrations.hologram.HologramCMI
@@ -111,6 +114,7 @@ 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.math.evaluateExpression
import com.willfp.eco.internal.spigot.player.PlayerHealthFixer
import com.willfp.eco.internal.spigot.proxy.FastItemStackFactoryProxy
import com.willfp.eco.internal.spigot.proxy.SkullProxy
import com.willfp.eco.internal.spigot.proxy.TPSProxy
@@ -120,6 +124,7 @@ import com.willfp.eco.internal.spigot.recipes.listeners.ComplexInComplex
import com.willfp.eco.internal.spigot.recipes.listeners.ComplexInVanilla
import com.willfp.eco.internal.spigot.recipes.stackhandlers.ShapedCraftingRecipeStackHandler
import com.willfp.eco.internal.spigot.recipes.stackhandlers.ShapelessCraftingRecipeStackHandler
import com.willfp.eco.util.MenuUtils
import com.willfp.eco.util.NumberUtils
import com.willfp.eco.util.ServerUtils
import com.willfp.eco.util.SkullUtils
@@ -180,7 +185,9 @@ abstract class EcoSpigotPlugin : EcoPlugin() {
val tpsProxy = getProxy(TPSProxy::class.java)
ServerUtils.initialize { tpsProxy.getTPS() }
NumberUtils.initCrunch { expression, player, context -> evaluateExpression(expression, player, context) }
NumberUtils.initCrunch(::evaluateExpression)
MenuUtils.initialize { it.openInventory.topInventory.getMenu() }
CustomItemsManager.registerProviders()
@@ -239,6 +246,11 @@ abstract class EcoSpigotPlugin : EcoPlugin() {
override fun handleReload() {
CollatedRunnable(this)
DropManager.update(this)
this.scheduler.runLater(3) {
(Eco.getHandler().profileHandler as EcoProfileHandler).migrateIfNeeded()
}
ProfileSaver(this, Eco.getHandler().profileHandler)
this.scheduler.runTimer(
{ clearFrames() },
@@ -259,6 +271,7 @@ abstract class EcoSpigotPlugin : EcoPlugin() {
IntegrationLoader("IridiumSkyblock") { AntigriefManager.register(AntigriefIridiumSkyblock()) },
IntegrationLoader("DeluxeCombat") { AntigriefManager.register(AntigriefDeluxeCombat()) },
IntegrationLoader("SuperiorSkyblock2") { AntigriefManager.register(AntigriefSuperiorSkyblock2()) },
IntegrationLoader("FabledSkyBlock") { AntigriefManager.register(AntigriefFabledSkyBlock()) },
IntegrationLoader("BentoBox") { AntigriefManager.register(AntigriefBentoBox()) },
IntegrationLoader("WorldGuard") { AntigriefManager.register(AntigriefWorldGuard()) },
IntegrationLoader("GriefPrevention") { AntigriefManager.register(AntigriefGriefPrevention()) },
@@ -301,6 +314,7 @@ abstract class EcoSpigotPlugin : EcoPlugin() {
CraftingRecipeListener.registerValidator(CustomRecipeCustomCrafting())
},
IntegrationLoader("MythicMobs") { CustomItemsManager.register(CustomItemsMythicMobs(this)) },
IntegrationLoader("Scyther") { CustomItemsManager.register(CustomItemsScyther()) },
// Shop
IntegrationLoader("ShopGUIPlus") { ShopManager.register(ShopShopGuiPlus()) },
@@ -358,7 +372,9 @@ abstract class EcoSpigotPlugin : EcoPlugin() {
ArrowDataListener(this),
ArmorChangeEventListeners(this),
DataListener(this),
PlayerBlockListener(this)
PlayerBlockListener(this),
PlayerHealthFixer(this),
ServerLocking
)
if (Prerequisite.HAS_PAPER.isMet) {

View File

@@ -0,0 +1,28 @@
package com.willfp.eco.internal.spigot
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.bukkit.event.player.PlayerLoginEvent
object ServerLocking : Listener {
private var lockReason: String? = null
@Suppress("DEPRECATION")
@EventHandler
fun handle(event: PlayerLoginEvent) {
if (lockReason != null) {
event.disallow(
PlayerLoginEvent.Result.KICK_OTHER,
lockReason!!
)
}
}
fun lock(reason: String) {
lockReason = reason
}
fun unlock() {
lockReason = null
}
}

View File

@@ -1,6 +1,5 @@
package com.willfp.eco.internal.spigot.data
import com.willfp.eco.core.Eco
import com.willfp.eco.core.data.keys.KeyRegistry
import com.willfp.eco.core.data.keys.PersistentDataKey
import com.willfp.eco.core.data.keys.PersistentDataKeyType
@@ -8,7 +7,6 @@ import org.bukkit.NamespacedKey
class EcoKeyRegistry : KeyRegistry {
private val registry = mutableMapOf<NamespacedKey, PersistentDataKey<*>>()
private val categories = mutableMapOf<NamespacedKey, KeyRegistry.KeyCategory>()
override fun registerKey(key: PersistentDataKey<*>) {
if (this.registry.containsKey(key.key)) {
@@ -24,10 +22,6 @@ class EcoKeyRegistry : KeyRegistry {
return registry.values.toMutableSet()
}
override fun getCategory(key: PersistentDataKey<*>): KeyRegistry.KeyCategory? {
return categories[key.key]
}
private fun <T> validateKey(key: PersistentDataKey<T>) {
val default = key.defaultValue
@@ -52,11 +46,6 @@ class EcoKeyRegistry : KeyRegistry {
}
}
override fun markKeyAs(key: PersistentDataKey<*>, category: KeyRegistry.KeyCategory) {
categories[key.key] = category
(Eco.getHandler().profileHandler as EcoProfileHandler).handler.categorize(key, category) // ew
}
override fun getKeyFrom(namespacedKey: NamespacedKey): PersistentDataKey<*>? {
return registry[namespacedKey]
}

View File

@@ -1,14 +1,16 @@
package com.willfp.eco.internal.spigot.data
import com.willfp.eco.core.Eco
import com.willfp.eco.core.data.PlayerProfile
import com.willfp.eco.core.data.Profile
import com.willfp.eco.core.data.ProfileHandler
import com.willfp.eco.core.data.ServerProfile
import com.willfp.eco.core.data.keys.PersistentDataKey
import com.willfp.eco.core.data.profile
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
@@ -27,6 +29,7 @@ class EcoProfileHandler(
HandlerType.YAML -> YamlDataHandler(plugin, this)
HandlerType.MYSQL -> MySQLDataHandler(plugin, this)
HandlerType.MONGO -> MongoDataHandler(plugin, this)
HandlerType.LEGACY_MYSQL -> LegacyMySQLDataHandler(plugin, this)
}
fun loadGenericProfile(uuid: UUID): Profile {
@@ -64,7 +67,7 @@ class EcoProfileHandler(
handler.save()
}
private fun migrateIfNeeded() {
fun migrateIfNeeded() {
if (!plugin.configYml.getBool("perform-data-migration")) {
return
}
@@ -74,7 +77,12 @@ class EcoProfileHandler(
plugin.dataYml.save()
}
val previousHandlerType = HandlerType.valueOf(plugin.dataYml.getString("previous-handler"))
var previousHandlerType = HandlerType.valueOf(plugin.dataYml.getString("previous-handler"))
if (previousHandlerType == HandlerType.MYSQL && !plugin.dataYml.has("new-mysql")) {
previousHandlerType = HandlerType.LEGACY_MYSQL
}
if (previousHandlerType == type) {
return
@@ -84,12 +92,18 @@ class EcoProfileHandler(
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.")
plugin.logger.info("eco has detected a change in data handler!")
plugin.logger.info("Migrating server data from ${previousHandlerType.name} to ${type.name}")
plugin.logger.info("This will take a while!")
plugin.logger.info("Initializing previous handler...")
previousHandler.initialize()
val players = Bukkit.getOfflinePlayers().map { it.uniqueId }
plugin.logger.info("Found data for ${players.size} players!")
@@ -98,12 +112,9 @@ class EcoProfileHandler(
Declared here as its own function to be able to use T.
*/
fun <T : Any> migrateKey(uuid: UUID, key: PersistentDataKey<T>, from: DataHandler, to: DataHandler) {
val category = Eco.getHandler().keyRegistry.getCategory(key)
if (category != null) {
from.categorize(key, category)
}
val previous: T? = from.read(uuid, key)
if (previous != null) {
Bukkit.getOfflinePlayer(uuid).profile.write(key, previous) // Nope, no idea.
to.write(uuid, key, previous)
}
}
@@ -111,29 +122,35 @@ class EcoProfileHandler(
var i = 1
for (uuid in players) {
plugin.logger.info("Migrating data for $uuid... ($i / ${players.size})")
for (key in PersistentDataKey.values()) {
migrateKey(uuid, key, previousHandler, handler)
// Why this? Because known points *really* likes to break things with the legacy MySQL handler.
if (key.key.key == "known_points") {
continue
}
try {
migrateKey(uuid, key, previousHandler, handler)
} catch (e: Exception) {
plugin.logger.info("Could not migrate ${key.key} for $uuid! This is probably because they do not have any data.")
}
}
i++
}
plugin.logger.info("Saving new data...")
handler.save()
plugin.logger.info("Updating previous handler...")
plugin.dataYml.set("previous-handler", type.name)
plugin.dataYml.save()
plugin.logger.info("Done!")
plugin.logger.info("The server will now automatically be restarted...")
ServerLocking.unlock()
Bukkit.getServer().shutdown()
}
fun initialize() {
plugin.dataYml.getStrings("categorized-keys.player")
.mapNotNull { KeyHelpers.deserializeFromString(it) }
plugin.dataYml.getStrings("categorized-keys.server")
.mapNotNull { KeyHelpers.deserializeFromString(it, server = true) }
handler.initialize()
migrateIfNeeded()
}
}

View File

@@ -1,56 +0,0 @@
package com.willfp.eco.internal.spigot.data
import com.willfp.eco.core.data.keys.PersistentDataKey
import com.willfp.eco.core.data.keys.PersistentDataKeyType
import com.willfp.eco.util.NamespacedKeyUtils
@Suppress("UNCHECKED_CAST")
object KeyHelpers {
fun deserializeFromString(serialized: String, server: Boolean = false): PersistentDataKey<*>? {
val split = serialized.split(";").toTypedArray()
if (split.size < 2) {
return null
}
val key = NamespacedKeyUtils.fromStringOrNull(split[0]) ?: return null
val type = PersistentDataKeyType.valueOf(split[1]) ?: return null
val persistentKey = when (type) {
PersistentDataKeyType.STRING -> PersistentDataKey(
key,
type as PersistentDataKeyType<String>,
if (split.size >= 3) split.toList().subList(2, split.size).joinToString("") else ""
)
PersistentDataKeyType.INT -> PersistentDataKey(
key,
type as PersistentDataKeyType<Int>,
split[2].toInt()
)
PersistentDataKeyType.DOUBLE -> PersistentDataKey(
key,
type as PersistentDataKeyType<Double>,
split[2].toDouble()
)
PersistentDataKeyType.BOOLEAN -> PersistentDataKey(
key,
type as PersistentDataKeyType<Boolean>,
java.lang.Boolean.parseBoolean(split[2])
)
else -> null
}
if (persistentKey != null) {
if (server) {
persistentKey.server()
} else {
persistentKey.player()
}
}
return persistentKey
}
fun serializeToString(key: PersistentDataKey<*>): String {
return "${key.key};${key.type.name()};${key.defaultValue}"
}
}

View File

@@ -1,6 +1,5 @@
package com.willfp.eco.internal.spigot.data.storage
import com.willfp.eco.core.data.keys.KeyRegistry
import com.willfp.eco.core.data.keys.PersistentDataKey
import java.util.UUID
@@ -28,10 +27,6 @@ abstract class DataHandler(
}
open fun categorize(key: PersistentDataKey<*>, category: KeyRegistry.KeyCategory) {
}
open fun initialize() {
}

View File

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

View File

@@ -0,0 +1,309 @@
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.EcoProfileHandler
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(
private val plugin: EcoSpigotPlugin,
handler: EcoProfileHandler
) : DataHandler(HandlerType.LEGACY_MYSQL) {
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.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 class ImplementedMySQLHandler(
private val handler: EcoProfileHandler,
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-mysql-thread-%d").build()
private val executor = Executors.newFixedThreadPool(plugin.configYml.getInt("mysql.threads"), threadFactory)
val registeredKeys = mutableSetOf<PersistentDataKey<*>>()
init {
transaction {
SchemaUtils.create(table)
}
}
fun initialize() {
transaction {
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 {
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 {
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 {
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.getHandler().ecoPlugin.configYml.getBool("mysql.async-reads")) {
executor.submit(doRead).get()
} else {
doRead.call()
}
}
private fun <T> registerColumn(key: PersistentDataKey<T>) {
try {
transaction {
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)
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 {
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 {
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

@@ -2,64 +2,54 @@ 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.KeyRegistry
import com.willfp.eco.core.config.ConfigType
import com.willfp.eco.core.config.TransientConfig
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 com.willfp.eco.internal.spigot.EcoSpigotPlugin
import com.willfp.eco.internal.spigot.data.EcoProfileHandler
import com.willfp.eco.internal.spigot.data.KeyHelpers
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.TextColumnType
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
/*
Better than old MySQL data handler, but that's only because it's literally just dumping all the
data into a single text column, containing the contents of the players profile as a Config.
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.
Whatever. At least it works.
*/
@Suppress("UNCHECKED_CAST")
class MySQLDataHandler(
private val plugin: EcoSpigotPlugin,
handler: EcoProfileHandler
private val handler: EcoProfileHandler
) : DataHandler(HandlerType.MYSQL) {
private val playerHandler: ImplementedMySQLHandler
private val serverHandler: ImplementedMySQLHandler
private val table = UUIDTable("eco_data")
private val rows = Caffeine.newBuilder()
.expireAfterWrite(3, TimeUnit.SECONDS)
.build<UUID, ResultRow>()
private val threadFactory = ThreadFactoryBuilder().setNameFormat("eco-mysql-thread-%d").build()
private val executor = Executors.newFixedThreadPool(plugin.configYml.getInt("mysql.threads"), threadFactory)
private val dataColumn: Column<String>
get() = table.columns.first { it.name == "json_data" } as Column<String>
init {
plugin.logger.warning("You're using the MySQL Data Handler")
plugin.logger.warning("It's recommended to switch to MongoDB (mongo)!")
val config = HikariConfig()
config.driverClassName = "com.mysql.cj.jdbc.Driver"
config.username = plugin.configYml.getString("mysql.user")
@@ -72,238 +62,92 @@ class MySQLDataHandler(
Database.connect(HikariDataSource(config))
playerHandler = ImplementedMySQLHandler(
handler,
UUIDTable("eco_players"),
plugin
)
transaction {
SchemaUtils.create(table)
serverHandler = ImplementedMySQLHandler(
handler,
UUIDTable("eco_server"),
plugin
)
table.apply {
registerColumn<String>("json_data", TextColumnType())
}
SchemaUtils.createMissingTablesAndColumns(table, withLogs = false)
}
}
override fun <T : Any> read(uuid: UUID, key: PersistentDataKey<T>): T? {
return applyFor(uuid) {
it.read(uuid, key)
val data = getData(uuid)
val value: Any? = when (key.type) {
PersistentDataKeyType.INT -> data.getIntOrNull(key.key.toString())
PersistentDataKeyType.DOUBLE -> data.getDoubleOrNull(key.key.toString())
PersistentDataKeyType.STRING -> data.getStringOrNull(key.key.toString())
PersistentDataKeyType.BOOLEAN -> data.getBoolOrNull(key.key.toString())
PersistentDataKeyType.STRING_LIST -> data.getStringsOrNull(key.key.toString())
else -> null
}
return value as? T?
}
override fun <T : Any> write(uuid: UUID, key: PersistentDataKey<T>, value: T) {
applyFor(uuid) {
it.write(uuid, key, value)
}
val data = getData(uuid)
data.set(key.key.toString(), value)
setData(uuid, data)
}
override fun saveKeysFor(uuid: UUID, keys: Set<PersistentDataKey<*>>) {
applyFor(uuid) {
it.saveKeysForRow(uuid, keys)
val profile = handler.loadGenericProfile(uuid)
executor.submit {
val data = getData(uuid)
for (key in keys) {
data.set(key.key.toString(), profile.read(key))
}
setData(uuid, data)
}
}
private inline fun <R> applyFor(uuid: UUID, function: (ImplementedMySQLHandler) -> R): R {
return if (uuid == serverProfileUUID) {
function(serverHandler)
} else {
function(playerHandler)
private fun getData(uuid: UUID): Config {
val plaintext = transaction {
val row = rows.get(uuid) {
val row = table.select { table.id eq uuid }.limit(1).singleOrNull()
if (row != null) {
row
} else {
transaction {
table.insert { it[id] = uuid }
}
table.select { table.id eq uuid }.limit(1).singleOrNull()
}
}
row.getOrNull(dataColumn) ?: "{}"
}
return TransientConfig(plaintext, ConfigType.JSON)
}
private fun setData(uuid: UUID, config: Config) {
executor.submit {
transaction {
table.update({ table.id eq uuid }) {
it[dataColumn] = config.toPlaintext()
}
}
}
}
override fun categorize(key: PersistentDataKey<*>, category: KeyRegistry.KeyCategory) {
if (category == KeyRegistry.KeyCategory.SERVER) {
serverHandler.ensureKeyRegistration(key)
} else {
playerHandler.ensureKeyRegistration(key)
override fun initialize() {
transaction {
SchemaUtils.createMissingTablesAndColumns(table, withLogs = false)
}
}
override fun save() {
plugin.dataYml.set(
"categorized-keys.player",
playerHandler.registeredKeys
.map { KeyHelpers.serializeToString(it) }
)
plugin.dataYml.set(
"categorized-keys.server",
serverHandler.registeredKeys
.map { KeyHelpers.serializeToString(it) }
)
plugin.dataYml.set("new-mysql", true)
plugin.dataYml.save()
}
override fun initialize() {
playerHandler.initialize()
serverHandler.initialize()
}
}
@Suppress("UNCHECKED_CAST")
private class ImplementedMySQLHandler(
private val handler: EcoProfileHandler,
private val table: UUIDTable,
plugin: EcoPlugin
) {
private val rows = Caffeine.newBuilder()
.expireAfterWrite(3, TimeUnit.SECONDS)
.build<UUID, ResultRow>()
private val threadFactory = ThreadFactoryBuilder().setNameFormat("eco-mysql-thread-%d").build()
private val executor = Executors.newFixedThreadPool(plugin.configYml.getInt("mysql.threads"), threadFactory)
val registeredKeys = mutableSetOf<PersistentDataKey<*>>()
init {
transaction {
SchemaUtils.create(table)
}
}
fun initialize() {
transaction {
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 {
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 {
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 {
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.
return if (Eco.getHandler().ecoPlugin.configYml.getBool("mysql.async-reads")) {
executor.submit(doRead).get()
} else {
doRead.call()
}
}
private fun <T> registerColumn(key: PersistentDataKey<T>) {
transaction {
table.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)
else -> throw NullPointerException("Null value found!")
}
}
SchemaUtils.createMissingTablesAndColumns(table, withLogs = false)
}
}
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 {
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 {
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

@@ -17,11 +17,13 @@ class PacketSetSlot(plugin: EcoPlugin) : AbstractPacketAdapter(plugin, PacketTyp
player: Player,
event: PacketEvent
) {
packet.itemModifier.modify(0) { item: ItemStack? ->
Display.display(
item!!, player
)
}
packet.itemModifier.modify(0, object : VersionCompatiblePLibFunction<ItemStack> {
override fun apply(item: ItemStack) =
Display.display(
item,
player
)
})
player.lastDisplayFrame = DisplayFrame.EMPTY
}

View File

@@ -0,0 +1,8 @@
package com.willfp.eco.internal.spigot.display
import com.google.common.base.Function
import java.util.function.UnaryOperator
interface VersionCompatiblePLibFunction<T> : Function<T, T>, UnaryOperator<T> {
}

View File

@@ -13,6 +13,7 @@ import org.bukkit.event.EventPriority
import org.bukkit.event.Listener
import org.bukkit.event.inventory.InventoryClickEvent
import org.bukkit.event.inventory.InventoryCloseEvent
import org.bukkit.event.player.PlayerItemHeldEvent
class GUIListener(private val plugin: EcoPlugin) : Listener {
@EventHandler(priority = EventPriority.HIGH)
@@ -45,7 +46,6 @@ class GUIListener(private val plugin: EcoPlugin) : Listener {
}
val menu = inv.getMenu() ?: return
val rendered = inv.asRenderedInventory() ?: return
val (row, column) = MenuUtils.convertSlotToRowColumn(inv.firstEmpty())
@@ -54,8 +54,6 @@ class GUIListener(private val plugin: EcoPlugin) : Listener {
if (!slot.isCaptive) {
event.isCancelled = true
}
plugin.scheduler.run { rendered.render() }
}
@EventHandler(priority = EventPriority.HIGH)
@@ -66,4 +64,25 @@ 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: PlayerItemHeldEvent) {
val player = event.player
player.renderActiveMenu()
}
private fun Player.renderActiveMenu() {
val inv = this.openInventory.topInventory
val rendered = inv.asRenderedInventory() ?: return
rendered.render()
plugin.scheduler.run { rendered.render() }
}
}

View File

@@ -27,7 +27,9 @@ class AntigriefDeluxeCombat: AntigriefIntegration {
override fun canInjure(player: Player, victim: LivingEntity): Boolean {
val api = DeluxeCombatAPI()
return when(victim) {
is Player -> ((!api.hasProtection(victim) && api.hasPvPEnabled(victim)) || api.isInCombat(victim))
is Player -> {
if (api.hasProtection(player) || !api.hasPvPEnabled(player)) false
else ((!api.hasProtection(victim) && api.hasPvPEnabled(victim)) || api.isInCombat(victim))}
else -> true
}
}

View File

@@ -0,0 +1,94 @@
package com.willfp.eco.internal.spigot.integrations.antigrief
import com.songoda.skyblock.SkyBlock
import com.willfp.eco.core.integrations.antigrief.AntigriefIntegration
import org.bukkit.Location
import org.bukkit.block.Block
import org.bukkit.entity.LivingEntity
import org.bukkit.entity.Monster
import org.bukkit.entity.Player
class AntigriefFabledSkyBlock : AntigriefIntegration {
override fun getPluginName(): String {
return "FabledSkyBlock"
}
override fun canBreakBlock(player: Player, block: Block): Boolean {
val skyblock = SkyBlock.getInstance()
val island = skyblock.islandManager.getIslandAtLocation(block.location) ?: return true
if (player.hasPermission("fabledskyblock.bypass.destroy")) {
return true
}
if (skyblock.permissionManager.hasPermission(island, "Destroy", island.getRole(player))) {
return true
}
return false
}
override fun canCreateExplosion(player: Player, location: Location): Boolean {
val skyblock = SkyBlock.getInstance()
val island = skyblock.islandManager.getIslandAtLocation(location) ?: return true
if (player.hasPermission("fabledskyblock.bypass.explosions")) {
return true
}
if (skyblock.permissionManager.hasPermission(island, "Explosions", island.getRole(player))) {
return true
}
return false
}
override fun canPlaceBlock(player: Player, block: Block): Boolean {
val skyblock = SkyBlock.getInstance()
val island = skyblock.islandManager.getIslandAtLocation(block.location) ?: return true
if (player.hasPermission("fabledskyblock.bypass.place")) {
return true
}
if (skyblock.permissionManager.hasPermission(island, "Place", island.getRole(player))) {
return true
}
return false
}
override fun canInjure(player: Player, victim: LivingEntity): Boolean {
val skyblock = SkyBlock.getInstance()
val island = SkyBlock.getInstance().islandManager.getIslandAtLocation(victim.location) ?: return true
if (victim is Player) return skyblock.permissionManager.hasPermission(island, "PvP", island.getRole(player))
val islandPermission = when (victim) {
is Monster -> "MonsterHurting"
else -> "MobHurting"
}
if (skyblock.permissionManager.hasPermission(island, islandPermission, island.getRole(player))) {
return true
}
return false
}
override fun canPickupItem(player: Player, location: Location): Boolean {
val skyblock = SkyBlock.getInstance()
val island = SkyBlock.getInstance().islandManager.getIslandAtLocation(location) ?: return true
if (player.hasPermission("fabledskyblock.bypass.itempickup")) {
return true
}
if (skyblock.permissionManager.hasPermission(island, "ItemPickup", island.getRole(player))) {
return true
}
return false
}
}

View File

@@ -5,7 +5,7 @@ import com.massivecraft.factions.FLocation
import com.massivecraft.factions.FPlayer
import com.massivecraft.factions.FPlayers
import com.massivecraft.factions.Faction
import com.massivecraft.factions.perms.PermissibleAction
import com.massivecraft.factions.perms.PermissibleActions
import com.willfp.eco.core.integrations.antigrief.AntigriefIntegration
import org.bukkit.Location
import org.bukkit.block.Block
@@ -20,7 +20,7 @@ class AntigriefFactionsUUID : AntigriefIntegration {
val fplayer: FPlayer = FPlayers.getInstance().getByPlayer(player)
val flocation = FLocation(block.location)
val faction: Faction = Board.getInstance().getFactionAt(flocation)
return if (!faction.hasAccess(fplayer, PermissibleAction.DESTROY)) {
return if (!faction.hasAccess(fplayer, PermissibleActions.DESTROY, flocation)) {
fplayer.isAdminBypassing
} else true
}
@@ -41,7 +41,7 @@ class AntigriefFactionsUUID : AntigriefIntegration {
val fplayer: FPlayer = FPlayers.getInstance().getByPlayer(player)
val flocation = FLocation(block.location)
val faction: Faction = Board.getInstance().getFactionAt(flocation)
return if (!faction.hasAccess(fplayer, PermissibleAction.BUILD)) {
return if (!faction.hasAccess(fplayer, PermissibleActions.BUILD, flocation)) {
fplayer.isAdminBypassing
} else true
}
@@ -58,7 +58,7 @@ class AntigriefFactionsUUID : AntigriefIntegration {
return fplayer.isAdminBypassing
}
} else {
if (faction.hasAccess(fplayer, PermissibleAction.DESTROY)) {
if (faction.hasAccess(fplayer, PermissibleActions.DESTROY, flocation)) {
return fplayer.isAdminBypassing
}
}

View File

@@ -4,16 +4,16 @@ import com.willfp.eco.core.EcoPlugin
import com.willfp.eco.core.integrations.customitems.CustomItemsIntegration
import com.willfp.eco.core.items.Items
import com.willfp.eco.core.recipe.parts.EmptyTestableItem
import io.lumine.mythic.api.adapters.AbstractItemStack
import io.lumine.mythic.api.config.MythicLineConfig
import io.lumine.mythic.api.drops.DropMetadata
import io.lumine.mythic.api.drops.IMultiDrop
import io.lumine.mythic.api.drops.IItemDrop
import io.lumine.mythic.bukkit.adapters.BukkitItemStack
import io.lumine.mythic.bukkit.events.MythicDropLoadEvent
import io.lumine.mythic.core.drops.Drop
import io.lumine.mythic.core.drops.LootBag
import io.lumine.mythic.core.drops.droppables.ItemDrop
import org.bukkit.Material
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.bukkit.inventory.ItemStack
class CustomItemsMythicMobs(
private val plugin: EcoPlugin
@@ -39,19 +39,16 @@ class CustomItemsMythicMobs(
private class MythicMobsDrop(
private val plugin: EcoPlugin,
itemConfig: MythicLineConfig
) : Drop(itemConfig.line, itemConfig), IMultiDrop {
private val id = itemConfig.getString(arrayOf("type", "t", "item", "i"), this.dropVar)
override fun get(data: DropMetadata): LootBag {
val bag = LootBag(data)
) : IItemDrop {
private val id = itemConfig.getString(arrayOf("type", "t", "item", "i"), "eco")
override fun getDrop(data: DropMetadata, v: Double): AbstractItemStack {
val item = Items.lookup(id)
if (item is EmptyTestableItem) {
plugin.logger.warning("Item with ID $id is invalid, check your configs!")
return bag
return BukkitItemStack(ItemStack(Material.AIR))
}
bag.add(data, ItemDrop(this.line, this.config, BukkitItemStack(item.item)))
return bag
return BukkitItemStack(item.item.apply { amount = v.toInt() })
}
}
}

View File

@@ -0,0 +1,48 @@
package com.willfp.eco.internal.spigot.integrations.customitems
import com.willfp.eco.core.integrations.customitems.CustomItemsIntegration
import com.willfp.eco.core.items.CustomItem
import com.willfp.eco.core.items.Items
import com.willfp.eco.core.items.TestableItem
import com.willfp.eco.core.items.provider.ItemProvider
import com.willfp.eco.util.NamespacedKeyUtils
import dev.norska.scyther.Scyther
import dev.norska.scyther.api.ScytherAPI
import org.bukkit.Material
class CustomItemsScyther : CustomItemsIntegration {
override fun registerProvider() {
Items.registerItemProvider(ScytherProvider())
}
override fun getPluginName(): String {
return "Scyther"
}
private class ScytherProvider : ItemProvider("scyther") {
override fun provideForKey(key: String): TestableItem? {
val material = Material.matchMaterial(key.uppercase()) ?: Material.WOODEN_HOE
val hoe = ScytherAPI.createHarvesterHoe(
Scyther.getInstance(),
material,
0,
null,
1,
Int.MAX_VALUE,
null,
null
)
val namespacedKey = NamespacedKeyUtils.create("scyther", key)
return CustomItem(
namespacedKey,
{
ScytherAPI.isHarvesterItem(it) && it.type == material
},
hoe
)
}
}
}

View File

@@ -2,16 +2,27 @@ package com.willfp.eco.internal.spigot.integrations.shop
import com.willfp.eco.core.integrations.shop.ShopIntegration
import com.willfp.eco.core.integrations.shop.ShopSellEvent
import me.gypopo.economyshopgui.api.EconomyShopGUIHook
import me.gypopo.economyshopgui.api.events.PreTransactionEvent
import org.bukkit.Bukkit
import org.bukkit.entity.Player
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.bukkit.inventory.ItemStack
class ShopEconomyShopGUI : ShopIntegration {
override fun getSellEventAdapter(): Listener {
return EconomyShopGUISellEventListeners
}
override fun getPrice(itemStack: ItemStack, player: Player): Double {
return EconomyShopGUIHook.getItemSellPrice(player, itemStack)
}
override fun getPrice(itemStack: ItemStack): Double {
return EconomyShopGUIHook.getItemSellPrice(itemStack)
}
object EconomyShopGUISellEventListeners : Listener {
@EventHandler
fun shopEventToEcoEvent(event: PreTransactionEvent) {

View File

@@ -9,6 +9,7 @@ import net.brcdev.shopgui.provider.item.ItemProvider
import net.brcdev.shopgui.shop.ShopManager
import org.bukkit.Bukkit
import org.bukkit.configuration.ConfigurationSection
import org.bukkit.entity.Player
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.bukkit.inventory.ItemStack
@@ -22,6 +23,14 @@ class ShopShopGuiPlus : ShopIntegration {
return ShopGuiPlusSellEventListeners
}
override fun getPrice(itemStack: ItemStack): Double {
return ShopGuiPlusApi.getItemStackPriceSell(itemStack)
}
override fun getPrice(itemStack: ItemStack, player: Player): Double {
return ShopGuiPlusApi.getItemStackPriceSell(player, itemStack)
}
class EcoShopGuiPlusProvider : ItemProvider("eco") {
override fun isValidItem(itemStack: ItemStack?): Boolean {
itemStack ?: return false

View File

@@ -3,19 +3,31 @@ package com.willfp.eco.internal.spigot.math
import com.github.benmanes.caffeine.cache.Cache
import com.github.benmanes.caffeine.cache.Caffeine
import com.willfp.eco.core.integrations.placeholder.PlaceholderManager
import com.willfp.eco.core.placeholder.AdditionalPlayer
import com.willfp.eco.core.placeholder.PlaceholderInjectable
import org.bukkit.entity.Player
import redempt.crunch.CompiledExpression
import redempt.crunch.Crunch
import redempt.crunch.data.FastNumberParsing
import redempt.crunch.functional.EvaluationEnvironment
import redempt.crunch.functional.Function
import kotlin.math.max
import kotlin.math.min
private val cache: Cache<String, CompiledExpression> = Caffeine.newBuilder().build()
private val goToZero = Crunch.compileExpression("0")
fun evaluateExpression(expression: String, player: Player?, context: PlaceholderInjectable): Double {
private val min = Function("min", 2) {
min(it[0], it[1])
}
private val max = Function("max", 2) {
max(it[0], it[1])
}
fun evaluateExpression(expression: String, player: Player?, context: PlaceholderInjectable, additional: Collection<AdditionalPlayer>): Double {
val placeholderValues = PlaceholderManager.findPlaceholdersIn(expression)
.map { PlaceholderManager.translatePlaceholders(it, player, context) }
.map { PlaceholderManager.translatePlaceholders(it, player, context, additional) }
.map { runCatching { FastNumberParsing.parseDouble(it) }.getOrDefault(0.0) }
.toDoubleArray()
@@ -23,6 +35,7 @@ fun evaluateExpression(expression: String, player: Player?, context: Placeholder
val placeholders = PlaceholderManager.findPlaceholdersIn(it)
val env = EvaluationEnvironment()
env.setVariableNames(*placeholders.toTypedArray())
env.addFunctions(min, max)
runCatching { Crunch.compileExpression(expression, env) }.getOrDefault(goToZero)
}

View File

@@ -0,0 +1,42 @@
package com.willfp.eco.internal.spigot.player
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.core.data.profile
import org.bukkit.event.EventHandler
import org.bukkit.event.Listener
import org.bukkit.event.player.PlayerJoinEvent
import org.bukkit.event.player.PlayerQuitEvent
class PlayerHealthFixer(
private val plugin: EcoPlugin
): Listener {
private val lastHealthKey = PersistentDataKey(
plugin.namespacedKeyFactory.create("last_health"),
PersistentDataKeyType.DOUBLE,
0.0
)
@EventHandler
fun onLeave(event: PlayerQuitEvent) {
if (!plugin.configYml.getBool("health-fixer")) {
return
}
val player = event.player
player.profile.write(lastHealthKey, player.health)
}
@EventHandler
fun onJoin(event: PlayerJoinEvent) {
if (!plugin.configYml.getBool("health-fixer")) {
return
}
val player = event.player
plugin.scheduler.runLater(2) {
player.health = player.profile.read(lastHealthKey)
}
}
}

View File

@@ -6,8 +6,8 @@
# How player/server data is saved:
# yaml - Stored in data.yml: Good option for single-node servers (i.e. no BungeeCord/Velocity)
# mongo - (Recommended) If you're running on a network (Bungee/Velocity), you should use MongoDB if you can.
# mysql - (Not Recommended) The basic choice for Bungee/Velocity networks, less flexible and worse performance than MongoDB. Only use it if you can't use MongoDB.
# mongo - If you're running on a network (Bungee/Velocity), you should use MongoDB if you can.
# mysql - The alternative to MongoDB. Because of how eco data works, MongoDB is the best option; but use this if you can't.
data-handler: yaml
# If data should be migrated automatically when changing data handler.
@@ -24,17 +24,12 @@ mysql:
threads: 2
# The maximum number of MySQL connections.
connections: 10
# If read operations should be run in the thread pool. Runs on main thread by default.
async-reads: false
host: localhost
port: 3306
database: database
user: username
password: passy
# Ignore this option, it does nothing.
enabled: false # Ignore this - only for backwards compatibility
# Options to manage the conflict finder
conflicts:
whitelist: # Plugins that should never be marked as conflicts
@@ -76,4 +71,12 @@ log-full-extension-errors: false
# disable this option. Bear in mind that this means the auto-craft preview will fail to
# render items nicely, which may degrade the user experience on your server. If you use
# a custom crafting table, though, this won't affect anything, and you should disable the option.
displayed-recipes: true
displayed-recipes: true
# Save health on leave and set it back on join - works around attribute modifiers.
health-fixer: false
# If eco plugins should not check for updates; only enable this if you know what you're doing
# as there can be urgent hotfixes that you are then not notified about. If you're confident
# that you can manage updates on your own, turn this on.
no-update-checker: false

View File

@@ -1,71 +1 @@
# For internal storage use only, do not modify.
categorized-keys:
# Preloading known keys (as of the release of 6.25.0) for optimal performance.
# This is only used when MySQL is enabled as the columns must be added each time a new key is registered.
player:
- ecoskills:crit_damage;INT;0
- ecoskills:strong_impact;INT;0
- ecoskills:shamanism;INT;0
- ecoskills:reimbursement;INT;0
- ecoskills:armory_xp;DOUBLE;0.0
- ecoskills:bravery;INT;0
- ecoskills:seamless_movement;INT;0
- ecoskills:fishing;INT;0
- ecoskills:armory;INT;0
- ecoskills:accelerated_escape;INT;0
- ecoskills:alchemy_xp;DOUBLE;0.0
- boosters:2sell_multiplier;INT;0
- ecoskills:second_chance;INT;0
- ecoskills:health;INT;0
- ecoskills:spelunking;INT;0
- eco:player_name;STRING;Unknown Player
- ecoskills:strength;INT;0
- ecoskills:woodcutting_xp;DOUBLE;0.0
- ecoskills:versatile_tools;INT;0
- boosters:skill_xp;INT;0
- ecoskills:infernal_resistance;INT;0
- ecoskills:wisdom;INT;0
- ecoskills:master_lumberjack;INT;0
- ecoskills:defense;INT;0
- ecoskills:mystic_resilience;INT;0
- ecoskills:gainsound;BOOLEAN;true
- ecoskills:golden_yield;INT;0
- ecoskills:dazzle;INT;0
- ecoskills:dodging;INT;0
- ecoskills:efficient_brewing;INT;0
- ecoskills:bountiful_harvest;INT;0
- ecoskills:actionbar_enabled;BOOLEAN;true
- ecoskills:enchanting_xp;DOUBLE;0.0
- ecoskills:overcompensation;INT;0
- ecoskills:alchemy;INT;0
- ecoskills:woodcutting;INT;0
- ecoskills:mining;INT;0
- ecoskills:magnetic_rod;INT;0
- ecoskills:fishing_xp;DOUBLE;0.0
- ecoskills:farming_xp;DOUBLE;0.0
- ecoskills:speed;INT;0
- ecoskills:potionmaster;INT;0
- ecoskills:combat_xp;DOUBLE;0.0
- ecoskills:eye_of_the_depths;INT;0
- ecoskills:ferocity;INT;0
- ecoskills:combat;INT;0
- ecoskills:mining_xp;DOUBLE;0.0
- ecoskills:satiation;INT;0
- ecoskills:craftsmanship;INT;0
- ecoskills:crit_chance;INT;0
- ecoskills:dynamic_mining;INT;0
- ecoskills:exploration;INT;0
- boosters:1_5sell_multiplier;INT;0
- ecoskills:enchanting;INT;0
- ecoskills:endangering;INT;0
- ecoskills:serrated_strikes;INT;0
- ecoskills:exploration_xp;DOUBLE;0.0
- ecoskills:farming;INT;0
server:
- 'talismans:known_points;STRING;'
- 'ecoarmor:known_points;STRING;'
- 'ecoenchants:known_points;STRING;'
- 'ecoitems:known_points;STRING;'
- 'boosters:known_points;STRING;'
- 'reforges:known_points;STRING;'

View File

@@ -37,6 +37,7 @@ softdepend:
- DeluxeCombat
- IridiumSkyblock
- SuperiorSkyblock2
- FabledSkyBlock
- CrashClaim
- DecentHolograms
- MythicMobs
@@ -45,4 +46,5 @@ softdepend:
- RPGHorses
- EconomyShopGUI
- zShop
- DeluxeSellwands
- DeluxeSellwands
- Scyther

View File

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

Binary file not shown.

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

269
gradlew vendored
View File

@@ -1,7 +1,7 @@
#!/usr/bin/env sh
#!/bin/sh
#
# Copyright 2015 the original author or authors.
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -17,67 +17,101 @@
#
##############################################################################
##
## Gradle start up script for UN*X
##
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
MAX_FD=maximum
warn () {
echo "$*"
}
} >&2
die () {
echo
echo "$*"
echo
exit 1
}
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
@@ -87,9 +121,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD="$JAVA_HOME/bin/java"
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
@@ -98,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
@@ -106,80 +140,95 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

BIN
lib/FactionsUUID.jar Normal file

Binary file not shown.