groups) {
diff --git a/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CAClickResultBypassEvent.java b/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CAClickResultBypassEvent.java
index a27c65e..fe5e199 100644
--- a/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CAClickResultBypassEvent.java
+++ b/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CAClickResultBypassEvent.java
@@ -17,7 +17,7 @@ import org.jetbrains.annotations.NotNull;
* Most of the time you would likely need {@link CAPreAnvilBypassEvent} or {@link CAEarlyPreAnvilBypassEvent}
* for this event to be useful.
*
- * There is also {@link CATreatAnvilResultEvent} that may be better for some use case.
+ * There is also {@link CATreatAnvilResult2Event} that may be better for some use case.
*/
public class CAClickResultBypassEvent extends Event implements Cancellable {
diff --git a/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CAEarlyPreAnvilBypassEvent.java b/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CAEarlyPreAnvilBypassEvent.java
index 2fbd275..e92b4cd 100644
--- a/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CAEarlyPreAnvilBypassEvent.java
+++ b/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CAEarlyPreAnvilBypassEvent.java
@@ -15,7 +15,7 @@ import org.jetbrains.annotations.NotNull;
*
* You should also use {@link CAClickResultBypassEvent} if you want to use this event for something useful.
*
- * It is also recommended that you read about {@link CAPreAnvilBypassEvent} and {@link CATreatAnvilResultEvent}
+ * It is also recommended that you read about {@link CAPreAnvilBypassEvent} and {@link CATreatAnvilResult2Event}
* as your use case may be more prone to use theses.
*/
public class CAEarlyPreAnvilBypassEvent extends Event implements Cancellable {
diff --git a/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CAPreAnvilBypassEvent.java b/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CAPreAnvilBypassEvent.java
index 18334e3..9103a4b 100644
--- a/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CAPreAnvilBypassEvent.java
+++ b/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CAPreAnvilBypassEvent.java
@@ -18,7 +18,7 @@ import org.jetbrains.annotations.NotNull;
*
* You should also use {@link CAClickResultBypassEvent} if you want to use this event for something useful.
*
- * It is also recommended that you read about {@link CAEarlyPreAnvilBypassEvent} and {@link CATreatAnvilResultEvent}
+ * It is also recommended that you read about {@link CAEarlyPreAnvilBypassEvent} and {@link CATreatAnvilResult2Event}
* as your use case may be more prone to use theses.
*/
public class CAPreAnvilBypassEvent extends Event implements Cancellable {
diff --git a/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CATreatAnvilResult2Event.java b/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CATreatAnvilResult2Event.java
new file mode 100644
index 0000000..30c5380
--- /dev/null
+++ b/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CATreatAnvilResult2Event.java
@@ -0,0 +1,196 @@
+package xyz.alexcrea.cuanvil.api.event.listener;
+
+import org.bukkit.event.Event;
+import org.bukkit.event.HandlerList;
+import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.InventoryView;
+import org.bukkit.inventory.ItemStack;
+import org.jetbrains.annotations.ApiStatus;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import xyz.alexcrea.cuanvil.anvil.AnvilCost;
+import xyz.alexcrea.cuanvil.anvil.AnvilUseType;
+
+/**
+ * Called after custom anvil processed the click on the result on the anvil inventory.
+ * This event should be used to modify the result of an anvil use.
+ *
+ * You may also want to check {@link CAClickResultBypassEvent},
+ * {@link CAPreAnvilBypassEvent}
+ * and {@link CAEarlyPreAnvilBypassEvent} for your use case
+ *
+ * A null result will cancel this event
+ */
+@SuppressWarnings("unused")
+public class CATreatAnvilResult2Event extends Event {
+
+ private static final HandlerList HANDLERS = new HandlerList();
+
+ public static HandlerList getHandlerList() {
+ return HANDLERS;
+ }
+
+ @Override
+ public @NotNull HandlerList getHandlers() {
+ return HANDLERS;
+ }
+
+ @NotNull
+ private final InventoryView view;
+
+ private final AnvilUseType useType;
+
+ @Nullable
+ private final ItemStack left;
+ @Nullable
+ private final ItemStack right;
+
+ @Nullable
+ private ItemStack result;
+
+ private final AnvilCost cost;
+
+ @ApiStatus.Internal
+ public CATreatAnvilResult2Event(
+ @NotNull InventoryView view,
+ Inventory inv,
+ AnvilUseType useType,
+ @Nullable ItemStack result,
+ AnvilCost cost) {
+ this.view = view;
+ this.useType = useType;
+
+ this.left = inv.getItem(0); // TODO use view here
+ this.right = inv.getItem(1);
+ this.result = result;
+ this.cost = cost;
+ }
+
+ /**
+ * Get the bukkit inventory view.
+ *
+ * Temporarily marked as internal as it will get changed to anvil view on legacy removal
+ * so signature will change
+ *
+ * @return The inventory view of this event.
+ */
+ @ApiStatus.Internal
+ public @NotNull InventoryView getView() {
+ return view;
+ }
+
+
+ /**
+ * Get the type of use source of the result.
+ *
+ * @return The craft use type.
+ */
+ public AnvilUseType getUseType() {
+ return useType;
+ }
+
+ /**
+ * Get the left item of the anvil use
+ *
+ * @return the left item
+ */
+ public @Nullable ItemStack getLeftItem() {
+ return left;
+ }
+
+ /**
+ * Get the right item of the anvil use
+ *
+ * @return the right item
+ */
+ public @Nullable ItemStack getRightItem() {
+ return right;
+ }
+
+ /**
+ * Get the current result
+ *
+ * note that it will not be null unless another listener previously set it to null.
+ *
+ * @return The current result.
+ */
+ public @Nullable ItemStack getResult() {
+ return result;
+ }
+
+ /**
+ * Set the current result
+ *
+ * note that a null result will cancel this anvil use.
+ *
+ * @param result The new result
+ */
+ public void setResult(@Nullable ItemStack result) {
+ this.result = result;
+ }
+
+ /**
+ * Get the level cost displayed on the anvil.
+ *
Important note:
+ * the final price are re calculated on click for the following use case:
+ *
+ * - Custom craft
+ * - Unit repair
+ * - Lore edit
+ *
+ * This value will be used as final price for:
+ * Item merge
+ * Item rename
+ *
+ *
+ * @return The current cost.
+ * @deprecated use #{@link #getCost()} instead
+ */
+ @Deprecated(forRemoval = true, since = "1.17.0")
+ public int getLevelCost() {
+ return cost.asXpCost();
+ }
+
+ /**
+ * Set the level cost displayed on the anvil.
+ * Important note:
+ * the final price are re calculated on click for the following use case:
+ *
+ * - Custom craft
+ * - Unit repair
+ * - Lore edit
+ *
+ * This value will be used as final price for:
+ * Item merge
+ * Item rename
+ *
+ *
+ * @param levelCost The new cost.
+ * @deprecated use #{@link #getCost()} and set value on this instead
+ */
+ @Deprecated(forRemoval = true, since = "1.17.0")
+ public void setLevelCost(int levelCost) {
+ cost.setGeneric(levelCost - cost.getGeneric() - cost.asXpCost());
+ }
+
+ /**
+ * Allow access to the current cost of the event
+ * Note that modifying this object will change the event resulting cost
+ *
+ * Important note:
+ * the final price are re calculated on click for the following use case:
+ *
+ * - Custom craft
+ * - Unit repair
+ * - Lore edit
+ *
+ * This value will be used as final price for:
+ * Item merge
+ * Item rename
+ *
+ * @return the current anvil cost
+ */
+ public AnvilCost getCost() {
+ return cost;
+ }
+}
diff --git a/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CATreatAnvilResultEvent.java b/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CATreatAnvilResultEvent.java
index 1675d1a..80965b5 100644
--- a/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CATreatAnvilResultEvent.java
+++ b/src/main/java/xyz/alexcrea/cuanvil/api/event/listener/CATreatAnvilResultEvent.java
@@ -6,7 +6,8 @@ import org.bukkit.event.inventory.PrepareAnvilEvent;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-import xyz.alexcrea.cuanvil.util.AnvilUseType;
+import xyz.alexcrea.cuanvil.anvil.AnvilCost;
+import xyz.alexcrea.cuanvil.anvil.AnvilUseType;
/**
* Called after custom anvil processed the click on the result on the anvil inventory.
@@ -17,8 +18,12 @@ import xyz.alexcrea.cuanvil.util.AnvilUseType;
* and {@link CAEarlyPreAnvilBypassEvent} for your use case
*
* A null result will cancel this pre anvil event
+ *
+ * @deprecated Prepare anvil Event cannot be provided as it can be called on result and therefore not have prepared anvil event
+ * use {@link CATreatAnvilResult2Event} instead
*/
@SuppressWarnings("unused")
+@Deprecated(forRemoval = true, since = "1.17.0")
public class CATreatAnvilResultEvent extends Event {
private static final HandlerList HANDLERS = new HandlerList();
@@ -40,13 +45,13 @@ public class CATreatAnvilResultEvent extends Event {
@Nullable
private ItemStack result;
- private int levelCost;
+ private final AnvilCost cost;
- public CATreatAnvilResultEvent(@NotNull PrepareAnvilEvent event, AnvilUseType useType, @Nullable ItemStack result, int levelCost) {
+ public CATreatAnvilResultEvent(@NotNull PrepareAnvilEvent event, AnvilUseType useType, @Nullable ItemStack result, AnvilCost cost) {
this.event = event;
this.useType = useType;
this.result = result;
- this.levelCost = levelCost;
+ this.cost = cost;
}
/**
@@ -104,9 +109,11 @@ public class CATreatAnvilResultEvent extends Event {
*
*
* @return The current cost.
+ * @deprecated use #{@link #getCost()} instead
*/
+ @Deprecated(forRemoval = true, since = "1.17.0")
public int getLevelCost() {
- return levelCost;
+ return cost.asXpCost();
}
/**
@@ -124,8 +131,32 @@ public class CATreatAnvilResultEvent extends Event {
*
*
* @param levelCost The new cost.
+ * @deprecated use #{@link #getCost()} and set value on this instead
*/
+ @Deprecated(forRemoval = true, since = "1.17.0")
public void setLevelCost(int levelCost) {
- this.levelCost = levelCost;
+ cost.setGeneric(levelCost - cost.getGeneric() - cost.asXpCost());
}
+
+ /**
+ * Allow access to the current cost of the event
+ * Note that modifying this object will change the event resulting cost
+ *
+ *
Important note:
+ * the final price are re calculated on click for the following use case:
+ *
+ * - Custom craft
+ * - Unit repair
+ * - Lore edit
+ *
+ * This value will be used as final price for:
+ * Item merge
+ * Item rename
+ *
+ * @return the current anvil cost
+ */
+ public AnvilCost getCost() {
+ return cost;
+ }
+
}
diff --git a/src/main/java/xyz/alexcrea/cuanvil/config/ConfigHolder.java b/src/main/java/xyz/alexcrea/cuanvil/config/ConfigHolder.java
index 2037e23..f6a7e80 100644
--- a/src/main/java/xyz/alexcrea/cuanvil/config/ConfigHolder.java
+++ b/src/main/java/xyz/alexcrea/cuanvil/config/ConfigHolder.java
@@ -9,6 +9,7 @@ import org.jetbrains.annotations.Nullable;
import xyz.alexcrea.cuanvil.group.EnchantConflictManager;
import xyz.alexcrea.cuanvil.group.ItemGroupManager;
import xyz.alexcrea.cuanvil.recipe.CustomAnvilRecipeManager;
+import xyz.alexcrea.cuanvil.util.MetricsUtil;
import java.io.File;
import java.io.IOException;
@@ -145,6 +146,7 @@ public abstract class ConfigHolder {
sufficientSuccess = true;
} catch (IOException e) {
CustomAnvil.instance.getLogger().log(Level.WARNING, "Could not copy backup saving config " + base.getName(), e);
+ MetricsUtil.INSTANCE.trackError(e);
}
}
// save last backup
@@ -275,6 +277,7 @@ public abstract class ConfigHolder {
this.deletedConfigFile.createNewFile();
} catch (IOException e) {
CustomAnvil.instance.getLogger().log(Level.WARNING, "Could not create " + this.deletedConfigFile.getPath(), e);
+ MetricsUtil.INSTANCE.trackError(e);
}
loadDeletedListFile(false);
@@ -312,6 +315,7 @@ public abstract class ConfigHolder {
this.deletedListConfig.save(this.deletedConfigFile);
} catch (IOException e) {
CustomAnvil.instance.getLogger().log(Level.WARNING, "Could not save " + this.deletedConfigFile.getPath(), e);
+ MetricsUtil.INSTANCE.trackError(e);
return false;
}
diff --git a/src/main/java/xyz/alexcrea/cuanvil/config/WorkPenaltyType.java b/src/main/java/xyz/alexcrea/cuanvil/config/WorkPenaltyType.java
index 75b0861..d374999 100644
--- a/src/main/java/xyz/alexcrea/cuanvil/config/WorkPenaltyType.java
+++ b/src/main/java/xyz/alexcrea/cuanvil/config/WorkPenaltyType.java
@@ -2,7 +2,7 @@ package xyz.alexcrea.cuanvil.config;
import com.google.common.collect.ImmutableMap;
import org.jetbrains.annotations.Nullable;
-import xyz.alexcrea.cuanvil.util.AnvilUseType;
+import xyz.alexcrea.cuanvil.anvil.AnvilUseType;
import java.util.EnumMap;
diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/AdditionalTestEnchantment.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/AdditionalTestEnchantment.java
index 832e5af..821838f 100644
--- a/src/main/java/xyz/alexcrea/cuanvil/enchant/AdditionalTestEnchantment.java
+++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/AdditionalTestEnchantment.java
@@ -1,6 +1,7 @@
package xyz.alexcrea.cuanvil.enchant;
import org.bukkit.Material;
+import org.bukkit.NamespacedKey;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
@@ -11,24 +12,23 @@ public interface AdditionalTestEnchantment {
/**
* Test if the provided enchantments can be compatible with this enchantment. only non-Custom Anvil conflict.
* @param enchantments Immutable map of validated enchantments for the item.
- * @param itemMat Material of the tested item.
+ * @param itemType Material namespaced key of the tested item.
* @return If there is a conflict with the enchantments.
*/
boolean isEnchantConflict(
@NotNull Map enchantments,
- @NotNull Material itemMat);
-
+ @NotNull NamespacedKey itemType);
/**
* Test if the provided item can be compatible with this enchantment. only non-Custom Anvil conflict.
* @param enchantments Immutable map of validated enchantments for the item.
- * @param itemMat Material of the tested item.
+ * @param itemType Material namespaced key of the tested item.
* @param item Provide a new instance of the used item stack with the partial enchantment applied.
* @return If there is a conflict with the enchantment and the item.
*/
boolean isItemConflict(
@NotNull Map enchantments,
- @NotNull Material itemMat,
+ @NotNull NamespacedKey itemType,
@NotNull ItemStack item);
}
diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/CAEnchantmentRegistry.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/CAEnchantmentRegistry.java
index 4634a11..854ed55 100644
--- a/src/main/java/xyz/alexcrea/cuanvil/enchant/CAEnchantmentRegistry.java
+++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/CAEnchantmentRegistry.java
@@ -10,6 +10,7 @@ import xyz.alexcrea.cuanvil.enchant.bulk.BukkitEnchantBulkOperation;
import xyz.alexcrea.cuanvil.enchant.bulk.BulkCleanEnchantOperation;
import xyz.alexcrea.cuanvil.enchant.bulk.BulkGetEnchantOperation;
import xyz.alexcrea.cuanvil.enchant.wrapped.CABukkitEnchantment;
+import xyz.alexcrea.cuanvil.util.MetricsUtil;
import java.util.*;
import java.util.logging.Level;
@@ -85,11 +86,13 @@ public class CAEnchantmentRegistry {
return false;
}
+ var error = new IllegalStateException("enchantment " + enchantment.getKey() + " was already registered");
CustomAnvil.instance.getLogger().log(Level.WARNING,
"Duplicate distinct registered enchantment. This should NOT happen any time.\n" +
"If you are a custom anvil developer: Maybe custom anvil detected your enchantment as a bukkit enchantment. " +
"you should maybe remove enchantment with the same key before registering yours",
- new IllegalStateException("enchantment " + enchantment.getKey() + " was already registered"));
+ error);
+ MetricsUtil.INSTANCE.trackError(error);
return false;
}
diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/bulk/SuperEnchantBulkOperation.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/bulk/SuperEnchantBulkOperation.java
new file mode 100644
index 0000000..8bc729a
--- /dev/null
+++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/bulk/SuperEnchantBulkOperation.java
@@ -0,0 +1,47 @@
+package xyz.alexcrea.cuanvil.enchant.bulk;
+
+import com.maddoxh.superEnchants.items.EnchantApplicator;
+import com.maddoxh.superEnchants.items.EnchantReader;
+import io.delilaheve.CustomAnvil;
+import org.bukkit.NamespacedKey;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+import org.bukkit.plugin.Plugin;
+import org.jetbrains.annotations.NotNull;
+import xyz.alexcrea.cuanvil.api.EnchantmentApi;
+import xyz.alexcrea.cuanvil.enchant.CAEnchantment;
+
+import java.util.Map;
+
+public class SuperEnchantBulkOperation implements BulkGetEnchantOperation, BulkCleanEnchantOperation {
+
+ private Plugin plugin;
+ public SuperEnchantBulkOperation(Plugin plugin) {
+ this.plugin = plugin;
+ }
+
+ @Override
+ public void bulkGet(@NotNull Map enchantmentMap, @NotNull ItemStack item, @NotNull ItemMeta meta) {
+ EnchantReader.INSTANCE.readEnchants(item).forEach((ench, level) -> {
+ var enchantment = EnchantmentApi.getByKey(NamespacedKey.fromString(ench, plugin));
+ if(enchantment == null) {
+ CustomAnvil.log("Enchantment " + ench + " not found in custom anvil");
+ return;
+ }
+
+ enchantmentMap.put(enchantment, level);
+ }
+ );
+ }
+
+ @Override
+ public void bulkClear(@NotNull ItemStack item) {
+ EnchantApplicator.INSTANCE.clearAllCustomEnchants(item);
+ }
+
+ @Override
+ public void bulkClear(@NotNull ItemStack item, @NotNull ItemMeta meta) {
+ // item meta is not preferred for enchantment squared clear
+ }
+
+}
diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEPreV5Enchantment.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEPreV5Enchantment.java
index d3082c9..783798d 100644
--- a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEPreV5Enchantment.java
+++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEPreV5Enchantment.java
@@ -1,6 +1,7 @@
package xyz.alexcrea.cuanvil.enchant.wrapped;
import org.bukkit.Material;
+import org.bukkit.NamespacedKey;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import su.nightexpress.excellentenchants.api.enchantment.CustomEnchantment;
@@ -39,7 +40,7 @@ public class CAEEPreV5Enchantment extends CABukkitEnchantment implements Additio
}
@Override
- public boolean isEnchantConflict(@NotNull Map enchantments, @NotNull Material itemMat) {
+ public boolean isEnchantConflict(@NotNull Map enchantments, @NotNull NamespacedKey itemType) {
if (!definition.hasConflicts()) return false;
Set conflicts = definition.getConflicts();
@@ -52,8 +53,8 @@ public class CAEEPreV5Enchantment extends CABukkitEnchantment implements Additio
}
@Override
- public boolean isItemConflict(@NotNull Map enchantments, @NotNull Material itemMat, @NotNull ItemStack item) {
- if (Material.ENCHANTED_BOOK.equals(itemMat)) return false;
+ public boolean isItemConflict(@NotNull Map enchantments, @NotNull NamespacedKey itemType, @NotNull ItemStack item) {
+ if (Material.ENCHANTED_BOOK.getKey().equals(itemType)) return false;
return !definition.getSupportedItems().is(item);
}
diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEV5Enchantment.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEV5Enchantment.java
index e91930f..2d8f945 100644
--- a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEV5Enchantment.java
+++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEV5Enchantment.java
@@ -1,48 +1,51 @@
package xyz.alexcrea.cuanvil.enchant.wrapped;
import org.bukkit.Material;
+import org.bukkit.NamespacedKey;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import su.nightexpress.excellentenchants.api.enchantment.CustomEnchantment;
import su.nightexpress.excellentenchants.api.item.ItemSet;
-import su.nightexpress.excellentenchants.api.wrapper.EnchantDefinition;
import xyz.alexcrea.cuanvil.enchant.AdditionalTestEnchantment;
import xyz.alexcrea.cuanvil.enchant.CAEnchantment;
import xyz.alexcrea.cuanvil.enchant.EnchantmentRarity;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;
public class CAEEV5Enchantment extends CABukkitEnchantment implements AdditionalTestEnchantment {
@NotNull CustomEnchantment eeenchantment;
- @NotNull EnchantDefinition definition;
+ @NotNull Object definition;
public CAEEV5Enchantment(@NotNull CustomEnchantment enchantment) {
- super(enchantment.getBukkitEnchantment(), EnchantmentRarity.getRarity(enchantment.getDefinition().getAnvilCost()));
+ super(enchantment.getBukkitEnchantment(), EnchantmentRarity.getRarity(getAnvilCost(enchantment)));
this.eeenchantment = enchantment;
- this.definition = enchantment.getDefinition();
+ this.definition = getDefinition(enchantment);
}
@Override
- public boolean isEnchantConflict(@NotNull Map enchantments, @NotNull Material itemMat) {
- if (!definition.hasConflicts()) return false;
+ public boolean isEnchantConflict(@NotNull Map enchantments, @NotNull NamespacedKey itemType) {
+ if (!hasConflicts()) return false;
- Set conflicts = definition.getExclusiveSet();
+ Set conflicts = getExclusiveSet();
for (CAEnchantment caEnchantment : enchantments.keySet()) {
if (conflicts.contains(caEnchantment.getName())) return true;
+ if (conflicts.contains(caEnchantment.getKey().toString())) return true;
}
return false;
}
@Override
- public boolean isItemConflict(@NotNull Map enchantments, @NotNull Material itemMat, @NotNull ItemStack item) {
- if (Material.ENCHANTED_BOOK.equals(itemMat)) return false;
+ public boolean isItemConflict(@NotNull Map enchantments, @NotNull NamespacedKey itemType, @NotNull ItemStack item) {
+ if (Material.ENCHANTED_BOOK.getKey().equals(itemType)) return false;
- String key = itemMat.getKey().getKey();
+ String key = itemType.getKey();
ItemSet primary = eeenchantment.getPrimaryItems();
if (primary.getMaterials().contains(key)) return false;
@@ -52,4 +55,74 @@ public class CAEEV5Enchantment extends CABukkitEnchantment implements Additional
return true;
}
+
+ private static final Method getDefinitonMethod;
+
+ private static final Method getAnvilCostMethod;
+ private static final Method hasConflictsMethod;
+ private static final Method getExclusiveSetMethod;
+ static {
+ var enchClazz = CustomEnchantment.class;
+ try {
+ getDefinitonMethod = enchClazz.getDeclaredMethod("getDefinition");
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException(e);
+ }
+
+ Class> definitionClazz;
+ try {
+ definitionClazz = Class.forName("su.nightexpress.excellentenchants.api.EnchantDefinition");
+ } catch (ClassNotFoundException e) {
+ try {
+ definitionClazz = Class.forName("su.nightexpress.excellentenchants.api.wrapper.EnchantDefinition");
+ } catch (ClassNotFoundException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ // Now definition methods
+ try {
+ getAnvilCostMethod = definitionClazz.getDeclaredMethod("getAnvilCost");
+ hasConflictsMethod = definitionClazz.getDeclaredMethod("hasConflicts");
+ getExclusiveSetMethod = definitionClazz.getDeclaredMethod("getExclusiveSet");
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException(e);
+ }
+
+ }
+
+ private static Object getDefinition(CustomEnchantment enchantment) {
+ try {
+ return getDefinitonMethod.invoke(enchantment);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static int getAnvilCost(CustomEnchantment enchantment) {
+ try {
+ return (int) getAnvilCostMethod.invoke(getDefinition(enchantment));
+ } catch (IllegalAccessException | InvocationTargetException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private boolean hasConflicts() {
+ try {
+ return (boolean) hasConflictsMethod.invoke(definition);
+ } catch (IllegalAccessException | InvocationTargetException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+
+ private Set getExclusiveSet() {
+ try {
+ return (Set) getExclusiveSetMethod.invoke(definition);
+ } catch (IllegalAccessException | InvocationTargetException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+
}
diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEV5_4Enchantment.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEV5_4Enchantment.java
new file mode 100644
index 0000000..7fb8627
--- /dev/null
+++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEEV5_4Enchantment.java
@@ -0,0 +1,29 @@
+package xyz.alexcrea.cuanvil.enchant.wrapped;
+
+import org.bukkit.NamespacedKey;
+import org.jetbrains.annotations.NotNull;
+import su.nightexpress.excellentenchants.api.enchantment.CustomEnchantment;
+import xyz.alexcrea.cuanvil.dependency.plugins.ExcellentEnchant5_4EnchantSettings;
+import xyz.alexcrea.cuanvil.enchant.CAEnchantment;
+
+import java.util.Map;
+
+public class CAEEV5_4Enchantment extends CAEEV5Enchantment {
+
+ public CAEEV5_4Enchantment(@NotNull CustomEnchantment enchantment) {
+ super(enchantment);
+ }
+
+ @Override
+ public boolean isEnchantConflict(@NotNull Map enchantments, @NotNull NamespacedKey itemMat) {
+ if(super.isEnchantConflict(enchantments, itemMat)) return true;
+
+ var limit = ExcellentEnchant5_4EnchantSettings.anvilLimit();
+ var count = enchantments.keySet().stream()
+ .filter(key -> key instanceof CAEEV5_4Enchantment)
+ .count();
+
+ return count > limit;
+ }
+
+}
diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEcoEnchant.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEcoEnchant.java
index 6e74b73..32d1346 100644
--- a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEcoEnchant.java
+++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAEcoEnchant.java
@@ -4,6 +4,7 @@ import com.willfp.ecoenchants.enchant.EcoEnchant;
import com.willfp.ecoenchants.target.EnchantmentTarget;
import com.willfp.ecoenchants.type.EnchantmentType;
import org.bukkit.Material;
+import org.bukkit.NamespacedKey;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import xyz.alexcrea.cuanvil.enchant.AdditionalTestEnchantment;
@@ -23,9 +24,13 @@ public class CAEcoEnchant extends CABukkitEnchantment implements AdditionalTestE
}
@Override
- public boolean isEnchantConflict(@NotNull Map enchantments, @NotNull Material itemMat) {
+ public boolean isEnchantConflict(@NotNull Map enchantments, @NotNull NamespacedKey itemType) {
if (enchantments.isEmpty()) return false;
+ // Check if there is only self
+ if (enchantments.size() == 1 && this.equals(enchantments.keySet().stream().findFirst().get()))
+ return false;
+
if (this.ecoEnchant.getConflictsWithEverything()) {
return true;
}
@@ -57,9 +62,9 @@ public class CAEcoEnchant extends CABukkitEnchantment implements AdditionalTestE
@Override
public boolean isItemConflict(@NotNull Map enchantments,
- @NotNull Material itemMat,
+ @NotNull NamespacedKey itemType,
@NotNull ItemStack item) {
- if (Material.ENCHANTED_BOOK.equals(itemMat)) {
+ if (Material.ENCHANTED_BOOK.getKey().equals(itemType)) {
return false;
}
diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAIncompatibleAllEnchant.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAIncompatibleAllEnchant.java
index 218ce87..552ecd4 100644
--- a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAIncompatibleAllEnchant.java
+++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CAIncompatibleAllEnchant.java
@@ -1,6 +1,7 @@
package xyz.alexcrea.cuanvil.enchant.wrapped;
import org.bukkit.Material;
+import org.bukkit.NamespacedKey;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
@@ -24,12 +25,12 @@ public class CAIncompatibleAllEnchant extends CABukkitEnchantment implements Add
@Override
- public boolean isEnchantConflict(@NotNull Map enchantments, @NotNull Material itemMat) {
+ public boolean isEnchantConflict(@NotNull Map enchantments, @NotNull NamespacedKey itemType) {
return !enchantments.isEmpty() && !(enchantments.size() == 1 && enchantments.containsKey(this));
}
@Override
- public boolean isItemConflict(@NotNull Map enchantments, @NotNull Material itemMat, @NotNull ItemStack item) {
+ public boolean isItemConflict(@NotNull Map enchantments, @NotNull NamespacedKey itemType, @NotNull ItemStack item) {
return false;
}
}
diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CALegacyEEEnchantment.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CALegacyEEEnchantment.java
index 191f8f3..74068d4 100644
--- a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CALegacyEEEnchantment.java
+++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CALegacyEEEnchantment.java
@@ -1,6 +1,7 @@
package xyz.alexcrea.cuanvil.enchant.wrapped;
import org.bukkit.Material;
+import org.bukkit.NamespacedKey;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import su.nightexpress.excellentenchants.api.enchantment.EnchantmentData;
@@ -22,7 +23,7 @@ public class CALegacyEEEnchantment extends CABukkitEnchantment implements Additi
}
@Override
- public boolean isEnchantConflict(@NotNull Map enchantments, @NotNull Material itemMat) {
+ public boolean isEnchantConflict(@NotNull Map enchantments, @NotNull NamespacedKey itemType) {
if (!eeenchantment.hasConflicts()) return false;
Set conflicts = eeenchantment.getConflicts();
@@ -35,8 +36,8 @@ public class CALegacyEEEnchantment extends CABukkitEnchantment implements Additi
}
@Override
- public boolean isItemConflict(@NotNull Map enchantments, @NotNull Material itemMat, @NotNull ItemStack item) {
- if (Material.ENCHANTED_BOOK.equals(itemMat)) return false;
+ public boolean isItemConflict(@NotNull Map enchantments, @NotNull NamespacedKey itemType, @NotNull ItemStack item) {
+ if (Material.ENCHANTED_BOOK.getKey().equals(itemType)) return false;
return !eeenchantment.getSupportedItems().is(item);
}
diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CALegacyEcoEnchant.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CALegacyEcoEnchant.java
index 3b4242d..cb24def 100644
--- a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CALegacyEcoEnchant.java
+++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CALegacyEcoEnchant.java
@@ -4,12 +4,14 @@ import com.willfp.ecoenchants.enchantments.EcoEnchant;
import com.willfp.ecoenchants.enchantments.meta.EnchantmentTarget;
import com.willfp.ecoenchants.enchantments.meta.EnchantmentType;
import org.bukkit.Material;
+import org.bukkit.NamespacedKey;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import xyz.alexcrea.cuanvil.enchant.AdditionalTestEnchantment;
import xyz.alexcrea.cuanvil.enchant.CAEnchantment;
import xyz.alexcrea.cuanvil.enchant.EnchantmentRarity;
+import xyz.alexcrea.cuanvil.util.MaterialUtil;
import java.util.Map;
@@ -23,7 +25,7 @@ public class CALegacyEcoEnchant extends CABukkitEnchantment implements Additiona
}
@Override
- public boolean isEnchantConflict(@NotNull Map enchantments, @NotNull Material itemMat) {
+ public boolean isEnchantConflict(@NotNull Map enchantments, @NotNull NamespacedKey itemType) {
if (enchantments.isEmpty()) return false;
EnchantmentType type = this.ecoEnchant.getType();
@@ -48,14 +50,15 @@ public class CALegacyEcoEnchant extends CABukkitEnchantment implements Additiona
@Override
public boolean isItemConflict(@NotNull Map enchantments,
- @NotNull Material itemMat,
+ @NotNull NamespacedKey itemType,
@NotNull ItemStack item) {
- if (Material.ENCHANTED_BOOK.equals(itemMat)) {
+ if (Material.ENCHANTED_BOOK.getKey().equals(itemType)) {
return false;
}
+ var mat = MaterialUtil.INSTANCE.getMatFromKey(itemType);
for (EnchantmentTarget target : this.ecoEnchant.getTargets()) {
- if (target.getMaterials().contains(itemMat)) {
+ if (target.getMaterials().contains(mat)) {
return false;
}
}
diff --git a/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CASuperEnchantEnchantment.java b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CASuperEnchantEnchantment.java
new file mode 100644
index 0000000..6039dc8
--- /dev/null
+++ b/src/main/java/xyz/alexcrea/cuanvil/enchant/wrapped/CASuperEnchantEnchantment.java
@@ -0,0 +1,76 @@
+package xyz.alexcrea.cuanvil.enchant.wrapped;
+
+import com.maddoxh.superEnchants.enchants.CustomEnchant;
+import com.maddoxh.superEnchants.enchants.EnchantManager;
+import com.maddoxh.superEnchants.items.EnchantApplicator;
+import com.maddoxh.superEnchants.items.EnchantReader;
+import com.maddoxh.superEnchants.util.ConflictChecker;
+import org.bukkit.Material;
+import org.bukkit.NamespacedKey;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+import org.bukkit.plugin.Plugin;
+import org.jetbrains.annotations.NotNull;
+import xyz.alexcrea.cuanvil.enchant.AdditionalTestEnchantment;
+import xyz.alexcrea.cuanvil.enchant.CAEnchantment;
+import xyz.alexcrea.cuanvil.enchant.CAEnchantmentBase;
+import xyz.alexcrea.cuanvil.enchant.EnchantmentRarity;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class CASuperEnchantEnchantment extends CAEnchantmentBase implements AdditionalTestEnchantment {
+
+ private @NotNull CustomEnchant enchant;
+ private @NotNull EnchantManager enchantManager;
+
+ public CASuperEnchantEnchantment(@NotNull CustomEnchant enchant, @NotNull Plugin plugin, @NotNull EnchantManager enchantManager) {
+ super(NamespacedKey.fromString(enchant.getId(), plugin), EnchantmentRarity.COMMON, enchant.getMaxLevel());
+
+ this.enchant = enchant;
+ this.enchantManager = enchantManager;
+ }
+
+ @Override
+ public int getLevel(@NotNull ItemStack item, @NotNull ItemMeta meta) {
+ return EnchantReader.INSTANCE.getEnchantLevel(item, enchant.getId());
+ }
+
+ @Override
+ public boolean isEnchantmentPresent(@NotNull ItemStack item, @NotNull ItemMeta meta) {
+ return EnchantReader.INSTANCE.hasEnchant(item, enchant.getId());
+ }
+
+ @Override
+ public void addEnchantmentUnsafe(@NotNull ItemStack item, int level) {
+ EnchantApplicator.INSTANCE.applyEnchant(item, enchant.getId(), level);
+ }
+
+ @Override
+ public void removeFrom(@NotNull ItemStack item) {
+ EnchantApplicator.INSTANCE.removeEnchant(item, enchant.getId());
+ }
+
+ @Override
+ public boolean isEnchantConflict(@NotNull Map enchantments, @NotNull NamespacedKey itemType) {
+ var idMap = new HashMap();
+
+ enchantments.forEach((enchant, level) -> {
+ if(!(enchant instanceof CASuperEnchantEnchantment superEnch)) return;
+ idMap.put(superEnch.enchant.getId(), level);
+ });
+
+ return ConflictChecker.INSTANCE.hasConflict(
+ idMap,
+ enchant.getId(),
+ enchantManager
+ ) != null;
+ }
+
+ @Override
+ public boolean isItemConflict(@NotNull Map enchantments, @NotNull NamespacedKey itemType, @NotNull ItemStack item) {
+ if(Material.ENCHANTED_BOOK.equals(item.getType())) return false;
+
+ return !enchant.canApplyTo(item.getType());
+ }
+}
diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/SelectMaterialContainer.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/SelectMaterialContainer.java
index 2f76694..3756341 100644
--- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/SelectMaterialContainer.java
+++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/SelectMaterialContainer.java
@@ -1,34 +1,35 @@
package xyz.alexcrea.cuanvil.gui.config;
import org.bukkit.Material;
+import org.bukkit.NamespacedKey;
import xyz.alexcrea.cuanvil.util.CasedStringUtil;
import java.util.*;
public interface SelectMaterialContainer {
- EnumSet getSelectedMaterials();
+ Set getSelectedMaterials();
- boolean setSelectedMaterials(EnumSet materials);
+ boolean setSelectedMaterials(Set materials);
- EnumSet illegalMaterials();
+ Set illegalMaterials();
static List getMaterialLore(SelectMaterialContainer container, String containerType, String action){
// Prepare material lore
ArrayList groupLore = new ArrayList<>();
groupLore.add("§7Allow you to select a list of §ematerials §7that this " + containerType + " should " + action);
- Set materialSet = container.getSelectedMaterials();
+ Set materialSet = container.getSelectedMaterials();
if (materialSet.isEmpty()) {
groupLore.add("§7There is no "+action+"d material for this "+containerType+".");
} else {
groupLore.add("§7List of "+action+"d materials for this "+containerType+":");
- Iterator materialIterator = materialSet.iterator();
+ Iterator materialIterator = materialSet.iterator();
boolean greaterThanMax = materialSet.size() > 5;
int maxindex = (greaterThanMax ? 4 : materialSet.size());
for (int i = 0; i < maxindex; i++) {
// format string like "- Stone Sword"
- String formattedName = CasedStringUtil.snakeToUpperSpacedCase(materialIterator.next().name().toLowerCase());
+ String formattedName = CasedStringUtil.snakeToUpperSpacedCase(materialIterator.next().getKey().toLowerCase());
groupLore.add("§7- §e" + formattedName);
}
diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/ask/ConfirmActionGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/ask/ConfirmActionGui.java
index 56bf848..5839663 100644
--- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/ask/ConfirmActionGui.java
+++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/ask/ConfirmActionGui.java
@@ -11,6 +11,7 @@ import org.bukkit.inventory.meta.ItemMeta;
import org.jetbrains.annotations.NotNull;
import xyz.alexcrea.cuanvil.gui.util.GuiGlobalActions;
import xyz.alexcrea.cuanvil.gui.util.GuiSharedConstant;
+import xyz.alexcrea.cuanvil.util.MetricsUtil;
import java.util.Arrays;
import java.util.function.Supplier;
@@ -41,6 +42,7 @@ public class ConfirmActionGui extends AbstractAskGui {
success = onConfirm.get();
} catch (Exception e) {
CustomAnvil.instance.getLogger().log(Level.WARNING, "Could not process confirmation supplier.", e);
+ MetricsUtil.INSTANCE.trackError(e);
success = false;
}
diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/ask/SelectItemTypeGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/ask/SelectItemTypeGui.java
index b2d6afe..66411bd 100644
--- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/ask/SelectItemTypeGui.java
+++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/ask/SelectItemTypeGui.java
@@ -12,6 +12,7 @@ import org.jetbrains.annotations.NotNull;
import xyz.alexcrea.cuanvil.gui.util.GuiGlobalActions;
import xyz.alexcrea.cuanvil.gui.util.GuiGlobalItems;
import xyz.alexcrea.cuanvil.gui.util.GuiSharedConstant;
+import xyz.alexcrea.cuanvil.util.MaterialUtil;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicReference;
@@ -52,7 +53,7 @@ public class SelectItemTypeGui extends AbstractAskGui {
event.setCancelled(true);
ItemStack cursor = event.getWhoClicked().getItemOnCursor();
- if(cursor.getType().isAir()) return;
+ if(MaterialUtil.INSTANCE.isAir(cursor)) return;
ItemStack finalItem;
if(materialOnly){
diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/BasicConfigGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/BasicConfigGui.java
index a07cb3c..51936c7 100644
--- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/BasicConfigGui.java
+++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/BasicConfigGui.java
@@ -14,6 +14,7 @@ import org.bukkit.inventory.meta.ItemMeta;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import xyz.alexcrea.cuanvil.config.ConfigHolder;
+import xyz.alexcrea.cuanvil.dependency.MinecraftVersionUtil;
import xyz.alexcrea.cuanvil.dependency.packet.PacketManager;
import xyz.alexcrea.cuanvil.gui.ValueUpdatableGui;
import xyz.alexcrea.cuanvil.gui.config.MainConfigGui;
@@ -283,7 +284,7 @@ public class BasicConfigGui extends ChestGui implements ValueUpdatableGui {
if(!this.packetManager.getCanSetInstantBuild()){
lore.add("");
- lore.add("§4/!\\§cCaution§4/!\\ §cYou need ProtocoLib installed and working or a newer version of this plugin for this to work.");
+ lore.add("§4/!\\§cCaution§4/!\\ §cYou need ProtocoLib installed and working or a paper server.");
lore.add("§cCurrently ProtocoLib is not detected.");
}
diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/EnchantLimitConfigGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/EnchantLimitConfigGui.java
index d624bff..e9edbeb 100644
--- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/EnchantLimitConfigGui.java
+++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/global/EnchantLimitConfigGui.java
@@ -17,7 +17,7 @@ import java.util.Locale;
*/
public class EnchantLimitConfigGui extends AbstractEnchantConfigGui {
- private static final String SECTION_NAME = "enchant_limits";
+ private static final String SECTION_NAME = ConfigOptions.ENCHANT_LIMIT_ROOT;
private static EnchantLimitConfigGui INSTANCE = null;
@@ -41,18 +41,34 @@ public class EnchantLimitConfigGui extends AbstractEnchantConfigGui "Default (" + defaultValue + ")";
+ case RESET -> String.valueOf(defaultValue);
+ default -> "Default";
+ };
+
+ }
+ else return super.valueDisplayName(type, value);
}
};
}
diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/elements/EnchantConflictSubSettingGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/elements/EnchantConflictSubSettingGui.java
index 99bba84..97bdfcb 100644
--- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/elements/EnchantConflictSubSettingGui.java
+++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/elements/EnchantConflictSubSettingGui.java
@@ -25,6 +25,7 @@ import xyz.alexcrea.cuanvil.gui.util.GuiGlobalActions;
import xyz.alexcrea.cuanvil.gui.util.GuiGlobalItems;
import xyz.alexcrea.cuanvil.gui.util.GuiSharedConstant;
import xyz.alexcrea.cuanvil.util.CasedStringUtil;
+import xyz.alexcrea.cuanvil.util.MetricsUtil;
import java.util.*;
import java.util.function.Supplier;
@@ -264,6 +265,7 @@ public class EnchantConflictSubSettingGui extends MappedToListSubSettingGui impl
updateGuiValues();
} catch (Exception e) {
CustomAnvil.instance.getLogger().log(Level.WARNING, "An error occurred while updating enchants for " + this.enchantConflict, e);
+ MetricsUtil.INSTANCE.trackError(e);
}
// Save file configuration to disk
@@ -308,6 +310,7 @@ public class EnchantConflictSubSettingGui extends MappedToListSubSettingGui impl
updateGuiValues();
} catch (Exception e) {
CustomAnvil.instance.getLogger().log(Level.WARNING, "An error occurred while updating group for " + this.enchantConflict, e);
+ MetricsUtil.INSTANCE.trackError(e);
}
// Save file configuration to disk
diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/elements/GroupConfigSubSettingGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/elements/GroupConfigSubSettingGui.java
index c49fec7..3d05674 100644
--- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/elements/GroupConfigSubSettingGui.java
+++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/list/elements/GroupConfigSubSettingGui.java
@@ -5,6 +5,7 @@ import com.github.stefvanschie.inventoryframework.pane.PatternPane;
import com.github.stefvanschie.inventoryframework.pane.util.Pattern;
import io.delilaheve.CustomAnvil;
import org.bukkit.Material;
+import org.bukkit.NamespacedKey;
import org.bukkit.entity.HumanEntity;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.ItemFlag;
@@ -325,19 +326,19 @@ public class GroupConfigSubSettingGui extends MappedToListSubSettingGui implemen
// ----------------------------
@Override
- public EnumSet getSelectedMaterials() {
+ public Set getSelectedMaterials() {
return this.group.getNonGroupInheritedMaterials();
}
@Override
- public boolean setSelectedMaterials(EnumSet materials) {
+ public boolean setSelectedMaterials(Set materials) {
this.group.setNonGroupInheritedMaterials(materials);
// Write to file configuration
String[] groupNames = new String[materials.size()];
int index = 0;
- for (Material otherGroup : materials) {
- groupNames[index++] = otherGroup.name().toLowerCase();
+ for (NamespacedKey otherGroup : materials) {
+ groupNames[index++] = otherGroup.getKey().toLowerCase();
}
ConfigHolder.ITEM_GROUP_HOLDER.getConfig().set(this.group.getName()+"."+ItemGroupManager.MATERIAL_LIST_PATH, groupNames);
@@ -353,8 +354,8 @@ public class GroupConfigSubSettingGui extends MappedToListSubSettingGui implemen
}
@Override
- public EnumSet illegalMaterials() {
- return EnumSet.of(Material.AIR);
+ public Set illegalMaterials() {
+ return Set.of(Material.AIR.getKey());
}
// ----------------------------
diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/IntSettingsGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/IntSettingsGui.java
index af977e9..73121a6 100644
--- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/IntSettingsGui.java
+++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/IntSettingsGui.java
@@ -72,7 +72,8 @@ public class IntSettingsGui extends AbstractSettingGui {
assert meta != null;
meta.setDisplayName("§eReset to default value");
- meta.setLore(Collections.singletonList("§7Default value is §e" + holder.defaultVal));
+ meta.setLore(Collections.singletonList("§7Default value is §e" +
+ holder.valueDisplayName(ValueDisplayType.RESET, holder.defaultVal)));
item.setItemMeta(meta);
returnToDefault = new GuiItem(item, event -> {
event.setCancelled(true);
@@ -86,41 +87,23 @@ public class IntSettingsGui extends AbstractSettingGui {
* Update item using the setting value to match the new value.
*/
protected void updateValueDisplay() {
-
PatternPane pane = getPane();
// minus item
GuiItem minusItem;
if (now > holder.min) {
int planned = Math.max(holder.min, now - step);
- ItemStack item = new ItemStack(Material.RED_TERRACOTTA);
- ItemMeta meta = item.getItemMeta();
- assert meta != null;
-
- meta.setDisplayName("§e" + now + " §f-> §e" + planned + " §r(§c-" + (now - planned) + "§r)");
- meta.setLore(Collections.singletonList(AbstractSettingGui.CLICK_LORE));
- item.setItemMeta(meta);
-
- minusItem = new GuiItem(item, updateNowConsumer(planned), CustomAnvil.instance);
+ minusItem = valueEditItem(Material.RED_TERRACOTTA, ValueDisplayType.REMOVE, planned);
} else {
minusItem = GuiGlobalItems.backgroundItem(Material.BARRIER);
}
pane.bindItem('-', minusItem);
//plus item
- // may do a function to generalise ?
GuiItem plusItem;
if (now < holder.max) {
int planned = Math.min(holder.max, now + step);
- ItemStack item = new ItemStack(Material.GREEN_TERRACOTTA);
- ItemMeta meta = item.getItemMeta();
- assert meta != null;
-
- meta.setDisplayName("§e" + now + " §f-> §e" + planned + " §r(§a+" + (planned - now) + "§r)");
- meta.setLore(Collections.singletonList(AbstractSettingGui.CLICK_LORE));
- item.setItemMeta(meta);
-
- plusItem = new GuiItem(item, updateNowConsumer(planned), CustomAnvil.instance);
+ plusItem = valueEditItem(Material.GREEN_TERRACOTTA, ValueDisplayType.ADD, planned);
} else {
plusItem = GuiGlobalItems.backgroundItem(Material.BARRIER);
}
@@ -131,7 +114,7 @@ public class IntSettingsGui extends AbstractSettingGui {
ItemMeta resultMeta = resultPaper.getItemMeta();
assert resultMeta != null;
- resultMeta.setDisplayName("§fValue: §e" + now);
+ resultMeta.setDisplayName("§fValue: §e" + holder.valueDisplayName(ValueDisplayType.CURRENT, now));
resultMeta.setLore(holder.displayLore);
resultPaper.setItemMeta(resultMeta);
@@ -149,7 +132,21 @@ public class IntSettingsGui extends AbstractSettingGui {
}
pane.bindItem('D', returnToDefault);
+ }
+ private GuiItem valueEditItem(Material mat, ValueDisplayType type, int planned) {
+ ItemStack item = new ItemStack(mat);
+ ItemMeta meta = item.getItemMeta();
+ assert meta != null;
+
+ var nowDisplay = holder.valueDisplayName(type, now);
+ var plannedDisplay = holder.valueDisplayName(type, planned);
+ var deltaDisplay = holder.deltaDisplay(type, now, planned);
+ meta.setDisplayName("§e" + nowDisplay + " §f-> §e" + plannedDisplay + " §r(§c" + deltaDisplay + "§r)");
+
+ meta.setLore(Collections.singletonList(AbstractSettingGui.CLICK_LORE));
+ item.setItemMeta(meta);
+ return new GuiItem(item, updateNowConsumer(planned), CustomAnvil.instance);
}
/**
@@ -389,6 +386,23 @@ public class IntSettingsGui extends AbstractSettingGui {
return getItem(itemMat, CasedStringUtil.detectToUpperSpacedCase(configPath));
}
+ protected String valueDisplayName(ValueDisplayType type, int value) {
+ return String.valueOf(value);
+ }
+
+ protected String deltaDisplay(ValueDisplayType type, int now, int planned) {
+ var delta = planned - now;
+ if(delta < 0) return "§c" + delta;
+ else return "§a+" + delta;
+ }
+
+ }
+
+ public enum ValueDisplayType {
+ ADD,
+ CURRENT,
+ REMOVE,
+ RESET,
}
}
diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/MaterialSelectSettingGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/MaterialSelectSettingGui.java
index a3963ce..fc519ff 100644
--- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/MaterialSelectSettingGui.java
+++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/MaterialSelectSettingGui.java
@@ -5,6 +5,7 @@ import com.github.stefvanschie.inventoryframework.gui.type.util.Gui;
import com.github.stefvanschie.inventoryframework.pane.util.Pattern;
import io.delilaheve.CustomAnvil;
import org.bukkit.Material;
+import org.bukkit.NamespacedKey;
import org.bukkit.entity.HumanEntity;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.ItemFlag;
@@ -18,18 +19,19 @@ import xyz.alexcrea.cuanvil.gui.util.GuiGlobalActions;
import xyz.alexcrea.cuanvil.gui.util.GuiGlobalItems;
import xyz.alexcrea.cuanvil.gui.util.GuiSharedConstant;
import xyz.alexcrea.cuanvil.util.CasedStringUtil;
+import xyz.alexcrea.cuanvil.util.MaterialUtil;
import java.util.*;
import java.util.function.Consumer;
-public class MaterialSelectSettingGui extends MappedElementListConfigGui {
+public class MaterialSelectSettingGui extends MappedElementListConfigGui {
private final SelectMaterialContainer selector;
private final Gui backGui;
private boolean instantRemove;
- private final List defaultMaterials;
- private final EnumSet illegalMaterials;
+ private final List defaultMaterials;
+ private final Set illegalMaterials;
private final int defaultMaterialHash;
private int nowMaterialHash;
@@ -161,8 +163,7 @@ public class MaterialSelectSettingGui extends MappedElementListConfigGui result = EnumSet.noneOf(Material.class);
- result.addAll(this.elementGuiMap.keySet());
+ Set result = new HashSet<>(this.elementGuiMap.keySet());
if(!this.selector.setSelectedMaterials(result)){
player.sendMessage("§cSomething went wrong while saving the change of value.");
@@ -185,8 +186,8 @@ public class MaterialSelectSettingGui extends MappedElementListConfigGui getEveryDisplayableInstanceOfGeneric() {
+ protected Collection getEveryDisplayableInstanceOfGeneric() {
return this.defaultMaterials;
}
@Override
- protected void updateElement(Material material, GuiItem element) {
+ protected void updateElement(NamespacedKey material, GuiItem element) {
// Nothing happen here I think
}
@Override
- protected GuiItem newElementRequested(Material material, GuiItem newItem) {
+ protected GuiItem newElementRequested(NamespacedKey material, GuiItem newItem) {
newItem.setAction(event -> {
if(this.instantRemove){
removeMaterial(material);
}else {
- String materialName = CasedStringUtil.snakeToUpperSpacedCase(material.name().toLowerCase());
+ String materialName = CasedStringUtil.snakeToUpperSpacedCase(material.getKey().toLowerCase());
// Create and show confirm remove gui.
ConfirmActionGui confirmGui = new ConfirmActionGui(
@@ -250,7 +251,7 @@ public class MaterialSelectSettingGui extends MappedElementListConfigGui materialList){
+ private static int hashFromMaterialList(List materialList){
int defaultMaterialHash = 0;
- for (Material material : materialList) {
+ for (NamespacedKey material : materialList) {
defaultMaterialHash ^= material.hashCode();
}
return defaultMaterialHash;
diff --git a/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/WorkPenaltyTypeSettingGui.java b/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/WorkPenaltyTypeSettingGui.java
index 888aa25..4345aa1 100644
--- a/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/WorkPenaltyTypeSettingGui.java
+++ b/src/main/java/xyz/alexcrea/cuanvil/gui/config/settings/WorkPenaltyTypeSettingGui.java
@@ -11,11 +11,11 @@ import org.bukkit.entity.HumanEntity;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.jetbrains.annotations.NotNull;
+import xyz.alexcrea.cuanvil.anvil.AnvilUseType;
import xyz.alexcrea.cuanvil.config.ConfigHolder;
import xyz.alexcrea.cuanvil.config.WorkPenaltyType;
import xyz.alexcrea.cuanvil.gui.config.global.BasicConfigGui;
import xyz.alexcrea.cuanvil.gui.util.GuiGlobalActions;
-import xyz.alexcrea.cuanvil.util.AnvilUseType;
import java.util.ArrayList;
import java.util.EnumMap;
diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/ModrinthUpdateChecker.java b/src/main/java/xyz/alexcrea/cuanvil/update/ModrinthUpdateChecker.java
new file mode 100644
index 0000000..489c636
--- /dev/null
+++ b/src/main/java/xyz/alexcrea/cuanvil/update/ModrinthUpdateChecker.java
@@ -0,0 +1,214 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2025 Clickism
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+ * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ * OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+package xyz.alexcrea.cuanvil.update;
+
+import com.google.gson.*;
+import org.jetbrains.annotations.Nullable;
+
+import java.net.URI;
+import java.net.http.HttpClient;
+import java.net.http.HttpRequest;
+import java.net.http.HttpResponse;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/**
+ * Utility class to check for newer versions of a project hosted on Modrinth.
+ */
+public class ModrinthUpdateChecker {
+
+ private static final String API_URL = "https://api.modrinth.com/v2/project/{id}/version";
+
+ private final String projectId;
+ private final String loader;
+ @Nullable
+ private final String minecraftVersion;
+
+ @Nullable
+ private Boolean featured = null;
+
+ @Nullable
+ public Consumer onError = null;
+ @Nullable
+ public Function getRawVersion = ModrinthUpdateChecker::getRawVersion;
+
+ /**
+ * Create a new update checker for the given project.
+ * This will check the latest version for the given loader and any minecraft version.
+ *
+ * @param projectId the project ID
+ * @param loader the loader
+ */
+ public ModrinthUpdateChecker(String projectId, String loader) {
+ this(projectId, loader, null);
+ }
+
+ /**
+ * Create a new update checker for the given project.
+ * This will check the latest version for the given loader and minecraft version.
+ *
+ * @param projectId the project ID
+ * @param loader the loader
+ * @param minecraftVersion the minecraft version, or null for any version
+ */
+ public ModrinthUpdateChecker(String projectId, String loader, @Nullable String minecraftVersion) {
+ this.projectId = projectId;
+ this.loader = loader;
+ this.minecraftVersion = minecraftVersion;
+ }
+
+ /**
+ * Check the latest version of the project for the given loader and minecraft version
+ * and call the consumer with it.
+ *
+ * @param consumer the consumer
+ */
+ public void checkVersion(Consumer consumer) {
+ try {
+ HttpClient client = HttpClient.newHttpClient();
+ HttpRequest request = HttpRequest.newBuilder()
+ .uri(prepareURI())
+ .GET()
+ .build();
+
+ client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
+ .thenAcceptAsync(response -> {
+ if (response.statusCode() != 200) {
+ if(onError != null)
+ onError.accept(new RuntimeException("wrong response status code: " + response.statusCode()));
+ return;
+ }
+ JsonArray versionsArray = JsonParser.parseString(response.body()).getAsJsonArray();
+ String latestVersion = getLatestVersion(versionsArray);
+ if (latestVersion == null) {
+ if(onError != null)
+ onError.accept(new RuntimeException("latest version is null"));
+ return;
+ }
+ consumer.accept(latestVersion);
+ });
+ } catch (Exception e) {
+ if(onError != null) onError.accept(e);
+ }
+ }
+
+ /**
+ * Get the latest compatible version from the versions array.
+ *
+ * @param versions the versions array
+ * @return the latest compatible version
+ */
+ @Nullable
+ protected String getLatestVersion(JsonArray versions) {
+ return versions.asList().stream().findFirst()
+ .map(JsonElement::getAsJsonObject)
+ .map(version -> version.get("version_number").getAsString())
+ .map(getRawVersion != null ? getRawVersion : (v -> v))
+ .orElse(null);
+ }
+
+ /**
+ * Gets the raw version from a version string.
+ * i.E: "fabric-1.2+1.17.1" -> "1.2"
+ *
+ * @param version the version string
+ * @return the raw version string
+ */
+ public static String getRawVersion(String version) {
+ if (version.isEmpty()) return version;
+ version = version.replaceAll("^\\D+", "");
+ String[] split = version.split("\\+");
+ return split[0];
+ }
+
+ /**
+ * Prepare this request uri based on current parameters.
+ * @return the request uri
+ */
+ private URI prepareURI() {
+ var url = new StringBuilder(API_URL.replace("{id}", projectId));
+
+ var parameters = prepareParameters();
+ String[] paramArray = new String[parameters.size()];
+ int i = 0;
+ for (Map.Entry entry : parameters.entrySet()) {
+ paramArray[i++] = entry.getKey() + '=' + entry.getValue();
+ }
+ url.append('?').append(String.join("&", paramArray));
+
+ return URI.create(url.toString());
+ }
+
+ /**
+ * Get the parameters for the version request.
+ *
+ * @return a map of key-value map of the request parameters
+ */
+ private Map prepareParameters(){
+ var parameters = new HashMap();
+
+ parameters.put("loaders", List.of(loader).toString());
+ if(minecraftVersion != null) parameters.put("game_versions", List.of(minecraftVersion).toString());
+ if(featured != null) parameters.put("featured", featured.toString());
+
+ parameters.put("include_changelog", "false");
+ return parameters;
+ }
+
+ /**
+ * Only get featured or non-featured versions.
+ * Null represent no filter.
+ * @param featured should be restricted to featured version ? default null if not called
+ * @return this
+ */
+ public ModrinthUpdateChecker setFeatured(@Nullable Boolean featured) {
+ this.featured = featured;
+ return this;
+ }
+
+ /**
+ * Function called on error calling the api.
+ * @param onError What should happen on error
+ * @return this
+ */
+ public ModrinthUpdateChecker setOnError(@Nullable Consumer onError) {
+ this.onError = onError;
+ return this;
+ }
+
+ /**
+ * Set the function to get raw version from the modrinth version.
+ * If null provided raw version will act as in the identity function.
+ * @param getRawVersion The function transforming modrinth version to raw version
+ * @return this
+ */
+ public ModrinthUpdateChecker setGetRawVersion(@Nullable Function getRawVersion) {
+ this.getRawVersion = getRawVersion;
+ return this;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/PluginSetDefault.java b/src/main/java/xyz/alexcrea/cuanvil/update/PluginSetDefault.java
index 363bd6a..707a218 100644
--- a/src/main/java/xyz/alexcrea/cuanvil/update/PluginSetDefault.java
+++ b/src/main/java/xyz/alexcrea/cuanvil/update/PluginSetDefault.java
@@ -5,6 +5,7 @@ import io.delilaheve.util.ConfigOptions;
import org.bukkit.configuration.file.FileConfiguration;
import org.jetbrains.annotations.NotNull;
import xyz.alexcrea.cuanvil.config.ConfigHolder;
+import xyz.alexcrea.cuanvil.util.MetricType;
import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil;
import xyz.alexcrea.cuanvil.util.config.LoreEditType;
@@ -18,6 +19,9 @@ public class PluginSetDefault {
int nbSet = 0;
+ nbSet += trySetDefault(config, METRIC_TYPE, MetricType.AUTO.getValue());
+ nbSet += trySetDefault(config, METRIC_COLLECT_ERROR, true);
+
nbSet += trySetDefault(config, CAP_ANVIL_COST, DEFAULT_CAP_ANVIL_COST);
nbSet += trySetDefault(config, MAX_ANVIL_COST, DEFAULT_MAX_ANVIL_COST);
nbSet += trySetDefault(config, REMOVE_ANVIL_COST_LIMIT, DEFAULT_REMOVE_ANVIL_COST_LIMIT);
@@ -30,7 +34,7 @@ public class PluginSetDefault {
nbSet += trySetDefault(config, ALLOW_HEXADECIMAL_COLOR, DEFAULT_ALLOW_HEXADECIMAL_COLOR);
nbSet += trySetDefault(config, PERMISSION_NEEDED_FOR_COLOR, DEFAULT_PERMISSION_NEEDED_FOR_COLOR);
nbSet += trySetDefault(config, USE_OF_COLOR_COST, DEFAULT_USE_OF_COLOR_COST);
- nbSet += trySetDefault(config, DEFAULT_LIMIT_PATH, DEFAULT_ENCHANT_LIMIT);
+ nbSet += trySetDefault(config, PER_COLOR_CODE_PERMISSION, DEFAULT_PER_COLOR_CODE_PERMISSION);
// Lore Edit defaults
for (@NotNull LoreEditType value : LoreEditType.values()) {
@@ -48,7 +52,6 @@ public class PluginSetDefault {
nbSet += trySetDefault(config, path + ALLOW_HEX_COLOR, DEFAULT_ALLOW_HEX_COLOR);
nbSet += trySetDefault(config, path + USE_COLOR_COST, DEFAULT_USE_COLOR_COST);
} else {
- nbSet += trySetDefault(config, path + REMOVE_COLOR_ON_LORE_REMOVE, DEFAULT_REMOVE_COLOR_ON_LORE_REMOVE);
nbSet += trySetDefault(config, path + REMOVE_COLOR_COST, DEFAULT_REMOVE_COLOR_COST);
}
}
@@ -58,6 +61,11 @@ public class PluginSetDefault {
nbSet += trySetDefault(config, PAPER_EDIT_ORDER, DEFAULT_PAPER_EDIT_ORDER);
+ nbSet += trySetDefault(config, DIALOG_RENAME_ENABLED, DEFAULT_DIALOG_RENAME_ENABLED);
+ nbSet += trySetDefault(config, DIALOG_MAX_SIZE, DEFAULT_DIALOG_MAX_SIZE);
+ nbSet += trySetDefault(config, DIALOG_RENAME_USE_PERMISSION, DEFAULT_DIALOG_RENAME_USE_PERMISSION);
+ nbSet += trySetDefault(config, DIALOG_KEEP_USER_TEXT, DEFAULT_DIALOG_KEEP_USER_TEXT);
+
if (nbSet > 0) {
CustomAnvil.instance.getLogger().info("Adding " + nbSet + " absent default config values.");
ConfigHolder.DEFAULT_CONFIG.saveToDisk(true);
diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/UpdateHandler.java b/src/main/java/xyz/alexcrea/cuanvil/update/UpdateHandler.java
new file mode 100644
index 0000000..660accb
--- /dev/null
+++ b/src/main/java/xyz/alexcrea/cuanvil/update/UpdateHandler.java
@@ -0,0 +1,105 @@
+package xyz.alexcrea.cuanvil.update;
+
+import io.delilaheve.CustomAnvil;
+import xyz.alexcrea.cuanvil.config.ConfigHolder;
+import xyz.alexcrea.cuanvil.update.minecraft.MCUpdate;
+import xyz.alexcrea.cuanvil.update.minecraft.Update_1_21;
+import xyz.alexcrea.cuanvil.update.minecraft.Update_1_21_11;
+import xyz.alexcrea.cuanvil.update.minecraft.Update_1_21_9;
+import xyz.alexcrea.cuanvil.update.plugin.*;
+
+import javax.annotation.Nonnull;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+
+public class UpdateHandler {
+
+ private static final String CONFIG_VERSION_PATH = "configVersion";
+
+ // Handle mc version update then plugin version update
+ public static void handleUpdates() {
+ handleMCVersionUpdate();
+ handlePluginUpdate();
+ }
+
+ private static final Map>> pUpdateMap = Map.of(
+ new Version(1, 6, 2), PUpdate_1_6_2::handleUpdate,
+ new Version(1, 6, 7), PUpdate_1_6_7::handleUpdate,
+ new Version(1, 8, 0), PUpdate_1_8_0::handleUpdate,
+ new Version(1, 11, 0), PUpdate_1_11_0::handleUpdate,
+ new Version(1, 15, 5), PUpdate_1_15_5::handleUpdate,
+ new Version(1, 15, 6), PUpdate_1_15_6::handleUpdate
+ );
+
+ private static final List mcUpdateMap = List.of(
+ new Update_1_21(),
+ new Update_1_21_9(),
+ new Update_1_21_11()
+ );
+
+ // Handle only plugin update
+ private static void handlePluginUpdate() {
+ String versionString = ConfigHolder.DEFAULT_CONFIG.getConfig().getString(CONFIG_VERSION_PATH);
+ Version current = versionString == null ? new Version(0) : Version.fromString(versionString);
+
+ Set toSave = new HashSet<>();
+
+ AtomicReference latest = new AtomicReference<>(null);
+
+ // Hopefully, should iterate in the "insertion" order
+ pUpdateMap.forEach((ver, consumer) -> {
+ if (ver.greaterThan(current)) {
+ CustomAnvil.log("handling plugin update to " + ver);
+ consumer.accept(toSave);
+
+ latest.set(ver);
+ }
+ });
+
+ if (latest.get() != null) {
+ finishConfiguration(latest.get().toString(), toSave);
+ }
+ }
+
+ // Handle minecraft version update (not plugin version update)
+ public static void handleMCVersionUpdate() {
+ Version current = UpdateUtils.currentMinecraftVersion();
+
+ boolean hadUpdate = false;
+ for (MCUpdate mcUpdate : mcUpdateMap) {
+ hadUpdate |= mcUpdate.handleUpdate(current, hadUpdate);
+ }
+
+ if (hadUpdate) {
+ CustomAnvil.instance.getLogger().info("Updating Done !");
+ }
+
+ if(current.major() == 1 && current.minor() < 21) {
+ var logger = CustomAnvil.instance.getLogger();
+ logger.warning("Your are running an old version of minecraft (lower than 1.21)");
+ logger.warning("Custom Anvil will stop supporting this version on the first of july 2026");
+ }
+ }
+
+ private static void finishConfiguration(@Nonnull String newVersion, @Nonnull Set toSave) {
+ CustomAnvil.instance.getLogger().info("Configuration file updated to " + newVersion);
+ ConfigHolder.DEFAULT_CONFIG.getConfig().set(CONFIG_VERSION_PATH, newVersion);
+
+ toSave.add(ConfigHolder.DEFAULT_CONFIG);
+ // save
+ for (ConfigHolder configHolder : toSave) {
+ configHolder.saveToDisk(true);
+ }
+
+ // then reload
+ for (ConfigHolder configHolder : toSave) {
+ configHolder.reload();
+ }
+
+ }
+
+}
diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/UpdateUtils.java b/src/main/java/xyz/alexcrea/cuanvil/update/UpdateUtils.java
index c13515f..2907fef 100644
--- a/src/main/java/xyz/alexcrea/cuanvil/update/UpdateUtils.java
+++ b/src/main/java/xyz/alexcrea/cuanvil/update/UpdateUtils.java
@@ -15,22 +15,6 @@ public class UpdateUtils {
return Version.fromString(versionString);
}
- @Deprecated
- public static int[] currentMinecraftVersionArray() {
- String versionString = Bukkit.getServer().getBukkitVersion().split("-")[0];
- return UpdateUtils.readVersionFromString(versionString);
- }
-
- public static int[] readVersionFromString(String versionString) {
- String[] partialVersion = versionString.split("\\.");
- int[] versionParts = new int[]{0, 0, 0};
-
- for (int i = 0; i < Math.min(3, partialVersion.length); i++) {
- versionParts[i] = Integer.parseInt(partialVersion[i]);
- }
- return versionParts;
- }
-
public static void addToStringList(FileConfiguration config, String path, String... toAdd) {
List groups = new ArrayList<>(config.getStringList(path));
groups.addAll(Arrays.asList(toAdd));
diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/Version.java b/src/main/java/xyz/alexcrea/cuanvil/update/Version.java
index e6f63cf..a49fbdd 100644
--- a/src/main/java/xyz/alexcrea/cuanvil/update/Version.java
+++ b/src/main/java/xyz/alexcrea/cuanvil/update/Version.java
@@ -1,5 +1,7 @@
package xyz.alexcrea.cuanvil.update;
+import org.jetbrains.annotations.NotNull;
+
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
@@ -19,7 +21,11 @@ public record Version(int major, int minor, int patch) {
int[] versionParts = new int[]{0, 0, 0};
for (int i = 0; i < Math.min(3, partialVersion.length); i++) {
- versionParts[i] = Integer.parseInt(partialVersion[i]);
+ try {
+ versionParts[i] = Integer.parseInt(partialVersion[i]);
+ } catch (NumberFormatException e) {
+ break;
+ }
}
return new Version(versionParts[0], versionParts[1], versionParts[2]);
}
@@ -48,6 +54,7 @@ public record Version(int major, int minor, int patch) {
this.patch <= other.patch)));
}
+ @NotNull
@Override
public String toString() {
return major + "." + minor + "." + patch;
diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/MCUpdate.java b/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/MCUpdate.java
new file mode 100644
index 0000000..40fc587
--- /dev/null
+++ b/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/MCUpdate.java
@@ -0,0 +1,38 @@
+package xyz.alexcrea.cuanvil.update.minecraft;
+
+import io.delilaheve.CustomAnvil;
+import xyz.alexcrea.cuanvil.config.ConfigHolder;
+import xyz.alexcrea.cuanvil.update.UpdateUtils;
+import xyz.alexcrea.cuanvil.update.Version;
+
+public abstract class MCUpdate {
+
+ public final Version version;
+
+ public MCUpdate(Version version){
+ this.version = version;
+ }
+
+ public boolean handleUpdate(Version current, boolean hadUpdate){
+ // Test if we are running in this update version or better
+ if(version.greaterThan(current))
+ return false;
+
+ // if version path is not null then check if its it's before this update version
+ String oldVersion = ConfigHolder.DEFAULT_CONFIG.getConfig().getString(UpdateUtils.MINECRAFT_VERSION_PATH);
+ if(oldVersion != null){
+ var version = Version.fromString(oldVersion);
+ if(this.version.lesserEqual(version)) return false;
+ }
+
+ if(!hadUpdate){
+ CustomAnvil.instance.getLogger().info("Updating config to support minecraft " + current +" ...");
+ }
+ doUpdate();
+ return true;
+ }
+
+ protected abstract void doUpdate();
+
+
+}
diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/Update_1_21.java b/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/Update_1_21.java
similarity index 64%
rename from src/main/java/xyz/alexcrea/cuanvil/update/Update_1_21.java
rename to src/main/java/xyz/alexcrea/cuanvil/update/minecraft/Update_1_21.java
index 332b5fe..3aa6073 100644
--- a/src/main/java/xyz/alexcrea/cuanvil/update/Update_1_21.java
+++ b/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/Update_1_21.java
@@ -1,42 +1,24 @@
-package xyz.alexcrea.cuanvil.update;
+package xyz.alexcrea.cuanvil.update.minecraft;
import io.delilaheve.CustomAnvil;
-import org.bukkit.configuration.file.FileConfiguration;
import xyz.alexcrea.cuanvil.config.ConfigHolder;
+import xyz.alexcrea.cuanvil.update.UpdateUtils;
+import xyz.alexcrea.cuanvil.update.Version;
import static xyz.alexcrea.cuanvil.update.UpdateUtils.addAbsentToList;
-// This is a temporary class that aim to handle 1.21 update.
-// It will be replaced by a better system later.
-public class Update_1_21 {
-
- private static final Version V1_21 = new Version(1, 21);
-
- public static void handleUpdate(){
- // Assume if version path is not null then it's 1.21
- String oldVersion = ConfigHolder.DEFAULT_CONFIG.getConfig().getString(UpdateUtils.MINECRAFT_VERSION_PATH);
- if(oldVersion != null){
- Version version = Version.fromString(oldVersion);
-
- // Test 1.21
- if(V1_21.greaterEqual(version)) return;
- }
- Version current = UpdateUtils.currentMinecraftVersion();
-
- // Test 1.21
- if(current.greaterEqual(V1_21)){
- doUpdate();
- }
+public class Update_1_21 extends MCUpdate {
+ public Update_1_21() {
+ super(new Version(1, 21));
}
- private static void doUpdate() {
- CustomAnvil.instance.getLogger().info("Updating config to support 1.21 ...");
-
- FileConfiguration baseConfig = ConfigHolder.DEFAULT_CONFIG.getConfig();
- FileConfiguration groupConfig = ConfigHolder.ITEM_GROUP_HOLDER.getConfig();
- FileConfiguration conflictConfig = ConfigHolder.CONFLICT_HOLDER.getConfig();
- FileConfiguration unitConfig = ConfigHolder.UNIT_REPAIR_HOLDER.getConfig();
+ @Override
+ protected void doUpdate() {
+ var baseConfig = ConfigHolder.DEFAULT_CONFIG.getConfig();
+ var groupConfig = ConfigHolder.ITEM_GROUP_HOLDER.getConfig();
+ var conflictConfig = ConfigHolder.CONFLICT_HOLDER.getConfig();
+ var unitConfig = ConfigHolder.UNIT_REPAIR_HOLDER.getConfig();
// Add mace to groups
groupConfig.set("mace.type", "include");
@@ -80,8 +62,8 @@ public class Update_1_21 {
// Add unit repair for mace
unitConfig.set("breeze_rod.mace", 0.25);
- // Set version string as 1.21
- baseConfig.set(UpdateUtils.MINECRAFT_VERSION_PATH, "1.21");
+ // Set version string as current
+ baseConfig.set(UpdateUtils.MINECRAFT_VERSION_PATH, version.toString());
// Save
ConfigHolder.DEFAULT_CONFIG.saveToDisk(true);
@@ -92,9 +74,6 @@ public class Update_1_21 {
// imply reload of CONFLICT_HOLDER
// We also do not need to reload base config as there is no object related to it.
ConfigHolder.ITEM_GROUP_HOLDER.reload();
-
- CustomAnvil.instance.getLogger().info("Updating Done !");
-
}
}
diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/Update_1_21_11.java b/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/Update_1_21_11.java
new file mode 100644
index 0000000..d639596
--- /dev/null
+++ b/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/Update_1_21_11.java
@@ -0,0 +1,84 @@
+package xyz.alexcrea.cuanvil.update.minecraft;
+
+import xyz.alexcrea.cuanvil.config.ConfigHolder;
+import xyz.alexcrea.cuanvil.update.UpdateUtils;
+import xyz.alexcrea.cuanvil.update.Version;
+
+import static xyz.alexcrea.cuanvil.update.UpdateUtils.addAbsentToList;
+
+public class Update_1_21_11 extends MCUpdate{
+
+ public Update_1_21_11() {
+ super(new Version(1, 21, 11));
+ }
+
+ @Override
+ protected void doUpdate() {
+ var baseConfig = ConfigHolder.DEFAULT_CONFIG.getConfig();
+ var groupConfig = ConfigHolder.ITEM_GROUP_HOLDER.getConfig();
+ var conflictConfig = ConfigHolder.CONFLICT_HOLDER.getConfig();
+ var unitConfig = ConfigHolder.UNIT_REPAIR_HOLDER.getConfig();
+
+ // Create spear group
+ groupConfig.set("spears.type", "include");
+ addAbsentToList(groupConfig, "spears.items",
+ "wooden_spear",
+ "golden_spear",
+ "stone_spear",
+ "copper_spear",
+ "iron_spear",
+ "diamond_spear",
+ "netherite_spear");
+
+ // Add spear group to super group and enchantments
+ addAbsentToList(groupConfig, "melee_weapons.groups", "spears");
+
+ addAbsentToList(conflictConfig, "restriction_looting.notAffectedGroups", "spears");
+ addAbsentToList(conflictConfig, "restriction_knockback.notAffectedGroups", "spears");
+ addAbsentToList(conflictConfig, "restriction_fire_aspect.notAffectedGroups", "spears");
+
+ // Unit repair for spears
+ unitConfig.set("gold_ingot.golden_spear", 0.25);
+ unitConfig.set("copper_ingot.copper_spear", 0.25);
+ unitConfig.set("iron_ingot.iron_spear", 0.25);
+ unitConfig.set("diamond.diamond_spear", 0.25);
+ unitConfig.set("netherite_ingot.netherite_spear", 0.25);
+
+ unitConfig.set("cobblestone.stone_spear", 0.25);
+ unitConfig.set("cobbled_deepslate.stone_spear", 0.25);
+
+ unitConfig.set("oak_planks.wooden_spear", 0.25);
+ unitConfig.set("spruce_planks.wooden_spear", 0.25);
+ unitConfig.set("birch_planks.wooden_spear", 0.25);
+ unitConfig.set("jungle_planks.wooden_spear", 0.25);
+ unitConfig.set("acacia_planks.wooden_spear", 0.25);
+ unitConfig.set("dark_oak_planks.wooden_spear", 0.25);
+ unitConfig.set("mangrove_planks.wooden_spear", 0.25);
+ unitConfig.set("cherry_planks.wooden_spear", 0.25);
+ unitConfig.set("bamboo_planks.wooden_spear", 0.25);
+ unitConfig.set("crimson_planks.wooden_spear", 0.25);
+ unitConfig.set("warped_planks.wooden_spear", 0.25);
+
+ // Create lunge enchant value and group
+ baseConfig.set("enchant_limits.minecraft:lunge", 3);
+ baseConfig.set("enchant_values.minecraft:lunge.item", 2);
+ baseConfig.set("enchant_values.minecraft:lunge.book", 1);
+
+ addAbsentToList(conflictConfig, "restriction_lunge.enchantments", "minecraft:lunge");
+ addAbsentToList(conflictConfig, "restriction_lunge.notAffectedGroups", "spears", "enchanted_book");
+
+ // Set version string as current
+ baseConfig.set(UpdateUtils.MINECRAFT_VERSION_PATH, version.toString());
+
+ // Save
+ ConfigHolder.DEFAULT_CONFIG.saveToDisk(true);
+ ConfigHolder.ITEM_GROUP_HOLDER.saveToDisk(true);
+ ConfigHolder.CONFLICT_HOLDER.saveToDisk(true);
+ ConfigHolder.UNIT_REPAIR_HOLDER.saveToDisk(true);
+
+ // imply reload of CONFLICT_HOLDER
+ // We also do not need to reload base config as there is no object related to it.
+ ConfigHolder.ITEM_GROUP_HOLDER.reload();
+ }
+
+}
diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/Update_1_21_9.java b/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/Update_1_21_9.java
new file mode 100644
index 0000000..d289b9b
--- /dev/null
+++ b/src/main/java/xyz/alexcrea/cuanvil/update/minecraft/Update_1_21_9.java
@@ -0,0 +1,64 @@
+package xyz.alexcrea.cuanvil.update.minecraft;
+
+import org.bukkit.configuration.file.FileConfiguration;
+import xyz.alexcrea.cuanvil.config.ConfigHolder;
+import xyz.alexcrea.cuanvil.update.UpdateUtils;
+import xyz.alexcrea.cuanvil.update.Version;
+
+import static xyz.alexcrea.cuanvil.update.UpdateUtils.addAbsentToList;
+
+public class Update_1_21_9 extends MCUpdate{
+
+ public Update_1_21_9() {
+ super(new Version(1, 21, 9));
+ }
+
+ @Override
+ protected void doUpdate() {
+ var baseConfig = ConfigHolder.DEFAULT_CONFIG.getConfig();
+ var groupConfig = ConfigHolder.ITEM_GROUP_HOLDER.getConfig();
+ var unitConfig = ConfigHolder.UNIT_REPAIR_HOLDER.getConfig();
+
+ // Add cooper items to groups
+ addAbsentToList(groupConfig, "helmets.items", "copper_helmet");
+ addAbsentToList(groupConfig, "chestplate.items", "copper_chestplate");
+ addAbsentToList(groupConfig, "leggings.items", "copper_leggings");
+ addAbsentToList(groupConfig, "boots.items", "copper_boots");
+
+ addAbsentToList(groupConfig, "pickaxes.items", "copper_pickaxe");
+ addAbsentToList(groupConfig, "shovels.items", "copper_shovel");
+ addAbsentToList(groupConfig, "hoes.items", "copper_hoe");
+ addAbsentToList(groupConfig, "axes.items", "copper_axe");
+ addAbsentToList(groupConfig, "swords.items", "copper_sword");
+
+ // Add unit repair
+ addCopperUnitRepair(unitConfig);
+
+ // Set version string as current
+ baseConfig.set(UpdateUtils.MINECRAFT_VERSION_PATH, version.toString());
+
+ // Save
+ ConfigHolder.DEFAULT_CONFIG.saveToDisk(true);
+ ConfigHolder.ITEM_GROUP_HOLDER.saveToDisk(true);
+ ConfigHolder.UNIT_REPAIR_HOLDER.saveToDisk(true);
+
+ // imply reload of CONFLICT_HOLDER
+ // We also do not need to reload base config as there is no object related to it.
+ ConfigHolder.ITEM_GROUP_HOLDER.reload();
+ }
+
+ public static void addCopperUnitRepair(FileConfiguration unitConfig) {
+ // Add unit repair
+ unitConfig.set("copper_ingot.copper_helmet", 0.25);
+ unitConfig.set("copper_ingot.copper_chestplate", 0.25);
+ unitConfig.set("copper_ingot.copper_leggings", 0.25);
+ unitConfig.set("copper_ingot.copper_boots", 0.25);
+
+ unitConfig.set("copper_ingot.copper_pickaxe", 0.25);
+ unitConfig.set("copper_ingot.copper_shovel", 0.25);
+ unitConfig.set("copper_ingot.copper_hoe", 0.25);
+ unitConfig.set("copper_ingot.copper_axe", 0.25);
+ unitConfig.set("copper_ingot.copper_sword", 0.25);
+ }
+
+}
diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_11_0.java b/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_11_0.java
index 6d6baca..9740971 100644
--- a/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_11_0.java
+++ b/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_11_0.java
@@ -1,6 +1,7 @@
package xyz.alexcrea.cuanvil.update.plugin;
import org.bukkit.Material;
+import org.bukkit.NamespacedKey;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
import org.jetbrains.annotations.NotNull;
@@ -11,6 +12,7 @@ import xyz.alexcrea.cuanvil.group.AbstractMaterialGroup;
import xyz.alexcrea.cuanvil.group.IncludeGroup;
import javax.annotation.Nonnull;
+import java.util.Arrays;
import java.util.List;
import java.util.Set;
@@ -69,7 +71,12 @@ public class PUpdate_1_11_0 {
// Create new group
IncludeGroup group = new IncludeGroup(toolset);
- group.addAll(toolMats);
+ NamespacedKey[] keys = new NamespacedKey[toolMats.length];
+ for (int i = 0; i < toolMats.length; i++) {
+ keys[i] = toolMats[i].getKey();
+ }
+
+ group.addAll(keys);
MaterialGroupApi.addMaterialGroup(group, true);
@@ -77,8 +84,8 @@ public class PUpdate_1_11_0 {
if (tools == null) return;
if (!(tools instanceof IncludeGroup include)) return;
- List mats = List.of(toolMats);
- Set matSet = include.getNonGroupInheritedMaterials();
+ List mats = List.of(keys);
+ Set matSet = include.getNonGroupInheritedMaterials();
if (!matSet.containsAll(mats)) return;
mats.forEach(matSet::remove);
diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_15_5.java b/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_15_5.java
new file mode 100644
index 0000000..76f51af
--- /dev/null
+++ b/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_15_5.java
@@ -0,0 +1,27 @@
+package xyz.alexcrea.cuanvil.update.plugin;
+
+import org.bukkit.configuration.file.FileConfiguration;
+import xyz.alexcrea.cuanvil.config.ConfigHolder;
+
+import javax.annotation.Nonnull;
+import java.util.Set;
+
+import static xyz.alexcrea.cuanvil.update.UpdateUtils.addAbsentToList;
+
+public class PUpdate_1_15_5 {
+
+ public static void handleUpdate(@Nonnull Set toSave) {
+ FileConfiguration config = ConfigHolder.CONFLICT_HOLDER.getConfig();
+
+ if (config.isConfigurationSection("restriction_luck_of_the_sea")) return;
+
+ // We fix the luck of the see enchantment
+ addAbsentToList(config, "restriction_luck_of_the_sea.enchantments",
+ "minecraft:luck_of_the_sea");
+ addAbsentToList(config, "restriction_luck_of_the_sea.notAffectedGroups",
+ "enchanted_book", "fishing_rod");
+
+ toSave.add(ConfigHolder.CONFLICT_HOLDER);
+ }
+
+}
diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_15_6.java b/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_15_6.java
new file mode 100644
index 0000000..fde7cfc
--- /dev/null
+++ b/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_15_6.java
@@ -0,0 +1,27 @@
+package xyz.alexcrea.cuanvil.update.plugin;
+
+import org.bukkit.configuration.file.FileConfiguration;
+import xyz.alexcrea.cuanvil.config.ConfigHolder;
+import xyz.alexcrea.cuanvil.update.UpdateUtils;
+import xyz.alexcrea.cuanvil.update.Version;
+import xyz.alexcrea.cuanvil.update.minecraft.Update_1_21_9;
+
+import javax.annotation.Nonnull;
+import java.util.Set;
+
+public class PUpdate_1_15_6 {
+
+ public static void handleUpdate(@Nonnull Set toSave) {
+ // fix only needed for 1.21.9 and above
+ Version current = UpdateUtils.currentMinecraftVersion();
+ if (new Version(1, 21, 9).greaterThan(current)) return;
+
+ FileConfiguration unitConfig = ConfigHolder.UNIT_REPAIR_HOLDER.getConfig();
+
+ // Add unit repair
+ Update_1_21_9.addCopperUnitRepair(unitConfig);
+
+ toSave.add(ConfigHolder.UNIT_REPAIR_HOLDER);
+ }
+
+}
diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_8_0.java b/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_8_0.java
index 7685f92..81ce1aa 100644
--- a/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_8_0.java
+++ b/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PUpdate_1_8_0.java
@@ -2,10 +2,10 @@ package xyz.alexcrea.cuanvil.update.plugin;
import io.delilaheve.util.ConfigOptions;
import org.bukkit.configuration.file.FileConfiguration;
+import xyz.alexcrea.cuanvil.anvil.AnvilUseType;
import xyz.alexcrea.cuanvil.config.ConfigHolder;
import xyz.alexcrea.cuanvil.config.WorkPenaltyType;
import xyz.alexcrea.cuanvil.gui.config.settings.WorkPenaltyTypeSettingGui;
-import xyz.alexcrea.cuanvil.util.AnvilUseType;
import javax.annotation.Nonnull;
import java.util.EnumMap;
diff --git a/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PluginUpdates.java b/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PluginUpdates.java
deleted file mode 100644
index 03c858e..0000000
--- a/src/main/java/xyz/alexcrea/cuanvil/update/plugin/PluginUpdates.java
+++ /dev/null
@@ -1,52 +0,0 @@
-package xyz.alexcrea.cuanvil.update.plugin;
-
-import io.delilaheve.CustomAnvil;
-import xyz.alexcrea.cuanvil.config.ConfigHolder;
-import xyz.alexcrea.cuanvil.update.Version;
-
-import javax.annotation.Nonnull;
-import java.util.HashSet;
-import java.util.Set;
-
-public class PluginUpdates {
-
- private static final String CONFIG_VERSION_PATH = "configVersion";
-
- public static void handlePluginUpdate() {
- String versionString = ConfigHolder.DEFAULT_CONFIG.getConfig().getString(CONFIG_VERSION_PATH);
- Version current = versionString == null ? new Version(0) : Version.fromString(versionString);
-
- Set toSave = new HashSet<>();
-
- if (new Version(1, 6, 2).greaterThan(current)) {
- PUpdate_1_6_2.handleUpdate(toSave);
- // We assume 1.6.7 will run. TODO a better system instead of that I guess
- }
- if (new Version(1, 6, 7).greaterThan(current)) {
- PUpdate_1_6_7.handleUpdate(toSave);
- // We assume 1.8.0 will run.
- }
- if (new Version(1, 8, 0).greaterThan(current)) {
- PUpdate_1_8_0.handleUpdate(toSave);
- // We assume 1.11.0 will run.
- }
-
- if (new Version(1, 11, 0).greaterThan(current)) {
- PUpdate_1_11_0.handleUpdate(toSave);
-
- finishConfiguration("1.11.0", toSave);
- }
-
- }
-
- private static void finishConfiguration(@Nonnull String newVersion, @Nonnull Set toSave) {
- CustomAnvil.instance.getLogger().info("Configuration file updated to " + newVersion);
- ConfigHolder.DEFAULT_CONFIG.getConfig().set(CONFIG_VERSION_PATH, newVersion);
-
- toSave.add(ConfigHolder.DEFAULT_CONFIG);
- for (ConfigHolder configHolder : toSave) {
- configHolder.saveToDisk(true);
- }
- }
-
-}
diff --git a/src/main/kotlin/io/delilaheve/CustomAnvil.kt b/src/main/kotlin/io/delilaheve/CustomAnvil.kt
index f629c2f..4d99faf 100644
--- a/src/main/kotlin/io/delilaheve/CustomAnvil.kt
+++ b/src/main/kotlin/io/delilaheve/CustomAnvil.kt
@@ -6,10 +6,14 @@ import org.bukkit.configuration.file.YamlConfiguration
import org.bukkit.plugin.java.JavaPlugin
import xyz.alexcrea.cuanvil.api.event.CAConfigReadyEvent
import xyz.alexcrea.cuanvil.api.event.CAEnchantRegistryReadyEvent
+import xyz.alexcrea.cuanvil.command.CustomAnvilCommand
import xyz.alexcrea.cuanvil.command.EditConfigExecutor
import xyz.alexcrea.cuanvil.command.ReloadExecutor
import xyz.alexcrea.cuanvil.config.ConfigHolder
import xyz.alexcrea.cuanvil.dependency.DependencyManager
+import xyz.alexcrea.cuanvil.dependency.MinecraftVersionUtil
+import xyz.alexcrea.cuanvil.dependency.economy.EconomyManager
+import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil
import xyz.alexcrea.cuanvil.enchant.CAEnchantmentRegistry
import xyz.alexcrea.cuanvil.gui.config.MainConfigGui
import xyz.alexcrea.cuanvil.gui.util.GuiSharedConstant
@@ -17,10 +21,10 @@ import xyz.alexcrea.cuanvil.listener.AnvilCloseListener
import xyz.alexcrea.cuanvil.listener.AnvilResultListener
import xyz.alexcrea.cuanvil.listener.ChatEventListener
import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener
+import xyz.alexcrea.cuanvil.update.ModrinthUpdateChecker
import xyz.alexcrea.cuanvil.update.PluginSetDefault
-import xyz.alexcrea.cuanvil.update.Update_1_21
-import xyz.alexcrea.cuanvil.update.plugin.PluginUpdates
-import xyz.alexcrea.cuanvil.util.Metrics
+import xyz.alexcrea.cuanvil.update.UpdateHandler
+import xyz.alexcrea.cuanvil.util.MetricsUtil
import java.io.File
import java.io.FileReader
import java.util.logging.Level
@@ -31,8 +35,8 @@ import java.util.logging.Level
open class CustomAnvil : JavaPlugin() {
companion object {
- // bstats plugin id
- private const val bstatsPluginId = 20923
+ // pluginIDS
+ private const val modrinthPluginID = "S75Ueiq9"
// Permission string required to use the plugin's features
const val affectedByPluginPermission = "ca.affected"
@@ -46,9 +50,13 @@ open class CustomAnvil : JavaPlugin() {
// Permission string required to reload the config
const val commandReloadPermission = "ca.command.reload"
+ // Permission string required to get diagnostic data
+ const val diagnosticPermission = "ca.command.diagnostic"
+
// Permission string required to edit the plugin's config
const val editConfigPermission = "ca.config.edit"
+
// Command Name to reload the config
const val commandReloadName = "anvilconfigreload"
@@ -61,10 +69,12 @@ open class CustomAnvil : JavaPlugin() {
// Chat message listener
lateinit var chatListener: ChatEventListener
+ var latestVer: String? = null
+
/**
* Logging handler
*/
- fun log(message: String) {
+ @JvmStatic fun log(message: String) {
if (ConfigOptions.debugLog) {
instance.logger.info(message)
}
@@ -79,7 +89,26 @@ open class CustomAnvil : JavaPlugin() {
}
}
+ }
+ // stop plugin if we do not force a dirty start (true by default)
+ // Return true if start was stopped
+ private fun tryDirtyStart(): Boolean {
+ if(!ConfigHolder.DEFAULT_CONFIG.config.getBoolean("dirty_start", false)) {
+ Bukkit.getPluginManager().disablePlugin(this)
+ return true
+ }
+ return false
+ }
+
+ // stop plugin if we force a safe start (false by default)
+ // Return true if start was stopped
+ private fun trySafeStart(): Boolean {
+ if(ConfigHolder.DEFAULT_CONFIG.config.getBoolean("safe_start", false)) {
+ Bukkit.getPluginManager().disablePlugin(this)
+ return true
+ }
+ return false
}
/**
@@ -87,7 +116,74 @@ open class CustomAnvil : JavaPlugin() {
*/
override fun onEnable() {
instance = this
+ try {
+ legacyCheck()
+ } catch (e: Exception) {
+ logger.log(Level.SEVERE, "error trying to check for legacy system", e)
+ MetricsUtil.trackError(e)
+ if(trySafeStart()) return
+ }
+ // Add commands
+ try {
+ prepareCommand()
+ } catch (e: Exception) {
+ logger.log(Level.SEVERE, "error trying to register commands", e)
+ MetricsUtil.trackError(e)
+ if(trySafeStart()) return
+ }
+
+ // Load default configuration
+ try {
+ if(!ConfigHolder.loadDefaultConfig())
+ throw RuntimeException("Error loading configuration file")
+ } catch (e: Exception) {
+ logger.log(Level.SEVERE, "error occurred loading default configuration", e)
+ MetricsUtil.trackError(e)
+ if(tryDirtyStart()) return
+ }
+
+ // Load dependency
+ try {
+ DependencyManager.loadDependency()
+ } catch (e: Exception) {
+ logger.log(Level.SEVERE, "error loading dependency compatibility", e)
+ MetricsUtil.trackError(e)
+ if(tryDirtyStart()) return
+ }
+
+ // Register listeners
+ try {
+ registerListeners()
+ } catch (e: Exception) {
+ logger.log(Level.SEVERE, "error registering listeners", e)
+ MetricsUtil.trackError(e)
+ if(tryDirtyStart()) return
+ }
+
+ // Load metrics
+ MetricsUtil.loadMetrics(this)
+
+ // Load other thing later.
+ // It is so other dependent plugins can implement there event listener before we fire them.
+ DependencyManager.scheduler.scheduleGlobally(this) { loadEnchantmentSystemDirty() }
+ }
+
+ override fun onDisable() {
+ MetricsUtil.shutdownMetrics()
+ }
+
+ private fun loadEnchantmentSystemDirty() {
+ try {
+ loadEnchantmentSystem()
+ } catch (e: Exception) {
+ logger.log(Level.SEVERE, "error initializing enchantment system", e)
+ MetricsUtil.trackError(e)
+ tryDirtyStart()
+ }
+ }
+
+ private fun legacyCheck() {
// Disable old plugin name if exist
val potentialPlugin = Bukkit.getPluginManager().getPlugin("UnsafeEnchantsPlus")
if (potentialPlugin != null) {
@@ -96,33 +192,43 @@ open class CustomAnvil : JavaPlugin() {
logger.warning("Please note CustomAnvil is a more recent version of UnsafeEnchantsPlus")
}
- // Add commands
- prepareCommand()
-
- // Load chat listener
- chatListener = ChatEventListener()
- server.pluginManager.registerEvents(chatListener, this)
-
- // Load default configuration
- if (!ConfigHolder.loadDefaultConfig()) {
- logger.log(Level.SEVERE,"could not load default config.")
- return
+ val isPaper = PlatformUtil.isPaper
+ if(!isPaper) {
+ logger.warning("It seems you are using spigot")
+ logger.warning("Please take notice that spigot is less supported than paper and derivatives")
+ if(MinecraftVersionUtil.isTooNewForSpigot) {
+ logger.warning("If replace too expensive is not working this is likely because of spigot")
+ logger.warning("As native nms is not supported for spigot starting 26.1")
+ }
}
- // Load dependency
- DependencyManager.loadDependency()
+ val loader = if(isPaper) "paper" else "spigot"
+
+ val version = description.version
+ val featured = if(version.contains("dev")) null else true
+
+ ModrinthUpdateChecker(modrinthPluginID, loader, null)
+ .setFeatured(featured)
+ .setOnError {
+ logger.log(Level.WARNING, "error trying to fetch latest update", it)
+ }
+ .checkVersion { latestVer: String? ->
+ CustomAnvil.latestVer = latestVer
+ if(latestVer == null || version.contains(latestVer)) return@checkVersion
+
+ logger.warning("An update may be available: $latestVer")
+ }
+ }
+
+ private fun registerListeners() {
+ // Register chat listener
+ chatListener = ChatEventListener()
+ server.pluginManager.registerEvents(chatListener, this)
// Register anvil events
server.pluginManager.registerEvents(PrepareAnvilListener(), this)
server.pluginManager.registerEvents(AnvilResultListener(), this)
server.pluginManager.registerEvents(AnvilCloseListener(DependencyManager.packetManager), this)
-
- // Load metrics
- Metrics(this, bstatsPluginId)
-
- // Load other thing later.
- // It is so other dependent plugins can implement there event listener before we fire them.
- DependencyManager.scheduler.scheduleGlobally(this, {loadEnchantmentSystem()})
}
private fun loadEnchantmentSystem(){
@@ -135,15 +241,13 @@ open class CustomAnvil : JavaPlugin() {
// Load config
if (!ConfigHolder.loadNonDefaultConfig()) {
- logger.log(Level.SEVERE,"could not load non default config.")
+ logger.log(Level.SEVERE,"Plugin has an issue while trying to load non default config... exiting...")
+ server.pluginManager.disablePlugin(this)
return
}
- // temporary: handle 1.21 update
- Update_1_21.handleUpdate()
-
- // plugin configuration updates
- PluginUpdates.handlePluginUpdate()
+ // Handle minecraft and plugin updates
+ UpdateHandler.handleUpdates()
// Register enchantment of compatible plugin and load configuration change.
DependencyManager.handleCompatibilityConfig()
@@ -156,9 +260,11 @@ open class CustomAnvil : JavaPlugin() {
MainConfigGui.getInstance().init(DependencyManager.packetManager)
GuiSharedConstant.loadConstants()
+ // Prepare economy if possible
+ EconomyManager.setupEconomy(this)
+
// Finally, re add default we may be missing
PluginSetDefault.reAddMissingDefault()
-
}
fun reloadResource(
@@ -210,6 +316,8 @@ open class CustomAnvil : JavaPlugin() {
command = getCommand(commandConfigName)
command?.setExecutor(EditConfigExecutor())
+
+ CustomAnvilCommand(this)
}
}
diff --git a/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt b/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt
index 2a8360f..9dc85f9 100644
--- a/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt
+++ b/src/main/kotlin/io/delilaheve/util/ConfigOptions.kt
@@ -2,14 +2,17 @@ package io.delilaheve.util
import io.delilaheve.CustomAnvil
import io.delilaheve.util.EnchantmentUtil.enchantmentName
-import org.bukkit.Material
import org.bukkit.NamespacedKey
+import org.bukkit.entity.HumanEntity
+import xyz.alexcrea.cuanvil.anvil.AnvilUseType
import xyz.alexcrea.cuanvil.config.ConfigHolder
import xyz.alexcrea.cuanvil.config.WorkPenaltyType
import xyz.alexcrea.cuanvil.config.WorkPenaltyType.WorkPenaltyPart
import xyz.alexcrea.cuanvil.dependency.DependencyManager
+import xyz.alexcrea.cuanvil.dependency.economy.EconomyManager
import xyz.alexcrea.cuanvil.enchant.CAEnchantment
-import xyz.alexcrea.cuanvil.util.AnvilUseType
+import xyz.alexcrea.cuanvil.util.dialog.AnvilRenameDialogUtil
+import java.math.BigDecimal
import java.util.*
/**
@@ -21,6 +24,9 @@ object ConfigOptions {
// Path for config values
// ----------------------
+ const val METRIC_TYPE = "metric_type"
+ const val METRIC_COLLECT_ERROR = "metric_collect_errors"
+
const val CAP_ANVIL_COST = "limit_repair_cost"
const val MAX_ANVIL_COST = "limit_repair_value"
const val REMOVE_ANVIL_COST_LIMIT = "remove_repair_limit"
@@ -38,9 +44,12 @@ object ConfigOptions {
// Color related config
const val ALLOW_COLOR_CODE = "allow_color_code"
const val ALLOW_HEXADECIMAL_COLOR = "allow_hexadecimal_color"
+ const val ALLOW_MINIMESSAGE = "allow_minimessage"
const val PERMISSION_NEEDED_FOR_COLOR = "permission_needed_for_color"
const val USE_OF_COLOR_COST = "use_of_color_cost"
+ const val PER_COLOR_CODE_PERMISSION = "per_color_code_permission"
+
// Work penalty config
const val WORK_PENALTY_ROOT = "work_penalty"
const val WORK_PENALTY_INCREASE = "shared_increase"
@@ -53,15 +62,25 @@ object ConfigOptions {
const val ENCHANT_COUNT_LIMIT_DEFAULT = "$ENCHANT_COUNT_LIMIT_ROOT.default"
const val ENCHANT_COUNT_LIMIT_ITEMS = "$ENCHANT_COUNT_LIMIT_ROOT.items"
- const val DEFAULT_LIMIT_PATH = "default_limit"
-
const val ENCHANT_LIMIT_ROOT = "enchant_limits"
const val ENCHANT_VALUES_ROOT = "enchant_values"
+ // Dialog menu rename
+ const val DIALOG_RENAME_ENABLED = "enable_dialog_rename"
+ const val DIALOG_MAX_SIZE = "dialog_rename_max_size"
+ const val DIALOG_RENAME_USE_PERMISSION = "permission_needed_for_dialog_rename"
+ const val DIALOG_KEEP_USER_TEXT = "dialog_rename_keep_user_text"
+
+ // Others
const val DISABLE_MERGE_OVER_ROOT = "disable-merge-over"
const val IMMUTABLE_ENCHANTMENT_LIST = "immutable_enchantments"
+ // Monetary configs
+ const val MONETARY_USAGE_ROOT = "monetary_cost"
+ const val SHOULD_USE_MONEY = "$MONETARY_USAGE_ROOT.enabled"
+ const val MONEY_CURRENCY = "$MONETARY_USAGE_ROOT.currency"
+ const val MONETARY_MULTIPLIER_ROOT = "$MONETARY_USAGE_ROOT.multipliers"
// Keys for specific enchantment values
private const val KEY_BOOK = "book"
@@ -87,22 +106,34 @@ object ConfigOptions {
const val DEFAULT_ITEM_RENAME_COST = 1
const val DEFAULT_SACRIFICE_ILLEGAL_COST = 1
- const val DEFAULT_ADD_BOOK_ENCHANTMENT_AS_STORED_ENCHANTMENT = false;
+ const val DEFAULT_ADD_BOOK_ENCHANTMENT_AS_STORED_ENCHANTMENT = false
const val DEFAULT_ENCHANT_COUNT_LIMIT = -1
// Color related config
const val DEFAULT_ALLOW_COLOR_CODE = false
const val DEFAULT_ALLOW_HEXADECIMAL_COLOR = false
+ const val DEFAULT_ALLOW_MINIMESSAGE = false
const val DEFAULT_PERMISSION_NEEDED_FOR_COLOR = true
const val DEFAULT_USE_OF_COLOR_COST = 0
- const val DEFAULT_ENCHANT_LIMIT = 5
+ const val DEFAULT_PER_COLOR_CODE_PERMISSION = false
+
+ // Monetary configs
+ const val DEFAULT_SHOULD_USE_MONEY = false
+ const val DEFAULT_MONEY_CURRENCY = "default"
+ const val DEFAULT_MONEY_MULTIPLIER = 1.0
// Debug flag
private const val DEFAULT_DEBUG_LOG = false
private const val DEFAULT_VERBOSE_DEBUG_LOG = false
+ // Dialog menu rename
+ const val DEFAULT_DIALOG_RENAME_ENABLED = false
+ const val DEFAULT_DIALOG_MAX_SIZE = 256
+ const val DEFAULT_DIALOG_RENAME_USE_PERMISSION = false
+ const val DEFAULT_DIALOG_KEEP_USER_TEXT = true
+
// -------------
// Config Ranges
// -------------
@@ -127,9 +158,11 @@ object ConfigOptions {
@JvmField
val USE_OF_COLOR_COST_RANGE = 0..1000
- // Valid range for an enchantment limit
@JvmField
- val ENCHANT_LIMIT_RANGE = 1..255
+ val DIALOG_MAX_SIZE_RANGE = 0..Int.MAX_VALUE
+
+ // Valid range for an enchantment limit
+ const val ENCHANT_LIMIT = 255
// Valid range for an enchantment count limit
@JvmField
@@ -145,6 +178,11 @@ object ConfigOptions {
// Default max before merge disabled (negative mean enabled)
const val DEFAULT_MAX_BEFORE_MERGE_DISABLED = -1
+ // -----------
+ // Permissions
+ // -----------
+ private const val RENAME_DIALOG_PERMISSION = "ca.rename.dialog"
+
// -------------
// Get methods
// -------------
@@ -269,12 +307,22 @@ object ConfigOptions {
.getBoolean(ALLOW_HEXADECIMAL_COLOR, DEFAULT_ALLOW_HEXADECIMAL_COLOR)
}
+ /**
+ * Allow usage of minimessage formating
+ */
+ val allowMinimessage: Boolean
+ get() {
+ return ConfigHolder.DEFAULT_CONFIG
+ .config
+ .getBoolean(ALLOW_MINIMESSAGE, DEFAULT_ALLOW_MINIMESSAGE)
+ }
+
/**
* If one of the color component is enabled
*/
val renameColorPossible: Boolean
get() {
- return allowColorCode || allowHexadecimalColor
+ return allowColorCode || allowHexadecimalColor || allowMinimessage
}
/**
@@ -287,6 +335,16 @@ object ConfigOptions {
.getBoolean(PERMISSION_NEEDED_FOR_COLOR, DEFAULT_PERMISSION_NEEDED_FOR_COLOR)
}
+ /**
+ * Should each color code require a permission
+ */
+ val usePerColorCodePermission: Boolean
+ get() {
+ return ConfigHolder.DEFAULT_CONFIG
+ .config
+ .getBoolean(PER_COLOR_CODE_PERMISSION, DEFAULT_PER_COLOR_CODE_PERMISSION)
+ }
+
/**
* How many xp should use of color should cost
*/
@@ -333,22 +391,12 @@ object ConfigOptions {
return WorkPenaltyPart(penaltyIncrease, penaltyAdditive, exclusivePenaltyIncrease, exclusivePenaltyAdditive)
}
- /**
- * Default enchantment limit
- */
- private val defaultEnchantLimit: Int
- get() {
- return ConfigHolder.DEFAULT_CONFIG
- .config
- .getInt(DEFAULT_LIMIT_PATH, DEFAULT_ENCHANT_LIMIT)
- }
-
/**
* Get material enchantment count limit
*
* @return the current enchantment limit. -1 if none
*/
- fun getEnchantCountLimit(type: Material): Int? {
+ fun getEnchantCountLimit(type: NamespacedKey): Int? {
val limit = materialEnchantCountLimit(type)
if(limit != null) return limit
@@ -362,8 +410,8 @@ object ConfigOptions {
*
* @return The current enchantment limit. -1 if none
*/
- private fun materialEnchantCountLimit(type: Material): Int? {
- val path = "$ENCHANT_COUNT_LIMIT_ITEMS.${type.key.key.lowercase()}"
+ private fun materialEnchantCountLimit(type: NamespacedKey): Int? {
+ val path = "$ENCHANT_COUNT_LIMIT_ITEMS.${type.key.lowercase()}"
if(!ConfigHolder.DEFAULT_CONFIG.config.isInt(path))
return null
@@ -403,46 +451,90 @@ object ConfigOptions {
.getBoolean(VERBOSE_DEBUG_LOGGING, DEFAULT_VERBOSE_DEBUG_LOG)
}
+ /**
+ * Is the dialog menu for rename enabled
+ */
+ val doRenameDialog: Boolean
+ get() {
+ return ConfigHolder.DEFAULT_CONFIG
+ .config
+ .getBoolean(DIALOG_RENAME_ENABLED, DEFAULT_DIALOG_RENAME_ENABLED)
+ }
+
+ /**
+ * Do the dialog menu require permission
+ */
+ val doRenameDialogUsePermission: Boolean
+ get() {
+ return ConfigHolder.DEFAULT_CONFIG
+ .config
+ .getBoolean(DIALOG_RENAME_USE_PERMISSION, DEFAULT_DIALOG_RENAME_USE_PERMISSION)
+ }
+
+ fun canUseDialogRename(player: HumanEntity): Boolean {
+ if(!doRenameDialog || !AnvilRenameDialogUtil.anvilRenameDialog.canSendDialog()) return false
+ if(doRenameDialogUsePermission && !player.hasPermission(RENAME_DIALOG_PERMISSION)) return false
+
+ return true
+ }
+
+ /**
+ * Do the dialog menu require permission
+ */
+ val renameDialogMaxSize: Int
+ get() {
+ return ConfigHolder.DEFAULT_CONFIG
+ .config
+ .getInt(DIALOG_MAX_SIZE, DEFAULT_DIALOG_MAX_SIZE)
+ .takeIf { it in DIALOG_MAX_SIZE_RANGE }
+ ?: Int.MAX_VALUE
+ }
+
+ /**
+ * Should the text used for rename should be kept in the item's pdc
+ */
+ val shouldKeepRenameText: Boolean
+ get() {
+ return ConfigHolder.DEFAULT_CONFIG
+ .config
+ .getBoolean(DIALOG_KEEP_USER_TEXT, DEFAULT_DIALOG_KEEP_USER_TEXT)
+ }
+
/**
* Get the given [enchantment]'s limit
*/
fun enchantLimit(enchantment: CAEnchantment): Int {
+ val limit = rawEnchantLimit(enchantment)
+ if(limit >= 0) return limit.coerceAtMost(ENCHANT_LIMIT)
+
+ // get default
+ return enchantment.defaultMaxLevel()
+ }
+
+ /**
+ * Get the given [enchantment]'s limit
+ */
+ fun rawEnchantLimit(enchantment: CAEnchantment): Int {
// Test namespace
var limit = enchantLimit(enchantment.key.toString())
- if (limit != null) return limit
+ if (limit >= 0) return limit
// Test legacy (name only)
limit = enchantLimit(enchantment.enchantmentName)
- if (limit != null) return limit
+ if (limit >= 0) return limit
- // get default (and test old legacy if present)
- return getDefaultLevel(enchantment.enchantmentName)
+ // Default to negative
+ return -1
}
/**
* Get the given [enchantmentName]'s limit
*/
- private fun enchantLimit(enchantmentName: String): Int? {
+ private fun enchantLimit(enchantmentName: String): Int {
val path = "${ENCHANT_LIMIT_ROOT}.$enchantmentName"
- return CustomAnvil.instance
- .config
- .getInt(path, ENCHANT_LIMIT_RANGE.first - 1)
- .takeIf { it in ENCHANT_LIMIT_RANGE }
- }
-
- /**
- * Get default value if enchantment do not exist on config
- */
- private fun getDefaultLevel(
- enchantmentName: String, // compatibility with 1.20.5. TODO better update system
- ): Int {
- if (enchantmentName == "sweeping_edge") {
- val limit = enchantLimit("sweeping")
- if (limit != null) return limit
-
- }
- return defaultEnchantLimit
+ return CustomAnvil.instance.config
+ .getInt(path, -1)
}
/**
@@ -514,20 +606,20 @@ object ConfigOptions {
fun maxBeforeMergeDisabled(enchantment: CAEnchantment): Int {
val key = enchantment.key.toString()
var value = maxBeforeMergeDisabled(key)
- if (value != null) return value
+ if (value >= 0) return value
// Legacy name
val legacy = enchantment.enchantmentName
value = maxBeforeMergeDisabled(legacy)
- if (value != null) return value
+ if (value >= 0) return value
if (key == "minecraft:sweeping_edge") {
value = maxBeforeMergeDisabled("minecraft:sweeping")
- if (value != null) return value
+ if (value >= 0) return value
// legacy name of legacy enchantment name
value = maxBeforeMergeDisabled("sweeping")
- if (value != null) return value
+ if (value >= 0) return value
}
return DEFAULT_MAX_BEFORE_MERGE_DISABLED
@@ -537,14 +629,13 @@ object ConfigOptions {
* Get the given [enchantmentName]'s level before merge is disabled
* a negative value would mean never disabled
*/
- private fun maxBeforeMergeDisabled(enchantmentName: String): Int? {
+ private fun maxBeforeMergeDisabled(enchantmentName: String): Int {
// find if set
val path = "${DISABLE_MERGE_OVER_ROOT}.$enchantmentName"
return CustomAnvil.instance
.config
- .getInt(path, ENCHANT_LIMIT_RANGE.min() - 1)
- .takeIf { it in ENCHANT_LIMIT_RANGE }
+ .getInt(path, -1)
}
fun isImmutable(key: NamespacedKey): Boolean {
@@ -560,4 +651,29 @@ object ConfigOptions {
return false
}
+ /*
+ * Monetary configs (only for 1.21.6+)
+ * Also require dialog rename
+ */
+ fun shouldUseMoney(player: HumanEntity): Boolean {
+ return EconomyManager.economy?.initialized() == true &&
+ canUseDialogRename(player) &&
+ ConfigHolder.DEFAULT_CONFIG
+ .config
+ .getBoolean(SHOULD_USE_MONEY, DEFAULT_SHOULD_USE_MONEY)
+ }
+
+ val usedCurrency: String
+ get() {
+ return ConfigHolder.DEFAULT_CONFIG
+ .config
+ .getString(MONEY_CURRENCY, DEFAULT_MONEY_CURRENCY)!!
+ }
+
+ fun getMonetaryMultiplier(type: String): BigDecimal {
+ return BigDecimal(ConfigHolder.DEFAULT_CONFIG
+ .config
+ .getDouble("$MONETARY_MULTIPLIER_ROOT.$type", DEFAULT_MONEY_MULTIPLIER))
+ }
+
}
diff --git a/src/main/kotlin/io/delilaheve/util/EnchantmentUtil.kt b/src/main/kotlin/io/delilaheve/util/EnchantmentUtil.kt
index bee9bda..af959f2 100644
--- a/src/main/kotlin/io/delilaheve/util/EnchantmentUtil.kt
+++ b/src/main/kotlin/io/delilaheve/util/EnchantmentUtil.kt
@@ -4,9 +4,9 @@ import io.delilaheve.CustomAnvil
import org.bukkit.entity.HumanEntity
import org.bukkit.inventory.ItemStack
import xyz.alexcrea.cuanvil.config.ConfigHolder
-import xyz.alexcrea.cuanvil.dependency.DependencyManager
import xyz.alexcrea.cuanvil.enchant.CAEnchantment
import xyz.alexcrea.cuanvil.group.ConflictType
+import xyz.alexcrea.cuanvil.util.MaterialUtil.customType
import kotlin.math.max
import kotlin.math.min
@@ -31,87 +31,97 @@ object EnchantmentUtil {
) = mutableMapOf().apply {
putAll(this@combineWith)
+ CustomAnvil.verboseLog("Testing merge")
val bypassFuse = player.hasPermission(CustomAnvil.bypassFusePermission)
val bypassLevel = player.hasPermission(CustomAnvil.bypassLevelPermission)
- var maxEnchantCount = ConfigOptions.getEnchantCountLimit(item.type)
+ var maxEnchantCount = ConfigOptions.getEnchantCountLimit(item.customType)
if(maxEnchantCount == null || maxEnchantCount < 0) maxEnchantCount = Int.MAX_VALUE
- other.forEach { (enchantment, level) ->
- if(!enchantment.isAllowed(player)) return@forEach
+ val allowed = other.filter { (enchantment, _) -> enchantment.isAllowed(player) }
+ val new = allowed.filter{ (enchantment, _) -> !containsKey(enchantment)}
+ val old = allowed.filter{ (enchantment, _) -> containsKey(enchantment)}
- // Get max level or 255 if player can bypass
- val maxLevel = if (bypassLevel) { 255 }
+ fun maxLevel(enchantment: CAEnchantment): Int {
+ val max = if (bypassLevel) { 255 }
else { ConfigOptions.enchantLimit(enchantment) }
- CustomAnvil.verboseLog("Max level of ${enchantment.key} is $maxLevel (bypassLevel is $bypassLevel)")
+ CustomAnvil.verboseLog("Max level of ${enchantment.key} is $max (bypassLevel is $bypassLevel)")
+ return max
+ }
+
+ old.forEach { (enchantment, level) ->
+ // Get max level or 255 if player can bypass
+ val maxLevel = maxLevel(enchantment)
val cappedLevel = min(level, maxLevel)
- // Enchantment not yet in result list
- if (!containsKey(enchantment)) {
- // Do not allow new enchantment if above maximum
- if(this.size >= maxEnchantCount) return@forEach
-
- // Add the enchantment if it doesn't have conflicts, or if player is allowed to bypass enchantment restrictions
- this[enchantment] = cappedLevel
- if(bypassFuse){
- CustomAnvil.verboseLog("Bypassed conflict check for ${enchantment.key}")
- return@forEach
- }
-
- val conflictType = ConfigHolder.CONFLICT_HOLDER.conflictManager
- .isConflicting(this, item, enchantment)
-
- if (conflictType != ConflictType.NO_CONFLICT) {
- CustomAnvil.verboseLog("Enchantment not yet in result list, but there is conflict (${enchantment.key}, conflict: $conflictType)")
- this.remove(enchantment)
- }
+ val oldLevel = this[enchantment]!! // <- should not be null. (enchantment already in result list)
+ // ... and they're not the same level
+ if (oldLevel != cappedLevel) {
+ // apply the greater of the two or left one if right is above max
+ this[enchantment] = max(oldLevel, cappedLevel)
}
- // Enchantment already in result list
+ // ... and they're the same level
else {
- val oldLevel = this[enchantment]!! // <- should not be null. (enchantment already in result list)
-
- if(bypassFuse){
- CustomAnvil.verboseLog("Bypassed conflict check for ${enchantment.key}")
- } else {
- val conflictType = ConfigHolder.CONFLICT_HOLDER.conflictManager
- .isConflicting(this, item, enchantment)
-
- // ... and they are conflicting
- if(conflictType != ConflictType.NO_CONFLICT){
+ // We test if it is allowed to merge at this level
+ if(!bypassLevel){
+ val maxBeforeDisabled = ConfigOptions.maxBeforeMergeDisabled(enchantment)
+ if((maxBeforeDisabled > 0) && (oldLevel >= maxBeforeDisabled)) {
CustomAnvil.verboseLog(
- "Enchantment already in result list, and they are conflicting (${enchantment.key}, conflict: $conflictType)")
+ "Reached max merge before disable for ${enchantment.key}: $oldLevel/$maxBeforeDisabled)")
return@forEach
}
}
- // ... and they're not the same level
- if (oldLevel != cappedLevel) {
- // apply the greater of the two or left one if right is above max
- this[enchantment] = max(oldLevel, cappedLevel)
+ // Now we increase the enchantment level by 1
+ var newLevel = oldLevel + 1
+ newLevel = max(min(newLevel, maxLevel), oldLevel)
+ this[enchantment] = newLevel
+ }
- }
- // ... and they're the same level
- else {
- // We test if it is allowed to merge at this level
- if(!bypassLevel){
- val maxBeforeDisabled = ConfigOptions.maxBeforeMergeDisabled(enchantment)
- if((maxBeforeDisabled > 0) && (oldLevel >= maxBeforeDisabled)) {
- CustomAnvil.verboseLog(
- "Reached max merge before disable for ${enchantment.key}: $oldLevel/$maxBeforeDisabled)")
- return@forEach
- }
- }
-
- // Now we increase the enchantment level by 1
- var newLevel = oldLevel + 1
- newLevel = max(min(newLevel, maxLevel), oldLevel)
- this[enchantment] = newLevel
+ if(bypassFuse){
+ CustomAnvil.verboseLog("Bypassed conflict check for ${enchantment.key}")
+ } else {
+ val conflictType = ConfigHolder.CONFLICT_HOLDER.conflictManager
+ .isConflicting(this, item, enchantment)
+ // ... and they are conflicting
+ if(conflictType != ConflictType.NO_CONFLICT){
+ CustomAnvil.verboseLog(
+ "Enchantment already in result list, and they are conflicting (${enchantment.key}, conflict: $conflictType)")
+ this[enchantment] = oldLevel
+ return@forEach
}
}
}
+
+ // Try to add new now
+ new.forEach { (enchantment, level) ->
+ // Get max level or 255 if player can bypass
+ val maxLevel = maxLevel(enchantment)
+ val cappedLevel = min(level, maxLevel)
+
+ // Do not allow new enchantment if above maximum
+ if(this.size >= maxEnchantCount) return@forEach
+
+ // Add the enchantment if it doesn't have conflicts, or if player is allowed to bypass enchantment restrictions
+ this[enchantment] = cappedLevel
+ if(bypassFuse){
+ CustomAnvil.verboseLog("Bypassed conflict check for ${enchantment.key}")
+ return@forEach
+ }
+
+ val conflictType = ConfigHolder.CONFLICT_HOLDER.conflictManager
+ .isConflicting(this, item, enchantment)
+
+ if (conflictType != ConflictType.NO_CONFLICT) {
+ CustomAnvil.verboseLog("Enchantment not yet in result list, but there is conflict (${enchantment.key}, conflict: $conflictType)")
+ this.remove(enchantment)
+ }
+
+ }
+
}
}
diff --git a/src/main/kotlin/io/delilaheve/util/ItemUtil.kt b/src/main/kotlin/io/delilaheve/util/ItemUtil.kt
index a85af39..25698ad 100644
--- a/src/main/kotlin/io/delilaheve/util/ItemUtil.kt
+++ b/src/main/kotlin/io/delilaheve/util/ItemUtil.kt
@@ -4,6 +4,9 @@ import org.bukkit.Material.ENCHANTED_BOOK
import org.bukkit.inventory.ItemStack
import org.bukkit.inventory.meta.Damageable
import xyz.alexcrea.cuanvil.enchant.CAEnchantment
+import xyz.alexcrea.cuanvil.update.UpdateUtils
+import xyz.alexcrea.cuanvil.util.MaterialUtil.customType
+import xyz.alexcrea.cuanvil.util.MaxDamageCheckerUtil
import kotlin.math.ceil
import kotlin.math.max
import kotlin.math.min
@@ -35,6 +38,13 @@ object ItemUtil {
}
+ private fun maxDamage(damageable: Damageable): Int {
+ val ver = UpdateUtils.currentMinecraftVersion()
+ if(ver.major <= 1 && ver.minor <= 20 && ver.patch < 5) return Integer.MAX_VALUE
+
+ return MaxDamageCheckerUtil.getMaxDamage(damageable)
+ }
+
/**
* Set this [ItemStack]s durability from a combination of the
* [first] and [second] item's durability values
@@ -54,7 +64,9 @@ object ItemUtil {
val secondDurability = durability - secondDamage
val combinedDurability = firstDurability + secondDurability
val newDurability = min(combinedDurability, durability)
- it.damage = durability - newDurability
+
+ val maxDamage = maxDamage(it)
+ it.damage = min(durability - newDurability, maxDamage)
itemMeta = it
return true
}
@@ -90,5 +102,5 @@ object ItemUtil {
*/
fun ItemStack.canMergeWith(
other: ItemStack?
- ) = (other != null) && (type == other.type || (other.isEnchantedBook()))
+ ) = (other != null) && (customType == other.customType || (other.isEnchantedBook()))
}
diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilCost.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilCost.kt
new file mode 100644
index 0000000..710687c
--- /dev/null
+++ b/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilCost.kt
@@ -0,0 +1,72 @@
+package xyz.alexcrea.cuanvil.anvil
+
+import io.delilaheve.util.ConfigOptions
+import java.math.BigDecimal
+import kotlin.math.min
+import io.delilaheve.util.ConfigOptions.getMonetaryMultiplier as moneyMultiplier
+
+open class AnvilCost {
+ private val isAlone: Boolean
+ var valid = true // Get set as invalid if cost can be satisfied
+ var isMonetary = false
+
+ var generic = 0
+ var enchantment = 0
+ var repair = 0
+ var rename = 0
+ var lore = 0
+ var illegalPenalty = 0
+ var workPenalty = 0
+ var recipe = 0
+
+ constructor(generic: Int) {
+ this.generic = generic
+ isAlone = true
+ }
+
+ constructor() {
+ isAlone = false
+ }
+
+ fun asXpCost(): Int {
+ return generic + enchantment + repair + rename + lore + illegalPenalty + workPenalty + recipe
+ }
+
+ fun filteredXpCost(ignoreRules: Boolean = false): Int {
+ val original = asXpCost()
+
+ // Test repair cost limit
+ return if (
+ !ignoreRules &&
+ !ConfigOptions.doRemoveCostLimit &&
+ ConfigOptions.doCapCost
+ ) {
+ min(original, ConfigOptions.maxAnvilCost)
+ } else {
+ original
+ }
+ }
+
+ open fun asMonetaryCost(): BigDecimal {
+ // multiply by per use type multipliers
+ return BigDecimal(generic)
+ .add(BigDecimal(enchantment).multiply(moneyMultiplier("enchantment")))
+ .add(BigDecimal(repair).multiply(moneyMultiplier("repair")))
+ .add(BigDecimal(rename).multiply(moneyMultiplier("rename")))
+ .add(BigDecimal(lore).multiply(moneyMultiplier("lore_edit")))
+ .add(BigDecimal(enchantment).multiply(moneyMultiplier("enchantment")))
+ .add(BigDecimal(illegalPenalty).multiply(moneyMultiplier("work_penalty")))
+ .add(BigDecimal(workPenalty).multiply(moneyMultiplier("work_penalty")))
+ .add(BigDecimal(recipe).multiply(moneyMultiplier("recipe")))
+ .multiply(moneyMultiplier("global"))
+ }
+}
+
+class CustomCraftCost(val rawCost: Int): AnvilCost() {
+
+ override fun asMonetaryCost(): BigDecimal {
+ return BigDecimal(rawCost)
+ .multiply(moneyMultiplier("global"))
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilMergeLogic.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilMergeLogic.kt
new file mode 100644
index 0000000..6b106fc
--- /dev/null
+++ b/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilMergeLogic.kt
@@ -0,0 +1,331 @@
+package xyz.alexcrea.cuanvil.anvil
+
+import io.delilaheve.CustomAnvil
+import io.delilaheve.util.ConfigOptions
+import io.delilaheve.util.EnchantmentUtil.combineWith
+import io.delilaheve.util.ItemUtil.findEnchantments
+import io.delilaheve.util.ItemUtil.isEnchantedBook
+import io.delilaheve.util.ItemUtil.repairFrom
+import io.delilaheve.util.ItemUtil.setEnchantmentsUnsafe
+import io.delilaheve.util.ItemUtil.unitRepair
+import org.bukkit.ChatColor
+import org.bukkit.Material
+import org.bukkit.entity.HumanEntity
+import org.bukkit.entity.Player
+import org.bukkit.inventory.AnvilInventory
+import org.bukkit.inventory.InventoryView
+import org.bukkit.inventory.ItemStack
+import org.bukkit.inventory.meta.ItemMeta
+import org.bukkit.persistence.PersistentDataType
+import xyz.alexcrea.cuanvil.dependency.DependencyManager
+import xyz.alexcrea.cuanvil.dialog.AnvilRenameDialog
+import xyz.alexcrea.cuanvil.enchant.CAEnchantment
+import xyz.alexcrea.cuanvil.recipe.AnvilCustomRecipe
+import xyz.alexcrea.cuanvil.util.CasedStringUtil
+import xyz.alexcrea.cuanvil.util.CustomRecipeUtil
+import xyz.alexcrea.cuanvil.util.MaterialUtil.isAir
+import xyz.alexcrea.cuanvil.util.MiniMessageUtil
+import xyz.alexcrea.cuanvil.util.UnitRepairUtil.getRepair
+import xyz.alexcrea.cuanvil.util.anvil.AnvilColorUtil
+import xyz.alexcrea.cuanvil.util.anvil.AnvilLoreEditUtil
+import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil
+import xyz.alexcrea.cuanvil.util.config.LoreEditType
+import xyz.alexcrea.cuanvil.util.dialog.AnvilRenameDialogUtil
+
+object AnvilMergeLogic {
+
+ open class AnvilResult {
+ companion object {
+ val EMPTY = AnvilResult(null, AnvilCost())
+ }
+
+ val item: ItemStack?
+ val cost: AnvilCost
+ val ignoreXpRules: Boolean
+
+ constructor(item: ItemStack?, cost: AnvilCost, ignoreXpRules: Boolean = false) {
+ this.item = item
+ this.cost = cost
+ this.ignoreXpRules = ignoreXpRules
+ }
+
+ fun isEmpty(): Boolean {
+ return item == null
+ }
+ }
+
+ class UnitRepairResult : AnvilResult {
+ companion object {
+ val EMPTY = UnitRepairResult(null, AnvilCost(), 0)
+ }
+
+ val repairAmount: Int
+
+ constructor(item: ItemStack?, cost: AnvilCost, repairAmount: Int) : super(item, cost) {
+ this.repairAmount = repairAmount
+ }
+ }
+
+ class CustomCraftResult : AnvilResult {
+ companion object {
+ val EMPTY = CustomCraftResult(null, CustomCraftCost(0), 0, null)
+ }
+
+ val customCraftCost: CustomCraftCost
+ val amount: Int
+ val recipe: AnvilCustomRecipe?
+
+ constructor(
+ item: ItemStack?, cost: CustomCraftCost,
+ amount: Int, recipe: AnvilCustomRecipe?
+ ) : super(item, cost, true) {
+ this.customCraftCost = cost
+ this.amount = amount
+ this.recipe = recipe
+ }
+ }
+
+ class LoreEditResult : AnvilResult {
+ companion object {
+ val EMPTY = LoreEditResult(null, AnvilCost(), LoreEditType.APPEND_PAPER)
+ }
+
+ val type: LoreEditType
+
+ constructor(item: ItemStack?, cost: AnvilCost, type: LoreEditType) : super(item, cost) {
+ this.type = type
+ }
+ }
+
+ fun doRenaming(
+ view: InventoryView, //TODO use anvil view
+ inventory: AnvilInventory,
+ player: Player, first: ItemStack
+ ): AnvilResult {
+ val resultItem = DependencyManager.cloneItem(player, first)
+ val cost = AnvilCost()
+ cost.rename = handleRename(resultItem, inventory, player)
+
+ // Test/stop if nothing changed.
+ if (first == resultItem) {
+ CustomAnvil.log("no right item, But input is same as output")
+ return AnvilResult.EMPTY
+ }
+
+ cost.workPenalty = AnvilXpUtil.calculatePenalty(first, null, resultItem, AnvilUseType.RENAME_ONLY)
+ val result =
+ DependencyManager.tryTreatAnvilResult(view, inventory, player, resultItem, AnvilUseType.RENAME_ONLY, cost)
+
+ return AnvilResult(result, cost)
+ }
+
+ private fun processDialogPCD(meta: ItemMeta, player: HumanEntity) {
+ val text = AnvilRenameDialogUtil.anvilRenameDialog.currentText(player)
+ return processPCD(meta, player, text)
+ }
+
+ fun processPCD(meta: ItemMeta, player: HumanEntity, text: String?) {
+ val keepDialog = ConfigOptions.canUseDialogRename(player) && ConfigOptions.shouldKeepRenameText
+
+ val pdc = meta.persistentDataContainer
+ if (!keepDialog)
+ pdc.remove(AnvilRenameDialog.PCD_KEEP_RENAME_TEXT_KEY)
+ else {
+ if (text == null || text.isBlank())
+ pdc.remove(AnvilRenameDialog.PCD_KEEP_RENAME_TEXT_KEY)
+ else pdc.set(AnvilRenameDialog.PCD_KEEP_RENAME_TEXT_KEY, PersistentDataType.STRING, text)
+ }
+ }
+
+ private fun handleRename(resultItem: ItemStack, inventory: AnvilInventory, player: HumanEntity): Int {
+ // Can be null
+ var renameText = ChatColor.stripColor(inventory.renameText)
+
+ var sumCost = 0
+ var useColor = false
+ if (ConfigOptions.renameColorPossible && renameText != null) {
+ val component = AnvilColorUtil.handleColor(
+ renameText,
+ AnvilColorUtil.renamePermission(player)
+ )
+
+ if (component != null) {
+ renameText = MiniMessageUtil.legacy_mm.serialize(component)
+
+ sumCost += ConfigOptions.useOfColorCost
+ useColor = true
+ }
+ }
+
+ // Rename item and add renaming cost
+ resultItem.itemMeta?.let {
+ val hasDisplayName = it.hasDisplayName()
+ val displayName = if (!hasDisplayName) null
+ else if (useColor) it.displayName
+ else ChatColor.stripColor(it.displayName)
+
+
+ if (!displayName.contentEquals(renameText) && !(displayName == null &&
+ renameText == "" ||
+ //TODO on recent paper check effective name instead
+ renameText == CasedStringUtil.snakeToUpperSpacedCase(resultItem.type.name.lowercase())
+ )
+ ) {
+ it.setDisplayName(renameText)
+ processDialogPCD(it, player)
+ resultItem.itemMeta = it
+
+ sumCost += ConfigOptions.itemRenameCost
+ }
+
+ return sumCost
+ }
+ return 0
+ }
+
+ fun doMerge(
+ view: InventoryView, //TODO use anvil view instead
+ inventory: AnvilInventory,
+ player: Player,
+ first: ItemStack, second: ItemStack
+ ): AnvilResult {
+ val newEnchants = first.findEnchantments()
+ .combineWith(second.findEnchantments(), first, player)
+ var hasChanged = !isIdentical(first.findEnchantments(), newEnchants)
+
+ val resultItem = DependencyManager.cloneItem(player, first)
+ val cost = AnvilCost()
+ if (hasChanged) {
+ resultItem.setEnchantmentsUnsafe(newEnchants)
+ // Calculate enchantment cost
+ AnvilXpUtil.getRightValues(second, resultItem, cost)
+ }
+
+ // Calculate repair cost
+ if (!first.isEnchantedBook() && !second.isEnchantedBook()) {
+ // we only need to be concerned with repair when neither item is a book
+ val repaired = resultItem.repairFrom(first, second)
+ cost.repair = if (repaired) ConfigOptions.itemRepairCost else 0
+ hasChanged = hasChanged || repaired
+ }
+
+ // Test/stop if nothing changed.
+ if (!hasChanged) {
+ CustomAnvil.log("Mergeable with second, But input is same as output")
+ return AnvilResult.EMPTY
+ }
+ // As calculatePenalty edit result, we need to calculate penalty after checking equality
+ cost.workPenalty = AnvilXpUtil.calculatePenalty(first, second, resultItem, AnvilUseType.MERGE)
+ // Calculate rename cost
+ cost.rename = handleRename(resultItem, inventory, player)
+
+ val result =
+ DependencyManager.tryTreatAnvilResult(view, inventory, player, resultItem, AnvilUseType.MERGE, cost)
+
+ return AnvilResult(result, cost)
+ }
+
+ private fun isIdentical(
+ firstEnchants: MutableMap,
+ resultEnchants: MutableMap
+ ): Boolean {
+ if (firstEnchants.size != resultEnchants.size) return false
+ for (entry in resultEnchants) {
+ if (firstEnchants.getOrDefault(entry.key, entry.value - 1) != entry.value) return false
+ }
+
+ return true
+ }
+
+ // return true if a custom recipe exist with these ingredients
+ fun testCustomRecipe(
+ view: InventoryView, //TODO use anvil view instead
+ inventory: AnvilInventory,
+ player: Player,
+ first: ItemStack, second: ItemStack?
+ ): CustomCraftResult {
+ val recipe = CustomRecipeUtil.getCustomRecipe(first, second)
+ CustomAnvil.verboseLog("custom recipe not null? ${recipe != null}")
+ if (recipe == null) return CustomCraftResult.EMPTY
+
+ val amount = CustomRecipeUtil.getCustomRecipeAmount(recipe, first, second)
+
+ val resultItem: ItemStack = DependencyManager.cloneItem(player, recipe.resultItem!!)
+ resultItem.amount *= amount
+
+ // Maybe add an option on custom craft to ignore/not ignore penalty ??
+ val xpCost = recipe.determineCost(amount, first, resultItem)
+
+ val cost = CustomCraftCost(xpCost)
+ // This is for displayed cost
+ cost.recipe = if (recipe.removeExactLinearXp) AnvilXpUtil.calculateMinimumLevelForXp(xpCost)
+ else AnvilXpUtil.calculateLevelForXp(xpCost)
+
+ val result =
+ DependencyManager.tryTreatAnvilResult(view, inventory, player, resultItem, AnvilUseType.CUSTOM_CRAFT, cost)
+ return CustomCraftResult(result, cost, amount, recipe)
+ }
+
+ fun testUnitRepair(
+ view: InventoryView, //TODO use anvil view
+ inventory: AnvilInventory,
+ player: Player,
+ first: ItemStack, second: ItemStack
+ ): UnitRepairResult {
+ val unitRepairAmount = first.getRepair(second) ?: return UnitRepairResult.EMPTY
+
+ return testUnitRepair(view, inventory, player, first, second, unitRepairAmount)
+ }
+
+ fun testUnitRepair(
+ view: InventoryView, //TODO use anvil view instead
+ inventory: AnvilInventory,
+ player: Player,
+ first: ItemStack, second: ItemStack,
+ unitRepairAmount: Double
+ ): UnitRepairResult {
+ val resultItem = DependencyManager.cloneItem(player, first)
+ val cost = AnvilCost()
+ cost.rename = handleRename(resultItem, inventory, player)
+
+ val repairAmount = resultItem.unitRepair(second.amount, unitRepairAmount)
+ if (repairAmount > 0)
+ cost.repair = repairAmount * ConfigOptions.unitRepairCost
+
+ // We do not care about right item penalty for unit repair
+ cost.workPenalty = AnvilXpUtil.calculatePenalty(first, null, resultItem, AnvilUseType.UNIT_REPAIR)
+
+ // Test/stop if nothing changed.
+ if (first == resultItem) {
+ CustomAnvil.log("unit repair, But input is same as output")
+ return UnitRepairResult.EMPTY
+ }
+
+ val result =
+ DependencyManager.tryTreatAnvilResult(view, inventory, player, resultItem, AnvilUseType.UNIT_REPAIR, cost)
+ return UnitRepairResult(result, cost, repairAmount)
+ }
+
+ fun testLoreEdit(
+ player: Player,
+ first: ItemStack, second: ItemStack
+ ): LoreEditResult {
+ val type = second.type
+
+ val result = if (Material.WRITABLE_BOOK == type)
+ AnvilLoreEditUtil.tryLoreEditByBook(player, first, second)
+ else if (Material.PAPER == type)
+ AnvilLoreEditUtil.tryLoreEditByPaper(player, first, second)
+ else LoreEditResult.EMPTY
+
+ if (result.isEmpty()) return result
+
+ if (result.item!!.isAir || first == result.item) {
+ CustomAnvil.log("lore edit, But input is same as output")
+ return LoreEditResult.EMPTY
+ }
+
+ return result
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilUseType.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilUseType.kt
similarity index 66%
rename from src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilUseType.kt
rename to src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilUseType.kt
index d17e908..67782f3 100644
--- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilUseType.kt
+++ b/src/main/kotlin/xyz/alexcrea/cuanvil/anvil/AnvilUseType.kt
@@ -1,60 +1,60 @@
-package xyz.alexcrea.cuanvil.util
+package xyz.alexcrea.cuanvil.anvil
import org.bukkit.Material
-import xyz.alexcrea.cuanvil.config.WorkPenaltyType.WorkPenaltyPart
-import xyz.alexcrea.cuanvil.util.config.LoreEditType
+import xyz.alexcrea.cuanvil.config.WorkPenaltyType
+import xyz.alexcrea.cuanvil.util.anvil.AnvilUseTypeUtil
enum class AnvilUseType(
val typeName: String, val path: String,
- val defaultPenalty: WorkPenaltyPart,
+ val defaultPenalty: WorkPenaltyType.WorkPenaltyPart,
val displayName: String, val displayMat: Material
) {
RENAME_ONLY(
"rename_only",
- WorkPenaltyPart(false, true),
+ WorkPenaltyType.WorkPenaltyPart(false, true),
"Rename Only", Material.NAME_TAG
),
MERGE(
"merge",
- WorkPenaltyPart(true, true),
+ WorkPenaltyType.WorkPenaltyPart(true, true),
"Merge", Material.ANVIL
),
UNIT_REPAIR(
"unit_repair",
- WorkPenaltyPart(true, true),
+ WorkPenaltyType.WorkPenaltyPart(true, true),
"Unit Repair", Material.DIAMOND
),
CUSTOM_CRAFT(
"custom_craft",
- WorkPenaltyPart(false, false),
+ WorkPenaltyType.WorkPenaltyPart(false, false),
"Custom Craft", Material.CRAFTING_TABLE
),
LORE_EDIT_BOOK_APPEND(
"lore_edit_book_append", "lore_edit.book_and_quil.append",
- WorkPenaltyPart(false, false),
+ WorkPenaltyType.WorkPenaltyPart(false, false),
"Book Add", Material.WRITABLE_BOOK
),
LORE_EDIT_BOOK_REMOVE(
"lore_edit_book_remove", "lore_edit.book_and_quil.remove",
- WorkPenaltyPart(false, false),
+ WorkPenaltyType.WorkPenaltyPart(false, false),
"Book Remove", Material.WRITABLE_BOOK
),
LORE_EDIT_PAPER_APPEND(
"lore_edit_paper_append", "lore_edit.paper.append_line",
- WorkPenaltyPart(false, false),
+ WorkPenaltyType.WorkPenaltyPart(false, false),
"Paper Add", Material.WRITABLE_BOOK
),
LORE_EDIT_PAPER_REMOVE(
"lore_edit_paper_remove", "lore_edit.paper.remove_line",
- WorkPenaltyPart(false, false),
+ WorkPenaltyType.WorkPenaltyPart(false, false),
"Paper Remove", Material.WRITABLE_BOOK
),
;
constructor(
typeName: String,
- defaultPenalty: WorkPenaltyPart,
+ defaultPenalty: WorkPenaltyType.WorkPenaltyPart,
displayName: String, displayMat: Material
) :
this(
diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/CASubCommand.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/CASubCommand.kt
new file mode 100644
index 0000000..4c71c68
--- /dev/null
+++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/CASubCommand.kt
@@ -0,0 +1,46 @@
+package xyz.alexcrea.cuanvil.command
+
+import org.bukkit.ChatColor
+import org.bukkit.command.Command
+import org.bukkit.command.CommandExecutor
+import org.bukkit.command.CommandSender
+
+abstract class CASubCommand: CommandExecutor {
+
+ private var alreadySaid = false;
+ override fun onCommand(
+ sender: CommandSender,
+ cmd: Command,
+ cmdstr: String,
+ args: Array
+ ): Boolean {
+ if(!alreadySaid){
+ sender.sendMessage(ChatColor.RED.toString() +
+ "Please not that this command will be replaced as a subcommand of `/customanvil` or `/ca`")
+ alreadySaid = true
+ }
+
+ return executeCommand(sender, cmd, cmdstr, args)
+ }
+
+ abstract fun executeCommand(
+ sender: CommandSender,
+ cmd: Command,
+ cmdstr: String,
+ args: Array): Boolean
+
+ open fun allowed(sender: CommandSender): Boolean {
+ return true
+ }
+
+ open fun tabCompleter(
+ sender: CommandSender,
+ args: Array,
+ list: MutableList) {
+ }
+
+ open fun description(): String {
+ return "no description"
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/CustomAnvilCommand.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/CustomAnvilCommand.kt
new file mode 100644
index 0000000..e5e689e
--- /dev/null
+++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/CustomAnvilCommand.kt
@@ -0,0 +1,96 @@
+package xyz.alexcrea.cuanvil.command
+
+import com.google.common.collect.ImmutableMap
+import io.delilaheve.CustomAnvil
+import org.bukkit.command.Command
+import org.bukkit.command.CommandExecutor
+import org.bukkit.command.CommandSender
+import org.bukkit.command.TabCompleter
+import xyz.alexcrea.cuanvil.util.MetricsUtil
+import java.util.ArrayList
+
+class CustomAnvilCommand(plugin: CustomAnvil) : CommandExecutor, TabCompleter {
+
+ // Name of the generic command
+ companion object {
+ private const val genericCommandName = "customanvil"
+ }
+
+ private val editConfigCommand = EditConfigExecutor()
+ private val helpCommand = HelpExecutor()
+ private val commands = ImmutableMap.of(
+ "gui", editConfigCommand,
+ "reload", ReloadExecutor(),
+ "diagnostic", DiagnosticExecutor(),
+ "help", helpCommand,
+ )
+
+ init {
+ val self = plugin.getCommand(genericCommandName)!!
+ self.setExecutor(this)
+ self.tabCompleter = this
+
+ helpCommand.commands = commands
+ }
+
+ override fun onCommand(
+ sender: CommandSender,
+ cmd: Command,
+ cmdstr: String,
+ args: Array
+ ): Boolean {
+ // Find sub command to execute based on the provided command name
+ val subcmd: CASubCommand?
+ val newargs: Array
+ if(args.isEmpty()) {
+ subcmd = editConfigCommand
+ newargs = args
+ }else {
+ subcmd = commands[args[0].lowercase()]
+ newargs = args.copyOfRange(1, args.size)
+ }
+
+ if(subcmd == null || !subcmd.allowed(sender)) {
+ sender.sendMessage("Invalid subcommand. run `$cmdstr help` to see available commands")
+ return true
+ }
+
+ try {
+ return subcmd.executeCommand(sender, cmd, cmdstr, newargs)
+ } catch (e: Throwable) {
+ MetricsUtil.trackError(e)
+ sender.sendMessage("§cError running this command")
+ return false
+ }
+ }
+
+ override fun onTabComplete(
+ sender: CommandSender,
+ cmd: Command,
+ cmdstr: String,
+ args: Array
+ ): MutableList {
+ val result = ArrayList()
+ if(args.size < 2) {
+ for ((key, cmd) in commands) {
+ if(!cmd.allowed(sender)) continue
+ result.add(key)
+ }
+ } else {
+ val subcmd = commands[args[0].lowercase()]
+
+ if(subcmd != null) {
+ val newArgs = args.copyOfRange(1, args.size)
+ if(!subcmd.allowed(sender)) return result
+
+ subcmd.tabCompleter(sender, newArgs, result)
+ }
+ }
+
+ //assumed all provided tab completed string are lowercase
+ return result.stream()
+ .filter { it.startsWith(args[args.size - 1]) }
+ .sorted()
+ .toList()
+ }
+}
diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/DiagnosticExecutor.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/DiagnosticExecutor.kt
new file mode 100644
index 0000000..053a3fc
--- /dev/null
+++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/DiagnosticExecutor.kt
@@ -0,0 +1,347 @@
+package xyz.alexcrea.cuanvil.command
+
+import com.github.stefvanschie.inventoryframework.inventoryview.interface_.InventoryViewUtil
+import io.delilaheve.CustomAnvil
+import net.md_5.bungee.api.chat.ClickEvent
+import net.md_5.bungee.api.chat.HoverEvent
+import net.md_5.bungee.api.chat.TextComponent
+import net.md_5.bungee.api.chat.hover.content.Text
+import org.bukkit.Bukkit
+import org.bukkit.ChatColor
+import org.bukkit.Material
+import org.bukkit.command.Command
+import org.bukkit.command.CommandSender
+import org.bukkit.enchantments.Enchantment
+import org.bukkit.entity.HumanEntity
+import org.bukkit.entity.Player
+import org.bukkit.event.inventory.InventoryType
+import org.bukkit.event.inventory.PrepareAnvilEvent
+import org.bukkit.inventory.AnvilInventory
+import org.bukkit.inventory.InventoryView
+import org.bukkit.inventory.ItemStack
+import org.bukkit.inventory.meta.Damageable
+import org.bukkit.inventory.meta.EnchantmentStorageMeta
+import org.bukkit.plugin.Plugin
+import org.bukkit.plugin.RegisteredListener
+import xyz.alexcrea.cuanvil.dependency.DependencyManager
+import xyz.alexcrea.cuanvil.dependency.packet.NoPacketManager
+import xyz.alexcrea.cuanvil.dependency.packet.ProtocoLibWrapper
+import xyz.alexcrea.cuanvil.enchant.CAEnchantmentRegistry
+import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener
+import xyz.alexcrea.cuanvil.util.MetricsUtil
+import java.util.*
+import java.util.stream.Collectors
+
+
+class DiagnosticExecutor: CASubCommand() {
+
+ companion object{
+ private const val NO_DIAG_PERM = "You do not have permission to diagnostic this server"
+
+ fun fetchNMSType(): String {
+ val packetManager = DependencyManager.packetManager
+ val packetManagerClass = packetManager.javaClass
+
+ val className = packetManagerClass.name
+ val result = if(className.contains("PaperPacket")) {
+ "Paper"
+ } else {
+ when (packetManagerClass) {
+ ProtocoLibWrapper::class.java -> "Protocolib"
+ NoPacketManager::class.java -> "None"
+ else -> "Version Specific"
+ }
+ }
+
+
+ return "$result ${if(packetManager.canSetInstantBuild) '✅' else '❌'}"
+ }
+ }
+
+ enum class DiagParams(val value: String) {
+ OS_PRIVACY("os_privacy"),
+ PLUGIN_PRIVACY("plugin_privacy"),
+ NO_MERGE_TEST("no_merge_test"),
+ FULL_ENCHANTMENT_DATA("full_enchantment_data"),
+ INCLUDE_LAST_ERROR("include_last_error"),
+ }
+
+ private fun fetchParameters(args: Array): EnumSet {
+ val result = EnumSet.noneOf(DiagParams::class.java)
+ val argSet = HashSet()
+
+ for (string in args) {
+ argSet.add(string.lowercase())
+ }
+
+ for (param in DiagParams.entries) {
+ if(argSet.contains(param.value))
+ result.add(param)
+ }
+
+ return result
+ }
+
+ override fun tabCompleter(
+ sender: CommandSender,
+ args: Array,
+ list: MutableList) {
+ if(!allowed(sender)) return
+
+ val map = fetchParameters(args)
+ for (param in DiagParams.entries) {
+ if(!map.contains(param))
+ list.add(param.value)
+ }
+
+ }
+
+ override fun executeCommand(
+ sender: CommandSender,
+ cmd: Command,
+ cmdstr: String,
+ args: Array
+ ): Boolean {
+ if (!allowed(sender)) {
+ sender.sendMessage(NO_DIAG_PERM)
+ return false
+ }
+
+ val stb = StringBuilder("```\n")
+ val params = fetchParameters(args)
+ var hasError = false
+ try {
+ diagnostic(sender, stb, params)
+ } catch(e: Throwable){
+ stb.append("\n\nError happened trying to get diagnostic data:\n")
+ .append(e.message).append("\n")
+ .append(e.stackTrace.joinToString("\n"))
+ hasError = true
+ e.printStackTrace()
+ }
+
+ stb.append("\n```")
+
+ if (sender is HumanEntity) {
+ if(hasError)
+ sender.spigot().sendMessage(TextComponent(ChatColor.RED.toString() + "There was an error running the diagnostic"))
+ val message = TextComponent(ChatColor.GREEN.toString() + "Click to copy diagnostic data")
+
+ message.clickEvent = ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, stb.toString())
+ message.hoverEvent = HoverEvent(HoverEvent.Action.SHOW_TEXT, Text("§7Click to copy"))
+
+ sender.spigot().sendMessage(message);
+ } else {
+ sender.sendMessage(stb.toString());
+ }
+
+ return true
+ }
+
+ override fun allowed(sender: CommandSender): Boolean {
+ return sender.hasPermission(CustomAnvil.diagnosticPermission)
+ }
+
+ fun diagnostic(sender: CommandSender, stb: StringBuilder, params: Set){
+ stb.append("Server Info\n")
+ val version = CustomAnvil.instance.description.version
+ stb.append("\nPlugin Version: ").append(version)
+ if(version.contains("dev")) stb.append(" (alpha)")
+
+ stb.append("\nLatest Update: ").append(CustomAnvil.latestVer)
+ stb.append("\nServer Version: ").append(Bukkit.getVersion()).append(" (").append(Bukkit.getName()).append(')')
+ stb.append("\nPlugin Enabled: ").append(if(CustomAnvil.instance.isEnabled) "Yes" else "No")
+ stb.append("\nNMS type: ").append(fetchNMSType())
+ if(!params.contains(DiagParams.OS_PRIVACY)) {
+ stb.append("\nJava Version: ").append(System.getProperty("java.version"))
+ stb.append("\nOS: ").append(System.getProperty("os.name")).append(" ")
+ .append(System.getProperty("os.version"))
+ .append(System.getProperty("os.arch"))
+ }
+
+ stb.append("\nHad detect error: ").append(if(MetricsUtil.lastError != null) "Yes" else "No")
+
+ if(!params.contains(DiagParams.PLUGIN_PRIVACY)) {
+ pluginListDiag(sender, stb)
+ }
+ prepareAnvilListeners(stb)
+
+ if(!params.contains(DiagParams.NO_MERGE_TEST)){
+ if(sender is Player) testMerge(sender, stb)
+ }
+
+ stb.append("\n\nEnchantments data:")
+ partialEnchantmentData(stb)
+ if(params.contains(DiagParams.FULL_ENCHANTMENT_DATA)){
+ fullEnchantmentData(stb)
+ }
+
+ if(params.contains(DiagParams.INCLUDE_LAST_ERROR)){
+ includeLastError(stb)
+ }
+ }
+
+ private fun testMerge(player: Player, stb: StringBuilder) {
+ val sword = ItemStack(Material.DIAMOND_SWORD)
+ val damagedSword = sword.clone()
+ val enchantedSword = sword.clone()
+ val enchantedBook = ItemStack(Material.ENCHANTED_BOOK)
+ val unitForRepair = ItemStack(Material.DIAMOND)
+
+ var meta = damagedSword.itemMeta
+ (meta as Damageable).damage = 5
+ damagedSword.itemMeta = meta
+
+ meta = enchantedSword.itemMeta
+ meta!!.addEnchant(Enchantment.DAMAGE_ALL, 1, true)
+ enchantedSword.itemMeta = meta
+
+ meta = enchantedBook.itemMeta
+ (meta as EnchantmentStorageMeta).addStoredEnchant(Enchantment.DAMAGE_ALL, 1, true)
+ enchantedBook.itemMeta = meta
+
+ stb.append("\n\nItem to Item repair:")
+ simulateAnvil(player, stb, damagedSword, damagedSword, sword)
+
+ stb.append("\n\nUnit repair:")
+ simulateAnvil(player, stb, damagedSword, unitForRepair, sword)
+
+ stb.append("\n\nEnchanting an item:")
+ simulateAnvil(player, stb, sword, enchantedBook, enchantedSword)
+ }
+
+ private val Plugin.pluginNameDisplay: String
+ get() {
+ return this.name + " v" + this.description.version
+ }
+
+ override fun description(): String {
+ return "Basic diagnostic of this plugin"
+ }
+
+ private fun pluginListDiag(sender: CommandSender, stb: StringBuilder) {
+ val enabledPlugins: MutableList = ArrayList()
+ val disabledPlugins: MutableList = ArrayList()
+ for (plugin in Bukkit.getPluginManager().plugins) {
+ if (plugin.isEnabled) {
+ enabledPlugins.add(plugin)
+ } else {
+ disabledPlugins.add(plugin)
+ }
+ }
+
+ stb.append("\nEnabled Plugins: ").append(
+ enabledPlugins.stream()
+ .map { plugin -> plugin!!.pluginNameDisplay }
+ .reduce { a: String?, b: String? -> "$a, $b" }.orElse("None")
+ )
+
+ stb.append("\nDisabled Plugins: ").append(
+ disabledPlugins.stream()
+ .map { plugin -> plugin!!.pluginNameDisplay }
+ .reduce { a: String?, b: String? -> "$a, $b" }.orElse("None")
+ )
+ }
+
+ fun prepareAnvilListeners(stb: StringBuilder) {
+ val eventListeners: MutableSet = Arrays
+ .stream(
+ PrepareAnvilEvent
+ .getHandlerList()
+ .getRegisteredListeners()
+ )
+ .map { obj: RegisteredListener? -> obj!!.plugin }
+ .collect(Collectors.toSet())
+
+ eventListeners.remove(CustomAnvil.instance)
+ stb.append("\nPrepare Anvil Listeners: ").append(
+ if (eventListeners.isEmpty()) "None" else eventListeners.stream()
+ .map { plugin -> plugin!!.pluginNameDisplay }
+ .reduce { a: String?, b: String? -> "$a, $b" }.orElse("None")
+ )
+ }
+
+ fun simulateAnvil(player: Player, stb: StringBuilder, left: ItemStack?, right: ItemStack?, result: ItemStack?) {
+ var invView: InventoryView
+ var event: PrepareAnvilEvent
+ try {
+ val fakeInv = Bukkit.createInventory(player, InventoryType.ANVIL)
+ invView = player.openInventory(fakeInv)!!
+ event = PrepareAnvilEvent(invView, result)
+ } catch (e: Throwable) {
+ // Help
+ val menuTypeClazz = Class.forName("org.bukkit.inventory.MenuType")
+ val anvilTypeField = menuTypeClazz.getField("ANVIL")
+ val anvilType = anvilTypeField.get(null)
+ val createMethod = anvilType.javaClass.getMethod("create", HumanEntity::class.java)
+ invView = createMethod.invoke(anvilType, player) as InventoryView
+
+ player.openInventory(invView)
+
+ val anvilViewClass = Class.forName("org.bukkit.inventory.view.AnvilView")
+ val constructor = PrepareAnvilEvent::class.java.getConstructor(anvilViewClass, ItemStack::class.java)
+ event = constructor.newInstance(invView, result)
+ }
+
+ val fakeInv = InventoryViewUtil.getInstance().getTopInventory(invView) as AnvilInventory
+ fakeInv.setItem(0, left)
+ fakeInv.setItem(1, right)
+
+ val xp = fakeInv.repairCost
+ val maxXp = fakeInv.maximumRepairCost
+ val mergeResult = fakeInv.getItem(2)
+ stb.append("\n${if(result == mergeResult) "E" else "Une"}xpected Result")
+
+ PrepareAnvilListener().anvilCombineCheck(event)
+ // Now we check if item and xp same
+ stb.append("\nXP/Max XP: ")
+ .append(if(fakeInv.repairCost == xp) "Correct" else "Incorrect")
+ .append("/")
+ .append(if(fakeInv.maximumRepairCost == maxXp) "Correct" else "Incorrect")
+ .append(" (${fakeInv.repairCost} $xp|${fakeInv.maximumRepairCost} $maxXp)")
+ .append("\nMerge result: ")
+ .append(if(fakeInv.getItem(2) == mergeResult) "Correct" else "Incorrect")
+
+ PrepareAnvilListener.IS_EMPTY_TEST = true
+ Bukkit.getPluginManager().callEvent(event)
+ stb.append("\nNull result test: ")
+ .append(if(event.result == null) "Correct" else "Incorrect")
+
+ fakeInv.setItem(0, null)
+ fakeInv.setItem(1, null)
+ fakeInv.setItem(2, null)
+ player.closeInventory()
+ }
+
+ private fun fullEnchantmentData(stb: StringBuilder) {
+ for (enchantment in CAEnchantmentRegistry.getInstance().values()) {
+ stb.append("\n- ").append(enchantment.key.toString())
+ .append(" ").append(enchantment.name)
+ .append(" ").append(enchantment.defaultMaxLevel())
+ }
+ }
+
+ private fun partialEnchantmentData(stb: StringBuilder) {
+ val map = HashMap()
+ for (enchant in CAEnchantmentRegistry.getInstance().values()) {
+ map[enchant.key.namespace] = map.getOrDefault(enchant.key.namespace, 0) + 1
+ }
+
+ stb.append("\nNamespaces: ${
+ map.entries.stream()
+ .map { (key, value) -> "$key ($value)" }
+ .reduce { a, b -> "$a, $b" }.get()
+ }")
+
+ }
+
+ private fun includeLastError(stb: StringBuilder) {
+ val e = MetricsUtil.lastError ?: return
+
+ stb.append("\n\nLast stack trace: ${e.stackTraceToString()}")
+
+
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/EditConfigExecutor.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/EditConfigExecutor.kt
index 85761d9..fba89b7 100644
--- a/src/main/kotlin/xyz/alexcrea/cuanvil/command/EditConfigExecutor.kt
+++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/EditConfigExecutor.kt
@@ -2,21 +2,25 @@ package xyz.alexcrea.cuanvil.command
import io.delilaheve.CustomAnvil
import org.bukkit.command.Command
-import org.bukkit.command.CommandExecutor
import org.bukkit.command.CommandSender
import org.bukkit.entity.HumanEntity
-import xyz.alexcrea.cuanvil.dependency.DependencyManager
+import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil
import xyz.alexcrea.cuanvil.gui.config.MainConfigGui
import xyz.alexcrea.cuanvil.gui.util.GuiGlobalActions
-class EditConfigExecutor : CommandExecutor {
+class EditConfigExecutor: CASubCommand() {
- override fun onCommand(sender: CommandSender, command: Command, label: String, args: Array): Boolean {
- if (!sender.hasPermission(CustomAnvil.editConfigPermission)) {
+ override fun executeCommand(sender: CommandSender,
+ cmd: Command,
+ cmdstr: String,
+ args: Array): Boolean {
+ if (sender !is HumanEntity) return false
+
+ if (!allowed(sender)) {
sender.sendMessage(GuiGlobalActions.NO_EDIT_PERM)
return false
}
- if(DependencyManager.isFolia){
+ if(PlatformUtil.isFolia){
sender.sendMessage("§cIt look like you are using Folia. Sadly Custom Anvil do not support Config gui for Folia.")
sender.sendMessage("§eIt is may come in a future version.")
sender.sendMessage("")
@@ -25,10 +29,17 @@ class EditConfigExecutor : CommandExecutor {
return false
}
- if (sender !is HumanEntity) return false
MainConfigGui.getInstance().show(sender)
return true
}
+ override fun allowed(sender: CommandSender): Boolean {
+ return sender.hasPermission(CustomAnvil.editConfigPermission)
+ }
+
+ override fun description(): String {
+ return "Gui to edit the plugin's config"
+ }
+
}
diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/HelpExecutor.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/HelpExecutor.kt
new file mode 100644
index 0000000..697f1ee
--- /dev/null
+++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/HelpExecutor.kt
@@ -0,0 +1,32 @@
+package xyz.alexcrea.cuanvil.command
+
+import com.google.common.collect.ImmutableMap
+import org.bukkit.command.Command
+import org.bukkit.command.CommandSender
+
+class HelpExecutor: CASubCommand() {
+
+ lateinit var commands: ImmutableMap
+
+ override fun executeCommand(sender: CommandSender,
+ cmd: Command,
+ cmdstr: String,
+ args: Array): Boolean {
+
+ val stb = StringBuilder("List of available commands:")
+ for ((key, cmd) in commands) {
+ if(!cmd.allowed(sender)) continue
+
+ stb.append("\n- $key: ").append(cmd.description())
+ }
+
+ sender.sendMessage(stb.toString())
+
+ return true
+ }
+
+ override fun description(): String {
+ return "Help command"
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/command/ReloadExecutor.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/command/ReloadExecutor.kt
index 38a98c0..ef23e7d 100644
--- a/src/main/kotlin/xyz/alexcrea/cuanvil/command/ReloadExecutor.kt
+++ b/src/main/kotlin/xyz/alexcrea/cuanvil/command/ReloadExecutor.kt
@@ -9,11 +9,15 @@ import xyz.alexcrea.cuanvil.api.event.CAConfigReadyEvent
import xyz.alexcrea.cuanvil.config.ConfigHolder
import xyz.alexcrea.cuanvil.dependency.DependencyManager
import xyz.alexcrea.cuanvil.gui.config.global.*
-import xyz.alexcrea.cuanvil.update.Update_1_21
+import xyz.alexcrea.cuanvil.update.UpdateHandler
-class ReloadExecutor : CommandExecutor {
- override fun onCommand(sender: CommandSender, cmd: Command, cmdstr: String, args: Array): Boolean {
- if (!sender.hasPermission(CustomAnvil.commandReloadPermission)) {
+class ReloadExecutor : CASubCommand() {
+
+ override fun executeCommand(sender: CommandSender,
+ cmd: Command,
+ cmdstr: String,
+ args: Array): Boolean {
+ if (!allowed(sender)) {
sender.sendMessage("§cYou do not have permission to reload the config")
return false
}
@@ -31,6 +35,14 @@ class ReloadExecutor : CommandExecutor {
return commandSuccess
}
+ override fun allowed(sender: CommandSender): Boolean {
+ return sender.hasPermission(CustomAnvil.commandReloadPermission)
+ }
+
+ override fun description(): String {
+ return "Reload the configuration of this plugin"
+ }
+
/**
* Execute the command, return true if success or false otherwise
*/
@@ -48,8 +60,8 @@ class ReloadExecutor : CommandExecutor {
UnitRepairConfigGui.getCurrentInstance()?.reloadValues()
CustomRecipeConfigGui.getCurrentInstance()?.reloadValues()
- // temporary: handle 1.21 update
- Update_1_21.handleUpdate()
+ // handle minecraft version update
+ UpdateHandler.handleMCVersionUpdate()
// Handle dependency reload
DependencyManager.handleConfigReload()
diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt
index e906a4a..b730a0e 100644
--- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt
+++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/DependencyManager.kt
@@ -1,39 +1,45 @@
package xyz.alexcrea.cuanvil.dependency
-import com.willfp.eco.core.gui.player
+import com.maddoxh.superEnchants.SuperEnchants
import io.delilaheve.CustomAnvil
+import net.kyori.adventure.text.Component
import org.bukkit.Bukkit
import org.bukkit.ChatColor
+import org.bukkit.command.CommandSender
import org.bukkit.entity.HumanEntity
import org.bukkit.entity.Player
import org.bukkit.event.inventory.InventoryClickEvent
import org.bukkit.event.inventory.PrepareAnvilEvent
import org.bukkit.inventory.AnvilInventory
+import org.bukkit.inventory.Inventory
+import org.bukkit.inventory.InventoryView
import org.bukkit.inventory.ItemStack
+import xyz.alexcrea.cuanvil.anvil.AnvilCost
+import xyz.alexcrea.cuanvil.anvil.AnvilUseType
import xyz.alexcrea.cuanvil.api.event.listener.CAClickResultBypassEvent
import xyz.alexcrea.cuanvil.api.event.listener.CAEarlyPreAnvilBypassEvent
import xyz.alexcrea.cuanvil.api.event.listener.CAPreAnvilBypassEvent
-import xyz.alexcrea.cuanvil.api.event.listener.CATreatAnvilResultEvent
+import xyz.alexcrea.cuanvil.api.event.listener.CATreatAnvilResult2Event
import xyz.alexcrea.cuanvil.config.ConfigHolder
import xyz.alexcrea.cuanvil.dependency.datapack.DataPackDependency
-import xyz.alexcrea.cuanvil.dependency.gui.ExternGuiTester
-import xyz.alexcrea.cuanvil.dependency.gui.GuiTesterSelector
+import xyz.alexcrea.cuanvil.dependency.gui.GenericExternGuiTester
import xyz.alexcrea.cuanvil.dependency.packet.PacketManager
import xyz.alexcrea.cuanvil.dependency.packet.PacketManagerSelector
import xyz.alexcrea.cuanvil.dependency.plugins.*
import xyz.alexcrea.cuanvil.dependency.scheduler.BukkitScheduler
import xyz.alexcrea.cuanvil.dependency.scheduler.FoliaScheduler
import xyz.alexcrea.cuanvil.dependency.scheduler.TaskScheduler
+import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil
+import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil.componentLore
import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener.Companion.ANVIL_OUTPUT_SLOT
-import xyz.alexcrea.cuanvil.util.AnvilUseType
+import xyz.alexcrea.cuanvil.util.MetricsUtil.trackError
import java.util.logging.Level
object DependencyManager {
- var isFolia: Boolean = false
lateinit var scheduler: TaskScheduler
lateinit var packetManager: PacketManager
- var externGuiTester: ExternGuiTester? = null
+ var externGuiTester: GenericExternGuiTester = GenericExternGuiTester()
var enchantmentSquaredCompatibility: EnchantmentSquaredDependency? = null
var ecoEnchantCompatibility: EcoEnchantDependency? = null
@@ -44,14 +50,15 @@ object DependencyManager {
var axPlayerWarpsCompatibility: AxPlayerWarpsDependency? = null
+ var itemsAdderCompatibility: ItemsAdderDependency? = null
+
val genericDependencies = ArrayList()
fun loadDependency() {
val pluginManager = Bukkit.getPluginManager()
// Bukkit or Paper scheduler ?
- isFolia = testIsFolia()
- scheduler = if (isFolia) {
+ scheduler = if (PlatformUtil.isFolia) {
CustomAnvil.instance.logger.info("Folia detected... Custom Anvil Folia support is experimental. issues are more likely to happens.")
FoliaScheduler()
@@ -60,7 +67,6 @@ object DependencyManager {
// Packet Manager
val forceProtocolib = ConfigHolder.DEFAULT_CONFIG.config.getBoolean("force_protocolib", false)
packetManager = PacketManagerSelector.selectPacketManager(forceProtocolib)
- externGuiTester = GuiTesterSelector.selectGuiTester
// Enchantment Squared dependency
if (pluginManager.isPluginEnabled("EnchantsSquared")) {
@@ -97,10 +103,25 @@ object DependencyManager {
axPlayerWarpsCompatibility = AxPlayerWarpsDependency()
}
+ if (pluginManager.isPluginEnabled("ItemsAdder")){
+ val dependency = ItemsAdderDependency(pluginManager.getPlugin("ItemsAdder")!!)
+ itemsAdderCompatibility = dependency
+ genericDependencies.add(dependency)
+ }
+
// "Generic" dependencies
if (pluginManager.isPluginEnabled("ToolStats"))
genericDependencies.add(ToolStatsDependency(pluginManager.getPlugin("ToolStats")!!))
+ if (pluginManager.isPluginEnabled("ItemsAdder"))
+ genericDependencies.add(GenericPluginDependency(pluginManager.getPlugin("ItemsAdder")!!))
+
+ if (pluginManager.isPluginEnabled("SuperEnchants")){
+ val compatibility = SuperEnchantDependency(pluginManager.getPlugin("SuperEnchants")!! as SuperEnchants)
+ if(compatibility.registerEnchantments())
+ genericDependencies.add(compatibility)
+ }
+
for (dependency in genericDependencies)
dependency.redirectListeners()
@@ -128,26 +149,35 @@ object DependencyManager {
ecoEnchantCompatibility?.handleConfigReload()
}
+ private fun logException(target: CommandSender, e: Exception) {
+ CustomAnvil.instance.logger.log(
+ Level.SEVERE,
+ "Error while trying to handle custom anvil supported plugin: ",
+ e
+ )
+ trackError(e)
+
+ // Finally, warn the player
+ target.sendMessage(
+ "[" + ChatColor.YELLOW.toString() + "CustomAnvil" + ChatColor.WHITE.toString() + "] " +
+ ChatColor.RED.toString() + "Error while handling the anvil."
+ )
+ }
+
+ private fun logExceptionAndClear(target: CommandSender, inventory: Inventory, e: Exception) {
+ // Just in case to avoid illegal items
+ inventory.setItem(ANVIL_OUTPUT_SLOT, null)
+
+ logException(target, e)
+ }
+
// Return true if should bypass (either by a dependency or error)
// called before immutability test
fun earlyTryEventPreAnvilBypass(event: PrepareAnvilEvent, player: HumanEntity): Boolean {
try {
return earlyUnsafeTryEventPreAnvilBypass(event, player)
} catch (e: Exception) {
- CustomAnvil.instance.logger.log(
- Level.SEVERE,
- "Error while trying to handle custom anvil supported plugin: ",
- e
- )
-
- // Just in case to avoid illegal items
- event.inventory.setItem(ANVIL_OUTPUT_SLOT, null)
-
- // Finally, warn the player, maybe a lot of time but better warn than do nothing
- event.view.player.sendMessage(
- "[" + ChatColor.YELLOW.toString() + "CustomAnvil" + ChatColor.WHITE.toString() + "] " +
- ChatColor.RED.toString() + "Error while handling the anvil."
- )
+ logExceptionAndClear(event.view.player, event.inventory, e)
return true
}
}
@@ -160,7 +190,7 @@ object DependencyManager {
var bypass = bypassEvent.isCancelled
// Test if the inventory is a gui(version specific)
- if (!bypass && (externGuiTester?.testIfGui(event.view) == true)) bypass = true
+ if (!bypass && externGuiTester.testIfGui(event.view)) bypass = true
// Test if in an ax player warp rating gui
if (!bypass && (axPlayerWarpsCompatibility?.testIfGui(player) == true)) bypass = true
@@ -169,29 +199,16 @@ object DependencyManager {
}
// Return true if should bypass (either by a dependency or error)
- fun tryEventPreAnvilBypass(event: PrepareAnvilEvent, player: HumanEntity): Boolean {
+ fun tryEventPreAnvilBypass(event: PrepareAnvilEvent, player: Player): Boolean {
try {
return unsafeTryEventPreAnvilBypass(event, player)
} catch (e: Exception) {
- CustomAnvil.instance.logger.log(
- Level.SEVERE,
- "Error while trying to handle custom anvil supported plugin: ",
- e
- )
-
- // Just in case to avoid illegal items
- event.inventory.setItem(ANVIL_OUTPUT_SLOT, null)
-
- // Finally, warn the player, maybe a lot of time but better warn than do nothing
- event.view.player.sendMessage(
- "[" + ChatColor.YELLOW.toString() + "CustomAnvil" + ChatColor.WHITE.toString() + "] " +
- ChatColor.RED.toString() + "Error while handling the anvil."
- )
+ logExceptionAndClear(event.view.player, event.inventory, e)
return true
}
}
- private fun unsafeTryEventPreAnvilBypass(event: PrepareAnvilEvent, player: HumanEntity): Boolean {
+ private fun unsafeTryEventPreAnvilBypass(event: PrepareAnvilEvent, player: Player): Boolean {
// Run the event
val bypassEvent = CAPreAnvilBypassEvent(event)
Bukkit.getPluginManager().callEvent(bypassEvent)
@@ -216,35 +233,24 @@ object DependencyManager {
// Return null if there was an issue
fun tryTreatAnvilResult(
- event: PrepareAnvilEvent,
+ view: InventoryView,
+ inventory: Inventory, // TODO REMOVE, use view instead on legacy removal
+ player: HumanEntity,
result: ItemStack,
useType: AnvilUseType,
- cost: Int
- ): CATreatAnvilResultEvent? {
- val treatEvent = CATreatAnvilResultEvent(event, useType, result, cost)
+ cost: AnvilCost
+ ): ItemStack? {
+ val treatEvent = CATreatAnvilResult2Event(view, inventory, useType, result, cost)
try {
unsafeTryTreatAnvilResult(treatEvent)
- return treatEvent;
+ return treatEvent.result
} catch (e: Exception) {
- CustomAnvil.instance.logger.log(
- Level.SEVERE,
- "Error while trying to handle custom anvil supported plugin: ",
- e
- )
-
- // Just in case to avoid illegal items
- event.inventory.setItem(ANVIL_OUTPUT_SLOT, null)
-
- // Finally, warn the player, maybe a lot of time but better warn than do nothing
- event.view.player.sendMessage(
- "[" + ChatColor.YELLOW.toString() + "CustomAnvil" + ChatColor.WHITE.toString() + "] " +
- ChatColor.RED.toString() + "Error while handling the anvil."
- )
+ logExceptionAndClear(player, inventory, e)
return null
}
}
- private fun unsafeTryTreatAnvilResult(event: CATreatAnvilResultEvent) {
+ private fun unsafeTryTreatAnvilResult(event: CATreatAnvilResult2Event) {
Bukkit.getPluginManager().callEvent(event)
excellentEnchantsCompatibility?.treatAnvilResult(event)
@@ -255,20 +261,7 @@ object DependencyManager {
try {
return unsafeTryClickAnvilResultBypass(event, inventory)
} catch (e: Exception) {
- CustomAnvil.instance.logger.log(
- Level.SEVERE,
- "Error while trying to handle custom anvil supported plugin: ",
- e
- )
-
- // Just in case to avoid illegal items
- event.inventory.setItem(ANVIL_OUTPUT_SLOT, null)
-
- // Finally, warn the player, maybe a lot of time but better warn than do nothing
- event.whoClicked.sendMessage(
- "[" + ChatColor.YELLOW.toString() + "CustomAnvil" + ChatColor.WHITE.toString() + "] " +
- ChatColor.RED.toString() + "Error while handling the anvil."
- )
+ logExceptionAndClear(event.view.player, event.inventory, e)
return true
}
}
@@ -294,23 +287,40 @@ object DependencyManager {
}
// Test if the inventory is a gui(version specific)
- if (!bypass && (externGuiTester?.testIfGui(event.view) == true)) bypass = true
+ if (!bypass && externGuiTester.testIfGui(event.view)) bypass = true
// Test if in an ax player warp rating gui
- if (!bypass && (axPlayerWarpsCompatibility?.testIfGui(event.player) == true)) bypass = true
+ if (!bypass && (axPlayerWarpsCompatibility?.testIfGui(event.view.player) == true)) bypass = true
return bypass
}
- fun stripLore(item: ItemStack): ArrayList {
- val lore = ArrayList()
+ // Clone item and use plugin specific clone if needed
+ fun cloneItem(player: HumanEntity, item: ItemStack): ItemStack {
+ try {
+ return unsafeCloneItem(item)
+ } catch (e: Exception) {
+ logException(player, e)
+ return item.clone()
+ }
+ }
+
+ private fun unsafeCloneItem(item: ItemStack): ItemStack {
+ val cloned = itemsAdderCompatibility?.tryClone(item)
+ if(cloned != null) return cloned
+
+ return item.clone()
+ }
+
+ fun stripLore(item: ItemStack): MutableList {
val dummy = item.clone()
enchantmentSquaredCompatibility?.stripLore(dummy)
- val itemLore = dummy.itemMeta!!.lore
- if (itemLore != null) lore.addAll(itemLore)
+ val itemLore = dummy.itemMeta?.componentLore() ?: return ArrayList()
+ val lore = ArrayList()
+ lore.addAll(itemLore)
return lore
}
@@ -318,13 +328,4 @@ object DependencyManager {
enchantmentSquaredCompatibility?.updateLore(item)
}
- private fun testIsFolia(): Boolean {
- try {
- Class.forName("io.papermc.paper.threadedregions.RegionizedServer")
- return true
- } catch (e: ClassNotFoundException) {
- return false
- }
- }
-
}
diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/MinecraftVersionUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/MinecraftVersionUtil.kt
new file mode 100644
index 0000000..cbd2209
--- /dev/null
+++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/MinecraftVersionUtil.kt
@@ -0,0 +1,58 @@
+package xyz.alexcrea.cuanvil.dependency
+
+import xyz.alexcrea.cuanvil.update.UpdateUtils
+
+object MinecraftVersionUtil {
+
+ val craftbukkitVersion: String?
+ get() {
+ val version = UpdateUtils.currentMinecraftVersion()
+ if (version.major != 1) return null
+
+ return when (version.minor) {
+ 17 -> when (version.patch) {
+ 0, 1 -> "1_17R1"
+ else -> null
+ }
+
+ 18 -> when (version.patch) {
+ 0, 1 -> "1_18R1"
+ 2 -> "1_18R2"
+ else -> null
+ }
+
+ 19 -> when (version.patch) {
+ 0, 1, 2 -> "1_19R1"
+ 3 -> "1_19R2"
+ 4 -> "1_19R3"
+ else -> null
+ }
+
+ 20 -> when (version.patch) {
+ 0, 1 -> "1_20R1"
+ 2 -> "1_20R2"
+ 3, 4 -> "1_20R3"
+ 5, 6 -> "1_20R4"
+ else -> null
+ }
+
+ 21 -> when (version.patch) {
+ 0, 1 -> "1_21R1"
+ 2, 3 -> "1_21R2"
+ 4 -> "1_21R3"
+ 5 -> "1_21R4"
+ 6, 7, 8 -> "1_21R5"
+ 9, 10 -> "1_21R6"
+ 11 -> "1_21R7"
+ else -> null
+ }
+
+ else -> null
+ }
+ }
+
+ val isTooNewForSpigot: Boolean get() {
+ return UpdateUtils.currentMinecraftVersion().major != 1
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/datapack/DataPackDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/datapack/DataPackDependency.kt
index f397200..6d54456 100644
--- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/datapack/DataPackDependency.kt
+++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/datapack/DataPackDependency.kt
@@ -17,7 +17,7 @@ import xyz.alexcrea.cuanvil.update.Version
import java.io.InputStreamReader
object DataPackDependency {
- private val START_DETECT_VERSION = Version(1, 19, 0)
+ private val START_DETECT_VERSION = Version(1, 20, 5)
/**
* Map of the latest CustomAnvil update related to the pack
@@ -145,7 +145,7 @@ object DataPackDependency {
CustomAnvil.instance.logger.warning("Could not find material $name for item group $groupName")
continue
}
- group.addToPolicy(mat)
+ group.addToPolicy(mat.key)
}
for (name in section.getStringList("groups")) {
val otherGroup = MaterialGroupApi.getGroup(name)
diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/EconomyManager.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/EconomyManager.kt
new file mode 100644
index 0000000..67a6c1e
--- /dev/null
+++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/EconomyManager.kt
@@ -0,0 +1,32 @@
+package xyz.alexcrea.cuanvil.dependency.economy
+
+import org.bukkit.entity.Player
+import org.bukkit.plugin.Plugin
+import java.math.BigDecimal
+
+interface EconomyManager {
+
+ companion object {
+ var economy: EconomyManager? = null
+
+ fun setupEconomy(plugin: Plugin) {
+ if (plugin.server.pluginManager.getPlugin("Vault") == null)
+ return
+ if (UnlockedEconomyManager.unlockedAvailable())
+ economy = UnlockedEconomyManager(plugin)
+
+ if (economy == null || !economy!!.initialized())
+ economy = VaultEconomyManager(plugin)
+ }
+
+ }
+
+ fun initialized(): Boolean
+
+ // We assume "initialized" got checked before these function get called
+ fun has(player: Player, money: BigDecimal): Boolean
+ fun remove(player: Player, money: BigDecimal): Boolean
+
+ fun format(money: BigDecimal): String;
+
+}
diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/UnlockedEconomyManager.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/UnlockedEconomyManager.kt
new file mode 100644
index 0000000..da66f30
--- /dev/null
+++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/UnlockedEconomyManager.kt
@@ -0,0 +1,74 @@
+package xyz.alexcrea.cuanvil.dependency.economy
+
+import io.delilaheve.util.ConfigOptions
+import net.milkbowl.vault2.economy.Economy
+import org.bukkit.entity.Player
+import org.bukkit.plugin.Plugin
+import java.math.BigDecimal
+
+class UnlockedEconomyManager : EconomyManager {
+
+ val plugin: String
+ val economy: Economy?
+
+ companion object {
+ fun unlockedAvailable(): Boolean {
+ try {
+ Class.forName("net.milkbowl.vault2.economy.Economy")
+ return true
+ } catch (_: ClassNotFoundException) {
+ return false
+ }
+ }
+ }
+
+ constructor(plugin: Plugin) {
+ this.plugin = plugin.name
+
+ val rsp = plugin.server.servicesManager.getRegistration(Economy::class.java)
+ economy = rsp?.getProvider()
+ }
+
+ override fun initialized(): Boolean {
+ return economy != null
+ }
+
+ private fun currency(): String {
+ val configured = ConfigOptions.usedCurrency
+
+ return if ("default".equals(configured, true))
+ economy!!.getDefaultCurrency(plugin)
+ else configured
+ }
+
+ override fun has(player: Player, money: BigDecimal): Boolean {
+ if (money.signum() <= 0) return true
+
+ return economy!!.has(
+ plugin,
+ player.uniqueId,
+ player.world.name,
+ currency(),
+ money
+ )
+ }
+
+ override fun remove(player: Player, money: BigDecimal): Boolean {
+ if (money.signum() <= 0) return true
+
+ return economy!!.withdraw(
+ plugin,
+ player.uniqueId,
+ player.world.name,
+ currency(),
+ money
+ )
+ .transactionSuccess()
+ }
+
+ override fun format(money: BigDecimal): String {
+ return economy!!.format(plugin, money, currency())
+ }
+
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/VaultEconomyManager.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/VaultEconomyManager.kt
new file mode 100644
index 0000000..058485e
--- /dev/null
+++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/economy/VaultEconomyManager.kt
@@ -0,0 +1,37 @@
+package xyz.alexcrea.cuanvil.dependency.economy
+
+import net.milkbowl.vault.economy.Economy
+import org.bukkit.entity.Player
+import org.bukkit.plugin.Plugin
+import java.math.BigDecimal
+
+class VaultEconomyManager : EconomyManager {
+
+ val economy: Economy?
+
+ constructor(plugin: Plugin) {
+ val rsp = plugin.server.servicesManager.getRegistration(Economy::class.java)
+ economy = rsp?.getProvider()
+ }
+
+ override fun initialized(): Boolean {
+ return economy != null
+ }
+
+ override fun has(player: Player, money: BigDecimal): Boolean {
+ if (money.signum() <= 0) return true
+
+ return economy!!.has(player, money.toDouble())
+ }
+
+ override fun remove(player: Player, money: BigDecimal): Boolean {
+ if (money.signum() <= 0) return true
+
+ return economy!!.withdrawPlayer(player, money.toDouble()).transactionSuccess()
+ }
+
+ override fun format(money: BigDecimal): String {
+ return economy!!.format(money.toDouble())
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GenericExternGuiTester.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GenericExternGuiTester.kt
new file mode 100644
index 0000000..4046f4a
--- /dev/null
+++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GenericExternGuiTester.kt
@@ -0,0 +1,111 @@
+package xyz.alexcrea.cuanvil.dependency.gui
+
+import org.bukkit.inventory.InventoryView
+import xyz.alexcrea.cuanvil.dependency.MinecraftVersionUtil
+import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil
+import java.lang.reflect.Method
+
+class GenericExternGuiTester {
+
+ companion object {
+ private const val ANVIL_CLASS_NAME = "org.bukkit.craftbukkit.inventory.view.CraftAnvilView"
+ private const val INV_CLASS_NAME = "org.bukkit.craftbukkit.inventory.CraftInventoryView"
+ private const val HANDLE_METHOD_NAME = "getHandle"
+
+ private const val CANONICAL_PAPER_ANVIL_MENU = "net.minecraft.world.inventory.AnvilMenu"
+ }
+
+ var testExist = false
+ var inTesting = false
+
+ var testedClass: String? = null
+ lateinit var getHandleMethod: Method
+
+ private fun getContainerClass(view: InventoryView): Class? {
+ if(!testedClass.contentEquals(view.javaClass.name))
+ return null
+
+ val container = getHandleMethod.invoke(view)
+ return container.javaClass
+ }
+
+ fun tryFromClass(className: String) {
+ val clazz = Class.forName(className)
+ testedClass = className
+
+ getHandleMethod = clazz.getMethod(HANDLE_METHOD_NAME)
+ }
+
+ fun isInTest(): Boolean {
+ if(!testExist) testClassExist()
+ return inTesting
+ }
+
+ fun testClassExist() {
+ testExist = true
+
+ // We first try to get craft anvil interface,
+ // but is absent on old version so we try craft inventory view before
+ try {
+ tryFromClass(ANVIL_CLASS_NAME)
+ return
+ }
+ catch (_: ClassNotFoundException) {}
+ catch (_: NoSuchMethodException) {}
+
+ try {
+ tryFromClass(INV_CLASS_NAME)
+ return
+ }
+ catch (_: ClassNotFoundException) {}
+ catch (_: NoSuchMethodException) {}
+
+ inTesting = true
+ }
+
+ // Try if were in another plugin anvil inventory
+ fun testIfGui(inventory: InventoryView): Boolean {
+ // In case we are in a test environment
+ if(isInTest()) return false
+
+ val clazz = getContainerClass(inventory) ?: return false
+
+ val clazzName = clazz.name
+ if(!PlatformUtil.isPaper){
+ // Blacklist gui causing issue
+ if (expectWesjd(clazzName)) return true
+ if (expectXenondevUI(clazzName)) return true
+ if (expectVanePortal(clazzName)) return true
+
+ return false
+ }
+
+ // Only allow cannonical anvil menu class
+ return !CANONICAL_PAPER_ANVIL_MENU.equals(clazzName, true)
+ }
+
+ // Known custom implementations
+ fun expectWesjd(name: String): Boolean {
+ val expectedWesjdGuiPath = "anvilgui.version.Wrapper${MinecraftVersionUtil.craftbukkitVersion}"
+
+ return name.contains(expectedWesjdGuiPath)
+ }
+
+ private val XenondevUIPrefix: String
+ get() = "xyz.xenondevs.inventoryaccess."
+ private val XenondevUISufix: String
+ get() = ".AnvilInventoryImpl"
+
+ fun expectXenondevUI(name: String): Boolean {
+ return name.startsWith(XenondevUIPrefix)
+ && name.endsWith(XenondevUISufix)
+ }
+
+ fun expectVanePortal(name: String): Boolean {
+ val expected = "org.oddlama.vane.core.menu.AnvilMenu\$AnvilContainer"
+
+ return name == expected
+ }
+
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GuiTesterSelector.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GuiTesterSelector.kt
deleted file mode 100644
index b8ce177..0000000
--- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/gui/GuiTesterSelector.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-package xyz.alexcrea.cuanvil.dependency.gui
-
-import xyz.alexcrea.cuanvil.dependency.gui.version.*;
-import xyz.alexcrea.cuanvil.update.UpdateUtils
-
-object GuiTesterSelector {
-
- val selectGuiTester: ExternGuiTester?
- get() {
- val versionParts = UpdateUtils.currentMinecraftVersionArray()
- if (versionParts[0] != 1) return null
-
- return when (versionParts[1]) {
- // Can't support 1.16.5 bc 1.16.5 paper userdev do not exist
-
- 17 -> when (versionParts[2]) {
- 0, 1 -> v1_17R1_ExternGuiTester()
- else -> null
- }
-
- 18 -> when (versionParts[2]) {
- 0, 1 -> v1_18R1_ExternGuiTester()
- 2 -> v1_18R2_ExternGuiTester()
- else -> null
- }
-
- 19 -> when (versionParts[2]) {
- 0, 1, 2 -> v1_19R1_ExternGuiTester()
- 3 -> v1_19R2_ExternGuiTester()
- 4 -> v1_19R3_ExternGuiTester()
- else -> null
- }
-
- 20 -> when (versionParts[2]) {
- 0, 1 -> v1_20R1_ExternGuiTester()
- 2 -> v1_20R2_ExternGuiTester()
- 3, 4 -> v1_20R3_ExternGuiTester()
- 5, 6 -> v1_20R4_ExternGuiTester()
- else -> null
- }
-
- 21 -> when (versionParts[2]) {
- 0, 1 -> v1_21R1_ExternGuiTester()
- 2, 3 -> v1_21R2_ExternGuiTester()
- 4 -> v1_21R3_ExternGuiTester()
- 5 -> v1_21R4_ExternGuiTester()
- 6, 7, 8 -> v1_21R5_ExternGuiTester()
- else -> null
- }
-
- else -> null
- }
- }
-
-}
\ No newline at end of file
diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt
index 450fbce..d74ef08 100644
--- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt
+++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/packet/PacketManagerSelector.kt
@@ -1,16 +1,31 @@
package xyz.alexcrea.cuanvil.dependency.packet
import org.bukkit.Bukkit
+import xyz.alexcrea.cuanvil.dependency.DependencyManager
+import xyz.alexcrea.cuanvil.dependency.MinecraftVersionUtil
import xyz.alexcrea.cuanvil.dependency.packet.versions.*
import xyz.alexcrea.cuanvil.update.UpdateUtils
object PacketManagerSelector {
+
+ private const val PAPER_CRAFT_PLAYER_CLASS = "org.bukkit.craftbukkit.entity.CraftPlayer"
+
fun selectPacketManager(forceProtocolib: Boolean): PacketManager {
// Try to find version
+ if(DependencyManager.externGuiTester.isInTest())
+ return NoPacketManager()
+
return if (forceProtocolib)
protocolibIfPresent
- else
- versionSpecificManager ?: protocolibIfPresent
+ else {
+ try {
+ Class.forName(PAPER_CRAFT_PLAYER_CLASS)
+
+ return PaperPacketManager()
+ } catch (_: ClassNotFoundException) {
+ return reobfPacketManager ?: protocolibIfPresent
+ }
+ }
}
private val protocolibIfPresent: PacketManager
@@ -19,50 +34,21 @@ object PacketManagerSelector {
ProtocoLibWrapper()
else
NoPacketManager()
- private val versionSpecificManager: PacketManagerBase?
+
+ // Reobfuscated packet manager for spigot or paper as it remap
+ private val reobfPacketManager: PacketManagerBase?
get() {
- val versionParts = UpdateUtils.currentMinecraftVersionArray()
- if (versionParts[0] != 1) return null
+ val versionParts = UpdateUtils.currentMinecraftVersion()
+ if (versionParts.major != 1) return null
- return when (versionParts[1]) {
- // Can't support 1.16.5 bc 1.16.5 paper userdev do not exist
+ try {
+ val clazz = Class.forName("xyz.alexcrea.cuanvil.dependency.packet.versions." +
+ "V${MinecraftVersionUtil.craftbukkitVersion}_PacketManager")
- 17 -> when (versionParts[2]) {
- 0, 1 -> V1_17R1_PacketManager()
- else -> null
- }
-
- 18 -> when (versionParts[2]) {
- 0, 1 -> V1_18R1_PacketManager()
- 2 -> V1_18R2_PacketManager()
- else -> null
- }
-
- 19 -> when (versionParts[2]) {
- 0, 1, 2 -> V1_19R1_PacketManager()
- 3 -> V1_19R2_PacketManager()
- 4 -> V1_19R3_PacketManager()
- else -> null
- }
-
- 20 -> when (versionParts[2]) {
- 0, 1 -> V1_20R1_PacketManager()
- 2 -> V1_20R2_PacketManager()
- 3, 4 -> V1_20R3_PacketManager()
- 5, 6 -> V1_20R4_PacketManager()
- else -> null
- }
-
- 21 -> when (versionParts[2]) {
- 0, 1 -> V1_21R1_PacketManager()
- 2, 3 -> V1_21R2_PacketManager()
- 4 -> V1_21R3_PacketManager()
- 5 -> V1_21R4_PacketManager()
- 6, 7, 8 -> V1_21R5_PacketManager()
- else -> null
- }
-
- else -> null
+ val manager = clazz.getConstructor().newInstance()
+ return manager as PacketManagerBase
+ } catch (_: ClassNotFoundException) {
+ return null
}
}
}
diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/DisenchantmentDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/DisenchantmentDependency.kt
index f014c8a..690b384 100644
--- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/DisenchantmentDependency.kt
+++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/DisenchantmentDependency.kt
@@ -8,14 +8,16 @@ import com.jankominek.disenchantment.events.ShatterEvent
import com.jankominek.disenchantment.listeners.DisenchantClickListener
import com.jankominek.disenchantment.listeners.ShatterClickListener
import io.delilaheve.CustomAnvil
-import org.bukkit.entity.HumanEntity
+import org.bukkit.entity.Player
import org.bukkit.event.Listener
import org.bukkit.event.inventory.InventoryClickEvent
import org.bukkit.event.inventory.PrepareAnvilEvent
import org.bukkit.inventory.AnvilInventory
import org.bukkit.inventory.ItemStack
+import xyz.alexcrea.cuanvil.anvil.AnvilCost
import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener
-import xyz.alexcrea.cuanvil.util.AnvilXpUtil
+import xyz.alexcrea.cuanvil.util.MetricsUtil.trackError
+import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil
import java.util.logging.Level
import kotlin.reflect.KClass
@@ -38,6 +40,7 @@ class DisenchantmentDependency {
Level.SEVERE, "Could not initialize disenchantment support" +
"please report this bug to the developer", e
)
+ trackError(e)
}
}
@@ -48,7 +51,7 @@ class DisenchantmentDependency {
InventoryClickEvent.getHandlerList().unregister(listener)
}
- fun testPrepareAnvil(event: PrepareAnvilEvent, player: HumanEntity): Boolean {
+ fun testPrepareAnvil(event: PrepareAnvilEvent, player: Player): Boolean {
val previousResult = event.result
event.result = null
@@ -56,14 +59,14 @@ class DisenchantmentDependency {
DisenchantEvent.onEvent(event)
if (event.result != null) {
CustomAnvil.log("Detected pre anvil item extract bypass.")
- AnvilXpUtil.setAnvilInvXp(event.inventory, event.view, player, event.inventory.repairCost)
+ AnvilXpUtil.setAnvilInvCost(event.inventory, event.view, player, AnvilCost(event.inventory.repairCost))
return true
}
ShatterEvent.onEvent(event)
if (event.result != null) {
CustomAnvil.log("Detected pre anvil split enchant bypass.")
- AnvilXpUtil.setAnvilInvXp(event.inventory, event.view, player, event.inventory.repairCost)
+ AnvilXpUtil.setAnvilInvCost(event.inventory, event.view, player, AnvilCost(event.inventory.repairCost))
return true
}
diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/EcoItemDependencyUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/EcoItemDependencyUtil.kt
new file mode 100644
index 0000000..7e84231
--- /dev/null
+++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/EcoItemDependencyUtil.kt
@@ -0,0 +1,38 @@
+package xyz.alexcrea.cuanvil.dependency.plugins
+
+import com.willfp.ecoitems.items.EcoItem
+import com.willfp.ecoitems.items.EcoItems
+import com.willfp.ecoitems.items.ecoItem
+import org.bukkit.Material
+import org.bukkit.NamespacedKey
+import org.bukkit.inventory.ItemStack
+
+object EcoItemDependencyUtil {
+
+ fun ecoItemNamespace(item: ItemStack): NamespacedKey? {
+ val ecoi = item.ecoItem ?: return null
+
+ return ecoi.id
+ }
+
+ fun ecoItemFromKey(key: NamespacedKey): EcoItem? {
+ return EcoItems.getByID(key.toString())
+ }
+
+ fun ecoItemMaterialFromKey(key: NamespacedKey): Material? {
+ val ecoi = ecoItemFromKey(key) ?: return null
+
+ return ecoi.itemStack.type
+ }
+
+ fun newEcoItemstack(key: NamespacedKey): ItemStack? {
+ val ecoi = ecoItemFromKey(key) ?: return null
+
+ return ecoi.itemStack
+ }
+
+ fun getItems(): List {
+ return EcoItems.values().map { item -> item.id }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/EnchantmentSquaredDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/EnchantmentSquaredDependency.kt
index f4da612..d769986 100644
--- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/EnchantmentSquaredDependency.kt
+++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/EnchantmentSquaredDependency.kt
@@ -102,15 +102,15 @@ class EnchantmentSquaredDependency(private val enchantmentSquaredPlugin: Plugin)
private fun writeMissingGroups(){
// Write group that do not exist on custom anvil.
val shield = IncludeGroup("shield")
- shield.addToPolicy(Material.SHIELD)
+ shield.addToPolicy(Material.SHIELD.key)
MaterialGroupApi.addMaterialGroup(shield)
val elytra = IncludeGroup("elytra")
- elytra.addToPolicy(Material.ELYTRA)
+ elytra.addToPolicy(Material.ELYTRA.key)
MaterialGroupApi.addMaterialGroup(elytra)
val trinkets = IncludeGroup("trinkets")
- trinkets.addToPolicy(Material.ROTTEN_FLESH)
+ trinkets.addToPolicy(Material.ROTTEN_FLESH.key)
MaterialGroupApi.addMaterialGroup(trinkets)
}
diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchantsDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchantsDependency.kt
index 2aee624..816a4df 100644
--- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchantsDependency.kt
+++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ExcellentEnchantsDependency.kt
@@ -8,10 +8,12 @@ import org.bukkit.event.inventory.PrepareAnvilEvent
import org.bukkit.inventory.ItemStack
import org.bukkit.plugin.RegisteredListener
import xyz.alexcrea.cuanvil.api.EnchantmentApi
-import xyz.alexcrea.cuanvil.api.event.listener.CATreatAnvilResultEvent
+import xyz.alexcrea.cuanvil.api.event.listener.CATreatAnvilResult2Event
import xyz.alexcrea.cuanvil.enchant.wrapped.CAEEPreV5Enchantment
import xyz.alexcrea.cuanvil.enchant.wrapped.CAEEV5Enchantment
+import xyz.alexcrea.cuanvil.enchant.wrapped.CAEEV5_4Enchantment
import xyz.alexcrea.cuanvil.enchant.wrapped.CALegacyEEEnchantment
+import java.lang.reflect.Constructor
import java.lang.reflect.Method
import su.nightexpress.excellentenchants.api.EnchantRegistry as V5EnchantRegistry
import su.nightexpress.excellentenchants.enchantment.impl.universal.CurseOfFragilityEnchant as LegacyCurseOfFragilityEnchant
@@ -25,6 +27,8 @@ import su.nightexpress.excellentenchants.registry.EnchantRegistry as PreV5Enchan
class ExcellentEnchantsDependency {
enum class ListenerVersion(val classPath: String) {
+ V5_4("su.nightexpress.excellentenchants.enchantment.EnchantSettings"),
+ V5_3("su.nightexpress.excellentenchants.enchantment.EnchantRegistry"),
V5("su.nightexpress.excellentenchants.manager.listener.AnvilListener"),
PRE_V5("su.nightexpress.excellentenchants.enchantment.listener.AnvilListener"),
LEGACY("su.nightexpress.excellentenchants.enchantment.listener.EnchantAnvilListener"),
@@ -49,6 +53,8 @@ class ExcellentEnchantsDependency {
if (listenerVersion == null) {
CustomAnvil.instance.logger.severe("Found issue with listener of Excellent Enchants. compatiblity is broken. please contact CustomAnvil devs")
+ } else{
+ CustomAnvil.log("Support version: " + listenerVersion.name)
}
var isModernCurseOfFragility = true
@@ -67,7 +73,14 @@ class ExcellentEnchantsDependency {
// As excellent enchants is loaded before custom anvil and register enchantment to registry, we need to unregister old "vanilla" enchant.
when (listenerVersion) {
- ListenerVersion.V5 -> {
+ ListenerVersion.V5_4 -> {
+ for (enchantment in ExcellentEnchant5_3Registry.getRegistered()) {
+ EnchantmentApi.unregisterEnchantment(enchantment.bukkitEnchantment.key)
+ EnchantmentApi.registerEnchantment(CAEEV5_4Enchantment(enchantment))
+ }
+ }
+
+ ListenerVersion.V5, ListenerVersion.V5_3 -> {
for (enchantment in V5EnchantRegistry.getRegistered()) {
EnchantmentApi.unregisterEnchantment(enchantment.bukkitEnchantment.key)
EnchantmentApi.registerEnchantment(CAEEV5Enchantment(enchantment))
@@ -105,6 +118,8 @@ class ExcellentEnchantsDependency {
private lateinit var handleRechargeMethod: Method
private lateinit var handleCombineMethod: Method
+ private val prepareAnvilConstructor = PrepareAnvilEvent::class.java.constructors.first() as Constructor
+
fun redirectListeners() {
val toUnregister = ArrayList()
// get required PrepareAnvilEvent listener
@@ -119,7 +134,10 @@ class ExcellentEnchantsDependency {
}
when (listenerVersion) {
- ListenerVersion.V5 -> {
+ ListenerVersion.V5,
+ ListenerVersion.V5_3,
+ ListenerVersion.V5_4,
+ -> {
if (listener is V5AnvilListener) {
this.v5AnvilListener = listener
toUnregister.add(registeredListener)
@@ -151,7 +169,10 @@ class ExcellentEnchantsDependency {
}
when (listenerVersion) {
- ListenerVersion.V5 -> this.usedAnvilListener = v5AnvilListener!!
+ ListenerVersion.V5_3,
+ ListenerVersion.V5,
+ ListenerVersion.V5_4,
+ -> this.usedAnvilListener = v5AnvilListener!!
ListenerVersion.PRE_V5 -> this.usedAnvilListener = preV5AnvilListener!!
ListenerVersion.LEGACY -> this.usedAnvilListener = legacyAnvilListener!!
null -> {}
@@ -170,11 +191,19 @@ class ExcellentEnchantsDependency {
)
this.handleRechargeMethod.setAccessible(true)
- this.handleCombineMethod = this.usedAnvilListener.javaClass.getDeclaredMethod(
- "handleCombine",
- PrepareAnvilEvent::class.java, ItemStack::class.java, ItemStack::class.java, ItemStack::class.java
- )
- this.handleCombineMethod.setAccessible(true)
+ try {
+ this.handleCombineMethod = this.usedAnvilListener.javaClass.getDeclaredMethod(
+ "anvilCombine",
+ PrepareAnvilEvent::class.java, ItemStack::class.java, ItemStack::class.java, ItemStack::class.java
+ )
+ this.handleCombineMethod.setAccessible(true)
+ } catch (_: NoSuchMethodException) {
+ this.handleCombineMethod = this.usedAnvilListener.javaClass.getDeclaredMethod(
+ "handleCombine",
+ PrepareAnvilEvent::class.java, ItemStack::class.java, ItemStack::class.java, ItemStack::class.java
+ )
+ this.handleCombineMethod.setAccessible(true)
+ }
}
@@ -192,20 +221,25 @@ class ExcellentEnchantsDependency {
return handleRechargeMethod.invoke(this.usedAnvilListener, event, first, second) as Boolean
}
- fun treatAnvilResult(event: CATreatAnvilResultEvent) {
- val result = event.result
- if (result == null) return
+ fun treatAnvilResult(event: CATreatAnvilResult2Event) {
+ val result = event.result ?: return
- val first: ItemStack = treatInput(event.event.inventory.getItem(0))
- val second: ItemStack = treatInput(event.event.inventory.getItem(1))
+ val first: ItemStack = treatInput(event.leftItem)
+ val second: ItemStack = treatInput(event.rightItem)
+ val fakeEvent = prepareAnvilConstructor.newInstance(event.view, result)
- handleCombineMethod.invoke(this.usedAnvilListener, event.event, first, second, result)
+ handleCombineMethod.invoke(this.usedAnvilListener, fakeEvent, first, second, result)
+
+ event.result = fakeEvent.result
}
fun testAnvilResult(event: InventoryClickEvent): Any {
if (event.inventory.getItem(2) != null) {
when (listenerVersion) {
- ListenerVersion.V5 -> v5AnvilListener!!.onClickAnvil(event)
+ ListenerVersion.V5,
+ ListenerVersion.V5_3,
+ ListenerVersion.V5_4,
+ -> v5AnvilListener!!.onClickAnvil(event)
ListenerVersion.PRE_V5 -> preV5AnvilListener!!.onClickAnvil(event)
ListenerVersion.LEGACY -> legacyAnvilListener!!.onClickAnvil(event)
null -> {}
diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/GenericPluginDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/GenericPluginDependency.kt
index 775a057..62dae9b 100644
--- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/GenericPluginDependency.kt
+++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/GenericPluginDependency.kt
@@ -5,20 +5,14 @@ import org.bukkit.event.inventory.PrepareAnvilEvent
import org.bukkit.plugin.Plugin
import org.bukkit.plugin.RegisteredListener
-abstract class GenericPluginDependency(protected val plugin: Plugin) {
+open class GenericPluginDependency(protected open val plugin: Plugin, private val testPrepare: Boolean = true) {
- protected val preAnvil = ArrayList()
- protected val postAnvil = ArrayList()
+ private val preAnvil = ArrayList()
+ private val postAnvil = ArrayList()
open fun redirectListeners() {
- // get PreAnvil and PostAnvil listeners
- for (registeredListener in PrepareAnvilEvent.getHandlerList().registeredListeners) {
-
- if (registeredListener.plugin != plugin) continue
- preAnvil.add(registeredListener)
- }
-
- postAnvil.addAll(postAnvilEvents())
+ fillPreAnvil(preAnvil)
+ fillPostAnvil(postAnvil, preAnvil)
// get required PrepareAnvilEvent listener
for (listener in preAnvil) {
@@ -28,17 +22,37 @@ abstract class GenericPluginDependency(protected val plugin: Plugin) {
for (listener in postAnvil) {
InventoryClickEvent.getHandlerList().unregister(listener)
}
+ }
+
+ open fun fillPreAnvil(preAnvil: ArrayList){
+ // get PreAnvil and PostAnvil listeners
+ for (registeredListener in PrepareAnvilEvent.getHandlerList().registeredListeners) {
+
+ if (registeredListener.plugin != plugin) continue
+ preAnvil.add(registeredListener)
+ }
+ }
+
+ protected open fun fillPostAnvil(
+ postAnvil: ArrayList,
+ preAnvil: ArrayList) {
}
- protected abstract fun postAnvilEvents(): Collection
-
open fun testPrepareAnvil(event: PrepareAnvilEvent): Boolean {
+ if(!testPrepare) return false
+
val previousResult = event.result
event.result = null
for (registeredListener in preAnvil) {
- registeredListener.callEvent(event)
+ // We do not want error from another plugin to be our fault
+ try {
+ registeredListener.callEvent(event)
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+
if (event.result != null) return true
}
@@ -47,8 +61,15 @@ abstract class GenericPluginDependency(protected val plugin: Plugin) {
}
open fun testAnvilResult(event: InventoryClickEvent): Boolean {
+ if(!testPrepare) return false
+
for (registeredListener in postAnvil) {
- registeredListener.callEvent(event)
+ // We do not want error from another plugin to be our fault
+ try {
+ registeredListener.callEvent(event)
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
if (event.inventory.getItem(2) == null) return true
}
diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/HavenBagsDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/HavenBagsDependency.kt
index 6e7cf60..6f30497 100644
--- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/HavenBagsDependency.kt
+++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/HavenBagsDependency.kt
@@ -1,7 +1,7 @@
package xyz.alexcrea.cuanvil.dependency.plugins
import io.delilaheve.CustomAnvil
-import org.bukkit.entity.HumanEntity
+import org.bukkit.entity.Player
import org.bukkit.event.inventory.InventoryClickEvent
import org.bukkit.event.inventory.PrepareAnvilEvent
import org.bukkit.inventory.AnvilInventory
@@ -9,8 +9,9 @@ import org.bukkit.plugin.RegisteredListener
import valorless.havenbags.HavenBags
import valorless.havenbags.features.BagSkin
import valorless.havenbags.features.BagUpgrade
+import xyz.alexcrea.cuanvil.anvil.AnvilCost
import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener
-import xyz.alexcrea.cuanvil.util.AnvilXpUtil
+import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil
class HavenBagsDependency {
@@ -45,7 +46,7 @@ class HavenBagsDependency {
}
- fun testPrepareAnvil(event: PrepareAnvilEvent, player: HumanEntity): Boolean {
+ fun testPrepareAnvil(event: PrepareAnvilEvent, player: Player): Boolean {
val previousResult = event.result
event.result = null
@@ -53,14 +54,14 @@ class HavenBagsDependency {
bagSkin.onPrepareAnvil(event)
if (event.result != null) {
CustomAnvil.log("Detected pre anvil heaven bag anvil skin.")
- AnvilXpUtil.setAnvilInvXp(event.inventory, event.view, player, event.inventory.repairCost)
+ AnvilXpUtil.setAnvilInvCost(event.inventory, event.view, player, AnvilCost(event.inventory.repairCost))
return true
}
bagUpgrade.onPrepareAnvil(event)
if (event.result != null) {
CustomAnvil.log("Detected pre anvil heaven bag anvil upgrade.")
- AnvilXpUtil.setAnvilInvXp(event.inventory, event.view, player, event.inventory.repairCost)
+ AnvilXpUtil.setAnvilInvCost(event.inventory, event.view, player, AnvilCost(event.inventory.repairCost))
return true
}
diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ItemsAdderDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ItemsAdderDependency.kt
new file mode 100644
index 0000000..7f977e1
--- /dev/null
+++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ItemsAdderDependency.kt
@@ -0,0 +1,42 @@
+package xyz.alexcrea.cuanvil.dependency.plugins
+
+import dev.lone.itemsadder.api.CustomStack
+import dev.lone.itemsadder.api.ItemsAdder
+import org.bukkit.NamespacedKey
+import org.bukkit.inventory.ItemStack
+import org.bukkit.plugin.Plugin
+
+class ItemsAdderDependency(plugin: Plugin) : GenericPluginDependency(plugin) {
+ var isLoaded: Boolean = false
+ get() {
+ if (field) return true
+
+ // We can't be sure the event is registered before being triggered so we need to use this function
+ field = ItemsAdder.areItemsLoaded()
+ return field
+ }
+
+ fun tryClone(item: ItemStack): ItemStack? {
+ if(!isLoaded) return null
+ val customItem = CustomStack.byItemStack(item) ?: return null
+
+ return CustomStack.getInstance(customItem.namespacedID)?.itemStack
+ }
+
+ fun fromKey(key: NamespacedKey): ItemStack? {
+ if(!isLoaded) return null
+ return CustomStack.getInstance(key.toString())?.itemStack
+ }
+
+ fun getKey(item: ItemStack) : NamespacedKey? {
+ if(!isLoaded) return null
+ val customItem = CustomStack.byItemStack(item) ?: return null
+
+ return NamespacedKey.fromString(customItem.namespacedID)
+ }
+
+ fun idsCount(): Set {
+ return CustomStack.getNamespacedIdsInRegistry()
+ }
+
+}
diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/SuperEnchantDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/SuperEnchantDependency.kt
new file mode 100644
index 0000000..11622f9
--- /dev/null
+++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/SuperEnchantDependency.kt
@@ -0,0 +1,91 @@
+package xyz.alexcrea.cuanvil.dependency.plugins
+
+import com.maddoxh.superEnchants.SuperEnchants
+import com.maddoxh.superEnchants.enchants.EnchantManager
+import com.maddoxh.superEnchants.listeners.AnvilListener
+import io.delilaheve.CustomAnvil
+import org.bukkit.command.Command
+import org.bukkit.command.CommandExecutor
+import org.bukkit.command.CommandSender
+import org.bukkit.event.inventory.InventoryClickEvent
+import org.bukkit.plugin.RegisteredListener
+import xyz.alexcrea.cuanvil.api.EnchantmentApi
+import xyz.alexcrea.cuanvil.enchant.bulk.SuperEnchantBulkOperation
+import xyz.alexcrea.cuanvil.enchant.wrapped.CASuperEnchantEnchantment
+import java.util.logging.Level
+
+class SuperEnchantDependency(override val plugin: SuperEnchants): GenericPluginDependency(plugin, false) {
+
+ lateinit var enchManager: EnchantManager
+ val enchantments = ArrayList()
+
+ fun registerEnchantments(): Boolean{
+ CustomAnvil.instance.logger.info("Preparing Super Enchant compatibility...")
+
+ val field = SuperEnchants::class.java.getDeclaredField("enchantManager")
+ if(field == null) {
+ CustomAnvil.instance.logger.log(Level.SEVERE, "Failed to initialize Super Enchant compatibility")
+ return false
+ }
+ field.setAccessible(true)
+
+ val bulkOpperations = SuperEnchantBulkOperation(plugin)
+ EnchantmentApi.addBulkGet(bulkOpperations)
+ EnchantmentApi.addBulkClean(bulkOpperations)
+
+ enchManager = field.get(plugin) as EnchantManager
+ overrideReloadCommand()
+
+ reload()
+ return true
+ }
+
+ fun reload() {
+ for (enchantment in enchantments) {
+ EnchantmentApi.unregisterEnchantment(enchantment)
+ }
+ enchantments.clear()
+
+ // Register enchantments
+ for (enchant in enchManager.getAll()) {
+ val enchantment = CASuperEnchantEnchantment(enchant, plugin, enchManager)
+ enchantments.add(enchantment)
+
+ EnchantmentApi.registerEnchantment(enchantment)
+ }
+ }
+
+ private fun overrideReloadCommand() {
+ val reload = CustomAnvil.instance.getCommand("sereload")
+
+ reload?.setExecutor(ReloadInterceptor(reload.executor))
+ }
+
+ inner class ReloadInterceptor(val other: CommandExecutor): CommandExecutor {
+
+ override fun onCommand(
+ sender: CommandSender,
+ command: Command,
+ label: String,
+ args: Array
+ ): Boolean {
+ val result = other.onCommand(sender, command, label, args)
+
+ CustomAnvil.log("Detected SuperEnchant reload")
+ reload()
+
+ return result
+ }
+
+ }
+
+ override fun fillPostAnvil(postAnvil: ArrayList, preAnvil: ArrayList) {
+
+ for (registeredListener in InventoryClickEvent.getHandlerList().registeredListeners) {
+
+ if (registeredListener.listener.javaClass != AnvilListener::class.java) continue
+ postAnvil.add(registeredListener)
+ }
+ }
+
+}
diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ToolStatsDependency.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ToolStatsDependency.kt
index 513038b..255f737 100644
--- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ToolStatsDependency.kt
+++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/plugins/ToolStatsDependency.kt
@@ -11,7 +11,7 @@ import java.lang.reflect.Method
class ToolStatsDependency(plugin: Plugin) : GenericPluginDependency(plugin) {
- // Sadly, getTokens function is private, so I need to do that
+ // Sadly, getTokens function is private, so I need to do some reflectino
private val getTokenMethod: Method =
ItemChecker::class.java.getDeclaredMethod("getTokens", ItemStack::class.java);
@@ -19,10 +19,6 @@ class ToolStatsDependency(plugin: Plugin) : GenericPluginDependency(plugin) {
getTokenMethod.trySetAccessible()
}
- override fun postAnvilEvents(): Collection {
- return listOf()
- }
-
private fun ItemChecker.getTokenSafe(item: ItemStack?): Array {
if (item == null) return arrayOf()
return getTokenMethod.invoke(this, item) as Array
diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/scheduler/BukkitScheduler.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/scheduler/BukkitScheduler.kt
index b26dd42..8c04162 100644
--- a/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/scheduler/BukkitScheduler.kt
+++ b/src/main/kotlin/xyz/alexcrea/cuanvil/dependency/scheduler/BukkitScheduler.kt
@@ -6,12 +6,12 @@ import org.bukkit.plugin.Plugin
class BukkitScheduler : TaskScheduler {
- override fun scheduleGlobally(plugin: Plugin, task: Runnable, time: Long): Any? {
+ override fun scheduleGlobally(plugin: Plugin, task: Runnable, time: Long): Any {
return Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, task, time)
}
- override fun scheduleOnEntity(plugin: Plugin, entity: Entity, task: Runnable, time: Long): Any? {
+ override fun scheduleOnEntity(plugin: Plugin, entity: Entity, task: Runnable, time: Long): Any {
return Bukkit.getScheduler().scheduleSyncDelayedTask(plugin, task, time)
}
}
diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/group/AbstractMaterialGroup.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/group/AbstractMaterialGroup.kt
index ec6e7bc..d0d2bda 100644
--- a/src/main/kotlin/xyz/alexcrea/cuanvil/group/AbstractMaterialGroup.kt
+++ b/src/main/kotlin/xyz/alexcrea/cuanvil/group/AbstractMaterialGroup.kt
@@ -1,7 +1,8 @@
package xyz.alexcrea.cuanvil.group
import org.bukkit.Material
-import java.util.*
+import org.bukkit.NamespacedKey
+import xyz.alexcrea.cuanvil.util.MaterialUtil
abstract class AbstractMaterialGroup(private val name: String) {
protected val includedMaterial by lazy { createDefaultSet() }
@@ -9,12 +10,12 @@ abstract class AbstractMaterialGroup(private val name: String) {
/**
* Get the group default set
*/
- protected abstract fun createDefaultSet(): EnumSet
+ protected abstract fun createDefaultSet(): MutableSet
/**
* Get if a material is allowed following the group policy
*/
- open fun contain(mat: Material): Boolean {
+ open fun contain(mat: NamespacedKey): Boolean {
return mat in getMaterials()
}
@@ -27,13 +28,13 @@ abstract class AbstractMaterialGroup(private val name: String) {
* Push a material to this group to follow this group policy
* @return this instance.
*/
- abstract fun addToPolicy(mat: Material): AbstractMaterialGroup
+ abstract fun addToPolicy(type: NamespacedKey): AbstractMaterialGroup
/**
* Push a list of material to this group to follow this group policy
* @return this instance.
*/
- fun addAll(vararg materials: Material): AbstractMaterialGroup {
+ fun addAll(vararg materials: NamespacedKey): AbstractMaterialGroup {
for (material in materials) {
addToPolicy(material)
}
@@ -60,19 +61,19 @@ abstract class AbstractMaterialGroup(private val name: String) {
/**
* Get the group contained material as a set
*/
- abstract fun getMaterials(): EnumSet
+ abstract fun getMaterials(): Set
/**
* Get the group non-inherited material as a set
*/
- open fun getNonGroupInheritedMaterials(): EnumSet {
+ open fun getNonGroupInheritedMaterials(): Set {
return includedMaterial
}
/**
* Get the group non-inherited material as a set
*/
- open fun setNonGroupInheritedMaterials(materials: EnumSet) {
+ open fun setNonGroupInheritedMaterials(materials: Set) {
this.includedMaterial.clear()
this.includedMaterial.addAll(materials)
}
@@ -102,8 +103,9 @@ abstract class AbstractMaterialGroup(private val name: String) {
// Test inner material
val matIterator = includedMaterial.iterator()
while (matIterator.hasNext()) {
- val material = matIterator.next()
- if (material.isAir) continue
+ val key = matIterator.next()
+ val material = MaterialUtil.getMatFromKey(key)
+ if (material == null || material.isAir) continue
return material
}
// Test included group representative material
diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictGroup.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictGroup.kt
index 56e923f..59841ac 100644
--- a/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictGroup.kt
+++ b/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictGroup.kt
@@ -2,15 +2,18 @@ package xyz.alexcrea.cuanvil.group
import io.delilaheve.CustomAnvil
import org.bukkit.Material
+import org.bukkit.NamespacedKey
import xyz.alexcrea.cuanvil.enchant.CAEnchantment
class EnchantConflictGroup(
val name: String,
private val cantConflict: AbstractMaterialGroup,
- var minBeforeBlock: Int
+ var minBeforeBlock: Int,
) {
private val enchantments = HashSet()
+ private val conflictsAfterLevel = HashMap()
+ private val conflictsBeforeLevel = HashMap()
fun addEnchantment(enchant: CAEnchantment) {
enchantments.add(enchant)
@@ -19,19 +22,56 @@ class EnchantConflictGroup(
enchantments.addAll(enchants)
}
- fun allowed(enchants: Set, mat: Material): Boolean {
+ private fun canBypassByBeforeLevel(enchants: Map): Boolean {
+ // Either there no "conflict after"
+ if(conflictsAfterLevel.isEmpty()) return false
+
+ // Or we check if any conflict after enchantment is true
+ for (entry in conflictsAfterLevel) {
+ val current = enchants.getOrDefault(entry.key, 0)
+ if(current > entry.value)
+ return false
+ }
+
+ return true
+ }
+
+ private fun canBypassByAfterLevel(enchants: Map): Boolean {
+ // Either there no "conflict after"
+ if(conflictsBeforeLevel.isEmpty()) return false
+
+ // Or we check if any conflict after enchantment is true
+ for (entry in conflictsBeforeLevel) {
+ val current = enchants.getOrDefault(entry.key, 0)
+ if(current < entry.value)
+ return false
+ }
+
+ return true
+ }
+
+ private fun canBypassConflictByLevel(enchants: Map): Boolean {
+ return canBypassByBeforeLevel(enchants) || canBypassByAfterLevel(enchants)
+ }
+
+ fun allowed(enchants: Map, mat: NamespacedKey): Boolean {
if (enchantments.size < minBeforeBlock) {
CustomAnvil.verboseLog("Conflicting bc of to many enchantments")
return true
}
- if (cantConflict.contain(mat)) {
+ if (cantConflict.contain(mat))
+ return true
+
+ // If empty we skip. else we
+ if(canBypassConflictByLevel(enchants))
return true
- }
// Count the amount of enchantment that are in the list
var enchantAmount = 0
- for (enchantment in enchants) {
+ for (entry in enchants) {
+ val enchantment = entry.key
+
if (enchantment !in enchantments) continue
CustomAnvil.verboseLog("Enchant ${enchantment.key} is in: ${enchantAmount + 1}/$minBeforeBlock ")
if (++enchantAmount > minBeforeBlock) {
@@ -56,6 +96,36 @@ class EnchantConflictGroup(
enchantments.addAll(enchants)
}
+ fun getConflictAfters(): HashMap {
+ return conflictsAfterLevel
+ }
+
+ fun putConflictAfterLevel(enchantment: CAEnchantment, level: Int): Boolean {
+ return null != (
+ if(level < 0) conflictsAfterLevel.remove(enchantment)
+ else conflictsAfterLevel.put(enchantment, level))
+ }
+
+ fun setConflictsAfterLevel(conflictAfterLevel: HashMap) {
+ this.conflictsAfterLevel.clear()
+ this.conflictsAfterLevel.putAll(conflictAfterLevel)
+ }
+
+ fun getConflictsBefore(): HashMap {
+ return conflictsBeforeLevel
+ }
+
+ fun putConflictsBeforeLevel(enchantment: CAEnchantment, level: Int): Boolean {
+ return null != (
+ if(level < 0) conflictsBeforeLevel.remove(enchantment)
+ else conflictsBeforeLevel.put(enchantment, level))
+ }
+
+ fun setConflictsBeforeLevel(conflictBeforeLevel: HashMap) {
+ this.conflictsBeforeLevel.clear()
+ this.conflictsBeforeLevel.putAll(conflictBeforeLevel)
+ }
+
fun getRepresentativeMaterial(): Material {
val groups = getCantConflictGroup().getGroups()
val groupIterator = groups.iterator()
diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictManager.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictManager.kt
index 169d9e9..38d5476 100644
--- a/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictManager.kt
+++ b/src/main/kotlin/xyz/alexcrea/cuanvil/group/EnchantConflictManager.kt
@@ -8,7 +8,9 @@ import org.bukkit.inventory.ItemStack
import xyz.alexcrea.cuanvil.enchant.AdditionalTestEnchantment
import xyz.alexcrea.cuanvil.enchant.CAEnchantment
import xyz.alexcrea.cuanvil.enchant.CAEnchantmentRegistry
+import xyz.alexcrea.cuanvil.util.MaterialUtil.customType
import java.util.*
+import kotlin.collections.set
class EnchantConflictManager {
@@ -16,6 +18,14 @@ class EnchantConflictManager {
// Path for the enchantments list
const val ENCH_LIST_PATH = "enchantments"
+ // Path for list of enchantments conflicting before level
+ //TODO add test and gui
+ const val CONFLICT_AFTER_LEVEL_LIST_PATH = "conflict_after_level"
+
+ // Path for list of enchantments conflicting before level
+ //TODO add test and gui
+ const val CONFLICT_BEFORE_LEVEL_LIST_PATH = "conflict_before_level"
+
// Path for group list related to the conflict
const val CONFLICT_GROUP_PATH = "notAffectedGroups"
@@ -39,6 +49,11 @@ class EnchantConflictManager {
lateinit var conflictList: ArrayList
+
+ private fun warnBadKey(key: String) {
+ CustomAnvil.instance.logger.warning("Invalid key $key for conflict: is not a conflict")
+ }
+
// Read and prepare all conflict
fun prepareConflicts(config: ConfigurationSection, itemManager: ItemGroupManager) {
conflictList = ArrayList()
@@ -50,7 +65,11 @@ class EnchantConflictManager {
val keys = config.getKeys(false)
for (key in keys) {
- val section = config.getConfigurationSection(key)!!
+ val section = config.getConfigurationSection(key)
+ if(section == null) {
+ warnBadKey(key)
+ continue
+ }
val conflict = createConflict(section, itemManager, key)
addConflict(conflict)
@@ -110,9 +129,36 @@ class EnchantConflictManager {
}
}
+ val conflictsAfterLevel = section.getConfigurationSection(CONFLICT_AFTER_LEVEL_LIST_PATH)
+ val conflictsAfterMap = conflict.getConflictAfters()
+ fetchConditionalRestriction(conflictsAfterMap, conflictsAfterLevel, conflictName)
+
+ val conflictsBeforeLevel = section.getConfigurationSection(CONFLICT_BEFORE_LEVEL_LIST_PATH)
+ val conflictsBeforeMap = conflict.getConflictsBefore()
+ fetchConditionalRestriction(conflictsBeforeMap, conflictsBeforeLevel, conflictName)
+
return conflict
}
+ private fun fetchConditionalRestriction(restrictions: MutableMap, section: ConfigurationSection?, conflictName: String) {
+ if(section == null) return
+ for (enchantName in section.getKeys(false)) {
+ val enchants = getEnchantByIdentifier(enchantName)
+ if (enchants.isEmpty()) {
+ CustomAnvil.instance.logger.warning("Enchantment $enchantName do not exist but was asked for conditional restriction for conflict $conflictName")
+ continue
+ }
+
+ val value = section.getInt(enchantName, -1)
+ if(value < 0) continue
+
+ for (enchant in enchants) {
+ val previous = restrictions.getOrDefault(enchant, value)
+ restrictions[enchant] = value.coerceAtMost(previous)
+ }
+ }
+ }
+
private fun getEnchantByIdentifier(enchantName: String): List {
val key = NamespacedKey.fromString(enchantName)
if (key != null) {
@@ -175,8 +221,8 @@ class EnchantConflictManager {
item: ItemStack,
newEnchant: CAEnchantment
): ConflictType {
- val mat = item.type
- CustomAnvil.verboseLog("Testing conflict for ${newEnchant.key} on ${mat.key}")
+ val type = item.customType
+ CustomAnvil.verboseLog("Testing conflict for ${newEnchant.key} on ${type}")
val conflictList = newEnchant.conflicts
var result = ConflictType.NO_CONFLICT
@@ -187,7 +233,7 @@ class EnchantConflictManager {
continue
}
- val allowed = conflict.allowed(appliedEnchants.keys, mat)
+ val allowed = conflict.allowed(appliedEnchants, type)
CustomAnvil.verboseLog("Was against $conflict and conflicting: ${!allowed} ")
if (!allowed) {
if (conflict.getEnchants().size <= 1) {
@@ -203,7 +249,7 @@ class EnchantConflictManager {
val immutableEnchants = Collections.unmodifiableMap(appliedEnchants)
for (appliedEnchant in appliedEnchants.keys) {
if (appliedEnchant is AdditionalTestEnchantment) {
- val doConflict = appliedEnchant.isEnchantConflict(immutableEnchants, mat)
+ val doConflict = appliedEnchant.isEnchantConflict(immutableEnchants, type)
if (doConflict) {
CustomAnvil.verboseLog("Big conflict by additional test, stopping")
return ConflictType.ENCHANTMENT_CONFLICT
@@ -215,7 +261,7 @@ class EnchantConflictManager {
if ((result != ConflictType.ITEM_CONFLICT) && (newEnchant is AdditionalTestEnchantment)) {
val partialItem = createPartialResult(item, immutableEnchants)
- if (newEnchant.isItemConflict(immutableEnchants, mat, partialItem)) {
+ if (newEnchant.isItemConflict(immutableEnchants, type, partialItem)) {
return ConflictType.ITEM_CONFLICT
}
diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/group/ExcludeGroup.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/group/ExcludeGroup.kt
index 7684c3f..d752db5 100644
--- a/src/main/kotlin/xyz/alexcrea/cuanvil/group/ExcludeGroup.kt
+++ b/src/main/kotlin/xyz/alexcrea/cuanvil/group/ExcludeGroup.kt
@@ -1,11 +1,12 @@
package xyz.alexcrea.cuanvil.group
-import org.bukkit.Material
+import org.bukkit.NamespacedKey
import java.util.*
class ExcludeGroup(name: String) : AbstractMaterialGroup(name) {
- override fun createDefaultSet(): EnumSet {
- return EnumSet.allOf(Material::class.java)
+
+ override fun createDefaultSet(): MutableSet {
+ return NegativeMaterialSet()
}
private var includedGroup: MutableSet = HashSet()
@@ -20,9 +21,9 @@ class ExcludeGroup(name: String) : AbstractMaterialGroup(name) {
return false
}
- override fun addToPolicy(mat: Material): ExcludeGroup {
- includedMaterial.remove(mat)
- groupItems.remove(mat)
+ override fun addToPolicy(type: NamespacedKey): ExcludeGroup {
+ includedMaterial.remove(type)
+ groupItems.remove(type)
return this
}
@@ -60,7 +61,7 @@ class ExcludeGroup(name: String) : AbstractMaterialGroup(name) {
}
}
- override fun getMaterials(): EnumSet {
+ override fun getMaterials(): MutableSet {
return groupItems
}
diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/group/IncludeGroup.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/group/IncludeGroup.kt
index 848789f..fc9614b 100644
--- a/src/main/kotlin/xyz/alexcrea/cuanvil/group/IncludeGroup.kt
+++ b/src/main/kotlin/xyz/alexcrea/cuanvil/group/IncludeGroup.kt
@@ -1,11 +1,12 @@
package xyz.alexcrea.cuanvil.group
import org.bukkit.Material
+import org.bukkit.NamespacedKey
import java.util.*
class IncludeGroup(name: String) : AbstractMaterialGroup(name) {
- override fun createDefaultSet(): EnumSet {
- return EnumSet.noneOf(Material::class.java)
+ override fun createDefaultSet(): MutableSet {
+ return HashSet()
}
private var includedGroup: MutableSet = HashSet()
@@ -20,9 +21,9 @@ class IncludeGroup(name: String) : AbstractMaterialGroup(name) {
return false
}
- override fun addToPolicy(mat: Material): IncludeGroup {
- includedMaterial.add(mat)
- groupItems.add(mat)
+ override fun addToPolicy(type: NamespacedKey): IncludeGroup {
+ includedMaterial.add(type)
+ groupItems.add(type)
return this
}
@@ -47,7 +48,7 @@ class IncludeGroup(name: String) : AbstractMaterialGroup(name) {
}
}
- override fun setNonGroupInheritedMaterials(materials: EnumSet) {
+ override fun setNonGroupInheritedMaterials(materials: Set) {
super.setNonGroupInheritedMaterials(materials)
updateMaterials()
@@ -66,7 +67,7 @@ class IncludeGroup(name: String) : AbstractMaterialGroup(name) {
}
}
- override fun getMaterials(): EnumSet {
+ override fun getMaterials(): MutableSet {
return groupItems
}
diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/group/ItemGroupManager.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/group/ItemGroupManager.kt
index 65eef34..348d0ff 100644
--- a/src/main/kotlin/xyz/alexcrea/cuanvil/group/ItemGroupManager.kt
+++ b/src/main/kotlin/xyz/alexcrea/cuanvil/group/ItemGroupManager.kt
@@ -31,6 +31,8 @@ class ItemGroupManager {
for (key in keys) {
if (groupMap.containsKey(key))
continue
+ if (!config.isConfigurationSection(key))
+ continue
createGroup(config, keys, key)
}
}
@@ -51,6 +53,7 @@ class ItemGroupManager {
key: String
): AbstractMaterialGroup {
val groupSection = config.getConfigurationSection(key)!!
+
val groupType = groupSection.getString(GROUP_TYPE_PATH, null)
// Create Material group according to the group type
@@ -91,7 +94,7 @@ class ItemGroupManager {
}
continue
}
- group.addToPolicy(material)
+ group.addToPolicy(material.key)
}
// Read group to include in this group policy.
@@ -105,11 +108,13 @@ class ItemGroupManager {
continue
}
// Get other group or create it if not yet created
- val otherGroup = if (!groupMap.containsKey(groupName)) {
+ val otherGroup =
+ if (!groupMap.containsKey(groupName)) {
+ if(!config.isConfigurationSection(groupName)) continue
createGroup(config, keys, groupName)
- } else {
- groupMap[groupName]!!
}
+ else groupMap[groupName]!!
+
// Avoid self reference or it will create an infinite loop
if (otherGroup.isReferencing(group)) {
CustomAnvil.instance.logger.warning(
diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/group/NegativeMaterialSet.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/group/NegativeMaterialSet.kt
new file mode 100644
index 0000000..d87004d
--- /dev/null
+++ b/src/main/kotlin/xyz/alexcrea/cuanvil/group/NegativeMaterialSet.kt
@@ -0,0 +1,22 @@
+package xyz.alexcrea.cuanvil.group
+
+import org.bukkit.NamespacedKey
+import xyz.alexcrea.cuanvil.util.MaterialUtil
+import xyz.alexcrea.cuanvil.util.NegativeSet
+
+class NegativeMaterialSet: NegativeSet() {
+
+ override fun iterator(): MutableIterator {
+ val materials = MaterialUtil.getMaterials()
+ materials.removeIf { negate.contains(it) }
+
+ return materials.iterator()
+ }
+
+ override fun isEmpty(): Boolean {
+ return negate.size >= MaterialUtil.getMaterialCount()
+ }
+
+ override val size get() = MaterialUtil.getMaterialCount() - negate.size
+
+}
\ No newline at end of file
diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilCloseListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilCloseListener.kt
index 60a0339..d707b56 100644
--- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilCloseListener.kt
+++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilCloseListener.kt
@@ -7,6 +7,7 @@ import org.bukkit.event.Listener
import org.bukkit.event.inventory.InventoryCloseEvent
import org.bukkit.inventory.AnvilInventory
import xyz.alexcrea.cuanvil.dependency.packet.PacketManager
+import xyz.alexcrea.cuanvil.util.dialog.AnvilRenameDialogUtil
class AnvilCloseListener(private val packetManager: PacketManager) : Listener {
@@ -18,6 +19,7 @@ class AnvilCloseListener(private val packetManager: PacketManager) : Listener {
packetManager.setInstantBuild(player, false)
}
+ AnvilRenameDialogUtil.anvilRenameDialog.closeInventory(player)
}
}
\ No newline at end of file
diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt
index 64e43d1..e393dbd 100644
--- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt
+++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/AnvilResultListener.kt
@@ -3,7 +3,6 @@ package xyz.alexcrea.cuanvil.listener
import io.delilaheve.CustomAnvil
import io.delilaheve.util.ConfigOptions
import io.delilaheve.util.ItemUtil.canMergeWith
-import io.delilaheve.util.ItemUtil.unitRepair
import org.bukkit.GameMode
import org.bukkit.Material
import org.bukkit.entity.Player
@@ -16,20 +15,26 @@ import org.bukkit.inventory.AnvilInventory
import org.bukkit.inventory.InventoryView
import org.bukkit.inventory.ItemStack
import org.bukkit.inventory.meta.BookMeta
+import xyz.alexcrea.cuanvil.anvil.AnvilCost
+import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic
+import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.AnvilResult
+import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.CustomCraftResult
+import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.LoreEditResult
+import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.UnitRepairResult
import xyz.alexcrea.cuanvil.dependency.DependencyManager
+import xyz.alexcrea.cuanvil.dependency.economy.EconomyManager
+import xyz.alexcrea.cuanvil.dependency.util.PlatformUtil.setComponentDisplayName
import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener.Companion.ANVIL_INPUT_LEFT
import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener.Companion.ANVIL_INPUT_RIGHT
import xyz.alexcrea.cuanvil.listener.PrepareAnvilListener.Companion.ANVIL_OUTPUT_SLOT
-import xyz.alexcrea.cuanvil.recipe.AnvilCustomRecipe
-import xyz.alexcrea.cuanvil.util.AnvilLoreEditUtil
-import xyz.alexcrea.cuanvil.util.AnvilUseType
-import xyz.alexcrea.cuanvil.util.AnvilXpUtil
import xyz.alexcrea.cuanvil.util.CustomRecipeUtil
-import xyz.alexcrea.cuanvil.util.UnitRepairUtil.getRepair
+import xyz.alexcrea.cuanvil.util.MiniMessageUtil
+import xyz.alexcrea.cuanvil.util.anvil.AnvilLoreEditUtil
+import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil
import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil
import xyz.alexcrea.cuanvil.util.config.LoreEditType
import java.util.*
-import java.util.concurrent.atomic.AtomicInteger
+import java.util.concurrent.atomic.AtomicReference
import kotlin.math.min
class AnvilResultListener : Listener {
@@ -47,6 +52,7 @@ class AnvilResultListener : Listener {
fun anvilExtractionCheck(event: InventoryClickEvent) {
val player = event.whoClicked as? Player ?: return
val inventory = event.inventory as? AnvilInventory ?: return
+ val view = event.view
if (event.rawSlot != ANVIL_OUTPUT_SLOT) {
return
@@ -61,84 +67,104 @@ class AnvilResultListener : Listener {
val leftItem = inventory.getItem(ANVIL_INPUT_LEFT) ?: return
val rightItem = inventory.getItem(ANVIL_INPUT_RIGHT)
+ // Deny by default. allow if working
+ event.result = Event.Result.DENY
if (GameMode.CREATIVE != player.gameMode && inventory.repairCost >= inventory.maximumRepairCost) {
- event.result = Event.Result.DENY
return
}
// Test custom recipe
- val recipe = CustomRecipeUtil.getCustomRecipe(leftItem, rightItem)
- if (recipe != null) {
- event.result = Event.Result.ALLOW
+ val customRecipeResult = AnvilMergeLogic.testCustomRecipe(view, inventory, player, leftItem, rightItem)
+ if (!customRecipeResult.isEmpty()) {
onCustomCraft(
- event, recipe, player,
- leftItem, rightItem, output, inventory
+ event, player, inventory,
+ leftItem, rightItem, customRecipeResult
)
return
}
// Do not continue if there was no change
if ((output == inventory.getItem(ANVIL_INPUT_LEFT))) {
- event.result = Event.Result.DENY
return
}
// Rename
if (rightItem == null) {
- event.result = Event.Result.ALLOW
+ val result = AnvilMergeLogic.doRenaming(view, inventory, player, leftItem)
+ if (result.isEmpty()) return
+
+ extractAnvilResult(
+ event, player, inventory,
+ null, 0,
+ null, 0,
+ result
+ )
return
}
// Merge
val canMerge = leftItem.canMergeWith(rightItem)
if (canMerge) {
- event.result = Event.Result.ALLOW
+ val result = AnvilMergeLogic.doMerge(view, inventory, player, leftItem, rightItem)
+
+ extractAnvilResult(
+ event, player, inventory,
+ null, 0,
+ null, 0,
+ result
+ )
return
}
// Unit repair
- val unitRepairResult = leftItem.getRepair(rightItem)
- if (unitRepairResult != null) {
+ val unitRepairResult = AnvilMergeLogic.testUnitRepair(
+ view, inventory, player,
+ leftItem, rightItem
+ )
+ if (!unitRepairResult.isEmpty()) {
onUnitRepairExtract(
- leftItem, rightItem, output,
- unitRepairResult, event, player, inventory
+ rightItem, event, player, inventory,
+ unitRepairResult
)
return
}
// For lore edit
- if (handleBookLoreEdit(event, inventory, player, leftItem, rightItem, output)) {
- return
- } else if (handlePaperLoreEdit(event, inventory, player, leftItem, rightItem, output)) {
+ val loreResult = AnvilMergeLogic.testLoreEdit(player, leftItem, rightItem)
+ if (!loreResult.isEmpty()) {
+ if (loreResult.type.isBook)
+ handleBookLoreEdit(event, inventory, player, leftItem, rightItem, loreResult)
+ else
+ handlePaperLoreEdit(event, inventory, player, leftItem, rightItem, loreResult)
return
}
-
- // Else there was no working situation somehow so we deny
- event.result = Event.Result.DENY
}
private fun onCustomCraft(
event: InventoryClickEvent,
- recipe: AnvilCustomRecipe,
player: Player,
+ inventory: AnvilInventory,
leftItem: ItemStack,
rightItem: ItemStack?,
- output: ItemStack,
- inventory: AnvilInventory
+ result: CustomCraftResult,
) {
- event.result = Event.Result.DENY
-
- if (recipe.leftItem == null) return // in case it changed
-
- val amount = CustomRecipeUtil.getCustomRecipeAmount(recipe, leftItem, rightItem)
- val xpCost = recipe.determineCost(amount, leftItem, output)
+ val recipe = result.recipe!!
+ val rawCost = result.customCraftCost.rawCost
val finalCost =
- if (recipe.removeExactLinearXp) xpCost
- else AnvilXpUtil.calculateLevelForXp(xpCost)
+ if (recipe.removeExactLinearXp) rawCost
+ else AnvilXpUtil.calculateLevelForXp(rawCost)
+
+ CustomAnvil.log(
+ "gamemode: ${player.gameMode != GameMode.CREATIVE}, " +
+ "cost: $finalCost, level: ${player.level}, " +
+ "result: ${player.totalExperience < finalCost} ${player.level < finalCost}"
+ )
- CustomAnvil.log("gamemode: ${player.gameMode != GameMode.CREATIVE}, cost: $finalCost, level: ${player.level}, result: ${player.totalExperience < finalCost} ${player.level < finalCost}")
if (player.gameMode != GameMode.CREATIVE) {
- if (recipe.removeExactLinearXp) {
+ if (ConfigOptions.shouldUseMoney(player)) {
+ result.cost.isMonetary = true
+ if (!EconomyManager.economy!!.has(player, result.cost.asMonetaryCost())) return
+ } else if (recipe.removeExactLinearXp) {
val levelXp = AnvilXpUtil.calculateXpForLevel(player.level)
val delta = AnvilXpUtil.calculateXpForLevel(player.level + 1) - levelXp
val totalXp = levelXp + player.exp * delta
@@ -155,31 +181,31 @@ class AnvilResultListener : Listener {
if (event.click != ClickType.MIDDLE &&
!handleCustomCraftClick(
event,
- recipe,
inventory,
player,
leftItem,
rightItem,
- amount,
- finalCost,
- recipe.removeExactLinearXp
+ result
)
) return
// Finally, we add the item to the player
if (slotDestination.type == SlotType.CURSOR) {
- player.setItemOnCursor(output)
+ player.setItemOnCursor(result.item)
} else {// We assume SlotType == SlotType.INVENTORY
- player.inventory.setItem(slotDestination.slot, output)
+ player.inventory.setItem(slotDestination.slot, result.item)
}
}
private fun handleCustomCraftClick(
- event: InventoryClickEvent, recipe: AnvilCustomRecipe,
+ event: InventoryClickEvent,
inventory: AnvilInventory, player: Player,
leftItem: ItemStack, rightItem: ItemStack?,
- amount: Int, xpCost: Int, linearCost: Boolean = false
+ result: CustomCraftResult
): Boolean {
+ val amount = result.amount
+ val recipe = result.recipe!!
+
// We remove what should be removed
if (rightItem != null) {
if (recipe.rightItem == null) return false// in case it changed
@@ -191,25 +217,7 @@ class AnvilResultListener : Listener {
leftItem.amount -= amount * recipe.leftItem!!.amount
inventory.setItem(ANVIL_INPUT_LEFT, leftItem)
- if (player.gameMode != GameMode.CREATIVE) {
- if (linearCost) {
- val levelXp = AnvilXpUtil.calculateXpForLevel(player.level)
- val delta = AnvilXpUtil.calculateXpForLevel(player.level + 1) - levelXp
- var totalXp = levelXp + player.exp * delta
- totalXp -= xpCost
-
- val newLevel = AnvilXpUtil.calculateLevelForXp(totalXp.toInt())
-
- val newLevelXp = AnvilXpUtil.calculateXpForLevel(newLevel)
- val newDelta = AnvilXpUtil.calculateXpForLevel(newLevel + 1) - newLevelXp
- val xp = (totalXp - newLevelXp) / newDelta
-
- player.level = newLevel
- player.exp = xp / newDelta
- } else {
- player.level -= xpCost
- }
- }
+ removeCustomCraftCost(player, result)
// Then we try to find the new values for the anvil
val newAmount = CustomRecipeUtil.getCustomRecipeAmount(recipe, leftItem, rightItem)
@@ -233,6 +241,53 @@ class AnvilResultListener : Listener {
return true
}
+ private fun removeCustomCraftCost(player: Player, result: CustomCraftResult) {
+ if (player.gameMode == GameMode.CREATIVE) return
+
+ val rawCost = result.customCraftCost.rawCost
+ if (result.cost.isMonetary) {
+ EconomyManager.economy!!.remove(player, result.cost.asMonetaryCost())
+ return
+ }
+
+ if (result.recipe!!.removeExactLinearXp) {
+ val levelXp = AnvilXpUtil.calculateXpForLevel(player.level)
+ val delta = AnvilXpUtil.calculateXpForLevel(player.level + 1) - levelXp
+ var totalXp = levelXp + player.exp * delta
+ totalXp -= rawCost
+
+ val newLevel = AnvilXpUtil.calculateLevelForXp(totalXp.toInt())
+
+ val newLevelXp = AnvilXpUtil.calculateXpForLevel(newLevel)
+ val newDelta = AnvilXpUtil.calculateXpForLevel(newLevel + 1) - newLevelXp
+ val xp = (totalXp - newLevelXp) / newDelta
+
+ player.level = newLevel
+ player.exp = xp / newDelta
+ } else {
+ player.level -= AnvilXpUtil.calculateLevelForXp(rawCost)
+ }
+
+ }
+
+ private fun tryRemoveCost(player: Player, result: AnvilResult): Boolean {
+ if (player.gameMode == GameMode.CREATIVE) return true
+
+ val cost = result.cost
+ if (cost.isMonetary) {
+ val result = EconomyManager.economy!!.remove(player, cost.asMonetaryCost())
+ if (!result) return false
+ } else {
+ val xpCost = cost.filteredXpCost()
+ if (xpCost > AnvilXpUtil.maximumXpCost(result.ignoreXpRules)) return false
+ if (player.level < xpCost) return false
+
+ player.level -= xpCost
+ }
+
+ return true
+ }
+
private fun extractAnvilResult(
event: InventoryClickEvent,
player: Player,
@@ -241,15 +296,17 @@ class AnvilResultListener : Listener {
leftRemoveCount: Int,
rightItem: ItemStack?,
rightRemoveCount: Int,
- output: ItemStack,
- repairCost: Int,
+ result: AnvilResult
): Boolean {
+ if (result.isEmpty()) return false
+
// To avoid vanilla, we cancel the event
event.result = Event.Result.DENY
event.isCancelled = true
+ val cost = result.cost
- // Assumed if player do not have enough xp then it returned MIN_VALUE
- if (repairCost == Int.MIN_VALUE) return false
+ processCost(inventory, player, cost)
+ if (!cost.valid && player.gameMode != GameMode.CREATIVE) return false
// Where should we get the item
val slotDestination = getActionSlot(event, player)
@@ -257,6 +314,8 @@ class AnvilResultListener : Listener {
// If not creative middle click...
if (event.click != ClickType.MIDDLE) {
+ if (!tryRemoveCost(player, result)) return false
+
// We remove what should be removed
if (leftItem != null) leftItem.amount -= leftRemoveCount
inventory.setItem(ANVIL_INPUT_LEFT, leftItem)
@@ -265,99 +324,58 @@ class AnvilResultListener : Listener {
inventory.setItem(ANVIL_INPUT_RIGHT, rightItem)
inventory.setItem(ANVIL_OUTPUT_SLOT, null)
- player.level -= repairCost
+
}
// Finally, we add the item to the player
if (SlotType.CURSOR == slotDestination.type) {
- player.setItemOnCursor(output)
+ player.setItemOnCursor(result.item)
} else {// We assume SlotType == SlotType.INVENTORY
- player.inventory.setItem(slotDestination.slot, output)
+ player.inventory.setItem(slotDestination.slot, result.item)
}
// TODO probably anvil damage & sound here ??
return true
}
- private fun onUnitRepairExtract(
- leftItem: ItemStack,
- rightItem: ItemStack,
- output: ItemStack,
- unitRepairResult: Double,
- event: InventoryClickEvent,
- player: Player,
- inventory: AnvilInventory
- ) {
- val resultCopy = leftItem.clone()
- val resultAmount = resultCopy.unitRepair(
- rightItem.amount, unitRepairResult
- )
-
- // Get repair cost
- val repairCost = getUnitRepairCost(inventory, player, leftItem, output, resultCopy, resultAmount)
-
- // And then we give the item manually
- extractAnvilResult(
- event, player, inventory,
- null, 0,
- rightItem, resultAmount,
- resultCopy, repairCost
- )
- }
-
- private fun getUnitRepairCost(
- inventory: AnvilInventory, player: Player,
- leftItem: ItemStack, output: ItemStack,
- resultCopy: ItemStack, resultAmount: Int
- ): Int {
- if (player.gameMode == GameMode.CREATIVE) return 0
-
- var repairCost = 0
- // Get repairCost
- leftItem.itemMeta?.let { leftMeta ->
- val leftName = leftMeta.displayName
- output.itemMeta?.let {
- // Rename cost
- if (!leftName.contentEquals(it.displayName)) {
- repairCost += ConfigOptions.itemRenameCost
-
- // Color cost
- if (it.displayName.contains('§')) {
- repairCost += ConfigOptions.useOfColorCost
- }
- }
- }
- }
-
- repairCost += AnvilXpUtil.calculatePenalty(leftItem, null, resultCopy, AnvilUseType.UNIT_REPAIR)
- repairCost += resultAmount * ConfigOptions.unitRepairCost
+ private fun processCost(inventory: AnvilInventory, player: Player, cost: AnvilCost) {
+ var sum = cost.repair
if (
!ConfigOptions.doRemoveCostLimit &&
ConfigOptions.doCapCost
) {
- repairCost = min(repairCost, ConfigOptions.maxAnvilCost)
+ val final = min(sum, ConfigOptions.maxAnvilCost)
+ cost.generic += (final - sum)
+
+ sum = final
}
- if ((inventory.maximumRepairCost <= repairCost)
- || (player.level < repairCost)
- ) return Int.MIN_VALUE
-
- return repairCost
+ if (ConfigOptions.shouldUseMoney(player)) {
+ cost.isMonetary = true
+ if (!EconomyManager.economy!!.has(player, cost.asMonetaryCost()))
+ cost.valid = false
+ } else {
+ if ((inventory.maximumRepairCost <= sum)
+ || (player.level < sum)
+ ) cost.valid = false
+ }
}
- private fun getFromLoreEditXpCost(
- xpCost: AtomicInteger,
+ private fun onUnitRepairExtract(
+ rightItem: ItemStack,
+ event: InventoryClickEvent,
player: Player,
inventory: AnvilInventory,
- ): Int {
- if (GameMode.CREATIVE == player.gameMode) return 0
-
- val repairCost = xpCost.get()
- return if ((inventory.maximumRepairCost <= repairCost)
- || (player.level < repairCost)
- ) Int.MIN_VALUE
- else repairCost
+ result: UnitRepairResult,
+ ) {
+ // We give the item manually
+ extractAnvilResult(
+ event, player, inventory,
+ null, 0,
+ rightItem, result.repairAmount,
+ result
+ )
}
private fun handleBookLoreEdit(
@@ -366,70 +384,84 @@ class AnvilResultListener : Listener {
player: Player,
leftItem: ItemStack,
rightItem: ItemStack,
- output: ItemStack,
- ): Boolean {
- if (Material.WRITABLE_BOOK != rightItem.type) return false
- val bookMeta = rightItem.itemMeta as BookMeta? ?: return false
+ result: LoreEditResult
+ ) {
+ if (result.type.isAppend)
+ handleBookLoreAppend(event, inventory, player, rightItem, result)
+ else
+ handleBookLoreRemove(event, inventory, player, leftItem, rightItem, result)
+ }
- val editType = AnvilLoreEditUtil.bookLoreEditIsAppend(leftItem, rightItem) ?: return false
+ private fun handleBookLoreAppend(
+ event: InventoryClickEvent,
+ inventory: AnvilInventory,
+ player: Player,
+ rightItem: ItemStack,
+ result: LoreEditResult
+ ) {
+ val bookMeta = rightItem.itemMeta as BookMeta? ?: return
- val xpCost = AtomicInteger()
- if (editType) {
- if (output != AnvilLoreEditUtil.handleLoreAppendByBook(player, leftItem, bookMeta, xpCost)) return false
-
- // Remove pages to book
- val clearedBook: ItemStack?
- if (LoreEditType.APPEND_BOOK.doConsume) {
- clearedBook = null
- } else {
- clearedBook = rightItem.clone()
- bookMeta.pages = Collections.emptyList()
- clearedBook.itemMeta = bookMeta
- }
-
- return extractAnvilResult(
- event, player, inventory,
- null, 0,
- clearedBook, 0,
- output, getFromLoreEditXpCost(xpCost, player, inventory)
- )
+ // Remove pages to book
+ val clearedBook: ItemStack?
+ if (LoreEditType.APPEND_BOOK.doConsume) {
+ clearedBook = null
} else {
- if (output != AnvilLoreEditUtil.handleLoreRemoveByBook(player, leftItem, xpCost)) return false
+ clearedBook = rightItem.clone()
+ bookMeta.pages = Collections.emptyList()
+ clearedBook.itemMeta = bookMeta
+ }
- // fill book meta
- val meta = leftItem.itemMeta
- if (meta == null || !meta.hasLore()) return false
- val lore = DependencyManager.stripLore(leftItem)
- if (lore.isEmpty()) return false
+ extractAnvilResult(
+ event, player, inventory,
+ null, 0,
+ clearedBook, 0,
+ result
+ )
+ }
- val rightCopy: ItemStack?
- if (LoreEditType.REMOVE_BOOK.doConsume) {
- rightCopy = null
- } else {
- // Uncolor the page
- AnvilLoreEditUtil.uncolorLines(player, lore, LoreEditType.REMOVE_BOOK)
+ private fun handleBookLoreRemove(
+ event: InventoryClickEvent,
+ inventory: AnvilInventory,
+ player: Player,
+ leftItem: ItemStack,
+ rightItem: ItemStack,
+ result: LoreEditResult
+ ) {
+ val bookMeta = rightItem.itemMeta as BookMeta? ?: return
- val bookPage = StringBuilder()
- lore.forEach {
- if (bookPage.isNotEmpty()) bookPage.append('\n')
- bookPage.append(it)
- }
+ // fill book meta
+ val lore = DependencyManager.stripLore(leftItem)
+ if (lore.isEmpty()) return
- val resultPage = bookPage.toString()
- //TODO maybe check page size ? bc it may be too big ???
+ val rightCopy: ItemStack?
+ if (LoreEditType.REMOVE_BOOK.doConsume) {
+ rightCopy = null
+ } else {
+ // Uncolor the page
+ AnvilLoreEditUtil.uncolorLines(player, lore, LoreEditType.REMOVE_BOOK)
- rightCopy = rightItem.clone()
- bookMeta.setPages(resultPage)
- rightCopy.itemMeta = bookMeta
+ val bookPage = StringBuilder()
+ lore.forEach {
+ if (bookPage.isNotEmpty()) bookPage.append('\n')
+ if (it == null) return@forEach
+
+ bookPage.append(MiniMessageUtil.plain_text_mm.serialize(it))
}
- return extractAnvilResult(
- event, player, inventory,
- null, 0,
- rightCopy, 0,
- output, getFromLoreEditXpCost(xpCost, player, inventory)
- )
+ val resultPage = bookPage.toString()
+ //TODO maybe check page size ? bc it may be too big ???
+
+ rightCopy = rightItem.clone()
+ bookMeta.setPages(resultPage)
+ rightCopy.itemMeta = bookMeta
}
+
+ extractAnvilResult(
+ event, player, inventory,
+ null, 0,
+ rightCopy, 0,
+ result
+ )
}
private fun handlePaperLoreEdit(
@@ -438,91 +470,106 @@ class AnvilResultListener : Listener {
player: Player,
leftItem: ItemStack,
rightItem: ItemStack,
- output: ItemStack,
- ): Boolean {
- if (Material.PAPER != rightItem.type) return false
- val paperMeta = rightItem.itemMeta ?: return false
+ result: LoreEditResult
+ ) {
+ if (result.type.isAppend)
+ handlePaperLoreAppend(event, inventory, player, rightItem, result)
+ else
+ handlePaperLoreRemove(event, inventory, player, leftItem, rightItem, result)
+ }
- val editType = AnvilLoreEditUtil.paperLoreEditIsAppend(leftItem, rightItem) ?: return false
+ private fun handlePaperLoreAppend(
+ event: InventoryClickEvent,
+ inventory: AnvilInventory,
+ player: Player,
+ rightItem: ItemStack,
+ result: LoreEditResult
+ ) {
+ val paperMeta = rightItem.itemMeta ?: return
- val xpCost = AtomicInteger()
- if (editType) {
- if (output != AnvilLoreEditUtil.handleLoreAppendByPaper(player, leftItem, rightItem, xpCost)) return false
- val paperCopy: ItemStack?
- if (LoreEditType.APPEND_PAPER.doConsume) {
- paperCopy = null
- } else {
- // Remove custom name to paper
- paperCopy = rightItem.clone()
- paperCopy.amount = 1
- paperMeta.setDisplayName(null)
- paperCopy.itemMeta = paperMeta
- }
-
- return if (rightItem.amount > 1) {
- extractAnvilResult(
- event, player, inventory,
- paperCopy, 0,
- rightItem, 1,
- output, getFromLoreEditXpCost(xpCost, player, inventory)
- )
- } else {
- extractAnvilResult(
- event, player, inventory,
- null, 0,
- paperCopy, 0,
- output, getFromLoreEditXpCost(xpCost, player, inventory)
- )
- }
+ val paperCopy: ItemStack?
+ if (LoreEditType.APPEND_PAPER.doConsume) {
+ paperCopy = null
} else {
- if (output != AnvilLoreEditUtil.handleLoreRemoveByPaper(player, leftItem, xpCost)) return false
+ // Remove custom name to paper
+ paperCopy = rightItem.clone()
+ paperCopy.amount = 1
+ paperMeta.setComponentDisplayName(null)
- val leftMeta = leftItem.itemMeta
- if (leftMeta == null || !leftMeta.hasLore()) return false
- val lore = DependencyManager.stripLore(leftItem)
- if (lore.isEmpty()) return false
+ // Remove pcd name
+ AnvilMergeLogic.processPCD(paperMeta, player, null)
- // Create result item
- val rightClone: ItemStack?
- if (LoreEditType.REMOVE_PAPER.doConsume) {
- rightClone = null
- } else {
- val removeEnd = LoreEditConfigUtil.paperLoreOrderIsEnd
- var line = if (removeEnd) lore[lore.size - 1]
- else lore[0]
-
- // Overkill but uncolor the line
- val tempList = ArrayList(1)
- tempList.add(line)
- AnvilLoreEditUtil.uncolorLines(player, tempList, LoreEditType.REMOVE_PAPER)
- line = tempList[0]
-
- rightClone = rightItem.clone()
- rightClone.amount = 1
-
- val resultMeta = rightClone.itemMeta ?: return false
- resultMeta.setDisplayName(line)
- rightClone.itemMeta = resultMeta
- }
-
- return if (rightItem.amount > 1) {
- extractAnvilResult(
- event, player, inventory,
- rightClone, 0,
- rightItem, 1,
- output, getFromLoreEditXpCost(xpCost, player, inventory)
- )
- } else {
- extractAnvilResult(
- event, player, inventory,
- null, 0,
- rightClone, 0,
- output, getFromLoreEditXpCost(xpCost, player, inventory)
- )
- }
+ paperCopy.itemMeta = paperMeta
}
+ if (rightItem.amount > 1) {
+ extractAnvilResult(
+ event, player, inventory,
+ paperCopy, 0,
+ rightItem, 1,
+ result
+ )
+ } else {
+ extractAnvilResult(
+ event, player, inventory,
+ null, 0,
+ paperCopy, 0,
+ result
+ )
+ }
+ }
+
+ private fun handlePaperLoreRemove(
+ event: InventoryClickEvent,
+ inventory: AnvilInventory,
+ player: Player,
+ leftItem: ItemStack,
+ rightItem: ItemStack,
+ result: LoreEditResult
+ ) {
+ val leftMeta = leftItem.itemMeta
+ if (leftMeta == null || !leftMeta.hasLore()) return
+
+ val lore = DependencyManager.stripLore(leftItem)
+ if (lore.isEmpty()) return
+
+ // Create result item
+ val rightClone: ItemStack?
+ if (LoreEditType.REMOVE_PAPER.doConsume) {
+ rightClone = null
+ } else {
+ val removeEnd = LoreEditConfigUtil.paperLoreOrderIsEnd
+ val line = if (removeEnd) lore[lore.size - 1]
+ else lore[0]
+
+ // uncolor the line
+ val ref = AtomicReference(line)
+ AnvilLoreEditUtil.uncolorLine(player, ref, LoreEditType.REMOVE_PAPER)
+
+ rightClone = rightItem.clone()
+ rightClone.amount = 1
+
+ val resultMeta = rightClone.itemMeta ?: return
+ resultMeta.setComponentDisplayName(ref.get())
+ rightClone.itemMeta = resultMeta
+ }
+
+ if (rightItem.amount > 1) {
+ extractAnvilResult(
+ event, player, inventory,
+ rightClone, 0,
+ rightItem, 1,
+ result
+ )
+ } else {
+ extractAnvilResult(
+ event, player, inventory,
+ null, 0,
+ rightClone, 0,
+ result
+ )
+ }
}
/**
diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt
index 8eefc97..0217983 100644
--- a/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt
+++ b/src/main/kotlin/xyz/alexcrea/cuanvil/listener/PrepareAnvilListener.kt
@@ -3,29 +3,29 @@ package xyz.alexcrea.cuanvil.listener
import com.github.stefvanschie.inventoryframework.util.InventoryViewUtil
import io.delilaheve.CustomAnvil
import io.delilaheve.util.ConfigOptions
-import io.delilaheve.util.EnchantmentUtil.combineWith
import io.delilaheve.util.ItemUtil.canMergeWith
-import io.delilaheve.util.ItemUtil.findEnchantments
-import io.delilaheve.util.ItemUtil.isEnchantedBook
-import io.delilaheve.util.ItemUtil.repairFrom
-import io.delilaheve.util.ItemUtil.setEnchantmentsUnsafe
-import io.delilaheve.util.ItemUtil.unitRepair
-import org.bukkit.ChatColor
-import org.bukkit.Material
import org.bukkit.entity.HumanEntity
+import org.bukkit.entity.Player
import org.bukkit.event.EventHandler
import org.bukkit.event.EventPriority
import org.bukkit.event.Listener
import org.bukkit.event.inventory.PrepareAnvilEvent
import org.bukkit.inventory.AnvilInventory
+import org.bukkit.inventory.InventoryView
import org.bukkit.inventory.ItemStack
import org.bukkit.inventory.meta.EnchantmentStorageMeta
import org.bukkit.inventory.meta.ItemMeta
+import xyz.alexcrea.cuanvil.anvil.AnvilCost
+import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.AnvilResult
+import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.doMerge
+import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.doRenaming
+import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.testCustomRecipe
+import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.testLoreEdit
+import xyz.alexcrea.cuanvil.anvil.AnvilMergeLogic.testUnitRepair
import xyz.alexcrea.cuanvil.dependency.DependencyManager
-import xyz.alexcrea.cuanvil.enchant.CAEnchantment
-import xyz.alexcrea.cuanvil.util.*
-import xyz.alexcrea.cuanvil.util.UnitRepairUtil.getRepair
-import java.util.concurrent.atomic.AtomicInteger
+import xyz.alexcrea.cuanvil.util.MaterialUtil.isAir
+import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil
+import xyz.alexcrea.cuanvil.util.dialog.AnvilRenameDialogUtil
/**
* Listener for anvil events
@@ -38,6 +38,8 @@ class PrepareAnvilListener : Listener {
const val ANVIL_INPUT_LEFT = 0
const val ANVIL_INPUT_RIGHT = 1
const val ANVIL_OUTPUT_SLOT = 2
+
+ var IS_EMPTY_TEST = false
}
/**
@@ -45,66 +47,105 @@ class PrepareAnvilListener : Listener {
*/
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
fun anvilCombineCheck(event: PrepareAnvilEvent) {
- // Should find player
- val player: HumanEntity = InventoryViewUtil.getInstance().getPlayer(event.view)
+ val view = event.view
val inventory = event.inventory
+ val player = InventoryViewUtil.getInstance().getPlayer(view)
+ if(player !is Player) return
+
+ tryRenameDialog(player, event)
+
// Test if custom anvil is bypassed before immutability test
if (DependencyManager.earlyTryEventPreAnvilBypass(event, player)) {
// even if we got bypassed we still want to set price
- AnvilXpUtil.setAnvilInvXp(inventory, event.view, player, event.inventory.repairCost)
+ AnvilXpUtil.setAnvilInvCost(inventory, view, player, AnvilCost(event.inventory.repairCost))
return
}
- val first = inventory.getItem(ANVIL_INPUT_LEFT) ?: return
+ val first = inventory.getItem(ANVIL_INPUT_LEFT)
val second = inventory.getItem(ANVIL_INPUT_RIGHT)
+ if(IS_EMPTY_TEST) {
+ IS_EMPTY_TEST = false
+ applyResult(event, player, AnvilResult.EMPTY)
+ return
+ }
+
+ if (ConfigOptions.verboseDebugLog) {
+ CustomAnvil.verboseLog("Testing items:")
+ CustomAnvil.verboseLog("first: $first")
+ CustomAnvil.verboseLog("second: $second")
+ }
if (isImmutable(first) || isImmutable(second)) {
CustomAnvil.verboseLog("Skipping anvil process as one of the two item is immutable")
- event.result = null
+ applyResult(event, player, AnvilResult.EMPTY)
return
}
// Test if the event should bypass custom anvil.
if (DependencyManager.tryEventPreAnvilBypass(event, player)) {
// even if we got bypassed we still want to set price
- AnvilXpUtil.setAnvilInvXp(inventory, event.view, player, event.inventory.repairCost)
+ AnvilXpUtil.setAnvilInvCost(inventory, view, player, AnvilCost(event.inventory.repairCost))
return
}
if (!player.hasPermission(CustomAnvil.affectedByPluginPermission)) return
+ val result = getResult(view, inventory, player, first, second)
+ applyResult(event, player, result)
+ }
+
+ fun getResult(
+ view: InventoryView, //TODO use anvil view
+ inventory: AnvilInventory,
+ player: Player,
+ first: ItemStack?, second: ItemStack?) : AnvilResult
+ {
+ if(first == null)
+ return AnvilResult.EMPTY
+
// Test custom recipe
- if (testCustomRecipe(event, inventory, player, first, second)) return
+ var result: AnvilResult = testCustomRecipe(view, inventory, player, first, second)
+ if (!result.isEmpty())
+ return result
// Test rename lonely item
- if (second == null) {
- doRenaming(event, inventory, player, first)
- return
- }
+ val shouldTryRename = second.isAir
+ CustomAnvil.verboseLog("checking air in main logic: $shouldTryRename")
+ if (shouldTryRename)
+ return doRenaming(view, inventory, player, first)
// Test for merge
- if (first.canMergeWith(second)) {
- doMerge(event, inventory, player, first, second)
- return
- }
+ if (first.canMergeWith(second!!))
+ return doMerge(view, inventory, player, first, second)
// Test for unit repair
- if (testUnitRepair(event, inventory, player, first, second)) return
+ result = testUnitRepair(view, inventory, player, first, second)
+ if (!result.isEmpty())
+ return result
// Test for lore edit
- if (testLoreEdit(event, inventory, player, first, second)) return
+ result = testLoreEdit(player, first, second)
+ if (!result.isEmpty())
+ return result
- CustomAnvil.log("no anvil fuse type found")
- event.result = null
+ return AnvilResult.EMPTY
+ }
+ private fun tryRenameDialog(
+ player: HumanEntity,
+ event: PrepareAnvilEvent
+ ) {
+ if(!ConfigOptions.canUseDialogRename(player)) return
+
+ AnvilRenameDialogUtil.anvilRenameDialog.tryShowDialog(player, event)
}
private fun isImmutable(item: ItemStack?): Boolean {
- if (item == null) return false
+ if (item.isAir) return false
- val meta = item.itemMeta
+ val meta = item!!.itemMeta
return meta != null &&
(hasImmutableEnchants(meta) || hasImmutableStoredEnchants(meta))
}
@@ -127,219 +168,14 @@ class PrepareAnvilListener : Listener {
return false
}
- // return true if a custom recipe exist with these ingredients
- private fun testCustomRecipe(
- event: PrepareAnvilEvent, inventory: AnvilInventory,
- player: HumanEntity,
- first: ItemStack, second: ItemStack?
- ): Boolean {
- val recipe = CustomRecipeUtil.getCustomRecipe(first, second)
- CustomAnvil.verboseLog("custom recipe not null? ${recipe != null}")
- if (recipe == null) return false
+ private fun applyResult(event: PrepareAnvilEvent, player: Player, result: AnvilResult) {
+ event.result = result.item
- val amount = CustomRecipeUtil.getCustomRecipeAmount(recipe, first, second)
-
- val resultItem: ItemStack = recipe.resultItem!!.clone()
- resultItem.amount *= amount
-
- // Maybe add an option on custom craft to ignore/not ignore penalty ??
- val xpCost = recipe.determineCost(amount, first, resultItem)
-
- val levelCost =
- if (recipe.removeExactLinearXp) AnvilXpUtil.calculateMinimumLevelForXp(xpCost)
- else AnvilXpUtil.calculateLevelForXp(xpCost)
-
- val finalResult = DependencyManager.tryTreatAnvilResult(event, resultItem, AnvilUseType.CUSTOM_CRAFT, levelCost)
- if (finalResult == null) return false
-
- event.result = finalResult.result
- if (finalResult.result == null) return false
-
- AnvilXpUtil.setAnvilInvXp(inventory, event.view, player, finalResult.levelCost, true)
- return true
- }
-
- private fun doRenaming(
- event: PrepareAnvilEvent, inventory: AnvilInventory,
- player: HumanEntity, first: ItemStack
- ) {
- val resultItem = first.clone()
- var anvilCost = handleRename(resultItem, inventory, player)
-
- // Test/stop if nothing changed.
- if (first == resultItem) {
- CustomAnvil.log("no right item, But input is same as output")
- event.result = null
+ if(result.item == null) {
+ AnvilXpUtil.onNoResult(player, event.view)
return
}
-
- anvilCost += AnvilXpUtil.calculatePenalty(first, null, resultItem, AnvilUseType.RENAME_ONLY)
-
- val finalResult = DependencyManager.tryTreatAnvilResult(event, resultItem, AnvilUseType.RENAME_ONLY, anvilCost)
- if (finalResult == null) return
-
- event.result = finalResult.result
- if (finalResult.result == null) return
-
- AnvilXpUtil.setAnvilInvXp(inventory, event.view, player, finalResult.levelCost)
+ AnvilXpUtil.setAnvilInvCost(event.inventory, event.view, player, result.cost, result.ignoreXpRules)
}
- private fun handleRename(resultItem: ItemStack, inventory: AnvilInventory, player: HumanEntity): Int {
- // Can be null
- var inventoryName = ChatColor.stripColor(inventory.renameText)
-
- var sumCost = 0
- var useColor = false
- if (ConfigOptions.renameColorPossible && inventoryName != null) {
- val resultString = StringBuilder(inventoryName)
-
- useColor = AnvilColorUtil.handleColor(
- resultString, player,
- ConfigOptions.permissionNeededForColor,
- ConfigOptions.allowColorCode, ConfigOptions.allowHexadecimalColor,
- AnvilColorUtil.ColorUseType.RENAME
- )
-
- if (useColor) {
- inventoryName = resultString.toString()
-
- sumCost += ConfigOptions.useOfColorCost
- }
- }
-
- // Rename item and add renaming cost
- resultItem.itemMeta?.let {
- val hasDisplayName = it.hasDisplayName()
- val displayName = if (!hasDisplayName) null
- else if (useColor) it.displayName
- else ChatColor.stripColor(it.displayName)
-
- if (!displayName.contentEquals(inventoryName)) {
- it.setDisplayName(inventoryName)
- resultItem.itemMeta = it
-
- sumCost += ConfigOptions.itemRenameCost
- }
-
- return sumCost
- }
- return 0
- }
-
- private fun doMerge(
- event: PrepareAnvilEvent, inventory: AnvilInventory,
- player: HumanEntity,
- first: ItemStack, second: ItemStack
- ) {
- val newEnchants = first.findEnchantments()
- .combineWith(second.findEnchantments(), first, player)
- var hasChanged = !isIdentical(first.findEnchantments(), newEnchants);
-
- val resultItem = first.clone()
- var anvilCost = 0;
- if(hasChanged){
- resultItem.setEnchantmentsUnsafe(newEnchants)
- // Calculate enchantment cost
- anvilCost+= AnvilXpUtil.getRightValues(second, resultItem)
- }
-
- // Calculate repair cost
- if (!first.isEnchantedBook() && !second.isEnchantedBook()) {
- // we only need to be concerned with repair when neither item is a book
- val repaired = resultItem.repairFrom(first, second)
- anvilCost += if (repaired) ConfigOptions.itemRepairCost else 0
- hasChanged = hasChanged || repaired;
- }
-
- // Test/stop if nothing changed.
- if (!hasChanged) {
- CustomAnvil.log("Mergable with second, But input is same as output")
- event.result = null
- return
- }
- // As calculatePenalty edit result, we need to calculate penalty after checking equality
- anvilCost += AnvilXpUtil.calculatePenalty(first, second, resultItem, AnvilUseType.MERGE)
- // Calculate rename cost
- anvilCost += handleRename(resultItem, inventory, player)
-
- // Finally, we set result
- val finalResult = DependencyManager.tryTreatAnvilResult(event, resultItem, AnvilUseType.MERGE, anvilCost)
- if (finalResult == null) return
-
- event.result = finalResult.result
- if (finalResult.result == null) return
-
- AnvilXpUtil.setAnvilInvXp(inventory, event.view, player, finalResult.levelCost)
- }
-
- private fun isIdentical(
- firstEnchants: MutableMap,
- resultEnchants: MutableMap
- ): Boolean {
- if(firstEnchants.size != resultEnchants.size) return false
- for (entry in resultEnchants) {
- if(firstEnchants.getOrDefault(entry.key, entry.value-1) != entry.value) return false
- }
-
- return true
- }
-
- // return true if there is a valid unit repair with these ingredients
- private fun testUnitRepair(
- event: PrepareAnvilEvent, inventory: AnvilInventory, player: HumanEntity,
- first: ItemStack, second: ItemStack
- ): Boolean {
- val unitRepairAmount = first.getRepair(second) ?: return false
-
- val resultItem = first.clone()
- var anvilCost = handleRename(resultItem, inventory, player)
-
- val repairAmount = resultItem.unitRepair(second.amount, unitRepairAmount)
- if (repairAmount > 0) {
- anvilCost += repairAmount * ConfigOptions.unitRepairCost
- }
- // We do not care about right item penalty for unit repair
- anvilCost += AnvilXpUtil.calculatePenalty(first, null, resultItem, AnvilUseType.UNIT_REPAIR)
-
- // Test/stop if nothing changed.
- if (first == resultItem) {
- CustomAnvil.log("unit repair, But input is same as output")
- event.result = null
- return true
- }
-
- val finalResult = DependencyManager.tryTreatAnvilResult(event, resultItem, AnvilUseType.UNIT_REPAIR, anvilCost)
- if (finalResult == null) return false
-
- event.result = finalResult.result
- if (finalResult.result == null) return false
-
- AnvilXpUtil.setAnvilInvXp(inventory, event.view, player, finalResult.levelCost)
- return true
- }
-
- private fun testLoreEdit(
- event: PrepareAnvilEvent, inventory: AnvilInventory, player: HumanEntity,
- first: ItemStack, second: ItemStack
- ): Boolean {
- val type = second.type
- var result: ItemStack? = null
-
- val xpCost = AtomicInteger()
- if (Material.WRITABLE_BOOK == type) {
- result = AnvilLoreEditUtil.tryLoreEditByBook(player, first, second, xpCost)
- } else if (Material.PAPER == type) {
- result = AnvilLoreEditUtil.tryLoreEditByPaper(player, first, second, xpCost)
- }
-
- if (result == null || first == result) {
- CustomAnvil.log("lore edit, But input is same as output")
- event.result = null
- return false
- }
-
- event.result = result
- AnvilXpUtil.setAnvilInvXp(inventory, event.view, player, xpCost.get())
- return true
- }
}
\ No newline at end of file
diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/recipe/AnvilCustomRecipe.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/recipe/AnvilCustomRecipe.kt
index fa1a977..fd079c3 100644
--- a/src/main/kotlin/xyz/alexcrea/cuanvil/recipe/AnvilCustomRecipe.kt
+++ b/src/main/kotlin/xyz/alexcrea/cuanvil/recipe/AnvilCustomRecipe.kt
@@ -3,10 +3,11 @@ package xyz.alexcrea.cuanvil.recipe
import io.delilaheve.CustomAnvil
import org.bukkit.configuration.ConfigurationSection
import org.bukkit.inventory.ItemStack
+import xyz.alexcrea.cuanvil.anvil.AnvilUseType
import xyz.alexcrea.cuanvil.config.ConfigHolder
import xyz.alexcrea.cuanvil.gui.util.GuiSharedConstant
-import xyz.alexcrea.cuanvil.util.AnvilUseType
-import xyz.alexcrea.cuanvil.util.AnvilXpUtil
+import xyz.alexcrea.cuanvil.util.MaterialUtil.isAir
+import xyz.alexcrea.cuanvil.util.anvil.AnvilXpUtil
class AnvilCustomRecipe(
val name: String,
@@ -80,11 +81,9 @@ class AnvilCustomRecipe(
}
fun validate(): Boolean {
- return (leftItem != null) && !(leftItem!!.type.isAir) && (leftItem!!.amount > 0) &&
- //(rightItem != null) && !(rightItem!!.type.isAir) && (rightItem!!.amount > 0) &&
- ((rightItem == null) || (!(rightItem!!.type.isAir) && (rightItem!!.amount > 0))) &&
- (resultItem != null) && !(resultItem!!.type.isAir) && (resultItem!!.amount > 0)
-
+ return !leftItem.isAir &&
+ (rightItem == null || !resultItem.isAir) &&
+ !resultItem.isAir
}
fun saveToFile(writeFile: Boolean, doBackup: Boolean) {
@@ -162,7 +161,7 @@ class AnvilCustomRecipe(
CustomAnvil.verboseLog("Testing $name $leftItem")
// We assume this function can be call only if leftItem != null
- // Test is valid
+ // Test if valid
if (!validate()) return false
val leftSimilar = leftItem!!.isSimilar(item1)
diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilColorUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilColorUtil.kt
deleted file mode 100644
index 277a8d1..0000000
--- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilColorUtil.kt
+++ /dev/null
@@ -1,186 +0,0 @@
-package xyz.alexcrea.cuanvil.util
-
-import org.bukkit.permissions.Permissible
-import java.util.regex.Matcher
-import java.util.regex.Pattern
-
-object AnvilColorUtil {
- private val HEX_PATTERN: Pattern = Pattern.compile("#[A-Fa-f0-9]{6}") // pattern to find hexadecimal string
- private val TRANSFORMED_HEX_PATTERN = Pattern.compile("§x(§[0-9a-fA-F]){6}") // pattern to find minecraft hex string
-
- /**
- * Color a stringbuilder object depending on allowed color type and player permissions on color use type
- * @return if the stringbuilder was changed and color applied
- */
- fun handleColor(
- textToColor: StringBuilder,
- player: Permissible,
- usePermission: Boolean,
- allowColorCode: Boolean,
- allowHexadecimalColor: Boolean,
- useType: ColorUseType
- ): Boolean {
- if (!allowColorCode && !allowHexadecimalColor) return false
-
- val canUseColorCode =
- allowColorCode && (!usePermission || useType.colorCodePerm == null || player.hasPermission(
- useType.colorCodePerm
- ))
- val canUseHexColor =
- allowHexadecimalColor && (!usePermission || useType.hexColorPerm == null || player.hasPermission(
- useType.hexColorPerm
- ))
-
- if ((!canUseColorCode) && (!canUseHexColor)) return false
-
- var useColor = false
- // Handle color code
- if (canUseColorCode) {
- var nbReplacement = replaceAll(textToColor, "&", "§", 2)
- nbReplacement -= 2 * replaceAll(textToColor, "§§", "&", 2)
-
- if (nbReplacement > 0) useColor = true
- }
-
- if (canUseHexColor) {
- val nbReplacement = replaceHexToColor(textToColor, 7)
-
- if (nbReplacement > 0) useColor = true
- }
-
- return useColor
- }
-
- /**
- * Revert a stringbuilder to a state where applying handleColor with the same options would give the same result
- * @return if the stringbuilder was changed and color unapplied
- */
- fun revertColor(
- colorToText: StringBuilder,
- player: Permissible,
- usePermission: Boolean,
- allowColorCode: Boolean,
- allowHexadecimalColor: Boolean,
- useType: ColorUseType
- ): Boolean {
- if (!allowColorCode && !allowHexadecimalColor) return false
-
- val canUseColorCode =
- allowColorCode && (!usePermission || useType.colorCodePerm == null || player.hasPermission(
- useType.colorCodePerm
- ))
- val canUseHexColor =
- allowHexadecimalColor && (!usePermission || useType.hexColorPerm == null || player.hasPermission(
- useType.hexColorPerm
- ))
-
- if ((!canUseColorCode) && (!canUseHexColor)) return false
- var hasReversed = false
-
- // Reverse hex pattern
- if (canUseHexColor) {
- val nbReplacement = replaceColorToHex(colorToText, 14)
-
- if (nbReplacement > 0) hasReversed = true
- }
-
- if (canUseColorCode) {
- replaceAll(colorToText, "&", "&&", 1)
- val nbReplacement = replaceAll(colorToText, "§", "&", 2)
-
- if (nbReplacement > 0) hasReversed = true
- }
-
- return hasReversed
- }
-
- /**
- * Replace every instance of "from" to "to".
- * @param builder The builder to replace the string from.
- * @param from The source that should be replaced.
- * @param to The string that should replace.
- * @param endOffset Amount of character that should be ignored at the end.
- * @return The number of replacement was that was done.
- */
- private fun replaceAll(builder: java.lang.StringBuilder, from: String, to: String, endOffset: Int): Int {
- var index = builder.indexOf(from)
- var numberOfChanges = 0
-
- while (index != -1 && index < builder.length - endOffset) {
- builder.replace(index, index + from.length, to)
- index += to.length
- index = builder.indexOf(from, index)
-
- numberOfChanges += 1
- }
-
- return numberOfChanges
- }
-
- /**
- * Replace every hex color formatted like #000000 to the minecraft format
- * @param builder The builder to replace the hex color from.
- * @param endOffset Amount of character that should be ignored at the end.
- * @return The number of replacement was that was done.
- */
- private fun replaceHexToColor(builder: StringBuilder, endOffset: Int): Int {
- val matcher: Matcher = HEX_PATTERN.matcher(builder)
-
- var numberOfChanges = 0
- var startIndex = 0
-
- while (matcher.find(startIndex)) {
- startIndex = matcher.start()
- if (startIndex >= builder.length - endOffset) break //HOW AND WHERE WOULD THIS HAPPEN ?????
-
- builder.replace(startIndex, startIndex + 1, "§x")
- startIndex += 2
- for (i in 0..5) {
- builder.insert(startIndex, '§')
- startIndex += 2
- }
-
- numberOfChanges += 1
- }
-
- return numberOfChanges
- }
-
- /**
- * Replace every hex color from the minecraft format to a format like #000000
- * @param builder The builder to replace the minecraft hex color from.
- * @param endOffset Amount of character that should be ignored at the end.
- * @return The number of replacement was that was done.
- */
- private fun replaceColorToHex(builder: StringBuilder, endOffset: Int): Int {
- val matcher: Matcher = TRANSFORMED_HEX_PATTERN.matcher(builder)
-
- var numberOfChanges = 0
- var startIndex = 0
-
- while (matcher.find(startIndex)) {
- startIndex = matcher.start()
- if (startIndex >= builder.length - endOffset) break //HOW AND WHERE WOULD THIS HAPPEN ?????
-
- builder.replace(startIndex, startIndex + 2, "#")
- startIndex += 1
- for (i in 0..5) {
- builder.deleteCharAt(startIndex)
- startIndex += 1
- }
-
- numberOfChanges += 1
- }
-
- return numberOfChanges
- }
-
- enum class ColorUseType(
- val colorCodePerm: String?,
- val hexColorPerm: String?
- ) {
- RENAME("ca.color.code", "ca.color.hex"),
- LORE_EDIT(null, null)
- }
-
-}
\ No newline at end of file
diff --git a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilLoreEditUtil.kt b/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilLoreEditUtil.kt
deleted file mode 100644
index dd0da1e..0000000
--- a/src/main/kotlin/xyz/alexcrea/cuanvil/util/AnvilLoreEditUtil.kt
+++ /dev/null
@@ -1,301 +0,0 @@
-package xyz.alexcrea.cuanvil.util
-
-import org.bukkit.entity.HumanEntity
-import org.bukkit.inventory.ItemStack
-import org.bukkit.inventory.meta.BookMeta
-import org.bukkit.permissions.Permissible
-import xyz.alexcrea.cuanvil.dependency.DependencyManager
-import xyz.alexcrea.cuanvil.util.config.LoreEditConfigUtil
-import xyz.alexcrea.cuanvil.util.config.LoreEditType
-import java.util.concurrent.atomic.AtomicInteger
-
-object AnvilLoreEditUtil {
-
- private const val LORE_BY_BOOK: String = "ca.lore_edit.book"
- private const val LORE_BY_PAPER: String = "ca.lore_edit.paper"
-
- private fun hasLoreEditByBookPermission(player: Permissible): Boolean {
- return !LoreEditConfigUtil.bookLoreEditNeedPermission || player.hasPermission(LORE_BY_BOOK)
- }
-
- private fun hasLoreEditByPaperPermission(player: Permissible): Boolean {
- return !LoreEditConfigUtil.paperLoreEditNeedPermission || player.hasPermission(LORE_BY_PAPER)
- }
-
- fun handleLoreAppendByBook(
- player: Permissible,
- first: ItemStack,
- book: BookMeta,
- xpCost: AtomicInteger
- ): ItemStack? {
- if (!hasLoreEditByBookPermission(player)) return null
-
- val result = first.clone()
- val meta = result.itemMeta ?: return null
- val lore = if (meta.hasLore()) {
- ArrayList(meta.lore!!)
- } else ArrayList()
-
- val page = book.pages[0]
- val lines = ArrayList(page.split("\n"))
- val colorCost = colorLines(player, lines, LoreEditType.APPEND_BOOK)
-
- lore.addAll(lines)
-
- meta.lore = lore
- result.itemMeta = meta
-
- if (result == first) return null
-
- // Handle xp
- xpCost.addAndGet(colorCost) // Cost of using color
- xpCost.addAndGet(lines.size * LoreEditType.APPEND_BOOK.perLineCost) // per line cost
- xpCost.addAndGet(baseEditLoreXpCost(first, result, LoreEditType.APPEND_BOOK)) // Fixed cost and work penalty
-
- return result
- }
-
- fun handleLoreRemoveByBook(player: Permissible, first: ItemStack, xpCost: AtomicInteger): ItemStack? {
- if (!hasLoreEditByBookPermission(player)) return null
-
- // remove lore
- val result = first.clone()
- val leftMeta = result.itemMeta ?: return null
- val currentLore: ArrayList = DependencyManager.stripLore(result)
- if (currentLore.isEmpty()) return null
-
- val uncolorCost = uncolorLines(player, currentLore, LoreEditType.REMOVE_BOOK)
-
- leftMeta.lore = null
- result.itemMeta = leftMeta
-
- DependencyManager.updateLore(result)
- if (result == first) return null
-
- // Handle xp
- xpCost.addAndGet(uncolorCost)
- xpCost.addAndGet(currentLore.size * LoreEditType.REMOVE_BOOK.perLineCost)
- xpCost.addAndGet(baseEditLoreXpCost(first, result, LoreEditType.REMOVE_BOOK))
-
- return result
- }
-
- // Return true if appended, false if removed, null if neither
- fun bookLoreEditIsAppend(first: ItemStack, second: ItemStack): Boolean? {
- // Test if the book & quil contain content
- val meta = second.itemMeta as BookMeta? ?: return false
-
- var hasContent = false
- if (meta.hasPages() && meta.pageCount >= 1) {
- // Test if the pages is ok
- for (page in meta.pages) {
- if (page.isNotBlank()) {
- hasContent = true
- break
- }
- }
- }
-
- // We don't want to "add" the first page is there is content and the first page is empty
- if (hasContent) {
- if (meta.pages[0].isEmpty()) return null
- if (LoreEditType.APPEND_BOOK.enabled)
- return true
- } else if (LoreEditType.REMOVE_BOOK.enabled) {
- if (!first.hasItemMeta()) return null
-
- val leftMeta = first.itemMeta!!
- return if (leftMeta.hasLore()) false
- else null
- }
- return null
- }
-
- fun tryLoreEditByBook(player: HumanEntity, first: ItemStack, second: ItemStack, xpCost: AtomicInteger): ItemStack? {
- val bookType = bookLoreEditIsAppend(first, second) ?: return null
-
- val meta = second.itemMeta as BookMeta
- return if (bookType) handleLoreAppendByBook(player, first, meta, xpCost)
- else handleLoreRemoveByBook(player, first, xpCost)
- }
-
- // Return true if appended, false if removed, null if neither
- fun paperLoreEditIsAppend(first: ItemStack, second: ItemStack): Boolean? {
- // Test if the paper contain a display name
- val meta = second.itemMeta ?: return false
-
- val hasContent = meta.hasDisplayName()
- if (hasContent) {
- if (LoreEditType.APPEND_PAPER.enabled)
- return true
- } else if (LoreEditType.REMOVE_PAPER.enabled) {
- if (!first.hasItemMeta()) return null
-
- val leftMeta = first.itemMeta!!
- return if (leftMeta.hasLore() && leftMeta.lore!!.isNotEmpty()) false
- else null
- }
- return null
- }
-
- fun handleLoreAppendByPaper(
- player: Permissible,
- first: ItemStack,
- second: ItemStack,
- xpCost: AtomicInteger
- ): ItemStack? {
- if (!hasLoreEditByPaperPermission(player)) return null
-
- val result = first.clone()
- val meta = result.itemMeta ?: return null
- val lore = if (meta.hasLore()) {
- ArrayList(meta.lore!!)
- } else ArrayList()
-
- val appendEnd = LoreEditConfigUtil.paperLoreOrderIsEnd
-
- // A bit overdone to color 1 line but hey
- val tempList = ArrayList