diff --git a/README.MD b/README.MD index 39f7f57..ef35158 100644 --- a/README.MD +++ b/README.MD @@ -22,6 +22,12 @@ Replants broken crops using seeds in your inventory or from the loot of the crop **Description**: Teleports dropped items to players location and makes them immediately pickuppable. +### Executioner +**Translation key**: `enchantio.enchantment.executioner` + +**Description**: +Items enchanted with executioner will deal more damage to entities under specific health threshold. + ## Configuration diff --git a/src/main/java/me/youhavetrouble/enchantio/Enchantio.java b/src/main/java/me/youhavetrouble/enchantio/Enchantio.java index 523a9ca..9de708a 100644 --- a/src/main/java/me/youhavetrouble/enchantio/Enchantio.java +++ b/src/main/java/me/youhavetrouble/enchantio/Enchantio.java @@ -1,5 +1,10 @@ package me.youhavetrouble.enchantio; +import me.youhavetrouble.enchantio.enchants.ExecutionerEnchant; +import me.youhavetrouble.enchantio.enchants.ReplantingEnchant; +import me.youhavetrouble.enchantio.enchants.SoulboundEnchant; +import me.youhavetrouble.enchantio.enchants.TelepathyEnchant; +import me.youhavetrouble.enchantio.listeners.ExecutionerListener; import me.youhavetrouble.enchantio.listeners.ReplantingListener; import me.youhavetrouble.enchantio.listeners.SoulboundListener; import me.youhavetrouble.enchantio.listeners.TelepathyListener; @@ -9,9 +14,18 @@ public final class Enchantio extends JavaPlugin { @Override public void onEnable() { - getServer().getPluginManager().registerEvents(new SoulboundListener(), this); - getServer().getPluginManager().registerEvents(new TelepathyListener(), this); - getServer().getPluginManager().registerEvents(new ReplantingListener(), this); + if (EnchantioConfig.ENCHANTS.containsKey(SoulboundEnchant.KEY)) { + getServer().getPluginManager().registerEvents(new SoulboundListener(), this); + } + if (EnchantioConfig.ENCHANTS.containsKey(TelepathyEnchant.KEY)) { + getServer().getPluginManager().registerEvents(new TelepathyListener(), this); + } + if (EnchantioConfig.ENCHANTS.containsKey(ReplantingEnchant.KEY)) { + getServer().getPluginManager().registerEvents(new ReplantingListener(), this); + } + if (EnchantioConfig.ENCHANTS.containsKey(ExecutionerEnchant.KEY)) { + getServer().getPluginManager().registerEvents(new ExecutionerListener(), this); + } } @Override diff --git a/src/main/java/me/youhavetrouble/enchantio/EnchantioConfig.java b/src/main/java/me/youhavetrouble/enchantio/EnchantioConfig.java index 65838a2..3f33e61 100644 --- a/src/main/java/me/youhavetrouble/enchantio/EnchantioConfig.java +++ b/src/main/java/me/youhavetrouble/enchantio/EnchantioConfig.java @@ -12,9 +12,7 @@ import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.inventory.ItemType; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; import java.nio.file.Path; import java.util.*; import java.util.logging.Logger; @@ -34,46 +32,32 @@ public class EnchantioConfig { } File configFile = new File(filePath.toFile(), "config.yml"); - if (!configFile.exists()) { - try (InputStream in = getClass().getResourceAsStream("/config.yml")) { - if (in == null) { - throw new IOException("Failed to load config.yml from resources"); - } - try (FileOutputStream out = new FileOutputStream(configFile)) { - byte[] buffer = new byte[1024]; - int bytesRead; - while ((bytesRead = in.read(buffer)) != -1) { - out.write(buffer, 0, bytesRead); - } - } - } catch (IOException e) { - throw new IOException("Failed saving default config", e); - } - } + configFile.createNewFile(); FileConfiguration configuration = YamlConfiguration.loadConfiguration(configFile); ConfigurationSection enchantsSection = configuration.getConfigurationSection("enchants"); if (enchantsSection == null) { - throw new IOException("Failed to load enchants section from config"); + enchantsSection = configuration.createSection("enchants"); } ConfigurationSection soulboundSection = enchantsSection.getConfigurationSection("soulbound"); if (soulboundSection == null) { soulboundSection = enchantsSection.createSection("soulbound"); } - ENCHANTS.put(SoulboundEnchant.KEY, new SoulboundEnchant( - soulboundSection.getInt("anvilCost", 1), - soulboundSection.getInt("weight", 10), + + SoulboundEnchant soulboundEnchant = new SoulboundEnchant( + getInt(soulboundSection, "anvilCost", 1), + getInt(soulboundSection, "weight", 10), EnchantmentRegistryEntry.EnchantmentCost.of( - soulboundSection.getInt("minimumCost.base", 10), - soulboundSection.getInt("minimumCost.additionalPerLevel", 1) + getInt(soulboundSection, "minimumCost.base", 10), + getInt(soulboundSection, "minimumCost.additionalPerLevel", 1) ), EnchantmentRegistryEntry.EnchantmentCost.of( - soulboundSection.getInt("maximumCost.base", 65), - soulboundSection.getInt("maximumCost.additionalPerLevel", 1) + getInt(soulboundSection, "maximumCost.base", 65), + getInt(soulboundSection, "maximumCost.additionalPerLevel", 1) ), - soulboundSection.getBoolean("canGetFromEnchantingTable", true), + getBoolean(soulboundSection, "canGetFromEnchantingTable", true), getTagsFromList(getStringList( soulboundSection, "supportedItemTags", @@ -83,24 +67,29 @@ public class EnchantioConfig { "#minecraft:enchantable/mining" ) )) - )); + ); + + if (getBoolean(soulboundSection, "enabled", true)) { + ENCHANTS.put(SoulboundEnchant.KEY, soulboundEnchant); + } ConfigurationSection telepathySection = enchantsSection.getConfigurationSection("telepathy"); if (telepathySection == null) { telepathySection = enchantsSection.createSection("telepathy"); } - ENCHANTS.put(TelepathyEnchant.KEY, new TelepathyEnchant( - telepathySection.getInt("anvilCost", 1), - telepathySection.getInt("weight", 5), + + TelepathyEnchant telepathyEnchant = new TelepathyEnchant( + getInt(telepathySection,"anvilCost", 1), + getInt(telepathySection,"weight", 5), EnchantmentRegistryEntry.EnchantmentCost.of( - telepathySection.getInt("minimumCost.base", 15), - telepathySection.getInt("minimumCost.additionalPerLevel", 1) + getInt(telepathySection,"minimumCost.base", 15), + getInt(telepathySection,"minimumCost.additionalPerLevel", 1) ), EnchantmentRegistryEntry.EnchantmentCost.of( - telepathySection.getInt("maximumCost.base", 65), - telepathySection.getInt("maximumCost.additionalPerLevel", 1) + getInt(telepathySection,"maximumCost.base", 65), + getInt(telepathySection,"maximumCost.additionalPerLevel", 1) ), - telepathySection.getBoolean("canGetFromEnchantingTable", true), + getBoolean(telepathySection, "canGetFromEnchantingTable", true), getTagsFromList(getStringList( telepathySection, "supportedItemTags", @@ -108,24 +97,29 @@ public class EnchantioConfig { "#minecraft:enchantable/mining" ) )) - )); + ); + + if (getBoolean(telepathySection, "enabled", true)) { + ENCHANTS.put(TelepathyEnchant.KEY, telepathyEnchant); + } ConfigurationSection replantingSection = enchantsSection.getConfigurationSection("replanting"); if (replantingSection == null) { replantingSection = enchantsSection.createSection("replanting"); } - ENCHANTS.put(ReplantingEnchant.KEY, new ReplantingEnchant( - replantingSection.getInt("anvilCost", 1), - replantingSection.getInt("weight", 10), + + ReplantingEnchant replantingEnchant = new ReplantingEnchant( + getInt(replantingSection, "anvilCost", 1), + getInt(replantingSection, "weight", 10), EnchantmentRegistryEntry.EnchantmentCost.of( - replantingSection.getInt("minimumCost.base", 1), - replantingSection.getInt("minimumCost.additionalPerLevel", 1) + getInt(replantingSection, "minimumCost.base", 1), + getInt(replantingSection, "minimumCost.additionalPerLevel", 1) ), EnchantmentRegistryEntry.EnchantmentCost.of( - replantingSection.getInt("maximumCost.base", 65), - replantingSection.getInt("maximumCost.additionalPerLevel", 1) + getInt(replantingSection, "maximumCost.base", 65), + getInt(replantingSection, "maximumCost.additionalPerLevel", 1) ), - replantingSection.getBoolean("canGetFromEnchantingTable", true), + getBoolean(replantingSection, "canGetFromEnchantingTable", true), getTagsFromList(getStringList( replantingSection, "supportedItemTags", @@ -133,16 +127,84 @@ public class EnchantioConfig { "#minecraft:hoes" ) )) - )); + ); + if (getBoolean(replantingSection, "enabled", true)) { + ENCHANTS.put(ReplantingEnchant.KEY, replantingEnchant); + } + + ConfigurationSection executionerSection = enchantsSection.getConfigurationSection("executioner"); + if (executionerSection == null) { + executionerSection = enchantsSection.createSection("executioner"); + } + + ExecutionerEnchant executionerEnchant = new ExecutionerEnchant( + getInt(executionerSection, "anvilCost", 1), + getInt(executionerSection, "weight", 10), + EnchantmentRegistryEntry.EnchantmentCost.of( + getInt(executionerSection, "minimumCost.base", 40), + getInt(executionerSection, "minimumCost.additionalPerLevel", 3) + ), + EnchantmentRegistryEntry.EnchantmentCost.of( + getInt(executionerSection, "maximumCost.base", 65), + getInt(executionerSection,"maximumCost.additionalPerLevel", 1) + ), + getBoolean(executionerSection, "canGetFromEnchantingTable", true), + getTagsFromList(getStringList( + executionerSection, + "supportedItemTags", + List.of( + "#minecraft:enchantable/weapon" + ) + )), + getInt(executionerSection,"maxLevel", 5), + getDouble(executionerSection, "damageMultiplierPerLevel", 0.05), + getDouble(executionerSection, "maxDamageHpThreshold", 0.25) + ); + + if (getBoolean(executionerSection, "enabled", true)) { + ENCHANTS.put(ExecutionerEnchant.KEY, executionerEnchant); + } + + configuration.save(configFile); } private List getStringList(ConfigurationSection section, String key, List defaultValue) { List list = section.contains(key) ? section.getStringList(key) : null; - if (list == null) return defaultValue; + if (list == null) { + section.set(key, defaultValue); + return defaultValue; + } return list; } + private int getInt(ConfigurationSection section, String key, int defaultValue) { + int value = section.contains(key) ? section.getInt(key) : -1; + if (value == -1) { + section.set(key, defaultValue); + return defaultValue; + } + return value; + } + + private double getDouble(ConfigurationSection section, String key, double defaultValue) { + double value = section.contains(key) ? section.getDouble(key) : -1; + if (value == -1) { + section.set(key, defaultValue); + return defaultValue; + } + return value; + } + + private boolean getBoolean(ConfigurationSection section, String key, boolean defaultValue) { + boolean value = section.contains(key) && section.getBoolean(key); + if (!value) { + section.set(key, defaultValue); + return defaultValue; + } + return true; + } + private Set> getTagsFromList(List tags) { Set> supportedItemTags = new HashSet<>(); for (String itemTag : tags) { diff --git a/src/main/java/me/youhavetrouble/enchantio/enchants/ExecutionerEnchant.java b/src/main/java/me/youhavetrouble/enchantio/enchants/ExecutionerEnchant.java new file mode 100644 index 0000000..026fc80 --- /dev/null +++ b/src/main/java/me/youhavetrouble/enchantio/enchants/ExecutionerEnchant.java @@ -0,0 +1,112 @@ +package me.youhavetrouble.enchantio.enchants; + +import io.papermc.paper.registry.RegistryKey; +import io.papermc.paper.registry.data.EnchantmentRegistryEntry; +import io.papermc.paper.registry.tag.TagKey; +import io.papermc.paper.tag.TagEntry; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.text.Component; +import org.bukkit.inventory.EquipmentSlotGroup; +import org.bukkit.inventory.ItemType; + +import java.util.Set; + +@SuppressWarnings("UnstableApiUsage") +public class ExecutionerEnchant implements EnchantioEnchant { + + public static final Key KEY = Key.key("enchantio:executioner"); + + private final int anvilCost, weight, maxLevel; + private final EnchantmentRegistryEntry.EnchantmentCost minimumCost; + private final EnchantmentRegistryEntry.EnchantmentCost maximumCost; + private final boolean canGetFromEnchantingTable; + private final Set> supportedItemTags; + private final double damageMultiplierPerLevel, maxDamageHpThreshold; + + + public ExecutionerEnchant( + int anvilCost, + int weight, + EnchantmentRegistryEntry.EnchantmentCost minimumCost, + EnchantmentRegistryEntry.EnchantmentCost maximumCost, + boolean canGetFromEnchantingTable, + Set> supportedItemTags, + int maxLevel, + double damageMultiplierPerLevel, + double maxDamageHpThreshold + ) { + this.anvilCost = anvilCost; + this.weight = weight; + this.minimumCost = minimumCost; + this.maximumCost = maximumCost; + this.canGetFromEnchantingTable = canGetFromEnchantingTable; + this.supportedItemTags = supportedItemTags; + this.maxLevel = maxLevel; + this.damageMultiplierPerLevel = damageMultiplierPerLevel; + this.maxDamageHpThreshold = maxDamageHpThreshold; + } + + @Override + public Key getKey() { + return KEY; + } + + @Override + public Component getDescription() { + return Component.translatable("enchantio.enchant.executioner","Executioner"); + } + + @Override + public int getAnvilCost() { + return anvilCost; + } + + @Override + public int getMaxLevel() { + return maxLevel; + } + + public double getDamageMultiplierPerLevel() { + return damageMultiplierPerLevel; + } + + public double getMaxDamageHpThreshold() { + return maxDamageHpThreshold; + } + + @Override + public int getWeight() { + return weight; + } + + @Override + public EnchantmentRegistryEntry.EnchantmentCost getMinimumCost() { + return minimumCost; + } + + @Override + public EnchantmentRegistryEntry.EnchantmentCost getMaximumCost() { + return maximumCost; + } + + @Override + public Iterable getActiveSlots() { + return Set.of(EquipmentSlotGroup.HAND); + } + + @Override + public boolean canGetFromEnchantingTable() { + return canGetFromEnchantingTable; + } + + @Override + public TagKey getTagForSupportedItems() { + return TagKey.create(RegistryKey.ITEM, Key.key("enchantio:executioner_enchantable")); + } + + @Override + public Set> getSupportedItems() { + return supportedItemTags; + } + +} diff --git a/src/main/java/me/youhavetrouble/enchantio/listeners/ExecutionerListener.java b/src/main/java/me/youhavetrouble/enchantio/listeners/ExecutionerListener.java new file mode 100644 index 0000000..2c5c58b --- /dev/null +++ b/src/main/java/me/youhavetrouble/enchantio/listeners/ExecutionerListener.java @@ -0,0 +1,60 @@ +package me.youhavetrouble.enchantio.listeners; + +import io.papermc.paper.registry.RegistryAccess; +import io.papermc.paper.registry.RegistryKey; +import me.youhavetrouble.enchantio.EnchantioConfig; +import me.youhavetrouble.enchantio.enchants.EnchantioEnchant; +import me.youhavetrouble.enchantio.enchants.ExecutionerEnchant; +import org.bukkit.Registry; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeInstance; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.inventory.EntityEquipment; +import org.bukkit.inventory.InventoryHolder; +import org.bukkit.inventory.ItemStack; + + +@SuppressWarnings("UnstableApiUsage") +public class ExecutionerListener implements Listener { + + private final Registry registry = RegistryAccess.registryAccess().getRegistry(RegistryKey.ENCHANTMENT); + private final Enchantment executioner = registry.get(ExecutionerEnchant.KEY); + + @EventHandler(ignoreCancelled = true, priority = EventPriority.NORMAL) + public void onExecutionerDamage(EntityDamageByEntityEvent event) { + if (executioner == null) return; + EnchantioEnchant enchant = EnchantioConfig.ENCHANTS.get(ExecutionerEnchant.KEY); + if (!(enchant instanceof ExecutionerEnchant executionerEnchant)) return; + Entity damager = event.getDamageSource().getCausingEntity(); + if (damager == null) return; + if (!damager.equals(event.getDamageSource().getDirectEntity())) return; + if (!(damager instanceof InventoryHolder inventoryHolder)) return; + if (!(inventoryHolder.getInventory() instanceof EntityEquipment entityEquipment)) return; + + ItemStack attackingItem = entityEquipment.getItemInMainHand(); + + if (!attackingItem.containsEnchantment(executioner)) return; + + Entity target = event.getEntity(); + if (!(target instanceof LivingEntity livingEntity)) return; + + AttributeInstance maxHealthAttribute = livingEntity.getAttribute(Attribute.GENERIC_MAX_HEALTH); + if (maxHealthAttribute == null) return; + double targetMaxHealth = maxHealthAttribute.getValue(); + + double targetHealthPercentage = livingEntity.getHealth() / targetMaxHealth; + + if (targetHealthPercentage < executionerEnchant.getMaxDamageHpThreshold()) { + double damageMultiplier = 1 + (executionerEnchant.getDamageMultiplierPerLevel() * attackingItem.getEnchantmentLevel(executioner)); + event.setDamage(event.getDamage() * damageMultiplier); + } + + } + +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml deleted file mode 100644 index 1a1b8ba..0000000 --- a/src/main/resources/config.yml +++ /dev/null @@ -1,42 +0,0 @@ -enchants: - soulbound: - enabled: true - canGetFromEnchantingTable: true - anvilCost: 1 - weight: 10 - minimumCost: - base: 10 - additionalPerLevel: 1 - maximumCost: - base: 65 - additionalPerLevel: 1 - supportedItemTags: - - "#minecraft:enchantable/armor" - - "#minecraft:enchantable/weapon" - - "#minecraft:enchantable/mining" - telepathy: - enabled: true - canGetFromEnchantingTable: true - anvilCost: 1 - weight: 5 - minimumCost: - base: 15 - additionalPerLevel: 1 - maximumCost: - base: 65 - additionalPerLevel: 1 - supportedItemTags: - - "#minecraft:enchantable/mining" - replanting: - enabled: true - canGetFromEnchantingTable: true - anvilCost: 1 - weight: 10 - minimumCost: - base: 1 - additionalPerLevel: 1 - maximumCost: - base: 65 - additionalPerLevel: 1 - supportedItemTags: - - "#minecraft:hoes"