diff --git a/README.MD b/README.MD index 27e1708..d285349 100644 --- a/README.MD +++ b/README.MD @@ -40,6 +40,12 @@ Adds a chance to drop the head of the entity killed (if entity has a head item a **Description**: Automatically smelts drops from mined blocks. +### Airbag +**Translation key**: `enchantio.enchantment.airbag` + +**Description**: +Reduces damage from hitting a wall while flying with elytra. + ## Curses ### Curse of Panic diff --git a/src/main/java/me/youhavetrouble/enchantio/Enchantio.java b/src/main/java/me/youhavetrouble/enchantio/Enchantio.java index a2faa03..28152a2 100644 --- a/src/main/java/me/youhavetrouble/enchantio/Enchantio.java +++ b/src/main/java/me/youhavetrouble/enchantio/Enchantio.java @@ -2,8 +2,16 @@ package me.youhavetrouble.enchantio; import me.youhavetrouble.enchantio.enchants.*; import me.youhavetrouble.enchantio.listeners.*; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.EntityEquipment; +import org.bukkit.inventory.EquipmentSlotGroup; +import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.NotNull; +import java.util.Set; + +@SuppressWarnings("UnstableApiUsage") public final class Enchantio extends JavaPlugin { @Override @@ -26,6 +34,10 @@ public final class Enchantio extends JavaPlugin { if (EnchantioConfig.ENCHANTS.containsKey(SmeltingEnchant.KEY)) { getServer().getPluginManager().registerEvents(new SmeltingListener(), this); } + if (EnchantioConfig.ENCHANTS.containsKey(AirbagEnchant.KEY)) { + getServer().getPluginManager().registerEvents(new AirbagListener(), this); + } + if (EnchantioConfig.ENCHANTS.containsKey(PanicEnchant.KEY)) { getServer().getPluginManager().registerEvents(new PanicListener(), this); } @@ -37,4 +49,100 @@ public final class Enchantio extends JavaPlugin { getLogger().severe("Enchantio is being disabled without a server shutdown. Server will be shut down to prevent issues."); getServer().shutdown(); } + + /** + * Returns highest enchantment level of the given enchantment on the given equipment. + * @param equipment The equipment to check for enchantments. + * @param enchantment The enchantment to check for. + * @return The highest level of the enchantment in the equipment. + */ + public static int getHighestEnchantLevel( + @NotNull EntityEquipment equipment, + @NotNull Enchantment enchantment + ) { + int highestLevel = 0; + Set equipmentSlotGroups = enchantment.getActiveSlotGroups(); + + if (equipmentSlotGroups.contains(EquipmentSlotGroup.ANY) || equipmentSlotGroups.contains(EquipmentSlotGroup.ARMOR) || equipmentSlotGroups.contains(EquipmentSlotGroup.FEET)) { + ItemStack boots = equipment.getBoots(); + if (boots != null) { + highestLevel = Math.max(highestLevel, boots.getEnchantmentLevel(enchantment)); + } + } + if (equipmentSlotGroups.contains(EquipmentSlotGroup.ANY) || equipmentSlotGroups.contains(EquipmentSlotGroup.ARMOR) || equipmentSlotGroups.contains(EquipmentSlotGroup.LEGS)) { + ItemStack leggings = equipment.getLeggings(); + if (leggings != null) { + highestLevel = Math.max(highestLevel, leggings.getEnchantmentLevel(enchantment)); + } + } + if (equipmentSlotGroups.contains(EquipmentSlotGroup.ANY) || equipmentSlotGroups.contains(EquipmentSlotGroup.ARMOR) || equipmentSlotGroups.contains(EquipmentSlotGroup.CHEST)) { + ItemStack chestplate = equipment.getChestplate(); + if (chestplate != null) { + highestLevel = Math.max(highestLevel, chestplate.getEnchantmentLevel(enchantment)); + } + } + if (equipmentSlotGroups.contains(EquipmentSlotGroup.ANY) || equipmentSlotGroups.contains(EquipmentSlotGroup.ARMOR) || equipmentSlotGroups.contains(EquipmentSlotGroup.HEAD)) { + ItemStack helmet = equipment.getHelmet(); + if (helmet != null) { + highestLevel = Math.max(highestLevel, helmet.getEnchantmentLevel(enchantment)); + } + } + if (equipmentSlotGroups.contains(EquipmentSlotGroup.ANY) || equipmentSlotGroups.contains(EquipmentSlotGroup.HAND) || equipmentSlotGroups.contains(EquipmentSlotGroup.MAINHAND)) { + highestLevel = Math.max(highestLevel, equipment.getItemInMainHand().getEnchantmentLevel(enchantment)); + } + if (equipmentSlotGroups.contains(EquipmentSlotGroup.ANY) || equipmentSlotGroups.contains(EquipmentSlotGroup.HAND) || equipmentSlotGroups.contains(EquipmentSlotGroup.OFFHAND)) { + highestLevel = Math.max(highestLevel, equipment.getItemInOffHand().getEnchantmentLevel(enchantment)); + } + + return highestLevel; + } + + /** + * Returns the sum of enchantment levels of the given enchantment on the given equipment. + * @param equipment The equipment to check for enchantments. + * @param enchantment The enchantment to check for. + * @return Sum of enchantment levels of the enchantment in the equipment. + */ + public static int getSumOfEnchantLevels( + @NotNull EntityEquipment equipment, + @NotNull Enchantment enchantment + ) { + int level = 0; + Set equipmentSlotGroups = enchantment.getActiveSlotGroups(); + + if (equipmentSlotGroups.contains(EquipmentSlotGroup.ANY) || equipmentSlotGroups.contains(EquipmentSlotGroup.ARMOR) || equipmentSlotGroups.contains(EquipmentSlotGroup.FEET)) { + ItemStack boots = equipment.getBoots(); + if (boots != null) { + level += boots.getEnchantmentLevel(enchantment); + } + } + if (equipmentSlotGroups.contains(EquipmentSlotGroup.ANY) || equipmentSlotGroups.contains(EquipmentSlotGroup.ARMOR) || equipmentSlotGroups.contains(EquipmentSlotGroup.LEGS)) { + ItemStack leggings = equipment.getLeggings(); + if (leggings != null) { + level += leggings.getEnchantmentLevel(enchantment); + } + } + if (equipmentSlotGroups.contains(EquipmentSlotGroup.ANY) || equipmentSlotGroups.contains(EquipmentSlotGroup.ARMOR) || equipmentSlotGroups.contains(EquipmentSlotGroup.CHEST)) { + ItemStack chestplate = equipment.getChestplate(); + if (chestplate != null) { + level += chestplate.getEnchantmentLevel(enchantment); + } + } + if (equipmentSlotGroups.contains(EquipmentSlotGroup.ANY) || equipmentSlotGroups.contains(EquipmentSlotGroup.ARMOR) || equipmentSlotGroups.contains(EquipmentSlotGroup.HEAD)) { + ItemStack helmet = equipment.getHelmet(); + if (helmet != null) { + level += helmet.getEnchantmentLevel(enchantment); + } + } + if (equipmentSlotGroups.contains(EquipmentSlotGroup.ANY) || equipmentSlotGroups.contains(EquipmentSlotGroup.HAND) || equipmentSlotGroups.contains(EquipmentSlotGroup.MAINHAND)) { + ItemStack mainHand = equipment.getItemInMainHand(); + level += mainHand.getEnchantmentLevel(enchantment); + } + if (equipmentSlotGroups.contains(EquipmentSlotGroup.ANY) || equipmentSlotGroups.contains(EquipmentSlotGroup.HAND) || equipmentSlotGroups.contains(EquipmentSlotGroup.OFFHAND)) { + ItemStack offHand = equipment.getItemInOffHand(); + level += offHand.getEnchantmentLevel(enchantment); + } + + return level; + } } diff --git a/src/main/java/me/youhavetrouble/enchantio/EnchantioConfig.java b/src/main/java/me/youhavetrouble/enchantio/EnchantioConfig.java index 71a3e0b..5924c35 100644 --- a/src/main/java/me/youhavetrouble/enchantio/EnchantioConfig.java +++ b/src/main/java/me/youhavetrouble/enchantio/EnchantioConfig.java @@ -1,5 +1,7 @@ package me.youhavetrouble.enchantio; +import io.papermc.paper.registry.RegistryKey; +import io.papermc.paper.registry.TypedKey; import io.papermc.paper.registry.data.EnchantmentRegistryEntry; import io.papermc.paper.registry.keys.tags.ItemTypeTagKeys; import io.papermc.paper.registry.tag.TagKey; @@ -228,6 +230,38 @@ public class EnchantioConfig { ENCHANTS.put(SmeltingEnchant.KEY, smeltingEnchant); } + ConfigurationSection airbagSection = enchantsSection.getConfigurationSection("airbag"); + if (airbagSection == null) { + airbagSection = enchantsSection.createSection("cushion"); + } + + AirbagEnchant airbagEnchant = new AirbagEnchant( + getInt(airbagSection, "anvilCost", 1), + getInt(airbagSection, "weight", 10), + EnchantmentRegistryEntry.EnchantmentCost.of( + getInt(airbagSection, "minimumCost.base", 40), + getInt(airbagSection, "minimumCost.additionalPerLevel", 3) + ), + EnchantmentRegistryEntry.EnchantmentCost.of( + getInt(airbagSection, "maximumCost.base", 65), + getInt(airbagSection, "maximumCost.additionalPerLevel", 1) + ), + getBoolean(airbagSection, "canGetFromEnchantingTable", true), + getTagsFromList(getStringList( + airbagSection, + "supportedItemTags", + List.of( + "minecraft:elytra" + ) + )), + getInt(airbagSection, "maxLevel", 4), + getDouble(airbagSection, "damageReductionPerLevel", 0.2) + ); + + if (getBoolean(airbagSection, "enabled", true)) { + ENCHANTS.put(AirbagEnchant.KEY, airbagEnchant); + } + ConfigurationSection cursesSection = configuration.getConfigurationSection("curses"); if (cursesSection == null) { cursesSection = configuration.createSection("curses"); diff --git a/src/main/java/me/youhavetrouble/enchantio/enchants/AirbagEnchant.java b/src/main/java/me/youhavetrouble/enchantio/enchants/AirbagEnchant.java new file mode 100644 index 0000000..f4a36d8 --- /dev/null +++ b/src/main/java/me/youhavetrouble/enchantio/enchants/AirbagEnchant.java @@ -0,0 +1,105 @@ +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 net.kyori.adventure.key.Key; +import net.kyori.adventure.text.Component; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.EquipmentSlotGroup; +import org.bukkit.inventory.ItemType; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +@SuppressWarnings("UnstableApiUsage") +public class AirbagEnchant implements EnchantioEnchant { + + public static final Key KEY = Key.key("enchantio:airbag"); + + private final int anvilCost, weight, maxLevel; + private final EnchantmentRegistryEntry.EnchantmentCost minimumCost; + private final EnchantmentRegistryEntry.EnchantmentCost maximumCost; + private final Set> supportedItemTags; + private final Set> enchantTagKeys = new HashSet<>(); + private final double damageReductionPerLevel; + + public AirbagEnchant( + int anvilCost, + int weight, + EnchantmentRegistryEntry.EnchantmentCost minimumCost, + EnchantmentRegistryEntry.EnchantmentCost maximumCost, + boolean canGetFromEnchantingTable, + Set> supportedItemTags, + int maxLevel, + double damageReductionPerLevel + ) { + this.anvilCost = anvilCost; + this.weight = weight; + this.maxLevel = maxLevel; + this.minimumCost = minimumCost; + this.maximumCost = maximumCost; + this.supportedItemTags = supportedItemTags; + this.damageReductionPerLevel = damageReductionPerLevel; + if (canGetFromEnchantingTable) { + enchantTagKeys.add(EnchantmentTagKeys.IN_ENCHANTING_TABLE); + } + } + + @Override + public Key getKey() { + return KEY; + } + + @Override + public Component getDescription() { + return Component.translatable("enchantio.enchant.airbag", "Airbag"); + } + + @Override + public int getAnvilCost() { + return anvilCost; + } + + @Override + public int getMaxLevel() { + return maxLevel; + } + + @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.ARMOR); + } + + @Override + public Set> getSupportedItems() { + return supportedItemTags; + } + + @Override + public Set> getEnchantTagKeys() { + return Collections.unmodifiableSet(enchantTagKeys); + } + + public double getDamageReductionPerLevel() { + return damageReductionPerLevel; + } + +} diff --git a/src/main/java/me/youhavetrouble/enchantio/listeners/AirbagListener.java b/src/main/java/me/youhavetrouble/enchantio/listeners/AirbagListener.java new file mode 100644 index 0000000..7883ac7 --- /dev/null +++ b/src/main/java/me/youhavetrouble/enchantio/listeners/AirbagListener.java @@ -0,0 +1,41 @@ +package me.youhavetrouble.enchantio.listeners; + +import io.papermc.paper.registry.RegistryAccess; +import io.papermc.paper.registry.RegistryKey; +import me.youhavetrouble.enchantio.Enchantio; +import me.youhavetrouble.enchantio.EnchantioConfig; +import me.youhavetrouble.enchantio.enchants.AirbagEnchant; +import org.bukkit.Registry; +import org.bukkit.enchantments.Enchantment; +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.EntityDamageEvent; +import org.bukkit.inventory.EntityEquipment; + +public class AirbagListener implements Listener { + + private final Registry registry = RegistryAccess.registryAccess().getRegistry(RegistryKey.ENCHANTMENT); + private final Enchantment airbag = registry.get(AirbagEnchant.KEY); + + @EventHandler(ignoreCancelled = true, priority = EventPriority.NORMAL) + public void onCushionedFall(EntityDamageEvent event) { + if (airbag == null) return; + if (!(event.getEntity() instanceof LivingEntity livingEntity)) return; + if (!event.getCause().equals(EntityDamageEvent.DamageCause.FLY_INTO_WALL)) return; + + double damage = event.getDamage(); + + EntityEquipment entityEquipment = livingEntity.getEquipment(); + if (entityEquipment == null) return; + + int levels = Enchantio.getSumOfEnchantLevels(entityEquipment, airbag); + if (levels == 0) return; + + AirbagEnchant airbagEnchant = (AirbagEnchant) EnchantioConfig.ENCHANTS.get(AirbagEnchant.KEY); + double percentageDamageReduction = Math.min(1, levels * airbagEnchant.getDamageReductionPerLevel()); + event.setDamage(damage * (1 - percentageDamageReduction)); + } + +}