diff --git a/src/main/java/me/youhavetrouble/enchantio/Enchantio.java b/src/main/java/me/youhavetrouble/enchantio/Enchantio.java index 1cf4bbc..250d43f 100644 --- a/src/main/java/me/youhavetrouble/enchantio/Enchantio.java +++ b/src/main/java/me/youhavetrouble/enchantio/Enchantio.java @@ -46,6 +46,9 @@ public final class Enchantio extends JavaPlugin { if (EnchantioConfig.ENCHANTS.containsKey(VolleyEnchant.KEY)) { getServer().getPluginManager().registerEvents(new VolleyListener(), this); } + if (EnchantioConfig.ENCHANTS.containsKey(WardEnchant.KEY)) { + getServer().getPluginManager().registerEvents(new WardListener(), this); + } if (EnchantioConfig.ENCHANTS.containsKey(PanicEnchant.KEY)) { getServer().getPluginManager().registerEvents(new PanicListener(), this); diff --git a/src/main/java/me/youhavetrouble/enchantio/EnchantioConfig.java b/src/main/java/me/youhavetrouble/enchantio/EnchantioConfig.java index f4c6502..5cd7e92 100644 --- a/src/main/java/me/youhavetrouble/enchantio/EnchantioConfig.java +++ b/src/main/java/me/youhavetrouble/enchantio/EnchantioConfig.java @@ -72,6 +72,9 @@ public class EnchantioConfig { ConfigurationSection volleySection = getConfigSection(enchantsSection, "volley"); VolleyEnchant.create(volleySection); + ConfigurationSection wardSection = getConfigSection(enchantsSection, "ward"); + WardEnchant.create(wardSection); + ConfigurationSection cursesSection = getConfigSection(configuration, "curses"); ConfigurationSection panicSection = getConfigSection(cursesSection, "panic"); @@ -95,6 +98,15 @@ public class EnchantioConfig { return list; } + public static String getString(ConfigurationSection section, String key, String defaultValue) { + String value = section.contains(key) ? section.getString(key) : null; + if (value == null) { + section.set(key, defaultValue); + return defaultValue; + } + return value; + } + public static int getInt(ConfigurationSection section, String key, int defaultValue) { int value = section.contains(key) ? section.getInt(key) : -1; if (value == -1) { diff --git a/src/main/java/me/youhavetrouble/enchantio/enchants/EnchantioEnchant.java b/src/main/java/me/youhavetrouble/enchantio/enchants/EnchantioEnchant.java index d5a879d..281141b 100644 --- a/src/main/java/me/youhavetrouble/enchantio/enchants/EnchantioEnchant.java +++ b/src/main/java/me/youhavetrouble/enchantio/enchants/EnchantioEnchant.java @@ -8,9 +8,12 @@ import io.papermc.paper.tag.TagEntry; import net.kyori.adventure.key.Key; import net.kyori.adventure.text.Component; import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.EntityEquipment; import org.bukkit.inventory.EquipmentSlotGroup; +import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemType; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.Set; @@ -54,4 +57,36 @@ public interface EnchantioEnchant { return TagEntry.valueEntry(TypedKey.create(RegistryKey.ENCHANTMENT, getKey())); } + static @Nullable ItemStack findFirstWithEnchant( + @NotNull EntityEquipment equipment, + @NotNull Enchantment enchantment) { + + Set equipmentSlotGroups = enchantment.getActiveSlotGroups(); + + if (equipmentSlotGroups.contains(EquipmentSlotGroup.ANY) || equipmentSlotGroups.contains(EquipmentSlotGroup.HAND) || equipmentSlotGroups.contains(EquipmentSlotGroup.MAINHAND)) { + if (equipment.getItemInMainHand().getEnchantmentLevel(enchantment) > 0) return equipment.getItemInMainHand(); + } + if (equipmentSlotGroups.contains(EquipmentSlotGroup.ANY) || equipmentSlotGroups.contains(EquipmentSlotGroup.HAND) || equipmentSlotGroups.contains(EquipmentSlotGroup.OFFHAND)) { + if (equipment.getItemInOffHand().getEnchantmentLevel(enchantment) > 0) return equipment.getItemInOffHand(); + } + if (equipmentSlotGroups.contains(EquipmentSlotGroup.ANY) || equipmentSlotGroups.contains(EquipmentSlotGroup.ARMOR) || equipmentSlotGroups.contains(EquipmentSlotGroup.HEAD)) { + ItemStack helmet = equipment.getHelmet(); + if (helmet != null && helmet.getEnchantmentLevel(enchantment) > 0) return helmet; + } + if (equipmentSlotGroups.contains(EquipmentSlotGroup.ANY) || equipmentSlotGroups.contains(EquipmentSlotGroup.ARMOR) || equipmentSlotGroups.contains(EquipmentSlotGroup.CHEST)) { + ItemStack chestplate = equipment.getChestplate(); + if (chestplate != null && chestplate.getEnchantmentLevel(enchantment) > 0) return chestplate; + } + if (equipmentSlotGroups.contains(EquipmentSlotGroup.ANY) || equipmentSlotGroups.contains(EquipmentSlotGroup.ARMOR) || equipmentSlotGroups.contains(EquipmentSlotGroup.LEGS)) { + ItemStack leggings = equipment.getLeggings(); + if (leggings != null && leggings.getEnchantmentLevel(enchantment) > 0) return leggings; + + } + if (equipmentSlotGroups.contains(EquipmentSlotGroup.ANY) || equipmentSlotGroups.contains(EquipmentSlotGroup.ARMOR) || equipmentSlotGroups.contains(EquipmentSlotGroup.FEET)) { + ItemStack boots = equipment.getBoots(); + if (boots != null && boots.getEnchantmentLevel(enchantment) > 0) return boots; + } + return null; + } + } diff --git a/src/main/java/me/youhavetrouble/enchantio/enchants/WardEnchant.java b/src/main/java/me/youhavetrouble/enchantio/enchants/WardEnchant.java new file mode 100644 index 0000000..8546caa --- /dev/null +++ b/src/main/java/me/youhavetrouble/enchantio/enchants/WardEnchant.java @@ -0,0 +1,156 @@ +package me.youhavetrouble.enchantio.enchants; + +import io.papermc.paper.registry.data.EnchantmentRegistryEntry; +import io.papermc.paper.registry.keys.tags.EnchantmentTagKeys; +import io.papermc.paper.registry.tag.TagKey; +import io.papermc.paper.tag.TagEntry; +import me.youhavetrouble.enchantio.EnchantioConfig; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.text.Component; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.EquipmentSlotGroup; +import org.bukkit.inventory.ItemType; +import org.jetbrains.annotations.NotNull; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static me.youhavetrouble.enchantio.EnchantioConfig.ENCHANTS; + +@SuppressWarnings("UnstableApiUsage") +public class WardEnchant implements EnchantioEnchant { + + public static final Key KEY = Key.key("enchantio:ward"); + + private final int anvilCost, weight, cooldownTicks; + private final EnchantmentRegistryEntry.EnchantmentCost minimumCost; + private final EnchantmentRegistryEntry.EnchantmentCost maximumCost; + private final Set> supportedItemTags; + private final Set> enchantTagKeys = new HashSet<>(); + private final Set activeSlots = new HashSet<>(); + private final String blockSound; + + public WardEnchant( + int anvilCost, + int weight, + EnchantmentRegistryEntry.EnchantmentCost minimumCost, + EnchantmentRegistryEntry.EnchantmentCost maximumCost, + boolean canGetFromEnchantingTable, + Set> supportedItemTags, + Set activeSlots, + int cooldownTicks, + String blockSound + ) { + this.anvilCost = anvilCost; + this.weight = weight; + this.minimumCost = minimumCost; + this.maximumCost = maximumCost; + this.supportedItemTags = supportedItemTags; + this.activeSlots.addAll(activeSlots); + this.cooldownTicks = cooldownTicks; + this.blockSound = blockSound; + if (canGetFromEnchantingTable) { + enchantTagKeys.add(EnchantmentTagKeys.IN_ENCHANTING_TABLE); + } + } + + @Override + public @NotNull Key getKey() { + return KEY; + } + + @Override + public @NotNull Component getDescription() { + return Component.translatable("enchantio.enchant.ward", "Ward"); + } + + @Override + public int getAnvilCost() { + return anvilCost; + } + + @Override + public int getMaxLevel() { + return 1; + } + + @Override + public int getWeight() { + return weight; + } + + @Override + public EnchantmentRegistryEntry.@NotNull EnchantmentCost getMinimumCost() { + return minimumCost; + } + + @Override + public EnchantmentRegistryEntry.@NotNull EnchantmentCost getMaximumCost() { + return maximumCost; + } + + @Override + public @NotNull Iterable getActiveSlots() { + return activeSlots; + } + + @Override + public @NotNull Set> getSupportedItems() { + return supportedItemTags; + } + + public int getCooldownTicks() { + return cooldownTicks; + } + + public @NotNull String getBlockSound() { + return blockSound; + } + + @Override + public @NotNull Set> getEnchantTagKeys() { + return Collections.unmodifiableSet(enchantTagKeys); + } + + public static WardEnchant create(ConfigurationSection configurationSection) { + WardEnchant wardEnchant = new WardEnchant( + EnchantioConfig.getInt(configurationSection, "anvilCost", 12), + EnchantioConfig.getInt(configurationSection, "weight", 2), + EnchantmentRegistryEntry.EnchantmentCost.of( + EnchantioConfig.getInt(configurationSection, "minimumCost.base", 35), + EnchantioConfig.getInt(configurationSection, "minimumCost.additionalPerLevel", 1) + ), + EnchantmentRegistryEntry.EnchantmentCost.of( + EnchantioConfig.getInt(configurationSection, "maximumCost.base", 65), + EnchantioConfig.getInt(configurationSection, "maximumCost.additionalPerLevel", 1) + ), + EnchantioConfig.getBoolean(configurationSection, "canGetFromEnchantingTable", true), + EnchantioConfig.getTagsFromList(EnchantioConfig.getStringList( + configurationSection, + "supportedItemTags", + List.of( + "minecraft:shield" + ) + )), + EnchantioConfig.getEquipmentSlotGroups(EnchantioConfig.getStringList( + configurationSection, + "activeSlots", + List.of( + "OFF_HAND" + ) + )), + EnchantioConfig.getInt(configurationSection, "cooldownTicks", 40), + EnchantioConfig.getString(configurationSection, "blockSound", "minecraft:item.shield.block") + ); + + if (EnchantioConfig.getBoolean(configurationSection, "enabled", true)) { + ENCHANTS.put(WardEnchant.KEY, wardEnchant); + } + + return wardEnchant; + } + +} diff --git a/src/main/java/me/youhavetrouble/enchantio/listeners/WardListener.java b/src/main/java/me/youhavetrouble/enchantio/listeners/WardListener.java new file mode 100644 index 0000000..a74c0cf --- /dev/null +++ b/src/main/java/me/youhavetrouble/enchantio/listeners/WardListener.java @@ -0,0 +1,61 @@ +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.*; +import org.bukkit.NamespacedKey; +import org.bukkit.Registry; +import org.bukkit.SoundCategory; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.HumanEntity; +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.ItemStack; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; +import org.jetbrains.annotations.NotNull; + +import java.time.Instant; + +public class WardListener implements Listener { + + private final Registry<@NotNull Enchantment> registry = RegistryAccess.registryAccess().getRegistry(RegistryKey.ENCHANTMENT); + private final Enchantment ward = registry.get(WardEnchant.KEY); + + private final NamespacedKey wardKey = new NamespacedKey(WardEnchant.KEY.namespace(), WardEnchant.KEY.value()); + + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onEntityDamageWithWard(EntityDamageByEntityEvent event) { + if (ward == null) return; + if (!(event.getEntity() instanceof LivingEntity entity)) return; + EntityEquipment equipment = entity.getEquipment(); + if (equipment == null) return; + ItemStack item = EnchantioEnchant.findFirstWithEnchant(equipment, ward); + if (item == null) return; + if (!(EnchantioConfig.ENCHANTS.get(WardEnchant.KEY) instanceof WardEnchant wardEnchant)) return; + if (entity instanceof HumanEntity humanEntity) { + if (humanEntity.getCooldown(item.getType()) > 0) return; + if (wardEnchant.getCooldownTicks() > 0) { + humanEntity.setCooldown(item, wardEnchant.getCooldownTicks()); + } + item.damage((int) Math.ceil(event.getFinalDamage()), humanEntity); + } else { + if (wardEnchant.getCooldownTicks() > 0) { + // non-human entities don't support cooldowns, so simulate it with a timestamp + PersistentDataContainer pdc = entity.getPersistentDataContainer(); + Long lastWard = pdc.get(wardKey, PersistentDataType.LONG); + if (lastWard != null && Instant.now().toEpochMilli() - lastWard < 50L * wardEnchant.getCooldownTicks()) return; + pdc.set(wardKey, PersistentDataType.LONG, Instant.now().toEpochMilli()); + } + item.damage((int) Math.ceil(event.getFinalDamage()), entity); + } + entity.getWorld().playSound(entity, wardEnchant.getBlockSound(), SoundCategory.MASTER, 1, 1); + event.setDamage(0); + } + +}