commit 87358cddb40ea317efa525cea3f53a3d22eaf610 Author: BuildTools Date: Mon Feb 5 04:26:03 2024 +0500 upload diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..854c9ae --- /dev/null +++ b/.gitignore @@ -0,0 +1,19 @@ +/.idea/ +/target/ +/pom.xml.versionsBackup +/Core/target/ +/Core/pom.xml.versionsBackup +/NMS/target/ +/NMS/pom.xml.versionsBackup +/V1_18_R2/target/ +/V1_18_R2/pom.xml.versionsBackup +/V1_19_R3/target/ +/V1_19_R3/pom.xml.versionsBackup +/V1_20_R1/target/ +/V1_20_R1/pom.xml.versionsBackup +/V1_20_R2/target/ +/V1_20_R2/pom.xml.versionsBackup +/V1_20_R3/target/ +/V1_20_R3/pom.xml.versionsBackup +/API/target/ +/API/pom.xml.versionsBackup \ No newline at end of file diff --git a/API/pom.xml b/API/pom.xml new file mode 100644 index 0000000..92e332d --- /dev/null +++ b/API/pom.xml @@ -0,0 +1,19 @@ + + + + ExcellentEnchants + su.nightexpress.excellentenchants + 3.6.5 + + 4.0.0 + + API + + + 17 + 17 + + + \ No newline at end of file diff --git a/API/src/main/java/su/nightexpress/excellentenchants/api/enchantment/IEnchantment.java b/API/src/main/java/su/nightexpress/excellentenchants/api/enchantment/IEnchantment.java new file mode 100644 index 0000000..391d5db --- /dev/null +++ b/API/src/main/java/su/nightexpress/excellentenchants/api/enchantment/IEnchantment.java @@ -0,0 +1,94 @@ +package su.nightexpress.excellentenchants.api.enchantment; + +import org.bukkit.Keyed; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.LivingEntity; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.api.config.JYML; + +import java.util.List; +import java.util.Set; + +public interface IEnchantment extends Keyed { + + boolean isAvailableToUse(@NotNull LivingEntity entity); + + @NotNull JYML getConfig(); + + @NotNull String getId(); + + @NotNull String getDisplayName(); + + @NotNull String getNameFormatted(int level); + + @NotNull String getNameFormatted(int level, int charges); + + @NotNull List getDescription(); + + @NotNull List getDescription(int level); + + @NotNull Set getConflicts(); + + @NotNull ITier getTier(); + + @NotNull EnchantmentTarget getCategory(); + + ItemCategory[] getFitItemTypes(); + + int getMaxLevel(); + + int getStartLevel(); + + int getLevelByEnchantCost(int expLevel); + + double getObtainChance(@NotNull ObtainType obtainType); + + int getObtainLevelMin(@NotNull ObtainType obtainType); + + int getObtainLevelMax(@NotNull ObtainType obtainType); + + int generateLevel(@NotNull ObtainType obtainType); + + int getAnvilMergeCost(int level); + + //@Deprecated + //boolean conflictsWith(@NotNull Enchantment enchantment); + + boolean checkEnchantCategory(@NotNull ItemStack item); + + boolean checkItemCategory(@NotNull ItemStack item); + + boolean isCurse(); + + boolean isTreasure(); + + boolean isTradeable(); + + boolean isDiscoverable(); + + boolean isChargesEnabled(); + + int getChargesMax(int level); + + int getChargesConsumeAmount(int level); + + int getChargesRechargeAmount(int level); + + @NotNull ItemStack getChargesFuel(); + + boolean isChargesFuel(@NotNull ItemStack item); + + int getCharges(@NotNull ItemStack item); + + boolean isFullOfCharges(@NotNull ItemStack item); + + boolean isOutOfCharges(@NotNull ItemStack item); + + void consumeCharges(@NotNull ItemStack item, int level); + + void consumeChargesNoUpdate(@NotNull ItemStack item, int level); + + EquipmentSlot[] getSlots(); +} diff --git a/API/src/main/java/su/nightexpress/excellentenchants/api/enchantment/ITier.java b/API/src/main/java/su/nightexpress/excellentenchants/api/enchantment/ITier.java new file mode 100644 index 0000000..23d34ef --- /dev/null +++ b/API/src/main/java/su/nightexpress/excellentenchants/api/enchantment/ITier.java @@ -0,0 +1,21 @@ +package su.nightexpress.excellentenchants.api.enchantment; + +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.api.placeholder.Placeholder; + +import java.util.Map; + +public interface ITier extends Placeholder { + + @NotNull String getId(); + + int getPriority(); + + @NotNull String getName(); + + @NotNull String getColor(); + + @NotNull Map getChance(); + + double getChance(@NotNull ObtainType obtainType); +} diff --git a/API/src/main/java/su/nightexpress/excellentenchants/api/enchantment/ItemCategory.java b/API/src/main/java/su/nightexpress/excellentenchants/api/enchantment/ItemCategory.java new file mode 100644 index 0000000..05c73fb --- /dev/null +++ b/API/src/main/java/su/nightexpress/excellentenchants/api/enchantment/ItemCategory.java @@ -0,0 +1,128 @@ +package su.nightexpress.excellentenchants.api.enchantment; + +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.utils.ItemUtil; + +import java.util.function.Predicate; + +public enum ItemCategory { + + HELMET(ItemUtil::isHelmet), + CHESTPLATE(ItemUtil::isChestplate), + LEGGINGS(ItemUtil::isLeggings), + BOOTS(ItemUtil::isBoots), + ELYTRA(item -> item.getType() == Material.ELYTRA), + + SWORD(ItemUtil::isSword), + TRIDENT(ItemUtil::isTrident), + AXE(ItemUtil::isAxe), + BOW(item -> item.getType() == Material.BOW), + CROSSBOW(item -> item.getType() == Material.CROSSBOW), + HOE(ItemUtil::isHoe), + PICKAXE(ItemUtil::isPickaxe), + SHOVEL(ItemUtil::isShovel), + FISHING_ROD(ItemUtil::isFishingRod), + + //@Deprecated WEAPON(item -> SWORD.isIncluded(item) || TRIDENT.isIncluded(item)), + TOOL(ItemUtil::isTool), + //@Deprecated ARMOR(ItemUtil::isArmor), + //UNIVERSAL(item -> WEAPON.isIncluded(item) || TOOL.isIncluded(item) || ARMOR.isIncluded(item)), + ; + + private Predicate predicate; + + ItemCategory(@NotNull Predicate predicate) { + this.setPredicate(predicate); + } + + @NotNull + public Predicate getPredicate() { + return predicate; + } + + public void setPredicate(@NotNull Predicate predicate) { + this.predicate = predicate; + } + + @Deprecated + public void patchPredicate(@NotNull Predicate extra) { + //this.setPredicate(item -> this.getPredicate().test(item) || (extra.test(item))); + } + + /*public EquipmentSlot[] getSlots() { + return switch (this) { + case BOW, CROSSBOW, TRIDENT, FISHING_ROD, WEAPON, TOOL, HOE, PICKAXE, AXE, SWORD, SHOVEL -> + new EquipmentSlot[]{EquipmentSlot.HAND}; + case HELMET -> new EquipmentSlot[]{EquipmentSlot.HEAD}; + case CHESTPLATE, ELYTRA -> new EquipmentSlot[]{EquipmentSlot.CHEST}; + case LEGGINGS -> new EquipmentSlot[]{EquipmentSlot.LEGS}; + case BOOTS -> new EquipmentSlot[]{EquipmentSlot.FEET}; + case ARMOR -> new EquipmentSlot[]{EquipmentSlot.HEAD, EquipmentSlot.CHEST, EquipmentSlot.LEGS, EquipmentSlot.FEET}; + case UNIVERSAL -> new EquipmentSlot[]{EquipmentSlot.HAND, EquipmentSlot.HEAD, EquipmentSlot.CHEST, EquipmentSlot.LEGS, EquipmentSlot.FEET}; + }; + } + + @NotNull + public EnchantmentTarget getEnchantmentTarget() { + return switch (this) { + case ARMOR -> EnchantmentTarget.ARMOR; + case BOOTS -> EnchantmentTarget.ARMOR_FEET; + case LEGGINGS -> EnchantmentTarget.ARMOR_LEGS; + case CHESTPLATE, ELYTRA -> EnchantmentTarget.ARMOR_TORSO; + case HELMET -> EnchantmentTarget.ARMOR_HEAD; + case WEAPON, SWORD -> EnchantmentTarget.WEAPON; + case TOOL, AXE, HOE, SHOVEL, PICKAXE -> EnchantmentTarget.TOOL; + case BOW -> EnchantmentTarget.BOW; + case FISHING_ROD -> EnchantmentTarget.FISHING_ROD; + case TRIDENT -> EnchantmentTarget.TRIDENT; + case CROSSBOW -> EnchantmentTarget.CROSSBOW; + case UNIVERSAL -> EnchantmentTarget.WEARABLE; + }; + } + + @NotNull + public static FitItemType getByEnchantmentTarget(@NotNull EnchantmentTarget target) { + return switch (target) { + case ARMOR -> ARMOR; + case ARMOR_FEET -> BOOTS; + case ARMOR_LEGS -> LEGGINGS; + case ARMOR_TORSO -> CHESTPLATE; + case ARMOR_HEAD -> HELMET; + case WEAPON -> WEAPON; + case TOOL -> TOOL; + case BOW -> BOW; + case FISHING_ROD -> FISHING_ROD; + case TRIDENT -> TRIDENT; + case CROSSBOW -> CROSSBOW; + case BREAKABLE, WEARABLE -> UNIVERSAL; + default -> throw new IllegalStateException("Unexpected value: " + target); + }; + }*/ + + public boolean isIncluded(@NotNull ItemStack item) { + return this.getPredicate().test(item); + + /*return switch (this) { + case UNIVERSAL -> ARMOR.isIncluded(item) || WEAPON.isIncluded(item) || TOOL.isIncluded(item) || BOW.isIncluded(item) || FISHING_ROD.isIncluded(item) || ELYTRA.isIncluded(item); + case HELMET -> ItemUtil.isHelmet(item); + case CHESTPLATE -> ItemUtil.isChestplate(item) || (Config.ENCHANTMENTS_ITEM_CHESTPLATE_ENCHANTS_TO_ELYTRA.get() && ELYTRA.isIncluded(item)); + case LEGGINGS -> ItemUtil.isLeggings(item); + case BOOTS -> ItemUtil.isBoots(item); + case ELYTRA -> item.getType() == Material.ELYTRA; + case WEAPON -> SWORD.isIncluded(item) || ItemUtil.isTrident(item); + case TOOL -> ItemUtil.isTool(item); + case ARMOR -> ItemUtil.isArmor(item); + case SWORD -> ItemUtil.isSword(item) || (Config.ENCHANTMENTS_ITEM_SWORD_ENCHANTS_TO_AXES.get() && AXE.isIncluded(item)); + case TRIDENT -> ItemUtil.isTrident(item); + case AXE -> ItemUtil.isAxe(item); + case BOW -> item.getType() == Material.BOW || (Config.ENCHANTMENTS_ITEM_BOW_ENCHANTS_TO_CROSSBOW.get() && CROSSBOW.isIncluded(item)); + case CROSSBOW -> item.getType() == Material.CROSSBOW; + case HOE -> ItemUtil.isHoe(item); + case PICKAXE -> ItemUtil.isPickaxe(item); + case SHOVEL -> ItemUtil.isShovel(item); + case FISHING_ROD -> item.getType() == Material.FISHING_ROD; + };*/ + } +} diff --git a/API/src/main/java/su/nightexpress/excellentenchants/api/enchantment/ObtainType.java b/API/src/main/java/su/nightexpress/excellentenchants/api/enchantment/ObtainType.java new file mode 100644 index 0000000..da894e1 --- /dev/null +++ b/API/src/main/java/su/nightexpress/excellentenchants/api/enchantment/ObtainType.java @@ -0,0 +1,23 @@ +package su.nightexpress.excellentenchants.api.enchantment; + +import org.jetbrains.annotations.NotNull; + +public enum ObtainType { + + ENCHANTING("Enchanting_Table"), + VILLAGER("Villagers"), + LOOT_GENERATION("Loot_Generation"), + FISHING("Fishing"), + MOB_SPAWNING("Mob_Spawning"); + + private final String pathName; + + ObtainType(@NotNull String pathName) { + this.pathName = pathName; + } + + @NotNull + public String getPathName() { + return pathName; + } +} diff --git a/API/src/main/java/su/nightexpress/excellentenchants/api/enchantment/Rarity.java b/API/src/main/java/su/nightexpress/excellentenchants/api/enchantment/Rarity.java new file mode 100644 index 0000000..1725955 --- /dev/null +++ b/API/src/main/java/su/nightexpress/excellentenchants/api/enchantment/Rarity.java @@ -0,0 +1,19 @@ +package su.nightexpress.excellentenchants.api.enchantment; + +public enum Rarity { + + COMMON(10), + UNCOMMON(5), + RARE(2), + VERY_RARE(1); + + private final int weight; + + Rarity(int weight) { + this.weight = weight; + } + + public int getWeight() { + return this.weight; + } +} diff --git a/Core/pom.xml b/Core/pom.xml new file mode 100644 index 0000000..ac556bf --- /dev/null +++ b/Core/pom.xml @@ -0,0 +1,154 @@ + + + + ExcellentEnchants + su.nightexpress.excellentenchants + 3.6.5 + + 4.0.0 + + Core + + + 16 + 16 + + + + + md_5-releases + https://repo.md-5.net/content/repositories/releases/ + + + dmulloy2-repo + https://repo.dmulloy2.net/repository/public/ + + + placeholderapi + https://repo.extendedclip.com/content/repositories/placeholderapi/ + + + lumine-repo + https://mvn.lumine.io/repository/maven-public/ + + + lumine-snapshot + http://mvn.lumine.io/repository/maven-snapshots/ + + + + + + org.spigotmc + spigot-api + 1.20.4-R0.1-SNAPSHOT + + + fr.neatmonster + nocheatplus + 3.16.1-SNAPSHOT + provided + + + io.lumine + Mythic-Dist + 5.0.1-SNAPSHOT + provided + + + com.comphenix.protocol + ProtocolLib + 5.1.0 + provided + + + me.clip + placeholderapi + 2.11.2 + provided + + + su.nightexpress.excellentenchants + API + 3.6.5 + + + su.nightexpress.excellentenchants + NMS + 3.6.5 + + + su.nightexpress.excellentenchants + V1_18_R2 + 3.6.5 + + + su.nightexpress.excellentenchants + V1_19_R3 + 3.6.5 + + + su.nightexpress.excellentenchants + V1_20_R1 + 3.6.5 + + + su.nightexpress.excellentenchants + V1_20_R2 + 3.6.5 + + + su.nightexpress.excellentenchants + V1_20_R3 + 3.6.5 + + + + + + + src/main/resources + true + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.2.4 + + + + org.ow2.asm + asm + 9.2 + + + org.ow2.asm + asm-commons + 9.2 + + + + + package + + shade + + + ${project.parent.basedir}\target\${project.parent.name}-${project.version}.jar + + + su.nightexpress.excellentenchants* + + + + + + + + + + \ No newline at end of file diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/ExcellentEnchants.java b/Core/src/main/java/su/nightexpress/excellentenchants/ExcellentEnchants.java new file mode 100644 index 0000000..b875dc9 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/ExcellentEnchants.java @@ -0,0 +1,172 @@ +package su.nightexpress.excellentenchants; + +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.NexPlugin; +import su.nexmedia.engine.Version; +import su.nexmedia.engine.api.command.GeneralCommand; +import su.nexmedia.engine.command.list.ReloadSubCommand; +import su.nexmedia.engine.utils.EngineUtils; +import su.nightexpress.excellentenchants.api.enchantment.ItemCategory; +import su.nightexpress.excellentenchants.api.enchantment.ObtainType; +import su.nightexpress.excellentenchants.command.BookCommand; +import su.nightexpress.excellentenchants.command.EnchantCommand; +import su.nightexpress.excellentenchants.command.ListCommand; +import su.nightexpress.excellentenchants.command.TierbookCommand; +import su.nightexpress.excellentenchants.config.Config; +import su.nightexpress.excellentenchants.config.Lang; +import su.nightexpress.excellentenchants.enchantment.EnchantManager; +import su.nightexpress.excellentenchants.enchantment.EnchantPopulator; +import su.nightexpress.excellentenchants.enchantment.registry.EnchantRegistry; +import su.nightexpress.excellentenchants.hook.HookId; +import su.nightexpress.excellentenchants.hook.impl.PlaceholderHook; +import su.nightexpress.excellentenchants.hook.impl.ProtocolHook; +import su.nightexpress.excellentenchants.nms.EnchantNMS; +import su.nightexpress.excellentenchants.nms.v1_18_R2.V1_18_R2; +import su.nightexpress.excellentenchants.nms.v1_19_R3.V1_19_R3; +import su.nightexpress.excellentenchants.nms.v1_20_R1.V1_20_R1; +import su.nightexpress.excellentenchants.nms.v1_20_R2.V1_20_R2; +import su.nightexpress.excellentenchants.nms.v1_20_R3.V1_20_R3; +import su.nightexpress.excellentenchants.tier.TierManager; + +public class ExcellentEnchants extends NexPlugin { + + // TODO Config option to use minecraft internal enchanting population + // TODO Custom name format for curse enchants + // TODO Custom name format for treasure enchants + + private EnchantRegistry registry; + private EnchantManager enchantManager; + private EnchantNMS enchantNMS; + private TierManager tierManager; + + @Override + @NotNull + protected ExcellentEnchants getSelf() { + return this; + } + + @Override + public void onLoad() { + super.onLoad(); + + //this.updateFitItemTypes(); + this.registry = new EnchantRegistry(this); + } + + @Override + public void enable() { + if (!this.setNMS()) { + this.error("Unsupported server version!"); + this.getPluginManager().disablePlugin(this); + return; + } + + this.tierManager = new TierManager(this); + this.tierManager.setup(); + + this.registry.setup(); + + this.enchantManager = new EnchantManager(this); + this.enchantManager.setup(); + + if (Config.ENCHANTMENTS_DISPLAY_MODE.get() == 2) { + if (EngineUtils.hasPlugin(HookId.PROTOCOL_LIB)) { + ProtocolHook.setup(); + } + else { + this.warn(HookId.PROTOCOL_LIB + " is not installed. Set display mode to Plain lore."); + Config.ENCHANTMENTS_DISPLAY_MODE.set(1); + } + } + } + + @Override + public void disable() { + if (this.enchantManager != null) { + this.enchantManager.shutdown(); + this.enchantManager = null; + } + if (this.tierManager != null) { + this.tierManager.shutdown(); + this.tierManager = null; + } + if (EngineUtils.hasPlaceholderAPI()) { + PlaceholderHook.shutdown(); + } + this.registry.shutdown(); + } + + private boolean setNMS() { + this.enchantNMS = switch (Version.getCurrent()) { + case V1_18_R2 -> new V1_18_R2(); + case V1_19_R3 -> new V1_19_R3(); + case V1_20_R1 -> new V1_20_R1(); + case V1_20_R2 -> new V1_20_R2(); + case V1_20_R3 -> new V1_20_R3(); + default -> null; + }; + return this.enchantNMS != null; + } + + @Override + public void loadConfig() { + this.getConfig().initializeOptions(Config.class); + } + + @Override + public void loadLang() { + this.getLangManager().loadMissing(Lang.class); + this.getLangManager().loadEnum(ItemCategory.class); + this.getLangManager().loadEnum(EnchantmentTarget.class); + this.getLangManager().loadEnum(ObtainType.class); + this.getLang().saveChanges(); + } + + @Override + public void registerCommands(@NotNull GeneralCommand mainCommand) { + mainCommand.addChildren(new BookCommand(this)); + mainCommand.addChildren(new EnchantCommand(this)); + mainCommand.addChildren(new ListCommand(this)); + mainCommand.addChildren(new TierbookCommand(this)); + mainCommand.addChildren(new ReloadSubCommand<>(this, Perms.COMMAND_RELOAD)); + } + + @Override + public void registerHooks() { + if (EngineUtils.hasPlaceholderAPI()) { + PlaceholderHook.setup(this); + } + } + + @Override + public void registerPermissions() { + this.registerPermissions(Perms.class); + } + + @NotNull + public EnchantPopulator createPopulator(@NotNull ItemStack item, @NotNull ObtainType obtainType) { + return new EnchantPopulator(this, item, obtainType); + } + + @NotNull + public TierManager getTierManager() { + return tierManager; + } + + @NotNull + public EnchantRegistry getRegistry() { + return registry; + } + + @NotNull + public EnchantManager getEnchantManager() { + return this.enchantManager; + } + + @NotNull + public EnchantNMS getEnchantNMS() { + return enchantNMS; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/ExcellentEnchantsAPI.java b/Core/src/main/java/su/nightexpress/excellentenchants/ExcellentEnchantsAPI.java new file mode 100644 index 0000000..a7ccda1 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/ExcellentEnchantsAPI.java @@ -0,0 +1,20 @@ +package su.nightexpress.excellentenchants; + +import org.jetbrains.annotations.NotNull; +import su.nightexpress.excellentenchants.enchantment.EnchantManager; +import su.nightexpress.excellentenchants.tier.TierManager; + +public class ExcellentEnchantsAPI { + + public static final ExcellentEnchants PLUGIN = ExcellentEnchants.getPlugin(ExcellentEnchants.class); + + @NotNull + public static EnchantManager getEnchantManager() { + return PLUGIN.getEnchantManager(); + } + + @NotNull + public static TierManager getTierManager() { + return PLUGIN.getTierManager(); + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/Perms.java b/Core/src/main/java/su/nightexpress/excellentenchants/Perms.java new file mode 100644 index 0000000..88045d3 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/Perms.java @@ -0,0 +1,25 @@ +package su.nightexpress.excellentenchants; + +import org.bukkit.permissions.PermissionDefault; +import su.nexmedia.engine.api.server.JPermission; + +public class Perms { + + private static final String PREFIX = "excellentenchants."; + private static final String PREFIX_COMMAND = PREFIX + "command."; + + public static final JPermission PLUGIN = new JPermission(PREFIX + Placeholders.WILDCARD, "Access to all the plugin functions."); + public static final JPermission COMMAND = new JPermission(PREFIX_COMMAND + Placeholders.WILDCARD, "Access to all the plugin commands."); + + public static final JPermission COMMAND_BOOK = new JPermission(PREFIX_COMMAND + "book", "Allows to use '/eenchants book' command."); + public static final JPermission COMMAND_ENCHANT = new JPermission(PREFIX_COMMAND + "enchant", "Allows to use '/eenchants enchant' command."); + public static final JPermission COMMAND_LIST = new JPermission(PREFIX_COMMAND + "list", "Allows to use '/eenchants list' command.", PermissionDefault.TRUE); + public static final JPermission COMMAND_TIERBOOK = new JPermission(PREFIX_COMMAND + "tierbook", "Allows to use '/eenchants tierbook' command."); + public static final JPermission COMMAND_RELOAD = new JPermission(PREFIX_COMMAND + "reload", "Allows to use '/eenchants reload' command."); + + static { + PLUGIN.addChildren(COMMAND); + + COMMAND.addChildren(COMMAND_BOOK, COMMAND_ENCHANT, COMMAND_LIST, COMMAND_RELOAD, COMMAND_TIERBOOK); + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/Placeholders.java b/Core/src/main/java/su/nightexpress/excellentenchants/Placeholders.java new file mode 100644 index 0000000..03b4bc5 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/Placeholders.java @@ -0,0 +1,54 @@ +/* + * Decompiled with CFR 0.151. + * + * Could not load the following classes: + * su.nexmedia.engine.utils.Placeholders + */ +package su.nightexpress.excellentenchants; + +public class Placeholders +extends su.nexmedia.engine.utils.Placeholders { + public static final String URL_WIKI = "https://github.com/nulli0n/ExcellentEnchants-spigot/wiki/"; + public static final String URL_PLACEHOLDERS = "https://github.com/nulli0n/ExcellentEnchants-spigot/wiki/Internal-Placeholders"; + public static final String URL_ENGINE_SCALER = "https://github.com/nulli0n/NexEngine-spigot/wiki/Configuration-Tips#scalable-sections"; + public static final String URL_ENGINE_ITEMS = "https://github.com/nulli0n/NexEngine-spigot/wiki/Configuration-Tips#item-sections"; + public static final String GENERIC_TYPE = "%type%"; + public static final String GENERIC_NAME = "%name%"; + public static final String GENERIC_ITEM = "%item%"; + public static final String GENERIC_LEVEL = "%level%"; + public static final String GENERIC_AMOUNT = "%amount%"; + public static final String GENERIC_DESCRIPTION = "%description%"; + public static final String GENERIC_ENCHANT = "%enchant%"; + public static final String ENCHANTMENT_CHANCE = "%enchantment_trigger_chance%"; + public static final String ENCHANTMENT_INTERVAL = "%enchantment_trigger_interval%"; + public static final String ENCHANTMENT_POTION_LEVEL = "%enchantment_potion_level%"; + public static final String ENCHANTMENT_POTION_DURATION = "%enchantment_potion_duration%"; + public static final String ENCHANTMENT_POTION_TYPE = "%enchantment_potion_type%"; + public static final String ENCHANTMENT_ID = "%enchantment_id%"; + public static final String ENCHANTMENT_NAME = "%enchantment_name%"; + public static final String ENCHANTMENT_NAME_FORMATTED = "%enchantment_name_formatted%"; + public static final String ENCHANTMENT_DESCRIPTION = "%enchantment_description%"; + public static final String ENCHANTMENT_LEVEL = "%enchantment_level%"; + public static final String ENCHANTMENT_LEVEL_MIN = "%enchantment_level_min%"; + public static final String ENCHANTMENT_LEVEL_MAX = "%enchantment_level_max%"; + public static final String ENCHANTMENT_TIER = "%enchantment_tier%"; + public static final String ENCHANTMENT_TIER_COLOR = "%enchantment_tier_color%"; + public static final String ENCHANTMENT_FIT_ITEM_TYPES = "%enchantment_fit_item_types%"; + public static final String ENCHANTMENT_OBTAIN_CHANCE_ENCHANTING = "%enchantment_obtain_chance_enchanting%"; + public static final String ENCHANTMENT_OBTAIN_CHANCE_VILLAGER = "%enchantment_obtain_chance_villager%"; + public static final String ENCHANTMENT_OBTAIN_CHANCE_LOOT_GENERATION = "%enchantment_obtain_chance_loot_generation%"; + public static final String ENCHANTMENT_OBTAIN_CHANCE_FISHING = "%enchantment_obtain_chance_fishing%"; + public static final String ENCHANTMENT_OBTAIN_CHANCE_MOB_SPAWNING = "%enchantment_obtain_chance_mob_spawning%"; + public static final String ENCHANTMENT_CHARGES_MAX_AMOUNT = "%enchantment_charges_max_amount%"; + public static final String ENCHANTMENT_CHARGES_CONSUME_AMOUNT = "%enchantment_charges_consume_amount%"; + public static final String ENCHANTMENT_CHARGES_RECHARGE_AMOUNT = "%enchantment_charges_recharge_amount%"; + public static final String ENCHANTMENT_CHARGES_FUEL_ITEM = "%enchantment_charges_fuel_item%"; + public static final String TIER_ID = "%tier_id%"; + public static final String TIER_NAME = "%tier_name%"; + public static final String TIER_OBTAIN_CHANCE_ENCHANTING = "%tier_obtain_chance_enchanting%"; + public static final String TIER_OBTAIN_CHANCE_VILLAGER = "%tier_obtain_chance_villager%"; + public static final String TIER_OBTAIN_CHANCE_LOOT_GENERATION = "%tier_obtain_chance_loot_generation%"; + public static final String TIER_OBTAIN_CHANCE_FISHING = "%tier_obtain_chance_fishing%"; + public static final String TIER_OBTAIN_CHANCE_MOB_SPAWNING = "%tier_obtain_chance_mob_spawning%"; +} + diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/Cleanable.java b/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/Cleanable.java new file mode 100644 index 0000000..4cc9d59 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/Cleanable.java @@ -0,0 +1,6 @@ +package su.nightexpress.excellentenchants.api.enchantment; + +public interface Cleanable { + + void clear(); +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/meta/Arrowed.java b/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/meta/Arrowed.java new file mode 100644 index 0000000..d9ff04b --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/meta/Arrowed.java @@ -0,0 +1,29 @@ +package su.nightexpress.excellentenchants.api.enchantment.meta; + +import org.bukkit.entity.Projectile; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.utils.values.UniParticle; + +import java.util.Optional; + +public interface Arrowed { + + @NotNull Arrowed getArrowImplementation(); + + @NotNull + default Optional getTrailParticle() { + return this.getArrowImplementation().getTrailParticle(); + } + + default void addTrail(@NotNull Projectile projectile) { + this.getArrowImplementation().addTrail(projectile); + } + + default void addData(@NotNull Projectile projectile) { + this.getArrowImplementation().addData(projectile); + } + + default boolean isOurProjectile(@NotNull Projectile projectile) { + return this.getArrowImplementation().isOurProjectile(projectile); + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/meta/Chanced.java b/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/meta/Chanced.java new file mode 100644 index 0000000..21f2b32 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/meta/Chanced.java @@ -0,0 +1,21 @@ +package su.nightexpress.excellentenchants.api.enchantment.meta; + +import org.jetbrains.annotations.NotNull; + +public interface Chanced { + + @NotNull Chanced getChanceImplementation(); + + /*@NotNull + default UnaryOperator replacePlaceholders(int level) { + return this.getChanceImplementation().replacePlaceholders(level); + }*/ + + default double getTriggerChance(int level) { + return this.getChanceImplementation().getTriggerChance(level); + } + + default boolean checkTriggerChance(int level) { + return getChanceImplementation().checkTriggerChance(level); + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/meta/Periodic.java b/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/meta/Periodic.java new file mode 100644 index 0000000..f2194ce --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/meta/Periodic.java @@ -0,0 +1,24 @@ +package su.nightexpress.excellentenchants.api.enchantment.meta; + +import org.jetbrains.annotations.NotNull; + +public interface Periodic { + + @NotNull Periodic getPeriodImplementation(); + + default long getInterval() { + return this.getPeriodImplementation().getInterval(); + } + + default long getNextTriggerTime() { + return this.getPeriodImplementation().getNextTriggerTime(); + } + + default boolean isTriggerTime() { + return this.getPeriodImplementation().isTriggerTime(); + } + + default void updateTriggerTime() { + this.getPeriodImplementation().updateTriggerTime(); + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/meta/Potioned.java b/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/meta/Potioned.java new file mode 100644 index 0000000..246074a --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/meta/Potioned.java @@ -0,0 +1,35 @@ +package su.nightexpress.excellentenchants.api.enchantment.meta; + +import org.bukkit.entity.LivingEntity; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.jetbrains.annotations.NotNull; + +public interface Potioned { + + @NotNull Potioned getPotionImplementation(); + + default boolean isPermanent() { + return this.getPotionImplementation().isPermanent(); + } + + default PotionEffectType getEffectType() { + return this.getPotionImplementation().getEffectType(); + } + + default int getEffectAmplifier(int level) { + return this.getPotionImplementation().getEffectAmplifier(level); + } + + default int getEffectDuration(int level) { + return this.getPotionImplementation().getEffectDuration(level); + } + + default PotionEffect createEffect(int level) { + return this.getPotionImplementation().createEffect(level); + } + + default boolean addEffect(@NotNull LivingEntity target, int level) { + return this.getPotionImplementation().addEffect(target, level); + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/type/BlockBreakEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/type/BlockBreakEnchant.java new file mode 100644 index 0000000..32de3aa --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/type/BlockBreakEnchant.java @@ -0,0 +1,18 @@ +package su.nightexpress.excellentenchants.api.enchantment.type; + +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nightexpress.excellentenchants.api.enchantment.IEnchantment; + +public interface BlockBreakEnchant extends IEnchantment { + + boolean onBreak(@NotNull BlockBreakEvent event, @NotNull LivingEntity player, @NotNull ItemStack item, int level); + + @NotNull + default EventPriority getBreakPriority() { + return EventPriority.HIGH; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/type/BlockDropEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/type/BlockDropEnchant.java new file mode 100644 index 0000000..e8c3268 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/type/BlockDropEnchant.java @@ -0,0 +1,18 @@ +package su.nightexpress.excellentenchants.api.enchantment.type; + +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockDropItemEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nightexpress.excellentenchants.api.enchantment.IEnchantment; + +public interface BlockDropEnchant extends IEnchantment { + + boolean onDrop(@NotNull BlockDropItemEvent event, @NotNull LivingEntity player, @NotNull ItemStack item, int level); + + @NotNull + default EventPriority getDropPriority() { + return EventPriority.NORMAL; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/type/BowEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/type/BowEnchant.java new file mode 100644 index 0000000..4653606 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/type/BowEnchant.java @@ -0,0 +1,37 @@ +package su.nightexpress.excellentenchants.api.enchantment.type; + +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Projectile; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityShootBowEvent; +import org.bukkit.event.entity.ProjectileHitEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nightexpress.excellentenchants.api.enchantment.IEnchantment; + +public interface BowEnchant extends IEnchantment { + + boolean onShoot(@NotNull EntityShootBowEvent event, @NotNull LivingEntity shooter, @NotNull ItemStack bow, int level); + + boolean onHit(@NotNull ProjectileHitEvent event, LivingEntity user, @NotNull Projectile projectile, @NotNull ItemStack bow, int level); + + boolean onDamage(@NotNull EntityDamageByEntityEvent event, @NotNull Projectile projectile, + @NotNull LivingEntity shooter, @NotNull LivingEntity victim, + @NotNull ItemStack weapon, int level); + + @NotNull + default EventPriority getShootPriority() { + return EventPriority.NORMAL; + } + + @NotNull + default EventPriority getHitPriority() { + return EventPriority.NORMAL; + } + + @NotNull + default EventPriority getDamagePriority() { + return EventPriority.NORMAL; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/type/CombatEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/type/CombatEnchant.java new file mode 100644 index 0000000..54e9535 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/type/CombatEnchant.java @@ -0,0 +1,29 @@ +package su.nightexpress.excellentenchants.api.enchantment.type; + +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nightexpress.excellentenchants.api.enchantment.IEnchantment; + +public interface CombatEnchant extends IEnchantment { + + boolean onAttack(@NotNull EntityDamageByEntityEvent event, + @NotNull LivingEntity damager, @NotNull LivingEntity victim, + @NotNull ItemStack weapon, int level); + + boolean onProtect(@NotNull EntityDamageByEntityEvent event, + @NotNull LivingEntity damager, @NotNull LivingEntity victim, + @NotNull ItemStack weapon, int level); + + @NotNull + default EventPriority getAttackPriority() { + return EventPriority.NORMAL; + } + + @NotNull + default EventPriority getProtectPriority() { + return EventPriority.NORMAL; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/type/DamageEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/type/DamageEnchant.java new file mode 100644 index 0000000..8099f3f --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/type/DamageEnchant.java @@ -0,0 +1,18 @@ +package su.nightexpress.excellentenchants.api.enchantment.type; + +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nightexpress.excellentenchants.api.enchantment.IEnchantment; + +public interface DamageEnchant extends IEnchantment { + + boolean onDamage(@NotNull EntityDamageEvent event, @NotNull LivingEntity entity, @NotNull ItemStack item, int level); + + @NotNull + default EventPriority getDamagePriority() { + return EventPriority.NORMAL; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/type/DeathEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/type/DeathEnchant.java new file mode 100644 index 0000000..e961ccc --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/type/DeathEnchant.java @@ -0,0 +1,29 @@ +package su.nightexpress.excellentenchants.api.enchantment.type; + +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.event.entity.EntityResurrectEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nightexpress.excellentenchants.api.enchantment.IEnchantment; + +public interface DeathEnchant extends IEnchantment { + + boolean onDeath(@NotNull EntityDeathEvent event, @NotNull LivingEntity entity, ItemStack item, int level); + + boolean onKill(@NotNull EntityDeathEvent event, @NotNull LivingEntity entity, @NotNull Player killer, ItemStack weapon, int level); + + boolean onResurrect(@NotNull EntityResurrectEvent event, @NotNull LivingEntity entity, @NotNull ItemStack item, int level); + + @NotNull + default EventPriority getDeathPriority() { + return EventPriority.NORMAL; + } + + @NotNull + default EventPriority getKillPriority() { + return EventPriority.NORMAL; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/type/FishingEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/type/FishingEnchant.java new file mode 100644 index 0000000..cc45db4 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/type/FishingEnchant.java @@ -0,0 +1,17 @@ +package su.nightexpress.excellentenchants.api.enchantment.type; + +import org.bukkit.event.EventPriority; +import org.bukkit.event.player.PlayerFishEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nightexpress.excellentenchants.api.enchantment.IEnchantment; + +public interface FishingEnchant extends IEnchantment { + + boolean onFishing(@NotNull PlayerFishEvent event, @NotNull ItemStack item, int level); + + @NotNull + default EventPriority getFishingPriority() { + return EventPriority.NORMAL; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/type/GenericEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/type/GenericEnchant.java new file mode 100644 index 0000000..f7aa000 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/type/GenericEnchant.java @@ -0,0 +1,7 @@ +package su.nightexpress.excellentenchants.api.enchantment.type; + +import su.nightexpress.excellentenchants.api.enchantment.IEnchantment; + +public interface GenericEnchant extends IEnchantment { + +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/type/InteractEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/type/InteractEnchant.java new file mode 100644 index 0000000..e4e526c --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/type/InteractEnchant.java @@ -0,0 +1,18 @@ +package su.nightexpress.excellentenchants.api.enchantment.type; + +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.EventPriority; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nightexpress.excellentenchants.api.enchantment.IEnchantment; + +public interface InteractEnchant extends IEnchantment { + + boolean onInteract(@NotNull PlayerInteractEvent event, @NotNull LivingEntity entity, @NotNull ItemStack item, int level); + + @NotNull + default EventPriority getInteractPriority() { + return EventPriority.NORMAL; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/type/PassiveEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/type/PassiveEnchant.java new file mode 100644 index 0000000..28c190d --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/api/enchantment/type/PassiveEnchant.java @@ -0,0 +1,12 @@ +package su.nightexpress.excellentenchants.api.enchantment.type; + +import org.bukkit.entity.LivingEntity; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nightexpress.excellentenchants.api.enchantment.IEnchantment; +import su.nightexpress.excellentenchants.api.enchantment.meta.Periodic; + +public interface PassiveEnchant extends IEnchantment, Periodic { + + boolean onTrigger(@NotNull LivingEntity entity, @NotNull ItemStack item, int level); +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/command/BookCommand.java b/Core/src/main/java/su/nightexpress/excellentenchants/command/BookCommand.java new file mode 100644 index 0000000..a63f2f2 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/command/BookCommand.java @@ -0,0 +1,81 @@ +package su.nightexpress.excellentenchants.command; + +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.command.CommandSender; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.api.command.AbstractCommand; +import su.nexmedia.engine.api.command.CommandResult; +import su.nexmedia.engine.utils.CollectionsUtil; +import su.nexmedia.engine.utils.PlayerUtil; +import su.nexmedia.engine.utils.random.Rnd; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Perms; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.config.Lang; +import su.nightexpress.excellentenchants.enchantment.util.EnchantUtils; + +import java.util.Arrays; +import java.util.List; + +public class BookCommand extends AbstractCommand { + + public BookCommand(@NotNull ExcellentEnchants plugin) { + super(plugin, new String[]{"book"}, Perms.COMMAND_BOOK); + this.setDescription(plugin.getMessage(Lang.COMMAND_BOOK_DESC)); + this.setUsage(plugin.getMessage(Lang.COMMAND_BOOK_USAGE)); + } + + @Override + @NotNull + public List getTab(@NotNull Player player, int arg, @NotNull String[] args) { + if (arg == 1) { + return CollectionsUtil.playerNames(player); + } + if (arg == 2) { + return Arrays.stream(Enchantment.values()).map(e -> e.getKey().getKey()).toList(); + } + if (arg == 3) { + return Arrays.asList("-1", "1", "5", "10"); + } + return super.getTab(player, arg, args); + } + + @Override + protected void onExecute(@NotNull CommandSender sender, @NotNull CommandResult result) { + if (result.length() < 4) { + this.printUsage(sender); + return; + } + + Player player = PlayerUtil.getPlayer(result.getArg(1)); + if (player == null) { + this.errorPlayer(sender); + return; + } + + Enchantment enchantment = Enchantment.getByKey(NamespacedKey.minecraft(result.getArg(2).toLowerCase())); + if (enchantment == null) { + plugin.getMessage(Lang.ERROR_NO_ENCHANT).send(sender); + return; + } + + int level = result.getInt(3, -1); + if (level < 1) { + level = Rnd.get(enchantment.getStartLevel(), enchantment.getMaxLevel()); + } + + ItemStack item = new ItemStack(Material.ENCHANTED_BOOK); + EnchantUtils.add(item, enchantment, level, true); + EnchantUtils.updateDisplay(item); + PlayerUtil.addItem(player, item); + + plugin.getMessage(Lang.COMMAND_BOOK_DONE) + .replace(Placeholders.GENERIC_ENCHANT, EnchantUtils.getLocalized(enchantment)) + .replace(Placeholders.forPlayer(player)) + .send(sender); + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/command/EnchantCommand.java b/Core/src/main/java/su/nightexpress/excellentenchants/command/EnchantCommand.java new file mode 100644 index 0000000..9d59d76 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/command/EnchantCommand.java @@ -0,0 +1,96 @@ +package su.nightexpress.excellentenchants.command; + +import org.bukkit.NamespacedKey; +import org.bukkit.command.CommandSender; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.Player; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.api.command.AbstractCommand; +import su.nexmedia.engine.api.command.CommandResult; +import su.nexmedia.engine.utils.*; +import su.nexmedia.engine.utils.random.Rnd; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Perms; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.config.Lang; +import su.nightexpress.excellentenchants.enchantment.util.EnchantUtils; + +import java.util.Arrays; +import java.util.List; + +public class EnchantCommand extends AbstractCommand { + + public EnchantCommand(@NotNull ExcellentEnchants plugin) { + super(plugin, new String[]{"enchant"}, Perms.COMMAND_ENCHANT); + this.setDescription(plugin.getMessage(Lang.COMMAND_ENCHANT_DESC)); + this.setUsage(plugin.getMessage(Lang.COMMAND_ENCHANT_USAGE)); + } + + @Override + @NotNull + public List getTab(@NotNull Player player, int arg, @NotNull String[] args) { + if (arg == 1) { + return Arrays.stream(Enchantment.values()).map(e -> e.getKey().getKey()).toList(); + } + if (arg == 2) { + return Arrays.asList("-1", "1", "5", "10"); + } + if (arg == 3) { + return CollectionsUtil.playerNames(player); + } + if (arg == 4) { + return CollectionsUtil.getEnumsList(EquipmentSlot.class); + } + return super.getTab(player, arg, args); + } + + @Override + protected void onExecute(@NotNull CommandSender sender, @NotNull CommandResult result) { + if (result.length() < 3) { + this.printUsage(sender); + return; + } + + Enchantment enchantment = Enchantment.getByKey(NamespacedKey.minecraft(result.getArg(1).toLowerCase())); + if (enchantment == null) { + plugin.getMessage(Lang.ERROR_NO_ENCHANT).send(sender); + return; + } + + Player player = PlayerUtil.getPlayer(result.getArg(3, sender.getName())); + if (player == null) { + this.errorPlayer(sender); + return; + } + + EquipmentSlot slot = StringUtil.getEnum(result.getArg(4, ""), EquipmentSlot.class).orElse(EquipmentSlot.HAND); + + ItemStack item = player.getInventory().getItem(slot); + if (item == null || item.getType().isAir()) { + this.plugin.getMessage(Lang.COMMAND_ENCHANT_ERROR_NO_ITEM).send(sender); + return; + } + + int level = result.getInt(2, -1); + if (level < 0) { + level = Rnd.get(enchantment.getStartLevel(), enchantment.getMaxLevel()); + } + + if (level > 0) { + EnchantUtils.add(item, enchantment, level, true); + } + else EnchantUtils.remove(item, enchantment); + + EnchantUtils.updateDisplay(item); + player.getInventory().setItem(slot, item); + + plugin.getMessage(sender == player ? Lang.COMMAND_ENCHANT_DONE_SELF : Lang.COMMAND_ENCHANT_DONE_OTHERS) + .replace(Placeholders.forPlayer(player)) + .replace(Placeholders.GENERIC_ITEM, ItemUtil.getItemName(item)) + .replace(Placeholders.GENERIC_ENCHANT, EnchantUtils.getLocalized(enchantment)) + .replace(Placeholders.GENERIC_LEVEL, NumberUtil.toRoman(level)) + .send(sender); + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/command/ListCommand.java b/Core/src/main/java/su/nightexpress/excellentenchants/command/ListCommand.java new file mode 100644 index 0000000..cb1dcdb --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/command/ListCommand.java @@ -0,0 +1,24 @@ +package su.nightexpress.excellentenchants.command; + +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.api.command.AbstractCommand; +import su.nexmedia.engine.api.command.CommandResult; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Perms; +import su.nightexpress.excellentenchants.config.Lang; + +public class ListCommand extends AbstractCommand { + + public ListCommand(@NotNull ExcellentEnchants plugin) { + super(plugin, new String[]{"list"}, Perms.COMMAND_LIST); + this.setDescription(plugin.getMessage(Lang.COMMAND_LIST_DESC)); + this.setPlayerOnly(true); + } + + @Override + protected void onExecute(@NotNull CommandSender sender, @NotNull CommandResult result) { + plugin.getEnchantManager().getEnchantsListGUI().open((Player) sender, 1); + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/command/TierbookCommand.java b/Core/src/main/java/su/nightexpress/excellentenchants/command/TierbookCommand.java new file mode 100644 index 0000000..f1813ac --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/command/TierbookCommand.java @@ -0,0 +1,90 @@ +package su.nightexpress.excellentenchants.command; + +import org.bukkit.Material; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.api.command.AbstractCommand; +import su.nexmedia.engine.api.command.CommandResult; +import su.nexmedia.engine.utils.CollectionsUtil; +import su.nexmedia.engine.utils.Placeholders; +import su.nexmedia.engine.utils.PlayerUtil; +import su.nexmedia.engine.utils.random.Rnd; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Perms; +import su.nightexpress.excellentenchants.config.Lang; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.registry.EnchantRegistry; +import su.nightexpress.excellentenchants.enchantment.util.EnchantUtils; +import su.nightexpress.excellentenchants.tier.Tier; + +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +public class TierbookCommand extends AbstractCommand { + + public TierbookCommand(@NotNull ExcellentEnchants plugin) { + super(plugin, new String[]{"tierbook"}, Perms.COMMAND_TIERBOOK); + this.setDescription(plugin.getMessage(Lang.COMMAND_TIER_BOOK_DESC)); + this.setUsage(plugin.getMessage(Lang.COMMAND_TIER_BOOK_USAGE)); + } + + @Override + @NotNull + public List getTab(@NotNull Player player, int arg, @NotNull String[] args) { + if (arg == 1) { + return CollectionsUtil.playerNames(player); + } + if (arg == 2) { + return plugin.getTierManager().getTierIds(); + } + if (arg == 3) { + return Arrays.asList("-1", "1", "5", "10"); + } + return super.getTab(player, arg, args); + } + + @Override + protected void onExecute(@NotNull CommandSender sender, @NotNull CommandResult result) { + if (result.length() < 4) { + this.printUsage(sender); + return; + } + + Player player = PlayerUtil.getPlayer(result.getArg(1)); + if (player == null) { + this.errorPlayer(sender); + return; + } + + Tier tier = plugin.getTierManager().getTierById(result.getArg(2).toLowerCase()); + if (tier == null) { + plugin.getMessage(Lang.COMMAND_TIER_BOOK_ERROR).send(sender); + return; + } + + Set enchants = EnchantRegistry.getOfTier(tier); + ExcellentEnchant enchant = enchants.isEmpty() ? null : Rnd.get(enchants); + if (enchant == null) { + plugin.getMessage(Lang.ERROR_NO_ENCHANT).send(sender); + return; + } + + int level = result.getInt(3, -1); + if (level < 1) { + level = Rnd.get(enchant.getStartLevel(), enchant.getMaxLevel()); + } + + ItemStack item = new ItemStack(Material.ENCHANTED_BOOK); + EnchantUtils.add(item, enchant.getBackend(), level, true); + EnchantUtils.updateDisplay(item); + PlayerUtil.addItem(player, item); + + plugin.getMessage(Lang.COMMAND_TIER_BOOK_DONE) + .replace(tier.replacePlaceholders()) + .replace(Placeholders.forPlayer(player)) + .send(sender); + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/config/Config.java b/Core/src/main/java/su/nightexpress/excellentenchants/config/Config.java new file mode 100644 index 0000000..fa89bde --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/config/Config.java @@ -0,0 +1,168 @@ +package su.nightexpress.excellentenchants.config; + +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.api.config.JOption; +import su.nexmedia.engine.api.config.JYML; +import su.nexmedia.engine.utils.Colorizer; +import su.nexmedia.engine.utils.Colors2; +import su.nexmedia.engine.utils.StringUtil; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.ObtainType; +import su.nightexpress.excellentenchants.tier.Tier; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class Config { + + public static final String DIR_MENU = "/menu/"; + + public static final JOption TASKS_ARROW_TRAIL_TICKS_INTERVAL = JOption.create("Tasks.Arrow_Trail.Tick_Interval", + 1L, + "Sets how often (in ticks) arrow trail particle effects will be spawned behind the arrow." + ); + + public static final JOption TASKS_PASSIVE_ENCHANTS_TRIGGER_INTERVAL = JOption.create("Tasks.Passive_Enchants.Trigger_Interval", + 100L, + "Sets how often (in ticks) the plugin will attempt to trigger passive enchantment effects on all alive entities.", + "For best results it's recommened to keep this value smaller, but at the same rate as enchantment 'Trigger_Interval' settings.", + "Examples:", + "--- Global: 100 ticks; Regrowth: 100 ticks; Saturation: 100 ticks;", + "--- Global: 50 ticks, Regrowth: 100 ticks; Saturation: 150 ticks;" + ); + + public static final JOption ENCHANTMENTS_INTERNAL_HANDLER = JOption.create("Enchantments.Internal_Distributor", false, + "[EXPERIMENTAL - DO NOT TOUCH]", + "Sets whether or not enchantment distribution will be handled by vanilla server mechanics."); + + public static final JOption ENCHANTMENTS_CHARGES_ENABLED = JOption.create("Enchantments.Charges.Enabled", + false, + "Enables the enchantment Charges feature.", + Placeholders.URL_WIKI + "Charges-System"); + + public static final JOption> ENCHANTMENTS_CHARGES_FORMAT = new JOption>("Enchantments.Charges.Format", + (cfg, path, def) -> { + TreeMap map = new TreeMap<>(); + for (String raw : cfg.getSection(path)) { + int percent = StringUtil.getInteger(raw, -1); + if (percent < 0) continue; + + String format = Colorizer.apply(cfg.getString(path + "." + raw, "")); + if (format.isEmpty()) continue; + + map.put(percent, format); + } + return map; + }, + () -> { + TreeMap map = new TreeMap<>(); + map.put(0, "#ff9a9a(" + Placeholders.GENERIC_AMOUNT + "⚡)"); + map.put(25, "#ffc39a(" + Placeholders.GENERIC_AMOUNT + "⚡)"); + map.put(50, "#f6ff9a(" + Placeholders.GENERIC_AMOUNT + "⚡)"); + map.put(75, "#bcff9a(" + Placeholders.GENERIC_AMOUNT + "⚡)"); + return map; + }, + "Enchantment charges format depends on amount of charges left (in percent).", + "If you don't want to display charges, leave only keys with negative values.", + "Use '" + Placeholders.GENERIC_AMOUNT + "' placeholder for amount of charges.") + .setWriter((cfg, path, map) -> map.forEach((perc, str) -> cfg.set(path + "." + perc, str))); + + public static final JOption ENCHANTMENTS_CHARGES_COMPARE_TYPE_ONLY = JOption.create("Enchantments.Charges.Compare_Material_Only", false, + "When enabled, only item material will be checked to determine if item can be used as an enchantment fuel.", + "When disabled (default), it will compare the whole item meta including name, lore, model data etc."); + + public static final JOption ENCHANTMENTS_CHARGES_FUEL_ITEM = JOption.create("Enchantments.Charges.Fuel_Item", + new ItemStack(Material.LAPIS_LAZULI), + "Default item used to recharge item's enchantments on anvils.", + "If you want different item for certain enchantments, you can do it in that enchantment configs.", + "Item Options: " + Placeholders.URL_ENGINE_SCALER) + .setWriter(JYML::setItem); + + public static final JOption> ENCHANTMENTS_DISABLED = JOption.create("Enchantments.Disabled", + Set.of("enchant_name", "other_enchant"), + "A list of enchantments, that will be disabled and removed from the game (server).", + "Enchantment names are the same as enchantment file name in /enchants/ folder. ! Must be in lower_case !", + "Example: To disable 'Explosive Arrows' you need to add 'explosive_arrows' here.") + .mapReader(set -> set.stream().map(String::toLowerCase).collect(Collectors.toSet())); + + public static final JOption>> ENCHANTMENTS_DISABLED_IN_WORLDS = new JOption>>("Enchantments.Disabled_In_Worlds", + (cfg, path, def) -> cfg.getSection(path).stream().collect(Collectors.toMap(k -> k, worldName -> cfg.getStringSet(path + "." + worldName))), + () -> Map.of("your_world_name", Set.of("enchantment_name", "ice_aspect")), + "Here you can disable certain enchantments in certain worlds.", + "Enchantment names are the same as enchantment file name in /enchants/ folder. ! Must be in lower_case !", + "To disable all enchantments for a world, use '" + Placeholders.WILDCARD + "' instead of enchantment names.") + .setWriter((cfg, path, map) -> map.forEach((world, enchants) -> cfg.set(path + "." + world, enchants))); + + public static final JOption ENCHANTMENTS_DISPLAY_MODE = JOption.create("Enchantments.Display.Mode", + 1, + "Sets how enchantment names and descriptions will be handled on items.", + "1 = Plain modification of item's lore (lore changes are real and persistent).", + "2 = Packet modification of item's lore (no real changes are made to the items). Requires ProtocolLib.", + "Plain mode is faster, but may not reflect all changes immediately.", + "Packet mode is slower, but instantly reflect all changes. In creative mode, there is a chance for lore duplication."); + + public static final JOption ENCHANTMENTS_DESCRIPTION_ENABLED = JOption.create("Enchantments.Description.Enabled", true, + "When 'true', adds the enchantment description to item lore under enchantment names.", + "For Display-Mode = 2 description is not shown while you're in Creative gamemode."); + + public static final JOption ENCHANTMENTS_DESCRIPTION_BOOKS_ONLY = JOption.create("Enchantments.Description.Books_Only", + false, + "Sets whether or not only enchanted books will have enchantment descriptions."); + + public static final JOption ENCHANTMENTS_DESCRIPTION_FORMAT = JOption.create("Enchantments.Description.Format", + "&8▸ " + Placeholders.GENERIC_DESCRIPTION, + "Sets the global enchantment description format.").mapReader(Colorizer::apply); + + public static final JOption ENCHANTMENTS_ITEM_CUSTOM_MAX = JOption.create("Enchantments.Item.Max_Custom_Enchants", 3, + "How many of custom enchantments the item can contain at the same time?"); + + public static final JOption ENCHANTMENTS_ITEM_SWORD_ENCHANTS_TO_AXES = JOption.create("Enchantments.Item.Sword_Enchants_To_Axes", true, + "Set this to 'true' to allow Sword enchantments for Axes."); + + public static final JOption ENCHANTMENTS_ITEM_BOW_ENCHANTS_TO_CROSSBOW = JOption.create("Enchantments.Item.Bow_Enchants_To_Crossbows", true, + "Set this to 'true' to allow Bow enchantments for Crossbows."); + + public static final JOption ENCHANTMENTS_ITEM_CHESTPLATE_ENCHANTS_TO_ELYTRA = JOption.create("Enchantments.Item.Chestplate_Enchants_To_Elytra", false, + "Set this to 'true' to allow Chestplate enchantments for Elytras."); + + public static final JOption ENCHANTMENTS_ENTITY_PASSIVE_FOR_MOBS = JOption.create("Enchantments.Entity.Apply_Passive_Enchants_To_Mobs", true, + "When enabled, passive enchantments (permanent potion effects, regeneration, etc.) will be applied to mobs as well.", + "Disable this if you're experiencing performance issues."); + + public static final JOption ENCHANTMENTS_SINGLE_ENCHANT_IN_VILLAGER_BOOKS = JOption.create("Enchantments.Single_Enchant_In_Villager_Books", true, + "Sets whether or not enchanted books in villager trades will have only 1 enchant, vanilla or custom one."); + + private static final JOption> OBTAIN_SETTINGS = new JOption>("Enchantments.Obtaining", + (cfg, path, def) -> Stream.of(ObtainType.values()).collect(Collectors.toMap(k -> k, v -> ObtainSettings.read(cfg, path + "." + v.getPathName()))), + () -> Stream.of(ObtainType.values()).collect(Collectors.toMap(k -> k, v -> new ObtainSettings(true, 4, 80D, 0, 2))), + "Settings for the different ways of obtaining enchantments.") + .setWriter((cfg, path, map) -> map.forEach((type, settings) -> ObtainSettings.write(cfg, path + "." + type.getPathName(), settings))); + + @NotNull + public static Optional getObtainSettings(@NotNull ObtainType obtainType) { + ObtainSettings settings = OBTAIN_SETTINGS.get().get(obtainType); + return settings == null || !settings.isEnabled() ? Optional.empty() : Optional.of(settings); + } + + @NotNull + public static List getDefaultTiers() { + List list = new ArrayList<>(); + list.add(new Tier("common", 1, "Common", Colors2.WHITE, getObtainWeight(50D))); + list.add(new Tier("rare", 2, "Rare", Colors2.GREEN, getObtainWeight(25D))); + list.add(new Tier("unique", 3, "Unique", Colors2.YELLOW, getObtainWeight(15D))); + list.add(new Tier("legendary", 4, "Legendary", Colors2.ORANGE, getObtainWeight(5D))); + return list; + } + + @NotNull + private static Map getObtainWeight(double weight) { + Map map = new HashMap<>(); + for (ObtainType obtainType : ObtainType.values()) { + map.put(obtainType, weight); + } + return map; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/config/Lang.java b/Core/src/main/java/su/nightexpress/excellentenchants/config/Lang.java new file mode 100644 index 0000000..1baeeba --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/config/Lang.java @@ -0,0 +1,30 @@ +package su.nightexpress.excellentenchants.config; + +import su.nexmedia.engine.api.lang.LangKey; +import su.nexmedia.engine.lang.EngineLang; + +import static su.nexmedia.engine.utils.Colors2.*; +import static su.nightexpress.excellentenchants.Placeholders.*; + +public class Lang extends EngineLang { + + public static final LangKey COMMAND_LIST_DESC = LangKey.of("Command.List.Desc", "List of all custom enchantments."); + + public static final LangKey COMMAND_ENCHANT_USAGE = LangKey.of("Command.Enchant.Usage", " [player] [slot]"); + public static final LangKey COMMAND_ENCHANT_DESC = LangKey.of("Command.Enchant.Desc", "Enchants the item in your hand."); + public static final LangKey COMMAND_ENCHANT_DONE_SELF = LangKey.of("Command.Enchant.Done.Self", LIGHT_ORANGE + GENERIC_ITEM + LIGHT_YELLOW + " enchanted with " + GENERIC_ENCHANT + " " + GENERIC_LEVEL + LIGHT_YELLOW + "!"); + public static final LangKey COMMAND_ENCHANT_DONE_OTHERS = LangKey.of("Command.Enchant.Done.Others", LIGHT_ORANGE + PLAYER_DISPLAY_NAME + LIGHT_YELLOW + "'s " + LIGHT_ORANGE + GENERIC_ITEM + LIGHT_YELLOW + " enchanted with " + GENERIC_ENCHANT + " " + GENERIC_LEVEL + LIGHT_YELLOW + "!"); + public static final LangKey COMMAND_ENCHANT_ERROR_NO_ITEM = LangKey.of("Command.Enchant.Error.NoItem", RED + "There is no item to enchant!"); + + public static final LangKey COMMAND_BOOK_USAGE = LangKey.of("Command.Book.Usage", " "); + public static final LangKey COMMAND_BOOK_DESC = LangKey.of("Command.Book.Desc", "Gives custom enchanted book."); + public static final LangKey COMMAND_BOOK_DONE = LangKey.of("Command.Book.Done", LIGHT_YELLOW + "Given " + LIGHT_ORANGE + GENERIC_ENCHANT + LIGHT_YELLOW + " enchanted book to " + LIGHT_ORANGE + PLAYER_DISPLAY_NAME + LIGHT_YELLOW + "."); + + public static final LangKey COMMAND_TIER_BOOK_USAGE = LangKey.of("Command.TierBook.Usage", " "); + public static final LangKey COMMAND_TIER_BOOK_DESC = LangKey.of("Command.TierBook.Desc", "Gives an enchanted book."); + public static final LangKey COMMAND_TIER_BOOK_ERROR = LangKey.of("Command.TierBook.Error", RED + "Invalid tier!"); + public static final LangKey COMMAND_TIER_BOOK_DONE = LangKey.of("Command.TierBook.Done", LIGHT_YELLOW + "Given " + LIGHT_ORANGE + TIER_NAME + LIGHT_YELLOW + " enchanted book to " + LIGHT_ORANGE + PLAYER_DISPLAY_NAME + LIGHT_YELLOW + "."); + + public static final LangKey ERROR_NO_ENCHANT = LangKey.of("Error.NoEnchant", RED + "Invalid enchantment."); + +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/config/ObtainSettings.java b/Core/src/main/java/su/nightexpress/excellentenchants/config/ObtainSettings.java new file mode 100644 index 0000000..d4d737f --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/config/ObtainSettings.java @@ -0,0 +1,67 @@ +package su.nightexpress.excellentenchants.config; + +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.api.config.JYML; + +public class ObtainSettings { + + private final boolean isEnabled; + private final int enchantsTotalMax; + private final double enchantsCustomGenerationChance; + private final int enchantsCustomMin; + private final int enchantsCustomMax; + + public ObtainSettings(boolean isEnabled, + int enchantsTotalMax, double enchantsCustomGenerationChance, + int enchantsCustomMin, int enchantsCustomMax) { + this.isEnabled = isEnabled; + this.enchantsTotalMax = enchantsTotalMax; + this.enchantsCustomGenerationChance = enchantsCustomGenerationChance; + this.enchantsCustomMin = enchantsCustomMin; + this.enchantsCustomMax = enchantsCustomMax; + } + + @NotNull + public static ObtainSettings read(@NotNull JYML cfg, @NotNull String path) { + boolean isEnabled = cfg.getBoolean(path + ".Enabled"); + int enchantsTotalMax = cfg.getInt(path + ".Enchantments.Total_Maximum", 4); + double enchantsCustomGenerationChance = cfg.getDouble(path + ".Enchantments.Custom_Generation_Chance", 50D); + int enchantsCustomMin = cfg.getInt(path + ".Enchantments.Custom_Minimum", 0); + int enchantsCustomMax = cfg.getInt(path + ".Enchantments.Custom_Maximum", 2); + + return new ObtainSettings(isEnabled, enchantsTotalMax, enchantsCustomGenerationChance, enchantsCustomMin, enchantsCustomMax); + } + + public static void write(@NotNull JYML cfg, @NotNull String path, @NotNull ObtainSettings settings) { + cfg.set(path + ".Enabled", settings.isEnabled()); + cfg.set(path + ".Enchantments.Total_Maximum", settings.getEnchantsTotalMax()); + cfg.set(path + ".Enchantments.Custom_Generation_Chance", settings.getEnchantsCustomGenerationChance()); + cfg.set(path + ".Enchantments.Custom_Minimum", settings.getEnchantsCustomMin()); + cfg.set(path + ".Enchantments.Custom_Maximum", settings.getEnchantsCustomMax()); + } + + public boolean isEnabled() { + return isEnabled; + } + + public int getEnchantsTotalMax() { + return enchantsTotalMax; + } + + public double getEnchantsCustomGenerationChance() { + return enchantsCustomGenerationChance; + } + + public int getEnchantsCustomMin() { + return enchantsCustomMin; + } + + public int getEnchantsCustomMax() { + return enchantsCustomMax; + } + + @Override + public String toString() { + return "ObtainSettings{" + "enchantsTotalMax=" + enchantsTotalMax + ", enchantsCustomGenerationChance=" + enchantsCustomGenerationChance + ", enchantsCustomMin=" + enchantsCustomMin + ", enchantsCustomMax=" + enchantsCustomMax + '}'; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/EnchantManager.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/EnchantManager.java new file mode 100644 index 0000000..c1e7ca0 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/EnchantManager.java @@ -0,0 +1,63 @@ +/* + * Decompiled with CFR 0.151. + * + * Could not load the following classes: + * org.jetbrains.annotations.NotNull + * su.nexmedia.engine.NexPlugin + * su.nexmedia.engine.api.manager.AbstractManager + * su.nexmedia.engine.api.manager.EventListener + */ +package su.nightexpress.excellentenchants.enchantment; + +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.api.manager.AbstractManager; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.enchantment.listener.EnchantAnvilListener; +import su.nightexpress.excellentenchants.enchantment.listener.EnchantGenericListener; +import su.nightexpress.excellentenchants.enchantment.menu.EnchantmentsListMenu; +import su.nightexpress.excellentenchants.enchantment.task.ArrowTrailsTask; +import su.nightexpress.excellentenchants.enchantment.task.PassiveEnchantsTask; + +public class EnchantManager +extends AbstractManager { + + public static final String DIR_ENCHANTS = "/enchants/"; + private EnchantmentsListMenu enchantmentsListMenu; + private ArrowTrailsTask arrowTrailsTask; + private PassiveEnchantsTask passiveEnchantsTask; + + public EnchantManager(@NotNull ExcellentEnchants plugin) { + super(plugin); + } + + protected void onLoad() { + this.enchantmentsListMenu = new EnchantmentsListMenu(this.plugin); + this.addListener(new EnchantGenericListener(this)); + this.addListener(new EnchantAnvilListener(this.plugin)); + this.arrowTrailsTask = new ArrowTrailsTask(this.plugin); + this.arrowTrailsTask.start(); + this.passiveEnchantsTask = new PassiveEnchantsTask(this.plugin); + this.passiveEnchantsTask.start(); + } + + protected void onShutdown() { + if (this.enchantmentsListMenu != null) { + this.enchantmentsListMenu.clear(); + this.enchantmentsListMenu = null; + } + if (this.arrowTrailsTask != null) { + this.arrowTrailsTask.stop(); + this.arrowTrailsTask = null; + } + if (this.passiveEnchantsTask != null) { + this.passiveEnchantsTask.stop(); + this.passiveEnchantsTask = null; + } + } + + @NotNull + public EnchantmentsListMenu getEnchantsListGUI() { + return this.enchantmentsListMenu; + } +} + diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/EnchantPopulator.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/EnchantPopulator.java new file mode 100644 index 0000000..07c4653 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/EnchantPopulator.java @@ -0,0 +1,232 @@ +package su.nightexpress.excellentenchants.enchantment; + +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import su.nexmedia.engine.utils.random.Rnd; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.config.Config; +import su.nightexpress.excellentenchants.config.ObtainSettings; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.registry.EnchantRegistry; +import su.nightexpress.excellentenchants.api.enchantment.ObtainType; +import su.nightexpress.excellentenchants.enchantment.util.EnchantUtils; +import su.nightexpress.excellentenchants.tier.Tier; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class EnchantPopulator { + + private final ExcellentEnchants plugin; + private final ObtainType obtainType; + private final ItemStack item; + private final Map> candidates; + private final Map population; + + private World world; + private Function levelGenerator; + + public EnchantPopulator(@NotNull ExcellentEnchants plugin, @NotNull ItemStack item, @NotNull ObtainType obtainType) { + this.plugin = plugin; + this.item = item; + this.obtainType = obtainType; + this.candidates = new HashMap<>(); + this.population = new HashMap<>(); + this.withLevelGenerator(enchant -> enchant.generateLevel(this.getObtainType())); + + this.fillDefaultCandidates(); + } + + @NotNull + public EnchantPopulator withWorld(@NotNull World world) { + this.world = world; + return this; + } + + @NotNull + public EnchantPopulator withLevelGenerator(@NotNull Function levelGenerator) { + this.levelGenerator = levelGenerator; + return this; + } + + @NotNull + public EnchantPopulator withDefaultPopulation(@NotNull Map population) { + this.getPopulation().putAll(population); + return this; + } + + private void fillDefaultCandidates() { + this.plugin.getTierManager().getTiers().forEach(tier -> { + Set enchants = EnchantRegistry.getOfTier(tier); + + enchants.removeIf(enchant -> { + return !enchant.isObtainable(this.getObtainType()) || (!enchant.getBackend().canEnchantItem(this.getItem()) && !EnchantUtils.isBook(this.getItem())); + }); + + this.candidates.put(tier, enchants); + }); + } + + public boolean isEmpty() { + return this.getCandidates().isEmpty() || this.getCandidates().values().stream().allMatch(Set::isEmpty); + } + + public boolean isEmpty(@NotNull Tier tier) { + return this.getCandidates(tier).isEmpty(); + } + + public void purge(@NotNull Tier tier) { + this.getCandidates().remove(tier); + } + + public void purge(@NotNull Tier tier, @NotNull ExcellentEnchant enchant) { + this.getCandidates(tier).remove(enchant); + this.getCandidates().keySet().removeIf(this::isEmpty); + } + + @NotNull + public ItemStack getItem() { + return item; + } + + @NotNull + public ObtainType getObtainType() { + return obtainType; + } + + @Nullable + public World getWorld() { + return world; + } + + @NotNull + public Function getLevelGenerator() { + return levelGenerator; + } + + @NotNull + public Map> getCandidates() { + return this.candidates; + } + + @NotNull + public Set getCandidates(@NotNull Tier tier) { + return this.getCandidates().getOrDefault(tier, new HashSet<>()); + } + + @NotNull + public Map getPopulation() { + return this.population; + } + + @Nullable + public Tier getTierByChance() { + Map map = this.getCandidates().keySet().stream() + .filter(tier -> tier.getChance(this.getObtainType()) > 0D) + .collect(Collectors.toMap(k -> k, v -> v.getChance(this.getObtainType()), (o, n) -> n, HashMap::new)); + if (map.isEmpty()) return null; + + return Rnd.getByWeight(map); + } + + @Nullable + public ExcellentEnchant getEnchantByChance(@NotNull Tier tier) { + Map map = this.getCandidates(tier).stream() + .collect(Collectors.toMap(k -> k, v -> v.getObtainChance(this.getObtainType()))); + return map.isEmpty() ? null : Rnd.getByWeight(map); + } + + @NotNull + public Map createPopulation() { + Map population = this.getPopulation(); + + ObtainSettings settings = Config.getObtainSettings(this.getObtainType()).orElse(null); + if (settings == null || !Rnd.chance(settings.getEnchantsCustomGenerationChance())) return population; + + int enchantsLimit = settings.getEnchantsTotalMax(); + int enchantsRolled = Rnd.get(settings.getEnchantsCustomMin(), settings.getEnchantsCustomMax()); + + // Try to populate as many as possible. + while (!this.isEmpty() && enchantsRolled > 0) { + // Limit reached. + if (population.size() >= enchantsLimit) break; + + Tier tier = this.getTierByChance(); + if (tier == null) break; // no tiers left. + + ExcellentEnchant enchant = this.getEnchantByChance(tier); + // Remove entire tier if no enchants can be selected. + if (enchant == null) { + this.purge(tier); + continue; + } + + // Remove disabled world enchants. + if (world != null && enchant.isDisabledInWorld(world)) { + this.purge(tier, enchant); + continue; + } + + // Remove conflicting enchants. + if (population.keySet().stream().anyMatch(has -> has.conflictsWith(enchant.getBackend()) || enchant.getBackend().conflictsWith(has))) { + this.purge(tier, enchant); + continue; + } + + // Level generation failed. + int level = this.getLevelGenerator().apply(enchant); + if (level < enchant.getStartLevel()) { + this.purge(tier, enchant); + continue; + } + + // All good! + this.purge(tier, enchant); + population.put(enchant.getBackend(), level); + enchantsRolled--; + } + + return population; + } + + public boolean populate() { + ItemStack item = this.getItem(); + AtomicBoolean status = new AtomicBoolean(false); + + var population = this.getPopulation().isEmpty() ? this.createPopulation() : this.getPopulation(); + + boolean singleVillagerBook = this.getObtainType() == ObtainType.VILLAGER + && item.getType() == Material.ENCHANTED_BOOK + && Config.ENCHANTMENTS_SINGLE_ENCHANT_IN_VILLAGER_BOOKS.get(); + + if (singleVillagerBook) { + if (!population.isEmpty()) { + EnchantUtils.getAll(item).keySet().forEach(enchantment -> EnchantUtils.remove(item, enchantment)); + } + while (population.size() > 1) { + population.remove(Rnd.get(population.keySet())); + } + } + + population.forEach((enchantment, level) -> { + if (EnchantUtils.add(item, enchantment, level, false)) { + status.set(true); + } + }); + + if (status.get()) { + EnchantUtils.updateDisplay(item); + } + + return status.get(); + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/config/EnchantDefaults.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/config/EnchantDefaults.java new file mode 100644 index 0000000..ba53052 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/config/EnchantDefaults.java @@ -0,0 +1,361 @@ +package su.nightexpress.excellentenchants.enchantment.config; + +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import su.nexmedia.engine.api.config.JOption; +import su.nexmedia.engine.api.config.JYML; +import su.nexmedia.engine.utils.Colorizer; +import su.nexmedia.engine.utils.StringUtil; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.ExcellentEnchantsAPI; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.config.Config; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.api.enchantment.ObtainType; +import su.nightexpress.excellentenchants.tier.Tier; +import su.nightexpress.excellentenchants.tier.TierManager; + +import java.util.*; + +public class EnchantDefaults { + + private final Map obtainChance; + private final Map obtainLevelCap; + + private String displayName; + private Tier tier; + private List description; + private boolean hiddenFromList; + private boolean isTreasure; + private boolean tradeable; + private boolean discoverable; + private int levelMin; + private int levelMax; + private int maxMergeLevel; + private EnchantScaler levelByEnchantCost; + private EnchantScaler anvilMergeCost; + private Set conflicts; + private boolean visualEffects; + + private boolean chargesEnabled; + private boolean chargesCustomFuel; + private EnchantScaler chargesMax; + private EnchantScaler chargesConsumeAmount; + private EnchantScaler chargesRechargeAmount; + private ItemStack chargesFuel; + + public EnchantDefaults(@NotNull ExcellentEnchant enchant) { + this.setDisplayName(StringUtil.capitalizeUnderscored(enchant.getId())); + this.setTier(0.1); + this.setDescription(new ArrayList<>()); + this.setHiddenFromList(false); + this.setTreasure(false); + this.setTradeable(true); + this.setDiscoverable(true); + this.setLevelMin(1); + this.setLevelMax(3); + this.setMaxMergeLevel(-1); + this.setConflicts(new HashSet<>()); + this.setVisualEffects(true); + this.obtainChance = new HashMap<>(); + this.obtainLevelCap = new HashMap<>(); + } + + public void load(@NotNull ExcellentEnchant enchant) { + ExcellentEnchants plugin = ExcellentEnchantsAPI.PLUGIN; + JYML cfg = enchant.getConfig(); + + this.setDisplayName(JOption.create("Name", this.getDisplayName(), + "Enchantment display name. It will be shown in item lore.").read(cfg)); + + Tier tier = plugin.getTierManager().getTierById(JOption.create("Tier", this.getTier().getId(), + "Enchantment tier. Must be a valid tier identifier from the '" + TierManager.FILE_NAME + "'.").read(cfg)); + this.setTier(tier == null ? plugin.getTierManager().getMostCommon() : tier); + //this.getTier().getEnchants().add(enchant); + + this.setDescription(JOption.create("Description", this.getDescription(), + "Enchantment description. It will be shown in item lore under enchantment name.", + "You can use 'Enchantment' placeholders: " + Placeholders.URL_PLACEHOLDERS) + .read(cfg)); + + this.setHiddenFromList(JOption.create("Hide_From_List", false, + "Sets whether or not this enchantment will be hidden from Enchants GUI.").read(cfg)); + + this.setTreasure(JOption.create("Is_Treasure", this.isTreasure(), + "Sets whether this enchantment is a treasure enchantment.", + "Treasure enchantments can only be received via looting, trading, or fishing.").read(cfg)); + + if (Config.ENCHANTMENTS_INTERNAL_HANDLER.get()) { + this.setTradeable(JOption.create("Tradeable", this.isTradeable(), + "Sets whether or not this enchantment can be populated in villager trades.").read(cfg)); + + this.setDiscoverable(JOption.create("Discoverable", this.isTradeable(), + "Sets whether or not this enchantment can be populated in enchanting table.").read(cfg)); + } + else { + this.setTradeable(false); + this.setDiscoverable(false); + } + + this.setLevelMin(JOption.create("Level.Min", this.getLevelMin(), + "Sets the minimal (start) enchantment level. Can not be less than 1.").read(cfg)); + + this.setLevelMax(JOption.create("Level.Max", this.getLevelMax(), + "Sets the maximal enchantment level. Can not be less than min. level.", + "Note: While you can 'bypass' this value by enchant commands, all level-dependant enchantment", + "settings will have a limit up to this setting.").read(cfg)); + + this.setMaxMergeLevel(JOption.create("Anvil.Max_Merge_Level", this.getMaxMergeLevel(), + "Sets max. enchantment level that can be obtained by combining 2 items with this enchantment.", + "Set this to '-1' to remove merge limit and just use 'Max Level' instead." + ).read(cfg)); + + this.setLevelByEnchantCost(EnchantScaler.read(enchant, ObtainType.ENCHANTING.getPathName() + ".Level_By_Exp_Cost", + (int)(30D / this.levelMax) + " * " + Placeholders.ENCHANTMENT_LEVEL, + "Sets how much XP levels must be used in enchanting table to obtain this enchantment.", + "With a default formula '9 * %enchantment_level%' it will be [9, 18, 27] XP levels for [1, 2, 3] enchantment levels.")); + + this.setAnvilMergeCost(EnchantScaler.read(enchant, "Anvil.Merge_Cost", Placeholders.ENCHANTMENT_LEVEL, + "Sets how much XP levels will be added to the anvil cost when combining custom enchantments.")); + + for (ObtainType obtainType : ObtainType.values()) { + double obtainChance = JOption.create(obtainType.getPathName() + ".Chance", 50D, + "Chance for this enchantment to be obtained via " + obtainType.getPathName()).read(cfg); + this.getObtainChance().put(obtainType, obtainChance); + + int levelMin = JOption.create(obtainType.getPathName() + ".Level.Min", -1, + "Minimal level when obtained via " + obtainType.getPathName(), + "Can not be less than enchantment min. level. Set -1 to use enchantment min. level.").read(cfg); + int levelMax = JOption.create(obtainType.getPathName() + ".Level.Max", -1, + "Maximal level when obtained via " + obtainType.getPathName(), + "Can not be greater than enchantment max. level. Set -1 to use enchantment max. level.").read(cfg); + this.getObtainLevelCap().put(obtainType, new int[]{levelMin, levelMax}); + } + + + this.setConflicts(JOption.create("Conflicts", this.getConflicts(), + "A list of conflicting enchantment names.", + "Conflicting enchantments can not be combined on anvils and obtained together on the same item.").read(cfg)); + + this.setVisualEffects(JOption.create("Settings.Visual_Effects", this.isVisualEffects(), + "Enables/Disables enchantment visual effects, such as particles.").read(cfg)); + + + if (Config.ENCHANTMENTS_CHARGES_ENABLED.get()) { + this.setChargesEnabled(JOption.create("Settings.Charges.Enabled", this.isChargesEnabled(), + "When 'true' enables the Charges system for this enchantment.", + "When enchanted the first time on enchanting table, it will have maximum charges amount.").read(cfg)); + + this.setChargesCustomFuel(JOption.create("Settings.Charges.Custom_Fuel", this.isChargesCustomFuel(), + "When 'true' uses different (non-default) fuel item (from the 'Fuel_Item' setting) to recharge.").read(cfg)); + + this.setChargesMax(EnchantScaler.read(enchant, "Settings.Charges.Maximum", "100", + "Maximum amount of charges for the enchantment.")); + + this.setChargesConsumeAmount(EnchantScaler.read(enchant, "Settings.Charges.Consume_Amount", "1", + "How many charges will be consumed when enchantment is triggered?")); + + this.setChargesRechargeAmount(EnchantScaler.read(enchant, "Settings.Charges.Recharge_Amount", "25", + "How many charges will be restored when using 'Fuel Item' in anvil?")); + + this.setChargesFuel(JOption.create("Settings.Charges.Fuel_Item", new ItemStack(Material.LAPIS_LAZULI), + "An item, that will be used to restore enchantment charges on anvils.", + "Item Options:" + Placeholders.URL_ENGINE_ITEMS) + .setWriter(JYML::setItem).read(cfg)); + } + } + + @NotNull + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(@NotNull String displayName) { + this.displayName = Colorizer.apply(displayName); + } + + @NotNull + public Tier getTier() { + return tier; + } + + public void setTier(double rarity) { + this.setTier(ExcellentEnchantsAPI.getTierManager().getByRarityModifier(rarity)); + } + + public void setTier(@NotNull Tier tier) { + this.tier = tier; + } + + public void setDescription(@NotNull String... description) { + this.setDescription(Arrays.asList(description)); + } + + public void setDescription(@NotNull List description) { + this.description = Colorizer.apply(description); + } + + @NotNull + public List getDescription() { + return description; + } + + public boolean isHiddenFromList() { + return hiddenFromList; + } + + public void setHiddenFromList(boolean hiddenFromList) { + this.hiddenFromList = hiddenFromList; + } + + public boolean isTreasure() { + return isTreasure; + } + + public void setTreasure(boolean treasure) { + isTreasure = treasure; + } + + public boolean isTradeable() { + return tradeable; + } + + public void setTradeable(boolean tradeable) { + this.tradeable = tradeable; + } + + public boolean isDiscoverable() { + return discoverable; + } + + public void setDiscoverable(boolean discoverable) { + this.discoverable = discoverable; + } + + public void setLevelMin(int levelMin) { + this.levelMin = Math.max(1, levelMin); + } + + public int getLevelMin() { + return levelMin; + } + + public void setLevelMax(int levelMax) { + this.levelMax = Math.max(1, levelMax); + } + + public int getLevelMax() { + return levelMax; + } + + public int getMaxMergeLevel() { + return this.maxMergeLevel; + } + + public void setMaxMergeLevel(int maxMergeLevel) { + this.maxMergeLevel = Math.min(this.getLevelMax(), maxMergeLevel); + } + + @NotNull + public EnchantScaler getLevelByEnchantCost() { + return levelByEnchantCost; + } + + public void setLevelByEnchantCost(@NotNull EnchantScaler levelByEnchantCost) { + this.levelByEnchantCost = levelByEnchantCost; + } + + @NotNull + public EnchantScaler getAnvilMergeCost() { + return anvilMergeCost; + } + + public void setAnvilMergeCost(@NotNull EnchantScaler anvilMergeCost) { + this.anvilMergeCost = anvilMergeCost; + } + + @NotNull + public Map getObtainChance() { + return obtainChance; + } + + @NotNull + public Map getObtainLevelCap() { + return obtainLevelCap; + } + + public void setConflicts(@NotNull String... conflicts) { + this.setConflicts(new HashSet<>(Arrays.asList(conflicts))); + } + + public void setConflicts(@NotNull Set conflicts) { + this.conflicts = conflicts; + } + + @NotNull + public Set getConflicts() { + return conflicts; + } + + public boolean isVisualEffects() { + return visualEffects; + } + + public void setVisualEffects(boolean visualEffects) { + this.visualEffects = visualEffects; + } + + public boolean isChargesEnabled() { + return chargesEnabled; + } + + public void setChargesEnabled(boolean chargesEnabled) { + this.chargesEnabled = chargesEnabled; + } + + public boolean isChargesCustomFuel() { + return chargesCustomFuel; + } + + public void setChargesCustomFuel(boolean chargesCustomFuel) { + this.chargesCustomFuel = chargesCustomFuel; + } + + @NotNull + public EnchantScaler getChargesMax() { + return chargesMax; + } + + public void setChargesMax(@NotNull EnchantScaler chargesMax) { + this.chargesMax = chargesMax; + } + + @Nullable + public ItemStack getChargesFuel() { + return chargesFuel; + } + + public void setChargesFuel(@Nullable ItemStack chargesFuel) { + this.chargesFuel = chargesFuel; + } + + @NotNull + public EnchantScaler getChargesConsumeAmount() { + return chargesConsumeAmount; + } + + public void setChargesConsumeAmount(@NotNull EnchantScaler chargesConsumeAmount) { + this.chargesConsumeAmount = chargesConsumeAmount; + } + + @NotNull + public EnchantScaler getChargesRechargeAmount() { + return chargesRechargeAmount; + } + + public void setChargesRechargeAmount(@NotNull EnchantScaler chargesRechargeAmount) { + this.chargesRechargeAmount = chargesRechargeAmount; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/config/EnchantScaler.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/config/EnchantScaler.java new file mode 100644 index 0000000..99c0ac6 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/config/EnchantScaler.java @@ -0,0 +1,80 @@ +package su.nightexpress.excellentenchants.enchantment.config; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import su.nexmedia.engine.api.config.JYML; +import su.nexmedia.engine.utils.Evaluator; +import su.nexmedia.engine.utils.StringUtil; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; + +import java.util.*; + +public class EnchantScaler { + + public EnchantScaler(@NotNull ExcellentEnchant enchant, @NotNull String path) { + this(enchant.getConfig(), path, Placeholders.ENCHANTMENT_LEVEL, enchant.getStartLevel(), enchant.getMaxLevel()); + } + + @NotNull + public static EnchantScaler read(@NotNull ExcellentEnchant enchant, @NotNull String path, @NotNull String def, @Nullable String... comments) { + enchant.getConfig().addMissing(path, def); + if (comments != null) { + List list = new ArrayList<>(Arrays.asList(comments)); + list.add("You can use formulas/expressions here: " + Placeholders.URL_ENGINE_SCALER); + list.add("Level placeholder: " + Placeholders.ENCHANTMENT_LEVEL); + enchant.getConfig().setComments(path, list); + } + return new EnchantScaler(enchant, path); + } + + private final int levelMin; + private final int levelMax; + private final TreeMap values; + + public EnchantScaler(@NotNull JYML cfg, @NotNull String path, @NotNull String levelPlaceholder, int levelMin, int levelMax) { + this.levelMin = levelMin; + this.levelMax = levelMax; + this.values = new TreeMap<>(); + + // Load different values for each object level. + Set lvlKeys = cfg.getSection(path); + if (!lvlKeys.isEmpty()) { + for (String sLvl : lvlKeys) { + int eLvl = StringUtil.getInteger(sLvl, 0); + if (eLvl < this.getLevelMin() || eLvl > this.getLevelMax()) continue; + + String formula = cfg.getString(path + "." + sLvl, "0").replace(levelPlaceholder, sLvl); + values.put(eLvl, Evaluator.evaluate(formula)); + } + return; + } + + // Load the single formula for all object levels. + for (int lvl = this.getLevelMin(); lvl < (this.getLevelMax() + 1); lvl++) { + String sLvl = String.valueOf(lvl); + String exChance = cfg.getString(path, "").replace(levelPlaceholder, sLvl); + if (exChance.isEmpty()) continue; + + values.put(lvl, Evaluator.evaluate(exChance)); + } + } + + public int getLevelMin() { + return this.levelMin; + } + + public int getLevelMax() { + return this.levelMax; + } + + @NotNull + public TreeMap getValues() { + return this.values; + } + + public double getValue(int level) { + Map.Entry en = this.values.floorEntry(level); + return en != null ? en.getValue() : 0D; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/ExcellentEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/ExcellentEnchant.java new file mode 100644 index 0000000..bcf30da --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/ExcellentEnchant.java @@ -0,0 +1,459 @@ +package su.nightexpress.excellentenchants.enchantment.impl; + +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.World; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.LivingEntity; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.api.config.JYML; +import su.nexmedia.engine.api.manager.EventListener; +import su.nexmedia.engine.api.placeholder.PlaceholderMap; +import su.nexmedia.engine.lang.LangManager; +import su.nexmedia.engine.utils.ItemUtil; +import su.nexmedia.engine.utils.NumberUtil; +import su.nexmedia.engine.utils.random.Rnd; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.IEnchantment; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.meta.Periodic; +import su.nightexpress.excellentenchants.api.enchantment.meta.Potioned; +import su.nightexpress.excellentenchants.config.Config; +import su.nightexpress.excellentenchants.enchantment.EnchantManager; +import su.nightexpress.excellentenchants.enchantment.config.EnchantDefaults; +import su.nightexpress.excellentenchants.api.enchantment.ItemCategory; +import su.nightexpress.excellentenchants.api.enchantment.ObtainType; +import su.nightexpress.excellentenchants.enchantment.util.EnchantUtils; +import su.nightexpress.excellentenchants.tier.Tier; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Stream; + +public abstract class ExcellentEnchant /*extends Enchantment*/ implements IEnchantment { + + protected final ExcellentEnchants plugin; + protected final JYML cfg; + protected final String id; + protected final EnchantDefaults defaults; + protected final NamespacedKey chargesKey; + protected final Map placeholdersMap; + + public ExcellentEnchant(@NotNull ExcellentEnchants plugin, @NotNull String id) { + //super(NamespacedKey.minecraft(id.toLowerCase())); + this.plugin = plugin; + this.id = id.toLowerCase();//this.getKey().getKey(); + this.cfg = new JYML(plugin.getDataFolder() + EnchantManager.DIR_ENCHANTS, id + ".yml"); + this.chargesKey = new NamespacedKey(plugin, this.getId() + ".charges"); + this.defaults = new EnchantDefaults(this); + this.placeholdersMap = new HashMap<>(); + } + + @NotNull + @Override + public NamespacedKey getKey() { + return EnchantUtils.createKey(this.getId()); + } + + public void loadSettings() { + this.cfg.reload(); + this.placeholdersMap.clear(); + + this.getDefaults().load(this); + + for (int i = this.getStartLevel(); i < this.getMaxLevel() + 1; i++) { + int level = i; + + PlaceholderMap map = new PlaceholderMap() + .add(Placeholders.ENCHANTMENT_DESCRIPTION, () -> String.join("\n", this.getDescription())) + .add(Placeholders.ENCHANTMENT_ID, this::getId) + .add(Placeholders.ENCHANTMENT_NAME, this::getDisplayName) + .add(Placeholders.ENCHANTMENT_NAME_FORMATTED, () -> this.getNameFormatted(level)) + .add(Placeholders.ENCHANTMENT_LEVEL, () -> NumberUtil.toRoman(level)) + .add(Placeholders.ENCHANTMENT_LEVEL_MIN, () -> String.valueOf(this.getStartLevel())) + .add(Placeholders.ENCHANTMENT_LEVEL_MAX, () -> String.valueOf(this.getMaxLevel())) + .add(Placeholders.ENCHANTMENT_TIER, () -> this.getTier().getName()) + .add(Placeholders.ENCHANTMENT_TIER_COLOR, () -> this.getTier().getColor()) + .add(Placeholders.ENCHANTMENT_FIT_ITEM_TYPES, () -> { + if (this.getFitItemTypes().length == 0) return plugin.getLangManager().getEnum(this.getCategory()); + + return String.join(", ", Stream.of(this.getFitItemTypes()).map(type -> plugin.getLangManager().getEnum(type)).toList()); + }) + .add(Placeholders.ENCHANTMENT_OBTAIN_CHANCE_ENCHANTING, () -> NumberUtil.format(this.getObtainChance(ObtainType.ENCHANTING))) + .add(Placeholders.ENCHANTMENT_OBTAIN_CHANCE_VILLAGER, () -> NumberUtil.format(this.getObtainChance(ObtainType.VILLAGER))) + .add(Placeholders.ENCHANTMENT_OBTAIN_CHANCE_LOOT_GENERATION, () -> NumberUtil.format(this.getObtainChance(ObtainType.LOOT_GENERATION))) + .add(Placeholders.ENCHANTMENT_OBTAIN_CHANCE_FISHING, () -> NumberUtil.format(this.getObtainChance(ObtainType.FISHING))) + .add(Placeholders.ENCHANTMENT_OBTAIN_CHANCE_MOB_SPAWNING, () -> NumberUtil.format(this.getObtainChance(ObtainType.MOB_SPAWNING))) + .add(Placeholders.ENCHANTMENT_CHARGES_MAX_AMOUNT, () -> NumberUtil.format(this.getChargesMax(level))) + .add(Placeholders.ENCHANTMENT_CHARGES_CONSUME_AMOUNT, () -> NumberUtil.format(this.getChargesConsumeAmount(level))) + .add(Placeholders.ENCHANTMENT_CHARGES_RECHARGE_AMOUNT, () -> NumberUtil.format(this.getChargesRechargeAmount(level))) + .add(Placeholders.ENCHANTMENT_CHARGES_FUEL_ITEM, () -> ItemUtil.getItemName(this.getChargesFuel())); + + if (this instanceof Chanced chanced) { + map.add(Placeholders.ENCHANTMENT_CHANCE, () -> NumberUtil.format(chanced.getTriggerChance(level))); + } + if (this instanceof Periodic periodic) { + map.add(Placeholders.ENCHANTMENT_INTERVAL, () -> NumberUtil.format(periodic.getInterval() / 20D)); + } + if (this instanceof Potioned potioned) { + map.add(Placeholders.ENCHANTMENT_POTION_LEVEL, () -> NumberUtil.toRoman(potioned.getEffectAmplifier(level))); + map.add(Placeholders.ENCHANTMENT_POTION_DURATION, () -> NumberUtil.format(potioned.getEffectDuration(level) / 20D)); + map.add(Placeholders.ENCHANTMENT_POTION_TYPE, () -> LangManager.getPotionType(potioned.getEffectType())); + } + map.add(this.getTier().getPlaceholders()); + + this.placeholdersMap.put(level, map); + } + } + + @NotNull + public PlaceholderMap getPlaceholders(int level) { + if (level > this.getMaxLevel()) level = this.getMaxLevel(); + if (level < this.getStartLevel()) level = this.getStartLevel(); + + return this.placeholdersMap.get(level); + } + + public void addPlaceholder(@NotNull String key, @NotNull Function replacer) { + for (int level = this.getStartLevel(); level < this.getMaxLevel() + 1; level++) { + this.getPlaceholders(level).add(key, replacer.apply(level)); + } + } + + public void registerListeners() { + if (this instanceof EventListener listener) { + this.plugin.getPluginManager().registerEvents(listener, plugin); + } + } + + @NotNull + public ItemCategory[] getFitItemTypes() { + //FitItemType itemType = FitItemType.getByEnchantmentTarget(this.getCategory()); + //return new FitItemType[]{itemType}; + + return new ItemCategory[0]; + } + + @Override + public EquipmentSlot[] getSlots() { + return switch (this.getCategory()) { + case BOW, CROSSBOW, TRIDENT, FISHING_ROD, WEAPON, TOOL -> new EquipmentSlot[]{EquipmentSlot.HAND}; + case ARMOR_HEAD -> new EquipmentSlot[]{EquipmentSlot.HEAD}; + case ARMOR_TORSO -> new EquipmentSlot[]{EquipmentSlot.CHEST}; + case ARMOR_LEGS -> new EquipmentSlot[]{EquipmentSlot.LEGS}; + case ARMOR_FEET -> new EquipmentSlot[]{EquipmentSlot.FEET}; + case ARMOR, WEARABLE -> new EquipmentSlot[]{EquipmentSlot.HEAD, EquipmentSlot.CHEST, EquipmentSlot.LEGS, EquipmentSlot.FEET}; + case BREAKABLE -> new EquipmentSlot[]{EquipmentSlot.HAND, EquipmentSlot.HEAD, EquipmentSlot.CHEST, EquipmentSlot.LEGS, EquipmentSlot.FEET}; + case VANISHABLE -> EquipmentSlot.values(); + default -> throw new IllegalStateException("Unexpected value: " + this.getCategory()); + }; + + /*Set slots = new HashSet<>(); + + for (FitItemType itemType : this.getFitItemTypes()) { + slots.addAll(Arrays.asList(itemType.getSlots())); + } + + return slots.toArray(new EquipmentSlot[0]);*/ + } + + public boolean isDisabledInWorld(@NotNull World world) { + Set disabled = Config.ENCHANTMENTS_DISABLED_IN_WORLDS.get().getOrDefault(world.getName(), Collections.emptySet()); + return disabled.contains(this.getKey().getKey()) || disabled.contains(Placeholders.WILDCARD); + } + + @Override + public boolean isAvailableToUse(@NotNull LivingEntity entity) { + return !this.isDisabledInWorld(entity.getWorld()); + } + + @NotNull + public JYML getConfig() { + return this.cfg; + } + + @NotNull + public String getId() { + return this.id; + } + + @NotNull + public EnchantDefaults getDefaults() { + return defaults; + } + + /*@NotNull + @Override + public String getName() { + return getId().toUpperCase(); + }*/ + + @NotNull + public String getDisplayName() { + return this.getDefaults().getDisplayName(); + } + + @NotNull + public String getNameFormatted(int level) { + String name = this.getTier().getColor() + this.getDisplayName(); + if (level > 1 || this.getMaxLevel() > 1) { + name += " " + NumberUtil.toRoman(level); + } + return name; + } + + @NotNull + public String getNameFormatted(int level, int charges) { + if (!this.isChargesEnabled() || charges < 0) return this.getNameFormatted(level); + + int chargesMax = this.getChargesMax(level); + int percent = (int) Math.ceil((double) charges / (double) chargesMax * 100D); + Map.Entry entry = Config.ENCHANTMENTS_CHARGES_FORMAT.get().floorEntry(percent); + if (entry == null) return this.getNameFormatted(level); + + String format = entry.getValue().replace(Placeholders.GENERIC_AMOUNT, String.valueOf(charges)); + return this.getNameFormatted(level) + " " + format; + } + + @NotNull + public List getDescription() { + return this.getDefaults().getDescription(); + } + + @NotNull + public List getDescription(int level) { + List description = new ArrayList<>(this.getDescription()); + description.replaceAll(this.getPlaceholders(level).replacer()); + return description; + } + + @NotNull + public List formatDescription() { + return new ArrayList<>(this.getDescription().stream() + .map(line -> Config.ENCHANTMENTS_DESCRIPTION_FORMAT.get().replace(Placeholders.GENERIC_DESCRIPTION, line)) + .toList()); + } + + @NotNull + public List formatDescription(int level) { + return new ArrayList<>(this.getDescription(level).stream() + .map(line -> Config.ENCHANTMENTS_DESCRIPTION_FORMAT.get().replace(Placeholders.GENERIC_DESCRIPTION, line)) + .toList()); + } + + public boolean hasConflicts() { + return !this.getConflicts().isEmpty(); + } + + @NotNull + public Set getConflicts() { + return this.getDefaults().getConflicts(); + } + + @NotNull + public Tier getTier() { + return this.getDefaults().getTier(); + } + + @Override + public int getMaxLevel() { + return this.getDefaults().getLevelMax(); + } + + @Override + public int getStartLevel() { + return this.getDefaults().getLevelMin(); + } + + public int getMaxMergeLevel() { + return this.getDefaults().getMaxMergeLevel() < 0 ? this.getMaxLevel() : this.getDefaults().getMaxMergeLevel(); + } + + public int getLevelByEnchantCost(int expLevel) { + int get = this.getDefaults().getLevelByEnchantCost().getValues().entrySet().stream() + .filter(en -> expLevel >= en.getValue().intValue()).max(Comparator.comparingInt(Map.Entry::getKey)) + .map(Map.Entry::getKey).orElse(0); + + return get != 0 ? this.fineLevel(get, ObtainType.ENCHANTING) : 0; + } + + public boolean isObtainable(@NotNull ObtainType obtainType) { + if (obtainType == ObtainType.ENCHANTING && (this.isTreasure() || this.isCurse())) return false; + + return this.getObtainChance(obtainType) > 0D; + } + + public double getObtainChance(@NotNull ObtainType obtainType) { + return this.getDefaults().getObtainChance().getOrDefault(obtainType, 0D); + } + + public int getObtainLevelMin(@NotNull ObtainType obtainType) { + return this.getDefaults().getObtainLevelCap().getOrDefault(obtainType, new int[]{-1, -1})[0]; + } + + public int getObtainLevelMax(@NotNull ObtainType obtainType) { + return this.getDefaults().getObtainLevelCap().getOrDefault(obtainType, new int[]{-1, -1})[1]; + } + + public int fineLevel(int level, @NotNull ObtainType obtainType) { + int levelCapMin = this.getObtainLevelMin(obtainType); + int levelCapMax = this.getObtainLevelMax(obtainType); + + if (levelCapMin > 0 && level < levelCapMin) level = levelCapMin; + if (levelCapMax > 0 && level > levelCapMax) level = levelCapMax; + + return level; + } + + public int generateLevel() { + return Rnd.get(this.getStartLevel(), this.getMaxLevel()); + } + + public int generateLevel(@NotNull ObtainType obtainType) { + int levelCapMin = this.getObtainLevelMin(obtainType); + int levelCapMax = this.getObtainLevelMax(obtainType); + + if (levelCapMin <= 0 || levelCapMin < this.getStartLevel()) levelCapMin = this.getStartLevel(); + if (levelCapMax <= 0 || levelCapMax > this.getMaxLevel()) levelCapMax = this.getMaxLevel(); + + return Rnd.get(levelCapMin, levelCapMax); + } + + public int getAnvilMergeCost(int level) { + return (int) this.getDefaults().getAnvilMergeCost().getValue(level); + } + + /*@Override + @Deprecated + public final boolean conflictsWith(@NotNull Enchantment enchantment) { + return this.getConflicts().contains(enchantment.getKey().getKey()); + }*/ + + @Deprecated + public Enchantment getBackend() { + return EnchantUtils.getEnchantment(this.getKey()); + } + + @Override + public final boolean checkEnchantCategory(@NotNull ItemStack item) { + EnchantmentTarget category = this.getCategory(); + + if (category == EnchantmentTarget.WEAPON && ItemUtil.isAxe(item)) { + return Config.ENCHANTMENTS_ITEM_SWORD_ENCHANTS_TO_AXES.get(); + } + if (category == EnchantmentTarget.BOW && item.getType() == Material.CROSSBOW) { + return Config.ENCHANTMENTS_ITEM_BOW_ENCHANTS_TO_CROSSBOW.get(); + } + if ((category == EnchantmentTarget.ARMOR || category == EnchantmentTarget.ARMOR_TORSO) && item.getType() == Material.ELYTRA) { + return Config.ENCHANTMENTS_ITEM_CHESTPLATE_ENCHANTS_TO_ELYTRA.get(); + } + return false; + } + + @Override + public boolean checkItemCategory(@NotNull ItemStack item) { + ItemCategory[] itemCategories = this.getFitItemTypes(); + if (itemCategories.length == 0) return false; + + return Stream.of(itemCategories).anyMatch(itemCategory -> itemCategory.isIncluded(item)); + } + + @Override + public boolean isCurse() { + return false; + } + + @Override + public final boolean isTreasure() { + return this.getDefaults().isTreasure(); + } + + @Override + public boolean isTradeable() { + return this.getDefaults().isTradeable(); + } + + @Override + public boolean isDiscoverable() { + return this.getDefaults().isDiscoverable(); + } + + public boolean hasVisualEffects() { + return this.getDefaults().isVisualEffects(); + } + + public boolean isChargesEnabled() { + return Config.ENCHANTMENTS_CHARGES_ENABLED.get() && this.getDefaults().isChargesEnabled(); + } + + public boolean isChargesCustomFuel() { + return this.getDefaults().isChargesCustomFuel(); + } + + public int getChargesMax(int level) { + return this.isChargesEnabled() ? (int) this.getDefaults().getChargesMax().getValue(level) : 0; + } + + public int getChargesConsumeAmount(int level) { + return this.isChargesEnabled() ? (int) this.getDefaults().getChargesConsumeAmount().getValue(level) : 0; + } + + public int getChargesRechargeAmount(int level) { + return this.isChargesEnabled() ? (int) this.getDefaults().getChargesRechargeAmount().getValue(level) : 0; + } + + @NotNull + public ItemStack getChargesFuel() { + ItemStack fuelHas = this.getDefaults().getChargesFuel(); + if (!this.isChargesCustomFuel() || fuelHas == null || fuelHas.getType().isAir()) { + return Config.ENCHANTMENTS_CHARGES_FUEL_ITEM.get(); + } + return new ItemStack(fuelHas); + } + + public boolean isChargesFuel(@NotNull ItemStack item) { + if (Config.ENCHANTMENTS_CHARGES_COMPARE_TYPE_ONLY.get()) { + return item.getType() == this.getChargesFuel().getType(); + } + return item.isSimilar(this.getChargesFuel()); + } + + @NotNull + public NamespacedKey getChargesKey() { + return chargesKey; + } + + @Override + public boolean isOutOfCharges(@NotNull ItemStack item) { + return EnchantUtils.isOutOfCharges(item, this); + } + + @Override + public boolean isFullOfCharges(@NotNull ItemStack item) { + return EnchantUtils.isFullOfCharges(item, this); + } + + @Override + public int getCharges(@NotNull ItemStack item) { + return EnchantUtils.getCharges(item, this); + } + + @Override + public void consumeChargesNoUpdate(@NotNull ItemStack item, int level) { + if (!this.isChargesEnabled()) return; + + EnchantUtils.consumeCharges(item, this, level); + } + + @Override + public void consumeCharges(@NotNull ItemStack item, int level) { + if (!this.isChargesEnabled()) return; + + this.consumeChargesNoUpdate(item, level); + EnchantUtils.updateDisplay(item); + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/armor/AquamanEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/armor/AquamanEnchant.java new file mode 100644 index 0000000..5bcb10d --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/armor/AquamanEnchant.java @@ -0,0 +1,59 @@ +package su.nightexpress.excellentenchants.enchantment.impl.armor; + +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.LivingEntity; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffectType; +import org.jetbrains.annotations.NotNull; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Potioned; +import su.nightexpress.excellentenchants.api.enchantment.type.PassiveEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.PeriodImplementation; +import su.nightexpress.excellentenchants.enchantment.impl.meta.PotionImplementation; + +public class AquamanEnchant extends ExcellentEnchant implements Potioned, PassiveEnchant { + + public static final String ID = "aquaman"; + + private PotionImplementation potionImplementation; + private PeriodImplementation periodImplementation; + + public AquamanEnchant(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setLevelMax(1); + this.getDefaults().setTier(0.4); + this.getDefaults().setDescription("Grants permanent " + Placeholders.ENCHANTMENT_POTION_TYPE + " " + Placeholders.ENCHANTMENT_POTION_LEVEL + " effect."); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.potionImplementation = PotionImplementation.create(this, PotionEffectType.WATER_BREATHING, true); + this.periodImplementation = PeriodImplementation.create(this, "100"); + } + + @NotNull + @Override + public PotionImplementation getPotionImplementation() { + return potionImplementation; + } + + @NotNull + @Override + public PeriodImplementation getPeriodImplementation() { + return periodImplementation; + } + + @Override + @NotNull + public EnchantmentTarget getCategory() { + return EnchantmentTarget.ARMOR_HEAD; + } + + @Override + public boolean onTrigger(@NotNull LivingEntity entity, @NotNull ItemStack item, int level) { + return this.addEffect(entity, level); + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/armor/ColdSteelEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/armor/ColdSteelEnchant.java new file mode 100644 index 0000000..78196eb --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/armor/ColdSteelEnchant.java @@ -0,0 +1,80 @@ +package su.nightexpress.excellentenchants.enchantment.impl.armor; + +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffectType; +import org.jetbrains.annotations.NotNull; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.meta.Potioned; +import su.nightexpress.excellentenchants.api.enchantment.type.CombatEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; +import su.nightexpress.excellentenchants.enchantment.impl.meta.PotionImplementation; + +public class ColdSteelEnchant extends ExcellentEnchant implements Chanced, Potioned, CombatEnchant { + + public static final String ID = "cold_steel"; + + private ChanceImplementation chanceImplementation; + private PotionImplementation potionImplementation; + + public ColdSteelEnchant(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription(Placeholders.ENCHANTMENT_CHANCE + "% chance to apply " + Placeholders.ENCHANTMENT_POTION_TYPE + " " + Placeholders.ENCHANTMENT_POTION_LEVEL + " (" + Placeholders.ENCHANTMENT_POTION_DURATION + "s.) on attacker."); + this.getDefaults().setTier(0.3); + this.getDefaults().setLevelMax(3); + + } + + @Override + public void loadSettings() { + super.loadSettings(); + + this.chanceImplementation = ChanceImplementation.create(this, + "60 + " + Placeholders.ENCHANTMENT_LEVEL + " * 5"); + this.potionImplementation = PotionImplementation.create(this, PotionEffectType.SLOW_DIGGING, false, + "4 + " + Placeholders.ENCHANTMENT_LEVEL, + Placeholders.ENCHANTMENT_LEVEL); + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + @NotNull + @Override + public PotionImplementation getPotionImplementation() { + return potionImplementation; + } + + @Override + @NotNull + public EnchantmentTarget getCategory() { + return EnchantmentTarget.ARMOR_TORSO; + } + + @NotNull + @Override + public EventPriority getProtectPriority() { + return EventPriority.HIGHEST; + } + + @Override + public boolean onAttack(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + return false; + } + + @Override + public boolean onProtect(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + if (!this.checkTriggerChance(level)) return false; + + return this.addEffect(damager, level); + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/armor/DarknessCloakEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/armor/DarknessCloakEnchant.java new file mode 100644 index 0000000..d441642 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/armor/DarknessCloakEnchant.java @@ -0,0 +1,86 @@ +package su.nightexpress.excellentenchants.enchantment.impl.armor; + +import org.bukkit.Particle; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffectType; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.utils.values.UniParticle; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.meta.Potioned; +import su.nightexpress.excellentenchants.api.enchantment.type.CombatEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; +import su.nightexpress.excellentenchants.enchantment.impl.meta.PotionImplementation; + +public class DarknessCloakEnchant extends ExcellentEnchant implements Chanced, Potioned, CombatEnchant { + + public static final String ID = "darkness_cloak"; + + private ChanceImplementation chanceImplementation; + private PotionImplementation potionImplementation; + + public DarknessCloakEnchant(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription(Placeholders.ENCHANTMENT_CHANCE + "% chance to apply " + Placeholders.ENCHANTMENT_POTION_TYPE + " " + Placeholders.ENCHANTMENT_POTION_LEVEL + " (" + Placeholders.ENCHANTMENT_POTION_DURATION + "s.) on attacker."); + this.getDefaults().setLevelMax(3); + this.getDefaults().setTier(0.2); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.chanceImplementation = ChanceImplementation.create(this, + "20.0 * " + Placeholders.ENCHANTMENT_LEVEL + " * 0.75"); + + this.potionImplementation = PotionImplementation.create(this, PotionEffectType.DARKNESS, false, + "2.5" + Placeholders.ENCHANTMENT_LEVEL, + Placeholders.ENCHANTMENT_LEVEL); + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + @NotNull + @Override + public PotionImplementation getPotionImplementation() { + return potionImplementation; + } + + @Override + @NotNull + public EnchantmentTarget getCategory() { + return EnchantmentTarget.ARMOR_TORSO; + } + + @NotNull + @Override + public EventPriority getProtectPriority() { + return EventPriority.HIGHEST; + } + + @Override + public boolean onAttack(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + return false; + } + + @Override + public boolean onProtect(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + if (!this.checkTriggerChance(level)) return false; + if (!this.addEffect(damager, level)) return false; + + if (this.hasVisualEffects()) { + UniParticle.of(Particle.ASH).play(damager.getEyeLocation(), 0.75, 0.1, 30); + } + + return true; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/armor/ElementalProtectionEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/armor/ElementalProtectionEnchant.java new file mode 100644 index 0000000..22ecba6 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/armor/ElementalProtectionEnchant.java @@ -0,0 +1,106 @@ +package su.nightexpress.excellentenchants.enchantment.impl.armor; + +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.api.config.JOption; +import su.nexmedia.engine.api.manager.EventListener; +import su.nexmedia.engine.utils.NumberUtil; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.type.GenericEnchant; +import su.nightexpress.excellentenchants.enchantment.config.EnchantScaler; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.util.EnchantUtils; + +import java.util.Set; + +public class ElementalProtectionEnchant extends ExcellentEnchant implements GenericEnchant, EventListener { + + public static final String ID = "elemental_protection"; + public static final String PLACEHOLDER_PROTECTION_AMOUNT = "%enchantment_protection_amount%"; + public static final String PLACEHOLDER_PROTECTION_CAPACITY = "%enchantment_protection_capacity%"; + + private static final Set DAMAGE_CAUSES = Set.of( + EntityDamageEvent.DamageCause.POISON, EntityDamageEvent.DamageCause.WITHER, + EntityDamageEvent.DamageCause.MAGIC, EntityDamageEvent.DamageCause.FREEZE, + EntityDamageEvent.DamageCause.LIGHTNING); + + private EnchantScaler protectionAmount; + private double protectionCapacity; + private boolean protectionAsModifier; + + public ElementalProtectionEnchant(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription("Reduces Poison, Magic, Wither, Lightning, Freeze damage by " + PLACEHOLDER_PROTECTION_AMOUNT + "."); + this.getDefaults().setLevelMax(5); + this.getDefaults().setTier(0.2); + } + + @Override + public void loadSettings() { + super.loadSettings(); + + this.protectionAmount = EnchantScaler.read(this, "Settings.Protection.Amount", + "0.05 * " + Placeholders.ENCHANTMENT_LEVEL, + "How protection the enchantment will have?"); + this.protectionCapacity = JOption.create("Settings.Protection.Capacity", 1D, + "Maximal possible protection value from all armor pieces together.").read(cfg); + this.protectionAsModifier = JOption.create("Settings.Protection.As_Modifier", false, + "When 'true' damage will be reduced by a percent of protection value.", + "When 'false' damage will be reduced by a plain protection value.").read(cfg); + + this.addPlaceholder(PLACEHOLDER_PROTECTION_AMOUNT, level -> NumberUtil.format(this.getProtectionAmount(level))); + this.addPlaceholder(PLACEHOLDER_PROTECTION_CAPACITY, level -> NumberUtil.format(this.getProtectionCapacity())); + } + + @NotNull + @Override + public EnchantmentTarget getCategory() { + return EnchantmentTarget.ARMOR; + } + + public double getProtectionAmount(int level) { + return this.protectionAmount.getValue(level); + } + + public double getProtectionCapacity() { + return this.protectionCapacity; + } + + public boolean isProtectionAsModifier() { + return this.protectionAsModifier; + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onDamage(EntityDamageEvent event) { + if (!DAMAGE_CAUSES.contains(event.getCause())) return; + if (!(event.getEntity() instanceof LivingEntity entity)) return; + if (!this.isAvailableToUse(entity)) return; + + double protectionAmount = 0D; + for (ItemStack armor : EnchantUtils.getEnchantedEquipment(entity).values()) { + int level = EnchantUtils.getLevel(armor, this.getBackend()); + if (level <= 0) continue; + + protectionAmount += this.getProtectionAmount(level); + this.consumeCharges(armor, level); + } + + if (protectionAmount <= 0D) return; + if (protectionAmount > this.getProtectionCapacity()) { + protectionAmount = this.getProtectionCapacity(); + } + + if (this.isProtectionAsModifier()) { + event.setDamage(Math.max(0, event.getDamage() * (1D - protectionAmount))); + } + else { + event.setDamage(Math.max(0, event.getDamage() - protectionAmount)); + } + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/armor/FireShieldEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/armor/FireShieldEnchant.java new file mode 100644 index 0000000..ce909de --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/armor/FireShieldEnchant.java @@ -0,0 +1,88 @@ +package su.nightexpress.excellentenchants.enchantment.impl.armor; + +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.utils.NumberUtil; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.type.CombatEnchant; +import su.nightexpress.excellentenchants.enchantment.config.EnchantScaler; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; + +public class FireShieldEnchant extends ExcellentEnchant implements Chanced, CombatEnchant { + + public static final String ID = "fire_shield"; + public static final String PLACEHOLDER_FIRE_DURATION = "%enchantment_fire_duration%"; + + private EnchantScaler fireDuration; + private ChanceImplementation chanceImplementation; + + public FireShieldEnchant(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription(Placeholders.ENCHANTMENT_CHANCE + "% chance to ignite the attacker for " + PLACEHOLDER_FIRE_DURATION + "s."); + this.getDefaults().setLevelMax(3); + this.getDefaults().setTier(0.4); + } + + @Override + public void loadSettings() { + super.loadSettings(); + + this.chanceImplementation = ChanceImplementation.create(this, + Placeholders.ENCHANTMENT_LEVEL + " * 15.0"); + this.fireDuration = EnchantScaler.read(this, "Settings.Fire.Duration", + "2.5 * " + Placeholders.ENCHANTMENT_LEVEL, + "Sets the fire duration (in seconds).", + "If entity's current fire ticks amount is less than this value, it will be set to this value.", + "If entity's current fire ticks amount is greater than this value, it won't be changed."); + + this.addPlaceholder(PLACEHOLDER_FIRE_DURATION, level -> NumberUtil.format(this.getFireDuration(level))); + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + @NotNull + @Override + public EnchantmentTarget getCategory() { + return EnchantmentTarget.ARMOR; + } + + @NotNull + @Override + public EventPriority getProtectPriority() { + return EventPriority.HIGHEST; + } + + public double getFireDuration(int level) { + return this.fireDuration.getValue(level); + } + + @Override + public boolean onAttack(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + return false; + } + + @Override + public boolean onProtect(@NotNull EntityDamageByEntityEvent event, + @NotNull LivingEntity damager, @NotNull LivingEntity victim, + @NotNull ItemStack weapon, int level) { + if (!this.checkTriggerChance(level)) return false; + + int ticksToSet = (int) (this.getFireDuration(level) * 20); + int ticksHas = damager.getFireTicks(); + if (ticksHas >= ticksToSet) return false; + + damager.setFireTicks(ticksToSet); + return true; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/armor/FlameWalkerEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/armor/FlameWalkerEnchant.java new file mode 100644 index 0000000..3ba0ec1 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/armor/FlameWalkerEnchant.java @@ -0,0 +1,210 @@ +package su.nightexpress.excellentenchants.enchantment.impl.armor; + +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockExplodeEvent; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.EntityExplodeEvent; +import org.bukkit.event.player.PlayerMoveEvent; +import org.bukkit.inventory.EntityEquipment; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.Version; +import su.nexmedia.engine.api.manager.EventListener; +import su.nexmedia.engine.api.server.AbstractTask; +import su.nexmedia.engine.utils.Pair; +import su.nexmedia.engine.utils.random.Rnd; +import su.nexmedia.engine.utils.values.UniParticle; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.api.enchantment.Cleanable; +import su.nightexpress.excellentenchants.api.enchantment.type.GenericEnchant; +import su.nightexpress.excellentenchants.enchantment.config.EnchantScaler; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.util.EnchantUtils; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.stream.Stream; + +public class FlameWalkerEnchant extends ExcellentEnchant implements GenericEnchant, EventListener, Cleanable { + + public static final String ID = "flame_walker"; + + private static final BlockFace[] FACES = {BlockFace.SOUTH, BlockFace.NORTH, BlockFace.EAST, BlockFace.WEST}; + private static final Map> BLOCKS_TO_DESTROY = new ConcurrentHashMap<>(); + + private EnchantScaler blockDecayTime; + private BlockTickTask blockTickTask; + + public FlameWalkerEnchant(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription("Ability to walk on lava and magma blocks without getting damage."); + this.getDefaults().setLevelMax(3); + this.getDefaults().setTier(0.7); + this.getDefaults().getConflicts().add(Enchantment.FROST_WALKER.getKey().getKey()); + + this.blockTickTask = new BlockTickTask(plugin); + this.blockTickTask.start(); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.blockDecayTime = EnchantScaler.read(this, "Settings.Block_Decay", "12.0", + "Sets up to how long (in seconds) blocks will stay before turn back to lava."); + } + + @Override + public void clear() { + if (this.blockTickTask != null) { + this.blockTickTask.stop(); + this.blockTickTask = null; + } + BLOCKS_TO_DESTROY.keySet().forEach(block -> block.setType(Material.LAVA)); + BLOCKS_TO_DESTROY.clear(); + } + + public static void addBlock(@NotNull Block block, double seconds) { + BLOCKS_TO_DESTROY.put(block, Pair.of(System.currentTimeMillis() + (long) seconds * 1000L, Rnd.get(100))); + } + + public static boolean isBlock(@NotNull Block block) { + return BLOCKS_TO_DESTROY.containsKey(block); + } + + @Override + @NotNull + public EnchantmentTarget getCategory() { + return EnchantmentTarget.ARMOR_FEET; + } + + public double getBlockDecayTime(int level) { + return this.blockDecayTime.getValue(level); + } + + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onPlayerMove(PlayerMoveEvent event) { + Player player = event.getPlayer(); + if (player.isFlying() || !this.isAvailableToUse(player)) return; + + Location from = event.getFrom(); + Location to = event.getTo(); + if (to == null) return; + if (from.getX() == to.getX() && from.getY() == to.getY() && from.getZ() == to.getZ()) return; + + ItemStack boots = player.getInventory().getBoots(); + if (boots == null || boots.getType().isAir()) return; + + int level = EnchantUtils.getLevel(boots, this.getBackend()); + if (level <= 0) return; + + Block bTo = to.getBlock().getRelative(BlockFace.DOWN); + boolean hasLava = Stream.of(FACES).anyMatch(face -> bTo.getRelative(face).getType() == Material.LAVA); + if (!hasLava) return; + + plugin.getEnchantNMS().handleFlameWalker(player, player.getLocation(), level).forEach(block -> { + addBlock(block, Rnd.getDouble(this.getBlockDecayTime(level)) + 1); + }); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onFlameWalkerBlock(BlockBreakEvent event) { + if (isBlock(event.getBlock())) { + event.setDropItems(false); + event.setExpToDrop(0); + event.getBlock().setType(Material.LAVA); + } + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onBlockExplode(EntityExplodeEvent event) { + this.processExplosion(event.blockList()); + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onBlockExplode2(BlockExplodeEvent event) { + this.processExplosion(event.blockList()); + } + + private void processExplosion(@NotNull List blocks) { + blocks.removeIf(block -> { + if (isBlock(block)) { + block.setType(Material.AIR); + return true; + } + return false; + }); + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onMagmaDamage(EntityDamageEvent event) { + if (event.getCause() != EntityDamageEvent.DamageCause.HOT_FLOOR) return; + if (!(event.getEntity() instanceof LivingEntity livingEntity)) return; + if (!this.isAvailableToUse(livingEntity)) return; + + EntityEquipment equipment = livingEntity.getEquipment(); + if (equipment == null) return; + + ItemStack boots = equipment.getBoots(); + if (boots == null || boots.getType().isAir()) return; + + int level = EnchantUtils.getLevel(boots, this.getBackend()); + if (level <= 0) return; + + event.setCancelled(true); + } + + static class BlockTickTask extends AbstractTask { + + public BlockTickTask(@NotNull ExcellentEnchants plugin) { + super(plugin, 1, false); + } + + @Override + public void action() { + long now = System.currentTimeMillis(); + + BLOCKS_TO_DESTROY.keySet().removeIf(block -> { + if (block.isEmpty() || block.getType() != Material.MAGMA_BLOCK) return true; + + Pair pair = BLOCKS_TO_DESTROY.get(block); + long time = pair.getFirst(); + if (now >= time) { + if (Version.isAtLeast(Version.V1_19_R3)) { + block.getWorld().getPlayers().forEach(player -> { + player.sendBlockDamage(block.getLocation(), 0F, pair.getSecond()); + }); + } + + block.setType(Material.LAVA); + UniParticle.blockCrack(Material.MAGMA_BLOCK).play(block.getLocation(), 0.5, 0.7, 0.5, 0.03, 30); + return true; + } + else if (Version.isAtLeast(Version.V1_19_R3)) { + long diff = TimeUnit.MILLISECONDS.toSeconds(time - now); + + float progress = (float) (1D - Math.min(1D, diff / 5D)); + if (progress > 1F) progress = 1F; + if (progress < 0F) progress = 0F; + + float finalProgress = progress; + block.getWorld().getPlayers().forEach(player -> { + player.sendBlockDamage(block.getLocation(), finalProgress, pair.getSecond()); + }); + } + return false; + }); + } + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/armor/HardenedEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/armor/HardenedEnchant.java new file mode 100644 index 0000000..175aa06 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/armor/HardenedEnchant.java @@ -0,0 +1,79 @@ +package su.nightexpress.excellentenchants.enchantment.impl.armor; + +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffectType; +import org.jetbrains.annotations.NotNull; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.meta.Potioned; +import su.nightexpress.excellentenchants.api.enchantment.type.CombatEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; +import su.nightexpress.excellentenchants.enchantment.impl.meta.PotionImplementation; + +public class HardenedEnchant extends ExcellentEnchant implements Chanced, Potioned, CombatEnchant { + + public static final String ID = "hardened"; + + private ChanceImplementation chanceImplementation; + private PotionImplementation potionImplementation; + + public HardenedEnchant(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription(Placeholders.ENCHANTMENT_CHANCE + "% chance to obtain " + Placeholders.ENCHANTMENT_POTION_TYPE + " " + Placeholders.ENCHANTMENT_POTION_LEVEL + " (" + Placeholders.ENCHANTMENT_POTION_DURATION + "s.) when damaged."); + this.getDefaults().setLevelMax(3); + this.getDefaults().setTier(0.4); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.chanceImplementation = ChanceImplementation.create(this, + "30.0 * " + Placeholders.ENCHANTMENT_LEVEL); + + this.potionImplementation = PotionImplementation.create(this, PotionEffectType.DAMAGE_RESISTANCE, false, + "3.0" + Placeholders.ENCHANTMENT_LEVEL, + Placeholders.ENCHANTMENT_LEVEL); + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + @NotNull + @Override + public PotionImplementation getPotionImplementation() { + return potionImplementation; + } + + @Override + @NotNull + public EnchantmentTarget getCategory() { + return EnchantmentTarget.ARMOR_TORSO; + } + + @NotNull + @Override + public EventPriority getProtectPriority() { + return EventPriority.HIGHEST; + } + + @Override + public boolean onAttack(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + return false; + } + + @Override + public boolean onProtect(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + if (!this.checkTriggerChance(level)) return false; + + return this.addEffect(victim, level); + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/armor/IceShieldEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/armor/IceShieldEnchant.java new file mode 100644 index 0000000..20d4435 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/armor/IceShieldEnchant.java @@ -0,0 +1,85 @@ +package su.nightexpress.excellentenchants.enchantment.impl.armor; + +import org.bukkit.Material; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffectType; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.utils.values.UniParticle; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.meta.Potioned; +import su.nightexpress.excellentenchants.api.enchantment.type.CombatEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; +import su.nightexpress.excellentenchants.enchantment.impl.meta.PotionImplementation; + +public class IceShieldEnchant extends ExcellentEnchant implements Chanced, Potioned, CombatEnchant { + + public static final String ID = "ice_shield"; + + private ChanceImplementation chanceImplementation; + private PotionImplementation potionImplementation; + + public IceShieldEnchant(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription(Placeholders.ENCHANTMENT_CHANCE + "% chance to freeze and apply " + Placeholders.ENCHANTMENT_POTION_TYPE + " " + Placeholders.ENCHANTMENT_POTION_LEVEL + " (" + Placeholders.ENCHANTMENT_POTION_DURATION + "s.) on attacker."); + this.getDefaults().setLevelMax(3); + this.getDefaults().setTier(0.1); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.chanceImplementation = ChanceImplementation.create(this, "25.0 * " + Placeholders.ENCHANTMENT_LEVEL); + this.potionImplementation = PotionImplementation.create(this, PotionEffectType.SLOW, false, + "3.0 + " + Placeholders.ENCHANTMENT_LEVEL, + Placeholders.ENCHANTMENT_LEVEL); + } + + @NotNull + @Override + public EnchantmentTarget getCategory() { + return EnchantmentTarget.ARMOR_TORSO; + } + + @NotNull + @Override + public EventPriority getProtectPriority() { + return EventPriority.HIGHEST; + } + + @Override + @NotNull + public Chanced getChanceImplementation() { + return this.chanceImplementation; + } + + @NotNull + @Override + public PotionImplementation getPotionImplementation() { + return potionImplementation; + } + + @Override + public boolean onAttack(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + return false; + } + + @Override + public boolean onProtect(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + if (!this.checkTriggerChance(level)) return false; + if (!this.addEffect(damager, level)) return false; + + damager.setFreezeTicks(damager.getMaxFreezeTicks()); + + if (this.hasVisualEffects()) { + UniParticle.blockCrack(Material.ICE).play(damager.getEyeLocation(), 0.25, 0.1, 20); + } + return true; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/armor/JumpingEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/armor/JumpingEnchant.java new file mode 100644 index 0000000..3520c6a --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/armor/JumpingEnchant.java @@ -0,0 +1,59 @@ +package su.nightexpress.excellentenchants.enchantment.impl.armor; + +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.LivingEntity; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffectType; +import org.jetbrains.annotations.NotNull; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Potioned; +import su.nightexpress.excellentenchants.api.enchantment.type.PassiveEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.PeriodImplementation; +import su.nightexpress.excellentenchants.enchantment.impl.meta.PotionImplementation; + +public class JumpingEnchant extends ExcellentEnchant implements Potioned, PassiveEnchant { + + public static final String ID = "bunny_hop"; + + private PotionImplementation potionImplementation; + private PeriodImplementation periodImplementation; + + public JumpingEnchant(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setLevelMax(3); + this.getDefaults().setTier(0.1); + this.getDefaults().setDescription("Grants permanent " + Placeholders.ENCHANTMENT_POTION_TYPE + " " + Placeholders.ENCHANTMENT_POTION_LEVEL + " effect."); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.potionImplementation = PotionImplementation.create(this, PotionEffectType.JUMP, true); + this.periodImplementation = PeriodImplementation.create(this, "100"); + } + + @NotNull + @Override + public PotionImplementation getPotionImplementation() { + return potionImplementation; + } + + @NotNull + @Override + public PeriodImplementation getPeriodImplementation() { + return periodImplementation; + } + + @Override + @NotNull + public EnchantmentTarget getCategory() { + return EnchantmentTarget.ARMOR_FEET; + } + + @Override + public boolean onTrigger(@NotNull LivingEntity entity, @NotNull ItemStack item, int level) { + return this.addEffect(entity, level); + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/armor/KamikadzeEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/armor/KamikadzeEnchant.java new file mode 100644 index 0000000..d6f9e9a --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/armor/KamikadzeEnchant.java @@ -0,0 +1,114 @@ +package su.nightexpress.excellentenchants.enchantment.impl.armor; + +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Item; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.event.entity.EntityResurrectEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.api.config.JOption; +import su.nexmedia.engine.api.manager.EventListener; +import su.nexmedia.engine.utils.NumberUtil; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.type.DeathEnchant; +import su.nightexpress.excellentenchants.enchantment.config.EnchantScaler; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; + +public class KamikadzeEnchant extends ExcellentEnchant implements Chanced, DeathEnchant, EventListener { + + public static final String ID = "self_destruction"; + private static final String PLACEHOLDER_EXPLOSION_POWER = "%enchantment_explosion_power%"; + + private EnchantScaler explosionSize; + private boolean applyOnResurrect; + private ChanceImplementation chanceImplementation; + + private Entity exploder; + + public KamikadzeEnchant(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription("%enchantment_trigger_chance%% chance to create an explosion on death."); + this.getDefaults().setLevelMax(3); + this.getDefaults().setTier(0.3); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.chanceImplementation = ChanceImplementation.create(this, + "20.0 + " + Placeholders.ENCHANTMENT_LEVEL + " * 10"); + + this.applyOnResurrect = JOption.create("Settings.Apply_On_Resurrect", true, + "Sets whether or not enchantment will trigger on resurrect (when a totem is used)." + ).read(cfg); + + this.explosionSize = EnchantScaler.read(this, "Settings.Explosion.Size", + "1.0" + Placeholders.ENCHANTMENT_LEVEL, + "A size of the explosion. The more size - the bigger the damage."); + + this.addPlaceholder(PLACEHOLDER_EXPLOSION_POWER, level -> NumberUtil.format(this.getExplosionSize(level))); + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + @Override + @NotNull + public EnchantmentTarget getCategory() { + return EnchantmentTarget.ARMOR_TORSO; + } + + public boolean isApplyOnResurrect() { + return this.applyOnResurrect; + } + + public final double getExplosionSize(int level) { + return this.explosionSize.getValue(level); + } + + public boolean createExplosion(@NotNull LivingEntity entity, @NotNull ItemStack item, int level) { + if (!this.checkTriggerChance(level)) return false; + + float size = (float) this.getExplosionSize(level); + this.exploder = entity; + boolean exploded = entity.getWorld().createExplosion(entity.getLocation(), size, false, false, entity); + this.exploder = null; + return exploded; + } + + @Override + public boolean onDeath(@NotNull EntityDeathEvent event, @NotNull LivingEntity entity, ItemStack item, int level) { + return this.createExplosion(entity, item, level); + } + + @Override + public boolean onResurrect(@NotNull EntityResurrectEvent event, @NotNull LivingEntity entity, @NotNull ItemStack item, int level) { + return this.isApplyOnResurrect() && this.createExplosion(entity, item, level); + } + + @Override + public boolean onKill(@NotNull EntityDeathEvent event, @NotNull LivingEntity entity, @NotNull Player killer, ItemStack weapon, int level) { + return false; + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onItemDamage(EntityDamageByEntityEvent event) { + if (this.exploder == null || event.getDamager() != this.exploder) return; + + if (event.getEntity() instanceof Item || event.getEntity() == this.exploder) { + event.setCancelled(true); + } + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/armor/NightVisionEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/armor/NightVisionEnchant.java new file mode 100644 index 0000000..d0d3274 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/armor/NightVisionEnchant.java @@ -0,0 +1,59 @@ +package su.nightexpress.excellentenchants.enchantment.impl.armor; + +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.LivingEntity; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffectType; +import org.jetbrains.annotations.NotNull; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Potioned; +import su.nightexpress.excellentenchants.api.enchantment.type.PassiveEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.PeriodImplementation; +import su.nightexpress.excellentenchants.enchantment.impl.meta.PotionImplementation; + +public class NightVisionEnchant extends ExcellentEnchant implements Potioned, PassiveEnchant { + + public static final String ID = "night_vision"; + + private PotionImplementation potionImplementation; + private PeriodImplementation periodImplementation; + + public NightVisionEnchant(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription("Grants permanent " + Placeholders.ENCHANTMENT_POTION_TYPE + " " + Placeholders.ENCHANTMENT_POTION_LEVEL + " effect."); + this.getDefaults().setLevelMax(1); + this.getDefaults().setTier(0.7); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.potionImplementation = PotionImplementation.create(this, PotionEffectType.NIGHT_VISION, true); + this.periodImplementation = PeriodImplementation.create(this, "100"); + } + + @NotNull + @Override + public PotionImplementation getPotionImplementation() { + return potionImplementation; + } + + @NotNull + @Override + public PeriodImplementation getPeriodImplementation() { + return periodImplementation; + } + + @Override + @NotNull + public EnchantmentTarget getCategory() { + return EnchantmentTarget.ARMOR_HEAD; + } + + @Override + public boolean onTrigger(@NotNull LivingEntity entity, @NotNull ItemStack item, int level) { + return this.addEffect(entity, level); + } +} \ No newline at end of file diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/armor/RegrowthEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/armor/RegrowthEnchant.java new file mode 100644 index 0000000..52aed77 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/armor/RegrowthEnchant.java @@ -0,0 +1,110 @@ +package su.nightexpress.excellentenchants.enchantment.impl.armor; + +import org.bukkit.Particle; +import org.bukkit.attribute.Attribute; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.LivingEntity; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.utils.EntityUtil; +import su.nexmedia.engine.utils.NumberUtil; +import su.nexmedia.engine.utils.values.UniParticle; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.type.PassiveEnchant; +import su.nightexpress.excellentenchants.enchantment.config.EnchantScaler; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; +import su.nightexpress.excellentenchants.enchantment.impl.meta.PeriodImplementation; + +public class RegrowthEnchant extends ExcellentEnchant implements Chanced, PassiveEnchant { + + public static final String ID = "regrowth"; + + private static final String PLACEHOLDER_HEAL_AMOUNT = "%enchantment_heal_amount%"; + private static final String PLACEHOLDER_HEAL_MIN_HEALTH = "%enchantment_heal_min_health%"; + private static final String PLACEHOLDER_HEAL_MAX_HEALTH = "%enchantment_heal_max_health%"; + + private EnchantScaler healMinHealth; + private EnchantScaler healMaxHealth; + private EnchantScaler healAmount; + + private ChanceImplementation chanceImplementation; + private PeriodImplementation periodImplementation; + + public RegrowthEnchant(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription("Restores " + PLACEHOLDER_HEAL_AMOUNT + " hearts every few seconds."); + this.getDefaults().setLevelMax(5); + this.getDefaults().setTier(0.7); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.chanceImplementation = ChanceImplementation.create(this, + "20.0 + " + Placeholders.ENCHANTMENT_LEVEL + " * 5"); + + this.periodImplementation = PeriodImplementation.create(this, "100"); + + this.healMinHealth = EnchantScaler.read(this, "Settings.Heal.Min_Health", "0.5", + "Minimal entity health for the enchantment to have effect."); + this.healMaxHealth = EnchantScaler.read(this, "Settings.Heal.Max_Health", "20.0", + "Maximal entity health when the enchantment will not heal anymore."); + this.healAmount = EnchantScaler.read(this, "Settings.Heal.Amount", "0.25", + "Amount of hearts to be restored."); + + this.addPlaceholder(PLACEHOLDER_HEAL_AMOUNT, level -> NumberUtil.format(this.getHealAmount(level))); + this.addPlaceholder(PLACEHOLDER_HEAL_MIN_HEALTH, level -> NumberUtil.format(this.getHealMaxHealth(level))); + this.addPlaceholder(PLACEHOLDER_HEAL_MAX_HEALTH, level -> NumberUtil.format(this.getHealMaxHealth(level))); + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + @NotNull + @Override + public PeriodImplementation getPeriodImplementation() { + return periodImplementation; + } + + @NotNull + @Override + public EnchantmentTarget getCategory() { + return EnchantmentTarget.ARMOR_TORSO; + } + + public double getHealAmount(int level) { + return this.healAmount.getValue(level); + } + + public double getHealMinHealth(int level) { + return this.healMinHealth.getValue(level); + } + + public double getHealMaxHealth(int level) { + return this.healMaxHealth.getValue(level); + } + + @Override + public boolean onTrigger(@NotNull LivingEntity entity, @NotNull ItemStack item, int level) { + if (!this.checkTriggerChance(level)) return false; + + double healthMax = EntityUtil.getAttribute(entity, Attribute.GENERIC_MAX_HEALTH); + double healthHas = entity.getHealth(); + if (healthHas < this.getHealMinHealth(level) || healthHas > this.getHealMaxHealth(level)) return false; + if (healthHas >= healthMax) return false; + + double amount = Math.min(healthMax, healthHas + this.getHealAmount(level)); + entity.setHealth(amount); + + if (this.hasVisualEffects()) { + UniParticle.of(Particle.HEART).play(entity.getEyeLocation(), 0.25, 0.1, 5); + } + return true; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/armor/SaturationEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/armor/SaturationEnchant.java new file mode 100644 index 0000000..79487cf --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/armor/SaturationEnchant.java @@ -0,0 +1,78 @@ +package su.nightexpress.excellentenchants.enchantment.impl.armor; + +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.utils.NumberUtil; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.type.PassiveEnchant; +import su.nightexpress.excellentenchants.enchantment.config.EnchantScaler; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.PeriodImplementation; + +public class SaturationEnchant extends ExcellentEnchant implements PassiveEnchant { + + public static final String ID = "saturation"; + + private static final String PLACEHOLDER_SATURATION_AMOUNT = "%enchantment_saturation_amount%"; + private static final String PLACEHOLDER_SATURATION_MAX_FOOD_LEVEL = "%enchantment_saturation_max_food_level%"; + + private EnchantScaler saturationAmount; + private EnchantScaler saturationMaxFoodLevel; + + private PeriodImplementation periodImplementation; + + public SaturationEnchant(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription("Restores " + PLACEHOLDER_SATURATION_AMOUNT + " food points every few seconds."); + this.getDefaults().setLevelMax(3); + this.getDefaults().setTier(0.5); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.periodImplementation = PeriodImplementation.create(this, "100"); + + this.saturationAmount = EnchantScaler.read(this, "Settings.Saturation.Amount", Placeholders.ENCHANTMENT_LEVEL, + "Amount of food points to restore."); + this.saturationMaxFoodLevel = EnchantScaler.read(this, "Settings.Saturation.Max_Food_Level", "20", + "Maximal player's food level for the enchantment to stop feeding them."); + + this.addPlaceholder(PLACEHOLDER_SATURATION_AMOUNT, level -> NumberUtil.format(this.getSaturationAmount(level))); + this.addPlaceholder(PLACEHOLDER_SATURATION_MAX_FOOD_LEVEL, level -> NumberUtil.format(this.getMaxFoodLevel(level))); + } + + @NotNull + @Override + public PeriodImplementation getPeriodImplementation() { + return periodImplementation; + } + + @Override + @NotNull + public EnchantmentTarget getCategory() { + return EnchantmentTarget.ARMOR_HEAD; + } + + public final int getSaturationAmount(int level) { + return (int) this.saturationAmount.getValue(level); + } + + public final int getMaxFoodLevel(int level) { + return (int) this.saturationMaxFoodLevel.getValue(level); + } + + @Override + public boolean onTrigger(@NotNull LivingEntity entity, @NotNull ItemStack item, int level) { + if (!(entity instanceof Player player)) return false; + if (player.getFoodLevel() >= this.getMaxFoodLevel(level)) return false; + + int amount = this.getSaturationAmount(level); + player.setFoodLevel(Math.min(20, player.getFoodLevel() + amount)); + return true; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/armor/SpeedyEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/armor/SpeedyEnchant.java new file mode 100644 index 0000000..0cce14d --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/armor/SpeedyEnchant.java @@ -0,0 +1,59 @@ +package su.nightexpress.excellentenchants.enchantment.impl.armor; + +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.LivingEntity; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffectType; +import org.jetbrains.annotations.NotNull; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Potioned; +import su.nightexpress.excellentenchants.api.enchantment.type.PassiveEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.PeriodImplementation; +import su.nightexpress.excellentenchants.enchantment.impl.meta.PotionImplementation; + +public class SpeedyEnchant extends ExcellentEnchant implements Potioned, PassiveEnchant { + + public static final String ID = "sonic"; + + private PotionImplementation potionImplementation; + private PeriodImplementation periodImplementation; + + public SpeedyEnchant(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription("Grants permanent " + Placeholders.ENCHANTMENT_POTION_TYPE + " " + Placeholders.ENCHANTMENT_POTION_LEVEL + " effect."); + this.getDefaults().setLevelMax(3); + this.getDefaults().setTier(0.3); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.potionImplementation = PotionImplementation.create(this, PotionEffectType.SPEED, true); + this.periodImplementation = PeriodImplementation.create(this, "100"); + } + + @NotNull + @Override + public PotionImplementation getPotionImplementation() { + return potionImplementation; + } + + @NotNull + @Override + public PeriodImplementation getPeriodImplementation() { + return periodImplementation; + } + + @Override + @NotNull + public EnchantmentTarget getCategory() { + return EnchantmentTarget.ARMOR_FEET; + } + + @Override + public boolean onTrigger(@NotNull LivingEntity entity, @NotNull ItemStack item, int level) { + return this.addEffect(entity, level); + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/armor/StoppingForceEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/armor/StoppingForceEnchant.java new file mode 100644 index 0000000..59f90cc --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/armor/StoppingForceEnchant.java @@ -0,0 +1,81 @@ +package su.nightexpress.excellentenchants.enchantment.impl.armor; + +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.utils.NumberUtil; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.type.CombatEnchant; +import su.nightexpress.excellentenchants.enchantment.config.EnchantScaler; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; + +public class StoppingForceEnchant extends ExcellentEnchant implements Chanced, CombatEnchant { + + public static final String ID = "stopping_force"; + + public static final String PLACEHOLDER_KNOCKBACK_RESISTANCE = "%knockback_resistance%"; + + private ChanceImplementation chanceImplementation; + private EnchantScaler knockbackModifier; + + public StoppingForceEnchant(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription(Placeholders.ENCHANTMENT_CHANCE + "% chance to resist knockback in combat by " + PLACEHOLDER_KNOCKBACK_RESISTANCE + "%."); + this.getDefaults().setLevelMax(3); + this.getDefaults().setTier(0.5); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.chanceImplementation = ChanceImplementation.create(this, "100.0"); + this.knockbackModifier = EnchantScaler.read(this, "Settings.Knockback_Modifier", + "0.7 - " + Placeholders.ENCHANTMENT_LEVEL + " / 5.0", + "Sets the knockback multiplier when taking damage.", "Lower value = less knockback."); + + this.addPlaceholder(PLACEHOLDER_KNOCKBACK_RESISTANCE, level -> NumberUtil.format(this.getKnockbackModifier(level) * 100)); + } + + public double getKnockbackModifier(int level) { + return this.knockbackModifier.getValue(level); + } + + @NotNull + @Override + public EnchantmentTarget getCategory() { + return EnchantmentTarget.ARMOR_LEGS; + } + + @NotNull + @Override + public EventPriority getProtectPriority() { + return EventPriority.HIGHEST; + } + + @NotNull + @Override + public Chanced getChanceImplementation() { + return this.chanceImplementation; + } + + @Override + public boolean onAttack(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + return false; + } + + @Override + public boolean onProtect(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + if (!this.checkTriggerChance(level)) return false; + + this.plugin.runTask(task -> { + victim.setVelocity(victim.getVelocity().multiply(this.getKnockbackModifier(level))); + }); + return true; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/bow/DarknessArrowsEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/bow/DarknessArrowsEnchant.java new file mode 100644 index 0000000..3b630f3 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/bow/DarknessArrowsEnchant.java @@ -0,0 +1,96 @@ +package su.nightexpress.excellentenchants.enchantment.impl.bow; + +import org.bukkit.Particle; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.AbstractArrow; +import org.bukkit.entity.Arrow; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Projectile; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityShootBowEvent; +import org.bukkit.event.entity.ProjectileHitEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffectType; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.utils.values.UniParticle; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Arrowed; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.meta.Potioned; +import su.nightexpress.excellentenchants.api.enchantment.type.BowEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ArrowImplementation; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; +import su.nightexpress.excellentenchants.enchantment.impl.meta.PotionImplementation; + +public class DarknessArrowsEnchant extends ExcellentEnchant implements Chanced, Arrowed, Potioned, BowEnchant { + + public static final String ID = "darkness_arrows"; + + private ArrowImplementation arrowImplementation; + private ChanceImplementation chanceImplementation; + private PotionImplementation potionImplementation; + + public DarknessArrowsEnchant(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription(Placeholders.ENCHANTMENT_CHANCE + "% chance to launch an arrow with " + Placeholders.ENCHANTMENT_POTION_TYPE + " " + Placeholders.ENCHANTMENT_POTION_LEVEL + " (" + Placeholders.ENCHANTMENT_POTION_DURATION + "s.)"); + this.getDefaults().setLevelMax(3); + this.getDefaults().setTier(0.1); + this.getDefaults().setConflicts(EnchantEnderBow.ID, EnchantGhast.ID, EnchantBomber.ID); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.arrowImplementation = ArrowImplementation.create(this, UniParticle.of(Particle.ASH)); + this.chanceImplementation = ChanceImplementation.create(this, + "25.0 + " + Placeholders.ENCHANTMENT_LEVEL + " * 5.0"); + this.potionImplementation = PotionImplementation.create(this, PotionEffectType.DARKNESS, false, + "4.0 + " + Placeholders.ENCHANTMENT_LEVEL, + Placeholders.ENCHANTMENT_LEVEL); + } + + @NotNull + @Override + public ArrowImplementation getArrowImplementation() { + return arrowImplementation; + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + @NotNull + @Override + public PotionImplementation getPotionImplementation() { + return potionImplementation; + } + + @NotNull + @Override + public EnchantmentTarget getCategory() { + return EnchantmentTarget.BOW; + } + + @Override + public boolean onShoot(@NotNull EntityShootBowEvent event, @NotNull LivingEntity shooter, @NotNull ItemStack bow, int level) { + if (!(event.getProjectile() instanceof Arrow arrow)) return false; + if (!this.checkTriggerChance(level)) return false; + + arrow.setPickupStatus(AbstractArrow.PickupStatus.DISALLOWED); + return arrow.addCustomEffect(this.createEffect(level), true); + } + + @Override + public boolean onHit(@NotNull ProjectileHitEvent event, LivingEntity user, @NotNull Projectile projectile, @NotNull ItemStack bow, int level) { + return false; + } + + @Override + public boolean onDamage(@NotNull EntityDamageByEntityEvent event, @NotNull Projectile projectile, @NotNull LivingEntity shooter, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + return false; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/bow/EnchantBomber.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/bow/EnchantBomber.java new file mode 100644 index 0000000..0626cb7 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/bow/EnchantBomber.java @@ -0,0 +1,104 @@ +package su.nightexpress.excellentenchants.enchantment.impl.bow; + +import org.bukkit.enchantments.Enchantment; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Projectile; +import org.bukkit.entity.TNTPrimed; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityShootBowEvent; +import org.bukkit.event.entity.ProjectileHitEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.utils.NumberUtil; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.type.BowEnchant; +import su.nightexpress.excellentenchants.enchantment.config.EnchantScaler; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; + +public class EnchantBomber extends ExcellentEnchant implements Chanced, BowEnchant { + + public static final String ID = "bomber"; + public static final String PLACEHOLDER_FUSE_TICKS = "%enchantment_fuse_ticks%"; + + private EnchantScaler fuseTicks; + private ChanceImplementation chanceImplementation; + + public EnchantBomber(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription(Placeholders.ENCHANTMENT_CHANCE + "% chance to launch TNT that explodes in " + PLACEHOLDER_FUSE_TICKS + "s."); + this.getDefaults().setLevelMax(3); + this.getDefaults().setTier(0.7); + this.getDefaults().setConflicts( + EnchantEnderBow.ID, EnchantGhast.ID, + EnchantExplosiveArrows.ID, EnchantPoisonedArrows.ID, EnchantConfusingArrows.ID, + EnchantWitheredArrows.ID, EnchantElectrifiedArrows.ID, EnchantDragonfireArrows.ID, + DarknessArrowsEnchant.ID, + EnchantHover.ID, FlareEnchant.ID, + Enchantment.ARROW_FIRE.getKey().getKey(), + Enchantment.ARROW_KNOCKBACK.getKey().getKey(), + Enchantment.ARROW_DAMAGE.getKey().getKey() + ); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.chanceImplementation = ChanceImplementation.create(this, + "5.0 * " + Placeholders.ENCHANTMENT_LEVEL); + this.fuseTicks = EnchantScaler.read(this, "Settings.Fuse_Ticks", + "100 - " + Placeholders.ENCHANTMENT_LEVEL + " * 10", + "Sets fuse ticks (before it will explode) for the launched TNT."); + + this.addPlaceholder(PLACEHOLDER_FUSE_TICKS, level -> NumberUtil.format((double) this.getFuseTicks(level) / 20D)); + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + public int getFuseTicks(int level) { + return (int) this.fuseTicks.getValue(level); + } + + @Override + @NotNull + public EnchantmentTarget getCategory() { + return EnchantmentTarget.BOW; + } + + @NotNull + @Override + public EventPriority getShootPriority() { + return EventPriority.LOWEST; + } + + @Override + public boolean onShoot(@NotNull EntityShootBowEvent event, @NotNull LivingEntity shooter, @NotNull ItemStack bow, int level) { + if (!this.checkTriggerChance(level)) return false; + if (!(event.getProjectile() instanceof Projectile projectile)) return false; + + TNTPrimed primed = projectile.getWorld().spawn(projectile.getLocation(), TNTPrimed.class); + primed.setVelocity(projectile.getVelocity().multiply(event.getForce() * 1.25)); + primed.setFuseTicks(this.getFuseTicks(level)); + primed.setSource(shooter); + event.setProjectile(primed); + return true; + } + + @Override + public boolean onHit(@NotNull ProjectileHitEvent event, LivingEntity user, @NotNull Projectile projectile, @NotNull ItemStack bow, int level) { + return false; + } + + @Override + public boolean onDamage(@NotNull EntityDamageByEntityEvent event, @NotNull Projectile projectile, @NotNull LivingEntity shooter, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + return false; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/bow/EnchantConfusingArrows.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/bow/EnchantConfusingArrows.java new file mode 100644 index 0000000..a6eed53 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/bow/EnchantConfusingArrows.java @@ -0,0 +1,96 @@ +package su.nightexpress.excellentenchants.enchantment.impl.bow; + +import org.bukkit.Particle; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.AbstractArrow; +import org.bukkit.entity.Arrow; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Projectile; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityShootBowEvent; +import org.bukkit.event.entity.ProjectileHitEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffectType; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.utils.values.UniParticle; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Arrowed; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.meta.Potioned; +import su.nightexpress.excellentenchants.api.enchantment.type.BowEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ArrowImplementation; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; +import su.nightexpress.excellentenchants.enchantment.impl.meta.PotionImplementation; + +public class EnchantConfusingArrows extends ExcellentEnchant implements Chanced, Arrowed, Potioned, BowEnchant { + + public static final String ID = "confusing_arrows"; + + private ArrowImplementation arrowImplementation; + private ChanceImplementation chanceImplementation; + private PotionImplementation potionImplementation; + + public EnchantConfusingArrows(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription(Placeholders.ENCHANTMENT_CHANCE + "% chance to launch an arrow with " + Placeholders.ENCHANTMENT_POTION_TYPE + " " + Placeholders.ENCHANTMENT_POTION_LEVEL + " (" + Placeholders.ENCHANTMENT_POTION_DURATION + "s.)"); + this.getDefaults().setLevelMax(3); + this.getDefaults().setTier(0.1); + this.getDefaults().setConflicts(EnchantEnderBow.ID, EnchantGhast.ID, EnchantBomber.ID); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.arrowImplementation = ArrowImplementation.create(this, UniParticle.of(Particle.SPELL_MOB)); + this.chanceImplementation = ChanceImplementation.create(this, + "20.0 + " + Placeholders.ENCHANTMENT_LEVEL + " * 5.0"); + this.potionImplementation = PotionImplementation.create(this, PotionEffectType.CONFUSION, false, + "6.0 + " + Placeholders.ENCHANTMENT_LEVEL + " * 3.0", + Placeholders.ENCHANTMENT_LEVEL); + } + + @NotNull + @Override + public ArrowImplementation getArrowImplementation() { + return arrowImplementation; + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + @NotNull + @Override + public PotionImplementation getPotionImplementation() { + return potionImplementation; + } + + @NotNull + @Override + public EnchantmentTarget getCategory() { + return EnchantmentTarget.BOW; + } + + @Override + public boolean onShoot(@NotNull EntityShootBowEvent event, @NotNull LivingEntity shooter, @NotNull ItemStack bow, int level) { + if (!(event.getProjectile() instanceof Arrow arrow)) return false; + if (!this.checkTriggerChance(level)) return false; + + arrow.setPickupStatus(AbstractArrow.PickupStatus.DISALLOWED); + return arrow.addCustomEffect(this.createEffect(level), true); + } + + @Override + public boolean onHit(@NotNull ProjectileHitEvent event, LivingEntity user, @NotNull Projectile projectile, @NotNull ItemStack bow, int level) { + return false; + } + + @Override + public boolean onDamage(@NotNull EntityDamageByEntityEvent event, @NotNull Projectile projectile, @NotNull LivingEntity shooter, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + return false; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/bow/EnchantDragonfireArrows.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/bow/EnchantDragonfireArrows.java new file mode 100644 index 0000000..9d7ecfb --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/bow/EnchantDragonfireArrows.java @@ -0,0 +1,155 @@ +package su.nightexpress.excellentenchants.enchantment.impl.bow; + +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.World; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.AreaEffectCloud; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Projectile; +import org.bukkit.entity.ThrownPotion; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityShootBowEvent; +import org.bukkit.event.entity.LingeringPotionSplashEvent; +import org.bukkit.event.entity.ProjectileHitEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.PotionMeta; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.projectiles.ProjectileSource; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.utils.ItemUtil; +import su.nexmedia.engine.utils.NumberUtil; +import su.nexmedia.engine.utils.values.UniParticle; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Arrowed; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.type.BowEnchant; +import su.nightexpress.excellentenchants.enchantment.config.EnchantScaler; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ArrowImplementation; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; + +public class EnchantDragonfireArrows extends ExcellentEnchant implements Chanced, Arrowed, BowEnchant { + + public static final String ID = "dragonfire_arrows"; + + public static final String PLACEHOLDER_FIRE_RADIUS = "%enchantment_fire_radius%"; + public static final String PLACEHOLDER_FIRE_DURATION = "%enchantment_fire_duration%"; + + private EnchantScaler fireDuration; + private EnchantScaler fireRadius; + + private ArrowImplementation arrowImplementation; + private ChanceImplementation chanceImplementation; + + public EnchantDragonfireArrows(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription(Placeholders.ENCHANTMENT_CHANCE + "% chance to launch an dragonfire arrow (R=" + PLACEHOLDER_FIRE_RADIUS + ", " + PLACEHOLDER_FIRE_DURATION + "s)."); + this.getDefaults().setLevelMax(3); + this.getDefaults().setTier(0.7); + this.getDefaults().setConflicts(EnchantEnderBow.ID, EnchantGhast.ID, EnchantBomber.ID); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.arrowImplementation = ArrowImplementation.create(this, UniParticle.of(Particle.DRAGON_BREATH)); + this.chanceImplementation = ChanceImplementation.create(this, + "10.0 + " + Placeholders.ENCHANTMENT_LEVEL + " * 5"); + this.fireDuration = EnchantScaler.read(this, "Settings.Fire.Duration", + "100 * " + Placeholders.ENCHANTMENT_LEVEL, + "Sets the dragonfire cloud effect duration (in ticks). 20 ticks = 1 second."); + this.fireRadius = EnchantScaler.read(this, "Settings.Fire.Radius", + "2.0 + " + Placeholders.ENCHANTMENT_LEVEL, + "Sets the dragonfire cloud effect radius."); + + this.addPlaceholder(PLACEHOLDER_FIRE_DURATION, level -> NumberUtil.format(this.getFireDuration(level) / 20D)); + this.addPlaceholder(PLACEHOLDER_FIRE_RADIUS, level -> NumberUtil.format(this.getFireRadius(level))); + } + + @NotNull + @Override + public ArrowImplementation getArrowImplementation() { + return arrowImplementation; + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + @NotNull + @Override + public EnchantmentTarget getCategory() { + return EnchantmentTarget.BOW; + } + + public int getFireDuration(int level) { + return (int) this.fireDuration.getValue(level); + } + + public double getFireRadius(int level) { + return this.fireRadius.getValue(level); + } + + @Override + public boolean onShoot(@NotNull EntityShootBowEvent event, @NotNull LivingEntity shooter, @NotNull ItemStack bow, int level) { + return this.checkTriggerChance(level); + } + + @Override + public boolean onHit(@NotNull ProjectileHitEvent event, LivingEntity user, @NotNull Projectile projectile, @NotNull ItemStack bow, int level) { + if (!this.isOurProjectile(projectile)) return false; + if (event.getHitEntity() != null) return false; + if (projectile.getShooter() == null) return false; + + this.createCloud(projectile.getShooter(), projectile.getLocation() , level); + return false; + } + + @Override + public boolean onDamage(@NotNull EntityDamageByEntityEvent event, @NotNull Projectile projectile, @NotNull LivingEntity shooter, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + if (!this.isOurProjectile(projectile)) return false; + + this.createCloud(shooter, victim.getLocation(), level); + return false; + } + + private void createCloud(@NotNull ProjectileSource shooter, @NotNull Location location, int level) { + World world = location.getWorld(); + if (world == null) return; + + // There are some tweaks to respect protection plugins using even call. + + ItemStack item = new ItemStack(Material.LINGERING_POTION); + ItemUtil.mapMeta(item, meta -> { + if (meta instanceof PotionMeta potionMeta) { + potionMeta.addCustomEffect(new PotionEffect(PotionEffectType.HARM, 20, 0), true); + } + }); + + ThrownPotion potion = shooter.launchProjectile(ThrownPotion.class); + potion.setItem(item); + potion.teleport(location); + + AreaEffectCloud cloud = world.spawn(location, AreaEffectCloud.class); + cloud.clearCustomEffects(); + cloud.setSource(shooter); + cloud.setParticle(Particle.DRAGON_BREATH); + cloud.setRadius((float) this.getFireRadius(level)); + cloud.setDuration(this.getFireDuration(level)); + cloud.setRadiusPerTick((7.0F - cloud.getRadius()) / (float) cloud.getDuration()); + cloud.addCustomEffect(new PotionEffect(PotionEffectType.HARM, 1, 1), true); + + LingeringPotionSplashEvent splashEvent = new LingeringPotionSplashEvent(potion, cloud); + plugin.getPluginManager().callEvent(splashEvent); + if (splashEvent.isCancelled()) { + cloud.remove(); + } + potion.remove(); + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/bow/EnchantElectrifiedArrows.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/bow/EnchantElectrifiedArrows.java new file mode 100644 index 0000000..5d9ed27 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/bow/EnchantElectrifiedArrows.java @@ -0,0 +1,114 @@ +package su.nightexpress.excellentenchants.enchantment.impl.bow; + +import org.bukkit.Location; +import org.bukkit.Particle; +import org.bukkit.block.Block; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.Item; +import org.bukkit.entity.ItemFrame; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Projectile; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityShootBowEvent; +import org.bukkit.event.entity.ProjectileHitEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.metadata.FixedMetadataValue; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.utils.LocationUtil; +import su.nexmedia.engine.utils.values.UniParticle; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Arrowed; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.type.BowEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ArrowImplementation; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; + +public class EnchantElectrifiedArrows extends ExcellentEnchant implements Chanced, Arrowed, BowEnchant { + + public static final String ID = "electrified_arrows"; + + private static final String META_NO_ITEM_DAMAGE = "itemNoDamage"; + + private ArrowImplementation arrowImplementation; + private ChanceImplementation chanceImplementation; + + public EnchantElectrifiedArrows(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription(Placeholders.ENCHANTMENT_CHANCE + "% chance to launch an electrified arrow."); + this.getDefaults().setLevelMax(3); + this.getDefaults().setTier(0.3); + this.getDefaults().setConflicts(EnchantEnderBow.ID, EnchantGhast.ID, EnchantBomber.ID); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.arrowImplementation = ArrowImplementation.create(this, UniParticle.of(Particle.FIREWORKS_SPARK)); + this.chanceImplementation = ChanceImplementation.create(this, + "10.0 + " + Placeholders.ENCHANTMENT_LEVEL + " * 5"); + } + + @NotNull + @Override + public ArrowImplementation getArrowImplementation() { + return arrowImplementation; + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + @NotNull + @Override + public EnchantmentTarget getCategory() { + return EnchantmentTarget.BOW; + } + + @Override + public boolean onShoot(@NotNull EntityShootBowEvent event, @NotNull LivingEntity shooter, @NotNull ItemStack bow, int level) { + return this.checkTriggerChance(level); + } + + @Override + public boolean onHit(@NotNull ProjectileHitEvent event, LivingEntity user, @NotNull Projectile projectile, @NotNull ItemStack bow, int level) { + if (!this.isOurProjectile(projectile)) return false; + if (event.getHitEntity() != null || event.getHitBlock() == null) return false; + + Block block = event.getHitBlock(); + block.getWorld().strikeLightning(block.getLocation()).setMetadata(META_NO_ITEM_DAMAGE, new FixedMetadataValue(plugin, true)); + if (this.hasVisualEffects()) { + Location center = LocationUtil.getCenter(block.getLocation()); + UniParticle.blockCrack(block.getType()).play(center, 1, 0.05, 120); + UniParticle.of(Particle.FIREWORKS_SPARK).play(center, 1, 0.05, 120); + } + return false; + } + + @Override + public boolean onDamage(@NotNull EntityDamageByEntityEvent event, @NotNull Projectile projectile, @NotNull LivingEntity shooter, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + if (!this.isOurProjectile(projectile)) return false; + + plugin.getServer().getScheduler().runTask(plugin, () -> { + if (victim.isDead()) return; + victim.setNoDamageTicks(0); + victim.getWorld().strikeLightning(victim.getLocation()).setMetadata(META_NO_ITEM_DAMAGE, new FixedMetadataValue(plugin, true)); + }); + + return false; + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onItemDamage(EntityDamageByEntityEvent e) { + if (!e.getDamager().hasMetadata(META_NO_ITEM_DAMAGE)) return; + if (e.getEntity() instanceof Item || e.getEntity() instanceof ItemFrame) { + e.setCancelled(true); + e.getEntity().setFireTicks(0); + } + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/bow/EnchantEnderBow.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/bow/EnchantEnderBow.java new file mode 100644 index 0000000..0db56b4 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/bow/EnchantEnderBow.java @@ -0,0 +1,88 @@ +package su.nightexpress.excellentenchants.enchantment.impl.bow; + +import org.bukkit.enchantments.Enchantment; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.EnderPearl; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Projectile; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityShootBowEvent; +import org.bukkit.event.entity.ProjectileHitEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.type.BowEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; + +public class EnchantEnderBow extends ExcellentEnchant implements BowEnchant, Chanced { + + public static final String ID = "ender_bow"; + + private ChanceImplementation chanceImplementation; + + public EnchantEnderBow(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription("Shoots ender pearls instead of arrows."); + this.getDefaults().setLevelMax(1); + this.getDefaults().setTier(1.0); + + this.getDefaults().setConflicts( + EnchantBomber.ID, EnchantGhast.ID, + EnchantExplosiveArrows.ID, EnchantPoisonedArrows.ID, EnchantConfusingArrows.ID, + EnchantWitheredArrows.ID, EnchantElectrifiedArrows.ID, EnchantDragonfireArrows.ID, + DarknessArrowsEnchant.ID, VampiricArrowsEnchant.ID, + EnchantHover.ID, FlareEnchant.ID, + Enchantment.ARROW_FIRE.getKey().getKey(), + Enchantment.ARROW_KNOCKBACK.getKey().getKey(), + Enchantment.ARROW_DAMAGE.getKey().getKey() + ); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.chanceImplementation = ChanceImplementation.create(this, "100"); + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + @Override + @NotNull + public EnchantmentTarget getCategory() { + return EnchantmentTarget.BOW; + } + + @NotNull + @Override + public EventPriority getShootPriority() { + return EventPriority.LOWEST; + } + + @Override + public boolean onShoot(@NotNull EntityShootBowEvent event, @NotNull LivingEntity shooter, @NotNull ItemStack bow, int level) { + if (!this.checkTriggerChance(level)) return false; + if (!(event.getProjectile() instanceof Projectile projectile)) return false; + + EnderPearl pearl = shooter.launchProjectile(EnderPearl.class); + pearl.setVelocity(projectile.getVelocity()); + event.setProjectile(pearl); + return true; + } + + @Override + public boolean onHit(@NotNull ProjectileHitEvent event, LivingEntity user, @NotNull Projectile projectile, @NotNull ItemStack bow, int level) { + return false; + } + + @Override + public boolean onDamage(@NotNull EntityDamageByEntityEvent event, @NotNull Projectile projectile, @NotNull LivingEntity shooter, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + return false; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/bow/EnchantExplosiveArrows.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/bow/EnchantExplosiveArrows.java new file mode 100644 index 0000000..4296cb6 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/bow/EnchantExplosiveArrows.java @@ -0,0 +1,137 @@ +package su.nightexpress.excellentenchants.enchantment.impl.bow; + +import org.bukkit.Particle; +import org.bukkit.World; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.*; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.EntityShootBowEvent; +import org.bukkit.event.entity.ProjectileHitEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.api.config.JOption; +import su.nexmedia.engine.utils.NumberUtil; +import su.nexmedia.engine.utils.values.UniParticle; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Arrowed; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.type.BowEnchant; +import su.nightexpress.excellentenchants.enchantment.config.EnchantScaler; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ArrowImplementation; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; + +public class EnchantExplosiveArrows extends ExcellentEnchant implements Chanced, Arrowed, BowEnchant { + + public static final String ID = "explosive_arrows"; + public static final String PLACEHOLDER_EXPLOSION_POWER = "%enchantment_explosion_power%"; + + private boolean explosionFireSpread; + private boolean explosionDamageItems; + private boolean explosionDamageBlocks; + private EnchantScaler explosionSize; + + private ArrowImplementation arrowImplementation; + private ChanceImplementation chanceImplementation; + + private Entity lastExploder; + + public EnchantExplosiveArrows(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription(Placeholders.ENCHANTMENT_CHANCE + "% chance to launch an explosive arrow."); + this.getDefaults().setLevelMax(3); + this.getDefaults().setTier(0.7); + this.getDefaults().setConflicts(EnchantEnderBow.ID, EnchantGhast.ID, EnchantBomber.ID); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.arrowImplementation = ArrowImplementation.create(this, UniParticle.of(Particle.SMOKE_NORMAL)); + this.chanceImplementation = ChanceImplementation.create(this, + "10.0 + " + Placeholders.ENCHANTMENT_LEVEL + " * 5"); + this.explosionFireSpread = JOption.create("Settings.Explosion.Fire_Spread", true, + "When 'true' creates fire on nearby blocks.").read(cfg); + this.explosionDamageItems = JOption.create("Settings.Explosion.Damage_Items", false, + "When 'true' inflicts damage to items on the ground.").read(cfg); + this.explosionDamageBlocks = JOption.create("Settings.Explosion.Damage_Blocks", false, + "When 'true' allows to break blocks by explosion.").read(cfg); + this.explosionSize = EnchantScaler.read(this, "Settings.Explosion.Size", + "2.0 + " + Placeholders.ENCHANTMENT_LEVEL, + "Sets the explosion size. The more size - the bigger explosion."); + + this.addPlaceholder(PLACEHOLDER_EXPLOSION_POWER, level -> NumberUtil.format(this.getExplosionSize(level))); + } + + @NotNull + @Override + public ArrowImplementation getArrowImplementation() { + return arrowImplementation; + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + @NotNull + @Override + public EnchantmentTarget getCategory() { + return EnchantmentTarget.BOW; + } + + public final double getExplosionSize(int level) { + return this.explosionSize.getValue(level); + } + + public final boolean isExplosionFireSpread() { + return this.explosionFireSpread; + } + + public final boolean isExplosionDamageBlocks() { + return this.explosionDamageBlocks; + } + + @Override + public boolean onShoot(@NotNull EntityShootBowEvent event, @NotNull LivingEntity shooter, @NotNull ItemStack bow, int level) { + return this.checkTriggerChance(level); + } + + @Override + public boolean onHit(@NotNull ProjectileHitEvent event, LivingEntity user, @NotNull Projectile projectile, @NotNull ItemStack bow, int level) { + if (!this.isOurProjectile(projectile)) return false; + + if (projectile.getShooter() instanceof Entity entity) { + this.lastExploder = entity; + } + + World world = projectile.getWorld(); + float explSize = (float) this.getExplosionSize(level); + boolean explFire = this.isExplosionFireSpread(); + boolean explBlocks = this.isExplosionDamageBlocks(); + boolean exploded = world.createExplosion(projectile.getLocation(), explSize, explFire, explBlocks, this.lastExploder); + this.lastExploder = null; + return false; + } + + @Override + public boolean onDamage(@NotNull EntityDamageByEntityEvent event, @NotNull Projectile projectile, @NotNull LivingEntity shooter, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + return false; + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onItemDamage(EntityDamageByEntityEvent event) { + if (event.getCause() != EntityDamageEvent.DamageCause.ENTITY_EXPLOSION) return; + if (this.explosionDamageItems) return; + if (this.lastExploder == null || this.lastExploder != event.getDamager()) return; + + if (event.getEntity() instanceof Item || event.getEntity() instanceof ItemFrame) { + event.setCancelled(true); + } + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/bow/EnchantGhast.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/bow/EnchantGhast.java new file mode 100644 index 0000000..e162f55 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/bow/EnchantGhast.java @@ -0,0 +1,122 @@ +package su.nightexpress.excellentenchants.enchantment.impl.bow; + +import org.bukkit.enchantments.Enchantment; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.Fireball; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Projectile; +import org.bukkit.entity.SmallFireball; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityShootBowEvent; +import org.bukkit.event.entity.ProjectileHitEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.api.config.JOption; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.type.BowEnchant; +import su.nightexpress.excellentenchants.enchantment.config.EnchantScaler; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; +import su.nightexpress.excellentenchants.enchantment.util.EnchantUtils; + +public class EnchantGhast extends ExcellentEnchant implements BowEnchant, Chanced { + + public static final String ID = "ghast"; + + private boolean fireSpread; + private EnchantScaler yield; + private ChanceImplementation chanceImplementation; + + public EnchantGhast(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription("Shoots fireballs instead of arrows."); + this.getDefaults().setLevelMax(1); + this.getDefaults().setTier(0.3); + + this.getDefaults().setConflicts( + EnchantEnderBow.ID, EnchantBomber.ID, + EnchantExplosiveArrows.ID, EnchantPoisonedArrows.ID, EnchantConfusingArrows.ID, + EnchantWitheredArrows.ID, EnchantElectrifiedArrows.ID, EnchantDragonfireArrows.ID, + DarknessArrowsEnchant.ID, VampiricArrowsEnchant.ID, + EnchantHover.ID, FlareEnchant.ID, + Enchantment.ARROW_FIRE.getKey().getKey(), + Enchantment.ARROW_KNOCKBACK.getKey().getKey(), + Enchantment.ARROW_DAMAGE.getKey().getKey() + ); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.chanceImplementation = ChanceImplementation.create(this, "100"); + this.fireSpread = JOption.create("Settings.Fire_Spread", true, + "When 'true' creates fire on nearby blocks.").read(cfg); + this.yield = EnchantScaler.read(this, "Settings.Yield", "1.0 + " + Placeholders.ENCHANTMENT_LEVEL, + "Fireball explosion size/radius. The more value = the bigger the explosion."); + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + public boolean isFireSpread() { + return fireSpread; + } + + public float getYield(int level) { + return (float) this.yield.getValue(level); + } + + @Override + @NotNull + public EnchantmentTarget getCategory() { + return EnchantmentTarget.BOW; + } + + @NotNull + @Override + public EventPriority getShootPriority() { + return EventPriority.LOWEST; + } + + @Override + public boolean onShoot(@NotNull EntityShootBowEvent event, @NotNull LivingEntity shooter, @NotNull ItemStack bow, int level) { + if (!this.checkTriggerChance(level)) return false; + if (!(event.getProjectile() instanceof Projectile projectile)) return false; + + Fireball fireball; + + // Shoot small fireballs for the Multishot enchantment, + // as large ones has a slow speed and punches each other on shoot. + if (EnchantUtils.contains(bow, Enchantment.MULTISHOT)) { + fireball = shooter.launchProjectile(SmallFireball.class); + fireball.setVelocity(projectile.getVelocity().normalize().multiply(0.5f)); + } + else { + fireball = shooter.launchProjectile(Fireball.class); + fireball.setDirection(projectile.getVelocity()); + } + fireball.setIsIncendiary(this.isFireSpread()); + fireball.setYield(this.getYield(level)); + + event.setProjectile(fireball); + return true; + } + + @Override + public boolean onHit(@NotNull ProjectileHitEvent event, LivingEntity user, @NotNull Projectile projectile, @NotNull ItemStack bow, int level) { + return false; + } + + @Override + public boolean onDamage(@NotNull EntityDamageByEntityEvent event, @NotNull Projectile projectile, + @NotNull LivingEntity shooter, @NotNull LivingEntity victim, + @NotNull ItemStack weapon, int level) { + return false; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/bow/EnchantHover.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/bow/EnchantHover.java new file mode 100644 index 0000000..841b7e2 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/bow/EnchantHover.java @@ -0,0 +1,96 @@ +package su.nightexpress.excellentenchants.enchantment.impl.bow; + +import org.bukkit.Particle; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.AbstractArrow; +import org.bukkit.entity.Arrow; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Projectile; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityShootBowEvent; +import org.bukkit.event.entity.ProjectileHitEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffectType; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.utils.values.UniParticle; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Arrowed; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.meta.Potioned; +import su.nightexpress.excellentenchants.api.enchantment.type.BowEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ArrowImplementation; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; +import su.nightexpress.excellentenchants.enchantment.impl.meta.PotionImplementation; + +public class EnchantHover extends ExcellentEnchant implements Chanced, Arrowed, Potioned, BowEnchant { + + public static final String ID = "hover"; + + private ArrowImplementation arrowImplementation; + private ChanceImplementation chanceImplementation; + private PotionImplementation potionImplementation; + + public EnchantHover(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription(Placeholders.ENCHANTMENT_CHANCE + "% chance to launch an arrow with " + Placeholders.ENCHANTMENT_POTION_TYPE + " " + Placeholders.ENCHANTMENT_POTION_LEVEL + " (" + Placeholders.ENCHANTMENT_POTION_DURATION + "s.)"); + this.getDefaults().setLevelMax(3); + this.getDefaults().setTier(0.1); + this.getDefaults().setConflicts(EnchantEnderBow.ID, EnchantGhast.ID, EnchantBomber.ID); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.arrowImplementation = ArrowImplementation.create(this, UniParticle.of(Particle.BUBBLE_POP)); + this.chanceImplementation = ChanceImplementation.create(this, + "10.0 + " + Placeholders.ENCHANTMENT_LEVEL + " * 5"); + this.potionImplementation = PotionImplementation.create(this, PotionEffectType.LEVITATION, false, + "2.5 + " + Placeholders.ENCHANTMENT_LEVEL, + Placeholders.ENCHANTMENT_LEVEL); + } + + @NotNull + @Override + public ArrowImplementation getArrowImplementation() { + return arrowImplementation; + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + @NotNull + @Override + public PotionImplementation getPotionImplementation() { + return potionImplementation; + } + + @NotNull + @Override + public EnchantmentTarget getCategory() { + return EnchantmentTarget.BOW; + } + + @Override + public boolean onShoot(@NotNull EntityShootBowEvent event, @NotNull LivingEntity shooter, @NotNull ItemStack bow, int level) { + if (!(event.getProjectile() instanceof Arrow arrow)) return false; + if (!this.checkTriggerChance(level)) return false; + + arrow.setPickupStatus(AbstractArrow.PickupStatus.DISALLOWED); + return arrow.addCustomEffect(this.createEffect(level), true); + } + + @Override + public boolean onHit(@NotNull ProjectileHitEvent event, LivingEntity user, @NotNull Projectile projectile, @NotNull ItemStack bow, int level) { + return false; + } + + @Override + public boolean onDamage(@NotNull EntityDamageByEntityEvent event, @NotNull Projectile projectile, @NotNull LivingEntity shooter, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + return false; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/bow/EnchantPoisonedArrows.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/bow/EnchantPoisonedArrows.java new file mode 100644 index 0000000..5f26d3b --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/bow/EnchantPoisonedArrows.java @@ -0,0 +1,96 @@ +package su.nightexpress.excellentenchants.enchantment.impl.bow; + +import org.bukkit.Particle; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.AbstractArrow; +import org.bukkit.entity.Arrow; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Projectile; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityShootBowEvent; +import org.bukkit.event.entity.ProjectileHitEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffectType; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.utils.values.UniParticle; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Arrowed; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.meta.Potioned; +import su.nightexpress.excellentenchants.api.enchantment.type.BowEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ArrowImplementation; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; +import su.nightexpress.excellentenchants.enchantment.impl.meta.PotionImplementation; + +public class EnchantPoisonedArrows extends ExcellentEnchant implements Chanced, Arrowed, Potioned, BowEnchant { + + public static final String ID = "poisoned_arrows"; + + private ArrowImplementation arrowImplementation; + private ChanceImplementation chanceImplementation; + private PotionImplementation potionImplementation; + + public EnchantPoisonedArrows(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription(Placeholders.ENCHANTMENT_CHANCE + "% chance to launch an arrow with " + Placeholders.ENCHANTMENT_POTION_TYPE + " " + Placeholders.ENCHANTMENT_POTION_LEVEL + " (" + Placeholders.ENCHANTMENT_POTION_DURATION + "s.)"); + this.getDefaults().setLevelMax(3); + this.getDefaults().setTier(0.1); + this.getDefaults().setConflicts(EnchantEnderBow.ID, EnchantGhast.ID, EnchantBomber.ID); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.arrowImplementation = ArrowImplementation.create(this, UniParticle.of(Particle.SLIME)); + this.chanceImplementation = ChanceImplementation.create(this, + "25.0 + " + Placeholders.ENCHANTMENT_LEVEL + " * 5"); + this.potionImplementation = PotionImplementation.create(this, PotionEffectType.POISON, false, + "2.5 + " + Placeholders.ENCHANTMENT_LEVEL, + Placeholders.ENCHANTMENT_LEVEL); + } + + @NotNull + @Override + public ArrowImplementation getArrowImplementation() { + return arrowImplementation; + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + @NotNull + @Override + public PotionImplementation getPotionImplementation() { + return potionImplementation; + } + + @NotNull + @Override + public EnchantmentTarget getCategory() { + return EnchantmentTarget.BOW; + } + + @Override + public boolean onShoot(@NotNull EntityShootBowEvent event, @NotNull LivingEntity shooter, @NotNull ItemStack bow, int level) { + if (!(event.getProjectile() instanceof Arrow arrow)) return false; + if (!this.checkTriggerChance(level)) return false; + + arrow.setPickupStatus(AbstractArrow.PickupStatus.DISALLOWED); + return arrow.addCustomEffect(this.createEffect(level), true); + } + + @Override + public boolean onHit(@NotNull ProjectileHitEvent event, LivingEntity user, @NotNull Projectile projectile, @NotNull ItemStack bow, int level) { + return false; + } + + @Override + public boolean onDamage(@NotNull EntityDamageByEntityEvent event, @NotNull Projectile projectile, @NotNull LivingEntity shooter, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + return false; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/bow/EnchantWitheredArrows.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/bow/EnchantWitheredArrows.java new file mode 100644 index 0000000..028581f --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/bow/EnchantWitheredArrows.java @@ -0,0 +1,96 @@ +package su.nightexpress.excellentenchants.enchantment.impl.bow; + +import org.bukkit.Particle; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.AbstractArrow; +import org.bukkit.entity.Arrow; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Projectile; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityShootBowEvent; +import org.bukkit.event.entity.ProjectileHitEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffectType; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.utils.values.UniParticle; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Arrowed; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.meta.Potioned; +import su.nightexpress.excellentenchants.api.enchantment.type.BowEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ArrowImplementation; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; +import su.nightexpress.excellentenchants.enchantment.impl.meta.PotionImplementation; + +public class EnchantWitheredArrows extends ExcellentEnchant implements Chanced, Arrowed, Potioned, BowEnchant { + + public static final String ID = "withered_arrows"; + + private ArrowImplementation arrowImplementation; + private ChanceImplementation chanceImplementation; + private PotionImplementation potionImplementation; + + public EnchantWitheredArrows(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription(Placeholders.ENCHANTMENT_CHANCE + "% chance to launch an arrow with " + Placeholders.ENCHANTMENT_POTION_TYPE + " " + Placeholders.ENCHANTMENT_POTION_LEVEL + " (" + Placeholders.ENCHANTMENT_POTION_DURATION + "s.)"); + this.getDefaults().setLevelMax(3); + this.getDefaults().setTier(0.5); + this.getDefaults().setConflicts(EnchantEnderBow.ID, EnchantGhast.ID, EnchantBomber.ID); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.arrowImplementation = ArrowImplementation.create(this, UniParticle.of(Particle.SPELL_WITCH)); + this.chanceImplementation = ChanceImplementation.create(this, + "15.0 + " + Placeholders.ENCHANTMENT_LEVEL + " * 5.0"); + this.potionImplementation = PotionImplementation.create(this, PotionEffectType.WITHER, false, + "2.5 + " + Placeholders.ENCHANTMENT_LEVEL + " * 0.75", + Placeholders.ENCHANTMENT_LEVEL); + } + + @NotNull + @Override + public ArrowImplementation getArrowImplementation() { + return arrowImplementation; + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + @NotNull + @Override + public PotionImplementation getPotionImplementation() { + return potionImplementation; + } + + @NotNull + @Override + public EnchantmentTarget getCategory() { + return EnchantmentTarget.BOW; + } + + @Override + public boolean onShoot(@NotNull EntityShootBowEvent event, @NotNull LivingEntity shooter, @NotNull ItemStack bow, int level) { + if (!(event.getProjectile() instanceof Arrow arrow)) return false; + if (!this.checkTriggerChance(level)) return false; + + arrow.setPickupStatus(AbstractArrow.PickupStatus.DISALLOWED); + return arrow.addCustomEffect(this.createEffect(level), true); + } + + @Override + public boolean onHit(@NotNull ProjectileHitEvent event, LivingEntity user, @NotNull Projectile projectile, @NotNull ItemStack bow, int level) { + return false; + } + + @Override + public boolean onDamage(@NotNull EntityDamageByEntityEvent event, @NotNull Projectile projectile, @NotNull LivingEntity shooter, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + return false; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/bow/FlareEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/bow/FlareEnchant.java new file mode 100644 index 0000000..de983e3 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/bow/FlareEnchant.java @@ -0,0 +1,121 @@ +package su.nightexpress.excellentenchants.enchantment.impl.bow; + +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.data.Directional; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.Arrow; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityShootBowEvent; +import org.bukkit.event.entity.ProjectileHitEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.utils.values.UniParticle; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Arrowed; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.type.BowEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ArrowImplementation; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; + +public class FlareEnchant extends ExcellentEnchant implements Chanced, Arrowed, BowEnchant { + + public static final String ID = "flare"; + + private ChanceImplementation chanceImplementation; + private ArrowImplementation arrowImplementation; + + public FlareEnchant(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + + this.getDefaults().setDescription(Placeholders.ENCHANTMENT_CHANCE + "% chance to create a torch where arrow lands."); + this.getDefaults().setLevelMax(1); + this.getDefaults().setTier(0.4); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.chanceImplementation = ChanceImplementation.create(this, "100.0"); + this.arrowImplementation = ArrowImplementation.create(this, UniParticle.of(Particle.FIREWORKS_SPARK)); + } + + @NotNull + @Override + public EnchantmentTarget getCategory() { + return EnchantmentTarget.BOW; + } + + @NotNull + @Override + public EventPriority getHitPriority() { + return EventPriority.HIGHEST; + } + + @NotNull + @Override + public Chanced getChanceImplementation() { + return this.chanceImplementation; + } + + @NotNull + @Override + public ArrowImplementation getArrowImplementation() { + return this.arrowImplementation; + } + + @Override + public boolean onShoot(@NotNull EntityShootBowEvent event, @NotNull LivingEntity shooter, @NotNull ItemStack bow, int level) { + if (!(event.getProjectile() instanceof Arrow arrow)) return false; + if (!this.checkTriggerChance(level)) return false; + + this.addData(arrow); + return true; + } + + @Override + public boolean onHit(@NotNull ProjectileHitEvent e, LivingEntity user, @NotNull Projectile projectile, @NotNull ItemStack bow, int level) { + Block block = e.getHitBlock(); + if (block == null) return false; + + BlockFace face = e.getHitBlockFace(); + if (face == null || face == BlockFace.DOWN) return false; + + Block relative = block.getRelative(face); + if (!relative.getType().isAir()) return false; + + if (projectile.getShooter() instanceof Player player) { + BlockPlaceEvent event = new BlockPlaceEvent(relative, relative.getState(), block, new ItemStack(Material.TORCH), player,true, EquipmentSlot.HAND); + plugin.getPluginManager().callEvent(event); + if (event.isCancelled() || !event.canBuild()) return false; + } + + if (face == BlockFace.UP) { + relative.setType(Material.TORCH); + } + else { + relative.setType(Material.WALL_TORCH); + + Directional directional = (Directional) relative.getBlockData(); + directional.setFacing(face); + relative.setBlockData(directional, true); + } + + return false; + } + + @Override + public boolean onDamage(@NotNull EntityDamageByEntityEvent event, @NotNull Projectile projectile, @NotNull LivingEntity shooter, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + return false; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/bow/SniperEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/bow/SniperEnchant.java new file mode 100644 index 0000000..21be4fc --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/bow/SniperEnchant.java @@ -0,0 +1,95 @@ +package su.nightexpress.excellentenchants.enchantment.impl.bow; + +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Projectile; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityShootBowEvent; +import org.bukkit.event.entity.ProjectileHitEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.utils.NumberUtil; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.type.BowEnchant; +import su.nightexpress.excellentenchants.enchantment.config.EnchantScaler; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; + +public class SniperEnchant extends ExcellentEnchant implements BowEnchant, Chanced { + + public static final String ID = "sniper"; + + private static final String PLACEHOLDER_PROJECTILE_SPEED = "%enchantment_projectile_speed%"; + + private ChanceImplementation chanceImplementation; + private EnchantScaler speedModifier; + + public SniperEnchant(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + + this.getDefaults().setDescription("Increases projectile speed by " + PLACEHOLDER_PROJECTILE_SPEED + "%"); + this.getDefaults().setLevelMax(5); + this.getDefaults().setTier(0.3); + } + + @Override + public void loadSettings() { + super.loadSettings(); + + this.chanceImplementation = ChanceImplementation.create(this, "100.0"); + this.speedModifier = EnchantScaler.read(this, "Settings.Speed_Modifier", + "1.0 + " + Placeholders.ENCHANTMENT_LEVEL + " / 5.0", "Sets projectile's speed modifier."); + + this.addPlaceholder(PLACEHOLDER_PROJECTILE_SPEED, level -> NumberUtil.format(this.getSpeedModifier(level) * 100D)); + } + + @NotNull + @Override + public Chanced getChanceImplementation() { + return this.chanceImplementation; + } + + public double getSpeedModifier(int level) { + return this.speedModifier.getValue(level); + } + + @NotNull + @Override + public EnchantmentTarget getCategory() { + return EnchantmentTarget.BOW; + } + + @NotNull + @Override + public EventPriority getShootPriority() { + return EventPriority.LOWEST; + } + + @Override + public boolean onShoot(@NotNull EntityShootBowEvent event, @NotNull LivingEntity shooter, @NotNull ItemStack bow, int level) { + if (!this.checkTriggerChance(level)) return false; + + double modifier = this.getSpeedModifier(level); + + Entity entity = event.getProjectile(); + Vector vector = entity.getVelocity(); + entity.setVelocity(vector.multiply(modifier)); + + return true; + } + + @Override + public boolean onHit(@NotNull ProjectileHitEvent event, LivingEntity user, @NotNull Projectile projectile, @NotNull ItemStack bow, int level) { + return false; + } + + @Override + public boolean onDamage(@NotNull EntityDamageByEntityEvent event, @NotNull Projectile projectile, @NotNull LivingEntity shooter, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + return false; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/bow/VampiricArrowsEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/bow/VampiricArrowsEnchant.java new file mode 100644 index 0000000..c9600bf --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/bow/VampiricArrowsEnchant.java @@ -0,0 +1,127 @@ +package su.nightexpress.excellentenchants.enchantment.impl.bow; + +import org.bukkit.Color; +import org.bukkit.Particle; +import org.bukkit.attribute.Attribute; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.Arrow; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Projectile; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityRegainHealthEvent; +import org.bukkit.event.entity.EntityShootBowEvent; +import org.bukkit.event.entity.ProjectileHitEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.utils.EntityUtil; +import su.nexmedia.engine.utils.NumberUtil; +import su.nexmedia.engine.utils.values.UniParticle; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Arrowed; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.type.BowEnchant; +import su.nightexpress.excellentenchants.enchantment.config.EnchantScaler; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ArrowImplementation; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; + +public class VampiricArrowsEnchant extends ExcellentEnchant implements BowEnchant, Arrowed, Chanced { + + public static final String ID = "vampiric_arrows"; + + public static final String PLACEHOLDER_HEAL_AMOUNT = "%heal_amount%"; + + private ArrowImplementation arrowImplementation; + private ChanceImplementation chanceImplementation; + private EnchantScaler healAmount; + + public VampiricArrowsEnchant(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription(Placeholders.ENCHANTMENT_CHANCE + "% chance to restore " + PLACEHOLDER_HEAL_AMOUNT + "❤ on arrow hit."); + this.getDefaults().setLevelMax(3); + this.getDefaults().setTier(0.3); + this.getDefaults().setConflicts(EnchantEnderBow.ID, EnchantGhast.ID, EnchantBomber.ID); + } + + @Override + public void loadSettings() { + super.loadSettings(); + + this.arrowImplementation = ArrowImplementation.create(this, UniParticle.redstone(Color.RED, 1f)); + + this.chanceImplementation = ChanceImplementation.create(this, "20.0 * " + Placeholders.ENCHANTMENT_LEVEL); + + this.healAmount = EnchantScaler.read(this, "Settings.Heal_Amount", + Placeholders.ENCHANTMENT_LEVEL, + "Amount of health to be restored on hit."); + + this.addPlaceholder(PLACEHOLDER_HEAL_AMOUNT, level -> NumberUtil.format(this.getHealAmount(level))); + } + + @NotNull + @Override + public EnchantmentTarget getCategory() { + return EnchantmentTarget.BOW; + } + + @NotNull + @Override + public Arrowed getArrowImplementation() { + return this.arrowImplementation; + } + + @NotNull + @Override + public Chanced getChanceImplementation() { + return this.chanceImplementation; + } + + public double getHealAmount(int level) { + return this.healAmount.getValue(level); + } + + @NotNull + @Override + public EventPriority getDamagePriority() { + return EventPriority.HIGHEST; + } + + @Override + public boolean onShoot(@NotNull EntityShootBowEvent event, @NotNull LivingEntity shooter, @NotNull ItemStack bow, int level) { + if (!(event.getProjectile() instanceof Arrow arrow)) return false; + if (!this.checkTriggerChance(level)) return false; + + this.addData(arrow); + return true; + } + + @Override + public boolean onHit(@NotNull ProjectileHitEvent event, LivingEntity user, @NotNull Projectile projectile, @NotNull ItemStack bow, int level) { + return false; + } + + @Override + public boolean onDamage(@NotNull EntityDamageByEntityEvent event, @NotNull Projectile projectile, @NotNull LivingEntity shooter, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + if (shooter.isDead() || shooter.getHealth() <= 0D) return false; + + double healAmount = this.getHealAmount(level); + if (healAmount <= 0D) return false; + + double health = shooter.getHealth(); + double maxHealth = EntityUtil.getAttribute(shooter, Attribute.GENERIC_MAX_HEALTH); + if (health >= maxHealth) return false; + + EntityRegainHealthEvent healthEvent = new EntityRegainHealthEvent(shooter, healAmount, EntityRegainHealthEvent.RegainReason.CUSTOM); + plugin.getPluginManager().callEvent(healthEvent); + if (healthEvent.isCancelled()) return false; + + shooter.setHealth(Math.min(maxHealth, health + healAmount)); + + if (this.hasVisualEffects()) { + UniParticle.of(Particle.HEART).play(shooter.getEyeLocation(), 0.25f, 0.15f, 5); + } + return false; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/fishing/AutoReelEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/fishing/AutoReelEnchant.java new file mode 100644 index 0000000..dbc65fd --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/fishing/AutoReelEnchant.java @@ -0,0 +1,40 @@ +package su.nightexpress.excellentenchants.enchantment.impl.fishing; + +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.event.player.PlayerFishEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.api.enchantment.type.FishingEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; + +public class AutoReelEnchant extends ExcellentEnchant implements FishingEnchant { + + public static final String ID = "auto_reel"; + + public AutoReelEnchant(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription("Automatically reels in a hook on bite."); + this.getDefaults().setLevelMax(1); + this.getDefaults().setTier(1.0); + } + + @NotNull + @Override + public EnchantmentTarget getCategory() { + return EnchantmentTarget.FISHING_ROD; + } + + @Override + public boolean onFishing(@NotNull PlayerFishEvent event, @NotNull ItemStack item, int level) { + if (event.getState() != PlayerFishEvent.State.BITE) return false; + + this.plugin.runTask(task -> { + if (event.isCancelled()) return; + + plugin.getEnchantNMS().sendAttackPacket(event.getPlayer(), 0); + plugin.getEnchantNMS().retrieveHook(event.getHook(), item); + }); + return true; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/fishing/CurseOfDrownedEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/fishing/CurseOfDrownedEnchant.java new file mode 100644 index 0000000..ab2cd4a --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/fishing/CurseOfDrownedEnchant.java @@ -0,0 +1,70 @@ +package su.nightexpress.excellentenchants.enchantment.impl.fishing; + +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.Drowned; +import org.bukkit.entity.FishHook; +import org.bukkit.event.player.PlayerFishEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.utils.values.UniParticle; +import su.nexmedia.engine.utils.values.UniSound; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.type.FishingEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; + +public class CurseOfDrownedEnchant extends ExcellentEnchant implements FishingEnchant, Chanced { + + public static final String ID = "curse_of_drowned"; + + private ChanceImplementation chanceImplementation; + + public CurseOfDrownedEnchant(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription(Placeholders.ENCHANTMENT_CHANCE + "% chance to fish up a Drowned Zombie."); + this.getDefaults().setLevelMax(5); + this.getDefaults().setTier(0D); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.chanceImplementation = ChanceImplementation.create(this, + "5.0 + " + Placeholders.ENCHANTMENT_LEVEL); + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + @NotNull + @Override + public EnchantmentTarget getCategory() { + return EnchantmentTarget.FISHING_ROD; + } + + @Override + public boolean onFishing(@NotNull PlayerFishEvent event, @NotNull ItemStack item, int level) { + if (event.getState() != PlayerFishEvent.State.CAUGHT_FISH) return false; + if (!this.checkTriggerChance(level)) return false; + + FishHook hook = event.getHook(); + Drowned drowned = hook.getWorld().spawn(hook.getLocation(), Drowned.class); + hook.setHookedEntity(drowned); + hook.pullHookedEntity(); + + event.setCancelled(true); + + if (this.hasVisualEffects()) { + UniParticle.of(Particle.WATER_SPLASH).play(hook.getLocation(), 0.5, 0.1, 50); + UniSound.of(Sound.ENTITY_DROWNED_AMBIENT).play(event.getPlayer()); + } + return true; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/fishing/DoubleCatchEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/fishing/DoubleCatchEnchant.java new file mode 100644 index 0000000..160f7dd --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/fishing/DoubleCatchEnchant.java @@ -0,0 +1,66 @@ +package su.nightexpress.excellentenchants.enchantment.impl.fishing; + +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.Item; +import org.bukkit.event.EventPriority; +import org.bukkit.event.player.PlayerFishEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.type.FishingEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; + +public class DoubleCatchEnchant extends ExcellentEnchant implements FishingEnchant, Chanced { + + public static final String ID = "double_catch"; + + private ChanceImplementation chanceImplementation; + + public DoubleCatchEnchant(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription("Increases amount of caught item by x2 with " + Placeholders.ENCHANTMENT_CHANCE + "% chance."); + this.getDefaults().setLevelMax(3); + this.getDefaults().setTier(0.5); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.chanceImplementation = ChanceImplementation.create(this, + "10.0 * " + Placeholders.ENCHANTMENT_LEVEL); + } + + @NotNull + @Override + public EnchantmentTarget getCategory() { + return EnchantmentTarget.FISHING_ROD; + } + + @NotNull + @Override + public EventPriority getFishingPriority() { + return EventPriority.HIGHEST; + } + + @Override + @NotNull + public ChanceImplementation getChanceImplementation() { + return this.chanceImplementation; + } + + @Override + public boolean onFishing(@NotNull PlayerFishEvent event, @NotNull ItemStack item, int level) { + if (event.getState() != PlayerFishEvent.State.CAUGHT_FISH) return false; + if (!(event.getCaught() instanceof Item drop)) return false; + if (!this.checkTriggerChance(level)) return false; + + ItemStack stack = drop.getItemStack(); + stack.setAmount(Math.min(stack.getMaxStackSize(), stack.getAmount() * 2)); + drop.setItemStack(stack); + + return true; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/fishing/RiverMasterEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/fishing/RiverMasterEnchant.java new file mode 100644 index 0000000..a29c9ab --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/fishing/RiverMasterEnchant.java @@ -0,0 +1,68 @@ +package su.nightexpress.excellentenchants.enchantment.impl.fishing; + +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.FishHook; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.ProjectileLaunchEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.api.manager.EventListener; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.type.GenericEnchant; +import su.nightexpress.excellentenchants.enchantment.config.EnchantScaler; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.util.EnchantUtils; + +public class RiverMasterEnchant extends ExcellentEnchant implements GenericEnchant, EventListener { + + public static final String ID = "river_master"; + + private EnchantScaler distanceMod; + + public RiverMasterEnchant(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription("Increases casting distance."); + this.getDefaults().setLevelMax(5); + this.getDefaults().setTier(0.1); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.distanceMod = EnchantScaler.read(this, "Settings.Distance_Modifier", + "1.25 + " + Placeholders.ENCHANTMENT_LEVEL + " / 5", + "Multiplies the casted fish hook's velocity by specified value.", + "Setting too high values will result in hook auto removal by vanilla game/server mechanics."); + } + + @NotNull + @Override + public EnchantmentTarget getCategory() { + return EnchantmentTarget.FISHING_ROD; + } + + public double getDistanceMod(int level) { + return this.distanceMod.getValue(level); + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onHookLaunch(ProjectileLaunchEvent event) { + if (!(event.getEntity() instanceof FishHook hook)) return; + if (!(hook.getShooter() instanceof Player player)) return; + + ItemStack rod = EnchantUtils.getFishingRod(player); + if (rod == null) return; + + int level = EnchantUtils.getLevel(rod, this.getBackend()); + if (level < 1) return; + + if (this.isOutOfCharges(rod)) return; + + hook.setVelocity(hook.getVelocity().multiply(this.getDistanceMod(level))); + + this.consumeCharges(rod, level); + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/fishing/SeasonedAnglerEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/fishing/SeasonedAnglerEnchant.java new file mode 100644 index 0000000..2398fb8 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/fishing/SeasonedAnglerEnchant.java @@ -0,0 +1,59 @@ +package su.nightexpress.excellentenchants.enchantment.impl.fishing; + +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.event.player.PlayerFishEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.utils.NumberUtil; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.type.FishingEnchant; +import su.nightexpress.excellentenchants.enchantment.config.EnchantScaler; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; + +public class SeasonedAnglerEnchant extends ExcellentEnchant implements FishingEnchant { + + public static final String ID = "seasoned_angler"; + + private EnchantScaler expMod; + + public SeasonedAnglerEnchant(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription("Increases amount of XP gained from fishing by " + Placeholders.GENERIC_AMOUNT + "%."); + this.getDefaults().setLevelMax(4); + this.getDefaults().setTier(0.1); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.expMod = EnchantScaler.read(this, "Settings.Exp_Percent", + "25.0 * " + Placeholders.ENCHANTMENT_LEVEL, + "Amount (in percent) of additional XP from fishing."); + + this.addPlaceholder(Placeholders.GENERIC_AMOUNT, level -> NumberUtil.format(this.getExpPercent(level))); + } + + public int getExpPercent(int level) { + return (int) this.expMod.getValue(level); + } + + @NotNull + @Override + public EnchantmentTarget getCategory() { + return EnchantmentTarget.FISHING_ROD; + } + + @Override + public boolean onFishing(@NotNull PlayerFishEvent event, @NotNull ItemStack item, int level) { + if (event.getState() != PlayerFishEvent.State.CAUGHT_FISH) return false; + if (event.getExpToDrop() == 0) return false; + + int expDrop = event.getExpToDrop(); + int expPercent = this.getExpPercent(level); + int expModified = (int) Math.ceil(expDrop * (1D + expPercent / 100D)); + + event.setExpToDrop(expModified); + return true; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/fishing/SurvivalistEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/fishing/SurvivalistEnchant.java new file mode 100644 index 0000000..c9ab4d0 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/fishing/SurvivalistEnchant.java @@ -0,0 +1,83 @@ +package su.nightexpress.excellentenchants.enchantment.impl.fishing; + +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.Item; +import org.bukkit.event.EventPriority; +import org.bukkit.event.player.PlayerFishEvent; +import org.bukkit.inventory.CookingRecipe; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.type.FishingEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; + +import java.util.HashSet; +import java.util.Set; + +public class SurvivalistEnchant extends ExcellentEnchant implements FishingEnchant, Chanced { + + public static final String ID = "survivalist"; + + private final Set> cookingRecipes; + + private ChanceImplementation chanceImplementation; + + public SurvivalistEnchant(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription("Automatically cooks fish if what is caught is raw."); + this.getDefaults().setLevelMax(1); + this.getDefaults().setTier(0.4); + + this.cookingRecipes = new HashSet<>(); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.chanceImplementation = ChanceImplementation.create(this, "100"); + + this.cookingRecipes.clear(); + this.plugin.getServer().recipeIterator().forEachRemaining(recipe -> { + if (recipe instanceof CookingRecipe cookingRecipe) { + this.cookingRecipes.add(cookingRecipe); + } + }); + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + @NotNull + @Override + public EnchantmentTarget getCategory() { + return EnchantmentTarget.FISHING_ROD; + } + + @NotNull + @Override + public EventPriority getFishingPriority() { + return EventPriority.HIGH; + } + + @Override + public boolean onFishing(@NotNull PlayerFishEvent event, @NotNull ItemStack item, int level) { + if (event.getState() != PlayerFishEvent.State.CAUGHT_FISH) return false; + if (!this.checkTriggerChance(level)) return false; + if (!(event.getCaught() instanceof Item drop)) return false; + + ItemStack stack = drop.getItemStack(); + + CookingRecipe recipe = this.cookingRecipes.stream().filter(r -> r.getInput().isSimilar(stack)).findFirst().orElse(null); + if (recipe == null) return false; + + ItemStack cooked = recipe.getResult(); + cooked.setAmount(stack.getAmount()); + drop.setItemStack(cooked); + return false; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/meta/ArrowImplementation.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/meta/ArrowImplementation.java new file mode 100644 index 0000000..c4aaf56 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/meta/ArrowImplementation.java @@ -0,0 +1,87 @@ +package su.nightexpress.excellentenchants.enchantment.impl.meta; + +import org.bukkit.NamespacedKey; +import org.bukkit.Particle; +import org.bukkit.entity.Projectile; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import su.nexmedia.engine.api.config.JOption; +import su.nexmedia.engine.api.config.JYML; +import su.nexmedia.engine.utils.PDCUtil; +import su.nexmedia.engine.utils.values.UniParticle; +import su.nightexpress.excellentenchants.ExcellentEnchantsAPI; +import su.nightexpress.excellentenchants.api.enchantment.meta.Arrowed; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.task.ArrowTrailsTask; + +import java.util.Optional; + +public final class ArrowImplementation implements Arrowed { + + private final ExcellentEnchant enchant; + private final NamespacedKey projectileKey; + + private final UniParticle trailParticle; + + private ArrowImplementation(@NotNull ExcellentEnchant enchant, @Nullable UniParticle trailParticle) { + this.enchant = enchant; + this.projectileKey = new NamespacedKey(ExcellentEnchantsAPI.PLUGIN, "arrow.enchant_id"); + this.trailParticle = trailParticle; + } + + @NotNull + public static ArrowImplementation create(@NotNull ExcellentEnchant enchant) { + return create(enchant, UniParticle.of(Particle.REDSTONE)); + } + + @NotNull + public static ArrowImplementation create(@NotNull ExcellentEnchant enchant, @NotNull UniParticle particle) { + JYML cfg = enchant.getConfig(); + + UniParticle effect = new JOption<>("Settings.Arrow.Trail_Effect", + (cfg1, path, def) -> UniParticle.read(cfg1, path), + particle, + "Sets particle effect for the arrow trail of this enchantment." + ).setWriter((cfg1, path, particle1) -> particle1.write(cfg1, path)).read(cfg); + + return new ArrowImplementation(enchant, effect); + } + + @Override + @NotNull + public Arrowed getArrowImplementation() { + return this; + } + + @Override + public void addTrail(@NotNull Projectile projectile) { + if (!this.enchant.hasVisualEffects()) return; + if (this.getTrailParticle().isEmpty()) return; + + this.getTrailParticle().ifPresent(particle -> { + ArrowTrailsTask.add(projectile, particle); + }); + } + + @NotNull + @Override + public Optional getTrailParticle() { + return trailParticle == null ? Optional.empty() : Optional.of(trailParticle); + } + + @NotNull + public NamespacedKey getProjectileKey() { + return projectileKey; + } + + @Override + public void addData(@NotNull Projectile projectile) { + PDCUtil.set(projectile, this.getProjectileKey(), this.enchant.getId()); + } + + @Override + public boolean isOurProjectile(@NotNull Projectile projectile) { + String enchantId = PDCUtil.getString(projectile, this.getProjectileKey()).orElse(null); + return this.enchant.getId().equalsIgnoreCase(enchantId); + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/meta/ChanceImplementation.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/meta/ChanceImplementation.java new file mode 100644 index 0000000..c1bad7f --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/meta/ChanceImplementation.java @@ -0,0 +1,45 @@ +package su.nightexpress.excellentenchants.enchantment.impl.meta; + +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.utils.random.Rnd; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.enchantment.config.EnchantScaler; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; + +public final class ChanceImplementation implements Chanced { + + //private final ExcellentEnchant enchant; + private final EnchantScaler triggerChance; + + private ChanceImplementation(@NotNull ExcellentEnchant enchant, @NotNull EnchantScaler triggerChance) { + //this.enchant = enchant; + this.triggerChance = triggerChance; + } + + @NotNull + public static ChanceImplementation create(@NotNull ExcellentEnchant enchant) { + return create(enchant, "100"); + } + + @NotNull + public static ChanceImplementation create(@NotNull ExcellentEnchant enchant, @NotNull String def) { + return new ChanceImplementation(enchant, EnchantScaler.read(enchant, "Settings.Trigger_Chance", def, + "A chance that this enchantment will be triggered.")); + } + + @Override + @NotNull + public Chanced getChanceImplementation() { + return this; + } + + @Override + public double getTriggerChance(int level) { + return this.triggerChance.getValue(level); + } + + @Override + public boolean checkTriggerChance(int level) { + return Rnd.chance(this.getTriggerChance(level)); + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/meta/PeriodImplementation.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/meta/PeriodImplementation.java new file mode 100644 index 0000000..a7219f1 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/meta/PeriodImplementation.java @@ -0,0 +1,56 @@ +package su.nightexpress.excellentenchants.enchantment.impl.meta; + +import org.jetbrains.annotations.NotNull; +import su.nightexpress.excellentenchants.api.enchantment.meta.Periodic; +import su.nightexpress.excellentenchants.enchantment.config.EnchantScaler; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; + +public class PeriodImplementation implements Periodic { + + private final EnchantScaler triggerInterval; + + private long nextTriggerTime; + + public PeriodImplementation(@NotNull ExcellentEnchant enchant, @NotNull EnchantScaler triggerInterval) { + this.triggerInterval = triggerInterval; + this.updateTriggerTime(); + } + + @NotNull + public static PeriodImplementation create(@NotNull ExcellentEnchant enchant) { + return create(enchant, "100"); + } + + @NotNull + public static PeriodImplementation create(@NotNull ExcellentEnchant enchant, @NotNull String def) { + return new PeriodImplementation(enchant, EnchantScaler.read(enchant, "Settings.Trigger_Interval", def, + "Sets how often (in ticks) this enchantment will be triggered.", + "20 ticks = 1 second.")); + } + + @NotNull + @Override + public Periodic getPeriodImplementation() { + return this; + } + + @Override + public long getInterval() { + return (long) this.triggerInterval.getValue(1); + } + + @Override + public long getNextTriggerTime() { + return nextTriggerTime; + } + + @Override + public boolean isTriggerTime() { + return System.currentTimeMillis() >= this.getNextTriggerTime(); + } + + @Override + public void updateTriggerTime() { + this.nextTriggerTime = System.currentTimeMillis() + this.getInterval() * 50L - 100L; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/meta/PotionImplementation.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/meta/PotionImplementation.java new file mode 100644 index 0000000..a15c83d --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/meta/PotionImplementation.java @@ -0,0 +1,91 @@ +package su.nightexpress.excellentenchants.enchantment.impl.meta; + +import org.bukkit.entity.LivingEntity; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.jetbrains.annotations.NotNull; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Potioned; +import su.nightexpress.excellentenchants.config.Config; +import su.nightexpress.excellentenchants.enchantment.config.EnchantScaler; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; + +public final class PotionImplementation implements Potioned { + + private final ExcellentEnchant enchant; + private final PotionEffectType effectType; + private final EnchantScaler duration; + private final EnchantScaler amplifier; + private final boolean isPermanent; + + private PotionImplementation(@NotNull ExcellentEnchant enchant, + @NotNull PotionEffectType effectType, boolean isPermanent, + @NotNull EnchantScaler duration, @NotNull EnchantScaler amplifier) { + this.enchant = enchant; + this.effectType = effectType; + this.duration = duration; + this.amplifier = amplifier; + this.isPermanent = isPermanent; + } + + @Override + @NotNull + public Potioned getPotionImplementation() { + return this; + } + + public static PotionImplementation create(@NotNull ExcellentEnchant enchant, @NotNull PotionEffectType type, boolean isPermanent) { + return create(enchant, type, isPermanent, "5 * " + Placeholders.ENCHANTMENT_LEVEL, Placeholders.ENCHANTMENT_LEVEL); + } + + public static PotionImplementation create(@NotNull ExcellentEnchant enchant, + @NotNull PotionEffectType type, boolean isPermanent, + @NotNull String duration, @NotNull String amplifier) { + + EnchantScaler durationScale = EnchantScaler.read(enchant, "Settings.Potion_Effect.Duration", duration, + "Potion effect duration (in seconds). This setting is useless for 'permanent' effects."); + + EnchantScaler amplifierScale = EnchantScaler.read(enchant, "Settings.Potion_Effect.Level", amplifier, + "Potion effect level."); + + return new PotionImplementation(enchant, type, isPermanent, durationScale, amplifierScale); + } + + @Override + public boolean isPermanent() { + return this.isPermanent; + } + + @NotNull + public PotionEffectType getEffectType() { + return this.effectType; + } + + public int getEffectDuration(int level) { + if (this.isPermanent()) { + int duration = Config.TASKS_PASSIVE_ENCHANTS_TRIGGER_INTERVAL.get().intValue() * 2; + if (this.getEffectType().getName().equalsIgnoreCase(PotionEffectType.NIGHT_VISION.getName()) && duration < 600) { + duration += 30 * 20; + } + return duration; + } + return (int) (this.duration.getValue(level) * 20); + } + + public int getEffectAmplifier(int level) { + return (int) this.amplifier.getValue(level); + } + + @NotNull + public PotionEffect createEffect(int level) { + int duration = this.getEffectDuration(level); + int amplifier = Math.max(0, this.getEffectAmplifier(level) - 1); + + return new PotionEffect(this.getEffectType(), duration, amplifier, false, this.enchant.hasVisualEffects()); + } + + public boolean addEffect(@NotNull LivingEntity target, int level) { + target.addPotionEffect(this.createEffect(level)); + return true; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/tool/BlastMiningEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/tool/BlastMiningEnchant.java new file mode 100644 index 0000000..fd5ebda --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/tool/BlastMiningEnchant.java @@ -0,0 +1,150 @@ +package su.nightexpress.excellentenchants.enchantment.impl.tool; + +import org.bukkit.block.Block; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityDamageEvent.DamageCause; +import org.bukkit.event.entity.EntityExplodeEvent; +import org.bukkit.event.player.PlayerItemDamageEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.api.manager.EventListener; +import su.nexmedia.engine.utils.NumberUtil; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.type.BlockBreakEnchant; +import su.nightexpress.excellentenchants.enchantment.config.EnchantScaler; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; +import su.nightexpress.excellentenchants.api.enchantment.ItemCategory; +import su.nightexpress.excellentenchants.enchantment.util.EnchantUtils; +import su.nightexpress.excellentenchants.hook.impl.NoCheatPlusHook; + +import java.util.List; + +public class BlastMiningEnchant extends ExcellentEnchant implements Chanced, BlockBreakEnchant, EventListener { + + public static final String ID = "blast_mining"; + public static final String PLACEHOLDER_EXPLOSION_POWER = "%enchantment_explosion_power%"; + + private EnchantScaler explosionPower; + private EnchantScaler minBlockStrength; + private ChanceImplementation chanceImplementation; + + private int explodeLevel; + + public BlastMiningEnchant(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription(Placeholders.ENCHANTMENT_CHANCE + "% chance to mine blocks by explosion."); + this.getDefaults().setLevelMax(5); + this.getDefaults().setTier(1.0); + this.getDefaults().setConflicts(VeinminerEnchant.ID, TunnelEnchant.ID); + } + + @Override + public void loadSettings() { + super.loadSettings(); + + this.chanceImplementation = ChanceImplementation.create(this, + "20.0 * " + Placeholders.ENCHANTMENT_LEVEL); + + this.explosionPower = EnchantScaler.read(this, "Settings.Explosion.Power", + "3.0 + (" + Placeholders.ENCHANTMENT_LEVEL + " - 1.0 * 0.25)", + "Explosion power. The more power = the more blocks (area) to explode."); + + this.minBlockStrength = EnchantScaler.read(this, "Settings.Min_Block_Strength", + "1.5 - " + Placeholders.ENCHANTMENT_LEVEL + " / 10", + "Minimal block strength value for the enchantment to have effect.", + "Block strength value is how long it takes to break the block by a hand.", + "For example, a Stone has 3.0 strength."); + + this.addPlaceholder(PLACEHOLDER_EXPLOSION_POWER, level -> NumberUtil.format(this.getExplosionPower(level))); + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + public double getExplosionPower(int level) { + return this.explosionPower.getValue(level); + } + + public float getMinBlockStrength(int level) { + return (float) minBlockStrength.getValue(level); + } + + private boolean isHardEnough(@NotNull Block block, int level) { + float strength = block.getType().getHardness(); + return (strength >= this.getMinBlockStrength(level)); + } + + @Override + @NotNull + public ItemCategory[] getFitItemTypes() { + return new ItemCategory[]{ItemCategory.PICKAXE}; + } + + @Override + @NotNull + public EnchantmentTarget getCategory() { + return EnchantmentTarget.TOOL; + } + + @Override + public boolean onBreak(@NotNull BlockBreakEvent event, @NotNull LivingEntity entity, @NotNull ItemStack item, int level) { + if (!(entity instanceof Player player)) return false; + if (EnchantUtils.isBusy()) return false; + + Block block = event.getBlock(); + if (!this.isHardEnough(block, level)) return false; + if (!this.checkTriggerChance(level)) return false; + + float power = (float) this.getExplosionPower(level); + + this.explodeLevel = level; + NoCheatPlusHook.exemptBlocks(player); + boolean exploded = block.getWorld().createExplosion(block.getLocation(), power, false, true, player); + NoCheatPlusHook.unexemptBlocks(player); + this.explodeLevel = -1; + + return exploded; + } + + // Process explosion event to mine blocks. + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onBlastExplosionEvent(EntityExplodeEvent event) { + if (this.explodeLevel <= 0) return; + if (!(event.getEntity() instanceof Player player)) return; + + List blockList = event.blockList(); + blockList.forEach(block -> { + if (block.getLocation().equals(event.getLocation()) || !this.isHardEnough(block, this.explodeLevel)) return; + + EnchantUtils.safeBusyBreak(player, block); + }); + blockList.clear(); + } + + // Do not damage around entities by enchantment explosion. + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onBlastExplosionDamage(EntityDamageByEntityEvent e) { + if (e.getCause() != DamageCause.ENTITY_EXPLOSION) return; + if (!(e.getDamager() instanceof Player player)) return; + + e.setCancelled(this.explodeLevel > 0); + } + + // Do not reduce item durability for 'exploded' blocks. + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onBlastExplosionItemDamage(PlayerItemDamageEvent e) { + e.setCancelled(this.explodeLevel > 0); + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/tool/CurseOfBreakingEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/tool/CurseOfBreakingEnchant.java new file mode 100644 index 0000000..fae2ab2 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/tool/CurseOfBreakingEnchant.java @@ -0,0 +1,85 @@ +package su.nightexpress.excellentenchants.enchantment.impl.tool; + +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.player.PlayerItemDamageEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.api.manager.EventListener; +import su.nexmedia.engine.utils.NumberUtil; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.type.GenericEnchant; +import su.nightexpress.excellentenchants.enchantment.config.EnchantScaler; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; +import su.nightexpress.excellentenchants.enchantment.util.EnchantUtils; + +public class CurseOfBreakingEnchant extends ExcellentEnchant implements GenericEnchant, EventListener, Chanced { + + public static final String ID = "curse_of_breaking"; + public static final String PLACEHOLDER_DURABILITY_AMOUNT = "%enchantment_durability_amount%"; + + private EnchantScaler durabilityAmount; + private ChanceImplementation chanceImplementation; + + public CurseOfBreakingEnchant(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription(Placeholders.ENCHANTMENT_CHANCE + "% chance to consume extra " + PLACEHOLDER_DURABILITY_AMOUNT + " durability points."); + this.getDefaults().setLevelMax(3); + this.getDefaults().setTier(0D); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.chanceImplementation = ChanceImplementation.create(this, + "10.0 * " + Placeholders.ENCHANTMENT_LEVEL); + this.durabilityAmount = EnchantScaler.read(this, "Settings.Durability_Amount", + Placeholders.ENCHANTMENT_LEVEL, + "Amount of durability points to be taken from the item."); + + this.addPlaceholder(PLACEHOLDER_DURABILITY_AMOUNT, level -> NumberUtil.format(this.getDurabilityAmount(level))); + } + + @Override + public boolean isCurse() { + return true; + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + public int getDurabilityAmount(int level) { + return (int) this.durabilityAmount.getValue(level); + } + + @NotNull + @Override + public EnchantmentTarget getCategory() { + return EnchantmentTarget.BREAKABLE; + } + + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onItemDurability(PlayerItemDamageEvent event) { + Player player = event.getPlayer(); + if (!this.isAvailableToUse(player)) return; + + ItemStack item = event.getItem(); + int level = EnchantUtils.getLevel(item, this.getBackend()); + + if (level < 1) return; + if (!this.checkTriggerChance(level)) return; + + int durabilityAmount = this.getDurabilityAmount(level); + if (durabilityAmount <= 0) return; + + event.setDamage(event.getDamage() + durabilityAmount); + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/tool/CurseOfMediocrityEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/tool/CurseOfMediocrityEnchant.java new file mode 100644 index 0000000..fe9de55 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/tool/CurseOfMediocrityEnchant.java @@ -0,0 +1,110 @@ +package su.nightexpress.excellentenchants.enchantment.impl.tool; + +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockDropItemEvent; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.event.entity.EntityResurrectEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.utils.ItemUtil; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.type.BlockDropEnchant; +import su.nightexpress.excellentenchants.api.enchantment.type.DeathEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; +import su.nightexpress.excellentenchants.api.enchantment.ItemCategory; + +public class CurseOfMediocrityEnchant extends ExcellentEnchant implements Chanced, BlockDropEnchant, DeathEnchant { + + public static final String ID = "curse_of_mediocrity"; + + private ChanceImplementation chanceImplementation; + + public CurseOfMediocrityEnchant(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription(Placeholders.ENCHANTMENT_CHANCE + "% chance to disenchant item drops."); + this.getDefaults().setLevelMax(3); + this.getDefaults().setTier(0D); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.chanceImplementation = ChanceImplementation.create(this, "25.0 * " + Placeholders.ENCHANTMENT_LEVEL); + } + + @NotNull + @Override + public EnchantmentTarget getCategory() { + return EnchantmentTarget.BREAKABLE; + } + + @Override + @NotNull + public ItemCategory[] getFitItemTypes() { + return new ItemCategory[] { + ItemCategory.SWORD, ItemCategory.BOW, ItemCategory.CROSSBOW, ItemCategory.TRIDENT, ItemCategory.TOOL + }; + } + + @NotNull + @Override + public EventPriority getDropPriority() { + return EventPriority.HIGHEST; + } + + @NotNull + @Override + public Chanced getChanceImplementation() { + return this.chanceImplementation; + } + + @Override + public boolean isCurse() { + return true; + } + + @Override + public boolean onDrop(@NotNull BlockDropItemEvent event, + @NotNull LivingEntity player, @NotNull ItemStack item, int level) { + if (!this.checkTriggerChance(level)) return false; + + event.getItems().forEach(drop -> { + ItemStack stack = drop.getItemStack(); + ItemUtil.mapMeta(stack, meta -> { + meta.getEnchants().keySet().forEach(meta::removeEnchant); + }); + drop.setItemStack(stack); + }); + + return true; + } + + @Override + public boolean onDeath(@NotNull EntityDeathEvent event, @NotNull LivingEntity entity, ItemStack item, int level) { + return false; + } + + @Override + public boolean onResurrect(@NotNull EntityResurrectEvent event, @NotNull LivingEntity entity, @NotNull ItemStack item, int level) { + return false; + } + + @Override + public boolean onKill(@NotNull EntityDeathEvent event, @NotNull LivingEntity entity, @NotNull Player killer, ItemStack weapon, int level) { + if (!this.checkTriggerChance(level)) return false; + + event.getDrops().forEach(stack -> { + ItemUtil.mapMeta(stack, meta -> { + meta.getEnchants().keySet().forEach(meta::removeEnchant); + }); + }); + + return true; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/tool/CurseOfMisfortuneEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/tool/CurseOfMisfortuneEnchant.java new file mode 100644 index 0000000..1c1d833 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/tool/CurseOfMisfortuneEnchant.java @@ -0,0 +1,119 @@ +package su.nightexpress.excellentenchants.enchantment.impl.tool; + +import org.bukkit.enchantments.Enchantment; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.Item; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.event.entity.EntityResurrectEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.api.config.JOption; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.type.BlockBreakEnchant; +import su.nightexpress.excellentenchants.api.enchantment.type.DeathEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; +import su.nightexpress.excellentenchants.api.enchantment.ItemCategory; + +public class CurseOfMisfortuneEnchant extends ExcellentEnchant implements Chanced, BlockBreakEnchant, DeathEnchant { + + public static final String ID = "curse_of_misfortune"; + + private boolean dropExp; + private ChanceImplementation chanceImplementation; + + public CurseOfMisfortuneEnchant(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription(Placeholders.ENCHANTMENT_CHANCE + "% chance to have no drops from blocks or mobs."); + this.getDefaults().setLevelMax(3); + this.getDefaults().setTier(0D); + this.getDefaults().setConflicts( + Enchantment.LOOT_BONUS_BLOCKS.getKey().getKey(), + Enchantment.LOOT_BONUS_MOBS.getKey().getKey() + ); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.chanceImplementation = ChanceImplementation.create(this, + "20.0 * " + Placeholders.ENCHANTMENT_LEVEL); + this.dropExp = JOption.create("Settings.Drop_Exp", false, + "When 'true' allows to drop exp from mobs/blocks.").read(cfg); + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + public boolean isDropExp() { + return dropExp; + } + + @Override + @NotNull + public ItemCategory[] getFitItemTypes() { + return new ItemCategory[] { + ItemCategory.SWORD, ItemCategory.BOW, ItemCategory.CROSSBOW, ItemCategory.TRIDENT, ItemCategory.TOOL + }; + } + + @NotNull + @Override + public EnchantmentTarget getCategory() { + return EnchantmentTarget.BREAKABLE; + } + + @NotNull + @Override + public EventPriority getBreakPriority() { + return EventPriority.HIGHEST; + } + + @NotNull + @Override + public EventPriority getKillPriority() { + return EventPriority.HIGH; + } + + @Override + public boolean isCurse() { + return true; + } + + @Override + public boolean onBreak(@NotNull BlockBreakEvent event, @NotNull LivingEntity player, @NotNull ItemStack item, int level) { + if (!this.checkTriggerChance(level)) return false; + + event.setDropItems(false); + if (!this.isDropExp()) event.setExpToDrop(0); + return true; + } + + @Override + public boolean onKill(@NotNull EntityDeathEvent event, @NotNull LivingEntity entity, @NotNull Player killer, ItemStack weapon, int level) { + if (!this.checkTriggerChance(level)) return false; + + event.getDrops().clear(); + if (!this.isDropExp()) event.setDroppedExp(0); + return true; + } + + @Override + public boolean onDeath(@NotNull EntityDeathEvent event, @NotNull LivingEntity entity, ItemStack item, int level) { + return false; + } + + @Override + public boolean onResurrect(@NotNull EntityResurrectEvent event, @NotNull LivingEntity entity, @NotNull ItemStack item, int level) { + return false; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/tool/DivineTouchEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/tool/DivineTouchEnchant.java new file mode 100644 index 0000000..6e8270d --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/tool/DivineTouchEnchant.java @@ -0,0 +1,168 @@ +package su.nightexpress.excellentenchants.enchantment.impl.tool; + +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.Particle; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.block.CreatureSpawner; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockDropItemEvent; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.BlockStateMeta; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.api.config.JOption; +import su.nexmedia.engine.api.manager.EventListener; +import su.nexmedia.engine.lang.LangManager; +import su.nexmedia.engine.utils.Colorizer; +import su.nexmedia.engine.utils.Colors; +import su.nexmedia.engine.utils.LocationUtil; +import su.nexmedia.engine.utils.PDCUtil; +import su.nexmedia.engine.utils.values.UniParticle; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.type.BlockBreakEnchant; +import su.nightexpress.excellentenchants.api.enchantment.type.BlockDropEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; +import su.nightexpress.excellentenchants.api.enchantment.ItemCategory; +import su.nightexpress.excellentenchants.enchantment.util.EnchantUtils; + +public class DivineTouchEnchant extends ExcellentEnchant implements Chanced, BlockBreakEnchant, BlockDropEnchant, EventListener { + + public static final String ID = "divine_touch"; + + private final NamespacedKey key; + + private String spawnerName; + private ChanceImplementation chanceImplementation; + + private Location handleSpawner; + + public DivineTouchEnchant(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.key = new NamespacedKey(plugin, "divine_spawner"); + + this.getDefaults().setDescription(Placeholders.ENCHANTMENT_CHANCE + "% chance to mine spawner."); + this.getDefaults().setLevelMax(5); + this.getDefaults().setTier(1.0); + this.getDefaults().setConflicts(SmelterEnchant.ID); + } + + @Override + public void loadSettings() { + super.loadSettings(); + + this.chanceImplementation = ChanceImplementation.create(this, + "15.0 * " + Placeholders.ENCHANTMENT_LEVEL); + + this.spawnerName = JOption.create("Settings.Spawner_Item.Name", + Colors.GREEN + "Mob Spawner " + Colors.GRAY + "(" + Placeholders.GENERIC_TYPE + ")", + "Spawner item display name.", + "Placeholder '" + Placeholders.GENERIC_TYPE + "' for the mob type." + ).mapReader(Colorizer::apply).read(cfg); + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + @Override + @NotNull + public ItemCategory[] getFitItemTypes() { + return new ItemCategory[]{ItemCategory.PICKAXE}; + } + + @Override + @NotNull + public EnchantmentTarget getCategory() { + return EnchantmentTarget.TOOL; + } + + @NotNull + @Override + public EventPriority getDropPriority() { + return EventPriority.NORMAL; + } + + @NotNull + @Override + public EventPriority getBreakPriority() { + return EventPriority.HIGH; + } + + @NotNull + public ItemStack getSpawner(@NotNull CreatureSpawner spawnerBlock) { + ItemStack itemSpawner = new ItemStack(Material.SPAWNER); + BlockStateMeta stateItem = (BlockStateMeta) itemSpawner.getItemMeta(); + if (stateItem == null || spawnerBlock.getSpawnedType() == null) return itemSpawner; + + CreatureSpawner spawnerItem = (CreatureSpawner) stateItem.getBlockState(); + spawnerItem.setSpawnedType(spawnerBlock.getSpawnedType()); + spawnerItem.update(true); + stateItem.setBlockState(spawnerItem); + stateItem.setDisplayName(this.spawnerName.replace(Placeholders.GENERIC_TYPE, LangManager.getEntityType(spawnerBlock.getSpawnedType()))); + itemSpawner.setItemMeta(stateItem); + + PDCUtil.set(itemSpawner, this.key, true); + return itemSpawner; + } + + @Override + public boolean onDrop(@NotNull BlockDropItemEvent event, @NotNull LivingEntity player, @NotNull ItemStack item, int level) { + BlockState state = event.getBlockState(); + Block block = state.getBlock(); + if (this.handleSpawner == null || !this.handleSpawner.equals(block.getLocation())) return false; + this.handleSpawner = null; + + if (!(state instanceof CreatureSpawner spawnerBlock)) return false; + + EnchantUtils.popResource(event, this.getSpawner(spawnerBlock)); + + if (this.hasVisualEffects()) { + Location location = LocationUtil.getCenter(block.getLocation()); + UniParticle.of(Particle.VILLAGER_HAPPY).play(location, 0.3, 0.15, 30); + } + return true; + } + + @Override + public boolean onBreak(@NotNull BlockBreakEvent event, @NotNull LivingEntity player, @NotNull ItemStack item, int level) { + Block block = event.getBlock(); + if (!(block.getState() instanceof CreatureSpawner spawnerBlock)) return false; + if (!this.checkTriggerChance(level)) return false; + + event.setExpToDrop(0); + event.setDropItems(true); + this.handleSpawner = block.getLocation(); + return false; // Do not consume charges + } + + // Update spawner type of the placed spawner mined by Divine Touch. + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onSpawnerPlace(BlockPlaceEvent e) { + Block block = e.getBlock(); + if (block.getType() != Material.SPAWNER) return; + + Player player = e.getPlayer(); + ItemStack spawner = player.getInventory().getItem(e.getHand()); + if (spawner == null || spawner.getType() != Material.SPAWNER || !(spawner.getItemMeta() instanceof BlockStateMeta meta)) return; + if (PDCUtil.getBoolean(spawner, this.key).isEmpty()) return; + + CreatureSpawner spawnerItem = (CreatureSpawner) meta.getBlockState(); + CreatureSpawner spawnerBlock = (CreatureSpawner) block.getState(); + + spawnerBlock.setSpawnedType(spawnerItem.getSpawnedType()); + spawnerBlock.update(); + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/tool/HasteEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/tool/HasteEnchant.java new file mode 100644 index 0000000..2f10391 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/tool/HasteEnchant.java @@ -0,0 +1,59 @@ +package su.nightexpress.excellentenchants.enchantment.impl.tool; + +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.LivingEntity; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffectType; +import org.jetbrains.annotations.NotNull; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Potioned; +import su.nightexpress.excellentenchants.api.enchantment.type.PassiveEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.PeriodImplementation; +import su.nightexpress.excellentenchants.enchantment.impl.meta.PotionImplementation; + +public class HasteEnchant extends ExcellentEnchant implements Potioned, PassiveEnchant { + + public static final String ID = "haste"; + + private PotionImplementation potionImplementation; + private PeriodImplementation periodImplementation; + + public HasteEnchant(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription("Grants permanent " + Placeholders.ENCHANTMENT_POTION_TYPE + " " + Placeholders.ENCHANTMENT_POTION_LEVEL + " effect."); + this.getDefaults().setLevelMax(3); + this.getDefaults().setTier(0.3); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.potionImplementation = PotionImplementation.create(this, PotionEffectType.FAST_DIGGING, true); + this.periodImplementation = PeriodImplementation.create(this, "100"); + } + + @NotNull + @Override + public PotionImplementation getPotionImplementation() { + return potionImplementation; + } + + @NotNull + @Override + public PeriodImplementation getPeriodImplementation() { + return periodImplementation; + } + + @Override + @NotNull + public EnchantmentTarget getCategory() { + return EnchantmentTarget.TOOL; + } + + @Override + public boolean onTrigger(@NotNull LivingEntity entity, @NotNull ItemStack item, int level) { + return this.addEffect(entity, level); + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/tool/LuckyMinerEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/tool/LuckyMinerEnchant.java new file mode 100644 index 0000000..79beb75 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/tool/LuckyMinerEnchant.java @@ -0,0 +1,75 @@ +package su.nightexpress.excellentenchants.enchantment.impl.tool; + +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.utils.NumberUtil; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.type.BlockBreakEnchant; +import su.nightexpress.excellentenchants.enchantment.config.EnchantScaler; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; +import su.nightexpress.excellentenchants.api.enchantment.ItemCategory; + +public class LuckyMinerEnchant extends ExcellentEnchant implements Chanced, BlockBreakEnchant { + + public static final String ID = "lucky_miner"; + private static final String PLACEHOLDER_EXP_MODIFIER = "%enchantment_exp_modifier%"; + + private EnchantScaler expModifier; + private ChanceImplementation chanceImplementation; + + public LuckyMinerEnchant(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription(Placeholders.ENCHANTMENT_CHANCE + "% chance to gain " + PLACEHOLDER_EXP_MODIFIER + "% more exp from ores."); + this.getDefaults().setLevelMax(5); + this.getDefaults().setTier(0.1); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.chanceImplementation = ChanceImplementation.create(this, + "30.0 + " + Placeholders.ENCHANTMENT_LEVEL + " * 7.0"); + this.expModifier = EnchantScaler.read(this, "Settings.Exp_Modifier", + "1.0 + " + Placeholders.ENCHANTMENT_LEVEL + " * 0.5", + "Exp modifier value. The original exp amount will be multiplied on this value."); + + this.addPlaceholder(PLACEHOLDER_EXP_MODIFIER, level -> NumberUtil.format(this.getExpModifier(level) * 100D - 100D)); + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + public double getExpModifier(int level) { + return this.expModifier.getValue(level); + } + + @Override + @NotNull + public ItemCategory[] getFitItemTypes() { + return new ItemCategory[]{ItemCategory.PICKAXE}; + } + + @Override + @NotNull + public EnchantmentTarget getCategory() { + return EnchantmentTarget.TOOL; + } + + @Override + public boolean onBreak(@NotNull BlockBreakEvent event, @NotNull LivingEntity player, @NotNull ItemStack item, int level) { + if (!this.checkTriggerChance(level)) return false; + + double expMod = this.getExpModifier(level); + event.setExpToDrop((int) ((double) event.getExpToDrop() * expMod)); + return true; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/tool/ReplanterEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/tool/ReplanterEnchant.java new file mode 100644 index 0000000..e1ddd78 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/tool/ReplanterEnchant.java @@ -0,0 +1,200 @@ +package su.nightexpress.excellentenchants.enchantment.impl.tool; + +import org.bukkit.Material; +import org.bukkit.Sound; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.data.Ageable; +import org.bukkit.block.data.BlockData; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.Action; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.api.config.JOption; +import su.nexmedia.engine.utils.values.UniSound; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.type.BlockBreakEnchant; +import su.nightexpress.excellentenchants.api.enchantment.type.InteractEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; +import su.nightexpress.excellentenchants.api.enchantment.ItemCategory; + +import java.util.Set; + +public class ReplanterEnchant extends ExcellentEnchant implements Chanced, InteractEnchant, BlockBreakEnchant { + + public static final String ID = "replanter"; + + private boolean replantOnRightClick; + private boolean replantOnPlantBreak; + + private ChanceImplementation chanceImplementation; + + private static final Set CROPS = Set.of( + Material.WHEAT_SEEDS, Material.BEETROOT_SEEDS, + Material.MELON_SEEDS, Material.PUMPKIN_SEEDS, + Material.POTATO, Material.CARROT, Material.NETHER_WART); + + public ReplanterEnchant(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription("Automatically replant crops on right click and when harvest."); + this.getDefaults().setLevelMax(1); + this.getDefaults().setTier(0.3); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.chanceImplementation = ChanceImplementation.create(this, "100"); + this.replantOnRightClick = JOption.create("Settings.Replant.On_Right_Click", true, + "When 'true', player will be able to replant crops when right-clicking farmland blocks.").read(cfg); + this.replantOnPlantBreak = JOption.create("Settings.Replant.On_Plant_Break", true, + "When 'true', crops will be automatically replanted when player break plants with enchanted tool in hand.").read(cfg); + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + public boolean isReplantOnPlantBreak() { + return replantOnPlantBreak; + } + + public boolean isReplantOnRightClick() { + return replantOnRightClick; + } + + @NotNull + private Material fineSeedsToBlock(@NotNull Material material) { + if (material == Material.POTATO) return Material.POTATOES; + if (material == Material.CARROT) return Material.CARROTS; + if (material == Material.BEETROOT_SEEDS) return Material.BEETROOTS; + if (material == Material.WHEAT_SEEDS) return Material.WHEAT; + if (material == Material.PUMPKIN_SEEDS) return Material.PUMPKIN_STEM; + if (material == Material.MELON_SEEDS) return Material.MELON_STEM; + return material; + } + + @NotNull + private Material fineBlockToSeeds(@NotNull Material material) { + if (material == Material.POTATOES) return Material.POTATO; + if (material == Material.CARROTS) return Material.CARROT; + if (material == Material.BEETROOTS) return Material.BEETROOT_SEEDS; + if (material == Material.WHEAT) return Material.WHEAT_SEEDS; + if (material == Material.MELON_STEM) return Material.MELON_SEEDS; + if (material == Material.PUMPKIN_STEM) return Material.PUMPKIN_SEEDS; + return material; + } + + private boolean takeSeeds(@NotNull Player player, @NotNull Material material) { + material = this.fineBlockToSeeds(material); + + int slot = player.getInventory().first(material); + if (slot < 0) return false; + + ItemStack seed = player.getInventory().getItem(slot); + if (seed == null || seed.getType().isAir()) return false; + + seed.setAmount(seed.getAmount() - 1); + return true; + } + + @Override + @NotNull + public ItemCategory[] getFitItemTypes() { + return new ItemCategory[]{ItemCategory.HOE}; + } + + @Override + @NotNull + public EnchantmentTarget getCategory() { + return EnchantmentTarget.TOOL; + } + + @NotNull + @Override + public EventPriority getInteractPriority() { + return EventPriority.HIGHEST; + } + + @Override + public boolean onInteract(@NotNull PlayerInteractEvent event, @NotNull LivingEntity entity, @NotNull ItemStack item, int level) { + if (!(entity instanceof Player player)) return false; + if (!this.isReplantOnRightClick()) return false; + if (!this.checkTriggerChance(level)) return false; + + // Check for a event hand. We dont want to trigger it twice. + if (event.getHand() != EquipmentSlot.HAND) return false; + if (event.getAction() != Action.RIGHT_CLICK_BLOCK) return false; + + // Check if player holds seeds to plant them by offhand interaction. + ItemStack off = player.getInventory().getItemInOffHand(); + if (!off.getType().isAir() && CROPS.contains(off.getType())) return false; + + // Check if clicked block is a farmland. + Block blockGround = event.getClickedBlock(); + if (blockGround == null) return false; + if (blockGround.getType() != Material.FARMLAND && blockGround.getType() != Material.SOUL_SAND) return false; + + // Check if someting is already growing on the farmland. + Block blockPlant = blockGround.getRelative(BlockFace.UP); + if (!blockPlant.isEmpty()) return false; + + // Get the first crops from player's inventory and plant them. + for (Material seed : CROPS) { + if (seed == Material.NETHER_WART && blockGround.getType() == Material.SOUL_SAND + || seed != Material.NETHER_WART && blockGround.getType() == Material.FARMLAND) { + if (this.takeSeeds(player, seed)) { + UniSound.of(seed == Material.NETHER_WART ? Sound.ITEM_NETHER_WART_PLANT : Sound.ITEM_CROP_PLANT).play(player); + plugin.getEnchantNMS().sendAttackPacket(player, 0); + blockPlant.setType(this.fineSeedsToBlock(seed)); + break; + } + } + } + return true; + } + + @Override + public boolean onBreak(@NotNull BlockBreakEvent event, @NotNull LivingEntity entity, @NotNull ItemStack item, int level) { + if (!(entity instanceof Player player)) return false; + if (!this.isReplantOnPlantBreak()) return false; + if (!this.checkTriggerChance(level)) return false; + + Block blockPlant = event.getBlock(); + //if (EnchantTelekinesis.isDropHandled(blockPlant)) return false; + //if (EnchantRegister.TELEKINESIS != null && item.containsEnchantment(EnchantRegister.TELEKINESIS)) return false; + + // Check if broken block is supported crop(s). + if (!CROPS.contains(this.fineBlockToSeeds(blockPlant.getType()))) return false; + + // Check if broken block is actually can grow. + BlockData dataPlant = blockPlant.getBlockData(); + if (!(dataPlant instanceof Ageable plant)) return false; + + // Check if crop is not at its maximal age to prevent accidient replant. + /*if (plant.getAge() < plant.getMaximumAge()) { + e.setCancelled(true); + return false; + }*/ + + // Replant the gathered crops with a new one. + if (this.takeSeeds(player, plant.getMaterial())) { + plugin.getServer().getScheduler().runTask(plugin, () -> { + blockPlant.setType(plant.getMaterial()); + plant.setAge(0); + blockPlant.setBlockData(plant); + }); + } + return true; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/tool/SilkChestEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/tool/SilkChestEnchant.java new file mode 100644 index 0000000..ef0e0c4 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/tool/SilkChestEnchant.java @@ -0,0 +1,203 @@ +package su.nightexpress.excellentenchants.enchantment.impl.tool; + +import org.bukkit.NamespacedKey; +import org.bukkit.block.Block; +import org.bukkit.block.BlockState; +import org.bukkit.block.Chest; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.Item; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockDropItemEvent; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryPickupItemEvent; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.BlockStateMeta; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.api.config.JOption; +import su.nexmedia.engine.api.manager.EventListener; +import su.nexmedia.engine.utils.Colorizer; +import su.nexmedia.engine.utils.ItemUtil; +import su.nexmedia.engine.utils.PDCUtil; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.type.BlockDropEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.api.enchantment.ItemCategory; +import su.nightexpress.excellentenchants.enchantment.util.EnchantUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +public class SilkChestEnchant extends ExcellentEnchant implements BlockDropEnchant, EventListener { + + public static final String ID = "silk_chest"; + + private String chestName; + private List chestLore; + private final NamespacedKey keyChest; + + public SilkChestEnchant(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription("Drop chests and saves all its content."); + this.getDefaults().setLevelMax(1); + this.getDefaults().setTier(0.5); + + this.keyChest = new NamespacedKey(plugin, ID + ".item"); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.chestName = JOption.create("Settings.Chest_Item.Name", "Chest &7(" + Placeholders.GENERIC_AMOUNT + " items)", + "Chest item display name.", + "Use '" + Placeholders.GENERIC_AMOUNT + "' for items amount.").mapReader(Colorizer::apply).read(cfg); + this.chestLore = JOption.create("Settings.Chest_Item.Lore", new ArrayList<>(), + "Chest item lore.", + "Use '" + Placeholders.GENERIC_AMOUNT + "' for items amount.").mapReader(Colorizer::apply).read(cfg); + } + + @Override + @NotNull + public ItemCategory[] getFitItemTypes() { + return new ItemCategory[]{ItemCategory.AXE}; + } + + @Override + @NotNull + public EnchantmentTarget getCategory() { + return EnchantmentTarget.TOOL; + } + + @NotNull + @Override + public EventPriority getDropPriority() { + return EventPriority.NORMAL; + } + + public boolean isSilkChest(@NotNull ItemStack item) { + return PDCUtil.getBoolean(item, this.keyChest).isPresent(); + } + + @NotNull + public ItemStack getSilkChest(@NotNull Chest chest) { + ItemStack chestStack = new ItemStack(chest.getType()); + + BlockStateMeta stateMeta = (BlockStateMeta) chestStack.getItemMeta(); + if (stateMeta == null) return chestStack; + + Chest chestItem = (Chest) stateMeta.getBlockState(); + chestItem.getBlockInventory().setContents(chest.getBlockInventory().getContents()); + chestItem.update(true); + + int amount = (int) Stream.of(chestItem.getBlockInventory().getContents()).filter(i -> i != null && !i.getType().isAir()).count(); + + stateMeta.setBlockState(chestItem); + stateMeta.setDisplayName(this.chestName); + stateMeta.setLore(this.chestLore); + chestStack.setItemMeta(stateMeta); + + ItemUtil.replace(chestStack, str -> str.replace(Placeholders.GENERIC_AMOUNT, String.valueOf(amount))); + PDCUtil.set(chestStack, this.keyChest, true); + return chestStack; + + // Store and count chest items. + /*int amount = 0; + int count = 0; + for (ItemStack itemInv : chest.getBlockInventory().getContents()) { + if (itemInv == null) itemInv = new ItemStack(Material.AIR); + else amount++; + + String base64 = ItemUtil.toBase64(itemInv); + if (base64 == null) continue; + if (base64.length() >= Short.MAX_VALUE) { + chest.getWorld().dropItemNaturally(chest.getLocation(), itemInv); + continue; + } + PDCUtil.setData(chestItem, this.getItemKey(count++), base64); + } + + // Apply item meta name and items data string. + ItemMeta meta = chestItem.getItemMeta(); + if (meta != null) { + String nameOrig = ItemUtil.getItemName(chestItem); + String nameChest = this.chestName.replace("%name%", nameOrig).replace("%items%", String.valueOf(amount)); + meta.setDisplayName(nameChest); + chestItem.setItemMeta(meta); + } + + return chestItem;*/ + } + + @Override + public boolean onDrop(@NotNull BlockDropItemEvent event, + @NotNull LivingEntity player, @NotNull ItemStack item, int level) { + BlockState state = event.getBlockState(); + Block block = state.getBlock(); + + if (!(state instanceof Chest chest)) return false; + + // Добавляем в сундук обратно предметы из дроп листа, кроме самого сундука. + event.getItems().removeIf(drop -> drop.getItemStack().getType() == state.getType() && drop.getItemStack().getAmount() == 1); + chest.getBlockInventory().addItem(event.getItems().stream().map(Item::getItemStack).toList().toArray(new ItemStack[0])); + event.getItems().clear(); + + if (chest.getBlockInventory().isEmpty()) { + EnchantUtils.popResource(event, new ItemStack(chest.getType())); + return false; + } + + EnchantUtils.popResource(event, this.getSilkChest(chest)); + + chest.getBlockInventory().clear(); + + return true; + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onSilkChestPlace(BlockPlaceEvent e) { + ItemStack item = e.getItemInHand(); + if (item.getType().isAir()) return; + + Block block = e.getBlockPlaced(); + BlockState state = block.getState(); + if (!(state instanceof Chest chest)) return; + + chest.setCustomName(null); + chest.update(true); + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onSilkChestStore(InventoryClickEvent e) { + Inventory inventory = e.getInventory(); + if (inventory.getType() == InventoryType.CRAFTING || inventory.getType() == InventoryType.CREATIVE) return; + + Player player = (Player) e.getWhoClicked(); + ItemStack item; + if (e.getHotbarButton() >= 0) { + item = player.getInventory().getItem(e.getHotbarButton()); + } + else item = e.getCurrentItem(); + + if (item == null || item.getType().isAir() || !this.isSilkChest(item)) return; + + Inventory clicked = e.getClickedInventory(); + if (e.getClick() != ClickType.NUMBER_KEY) { + if (clicked != null && clicked.equals(e.getView().getTopInventory())) return; + } + + e.setCancelled(true); + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onSilkChestHopper(InventoryPickupItemEvent e) { + e.setCancelled(this.isSilkChest(e.getItem().getItemStack())); + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/tool/SmelterEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/tool/SmelterEnchant.java new file mode 100644 index 0000000..193db0d --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/tool/SmelterEnchant.java @@ -0,0 +1,126 @@ +package su.nightexpress.excellentenchants.enchantment.impl.tool; + +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.block.Block; +import org.bukkit.block.Container; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockDropItemEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.api.config.JOption; +import su.nexmedia.engine.utils.LocationUtil; +import su.nexmedia.engine.utils.values.UniParticle; +import su.nexmedia.engine.utils.values.UniSound; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.type.BlockDropEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; +import su.nightexpress.excellentenchants.api.enchantment.ItemCategory; + +import java.util.Map; + +public class SmelterEnchant extends ExcellentEnchant implements Chanced, BlockDropEnchant { + + public static final String ID = "smelter"; + + private UniSound sound; + private Map smeltingTable; + private ChanceImplementation chanceImplementation; + + public SmelterEnchant(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription(Placeholders.ENCHANTMENT_CHANCE + "% chance to smelt a block/ore."); + this.getDefaults().setLevelMax(5); + this.getDefaults().setTier(0.3); + this.getDefaults().setConflicts( + DivineTouchEnchant.ID, + Enchantment.SILK_TOUCH.getKey().getKey() + ); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.chanceImplementation = ChanceImplementation.create(this, + "25.0 + " + Placeholders.ENCHANTMENT_LEVEL + " * 10"); + + this.sound = JOption.create("Settings.Sound",UniSound.of(Sound.BLOCK_LAVA_EXTINGUISH), + "Sound to play on smelting.", + "https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/Sound.html").read(cfg); + + this.smeltingTable = JOption.forMap("Settings.Smelting_Table", + key -> Material.getMaterial(key.toUpperCase()), + (cfg, path, key) -> Material.getMaterial(cfg.getString(path + "." + key, "").toUpperCase()), + Map.of( + Material.RAW_IRON, Material.IRON_INGOT, + Material.RAW_GOLD, Material.GOLD_INGOT + ), + "Table of Original -> Smelted items.", + "Syntax: 'Material Source : Material Result'.", + "Note: Material source is material name of the dropped item, not the broken block!", + "https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/Material.html" + ).setWriter((cfg, path, map) -> map.forEach((src, to) -> cfg.set(path + "." + src.name(), to.name()))).read(cfg); + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + @Override + @NotNull + public ItemCategory[] getFitItemTypes() { + return new ItemCategory[]{ItemCategory.PICKAXE, ItemCategory.AXE, ItemCategory.SHOVEL}; + } + + @Override + @NotNull + public EnchantmentTarget getCategory() { + return EnchantmentTarget.TOOL; + } + + @NotNull + @Override + public EventPriority getDropPriority() { + return EventPriority.NORMAL; + } + + @Override + public boolean onDrop(@NotNull BlockDropItemEvent event, @NotNull LivingEntity player, @NotNull ItemStack item, int level) { + // TODO Use furnace recipes & Re-add smelted items instead of setType + + if (event.getBlockState() instanceof Container) return false; + if (!this.checkTriggerChance(level)) return false; + if (event.getItems().stream().noneMatch(drop -> this.isSmeltable(drop.getItemStack().getType()))) return false; + + event.getItems().forEach(drop -> { + Material material = this.smeltingTable.get(drop.getItemStack().getType()); + if (material != null) { + ItemStack stack = drop.getItemStack(); + stack.setType(material); + drop.setItemStack(stack); + } + }); + + Block block = event.getBlockState().getBlock(); + if (this.hasVisualEffects()) { + Location location = LocationUtil.getCenter(block.getLocation(), true); + UniParticle.of(Particle.FLAME).play(location, 0.25, 0.05, 20); + this.sound.play(location); + } + return true; + } + + public boolean isSmeltable(@NotNull Material material) { + return this.smeltingTable.containsKey(material); + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/tool/TelekinesisEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/tool/TelekinesisEnchant.java new file mode 100644 index 0000000..b89452a --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/tool/TelekinesisEnchant.java @@ -0,0 +1,72 @@ +package su.nightexpress.excellentenchants.enchantment.impl.tool; + +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockDropItemEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.utils.PlayerUtil; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.type.BlockDropEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; +import su.nightexpress.excellentenchants.api.enchantment.ItemCategory; + +public class TelekinesisEnchant extends ExcellentEnchant implements Chanced, BlockDropEnchant { + + public static final String ID = "telekinesis"; + + private ChanceImplementation chanceImplementation; + + public TelekinesisEnchant(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription("Moves all blocks loot directly to your inventory."); + this.getDefaults().setLevelMax(1); + this.getDefaults().setTier(0.75); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.chanceImplementation = ChanceImplementation.create(this, "100"); + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + @Override + @NotNull + public ItemCategory[] getFitItemTypes() { + return new ItemCategory[]{ItemCategory.TOOL}; + } + + @Override + @NotNull + public EnchantmentTarget getCategory() { + return EnchantmentTarget.TOOL; + } + + @NotNull + @Override + public EventPriority getDropPriority() { + return EventPriority.HIGHEST; + } + + @Override + public boolean onDrop(@NotNull BlockDropItemEvent event, @NotNull LivingEntity entity, @NotNull ItemStack item, int level) { + if (!(entity instanceof Player player)) return false; + if (!this.checkTriggerChance(level)) return false; + + event.getItems().forEach(drop -> { + PlayerUtil.addItem(player, drop.getItemStack()); + }); + event.getItems().clear(); + return true; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/tool/TreasuresEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/tool/TreasuresEnchant.java new file mode 100644 index 0000000..f8f2269 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/tool/TreasuresEnchant.java @@ -0,0 +1,167 @@ +package su.nightexpress.excellentenchants.enchantment.impl.tool; + +import org.bukkit.Material; +import org.bukkit.Tag; +import org.bukkit.block.Block; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockDropItemEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.utils.blocktracker.PlayerBlockTracker; +import su.nexmedia.engine.utils.random.Rnd; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.Cleanable; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.type.BlockBreakEnchant; +import su.nightexpress.excellentenchants.api.enchantment.type.BlockDropEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; +import su.nightexpress.excellentenchants.api.enchantment.ItemCategory; +import su.nightexpress.excellentenchants.enchantment.util.EnchantUtils; + +import java.util.*; +import java.util.function.Predicate; + +public class TreasuresEnchant extends ExcellentEnchant implements Chanced, BlockBreakEnchant, BlockDropEnchant, Cleanable { + + public static final String ID = "treasures"; + + private final Predicate blockTracker; + + private Map> treasures; + private ChanceImplementation chanceImplementation; + + private Block handleDrop; + + public TreasuresEnchant(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription(Placeholders.ENCHANTMENT_CHANCE + "% chance to attempt to find a treasure in mined block."); + this.getDefaults().setLevelMax(5); + this.getDefaults().setTier(0.1); + + PlayerBlockTracker.initialize(); + PlayerBlockTracker.BLOCK_FILTERS.add(this.blockTracker = (block) -> { + return this.treasures.containsKey(block.getType()); + }); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.chanceImplementation = ChanceImplementation.create(this, + "10.0 + " + Placeholders.ENCHANTMENT_LEVEL + " * 4.0"); + + this.treasures = new HashMap<>(); + + if (cfg.getSection("Settings.Treasures").isEmpty()) { + Tag.BASE_STONE_OVERWORLD.getValues().forEach(material -> { + cfg.addMissing("Settings.Treasures." + material.name() + ".BONE_MEAL", 2.0); + }); + Tag.DIRT.getValues().forEach(material -> { + cfg.addMissing("Settings.Treasures." + material.name() + ".CLAY_BALL", 0.5); + cfg.addMissing("Settings.Treasures." + material.name() + ".BOWL", 1.0); + cfg.addMissing("Settings.Treasures." + material.name() + ".STICK", 2.0); + }); + Tag.SAND.getValues().forEach(material -> { + cfg.addMissing("Settings.Treasures." + material.name() + ".GLOWSTONE_DUST", 1.0); + cfg.addMissing("Settings.Treasures." + material.name() + ".GOLD_NUGGET", 0.3); + }); + Tag.LEAVES.getValues().forEach(material -> { + cfg.addMissing("Settings.Treasures." + material.name() + ".APPLE", 12.0); + }); + } + + for (String sFromArray : cfg.getSection("Settings.Treasures")) { + for (String sFrom : sFromArray.split(",")) { + Material mFrom = Material.getMaterial(sFrom.toUpperCase()); + if (mFrom == null) { + plugin.error("[Treasures] Invalid source material '" + sFrom + "' !"); + continue; + } + Map treasuresList = new HashMap<>(); + + for (String sTo : cfg.getSection("Settings.Treasures." + sFromArray)) { + Material mTo = Material.getMaterial(sTo.toUpperCase()); + if (mTo == null) { + plugin.error("[Treasures] Invalid result material '" + sTo + "' for '" + sFromArray + "' !"); + continue; + } + + double tChance = cfg.getDouble("Settings.Treasures." + sFromArray + "." + sTo); + treasuresList.put(mTo, tChance); + } + this.treasures.put(mFrom, treasuresList); + } + } + + this.cfg.setComments("Settings.Treasures", + "List of source materials (blocks that will drop additional loot). Separated by a comma.", + "https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/Material.html"); + } + + @Override + public void clear() { + PlayerBlockTracker.BLOCK_FILTERS.remove(this.blockTracker); + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + @Override + @NotNull + public ItemCategory[] getFitItemTypes() { + return new ItemCategory[]{ItemCategory.PICKAXE, ItemCategory.AXE, ItemCategory.SHOVEL}; + } + + @Override + @NotNull + public EnchantmentTarget getCategory() { + return EnchantmentTarget.TOOL; + } + + @NotNull + @Override + public EventPriority getDropPriority() { + return EventPriority.NORMAL; + } + + @Override + public boolean onBreak(@NotNull BlockBreakEvent event, @NotNull LivingEntity player, @NotNull ItemStack item, int level) { + if (!event.isDropItems()) return false; + if (PlayerBlockTracker.isTracked(event.getBlock())) return false; + + this.handleDrop = event.getBlock(); + return false; + } + + @Override + public boolean onDrop(@NotNull BlockDropItemEvent event, @NotNull LivingEntity player, @NotNull ItemStack item, int level) { + if (this.handleDrop != event.getBlock()) return false; + this.handleDrop = null; + + if (!this.checkTriggerChance(level)) return false; + + this.getTreasures(event.getBlockState().getType()).forEach(treasure -> { + EnchantUtils.popResource(event, treasure); + }); + return true; + } + + @NotNull + public final List getTreasures(@NotNull Material type) { + List list = new ArrayList<>(); + Map treasures = this.treasures.getOrDefault(type, Collections.emptyMap()); + treasures.forEach((mat, chance) -> { + if (mat.isAir() || !mat.isItem() || !Rnd.chance(chance)) return; + list.add(new ItemStack(mat)); + }); + return list; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/tool/TunnelEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/tool/TunnelEnchant.java new file mode 100644 index 0000000..a0e43b5 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/tool/TunnelEnchant.java @@ -0,0 +1,125 @@ +package su.nightexpress.excellentenchants.enchantment.impl.tool; + +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.api.config.JOption; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.api.enchantment.type.BlockBreakEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.api.enchantment.ItemCategory; +import su.nightexpress.excellentenchants.enchantment.util.EnchantUtils; +import su.nightexpress.excellentenchants.hook.impl.NoCheatPlusHook; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class TunnelEnchant extends ExcellentEnchant implements BlockBreakEnchant { + + public static final String ID = "tunnel"; + // X and Z offsets for each block AoE mined + private static final int[][] MINING_COORD_OFFSETS = new int[][]{{0, 0}, {0, -1}, {-1, 0}, {0, 1}, {1, 0}, {-1, -1}, {-1, 1}, {1, -1}, {1, 1},}; + private static final Set INTERACTABLE_BLOCKS = new HashSet<>(); + + static { + INTERACTABLE_BLOCKS.add(Material.REDSTONE_ORE); + INTERACTABLE_BLOCKS.add(Material.DEEPSLATE_REDSTONE_ORE); + } + + private boolean disableOnSneak; + + public TunnelEnchant(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + + this.getDefaults().setDescription("Mines multiple blocks at once in a certain shape."); + this.getDefaults().setLevelMax(3); + this.getDefaults().setTier(1.0); + this.getDefaults().setConflicts(VeinminerEnchant.ID, BlastMiningEnchant.ID); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.disableOnSneak = JOption.create("Settings.Ignore_When_Sneaking", true, + "When 'true' the enchantment won't be triggered when sneaking.").read(cfg); + } + + @Override + @NotNull + public ItemCategory[] getFitItemTypes() { + return new ItemCategory[]{ItemCategory.PICKAXE, ItemCategory.SHOVEL}; + } + + @Override + @NotNull + public EnchantmentTarget getCategory() { + return EnchantmentTarget.TOOL; + } + + @Override + public boolean onBreak(@NotNull BlockBreakEvent event, @NotNull LivingEntity entity, @NotNull ItemStack item, int level) { + if (!(entity instanceof Player player)) return false; + if (EnchantUtils.isBusy()) return false; + if (this.disableOnSneak && player.isSneaking()) return false; + + Block block = event.getBlock(); + if (block.getType().isInteractable() && !INTERACTABLE_BLOCKS.contains(block.getType())) return false; + if (block.getDrops(item).isEmpty()) return false; + + + final List lastTwoTargetBlocks = player.getLastTwoTargetBlocks(null, 10); + if (lastTwoTargetBlocks.size() != 2 || !lastTwoTargetBlocks.get(1).getType().isOccluding()) { + return false; + } + final Block targetBlock = lastTwoTargetBlocks.get(1); + final Block adjacentBlock = lastTwoTargetBlocks.get(0); + final BlockFace dir = targetBlock.getFace(adjacentBlock); + boolean isZ = dir == BlockFace.EAST || dir == BlockFace.WEST; + + // Mine + shape if Tunnel I, 3x3 if Tunnel II + int blocksBroken = 1; + if (level == 1) blocksBroken = 2; + else if (level == 2) blocksBroken = 5; + else if (level >= 3) blocksBroken = 9; + + NoCheatPlusHook.exemptBlocks(player); + + for (int i = 0; i < blocksBroken; i++) { + if (item.getType().isAir()) break; + + int xAdd = MINING_COORD_OFFSETS[i][0]; + int zAdd = MINING_COORD_OFFSETS[i][1]; + + Block blockAdd; + if (dir == BlockFace.UP || dir == BlockFace.DOWN) { + blockAdd = block.getLocation().clone().add(xAdd, 0, zAdd).getBlock(); + } else { + blockAdd = block.getLocation().clone().add(isZ ? 0 : xAdd, zAdd, isZ ? xAdd : 0).getBlock(); + } + + // Skip blocks that should not be mined + if (blockAdd.equals(block)) continue; + if (blockAdd.getDrops(item).isEmpty()) continue; + if (blockAdd.isLiquid()) continue; + + Material addType = blockAdd.getType(); + + // Some extra block checks. + if (addType.isInteractable() && !INTERACTABLE_BLOCKS.contains(addType)) continue; + if (addType == Material.BEDROCK || addType == Material.END_PORTAL || addType == Material.END_PORTAL_FRAME) continue; + if (addType == Material.OBSIDIAN && addType != block.getType()) continue; + + EnchantUtils.safeBusyBreak(player, blockAdd); + } + + NoCheatPlusHook.unexemptBlocks(player); + return true; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/tool/VeinminerEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/tool/VeinminerEnchant.java new file mode 100644 index 0000000..731a750 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/tool/VeinminerEnchant.java @@ -0,0 +1,140 @@ +package su.nightexpress.excellentenchants.enchantment.impl.tool; + +import org.bukkit.Material; +import org.bukkit.Tag; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.api.config.JOption; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.type.BlockBreakEnchant; +import su.nightexpress.excellentenchants.enchantment.config.EnchantScaler; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.api.enchantment.ItemCategory; +import su.nightexpress.excellentenchants.enchantment.util.EnchantUtils; +import su.nightexpress.excellentenchants.hook.impl.NoCheatPlusHook; + +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class VeinminerEnchant extends ExcellentEnchant implements BlockBreakEnchant { + + public static final String ID = "veinminer"; + + private static final BlockFace[] AREA = { + BlockFace.UP, BlockFace.DOWN, BlockFace.EAST, + BlockFace.WEST, BlockFace.SOUTH, BlockFace.NORTH + }; + + private static final String PLACEHOLDER_BLOCK_LIMIT = "%enchantment_block_limit%"; + + private EnchantScaler blocksLimit; + private Set blocksAffected; + + public VeinminerEnchant(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + + this.getDefaults().setDescription("Mines up to " + PLACEHOLDER_BLOCK_LIMIT + " blocks of the ore vein at once."); + this.getDefaults().setLevelMax(3); + this.getDefaults().setTier(0.3); + this.getDefaults().setConflicts(BlastMiningEnchant.ID, TunnelEnchant.ID); + } + + @Override + public void loadSettings() { + super.loadSettings(); + + this.blocksLimit = EnchantScaler.read(this, "Settings.Blocks.Max_At_Once", + "6 + " + Placeholders.ENCHANTMENT_LEVEL, + "How much amount of blocks can be destroted at single use?"); + + this.blocksAffected = JOption.forSet("Settings.Blocks.Affected", + str -> Material.getMaterial(str.toUpperCase()), + () -> { + Set set = new HashSet<>(); + set.addAll(Tag.COAL_ORES.getValues()); + set.addAll(Tag.COPPER_ORES.getValues()); + set.addAll(Tag.DIAMOND_ORES.getValues()); + set.addAll(Tag.EMERALD_ORES.getValues()); + set.addAll(Tag.GOLD_ORES.getValues()); + set.addAll(Tag.IRON_ORES.getValues()); + set.addAll(Tag.LAPIS_ORES.getValues()); + set.addAll(Tag.REDSTONE_ORES.getValues()); + set.add(Material.NETHER_GOLD_ORE); + set.add(Material.NETHER_QUARTZ_ORE); + return set; + }, + "List of blocks, that will be affected by this enchantment.", + "https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/Material.html" + ).setWriter((cfg, path, set) -> cfg.set(path, set.stream().map(Enum::name).toList())).read(cfg); + + this.addPlaceholder(PLACEHOLDER_BLOCK_LIMIT, level -> String.valueOf(this.getBlocksLimit(level))); + } + + @NotNull + public Set getBlocksAffected() { + return this.blocksAffected; + } + + public int getBlocksLimit(int level) { + return (int) this.blocksLimit.getValue(level); + } + + @Override + @NotNull + public ItemCategory[] getFitItemTypes() { + return new ItemCategory[]{ItemCategory.PICKAXE}; + } + + @NotNull + @Override + public EnchantmentTarget getCategory() { + return EnchantmentTarget.TOOL; + } + + @NotNull + private Set getNearby(@NotNull Block block) { + return Stream.of(AREA).map(block::getRelative) + .filter(blockAdded -> blockAdded.getType() == block.getType()).collect(Collectors.toSet()); + } + + private void vein(@NotNull Player player, @NotNull Block source, int level) { + Set ores = new HashSet<>(); + Set prepare = new HashSet<>(this.getNearby(source)); + + int limit = Math.min(this.getBlocksLimit(level), 30); + if (limit < 0) return; + + while (ores.addAll(prepare) && ores.size() < limit) { + Set nearby = new HashSet<>(); + prepare.forEach(prepared -> nearby.addAll(this.getNearby(prepared))); + prepare.clear(); + prepare.addAll(nearby); + } + ores.remove(source); + ores.forEach(ore -> EnchantUtils.safeBusyBreak(player, ore)); + } + + @Override + public boolean onBreak(@NotNull BlockBreakEvent event, @NotNull LivingEntity entity, @NotNull ItemStack tool, int level) { + if (!(entity instanceof Player player)) return false; + if (EnchantUtils.isBusy()) return false; + + Block block = event.getBlock(); + if (block.getDrops(tool, player).isEmpty()) return false; + if (!this.getBlocksAffected().contains(block.getType())) return false; + + NoCheatPlusHook.exemptBlocks(player); + this.vein(player, block, level); + NoCheatPlusHook.unexemptBlocks(player); + return true; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/universal/CurseOfFragilityEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/universal/CurseOfFragilityEnchant.java new file mode 100644 index 0000000..1a09985 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/universal/CurseOfFragilityEnchant.java @@ -0,0 +1,81 @@ +package su.nightexpress.excellentenchants.enchantment.impl.universal; + +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryDragEvent; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.event.inventory.PrepareAnvilEvent; +import org.bukkit.inventory.AnvilInventory; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.api.manager.EventListener; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.api.enchantment.type.GenericEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.util.EnchantUtils; + +public class CurseOfFragilityEnchant extends ExcellentEnchant implements GenericEnchant, EventListener { + + public static final String ID = "curse_of_fragility"; + + public CurseOfFragilityEnchant(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription("Prevents an item from being grindstoned or anviled."); + this.getDefaults().setLevelMax(1); + this.getDefaults().setTier(0D); + } + + @NotNull + @Override + public EnchantmentTarget getCategory() { + return EnchantmentTarget.BREAKABLE; + } + + @EventHandler(priority = EventPriority.HIGHEST) + public void onItemAnvil(PrepareAnvilEvent e) { + AnvilInventory inventory = e.getInventory(); + ItemStack first = inventory.getItem(0); + ItemStack second = inventory.getItem(1); + + boolean cursedFirst = (first != null && EnchantUtils.getLevel(first, this.getBackend()) >= 1); + boolean cursedSecond = (second != null && EnchantUtils.getLevel(second, this.getBackend()) >= 1); + + if (cursedFirst || cursedSecond) { + e.setResult(null); + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onItemGrindstoneClick(InventoryClickEvent e) { + Inventory inventory = e.getInventory(); + if (inventory.getType() != InventoryType.GRINDSTONE) return; + + this.stopGrindstone(inventory); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onItemGrindstoneDrag(InventoryDragEvent e) { + Inventory inventory = e.getInventory(); + if (inventory.getType() != InventoryType.GRINDSTONE) return; + + this.stopGrindstone(inventory); + } + + private void stopGrindstone(@NotNull Inventory inventory) { + plugin.getScheduler().runTask(plugin, () -> { + ItemStack first = inventory.getItem(0); + ItemStack second = inventory.getItem(1); + + boolean cursedFirst = (first != null && EnchantUtils.getLevel(first, this.getBackend()) >= 1); + boolean cursedSecond = (second != null && EnchantUtils.getLevel(second, this.getBackend()) >= 1); + + if (cursedFirst || cursedSecond) { + inventory.setItem(2, null); + } + }); + } + +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/universal/RestoreEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/universal/RestoreEnchant.java new file mode 100644 index 0000000..7abd1cd --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/universal/RestoreEnchant.java @@ -0,0 +1,97 @@ +package su.nightexpress.excellentenchants.enchantment.impl.universal; + +import org.bukkit.Sound; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.player.PlayerItemDamageEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.Damageable; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.api.manager.EventListener; +import su.nexmedia.engine.utils.NumberUtil; +import su.nexmedia.engine.utils.values.UniSound; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.type.GenericEnchant; +import su.nightexpress.excellentenchants.enchantment.config.EnchantScaler; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; +import su.nightexpress.excellentenchants.enchantment.util.EnchantUtils; + +public class RestoreEnchant extends ExcellentEnchant implements GenericEnchant, Chanced, EventListener { + + public static final String ID = "restore"; + public static final String PLACEHOLDER_DURABILITY_RESTORE = "%durability_restore%"; + + private ChanceImplementation chanceImplementation; + private EnchantScaler durabilityRestore; + + public RestoreEnchant(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription(Placeholders.ENCHANTMENT_CHANCE + "% chance to save item from breaking back to " + PLACEHOLDER_DURABILITY_RESTORE + "%"); + this.getDefaults().setLevelMax(5); + this.getDefaults().setTier(0.6); + } + + @Override + public void loadSettings() { + super.loadSettings(); + + this.chanceImplementation = ChanceImplementation.create(this, + "35.0 + " + Placeholders.ENCHANTMENT_LEVEL + " * 8"); + + this.durabilityRestore = EnchantScaler.read(this, "Settings.Durability_Restoration", + "25.0 + " + Placeholders.ENCHANTMENT_LEVEL + " * 5", + "Amount of durability (in percent of item max) to be restored."); + + this.addPlaceholder(PLACEHOLDER_DURABILITY_RESTORE, level -> NumberUtil.format(this.getDurabilityRestore(level))); + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + @NotNull + @Override + public EnchantmentTarget getCategory() { + return EnchantmentTarget.BREAKABLE; + } + + public double getDurabilityRestore(int level) { + return this.durabilityRestore.getValue(level); + } + + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onItemDamage(PlayerItemDamageEvent event) { + ItemStack item = event.getItem(); + if (!(item.getItemMeta() instanceof Damageable damageable)) return; + + int damage = event.getDamage(); + int maxDurability = item.getType().getMaxDurability(); + if (damageable.getDamage() + damage < maxDurability) return; + + int level = EnchantUtils.getLevel(item, this.getBackend()); + if (level <= 0) return; + + if (this.isOutOfCharges(item)) return; + if (!this.checkTriggerChance(level)) return; + + event.setCancelled(true); + this.consumeChargesNoUpdate(item, level); + + double restorePercent = 100D - this.getDurabilityRestore(level); + int restored = (int) (maxDurability * (restorePercent / 100D)); + + damageable.setDamage(restored); + item.setItemMeta(damageable); + EnchantUtils.remove(item, this.getBackend()); + + if (this.hasVisualEffects()) { + UniSound.of(Sound.ITEM_TOTEM_USE).play(event.getPlayer()); + } + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/universal/SoulboundEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/universal/SoulboundEnchant.java new file mode 100644 index 0000000..4ad31fa --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/universal/SoulboundEnchant.java @@ -0,0 +1,73 @@ +package su.nightexpress.excellentenchants.enchantment.impl.universal; + +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.api.manager.EventListener; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.api.enchantment.type.GenericEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.util.EnchantUtils; + +import java.util.ArrayList; +import java.util.List; + +public class SoulboundEnchant extends ExcellentEnchant implements GenericEnchant, EventListener { + + public static final String ID = "soulbound"; + + public SoulboundEnchant(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription("Protects from being dropped on death."); + this.getDefaults().setLevelMax(1); + this.getDefaults().setTier(0.8); + this.getDefaults().setConflicts(Enchantment.VANISHING_CURSE.getKey().getKey()); + } + + @NotNull + @Override + public EnchantmentTarget getCategory() { + return EnchantmentTarget.BREAKABLE; + } + + @EventHandler + public void onDeath(@NotNull PlayerDeathEvent deathEvent) { + Player player = deathEvent.getEntity(); + if (!this.isAvailableToUse(player)) return; + if (deathEvent.getKeepInventory()) return; + + List saveList = new ArrayList<>(); + Location location = player.getLocation(); + World world = player.getWorld(); + + deathEvent.getDrops().removeIf(drop -> { + if (EnchantUtils.getLevel(drop, this.getBackend()) > 0) { + if (this.isOutOfCharges(drop)) return false; + + saveList.add(drop); + return true; + } + return false; + }); + + if (saveList.isEmpty()) return; + + this.plugin.runTask(task -> { + saveList.forEach(save -> { + if (player.getInventory().firstEmpty() == -1) { + world.dropItemNaturally(location, save); + } + else { + this.consumeChargesNoUpdate(save, EnchantUtils.getLevel(save, this.getBackend())); + player.getInventory().addItem(save); + } + }); + }); + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/CurseOfDeathEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/CurseOfDeathEnchant.java new file mode 100644 index 0000000..c789f24 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/CurseOfDeathEnchant.java @@ -0,0 +1,72 @@ +package su.nightexpress.excellentenchants.enchantment.impl.weapon; + +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.event.entity.EntityResurrectEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.type.DeathEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; + +public class CurseOfDeathEnchant extends ExcellentEnchant implements DeathEnchant, Chanced { + + public static final String ID = "curse_of_death"; + + private ChanceImplementation chanceImplementation; + + public CurseOfDeathEnchant(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription("When killing players, you have a chance of dying too."); + this.getDefaults().setLevelMax(3); + this.getDefaults().setTier(0D); + } + + @Override + public void loadSettings() { + super.loadSettings(); + + this.chanceImplementation = ChanceImplementation.create(this, Placeholders.ENCHANTMENT_LEVEL + " * 0.1"); + } + + @NotNull + @Override + public EnchantmentTarget getCategory() { + return EnchantmentTarget.WEAPON; + } + + @NotNull + @Override + public Chanced getChanceImplementation() { + return this.chanceImplementation; + } + + @Override + public boolean isCurse() { + return true; + } + + @Override + public boolean onDeath(@NotNull EntityDeathEvent event, @NotNull LivingEntity entity, ItemStack item, int level) { + return false; + } + + @Override + public boolean onResurrect(@NotNull EntityResurrectEvent event, @NotNull LivingEntity entity, @NotNull ItemStack item, int level) { + return false; + } + + @Override + public boolean onKill(@NotNull EntityDeathEvent event, @NotNull LivingEntity entity, @NotNull Player killer, ItemStack weapon, int level) { + if (!(entity instanceof Player dead)) return false; + if (!this.checkTriggerChance(level)) return false; + + killer.setHealth(0D); + return true; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantBaneOfNetherspawn.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantBaneOfNetherspawn.java new file mode 100644 index 0000000..9dbbadc --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantBaneOfNetherspawn.java @@ -0,0 +1,83 @@ +package su.nightexpress.excellentenchants.enchantment.impl.weapon; + +import org.bukkit.Particle; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.api.config.JOption; +import su.nexmedia.engine.utils.NumberUtil; +import su.nexmedia.engine.utils.values.UniParticle; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.type.CombatEnchant; +import su.nightexpress.excellentenchants.enchantment.config.EnchantScaler; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; + +import java.util.Set; + +public class EnchantBaneOfNetherspawn extends ExcellentEnchant implements CombatEnchant { + + public static final String ID = "bane_of_netherspawn"; + + private static final String PLACEHOLDER_DAMAGE = "%enchantment_damage%"; + private static final Set ENTITY_TYPES = Set.of( + EntityType.BLAZE, EntityType.MAGMA_CUBE, + EntityType.WITHER_SKELETON, EntityType.GHAST, EntityType.WITHER, + EntityType.PIGLIN, EntityType.PIGLIN_BRUTE, + EntityType.ZOGLIN, EntityType.HOGLIN, + EntityType.STRIDER, EntityType.ZOMBIFIED_PIGLIN + ); + + private boolean damageModifier; + private EnchantScaler damageFormula; + + public EnchantBaneOfNetherspawn(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription("Inflicts " + PLACEHOLDER_DAMAGE + " more damage to nether mobs."); + this.getDefaults().setLevelMax(5); + this.getDefaults().setTier(0.1); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.damageModifier = JOption.create("Settings.Damage.As_Modifier", false, + "When 'true' multiplies the damage. When 'false' sums plain values.").read(cfg); + this.damageFormula = EnchantScaler.read(this, "Settings.Damage.Amount", + "0.5 * " + Placeholders.ENCHANTMENT_LEVEL, + "Amount of additional damage."); + + this.addPlaceholder(PLACEHOLDER_DAMAGE, level -> NumberUtil.format(this.getDamageModifier(level))); + } + + public double getDamageModifier(int level) { + return this.damageFormula.getValue(level); + } + + @Override + @NotNull + public EnchantmentTarget getCategory() { + return EnchantmentTarget.WEAPON; + } + + @Override + public boolean onAttack(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + if (!ENTITY_TYPES.contains(victim.getType())) return false; + + double damageEvent = event.getDamage(); + double damageAdd = this.getDamageModifier(level); + event.setDamage(this.damageModifier ? damageEvent * damageAdd : damageEvent + damageAdd); + if (this.hasVisualEffects()) { + UniParticle.of(Particle.SMOKE_NORMAL).play(victim.getEyeLocation(), 0.25, 0.1, 30); + } + return true; + } + + @Override + public boolean onProtect(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + return false; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantBlindness.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantBlindness.java new file mode 100644 index 0000000..7f3d4cf --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantBlindness.java @@ -0,0 +1,84 @@ +package su.nightexpress.excellentenchants.enchantment.impl.weapon; + +import org.bukkit.Particle; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffectType; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.utils.values.UniParticle; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.meta.Potioned; +import su.nightexpress.excellentenchants.api.enchantment.type.CombatEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; +import su.nightexpress.excellentenchants.enchantment.impl.meta.PotionImplementation; + +public class EnchantBlindness extends ExcellentEnchant implements Chanced, Potioned, CombatEnchant { + + public static final String ID = "blindness"; + + private ChanceImplementation chanceImplementation; + private PotionImplementation potionImplementation; + + public EnchantBlindness(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription(Placeholders.ENCHANTMENT_CHANCE + "% chance to apply " + Placeholders.ENCHANTMENT_POTION_TYPE + " " + Placeholders.ENCHANTMENT_POTION_LEVEL + " (" + Placeholders.ENCHANTMENT_POTION_DURATION + "s.) on hit."); + this.getDefaults().setLevelMax(3); + this.getDefaults().setTier(0.1); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.chanceImplementation = ChanceImplementation.create(this, + "15.0 + " + Placeholders.ENCHANTMENT_LEVEL + " * 3"); + this.potionImplementation = PotionImplementation.create(this, PotionEffectType.BLINDNESS, false, + "3.5 + " + Placeholders.ENCHANTMENT_LEVEL, + Placeholders.ENCHANTMENT_LEVEL); + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + @NotNull + @Override + public PotionImplementation getPotionImplementation() { + return potionImplementation; + } + + @NotNull + @Override + public EnchantmentTarget getCategory() { + return EnchantmentTarget.WEAPON; + } + + @NotNull + @Override + public EventPriority getAttackPriority() { + return EventPriority.HIGHEST; + } + + @Override + public boolean onAttack(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + if (!this.checkTriggerChance(level)) return false; + if (!this.addEffect(victim, level)) return false; + + if (this.hasVisualEffects()) { + UniParticle.of(Particle.SMOKE_NORMAL).play(victim.getEyeLocation(), 0.25, 0.1, 30); + } + return true; + } + + @Override + public boolean onProtect(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + return false; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantConfusion.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantConfusion.java new file mode 100644 index 0000000..50c5452 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantConfusion.java @@ -0,0 +1,84 @@ +package su.nightexpress.excellentenchants.enchantment.impl.weapon; + +import org.bukkit.Material; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffectType; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.utils.values.UniParticle; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.meta.Potioned; +import su.nightexpress.excellentenchants.api.enchantment.type.CombatEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; +import su.nightexpress.excellentenchants.enchantment.impl.meta.PotionImplementation; + +public class EnchantConfusion extends ExcellentEnchant implements Chanced, Potioned, CombatEnchant { + + public static final String ID = "confusion"; + + private ChanceImplementation chanceImplementation; + private PotionImplementation potionImplementation; + + public EnchantConfusion(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription(Placeholders.ENCHANTMENT_CHANCE + "% chance to apply " + Placeholders.ENCHANTMENT_POTION_TYPE + " " + Placeholders.ENCHANTMENT_POTION_LEVEL + " (" + Placeholders.ENCHANTMENT_POTION_DURATION + "s.) on hit."); + this.getDefaults().setLevelMax(3); + this.getDefaults().setTier(0.1); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.chanceImplementation = ChanceImplementation.create(this, + "15.0 * " + Placeholders.ENCHANTMENT_LEVEL); + this.potionImplementation = PotionImplementation.create(this, PotionEffectType.CONFUSION, false, + "5.0 + " + Placeholders.ENCHANTMENT_LEVEL + " * 1.5", + Placeholders.ENCHANTMENT_LEVEL); + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + @NotNull + @Override + public PotionImplementation getPotionImplementation() { + return potionImplementation; + } + + @NotNull + @Override + public EnchantmentTarget getCategory() { + return EnchantmentTarget.WEAPON; + } + + @NotNull + @Override + public EventPriority getAttackPriority() { + return EventPriority.HIGHEST; + } + + @Override + public boolean onAttack(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + if (!this.checkTriggerChance(level)) return false; + if (!this.addEffect(victim, level)) return false; + + if (this.hasVisualEffects()) { + UniParticle.itemCrack(Material.ROTTEN_FLESH).play(victim.getEyeLocation(), 0.25, 0.1, 30); + } + return true; + } + + @Override + public boolean onProtect(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + return false; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantCure.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantCure.java new file mode 100644 index 0000000..c4b9b8f --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantCure.java @@ -0,0 +1,88 @@ +package su.nightexpress.excellentenchants.enchantment.impl.weapon; + +import com.google.common.collect.Sets; +import org.bukkit.Particle; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.*; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.utils.values.UniParticle; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.type.CombatEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; + +import java.util.Set; + +public class EnchantCure extends ExcellentEnchant implements Chanced, CombatEnchant { + + public static final String ID = "cure"; + + private ChanceImplementation chanceImplementation; + + private static final Set CUREABLE = Sets.newHashSet(EntityType.ZOMBIFIED_PIGLIN, EntityType.ZOMBIE_VILLAGER); + + public EnchantCure(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription(Placeholders.ENCHANTMENT_CHANCE + "% chance to cure Zombified Piglins and Zombie Villagers on hit."); + this.getDefaults().setLevelMax(5); + this.getDefaults().setTier(0.5); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.chanceImplementation = ChanceImplementation.create(this, + "20.0 + " + Placeholders.ENCHANTMENT_LEVEL + " * 8"); + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + @Override + @NotNull + public EnchantmentTarget getCategory() { + return EnchantmentTarget.WEAPON; + } + + @NotNull + @Override + public EventPriority getAttackPriority() { + return EventPriority.HIGHEST; + } + + @Override + public boolean onAttack(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + if (!CUREABLE.contains(victim.getType())) return false; + if (!this.checkTriggerChance(level)) return false; + if (!(damager instanceof Player player)) return false; + + event.setCancelled(true); + + if (this.hasVisualEffects()) { + UniParticle.of(Particle.CLOUD).play(victim.getEyeLocation(), 0.25, 0.1, 30); + } + + if (victim instanceof PigZombie pigZombie) { + victim.getWorld().spawn(victim.getLocation(), Piglin.class); + victim.remove(); + } + else if (victim instanceof ZombieVillager zombieVillager) { + zombieVillager.setConversionTime(1); + zombieVillager.setConversionPlayer(player); + } + return true; + } + + @Override + public boolean onProtect(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + return false; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantCutter.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantCutter.java new file mode 100644 index 0000000..eaf9d66 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantCutter.java @@ -0,0 +1,126 @@ +package su.nightexpress.excellentenchants.enchantment.impl.weapon; + +import org.bukkit.Sound; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.Item; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.inventory.EntityEquipment; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.Damageable; +import org.bukkit.inventory.meta.ItemMeta; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.api.config.JOption; +import su.nexmedia.engine.utils.NumberUtil; +import su.nexmedia.engine.utils.random.Rnd; +import su.nexmedia.engine.utils.values.UniParticle; +import su.nexmedia.engine.utils.values.UniSound; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.type.CombatEnchant; +import su.nightexpress.excellentenchants.enchantment.config.EnchantScaler; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; + +public class EnchantCutter extends ExcellentEnchant implements Chanced, CombatEnchant { + + public static final String ID = "cutter"; + private static final String PLACEHOLDER_DURABILITY_DAMAGE = "%enchantment_durability_damage%"; + + private EnchantScaler durabilityReduction; + private ChanceImplementation chanceImplementation; + private boolean allowPlayers; + private boolean allowMobs; + + public EnchantCutter(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription(Placeholders.ENCHANTMENT_CHANCE + "% chance to throw away enemy''s armor and damage it for " + PLACEHOLDER_DURABILITY_DAMAGE + "%."); + this.getDefaults().setLevelMax(5); + this.getDefaults().setTier(0.75); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.chanceImplementation = ChanceImplementation.create(this, + "1.0 + " + Placeholders.ENCHANTMENT_LEVEL + " * 0.6"); + this.durabilityReduction = EnchantScaler.read(this, "Settings.Item.Durability_Reduction", + Placeholders.ENCHANTMENT_LEVEL + " / 100", + "Amount (in percent) of how much item durability will be reduced."); + + this.allowPlayers = JOption.create("Settings.Allow_Players", true, + "Sets whether or not this enchantment will have effect on players.").read(cfg); + + this.allowMobs = JOption.create("Settings.Allow_Mobs", true, + "Sets whether or not this enchantment will have effect on mobs.").read(cfg); + + this.addPlaceholder(PLACEHOLDER_DURABILITY_DAMAGE, level -> NumberUtil.format(this.getDurabilityReduction(level) * 100D)); + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + public final double getDurabilityReduction(int level) { + return this.durabilityReduction.getValue(level); + } + + @Override + @NotNull + public EnchantmentTarget getCategory() { + return EnchantmentTarget.WEAPON; + } + + @NotNull + @Override + public EventPriority getAttackPriority() { + return EventPriority.HIGHEST; + } + + @Override + public boolean onAttack(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + EntityEquipment equipment = victim.getEquipment(); + if (equipment == null) return false; + + ItemStack[] armor = equipment.getArmorContents(); + if (armor.length == 0) return false; + + boolean isPlayer = victim instanceof Player; + if (isPlayer && !this.allowPlayers || (!isPlayer && !this.allowMobs)) return false; + + int get = Rnd.get(armor.length); + ItemStack itemCut = armor[get]; + + if (itemCut == null || itemCut.getType().isAir() || itemCut.getType().getMaxDurability() == 0) return false; + + ItemMeta meta = itemCut.getItemMeta(); + if (!(meta instanceof Damageable damageable)) return false; + if (!this.checkTriggerChance(level)) return false; + + damageable.setDamage((int) (itemCut.getType().getMaxDurability() * this.getDurabilityReduction(level))); + itemCut.setItemMeta(damageable); + + armor[get] = null; + equipment.setArmorContents(armor); + + Item drop = victim.getWorld().dropItemNaturally(victim.getLocation(), itemCut); + drop.setPickupDelay(50); + drop.getVelocity().multiply(3D); + + if (this.hasVisualEffects()) { + UniParticle.itemCrack(itemCut).play(victim.getEyeLocation(), 0.25, 0.15, 30); + UniSound.of(Sound.ENTITY_ITEM_BREAK).play(victim.getLocation()); + } + return true; + } + + @Override + public boolean onProtect(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + return false; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantDecapitator.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantDecapitator.java new file mode 100644 index 0000000..e275682 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantDecapitator.java @@ -0,0 +1,281 @@ +package su.nightexpress.excellentenchants.enchantment.impl.weapon; + + +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.block.Skull; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockDropItemEvent; +import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.event.entity.EntityResurrectEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.SkullMeta; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.api.config.JOption; +import su.nexmedia.engine.lang.LangManager; +import su.nexmedia.engine.utils.Colorizer; +import su.nexmedia.engine.utils.ItemUtil; +import su.nexmedia.engine.utils.PDCUtil; +import su.nexmedia.engine.utils.StringUtil; +import su.nexmedia.engine.utils.values.UniParticle; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.type.DeathEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +public class EnchantDecapitator extends ExcellentEnchant implements Chanced, DeathEnchant { + + public static final String ID = "decapitator"; + + private Set ignoredEntityTypes; + private String headName; + private Map headTextures; + + private ChanceImplementation chanceImplementation; + + private final NamespacedKey skullKey; + + public EnchantDecapitator(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription(Placeholders.ENCHANTMENT_CHANCE + "% chance to obtain player''s or mob''s head."); + this.getDefaults().setLevelMax(4); + this.getDefaults().setTier(0.75); + + this.skullKey = new NamespacedKey(plugin, this.getId() + ".entity_type"); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.chanceImplementation = ChanceImplementation.create(this, + "5.0 + " + Placeholders.ENCHANTMENT_LEVEL + " * 1.75"); + + this.ignoredEntityTypes = JOption.forSet("Settings.Ignored_Entity_Types", + str -> StringUtil.getEnum(str, EntityType.class).orElse(null), + () -> Set.of( + EntityType.BAT, EntityType.BEE, EntityType.ENDER_DRAGON, EntityType.WITHER, EntityType.WITHER_SKELETON + ), + "List of entities, that won't drop heads." + ).setWriter((cfg, path, set) -> cfg.set(path, set.stream().map(Enum::name).toList())).read(cfg); + + this.headName = JOption.create("Settings.Head_Item.Name", "&c" + Placeholders.GENERIC_TYPE + "'s Head", + "Head item display name. Use '" + Placeholders.GENERIC_TYPE + "' for entity name.") + .mapReader(Colorizer::apply).read(cfg); + + this.headTextures = JOption.forMap("Settings.Head_Item.Textures", + id -> StringUtil.getEnum(id, EntityType.class).orElse(null), + (cfg, path, id) -> cfg.getString(path + "." + id), + () -> { + Map map = new HashMap<>(); + map.put(EntityType.AXOLOTL, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNThkYTFhMGEyYTEzZGQyMDliZmMyNTI5ZDljN2MyOWEyOWRkOWEyM2ZmNGI4MGIzOGI4OTk2MTc3MmU4MDM5ZSJ9fX0="); + map.put(EntityType.BAT, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOWU5OWRlZWY5MTlkYjY2YWMyYmQyOGQ2MzAyNzU2Y2NkNTdjN2Y4YjEyYjlkY2E4ZjQxYzNlMGEwNGFjMWNjIn19fQ=="); + map.put(EntityType.BEE, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMTI3MjRhOWE0Y2RkNjhiYTQ5NDE1NTYwZTViZTQwYjRhMWM0N2NiNWJlMWQ2NmFlZGI1MmEzMGU2MmVmMmQ0NyJ9fX0="); + map.put(EntityType.BLAZE, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYjc4ZWYyZTRjZjJjNDFhMmQxNGJmZGU5Y2FmZjEwMjE5ZjViMWJmNWIzNWE0OWViNTFjNjQ2Nzg4MmNiNWYwIn19fQ=="); + map.put(EntityType.CAT, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZTRiNDVjYmFhMTlmZTNkNjhjODU2Y2QzODQ2YzAzYjVmNTlkZTgxYTQ4MGVlYzkyMWFiNGZhM2NkODEzMTcifX19"); + map.put(EntityType.CAVE_SPIDER, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNTYxN2Y3ZGQ1ZWQxNmYzYmQxODY0NDA1MTdjZDQ0MGExNzAwMTViMWNjNmZjYjJlOTkzYzA1ZGUzM2YifX19"); + map.put(EntityType.CHICKEN, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMTYzODQ2OWE1OTljZWVmNzIwNzUzNzYwMzI0OGE5YWIxMWZmNTkxZmQzNzhiZWE0NzM1YjM0NmE3ZmFlODkzIn19fQ=="); + map.put(EntityType.COD, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNzg5MmQ3ZGQ2YWFkZjM1Zjg2ZGEyN2ZiNjNkYTRlZGRhMjExZGY5NmQyODI5ZjY5MTQ2MmE0ZmIxY2FiMCJ9fX0="); + map.put(EntityType.COW, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvN2RmYTBhYzM3YmFiYTJhYTI5MGU0ZmFlZTQxOWE2MTNjZDYxMTdmYTU2OGU3MDlkOTAzNzQ3NTNjMDMyZGNiMCJ9fX0="); + map.put(EntityType.DOLPHIN, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOGU5Njg4Yjk1MGQ4ODBiNTViN2FhMmNmY2Q3NmU1YTBmYTk0YWFjNmQxNmY3OGU4MzNmNzQ0M2VhMjlmZWQzIn19fQ=="); + map.put(EntityType.DONKEY, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNjNhOTc2YzA0N2Y0MTJlYmM1Y2IxOTcxMzFlYmVmMzBjMDA0YzBmYWY0OWQ4ZGQ0MTA1ZmNhMTIwN2VkYWZmMyJ9fX0="); + map.put(EntityType.DROWNED, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYzNmN2NjZjYxZGJjM2Y5ZmU5YTYzMzNjZGUwYzBlMTQzOTllYjJlZWE3MWQzNGNmMjIzYjNhY2UyMjA1MSJ9fX0="); + map.put(EntityType.ELDER_GUARDIAN, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMWM3OTc0ODJhMTRiZmNiODc3MjU3Y2IyY2ZmMWI2ZTZhOGI4NDEzMzM2ZmZiNGMyOWE2MTM5Mjc4YjQzNmIifX19"); + map.put(EntityType.ENDERMAN, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOTZjMGIzNmQ1M2ZmZjY5YTQ5YzdkNmYzOTMyZjJiMGZlOTQ4ZTAzMjIyNmQ1ZTgwNDVlYzU4NDA4YTM2ZTk1MSJ9fX0="); + map.put(EntityType.ENDERMITE, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNWExYTA4MzFhYTAzYWZiNDIxMmFkY2JiMjRlNWRmYWE3ZjQ3NmExMTczZmNlMjU5ZWY3NWE4NTg1NSJ9fX0="); + map.put(EntityType.EVOKER, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZDk1NDEzNWRjODIyMTM5NzhkYjQ3ODc3OGFlMTIxMzU5MWI5M2QyMjhkMzZkZDU0ZjFlYTFkYTQ4ZTdjYmE2In19fQ=="); + map.put(EntityType.FOX, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZDg5NTRhNDJlNjllMDg4MWFlNmQyNGQ0MjgxNDU5YzE0NGEwZDVhOTY4YWVkMzVkNmQzZDczYTNjNjVkMjZhIn19fQ=="); + map.put(EntityType.GHAST, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZGU4YTM4ZTlhZmJkM2RhMTBkMTliNTc3YzU1YzdiZmQ2YjRmMmU0MDdlNDRkNDAxN2IyM2JlOTE2N2FiZmYwMiJ9fX0="); + map.put(EntityType.GOAT, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNDU3YTBkNTM4ZmEwOGE3YWZmZTMxMjkwMzQ2ODg2MTcyMGY5ZmEzNGU4NmQ0NGI4OWRjZWM1NjM5MjY1ZjAzIn19fQ=="); + map.put(EntityType.GUARDIAN, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYTBiZjM0YTcxZTc3MTViNmJhNTJkNWRkMWJhZTVjYjg1Zjc3M2RjOWIwZDQ1N2I0YmZjNWY5ZGQzY2M3Yzk0In19fQ=="); + map.put(EntityType.HOGLIN, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOWJiOWJjMGYwMWRiZDc2MmEwOGQ5ZTc3YzA4MDY5ZWQ3Yzk1MzY0YWEzMGNhMTA3MjIwODU2MWI3MzBlOGQ3NSJ9fX0="); + map.put(EntityType.HORSE, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYjY2YjJiMzJkMzE1MzljNzM4M2Q5MjNiYWU0ZmFhZjY1ZGE2NzE1Y2Q1MjZjMzVkMmU0ZTY4MjVkYTExZmIifX19"); + map.put(EntityType.HUSK, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZDY3NGM2M2M4ZGI1ZjRjYTYyOGQ2OWEzYjFmOGEzNmUyOWQ4ZmQ3NzVlMWE2YmRiNmNhYmI0YmU0ZGIxMjEifX19"); + map.put(EntityType.ILLUSIONER, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMmYyODgyZGQwOTcyM2U0N2MwYWI5NjYzZWFiMDgzZDZhNTk2OTI3MzcwNjExMGM4MjkxMGU2MWJmOGE4ZjA3ZSJ9fX0="); + map.put(EntityType.IRON_GOLEM, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvODkwOTFkNzllYTBmNTllZjdlZjk0ZDdiYmE2ZTVmMTdmMmY3ZDQ1NzJjNDRmOTBmNzZjNDgxOWE3MTQifX19"); + map.put(EntityType.LLAMA, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvODNkOWI1OTE1OTEyZmZjMmI4NTc2MWQ2YWRjYjQyOGE4MTJmOWI4M2ZmNjM0ZTMzMTE2MmNlNDZjOTllOSJ9fX0="); + map.put(EntityType.MAGMA_CUBE, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYTFjOTdhMDZlZmRlMDRkMDAyODdiZjIwNDE2NDA0YWIyMTAzZTEwZjA4NjIzMDg3ZTFiMGMxMjY0YTFjMGYwYyJ9fX0="); + map.put(EntityType.MULE, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNDZkY2RhMjY1ZTU3ZTRmNTFiMTQ1YWFjYmY1YjU5YmRjNjA5OWZmZDNjY2UwYTY2MWIyYzAwNjVkODA5MzBkOCJ9fX0="); + map.put(EntityType.MUSHROOM_COW, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMmI1Mjg0MWYyZmQ1ODllMGJjODRjYmFiZjllMWMyN2NiNzBjYWM5OGY4ZDZiM2RkMDY1ZTU1YTRkY2I3MGQ3NyJ9fX0="); + map.put(EntityType.OCELOT, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNTY1N2NkNWMyOTg5ZmY5NzU3MGZlYzRkZGNkYzY5MjZhNjhhMzM5MzI1MGMxYmUxZjBiMTE0YTFkYjEifX19"); + map.put(EntityType.PANDA, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZDE4OGM5ODBhYWNmYTk0Y2YzMzA4ODUxMmIxYjk1MTdiYTgyNmIxNTRkNGNhZmMyNjJhZmY2OTc3YmU4YSJ9fX0="); + map.put(EntityType.PARROT, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZjBiZmE4NTBmNWRlNGIyOTgxY2NlNzhmNTJmYzJjYzdjZDdiNWM2MmNhZWZlZGRlYjljZjMxMWU4M2Q5MDk3In19fQ=="); + map.put(EntityType.PHANTOM, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNDExZDI1YmNkYWJhZmFkNWZkNmUwMTBjNWIxY2Y3YTAwYzljY2E0MGM1YTQ2NzQ3ZjcwNmRjOWNiM2EifX19"); + map.put(EntityType.PIG, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMWEzNzFhMDZlYTc4NThmODlkMjdjYzEwNTVjMzE3YjIzZjEwNWM5MTI1YmM1MTZkMzg5MWFhNGM4MzVjMjk5In19fQ=="); + map.put(EntityType.PIGLIN, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvY2NlZDlkODAxYWE2ZjgzZjhlNDlmOTBkOWE4Yjg1YjdmOGZkYTU4M2Q4NWY3MmNmZmI2OTg2NzI1Nzg5ZjYzNiJ9fX0="); + map.put(EntityType.PIGLIN_BRUTE, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvY2NlZDlkODAxYWE2ZjgzZjhlNDlmOTBkOWE4Yjg1YjdmOGZkYTU4M2Q4NWY3MmNmZmI2OTg2NzI1Nzg5ZjYzNiJ9fX0="); + map.put(EntityType.PILLAGER, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNGFlZTZiYjM3Y2JmYzkyYjBkODZkYjVhZGE0NzkwYzY0ZmY0NDY4ZDY4Yjg0OTQyZmRlMDQ0MDVlOGVmNTMzMyJ9fX0="); + map.put(EntityType.POLAR_BEAR, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYzRmZTkyNjkyMmZiYjQwNmYzNDNiMzRhMTBiYjk4OTkyY2VlNDQxMDEzN2QzZjg4MDk5NDI3YjIyZGUzYWI5MCJ9fX0="); + map.put(EntityType.PUFFERFISH, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMjkyMzUwYzlmMDk5M2VkNTRkYjJjNzExMzkzNjMyNTY4M2ZmYzIwMTA0YTliNjIyYWE0NTdkMzdlNzA4ZDkzMSJ9fX0="); + map.put(EntityType.RABBIT, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYzk3N2EzMjY2YmYzYjllYWYxN2U1YTAyZWE1ZmJiNDY4MDExNTk4NjNkZDI4OGI5M2U2YzEyYzljYiJ9fX0="); + map.put(EntityType.RAVAGER, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvY2QyMGJmNTJlYzM5MGEwNzk5Mjk5MTg0ZmM2NzhiZjg0Y2Y3MzJiYjFiZDc4ZmQxYzRiNDQxODU4ZjAyMzVhOCJ9fX0="); + map.put(EntityType.SALMON, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMjBlYTlhMjIzNjIwY2RiNTRiMzU3NDEzZDQzYmQ4OWM0MDA4YmNhNmEyMjdmM2I3ZGI5N2Y3NzMzZWFkNWZjZiJ9fX0="); + map.put(EntityType.SHEEP, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZjMxZjljY2M2YjNlMzJlY2YxM2I4YTExYWMyOWNkMzNkMThjOTVmYzczZGI4YTY2YzVkNjU3Y2NiOGJlNzAifX19"); + map.put(EntityType.SILVERFISH, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZGE5MWRhYjgzOTFhZjVmZGE1NGFjZDJjMGIxOGZiZDgxOWI4NjVlMWE4ZjFkNjIzODEzZmE3NjFlOTI0NTQwIn19fQ=="); + map.put(EntityType.SLIME, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvODk1YWVlYzZiODQyYWRhODY2OWY4NDZkNjViYzQ5NzYyNTk3ODI0YWI5NDRmMjJmNDViZjNiYmI5NDFhYmU2YyJ9fX0="); + map.put(EntityType.SNOWMAN, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOGU4ZDIwNmY2MWU2ZGU4YTc5ZDBjYjBiY2Q5OGFjZWQ0NjRjYmZlZmM5MjFiNDE2MGEyNTI4MjE2MzExMmEifX19"); + map.put(EntityType.SPIDER, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvY2Q1NDE1NDFkYWFmZjUwODk2Y2QyNThiZGJkZDRjZjgwYzNiYTgxNjczNTcyNjA3OGJmZTM5MzkyN2U1N2YxIn19fQ=="); + map.put(EntityType.SQUID, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNDljMmM5Y2U2N2ViNTk3MWNjNTk1ODQ2M2U2YzlhYmFiOGU1OTlhZGMyOTVmNGQ0MjQ5OTM2YjAwOTU3NjlkZCJ9fX0="); + map.put(EntityType.STRAY, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMmM1MDk3OTE2YmMwNTY1ZDMwNjAxYzBlZWJmZWIyODcyNzdhMzRlODY3YjRlYTQzYzYzODE5ZDUzZTg5ZWRlNyJ9fX0="); + map.put(EntityType.STRIDER, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvY2I3ZmZkZGE2NTZjNjhkODg4NTFhOGUwNWI0OGNkMjQ5Mzc3M2ZmYzRhYjdkNjRlOTMwMjIyOWZlMzU3MTA1OSJ9fX0="); + map.put(EntityType.TRADER_LLAMA, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvODQyNDc4MGIzYzVjNTM1MWNmNDlmYjViZjQxZmNiMjg5NDkxZGY2YzQzMDY4M2M4NGQ3ODQ2MTg4ZGI0Zjg0ZCJ9fX0="); + map.put(EntityType.TROPICAL_FISH, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZDZkZDVlNmFkZGI1NmFjYmM2OTRlYTRiYTU5MjNiMWIyNTY4ODE3OGZlZmZhNzIyOTAyOTllMjUwNWM5NzI4MSJ9fX0="); + map.put(EntityType.TURTLE, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMGE0MDUwZTdhYWNjNDUzOTIwMjY1OGZkYzMzOWRkMTgyZDdlMzIyZjlmYmNjNGQ1Zjk5YjU3MThhIn19fQ=="); + map.put(EntityType.VEX, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYzJlYzVhNTE2NjE3ZmYxNTczY2QyZjlkNWYzOTY5ZjU2ZDU1NzVjNGZmNGVmZWZhYmQyYTE4ZGM3YWI5OGNkIn19fQ=="); + map.put(EntityType.VILLAGER, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNDFiODMwZWI0MDgyYWNlYzgzNmJjODM1ZTQwYTExMjgyYmI1MTE5MzMxNWY5MTE4NDMzN2U4ZDM1NTU1ODMifX19"); + map.put(EntityType.VINDICATOR, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNmRlYWVjMzQ0YWIwOTViNDhjZWFkNzUyN2Y3ZGVlNjFiMDYzZmY3OTFmNzZhOGZhNzY2NDJjODY3NmUyMTczIn19fQ=="); + map.put(EntityType.WANDERING_TRADER, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNWYxMzc5YTgyMjkwZDdhYmUxZWZhYWJiYzcwNzEwZmYyZWMwMmRkMzRhZGUzODZiYzAwYzkzMGM0NjFjZjkzMiJ9fX0="); + map.put(EntityType.WITCH, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMjBlMTNkMTg0NzRmYzk0ZWQ1NWFlYjcwNjk1NjZlNDY4N2Q3NzNkYWMxNmY0YzNmODcyMmZjOTViZjlmMmRmYSJ9fX0="); + map.put(EntityType.WOLF, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZDA0OThkZTZmNWIwOWUwY2UzNWE3MjkyZmU1MGI3OWZjZTkwNjVkOWJlOGUyYTg3YzdhMTM1NjZlZmIyNmQ3MiJ9fX0="); + map.put(EntityType.ZOGLIN, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZTY3ZTE4NjAyZTAzMDM1YWQ2ODk2N2NlMDkwMjM1ZDg5OTY2NjNmYjllYTQ3NTc4ZDNhN2ViYmM0MmE1Y2NmOSJ9fX0="); + map.put(EntityType.ZOMBIFIED_PIGLIN, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvN2VhYmFlY2M1ZmFlNWE4YTQ5Yzg4NjNmZjQ4MzFhYWEyODQxOThmMWEyMzk4ODkwYzc2NWUwYThkZTE4ZGE4YyJ9fX0="); + map.put(EntityType.ZOMBIE_HORSE, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZDIyOTUwZjJkM2VmZGRiMThkZTg2ZjhmNTVhYzUxOGRjZTczZjEyYTZlMGY4NjM2ZDU1MWQ4ZWI0ODBjZWVjIn19fQ=="); + map.put(EntityType.ZOMBIE_VILLAGER, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZTVlMDhhODc3NmMxNzY0YzNmZTZhNmRkZDQxMmRmY2I4N2Y0MTMzMWRhZDQ3OWFjOTZjMjFkZjRiZjNhYzg5YyJ9fX0="); + map.put(EntityType.SKELETON_HORSE, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNDdlZmZjZTM1MTMyYzg2ZmY3MmJjYWU3N2RmYmIxZDIyNTg3ZTk0ZGYzY2JjMjU3MGVkMTdjZjg5NzNhIn19fQ=="); + return map; + }, + "Head texture values for each entity type.", + "You can take some from http://minecraft-heads.com", + "https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/entity/EntityType.html" + ).setWriter((cfg, path, map) -> map.forEach((type, txt) -> cfg.set(path + "." + type.name(), txt))).read(cfg); + + /*for (String sType : cfg.getSection("Settings.Head_Item.Textures")) { + this.headTextures.put(sType.toUpperCase(), cfg.getString("Settings.Head_Item.Textures." + sType, "")); + } + this.cfg.setComments("Settings.Head_Item.Textures", + "Head texture values for each entity type.", + "You can take some from http://minecraft-heads.com", + "https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/entity/EntityType.html"); + + this.headTextures = new JOption>("Settings.Head_Item.Textures", + (cfg, path, def) -> cfg.getSection(path).stream().collect(Collectors.toMap(String::toUpperCase, v -> cfg.getString(path + "." + v, ""))), + HashMap::new, + "Head texture values for each entity type.", + "You can take some from http://minecraft-heads.com", + "https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/entity/EntityType.html").read(cfg);*/ + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + @Override + @NotNull + public EnchantmentTarget getCategory() { + return EnchantmentTarget.WEAPON; + } + + @Override + public boolean onDeath(@NotNull EntityDeathEvent event, @NotNull LivingEntity entity, ItemStack item, int level) { + return false; + } + + @Override + public boolean onResurrect(@NotNull EntityResurrectEvent event, @NotNull LivingEntity entity, @NotNull ItemStack item, int level) { + return false; + } + + @Override + public boolean onKill(@NotNull EntityDeathEvent event, @NotNull LivingEntity entity, @NotNull Player killer, ItemStack weapon, int level) { + EntityType entityType = entity.getType(); + if (this.ignoredEntityTypes.contains(entityType)) return false; + if (!this.checkTriggerChance(level)) return false; + + ItemStack item; + if (entityType == EntityType.WITHER_SKELETON || entityType == EntityType.WITHER) { + item = new ItemStack(Material.WITHER_SKELETON_SKULL); + } + else if (entityType == EntityType.ZOMBIE || entityType == EntityType.GIANT) { + item = new ItemStack(Material.ZOMBIE_HEAD); + } + else if (entityType == EntityType.SKELETON) { + item = new ItemStack(Material.SKELETON_SKULL); + } + else if (entityType == EntityType.CREEPER) { + item = new ItemStack(Material.CREEPER_HEAD); + } + else if (entityType == EntityType.ENDER_DRAGON) { + item = new ItemStack(Material.DRAGON_HEAD); + } + else { + item = new ItemStack(Material.PLAYER_HEAD); + SkullMeta meta = (SkullMeta) item.getItemMeta(); + if (meta == null) return false; + + String entityName; + if (entity instanceof Player player) { + entityName = this.headName.replace(Placeholders.GENERIC_TYPE, entity.getName()); + meta.setOwningPlayer(player); + } + else { + String texture = this.headTextures.get(entity.getType()); + if (texture == null) return false; + + entityName = this.headName.replace(Placeholders.GENERIC_TYPE, LangManager.getEntityType(entity.getType())); + ItemUtil.setSkullTexture(item, texture); + meta = (SkullMeta) item.getItemMeta(); + } + + meta.setDisplayName(entityName); + item.setItemMeta(meta); + } + PDCUtil.set(item, this.skullKey, entityType.name()); + + entity.getWorld().dropItemNaturally(entity.getLocation(), item); + + if (this.hasVisualEffects()) { + UniParticle.blockCrack(Material.REDSTONE_BLOCK).play(entity.getEyeLocation(), 0.25, 0.15, 30); + } + return true; + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onBlockPlace(BlockPlaceEvent e) { + if (!(e.getBlock().getState() instanceof Skull skull)) return; + + ItemStack skullItem = e.getItemInHand(); + PDCUtil.getString(skullItem, this.skullKey).ifPresent(type -> { + PDCUtil.set(skull, this.skullKey, type); + skull.update(true); + }); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onBlockPlace(BlockDropItemEvent e) { + if (!(e.getBlockState() instanceof Skull skull)) return; + + PDCUtil.getString(skull, this.skullKey).ifPresent(type -> { + EntityType entityType = StringUtil.getEnum(type, EntityType.class).orElse(null); + if (entityType == null) return; + + String texture = this.headTextures.get(entityType); + if (texture == null) return; + + e.getItems().forEach(item -> { + ItemStack drop = item.getItemStack(); + if (drop.getType() == Material.PLAYER_HEAD) { + ItemUtil.setSkullTexture(drop, texture); + ItemUtil.mapMeta(drop, meta -> { + String name = this.headName.replace(Placeholders.GENERIC_TYPE, LangManager.getEntityType(entityType)); + meta.setDisplayName(name); + PDCUtil.set(meta, this.skullKey, type); + }); + } + item.setItemStack(drop); + }); + }); + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantDoubleStrike.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantDoubleStrike.java new file mode 100644 index 0000000..2d4f59b --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantDoubleStrike.java @@ -0,0 +1,75 @@ +package su.nightexpress.excellentenchants.enchantment.impl.weapon; + +import org.bukkit.Particle; +import org.bukkit.Sound; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.utils.values.UniParticle; +import su.nexmedia.engine.utils.values.UniSound; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.type.CombatEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; + +public class EnchantDoubleStrike extends ExcellentEnchant implements Chanced, CombatEnchant { + + public static final String ID = "double_strike"; + + private ChanceImplementation chanceImplementation; + + public EnchantDoubleStrike(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription(Placeholders.ENCHANTMENT_CHANCE + "% chance to inflict double damage."); + this.getDefaults().setLevelMax(4); + this.getDefaults().setTier(1.0); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.chanceImplementation = ChanceImplementation.create(this, + "4.0 + " + Placeholders.ENCHANTMENT_LEVEL + " * 0.8"); + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + @Override + @NotNull + public EnchantmentTarget getCategory() { + return EnchantmentTarget.WEAPON; + } + + @NotNull + @Override + public EventPriority getAttackPriority() { + return EventPriority.HIGHEST; + } + + @Override + public boolean onAttack(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + if (!this.checkTriggerChance(level)) return false; + + event.setDamage(event.getDamage() * 2D); + + if (this.hasVisualEffects()) { + UniParticle.of(Particle.EXPLOSION_NORMAL).play(victim.getEyeLocation(), 0.25, 0.15, 15); + UniSound.of(Sound.ENTITY_GENERIC_EXPLODE).play(victim.getLocation()); + } + return true; + } + + @Override + public boolean onProtect(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + return false; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantExhaust.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantExhaust.java new file mode 100644 index 0000000..d00d06d --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantExhaust.java @@ -0,0 +1,84 @@ +package su.nightexpress.excellentenchants.enchantment.impl.weapon; + +import org.bukkit.Material; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffectType; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.utils.values.UniParticle; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.meta.Potioned; +import su.nightexpress.excellentenchants.api.enchantment.type.CombatEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; +import su.nightexpress.excellentenchants.enchantment.impl.meta.PotionImplementation; + +public class EnchantExhaust extends ExcellentEnchant implements Chanced, Potioned, CombatEnchant { + + public static final String ID = "exhaust"; + + private ChanceImplementation chanceImplementation; + private PotionImplementation potionImplementation; + + public EnchantExhaust(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription(Placeholders.ENCHANTMENT_CHANCE + "% chance to apply " + Placeholders.ENCHANTMENT_POTION_TYPE + " " + Placeholders.ENCHANTMENT_POTION_LEVEL + " (" + Placeholders.ENCHANTMENT_POTION_DURATION + "s.) on hit."); + this.getDefaults().setLevelMax(3); + this.getDefaults().setTier(0.1); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.chanceImplementation = ChanceImplementation.create(this, + "20.0 * " + Placeholders.ENCHANTMENT_LEVEL); + this.potionImplementation = PotionImplementation.create(this, PotionEffectType.HUNGER, false, + "3.0 + " + Placeholders.ENCHANTMENT_LEVEL + " * 1.5", + Placeholders.ENCHANTMENT_LEVEL); + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + @NotNull + @Override + public PotionImplementation getPotionImplementation() { + return potionImplementation; + } + + @NotNull + @Override + public EnchantmentTarget getCategory() { + return EnchantmentTarget.WEAPON; + } + + @NotNull + @Override + public EventPriority getAttackPriority() { + return EventPriority.HIGHEST; + } + + @Override + public boolean onAttack(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + if (!this.checkTriggerChance(level)) return false; + if (!this.addEffect(victim, level)) return false; + + if (this.hasVisualEffects()) { + UniParticle.itemCrack(Material.ROTTEN_FLESH).play(victim.getEyeLocation(), 0.25, 0.1, 30); + } + return true; + } + + @Override + public boolean onProtect(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + return false; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantExpHunter.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantExpHunter.java new file mode 100644 index 0000000..4c4d26b --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantExpHunter.java @@ -0,0 +1,69 @@ +package su.nightexpress.excellentenchants.enchantment.impl.weapon; + +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.event.entity.EntityResurrectEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.utils.NumberUtil; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.type.DeathEnchant; +import su.nightexpress.excellentenchants.enchantment.config.EnchantScaler; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; + +public class EnchantExpHunter extends ExcellentEnchant implements DeathEnchant { + + public static final String ID = "exp_hunter"; + public static final String PLACEHOLDER_EXP_MODIFIER = "%enchantment_exp_modifier%"; + + private EnchantScaler expModifier; + + public EnchantExpHunter(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription("Increases exp drop from mobs by " + PLACEHOLDER_EXP_MODIFIER + "%."); + this.getDefaults().setLevelMax(5); + this.getDefaults().setTier(0.3); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.expModifier = EnchantScaler.read(this, "Settings.Exp_Modifier", + "1.0 + " + Placeholders.ENCHANTMENT_LEVEL + " * 0.5", + "Exp modifier value. The original exp amount will be multiplied on this value."); + + this.addPlaceholder(PLACEHOLDER_EXP_MODIFIER, level -> NumberUtil.format(this.getExpModifier(level) * 100D - 100D)); + } + + public final double getExpModifier(int level) { + return this.expModifier.getValue(level); + } + + @Override + @NotNull + public EnchantmentTarget getCategory() { + return EnchantmentTarget.WEAPON; + } + + @Override + public boolean onKill(@NotNull EntityDeathEvent event, @NotNull LivingEntity entity, @NotNull Player killer, ItemStack weapon, int level) { + double expModifier = this.getExpModifier(level); + double expFinal = Math.ceil((double) event.getDroppedExp() * expModifier); + + event.setDroppedExp((int) expFinal); + return true; + } + + @Override + public boolean onDeath(@NotNull EntityDeathEvent event, @NotNull LivingEntity entity, ItemStack item, int level) { + return false; + } + + @Override + public boolean onResurrect(@NotNull EntityResurrectEvent event, @NotNull LivingEntity entity, @NotNull ItemStack item, int level) { + return false; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantIceAspect.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantIceAspect.java new file mode 100644 index 0000000..ff3352b --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantIceAspect.java @@ -0,0 +1,85 @@ +package su.nightexpress.excellentenchants.enchantment.impl.weapon; + +import org.bukkit.Material; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffectType; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.utils.values.UniParticle; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.meta.Potioned; +import su.nightexpress.excellentenchants.api.enchantment.type.CombatEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; +import su.nightexpress.excellentenchants.enchantment.impl.meta.PotionImplementation; + +public class EnchantIceAspect extends ExcellentEnchant implements Chanced, Potioned, CombatEnchant { + + public static final String ID = "ice_aspect"; + + private ChanceImplementation chanceImplementation; + private PotionImplementation potionImplementation; + + public EnchantIceAspect(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription("Freezes and applies " + Placeholders.ENCHANTMENT_POTION_TYPE + " " + Placeholders.ENCHANTMENT_POTION_LEVEL + " (" + Placeholders.ENCHANTMENT_POTION_DURATION + "s.) on hit."); + this.getDefaults().setLevelMax(3); + this.getDefaults().setTier(0.1); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.chanceImplementation = ChanceImplementation.create(this, "100"); + this.potionImplementation = PotionImplementation.create(this, PotionEffectType.SLOW, false, + "3.0 + " + Placeholders.ENCHANTMENT_LEVEL, + Placeholders.ENCHANTMENT_LEVEL); + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + @NotNull + @Override + public PotionImplementation getPotionImplementation() { + return potionImplementation; + } + + @NotNull + @Override + public EnchantmentTarget getCategory() { + return EnchantmentTarget.WEAPON; + } + + @NotNull + @Override + public EventPriority getAttackPriority() { + return EventPriority.HIGHEST; + } + + @Override + public boolean onAttack(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + if (!this.checkTriggerChance(level)) return false; + if (!this.addEffect(victim, level)) return false; + + victim.setFreezeTicks(victim.getMaxFreezeTicks()); + + if (this.hasVisualEffects()) { + UniParticle.blockCrack(Material.ICE).play(victim.getEyeLocation(), 0.25, 0.15, 30); + } + return true; + } + + @Override + public boolean onProtect(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + return false; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantInfernus.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantInfernus.java new file mode 100644 index 0000000..8697e8a --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantInfernus.java @@ -0,0 +1,84 @@ +package su.nightexpress.excellentenchants.enchantment.impl.weapon; + +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Trident; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.ProjectileLaunchEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.api.manager.EventListener; +import su.nexmedia.engine.utils.NumberUtil; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.type.GenericEnchant; +import su.nightexpress.excellentenchants.enchantment.config.EnchantScaler; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.util.EnchantUtils; + +public class EnchantInfernus extends ExcellentEnchant implements GenericEnchant, EventListener { + + public static final String ID = "infernus"; + public static final String PLACEHOLDER_FIRE_DURATION = "%enchantment_fire_duration%"; + + private EnchantScaler fireTicks; + + public EnchantInfernus(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription("Launched trident will ignite the enemy for " + PLACEHOLDER_FIRE_DURATION + "s. on hit."); + this.getDefaults().setLevelMax(3); + this.getDefaults().setTier(0.1); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.fireTicks = EnchantScaler.read(this, "Settings.Fire_Ticks", + "60 + " + Placeholders.ENCHANTMENT_LEVEL + " * 20", + "Sets for how long (in ticks) entity will be ignited on hit. 20 ticks = 1 second."); + + this.addPlaceholder(PLACEHOLDER_FIRE_DURATION, level -> NumberUtil.format((double) this.getFireTicks(level) / 20D)); + } + + public int getFireTicks(int level) { + return (int) this.fireTicks.getValue(level); + } + + @Override + @NotNull + public EnchantmentTarget getCategory() { + return EnchantmentTarget.TRIDENT; + } + + @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true) + public void onInfernusTridentLaunch(ProjectileLaunchEvent e) { + Entity entity = e.getEntity(); + if (!(entity instanceof Trident trident)) return; + if (!(trident.getShooter() instanceof LivingEntity shooter)) return; + if (!this.isAvailableToUse(shooter)) return; + + ItemStack item = trident.getItem(); + + int level = EnchantUtils.getLevel(item, this.getBackend()); + if (level <= 0) return; + + trident.setFireTicks(Integer.MAX_VALUE); + } + + @EventHandler(priority = EventPriority.HIGH, ignoreCancelled = true) + public void onInfernusDamageApply(EntityDamageByEntityEvent e) { + Entity entity = e.getDamager(); + if (!(entity instanceof Trident trident)) return; + + ItemStack item = trident.getItem(); + + int level = EnchantUtils.getLevel(item, this.getBackend()); + if (level <= 0 || trident.getFireTicks() <= 0) return; + + int ticks = this.getFireTicks(level); + e.getEntity().setFireTicks(ticks); + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantNimble.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantNimble.java new file mode 100644 index 0000000..d3c38c5 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantNimble.java @@ -0,0 +1,73 @@ +package su.nightexpress.excellentenchants.enchantment.impl.weapon; + +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.event.entity.EntityResurrectEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.utils.PlayerUtil; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.type.DeathEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; + +public class EnchantNimble extends ExcellentEnchant implements Chanced, DeathEnchant { + + public static final String ID = "nimble"; + + private ChanceImplementation chanceImplementation; + + public EnchantNimble(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription("Moves all mob's loot directly to your inventory."); + this.getDefaults().setLevelMax(1); + this.getDefaults().setTier(0.4); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.chanceImplementation = ChanceImplementation.create(this); + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + @NotNull + @Override + public EnchantmentTarget getCategory() { + return EnchantmentTarget.WEAPON; + } + + @NotNull + @Override + public EventPriority getKillPriority() { + return EventPriority.HIGHEST; + } + + @Override + public boolean onKill(@NotNull EntityDeathEvent event, @NotNull LivingEntity entity, @NotNull Player killer, ItemStack weapon, int level) { + if (!this.checkTriggerChance(level)) return false; + + event.getDrops().forEach(item -> PlayerUtil.addItem(killer, item)); + event.getDrops().clear(); + return true; + } + + @Override + public boolean onDeath(@NotNull EntityDeathEvent event, @NotNull LivingEntity entity, ItemStack item, int level) { + return false; + } + + @Override + public boolean onResurrect(@NotNull EntityResurrectEvent event, @NotNull LivingEntity entity, @NotNull ItemStack item, int level) { + return false; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantParalyze.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantParalyze.java new file mode 100644 index 0000000..e0554e6 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantParalyze.java @@ -0,0 +1,84 @@ +package su.nightexpress.excellentenchants.enchantment.impl.weapon; + +import org.bukkit.Particle; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffectType; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.utils.values.UniParticle; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.meta.Potioned; +import su.nightexpress.excellentenchants.api.enchantment.type.CombatEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; +import su.nightexpress.excellentenchants.enchantment.impl.meta.PotionImplementation; + +public class EnchantParalyze extends ExcellentEnchant implements Chanced, Potioned, CombatEnchant { + + public static final String ID = "paralyze"; + + private ChanceImplementation chanceImplementation; + private PotionImplementation potionImplementation; + + public EnchantParalyze(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription(Placeholders.ENCHANTMENT_CHANCE + "% chance to apply " + Placeholders.ENCHANTMENT_POTION_TYPE + " " + Placeholders.ENCHANTMENT_POTION_LEVEL + " (" + Placeholders.ENCHANTMENT_POTION_DURATION + "s.) on hit."); + this.getDefaults().setLevelMax(3); + this.getDefaults().setTier(0.3); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.chanceImplementation = ChanceImplementation.create(this, + "10.0 * " + Placeholders.ENCHANTMENT_LEVEL); + this.potionImplementation = PotionImplementation.create(this, PotionEffectType.SLOW_DIGGING, false, + "2.5 + " + Placeholders.ENCHANTMENT_LEVEL + " * 0.5", + Placeholders.ENCHANTMENT_LEVEL); + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + @NotNull + @Override + public PotionImplementation getPotionImplementation() { + return potionImplementation; + } + + @NotNull + @Override + public EnchantmentTarget getCategory() { + return EnchantmentTarget.WEAPON; + } + + @NotNull + @Override + public EventPriority getAttackPriority() { + return EventPriority.HIGHEST; + } + + @Override + public boolean onAttack(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + if (!this.checkTriggerChance(level)) return false; + if (!this.addEffect(victim, level)) return false; + + if (this.hasVisualEffects()) { + UniParticle.of(Particle.CRIT_MAGIC).play(victim.getEyeLocation(), 0.25, 0.1, 30); + } + return true; + } + + @Override + public boolean onProtect(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + return false; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantRage.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantRage.java new file mode 100644 index 0000000..8ecb555 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantRage.java @@ -0,0 +1,84 @@ +package su.nightexpress.excellentenchants.enchantment.impl.weapon; + +import org.bukkit.Particle; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffectType; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.utils.values.UniParticle; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.meta.Potioned; +import su.nightexpress.excellentenchants.api.enchantment.type.CombatEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; +import su.nightexpress.excellentenchants.enchantment.impl.meta.PotionImplementation; + +public class EnchantRage extends ExcellentEnchant implements Chanced, Potioned, CombatEnchant { + + public static final String ID = "rage"; + + private ChanceImplementation chanceImplementation; + private PotionImplementation potionImplementation; + + public EnchantRage(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription(Placeholders.ENCHANTMENT_CHANCE + "% chance to get " + Placeholders.ENCHANTMENT_POTION_TYPE + " " + Placeholders.ENCHANTMENT_POTION_LEVEL + " (" + Placeholders.ENCHANTMENT_POTION_DURATION + "s.) on hit."); + this.getDefaults().setLevelMax(3); + this.getDefaults().setTier(0.5); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.chanceImplementation = ChanceImplementation.create(this, + "7.0 + " + Placeholders.ENCHANTMENT_LEVEL); + this.potionImplementation = PotionImplementation.create(this, PotionEffectType.INCREASE_DAMAGE, false, + "3.0 + " + Placeholders.ENCHANTMENT_LEVEL + " * 0.5", + Placeholders.ENCHANTMENT_LEVEL); + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + @NotNull + @Override + public PotionImplementation getPotionImplementation() { + return potionImplementation; + } + + @NotNull + @Override + public EnchantmentTarget getCategory() { + return EnchantmentTarget.WEAPON; + } + + @NotNull + @Override + public EventPriority getAttackPriority() { + return EventPriority.HIGHEST; + } + + @Override + public boolean onAttack(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + if (!this.checkTriggerChance(level)) return false; + if (!this.addEffect(damager, level)) return false; + + if (this.hasVisualEffects()) { + UniParticle.of(Particle.LAVA).play(damager.getEyeLocation(), 0.25, 0.1, 30); + } + return true; + } + + @Override + public boolean onProtect(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + return false; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantRocket.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantRocket.java new file mode 100644 index 0000000..6f41148 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantRocket.java @@ -0,0 +1,107 @@ +package su.nightexpress.excellentenchants.enchantment.impl.weapon; + +import org.bukkit.*; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Firework; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.FireworkMeta; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.utils.random.Rnd; +import su.nexmedia.engine.utils.values.UniSound; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.type.CombatEnchant; +import su.nightexpress.excellentenchants.enchantment.config.EnchantScaler; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; + +public class EnchantRocket extends ExcellentEnchant implements Chanced, CombatEnchant { + + public static final String ID = "rocket"; + + private EnchantScaler fireworkPower; + private ChanceImplementation chanceImplementation; + + public EnchantRocket(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription(Placeholders.ENCHANTMENT_CHANCE + "% chance to launch your enemy into the space."); + this.getDefaults().setLevelMax(3); + this.getDefaults().setTier(0.5); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.chanceImplementation = ChanceImplementation.create(this, + "4.0 + " + Placeholders.ENCHANTMENT_LEVEL); + this.fireworkPower = EnchantScaler.read(this, "Settings.Firework_Power", + Placeholders.ENCHANTMENT_LEVEL + " * 0.25", + "Firework power. The more power = the higher fly distance."); + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + public final double getFireworkPower(int level) { + return this.fireworkPower.getValue(level); + } + + @Override + @NotNull + public EnchantmentTarget getCategory() { + return EnchantmentTarget.WEAPON; + } + + @NotNull + @Override + public EventPriority getAttackPriority() { + return EventPriority.HIGHEST; + } + + @Override + public boolean onAttack(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + if (!this.checkTriggerChance(level)) return false; + + if (victim.isInsideVehicle()) { + victim.leaveVehicle(); + } + + Firework firework = this.createRocket(victim.getLocation(), level); + firework.addPassenger(victim); + + UniSound.of(Sound.ENTITY_FIREWORK_ROCKET_LAUNCH).play(victim.getLocation()); + return true; + } + + @NotNull + private Firework createRocket(@NotNull Location location, int level) { + World world = location.getWorld(); + if (world == null) { + throw new IllegalStateException("World is null!"); + } + + Firework firework = (Firework) world.spawnEntity(location, EntityType.FIREWORK); + FireworkMeta meta = firework.getFireworkMeta(); + FireworkEffect.Type type = Rnd.get(FireworkEffect.Type.values()); + Color color = Color.fromBGR(Rnd.nextInt(255), Rnd.nextInt(255), Rnd.nextInt(255)); + Color fade = Color.fromBGR(Rnd.nextInt(255), Rnd.nextInt(255), Rnd.nextInt(255)); + FireworkEffect effect = FireworkEffect.builder().flicker(Rnd.nextBoolean()).withColor(color).withFade(fade).with(type).trail(Rnd.nextBoolean()).build(); + meta.addEffect(effect); + meta.setPower((int) this.getFireworkPower(level)); + firework.setFireworkMeta(meta); + return firework; + } + + @Override + public boolean onProtect(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + return false; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantScavenger.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantScavenger.java new file mode 100644 index 0000000..df514bf --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantScavenger.java @@ -0,0 +1,131 @@ +package su.nightexpress.excellentenchants.enchantment.impl.weapon; + +import org.bukkit.Material; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.event.entity.EntityResurrectEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.utils.Pair; +import su.nexmedia.engine.utils.StringUtil; +import su.nexmedia.engine.utils.random.Rnd; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.type.DeathEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; + +import java.util.HashMap; +import java.util.Map; + +public class EnchantScavenger extends ExcellentEnchant implements Chanced, DeathEnchant { + + public static final String ID = "scavenger"; + + private Map>> loot; + + private ChanceImplementation chanceImplementation; + + public EnchantScavenger(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription(Placeholders.ENCHANTMENT_CHANCE + "% chance to obtain additional loot from mobs."); + this.getDefaults().setLevelMax(3); + this.getDefaults().setTier(0.3); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.chanceImplementation = ChanceImplementation.create(this, + "15.0 + " + Placeholders.ENCHANTMENT_LEVEL + " * 10"); + + this.loot = new HashMap<>(); + + if (!this.cfg.contains("Settings.Treasures")) { + cfg.addMissing("Settings.Treasures.VILLAGER.EMERALD.Amount", "1:1"); + cfg.addMissing("Settings.Treasures.VILLAGER.EMERALD.Chance", "50"); + + cfg.addMissing("Settings.Treasures.SKELETON.BONE_MEAL.Amount", "1:2"); + cfg.addMissing("Settings.Treasures.SKELETON.BONE_MEAL.Chance", "50"); + + cfg.saveChanges(); + } + + for (String eId : cfg.getSection("Settings.Treasures")) { + EntityType eType = StringUtil.getEnum(eId, EntityType.class).orElse(null); + if (eType == null || !eType.isAlive()) { + plugin.error("[Scavenger] Invalid entity type '" + eId + "' !"); + continue; + } + + Map> items = new HashMap<>(); + for (String sFromArray : cfg.getSection("Settings.Treasures." + eId)) { + Material material = Material.getMaterial(sFromArray.toUpperCase()); + if (material == null) { + plugin.error("[Scavenger] Invalid item material '" + sFromArray + "' !"); + continue; + } + + String path = "Settings.Treasures." + eId + "." + sFromArray + "."; + String[] amountSplit = cfg.getString(path + "Amount", "1:1").split(":"); + int amountMin = StringUtil.getInteger(amountSplit[0], 1); + int amountMax = StringUtil.getInteger(amountSplit[1], 1); + int[] amount = new int[]{amountMin, amountMax}; + + double chance = cfg.getDouble(path + "Chance"); + if (chance <= 0) continue; + + Pair item = Pair.of(amount, chance); + items.put(material, item); + } + this.loot.put(eType, items); + } + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + @Override + @NotNull + public EnchantmentTarget getCategory() { + return EnchantmentTarget.WEAPON; + } + + @Override + public boolean onKill(@NotNull EntityDeathEvent event, @NotNull LivingEntity entity, @NotNull Player killer, ItemStack weapon, int level) { + Map> items = this.loot.get(entity.getType()); + if (items == null) return false; + + if (!this.checkTriggerChance(level)) return false; + + items.forEach((material, data) -> { + double chance = data.getSecond(); + if (Rnd.get(true) > chance) return; + + int amount = Rnd.get(data.getFirst()[0], data.getFirst()[1]); + if (amount <= 0) return; + + ItemStack item = new ItemStack(material); + event.getDrops().add(item); + }); + + return true; + } + + @Override + public boolean onDeath(@NotNull EntityDeathEvent event, @NotNull LivingEntity entity, ItemStack item, int level) { + return false; + } + + @Override + public boolean onResurrect(@NotNull EntityResurrectEvent event, @NotNull LivingEntity entity, @NotNull ItemStack item, int level) { + return false; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantSurprise.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantSurprise.java new file mode 100644 index 0000000..1407c63 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantSurprise.java @@ -0,0 +1,91 @@ +package su.nightexpress.excellentenchants.enchantment.impl.weapon; + +import org.bukkit.Color; +import org.bukkit.Particle; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.utils.random.Rnd; +import su.nexmedia.engine.utils.values.UniParticle; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.meta.Potioned; +import su.nightexpress.excellentenchants.api.enchantment.type.CombatEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; +import su.nightexpress.excellentenchants.enchantment.impl.meta.PotionImplementation; + +public class EnchantSurprise extends ExcellentEnchant implements Chanced, Potioned, CombatEnchant { + + public static final String ID = "surprise"; + + private ChanceImplementation chanceImplementation; + private PotionImplementation potionImplementation; + + public EnchantSurprise(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription(Placeholders.ENCHANTMENT_CHANCE + "% chance to apply random potion effect to enemy on hit."); + this.getDefaults().setLevelMax(3); + this.getDefaults().setTier(0.75); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.chanceImplementation = ChanceImplementation.create(this, + "2.25 * " + Placeholders.ENCHANTMENT_LEVEL); + this.potionImplementation = PotionImplementation.create(this, PotionEffectType.BLINDNESS, false, + "3.0 + " + Placeholders.ENCHANTMENT_LEVEL, + Placeholders.ENCHANTMENT_LEVEL); + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + @NotNull + @Override + public PotionImplementation getPotionImplementation() { + return potionImplementation; + } + + @NotNull + @Override + public EnchantmentTarget getCategory() { + return EnchantmentTarget.WEAPON; + } + + @NotNull + @Override + public EventPriority getAttackPriority() { + return EventPriority.HIGHEST; + } + + @Override + public boolean onAttack(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + if (!this.checkTriggerChance(level)) return false; + + PotionEffect effect = new PotionEffect(Rnd.get(PotionEffectType.values()), this.getEffectDuration(level), Math.max(0, this.getEffectAmplifier(level) - 1), false, false); + if (!victim.addPotionEffect(effect)) return false; + + if (this.hasVisualEffects()) { + Color color = Color.fromRGB(Rnd.nextInt(256), Rnd.nextInt(256), Rnd.nextInt(256)); + Particle.DustOptions dustOptions = new Particle.DustOptions(color, 2f); + UniParticle.of(Particle.REDSTONE, dustOptions).play(victim.getEyeLocation(), 0.25, 0.1, 25); + } + return true; + } + + @Override + public boolean onProtect(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + return false; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantTemper.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantTemper.java new file mode 100644 index 0000000..244c4cb --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantTemper.java @@ -0,0 +1,99 @@ +package su.nightexpress.excellentenchants.enchantment.impl.weapon; + +import org.bukkit.attribute.Attribute; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.utils.EntityUtil; +import su.nexmedia.engine.utils.NumberUtil; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.type.CombatEnchant; +import su.nightexpress.excellentenchants.enchantment.config.EnchantScaler; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; + +public class EnchantTemper extends ExcellentEnchant implements CombatEnchant { + + public static final String ID = "temper"; + public static final String PLACEHOLDER_DAMAGE_AMOUNT = "%enchantment_damage_amount%"; + public static final String PLACEHOLDER_DAMAGE_CAPACITY = "%enchantment_damage_capacity%"; + public static final String PLACEHOLDER_HEALTH_POINT = "%enchantment_health_point%"; + + private EnchantScaler damageAmount; + private EnchantScaler damageCapacity; + private EnchantScaler healthPoint; + + public EnchantTemper(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription("Inflicts " + PLACEHOLDER_DAMAGE_AMOUNT + "% (max. " + PLACEHOLDER_DAMAGE_CAPACITY + "%) more damage for each " + PLACEHOLDER_HEALTH_POINT + " hearts missing."); + this.getDefaults().setLevelMax(5); + this.getDefaults().setTier(0.3); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.damageAmount = EnchantScaler.read(this, "Settings.Damage.Amount", + "0.01 * " + Placeholders.ENCHANTMENT_LEVEL, + "On how much (in percent) the damage will be increased per each Health Point?"); + this.damageCapacity = EnchantScaler.read(this, "Settings.Damage.Capacity", "2.0", + "Maximal possible value for the Damage.Amount."); + this.healthPoint = EnchantScaler.read(this, "Settings.Health.Point", "0.5", + "For how much every missing hearts damage will be increased?"); + + this.addPlaceholder(PLACEHOLDER_DAMAGE_AMOUNT, level -> NumberUtil.format(this.getDamageAmount(level) * 100D)); + this.addPlaceholder(PLACEHOLDER_DAMAGE_CAPACITY, level -> NumberUtil.format(this.getDamageCapacity(level) * 100D)); + this.addPlaceholder(PLACEHOLDER_HEALTH_POINT, level -> NumberUtil.format(this.getHealthPoint(level))); + } + + public double getDamageAmount(int level) { + return this.damageAmount.getValue(level); + } + + public double getDamageCapacity(int level) { + return this.damageCapacity.getValue(level); + } + + public double getHealthPoint(int level) { + return this.healthPoint.getValue(level); + } + + @NotNull + @Override + public EnchantmentTarget getCategory() { + return EnchantmentTarget.WEAPON; + } + + @NotNull + @Override + public EventPriority getAttackPriority() { + return EventPriority.NORMAL; + } + + @Override + public boolean onAttack(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + double healthPoint = this.getHealthPoint(level); + double healthHas = damager.getHealth(); + double healthMax = EntityUtil.getAttribute(damager, Attribute.GENERIC_MAX_HEALTH); + double healthDiff = healthMax - healthHas; + if (healthHas >= healthMax || healthDiff < healthPoint) return false; + + int pointAmount = (int) (healthDiff / healthPoint); + if (pointAmount == 0) return false; + + double damageAmount = this.getDamageAmount(level); + double damageCap = this.getDamageCapacity(level); + double damageFinal = Math.min(damageCap, 1D + damageAmount * pointAmount); + + event.setDamage(event.getDamage() * damageFinal); + return true; + } + + @Override + public boolean onProtect(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + return false; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantThrifty.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantThrifty.java new file mode 100644 index 0000000..4775865 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantThrifty.java @@ -0,0 +1,123 @@ +package su.nightexpress.excellentenchants.enchantment.impl.weapon; + +import org.bukkit.NamespacedKey; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.EntityType; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.CreatureSpawnEvent; +import org.bukkit.event.entity.EntityDeathEvent; +import org.bukkit.event.entity.EntityResurrectEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.api.config.JOption; +import su.nexmedia.engine.api.manager.EventListener; +import su.nexmedia.engine.utils.EngineUtils; +import su.nexmedia.engine.utils.PDCUtil; +import su.nexmedia.engine.utils.StringUtil; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.type.DeathEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; +import su.nightexpress.excellentenchants.hook.HookId; +import su.nightexpress.excellentenchants.hook.impl.MythicMobsHook; + +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +public class EnchantThrifty extends ExcellentEnchant implements Chanced, DeathEnchant, EventListener { + + public static final String ID = "thrifty"; + + private final NamespacedKey keyEntityIgnored; + + private Set ignoredEntityTypes; + private Set ignoredSpawnReasons; + private boolean ignoreMythicMobs; + + private ChanceImplementation chanceImplementation; + + public EnchantThrifty(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription(Placeholders.ENCHANTMENT_CHANCE + "% chance to obtain mob spawn egg on kill."); + this.getDefaults().setLevelMax(3); + this.getDefaults().setTier(0.75); + + this.keyEntityIgnored = new NamespacedKey(plugin, ID + "_ignored"); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.chanceImplementation = ChanceImplementation.create(this, + "5.0 + " + Placeholders.ENCHANTMENT_LEVEL + " * 3"); + + this.ignoredEntityTypes = JOption.create("Settings.Ignored_Entity_Types", + Set.of(EntityType.WITHER.name(), EntityType.ENDER_DRAGON.name()), + "List of entity types, that will not drop spawn eggs.", + "https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/entity/EntityType.html" + ).read(cfg).stream().map(e -> StringUtil.getEnum(e, EntityType.class).orElse(null)) + .filter(Objects::nonNull).collect(Collectors.toSet()); + + this.ignoredSpawnReasons = JOption.create("Settings.Ignored_Spawn_Reasons", + Set.of(CreatureSpawnEvent.SpawnReason.SPAWNER_EGG.name(), + CreatureSpawnEvent.SpawnReason.SPAWNER.name(), + CreatureSpawnEvent.SpawnReason.DISPENSE_EGG.name()), + "Entities will not drop spawn eggs if they were spawned by one of the reasons below.", + "https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/event/entity/CreatureSpawnEvent.SpawnReason.html" + ).read(cfg).stream().map(e -> StringUtil.getEnum(e, CreatureSpawnEvent.SpawnReason.class).orElse(null)) + .filter(Objects::nonNull).collect(Collectors.toSet()); + + this.ignoreMythicMobs = JOption.create("Settings.Ignore_MythicMobs", true, + "Sets whether or not MythicMobs should be immune to enchantment effect." + ).read(cfg); + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + @Override + @NotNull + public EnchantmentTarget getCategory() { + return EnchantmentTarget.WEAPON; + } + + @Override + public boolean onKill(@NotNull EntityDeathEvent event, @NotNull LivingEntity entity, @NotNull Player killer, ItemStack weapon, int level) { + if (this.ignoredEntityTypes.contains(entity.getType())) return false; + if (PDCUtil.getBoolean(entity, this.keyEntityIgnored).orElse(false)) return false; + if (this.ignoreMythicMobs && EngineUtils.hasPlugin(HookId.MYTHIC_MOBS) && MythicMobsHook.isMythicMob(entity)) return false; + if (!this.checkTriggerChance(level)) return false; + + ItemStack eggItem = plugin.getEnchantNMS().getSpawnEgg(entity); + if (eggItem == null) return false; + + event.getDrops().add(eggItem); + return true; + } + + @Override + public boolean onDeath(@NotNull EntityDeathEvent event, @NotNull LivingEntity entity, ItemStack item, int level) { + return false; + } + + @Override + public boolean onResurrect(@NotNull EntityResurrectEvent event, @NotNull LivingEntity entity, @NotNull ItemStack item, int level) { + return false; + } + + @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onCreatureSpawn(CreatureSpawnEvent event) { + if (!this.ignoredSpawnReasons.contains(event.getSpawnReason())) return; + + PDCUtil.set(event.getEntity(), this.keyEntityIgnored, true); + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantThunder.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantThunder.java new file mode 100644 index 0000000..5dbfb3f --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantThunder.java @@ -0,0 +1,95 @@ +package su.nightexpress.excellentenchants.enchantment.impl.weapon; + +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.Item; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.metadata.FixedMetadataValue; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.api.config.JOption; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.type.CombatEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; + +public class EnchantThunder extends ExcellentEnchant implements Chanced, CombatEnchant { + + public static final String ID = "thunder"; + + private static final String META_NO_ITEM_DAMAGE = "noItemDamage"; + + private boolean inThunderstormOnly; + private ChanceImplementation chanceImplementation; + + public EnchantThunder(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription(Placeholders.ENCHANTMENT_CHANCE + "% chance to summon lightning to enemy on hit."); + this.getDefaults().setLevelMax(3); + this.getDefaults().setTier(0.3); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.chanceImplementation = ChanceImplementation.create(this, + "10.0 * " + Placeholders.ENCHANTMENT_LEVEL); + this.inThunderstormOnly = JOption.create("Settings.During_Thunderstorm_Only", false, + "When 'true' the enchantment will be triggered only if there is an active thunderstorm in the world.").read(cfg); + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + public boolean isInThunderstormOnly() { + return inThunderstormOnly; + } + + @Override + @NotNull + public EnchantmentTarget getCategory() { + return EnchantmentTarget.WEAPON; + } + + @NotNull + @Override + public EventPriority getAttackPriority() { + return EventPriority.HIGHEST; + } + + @Override + public boolean onAttack(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + if (this.isInThunderstormOnly() && !victim.getWorld().isThundering()) return false; + if (victim.getLocation().getBlock().getLightFromSky() != 15) return false; + if (!this.checkTriggerChance(level)) return false; + + plugin.getServer().getScheduler().runTask(plugin, () -> { + if (victim.isDead()) return; + victim.setNoDamageTicks(0); + victim.getWorld().strikeLightning(victim.getLocation()).setMetadata(META_NO_ITEM_DAMAGE, new FixedMetadataValue(plugin, true)); + }); + + return true; + } + + @Override + public boolean onProtect(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + return false; + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onItemDamage(EntityDamageByEntityEvent e) { + if (!e.getDamager().hasMetadata(META_NO_ITEM_DAMAGE)) return; + if (!(e.getEntity() instanceof Item item)) return; + + e.setCancelled(true); + item.setFireTicks(0); + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantVampire.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantVampire.java new file mode 100644 index 0000000..b388773 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantVampire.java @@ -0,0 +1,108 @@ +package su.nightexpress.excellentenchants.enchantment.impl.weapon; + +import org.bukkit.Particle; +import org.bukkit.attribute.Attribute; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.EntityRegainHealthEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.api.config.JOption; +import su.nexmedia.engine.utils.EntityUtil; +import su.nexmedia.engine.utils.NumberUtil; +import su.nexmedia.engine.utils.values.UniParticle; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.type.CombatEnchant; +import su.nightexpress.excellentenchants.enchantment.config.EnchantScaler; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; + +public class EnchantVampire extends ExcellentEnchant implements Chanced, CombatEnchant { + + public static final String ID = "vampire"; + public static final String PLACEHOLDER_HEAL_AMOUNT = "%enchantment_heal_amount%"; + + private EnchantScaler healAmount; + private boolean healMultiplier; + private ChanceImplementation chanceImplementation; + + public EnchantVampire(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription(Placeholders.ENCHANTMENT_CHANCE + "% chance to heal for " + PLACEHOLDER_HEAL_AMOUNT + " heart(s) on hit."); + this.getDefaults().setLevelMax(4); + this.getDefaults().setTier(0.75); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.chanceImplementation = ChanceImplementation.create(this, + "25.0 + " + Placeholders.ENCHANTMENT_LEVEL + " * 5.0"); + + this.healAmount = EnchantScaler.read(this, "Settings.Heal.Amount", + "0.25 * " + Placeholders.ENCHANTMENT_LEVEL, + "Amount of health to be restored for attacker."); + this.healMultiplier = JOption.create("Settings.Heal.As_Multiplier", false, + "When 'true', the option above will work as a multiplier of the inflicted damage.").read(cfg); + + this.addPlaceholder(PLACEHOLDER_HEAL_AMOUNT, level -> NumberUtil.format(this.isHealMultiplier() ? getHealAmount(level) * 100D : getHealAmount(level))); + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + public double getHealAmount(int level) { + return this.healAmount.getValue(level); + } + + public boolean isHealMultiplier() { + return healMultiplier; + } + + @Override + @NotNull + public EnchantmentTarget getCategory() { + return EnchantmentTarget.WEAPON; + } + + @NotNull + @Override + public EventPriority getAttackPriority() { + return EventPriority.MONITOR; + } + + @Override + public boolean onAttack(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + double healthMax = EntityUtil.getAttribute(damager, Attribute.GENERIC_MAX_HEALTH); + double healthHas = damager.getHealth(); + if (healthHas == healthMax) return false; + + if (!this.checkTriggerChance(level)) return false; + + double healAmount = this.getHealAmount(level); + double healFinal = this.isHealMultiplier() ? event.getFinalDamage() * healAmount : healAmount; + + EntityRegainHealthEvent healthEvent = new EntityRegainHealthEvent(damager, healFinal, EntityRegainHealthEvent.RegainReason.CUSTOM); + plugin.getPluginManager().callEvent(healthEvent); + if (healthEvent.isCancelled()) return false; + + damager.setHealth(Math.min(healthMax, healthHas + healthEvent.getAmount())); + + if (this.hasVisualEffects()) { + UniParticle.of(Particle.HEART).play(damager.getEyeLocation(), 0.25, 0.15, 5); + } + return true; + } + + @Override + public boolean onProtect(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + return false; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantVenom.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantVenom.java new file mode 100644 index 0000000..371145a --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantVenom.java @@ -0,0 +1,84 @@ +package su.nightexpress.excellentenchants.enchantment.impl.weapon; + +import org.bukkit.Particle; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffectType; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.utils.values.UniParticle; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.meta.Potioned; +import su.nightexpress.excellentenchants.api.enchantment.type.CombatEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; +import su.nightexpress.excellentenchants.enchantment.impl.meta.PotionImplementation; + +public class EnchantVenom extends ExcellentEnchant implements Chanced, Potioned, CombatEnchant { + + public static final String ID = "venom"; + + private ChanceImplementation chanceImplementation; + private PotionImplementation potionImplementation; + + public EnchantVenom(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription(Placeholders.ENCHANTMENT_CHANCE + "% chance to apply " + Placeholders.ENCHANTMENT_POTION_TYPE + " " + Placeholders.ENCHANTMENT_POTION_LEVEL + " (" + Placeholders.ENCHANTMENT_POTION_DURATION + "s.) on hit."); + this.getDefaults().setLevelMax(3); + this.getDefaults().setTier(0.3); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.chanceImplementation = ChanceImplementation.create(this, + "30.0 + " + Placeholders.ENCHANTMENT_LEVEL + " * 10.0"); + this.potionImplementation = PotionImplementation.create(this, PotionEffectType.POISON, false, + "2.0 + " + Placeholders.ENCHANTMENT_LEVEL, + Placeholders.ENCHANTMENT_LEVEL); + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + @NotNull + @Override + public PotionImplementation getPotionImplementation() { + return potionImplementation; + } + + @NotNull + @Override + public EnchantmentTarget getCategory() { + return EnchantmentTarget.WEAPON; + } + + @NotNull + @Override + public EventPriority getAttackPriority() { + return EventPriority.HIGHEST; + } + + @Override + public boolean onAttack(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + if (!this.checkTriggerChance(level)) return false; + if (!this.addEffect(victim, level)) return false; + + if (this.hasVisualEffects()) { + UniParticle.of(Particle.SLIME).play(victim.getEyeLocation(), 0.25, 0.1, 30); + } + return true; + } + + @Override + public boolean onProtect(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + return false; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantVillageDefender.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantVillageDefender.java new file mode 100644 index 0000000..97c6bdd --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantVillageDefender.java @@ -0,0 +1,82 @@ +package su.nightexpress.excellentenchants.enchantment.impl.weapon; + +import org.bukkit.Particle; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.Illager; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.api.config.JOption; +import su.nexmedia.engine.utils.NumberUtil; +import su.nexmedia.engine.utils.values.UniParticle; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.type.CombatEnchant; +import su.nightexpress.excellentenchants.enchantment.config.EnchantScaler; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; + +public class EnchantVillageDefender extends ExcellentEnchant implements CombatEnchant { + + public static final String ID = "village_defender"; + public static final String PLACEHOLDER_DAMAGE_AMOUNT = "%enchantment_damage_amount%"; + + private boolean damageMultiplier; + private EnchantScaler damageAmount; + + public EnchantVillageDefender(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription("Inflicts " + PLACEHOLDER_DAMAGE_AMOUNT + " more damage to all pillagers."); + this.getDefaults().setLevelMax(5); + this.getDefaults().setTier(0.1); + } + + @Override + public void loadSettings() { + super.loadSettings(); + + this.damageAmount = EnchantScaler.read(this, "Settings.Damage.Formula", + "0.5 * " + Placeholders.ENCHANTMENT_LEVEL, + "Amount of additional damage."); + + this.damageMultiplier = JOption.create("Settings.Damage.As_Modifier", false, + "When 'true' the 'Damage.Formula' will work as a multiplier to the original damage.").read(cfg); + + this.addPlaceholder(PLACEHOLDER_DAMAGE_AMOUNT, level -> NumberUtil.format(this.getDamageAddict(level))); + } + + public double getDamageAddict(int level) { + return this.damageAmount.getValue(level); + } + + public boolean isDamageMultiplier() { + return damageMultiplier; + } + + @Override + @NotNull + public EnchantmentTarget getCategory() { + return EnchantmentTarget.WEAPON; + } + + @Override + public boolean onAttack(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + if (!(victim instanceof Illager)) return false; + + double damageAdd = this.getDamageAddict(level); + double damageHas = event.getDamage(); + double damageFinal = this.isDamageMultiplier() ? (damageHas * damageAdd) : (damageHas + damageAdd); + + event.setDamage(damageFinal); + + if (this.hasVisualEffects()) { + UniParticle.of(Particle.VILLAGER_ANGRY).play(victim.getEyeLocation(), 0.25, 0.1, 30); + } + return true; + } + + @Override + public boolean onProtect(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + return false; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantWither.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantWither.java new file mode 100644 index 0000000..5a82a35 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/EnchantWither.java @@ -0,0 +1,84 @@ +package su.nightexpress.excellentenchants.enchantment.impl.weapon; + +import org.bukkit.Particle; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionEffectType; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.utils.values.UniParticle; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.meta.Potioned; +import su.nightexpress.excellentenchants.api.enchantment.type.CombatEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; +import su.nightexpress.excellentenchants.enchantment.impl.meta.PotionImplementation; + +public class EnchantWither extends ExcellentEnchant implements Chanced, Potioned, CombatEnchant { + + public static final String ID = "wither"; + + private ChanceImplementation chanceImplementation; + private PotionImplementation potionImplementation; + + public EnchantWither(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription(Placeholders.ENCHANTMENT_CHANCE + "% chance to apply " + Placeholders.ENCHANTMENT_POTION_TYPE + " " + Placeholders.ENCHANTMENT_POTION_LEVEL + " (" + Placeholders.ENCHANTMENT_POTION_DURATION + "s.) on hit."); + this.getDefaults().setLevelMax(3); + this.getDefaults().setTier(0.5); + } + + @Override + public void loadSettings() { + super.loadSettings(); + this.chanceImplementation = ChanceImplementation.create(this, + "10.0 + " + Placeholders.ENCHANTMENT_LEVEL + " * 5"); + this.potionImplementation = PotionImplementation.create(this, PotionEffectType.WITHER, false, + "3.0 + " + Placeholders.ENCHANTMENT_LEVEL, + Placeholders.ENCHANTMENT_LEVEL); + } + + @NotNull + @Override + public ChanceImplementation getChanceImplementation() { + return chanceImplementation; + } + + @NotNull + @Override + public PotionImplementation getPotionImplementation() { + return potionImplementation; + } + + @NotNull + @Override + public EnchantmentTarget getCategory() { + return EnchantmentTarget.WEAPON; + } + + @NotNull + @Override + public EventPriority getAttackPriority() { + return EventPriority.HIGHEST; + } + + @Override + public boolean onAttack(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + if (!this.checkTriggerChance(level)) return false; + if (!this.addEffect(victim, level)) return false; + + if (this.hasVisualEffects()) { + UniParticle.of(Particle.ASH).play(victim.getEyeLocation(), 0.25, 0.1, 30); + } + return true; + } + + @Override + public boolean onProtect(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + return false; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/SwiperEnchant.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/SwiperEnchant.java new file mode 100644 index 0000000..1d6f989 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/impl/weapon/SwiperEnchant.java @@ -0,0 +1,100 @@ +package su.nightexpress.excellentenchants.enchantment.impl.weapon; + +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.utils.NumberUtil; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.meta.Chanced; +import su.nightexpress.excellentenchants.api.enchantment.type.CombatEnchant; +import su.nightexpress.excellentenchants.enchantment.config.EnchantScaler; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.meta.ChanceImplementation; + +public class SwiperEnchant extends ExcellentEnchant implements CombatEnchant, Chanced { + + public static final String ID = "swiper"; + + public static final String PLACEHOLER_XP_AMOUNT = "%xp_amount%"; + + private ChanceImplementation chanceImplementation; + private EnchantScaler xpAmount; + + public SwiperEnchant(@NotNull ExcellentEnchants plugin) { + super(plugin, ID); + this.getDefaults().setDescription(Placeholders.ENCHANTMENT_CHANCE + "% chance to steal " + PLACEHOLER_XP_AMOUNT + " XP from players."); + this.getDefaults().setLevelMax(3); + this.getDefaults().setTier(0.7); + } + + @Override + public void loadSettings() { + super.loadSettings(); + + this.chanceImplementation = ChanceImplementation.create(this, + "5.0 + " + Placeholders.ENCHANTMENT_LEVEL + " * 2.5"); + + this.xpAmount = EnchantScaler.read(this, "Settings.XP_Amount", + Placeholders.ENCHANTMENT_LEVEL, + "Amount of XP to be stolen on hit."); + + this.addPlaceholder(PLACEHOLER_XP_AMOUNT, level -> NumberUtil.format(this.getXPAmount(level))); + } + + @NotNull + @Override + public EnchantmentTarget getCategory() { + return EnchantmentTarget.WEAPON; + } + + @NotNull + @Override + public Chanced getChanceImplementation() { + return this.chanceImplementation; + } + + public int getXPAmount(int level) { + return (int) this.xpAmount.getValue(level); + } + + private int getExpRequired(int level) { + if (level <= 15) return 2 * level + 7; + if (level <= 30) return 5 * level - 38; + return 9 * level - 158; + } + + private void addXP(@NotNull Player player, int amount) { + int levelHas = player.getLevel(); + int xpHas = player.getTotalExperience(); + + xpHas = Math.max(0, xpHas + amount); + player.setExp(0F); + player.setTotalExperience(0); + player.setLevel(0); + player.giveExp(xpHas); + } + + @Override + public boolean onAttack(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + if (!(damager instanceof Player attacker)) return false; + if (!(victim instanceof Player defender)) return false; + if (defender.getTotalExperience() == 0) return false; + if (!this.checkTriggerChance(level)) return false; + + int amount = this.getXPAmount(level); + if (defender.getTotalExperience() < amount) amount = defender.getTotalExperience(); + + this.addXP(defender, -amount); + this.addXP(attacker, amount); + return true; + } + + @Override + public boolean onProtect(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull LivingEntity victim, @NotNull ItemStack weapon, int level) { + return false; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/listener/EnchantAnvilListener.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/listener/EnchantAnvilListener.java new file mode 100644 index 0000000..772843c --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/listener/EnchantAnvilListener.java @@ -0,0 +1,191 @@ +/* + * Decompiled with CFR 0.151. + * + * Could not load the following classes: + * org.bukkit.Material + * org.bukkit.NamespacedKey + * org.bukkit.Sound + * org.bukkit.entity.Player + * org.bukkit.event.EventHandler + * org.bukkit.event.EventPriority + * org.bukkit.event.inventory.InventoryClickEvent + * org.bukkit.event.inventory.PrepareAnvilEvent + * org.bukkit.inventory.AnvilInventory + * org.bukkit.inventory.Inventory + * org.bukkit.inventory.ItemStack + * org.bukkit.plugin.Plugin + * org.jetbrains.annotations.NotNull + * su.nexmedia.engine.NexPlugin + * su.nexmedia.engine.api.manager.AbstractListener + * su.nexmedia.engine.utils.PDCUtil + * su.nexmedia.engine.utils.values.UniSound + */ +package su.nightexpress.excellentenchants.enchantment.listener; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +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.ItemStack; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.NexPlugin; +import su.nexmedia.engine.api.manager.AbstractListener; +import su.nexmedia.engine.utils.PDCUtil; +import su.nexmedia.engine.utils.values.UniSound; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.ExcellentEnchantsAPI; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.util.EnchantUtils; + +public class EnchantAnvilListener extends AbstractListener { + + private static final NamespacedKey RECHARGED = new NamespacedKey(ExcellentEnchantsAPI.PLUGIN, "item.recharged"); + + public EnchantAnvilListener(@NotNull ExcellentEnchants plugin) { + super(plugin); + } + + @EventHandler(priority=EventPriority.HIGH) + public void onAnvilRename(PrepareAnvilEvent event) { + AnvilInventory inventory = event.getInventory(); + ItemStack first = inventory.getItem(0); + ItemStack second = inventory.getItem(1); + ItemStack result = event.getResult(); + if (first == null) { + first = new ItemStack(Material.AIR); + } + if (second == null) { + second = new ItemStack(Material.AIR); + } + if (result == null) { + result = new ItemStack(Material.AIR); + } + if (first.getType().isAir() || first.getAmount() > 1 || !EnchantUtils.isEnchantable(first)) { + return; + } + if (this.handleRename(event, first, second, result)) { + return; + } + if (this.handleRecharge(event, first, second, result)) { + return; + } + this.handleEnchantMerging(event, first, second, result); + } + + private boolean handleRename(@NotNull PrepareAnvilEvent event, @NotNull ItemStack first, @NotNull ItemStack second, @NotNull ItemStack result) { + if (!(second.getType().isAir() || second.getType() != first.getType() && second.getType() != Material.ENCHANTED_BOOK)) { + return false; + } + if (result.getType() != first.getType()) { + return false; + } + ItemStack result2 = new ItemStack(result); + EnchantUtils.getExcellents(first).forEach((hasEnch, hasLevel) -> EnchantUtils.add(result2, hasEnch.getBackend(), hasLevel, true)); + EnchantUtils.updateDisplay(result2); + event.setResult(result2); + return true; + } + + private boolean handleRecharge(@NotNull PrepareAnvilEvent event, @NotNull ItemStack first, @NotNull ItemStack second, @NotNull ItemStack result) { + int count; + if (second.getType().isAir()) { + return false; + } + HashMap chargable = new HashMap(); + EnchantUtils.getExcellents(first).forEach((enchant, level) -> { + if (enchant.isChargesEnabled() && enchant.isChargesFuel(second) && !enchant.isFullOfCharges(first)) { + chargable.put(enchant, level); + } + }); + if (chargable.isEmpty()) { + return false; + } + ItemStack result2 = new ItemStack(first); + for (count = 0; count < second.getAmount() && !chargable.keySet().stream().allMatch(en -> en.isFullOfCharges(result2)); ++count) { + chargable.forEach((enchant, level) -> EnchantUtils.rechargeCharges(result2, enchant, level)); + } + PDCUtil.set(result2, RECHARGED, count); + EnchantUtils.updateDisplay(result2); + event.setResult(result2); + this.plugin.runTask(task -> event.getInventory().setRepairCost(chargable.size())); + return true; + } + + private boolean handleEnchantMerging(@NotNull PrepareAnvilEvent event, @NotNull ItemStack first, @NotNull ItemStack second, @NotNull ItemStack result) { + if (second.getType().isAir() || second.getAmount() > 1 || !EnchantUtils.isEnchantable(second)) { + return false; + } + if (first.getType() == Material.ENCHANTED_BOOK && second.getType() != first.getType()) { + return false; + } + ItemStack result2 = new ItemStack(result.getType().isAir() ? first : result); + Map enchantments = EnchantUtils.getExcellents(first); + HashMap charges = new HashMap(enchantments.keySet().stream().collect(Collectors.toMap(k -> k, v -> v.getCharges(first)))); + AtomicInteger repairCost = new AtomicInteger(event.getInventory().getRepairCost()); + if (second.getType() == Material.ENCHANTED_BOOK || second.getType() == first.getType()) { + EnchantUtils.getExcellents(second).forEach((enchant, level) -> { + enchantments.merge(enchant, level, (oldLvl, newLvl) -> oldLvl.equals(newLvl) ? Math.min(enchant.getMaxMergeLevel(), oldLvl + 1) : Math.max(oldLvl, newLvl)); + charges.merge(enchant, enchant.getCharges(second), Integer::sum); + }); + } + enchantments.forEach((enchant, level) -> { + if (EnchantUtils.add(result2, enchant.getBackend(), level, false)) { + repairCost.addAndGet(enchant.getAnvilMergeCost(level)); + EnchantUtils.setCharges(result2, enchant, level, charges.getOrDefault(enchant, 0)); + } + }); + if (first.equals(result2)) { + return false; + } + EnchantUtils.updateDisplay(result2); + event.setResult(result2); + this.plugin.runTask(task -> event.getInventory().setRepairCost(repairCost.get())); + return true; + } + + @EventHandler(priority=EventPriority.NORMAL) + public void onClickAnvil(InventoryClickEvent event) { + Inventory inventory = event.getInventory(); + if (!(inventory instanceof AnvilInventory inventory2)) { + return; + } + if (event.getRawSlot() != 2) { + return; + } + ItemStack item = event.getCurrentItem(); + if (item == null) { + return; + } + int count = PDCUtil.getInt(item, RECHARGED).orElse(0); + if (count == 0) { + return; + } + Player player = (Player)event.getWhoClicked(); + if (player.getLevel() < inventory2.getRepairCost()) { + return; + } + player.setLevel(player.getLevel() - inventory2.getRepairCost()); + PDCUtil.remove(item, RECHARGED); + event.getView().setCursor(item); + event.setCancelled(false); + UniSound.of(Sound.BLOCK_ENCHANTMENT_TABLE_USE).play(player); + ItemStack second = inventory2.getItem(1); + if (second != null && !second.getType().isAir()) { + second.setAmount(second.getAmount() - count); + } + inventory2.setItem(0, null); + inventory2.setItem(2, null); + } +} + diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/listener/EnchantGenericListener.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/listener/EnchantGenericListener.java new file mode 100644 index 0000000..7b6f403 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/listener/EnchantGenericListener.java @@ -0,0 +1,252 @@ +package su.nightexpress.excellentenchants.enchantment.listener; + +import org.bukkit.World; +import org.bukkit.block.Chest; +import org.bukkit.entity.*; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.enchantment.EnchantItemEvent; +import org.bukkit.event.entity.*; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryDragEvent; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.event.player.PlayerFishEvent; +import org.bukkit.event.world.LootGenerateEvent; +import org.bukkit.inventory.*; +import org.bukkit.inventory.meta.EnchantmentStorageMeta; +import org.bukkit.inventory.meta.ItemMeta; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.Version; +import su.nexmedia.engine.api.manager.AbstractListener; +import su.nexmedia.engine.utils.EngineUtils; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.config.Config; +import su.nightexpress.excellentenchants.enchantment.EnchantManager; +import su.nightexpress.excellentenchants.enchantment.EnchantPopulator; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.api.enchantment.ObtainType; +import su.nightexpress.excellentenchants.enchantment.registry.EnchantRegistry; +import su.nightexpress.excellentenchants.enchantment.util.EnchantUtils; +import su.nightexpress.excellentenchants.hook.HookId; +import su.nightexpress.excellentenchants.hook.impl.MythicMobsHook; + +import java.util.HashMap; +import java.util.Map; + +public class EnchantGenericListener extends AbstractListener { + + public EnchantGenericListener(@NotNull EnchantManager enchantManager) { + super(enchantManager.plugin()); + } + + @EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = true) + public void onEnchantProjectileShoot(EntityShootBowEvent event) { + if (event.getProjectile() instanceof Projectile projectile) { + EnchantUtils.setSourceWeapon(projectile, event.getBow()); + } + } + + @EventHandler(priority = EventPriority.MONITOR) + public void onEnchantProjectileLand(ProjectileHitEvent event) { + this.plugin.runTask(task -> { + EnchantUtils.removeSourceWeapon(event.getEntity()); + }); + } + + // --------------------------------------------------------------- + // Update enchantment lore after grindstone + // --------------------------------------------------------------- + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onEnchantUpdateGrindstoneClick(InventoryClickEvent event) { + Inventory inventory = event.getInventory(); + if (inventory.getType() != InventoryType.GRINDSTONE) return; + if (event.getRawSlot() == 2) return; + + this.updateGrindstone(inventory); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onEnchantUpdateGrindstoneDrag(InventoryDragEvent event) { + Inventory inventory = event.getInventory(); + if (inventory.getType() != InventoryType.GRINDSTONE) return; + + this.updateGrindstone(inventory); + } + + private void updateGrindstone(@NotNull Inventory inventory) { + this.plugin.getServer().getScheduler().runTask(plugin, () -> { + ItemStack result = inventory.getItem(2); + if (result == null || result.getType().isAir()) return; + + // No NMS registration for 1.18.2, server does not know they are curses. + if (Version.isBehind(Version.V1_19_R3) && !Config.ENCHANTMENTS_INTERNAL_HANDLER.get()) { + Map curses = new HashMap<>(); + for (int slot = 0; slot < 2; slot++) { + ItemStack source = inventory.getItem(slot); + if (source == null || source.getType().isAir()) continue; + + curses.putAll(EnchantUtils.getExcellents(source)); + } + curses.entrySet().removeIf(entry -> !entry.getKey().isCurse()); + curses.forEach((excellentEnchant, level) -> { + EnchantUtils.add(result, excellentEnchant.getBackend(), level, true); + }); + } + EnchantUtils.updateDisplay(result); + }); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onEnchantUpdatePickup(EntityPickupItemEvent event) { + if (!(event.getEntity() instanceof Player player)) return; + + Item item = event.getItem(); + ItemStack itemStack = item.getItemStack(); + if (EnchantUtils.updateDisplay(itemStack)) { + item.setItemStack(itemStack); + } + } + + // --------------------------------------------------------------- + // Handle Enchanting Table + // --------------------------------------------------------------- + @EventHandler(priority = EventPriority.LOW, ignoreCancelled = true) + public void onEnchantPopulateEnchantingTable(final EnchantItemEvent event) { + ItemStack target = event.getItem(); + World world = event.getEnchanter().getWorld(); + + if (!Config.ENCHANTMENTS_INTERNAL_HANDLER.get()) { + EnchantPopulator populator = this.plugin.createPopulator(target, ObtainType.ENCHANTING) + .withWorld(world) + .withLevelGenerator(enchant -> enchant.getLevelByEnchantCost(event.getExpLevelCost())) + .withDefaultPopulation(event.getEnchantsToAdd()); + + event.getEnchantsToAdd().putAll(populator.createPopulation()); + } + + plugin.getServer().getScheduler().runTask(plugin, () -> { + ItemStack result = event.getInventory().getItem(0); + if (result == null) return; + + // Fix enchantments for Enchant Books. + // Enchants are not added on book because they do not exists in NMS. + // Server gets enchants from NMS to apply it on Book NBT tags. + if (Version.isBehind(Version.V1_19_R3)) { + ItemMeta meta = result.getItemMeta(); + if (meta instanceof EnchantmentStorageMeta storageMeta) { + event.getEnchantsToAdd().forEach((enchantment, level) -> { + if (!storageMeta.hasStoredEnchant(enchantment)) { + storageMeta.addStoredEnchant(enchantment, level, true); + } + }); + result.setItemMeta(storageMeta); + } + } + + event.getEnchantsToAdd().forEach((enchantment, level) -> { + ExcellentEnchant enchant = EnchantRegistry.getByKey(enchantment.getKey()); + if (enchant != null) { + EnchantUtils.restoreCharges(result, enchant, level); + } + }); + EnchantUtils.updateDisplay(result); + + event.getInventory().setItem(0, result); + }); + } + + // --------------------------------------------------------------- + // Adding Enchants to Villagers + // --------------------------------------------------------------- + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onEnchantPopulateVillagerAcquire(VillagerAcquireTradeEvent event) { + if (Config.ENCHANTMENTS_INTERNAL_HANDLER.get()) return; + + MerchantRecipe origin = event.getRecipe(); + ItemStack result = origin.getResult(); + if (!EnchantUtils.isEnchantable(result)) return; + + EnchantPopulator populator = this.plugin.createPopulator(result, ObtainType.VILLAGER) + .withWorld(event.getEntity().getWorld()); + if (!populator.populate()) return; + + int uses = origin.getUses(); + int maxUses = origin.getMaxUses(); + boolean expReward = origin.hasExperienceReward(); + int villagerExperience = origin.getVillagerExperience(); + float priceMultiplier = origin.getPriceMultiplier(); + int demand = origin.getDemand(); + int specialPrice = origin.getSpecialPrice(); + + MerchantRecipe recipe = new MerchantRecipe(result, uses, maxUses, expReward, villagerExperience, + priceMultiplier, demand, specialPrice); + recipe.setIngredients(origin.getIngredients()); + event.setRecipe(recipe); + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onEnchantPopulateLoot(LootGenerateEvent event) { + if (Config.ENCHANTMENTS_INTERNAL_HANDLER.get()) return; + if (Config.getObtainSettings(ObtainType.LOOT_GENERATION).isEmpty()) return; + + Entity entity = event.getEntity(); + InventoryHolder holder = event.getInventoryHolder(); + World world = event.getWorld(); + + if (entity instanceof Minecart || holder instanceof Chest) { + event.getLoot().forEach(item -> { + if (item != null && EnchantUtils.isEnchantable(item)) { + this.plugin.createPopulator(item, ObtainType.LOOT_GENERATION) + .withWorld(world) + .populate(); + } + }); + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onEnchantPopulateFishing(PlayerFishEvent event) { + if (Config.ENCHANTMENTS_INTERNAL_HANDLER.get()) return; + if (Config.getObtainSettings(ObtainType.FISHING).isEmpty()) return; + if (event.getState() != PlayerFishEvent.State.CAUGHT_FISH) return; + if (!(event.getCaught() instanceof Item item)) return; + + ItemStack itemStack = item.getItemStack(); + World world = item.getWorld(); + if (EnchantUtils.isEnchantable(itemStack)) { + this.plugin.createPopulator(itemStack, ObtainType.FISHING).withWorld(world).populate(); + } + } + + @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true) + public void onEnchantPopulateSpawn(CreatureSpawnEvent event) { + if (Config.ENCHANTMENTS_INTERNAL_HANDLER.get()) return; + + //if (Config.getObtainSettings(ObtainType.MOB_SPAWNING).isEmpty()) return; + LivingEntity entity = event.getEntity(); + if (entity.getType() == EntityType.ARMOR_STAND) return; + if (event.getSpawnReason() == CreatureSpawnEvent.SpawnReason.DISPENSE_EGG) return; + if (event.getSpawnReason() == CreatureSpawnEvent.SpawnReason.SPAWNER_EGG) return; + if (event.getSpawnReason() == CreatureSpawnEvent.SpawnReason.SPAWNER) return; + + this.plugin.runTaskLater(task -> { + EntityEquipment equipment = entity.getEquipment(); + if (equipment == null) return; + + World world = entity.getWorld(); + boolean isMythic = EngineUtils.hasPlugin(HookId.MYTHIC_MOBS) && MythicMobsHook.isMythicMob(entity); + boolean doPopulation = Config.getObtainSettings(ObtainType.MOB_SPAWNING).isPresent() && !isMythic; + + for (EquipmentSlot slot : EquipmentSlot.values()) { + ItemStack item = equipment.getItem(slot); + if (EnchantUtils.isEnchantable(item)) { + if (doPopulation) { + this.plugin.createPopulator(item, ObtainType.MOB_SPAWNING).withWorld(world).populate(); + } + EnchantUtils.getExcellents(item).forEach((enchant, level) -> EnchantUtils.restoreCharges(item, enchant, level)); + equipment.setItem(slot, item); + } + } + }, 40L); + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/menu/EnchantmentsListMenu.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/menu/EnchantmentsListMenu.java new file mode 100644 index 0000000..4eb38ae --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/menu/EnchantmentsListMenu.java @@ -0,0 +1,225 @@ +/* + * Decompiled with CFR 0.151. + * + * Could not load the following classes: + * org.bukkit.Material + * org.bukkit.NamespacedKey + * org.bukkit.entity.Player + * org.bukkit.event.inventory.InventoryType + * org.bukkit.inventory.ItemStack + * org.bukkit.plugin.Plugin + * org.jetbrains.annotations.NotNull + * su.nexmedia.engine.NexPlugin + * su.nexmedia.engine.api.config.JOption + * su.nexmedia.engine.api.config.JYML + * su.nexmedia.engine.api.menu.AutoPaged + * su.nexmedia.engine.api.menu.MenuItemType + * su.nexmedia.engine.api.menu.click.ClickHandler + * su.nexmedia.engine.api.menu.click.ItemClick + * su.nexmedia.engine.api.menu.impl.ConfigMenu + * su.nexmedia.engine.api.menu.impl.Menu + * su.nexmedia.engine.api.menu.impl.MenuOptions + * su.nexmedia.engine.api.menu.impl.MenuViewer + * su.nexmedia.engine.api.menu.item.MenuItem + * su.nexmedia.engine.api.placeholder.PlaceholderMap + * su.nexmedia.engine.utils.Colorizer + * su.nexmedia.engine.utils.ItemReplacer + * su.nexmedia.engine.utils.ItemUtil + * su.nexmedia.engine.utils.PDCUtil + */ +package su.nightexpress.excellentenchants.enchantment.menu; + +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.api.config.JOption; +import su.nexmedia.engine.api.config.JYML; +import su.nexmedia.engine.api.editor.EditorLocales; +import su.nexmedia.engine.api.menu.AutoPaged; +import su.nexmedia.engine.api.menu.MenuItemType; +import su.nexmedia.engine.api.menu.click.ClickHandler; +import su.nexmedia.engine.api.menu.click.ItemClick; +import su.nexmedia.engine.api.menu.impl.ConfigMenu; +import su.nexmedia.engine.api.menu.impl.MenuOptions; +import su.nexmedia.engine.api.menu.impl.MenuViewer; +import su.nexmedia.engine.api.menu.item.MenuItem; +import su.nexmedia.engine.api.placeholder.PlaceholderMap; +import su.nexmedia.engine.utils.Colorizer; +import su.nexmedia.engine.utils.ItemReplacer; +import su.nexmedia.engine.utils.ItemUtil; +import su.nexmedia.engine.utils.PDCUtil; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.api.enchantment.ObtainType; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.registry.EnchantRegistry; +import su.nightexpress.excellentenchants.enchantment.util.EnchantUtils; + +import java.util.*; +import java.util.function.Predicate; +import java.util.stream.IntStream; + +import static su.nexmedia.engine.utils.Colors2.*; +import static su.nightexpress.excellentenchants.Placeholders.*; + +public class EnchantmentsListMenu extends ConfigMenu implements AutoPaged { + + private static final String FILE = "enchants.yml"; + private static final String PLACEHOLDER_CONFLICTS = "%conflicts%"; + private static final String PLACEHOLDER_CHARGES = "%charges%"; + private static final String PLACEHOLDER_OBTAINING = "%obtaining%"; + + private final NamespacedKey keyLevel; + private final Map> iconCache; + + private ItemStack enchantIcon; + private String enchantName; + private List enchantLoreMain; + private List enchantLoreConflicts; + private List enchantLoreCharges; + private List enchantLoreObtaining; + private int[] enchantSlots; + + public EnchantmentsListMenu(@NotNull ExcellentEnchants plugin) { + super(plugin, new JYML(plugin.getDataFolder() + "/menu/", FILE)); + this.keyLevel = new NamespacedKey(plugin, "list_display_level"); + this.iconCache = new HashMap<>(); + this.registerHandler(MenuItemType.class).addClick(MenuItemType.CLOSE, ClickHandler.forClose(this)).addClick(MenuItemType.PAGE_NEXT, ClickHandler.forNextPage(this)).addClick(MenuItemType.PAGE_PREVIOUS, ClickHandler.forPreviousPage(this)); + this.load(); + } + + public void clear() { + super.clear(); + this.iconCache.clear(); + } + + public boolean isCodeCreation() { + return true; + } + + protected void loadAdditional() { + this.enchantIcon = JOption.create("Enchantment.Icon", new ItemStack(Material.ENCHANTED_BOOK)).read(this.cfg); + this.enchantName = JOption.create("Enchantment.Name", ENCHANTMENT_NAME_FORMATTED).read(this.cfg); + this.enchantLoreMain = JOption.create("Enchantment.Lore.Main", + Arrays.asList( + ENCHANTMENT_DESCRIPTION, + DARK_GRAY + "(click to switch level)", "", + LIGHT_YELLOW + BOLD + "Info:", + LIGHT_YELLOW + "▪ " + LIGHT_GRAY + "Tier: " + LIGHT_YELLOW + ENCHANTMENT_TIER, + LIGHT_YELLOW + "▪ " + LIGHT_GRAY + "Applies to: " + LIGHT_YELLOW + ENCHANTMENT_FIT_ITEM_TYPES, + LIGHT_YELLOW + "▪ " + LIGHT_GRAY + "Levels: " + LIGHT_YELLOW + ENCHANTMENT_LEVEL_MIN + LIGHT_GRAY + " - " + LIGHT_YELLOW + ENCHANTMENT_LEVEL_MAX, + PLACEHOLDER_CHARGES, PLACEHOLDER_CONFLICTS, PLACEHOLDER_OBTAINING + )).read(this.cfg); + + this.enchantLoreConflicts = JOption.create("Enchantment.Lore.Conflicts", + Arrays.asList( + "", LIGHT_RED + BOLD + "Conflicts:", + LIGHT_RED + "✘ " + LIGHT_GRAY + GENERIC_NAME + )).read(this.cfg); + + this.enchantLoreCharges = JOption.create("Enchantment.Lore.Charges", + List.of( + LIGHT_YELLOW + "▪ " + LIGHT_GRAY + "Charges: " + LIGHT_YELLOW + ENCHANTMENT_CHARGES_MAX_AMOUNT + "⚡" + LIGHT_GRAY + " (" + WHITE + ENCHANTMENT_CHARGES_FUEL_ITEM + LIGHT_GRAY + ")") + ).read(this.cfg); + + this.enchantLoreObtaining = JOption.create("Enchantment.Lore.Obtaining", + Arrays.asList("", LIGHT_GREEN + BOLD + "Obtaining:", LIGHT_GREEN + "✔ " + LIGHT_GRAY + GENERIC_TYPE)).read(this.cfg); + + this.enchantSlots = new JOption("Enchantment.Slots", (cfg, path, def) -> cfg.getIntArray(path), () -> IntStream.range(0, 27).toArray()).setWriter(JYML::setIntArray).read(this.cfg); + } + + @NotNull + protected MenuOptions createDefaultOptions() { + return new MenuOptions("#6c6c62&lCustom Enchants", 36, InventoryType.CHEST); + } + + @NotNull + protected List createDefaultItems() { + ArrayList list = new ArrayList<>(); + ItemStack nextPageStack = ItemUtil.createCustomHead("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZjMyY2E2NjA1NmI3Mjg2M2U5OGY3ZjMyYmQ3ZDk0YzdhMGQ3OTZhZjY5MWM5YWMzYTkxMzYzMzEzNTIyODhmOSJ9fX0="); + ItemUtil.mapMeta(nextPageStack, meta -> meta.setDisplayName(EditorLocales.NEXT_PAGE.getLocalizedName())); + ItemStack prevPageStack = ItemUtil.createCustomHead("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvODY5NzFkZDg4MWRiYWY0ZmQ2YmNhYTkzNjE0NDkzYzYxMmY4Njk2NDFlZDU5ZDFjOTM2M2EzNjY2YTVmYTYifX19"); + ItemUtil.mapMeta(prevPageStack, meta -> meta.setDisplayName(EditorLocales.PREVIOUS_PAGE.getLocalizedName())); + list.add(new MenuItem(nextPageStack).setSlots(new int[]{35}).setType(MenuItemType.PAGE_NEXT).setPriority(5)); + list.add(new MenuItem(prevPageStack).setSlots(new int[]{27}).setType(MenuItemType.PAGE_PREVIOUS).setPriority(5)); + return list; + } + + public void onPrepare(@NotNull MenuViewer viewer, @NotNull MenuOptions options) { + super.onPrepare(viewer, options); + this.getItemsForPage(viewer).forEach(this::addItem); + } + + public int[] getObjectSlots() { + return this.enchantSlots; + } + + @NotNull + public List getObjects(@NotNull Player player) { + return new ArrayList<>(EnchantRegistry.getRegistered().stream().filter(Predicate.not(enchant -> enchant.getDefaults().isHiddenFromList())).sorted(Comparator.comparing(e -> Colorizer.restrip(e.getDisplayName()))).toList()); + } + + @NotNull + public ItemStack getObjectStack(@NotNull Player player, @NotNull ExcellentEnchant enchant) { + return this.getEnchantIcon(enchant, 1); + } + + @NotNull + public ItemClick getObjectClick(@NotNull ExcellentEnchant enchant) { + return (viewer, event) -> { + if (!event.isLeftClick()) { + return; + } + ItemStack itemClick = event.getCurrentItem(); + if (itemClick == null) { + return; + } + int levelHas = PDCUtil.getInt(itemClick, this.keyLevel).orElse(0); + if (levelHas == 0) { + levelHas = enchant.getStartLevel(); + } + if (++levelHas > enchant.getMaxLevel()) { + levelHas = enchant.getStartLevel(); + } + itemClick = this.getEnchantIcon(enchant, levelHas); + PDCUtil.set(itemClick, this.keyLevel, levelHas); + event.setCurrentItem(itemClick); + }; + } + + private ItemStack getEnchantIcon(@NotNull ExcellentEnchant enchant, int level) { + return this.iconCache.computeIfAbsent(enchant.getId(), k -> new HashMap<>()).computeIfAbsent(level, k -> this.buildEnchantIcon(enchant, level)); + } + + @NotNull + private ItemStack buildEnchantIcon(@NotNull ExcellentEnchant enchant, int level) { + ItemStack icon = new ItemStack(this.enchantIcon); + List conflicts = new ArrayList<>(); + if (enchant.hasConflicts()) { + for (String line : this.enchantLoreConflicts) { + if (line.contains("%name%")) { + enchant.getConflicts().stream().map(EnchantUtils::getLocalized).filter(Objects::nonNull).forEach(conf -> conflicts.add(line.replace("%name%", conf))); + continue; + } + conflicts.add(line); + } + } + + List obtaining = new ArrayList<>(); + for (String line : this.enchantLoreObtaining) { + if (line.contains("%type%")) { + for (ObtainType obtainType : ObtainType.values()) { + if (!enchant.isObtainable(obtainType)) continue; + obtaining.add(line.replace("%type%", this.plugin.getLangManager().getEnum(obtainType))); + } + continue; + } + obtaining.add(line); + } + ItemReplacer.create(icon).hideFlags().trimmed().setDisplayName(this.enchantName).setLore(this.enchantLoreMain).replaceLoreExact(PLACEHOLDER_CHARGES, enchant.isChargesEnabled() ? new ArrayList<>(this.enchantLoreCharges) : Collections.emptyList()).replaceLoreExact(PLACEHOLDER_CONFLICTS, conflicts).replaceLoreExact(PLACEHOLDER_OBTAINING, obtaining).replaceLoreExact("%enchantment_description%", enchant.formatDescription()).replace(new PlaceholderMap[]{enchant.getPlaceholders(level)}).replace(Colorizer::apply).writeMeta(); + return icon; + } +} + diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/registry/EnchantRegistry.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/registry/EnchantRegistry.java new file mode 100644 index 0000000..c4dc05c --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/registry/EnchantRegistry.java @@ -0,0 +1,285 @@ +package su.nightexpress.excellentenchants.enchantment.registry; + +import org.bukkit.NamespacedKey; +import org.bukkit.event.Event; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockDropItemEvent; +import org.bukkit.event.entity.*; +import org.bukkit.event.player.PlayerFishEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import su.nexmedia.engine.Version; +import su.nexmedia.engine.api.manager.AbstractManager; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.api.enchantment.IEnchantment; +import su.nightexpress.excellentenchants.api.enchantment.type.*; +import su.nightexpress.excellentenchants.config.Config; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.armor.*; +import su.nightexpress.excellentenchants.enchantment.impl.bow.*; +import su.nightexpress.excellentenchants.enchantment.impl.fishing.*; +import su.nightexpress.excellentenchants.enchantment.impl.tool.*; +import su.nightexpress.excellentenchants.enchantment.impl.universal.CurseOfFragilityEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.universal.RestoreEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.universal.SoulboundEnchant; +import su.nightexpress.excellentenchants.enchantment.impl.weapon.*; +import su.nightexpress.excellentenchants.enchantment.registry.wrapper.DataGather; +import su.nightexpress.excellentenchants.enchantment.registry.wrapper.DataGathers; +import su.nightexpress.excellentenchants.enchantment.registry.wrapper.WrappedEvent; +import su.nightexpress.excellentenchants.enchantment.util.EnchantUtils; +import su.nightexpress.excellentenchants.tier.Tier; + +import java.util.*; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +public class EnchantRegistry extends AbstractManager { + + public static final Map REGISTRY_MAP = new HashMap<>(); + private static final Map, Set> ENCHANTS_MAP = new HashMap<>(); + + private boolean isLocked; + + public EnchantRegistry(@NotNull ExcellentEnchants plugin) { + super(plugin); + } + + @Override + protected void onLoad() { + this.registerType(GenericEnchant.class); + this.registerType(PassiveEnchant.class); + + this.registerWrapper(BlockBreakEvent.class, BlockBreakEnchant.class, DataGathers.BLOCK_BREAK); + this.registerWrapper(BlockDropItemEvent.class, BlockDropEnchant.class, DataGathers.BLOCK_DROP); + this.registerWrapper(EntityShootBowEvent.class, BowEnchant.class, DataGathers.BOW_SHOOT); + this.registerWrapper(ProjectileHitEvent.class, BowEnchant.class, DataGathers.PROJECTILE_HIT); + this.registerWrapper(EntityDamageByEntityEvent.class, BowEnchant.class, DataGathers.ENTITY_DAMAGE_SHOOT); + this.registerWrapper(EntityDamageByEntityEvent.class, CombatEnchant.class, DataGathers.ENTITY_DAMAGE_ATTACK); + this.registerWrapper(EntityDamageByEntityEvent.class, CombatEnchant.class, DataGathers.ENTITY_DAMAGE_DEFENSE); + this.registerWrapper(EntityDeathEvent.class, DeathEnchant.class, DataGathers.ENTITY_KILL); + this.registerWrapper(EntityDeathEvent.class, DeathEnchant.class, DataGathers.ENTITY_DEATH); + this.registerWrapper(EntityResurrectEvent.class, DeathEnchant.class, DataGathers.ENTITY_RESURRECT); + this.registerWrapper(PlayerFishEvent.class, FishingEnchant.class, DataGathers.FISHING); + this.registerWrapper(PlayerInteractEvent.class, InteractEnchant.class, DataGathers.INTERACT); + + // Prevent to register enchantments during the runtime. + if (this.isLocked) { + REGISTRY_MAP.values().forEach(enchant -> { + enchant.loadSettings(); + enchant.registerListeners(); + }); + return; + } + + //if (Version.isAtLeast(Version.V1_20_R3)) { + this.plugin.getEnchantNMS().unfreezeRegistry(); + //} + //else { + // Reflex.setFieldValue(Enchantment.class, "acceptingNew", true); + //} + + // Fishing Enchants + this.register(AutoReelEnchant.ID,() -> new AutoReelEnchant(plugin)); + this.register(DoubleCatchEnchant.ID, () -> new DoubleCatchEnchant(plugin)); + this.register(SeasonedAnglerEnchant.ID, () -> new SeasonedAnglerEnchant(plugin)); + this.register(SurvivalistEnchant.ID, () -> new SurvivalistEnchant(plugin)); + this.register(CurseOfDrownedEnchant.ID, () -> new CurseOfDrownedEnchant(plugin)); + this.register(RiverMasterEnchant.ID, () -> new RiverMasterEnchant(plugin)); + + // Tool enchants + this.register(BlastMiningEnchant.ID, () -> new BlastMiningEnchant(plugin)); + this.register(CurseOfBreakingEnchant.ID, () -> new CurseOfBreakingEnchant(plugin)); + this.register(CurseOfMisfortuneEnchant.ID, () -> new CurseOfMisfortuneEnchant(plugin)); + this.register(DivineTouchEnchant.ID, () -> new DivineTouchEnchant(plugin)); + this.register(HasteEnchant.ID, () -> new HasteEnchant(plugin)); + this.register(LuckyMinerEnchant.ID, () -> new LuckyMinerEnchant(plugin)); + this.register(ReplanterEnchant.ID, () -> new ReplanterEnchant(plugin)); + this.register(SilkChestEnchant.ID, () -> new SilkChestEnchant(plugin)); + this.register(SmelterEnchant.ID, () -> new SmelterEnchant(plugin)); + this.register(TelekinesisEnchant.ID, () -> new TelekinesisEnchant(plugin)); + this.register(TreasuresEnchant.ID, () -> new TreasuresEnchant(plugin)); + this.register(TunnelEnchant.ID, () -> new TunnelEnchant(plugin)); + this.register(VeinminerEnchant.ID, () -> new VeinminerEnchant(plugin)); + + // Weapon enchants + this.register(EnchantBaneOfNetherspawn.ID, () -> new EnchantBaneOfNetherspawn(plugin)); + this.register(EnchantBlindness.ID, () -> new EnchantBlindness(plugin)); + this.register(EnchantConfusion.ID, () -> new EnchantConfusion(plugin)); + this.register(EnchantCutter.ID, () -> new EnchantCutter(plugin)); + this.register(CurseOfDeathEnchant.ID, () -> new CurseOfDeathEnchant(plugin)); + this.register(EnchantDecapitator.ID, () -> new EnchantDecapitator(plugin)); + this.register(EnchantDoubleStrike.ID, () -> new EnchantDoubleStrike(plugin)); + this.register(EnchantExhaust.ID, () -> new EnchantExhaust(plugin)); + this.register(EnchantExpHunter.ID, () -> new EnchantExpHunter(plugin)); + this.register(EnchantIceAspect.ID, () -> new EnchantIceAspect(plugin)); + this.register(EnchantInfernus.ID, () -> new EnchantInfernus(plugin)); + this.register(EnchantNimble.ID, () -> new EnchantNimble(plugin)); + this.register(EnchantParalyze.ID, () -> new EnchantParalyze(plugin)); + this.register(EnchantCure.ID, () -> new EnchantCure(plugin)); + this.register(EnchantRage.ID, () -> new EnchantRage(plugin)); + this.register(EnchantRocket.ID, () -> new EnchantRocket(plugin)); + this.register(EnchantScavenger.ID, () -> new EnchantScavenger(plugin)); + this.register(EnchantSurprise.ID, () -> new EnchantSurprise(plugin)); + this.register(SwiperEnchant.ID, () -> new SwiperEnchant(plugin)); + this.register(EnchantTemper.ID, () -> new EnchantTemper(plugin)); + this.register(EnchantThrifty.ID, () -> new EnchantThrifty(plugin)); + this.register(EnchantThunder.ID, () -> new EnchantThunder(plugin)); + this.register(EnchantVampire.ID, () -> new EnchantVampire(plugin)); + this.register(EnchantVenom.ID, () -> new EnchantVenom(plugin)); + this.register(EnchantVillageDefender.ID, () -> new EnchantVillageDefender(plugin)); + this.register(EnchantWither.ID, () -> new EnchantWither(plugin)); + + // Armor enchants + this.register(AquamanEnchant.ID, () -> new AquamanEnchant(plugin)); + this.register(JumpingEnchant.ID, () -> new JumpingEnchant(plugin)); + this.register(ColdSteelEnchant.ID, () -> new ColdSteelEnchant(plugin)); + this.register(IceShieldEnchant.ID, () -> new IceShieldEnchant(plugin)); + this.register(ElementalProtectionEnchant.ID, () -> new ElementalProtectionEnchant(plugin)); + this.register(FireShieldEnchant.ID, () -> new FireShieldEnchant(plugin)); + this.register(FlameWalkerEnchant.ID, () -> new FlameWalkerEnchant(plugin)); + this.register(HardenedEnchant.ID, () -> new HardenedEnchant(plugin)); + this.register(NightVisionEnchant.ID, () -> new NightVisionEnchant(plugin)); + this.register(RegrowthEnchant.ID, () -> new RegrowthEnchant(plugin)); + this.register(SaturationEnchant.ID, () -> new SaturationEnchant(plugin)); + this.register(KamikadzeEnchant.ID, () -> new KamikadzeEnchant(plugin)); + this.register(StoppingForceEnchant.ID, () -> new StoppingForceEnchant(plugin)); + this.register(SpeedyEnchant.ID, () -> new SpeedyEnchant(plugin)); + + // Bow enchants + this.register(EnchantBomber.ID, () -> new EnchantBomber(plugin)); + this.register(EnchantConfusingArrows.ID, () -> new EnchantConfusingArrows(plugin)); + this.register(EnchantDragonfireArrows.ID, () -> new EnchantDragonfireArrows(plugin)); + this.register(EnchantElectrifiedArrows.ID, () -> new EnchantElectrifiedArrows(plugin)); + this.register(EnchantEnderBow.ID, () -> new EnchantEnderBow(plugin)); + this.register(EnchantExplosiveArrows.ID, () -> new EnchantExplosiveArrows(plugin)); + this.register(FlareEnchant.ID, () -> new FlareEnchant(plugin)); + this.register(EnchantGhast.ID, () -> new EnchantGhast(plugin)); + this.register(EnchantHover.ID, () -> new EnchantHover(plugin)); + this.register(SniperEnchant.ID, () -> new SniperEnchant(plugin)); + this.register(EnchantPoisonedArrows.ID, () -> new EnchantPoisonedArrows(plugin)); + this.register(VampiricArrowsEnchant.ID, () -> new VampiricArrowsEnchant(plugin)); + this.register(EnchantWitheredArrows.ID, () -> new EnchantWitheredArrows(plugin)); + if (Version.isAbove(Version.V1_18_R2)) { + this.register(DarknessArrowsEnchant.ID, () -> new DarknessArrowsEnchant(plugin)); + this.register(DarknessCloakEnchant.ID, () -> new DarknessCloakEnchant(plugin)); + } + + // Universal + this.register(CurseOfFragilityEnchant.ID, () -> new CurseOfFragilityEnchant(plugin)); + this.register(CurseOfMediocrityEnchant.ID, () -> new CurseOfMediocrityEnchant(plugin)); + this.register(SoulboundEnchant.ID, () -> new SoulboundEnchant(plugin)); + this.register(RestoreEnchant.ID, () -> new RestoreEnchant(plugin)); + + //if (Version.isAtLeast(Version.V1_20_R3)) { + this.plugin.getEnchantNMS().freezeRegistry(); + //} + //else { + // Enchantment.stopAcceptingRegistrations(); + //} + this.plugin.info("Enchantments Registered: " + EnchantRegistry.getRegistered().size()); + this.isLocked = true; + } + + @Override + protected void onShutdown() { + if (!isLocked) { + ENCHANTS_MAP.clear(); + } + } + + public void registerType(@NotNull Class enchantClass) { + ENCHANTS_MAP.computeIfAbsent(enchantClass, k -> new HashSet<>()); + } + + public void registerWrapper(@NotNull Class eventClass, + @NotNull Class enchantClass, + @NotNull DataGather dataGather) { + + for (EventPriority priority : EventPriority.values()) { + WrappedEvent event = new WrappedEvent<>(plugin, priority, eventClass, enchantClass, dataGather); + plugin.getPluginManager().registerEvent(eventClass, event, priority, event, plugin, true); + } + + this.registerType(enchantClass); + } + + private boolean registerEnchantType(@NotNull T enchant) { + Class enchantClass = enchant.getClass(); + + Set> assignables = ENCHANTS_MAP.keySet().stream().filter(clazz -> clazz.isAssignableFrom(enchantClass)).collect(Collectors.toSet()); + if (assignables.isEmpty()) { + this.plugin.warn("Could not register enchantment '" + enchant.getId() + "': Enchantment type is not registered."); + return false; + } + + assignables.forEach(clazz -> ENCHANTS_MAP.get(clazz).add(enchant)); + return true; + } + + private void register(@NotNull String id, @NotNull Supplier supplier) { + if (Config.ENCHANTMENTS_DISABLED.get().contains(id)) return; + + + ExcellentEnchant enchant = supplier.get(); + if (EnchantUtils.getEnchantment(enchant.getKey()) /*Enchantment.getByKey(enchant.getKey())*/ != null) { + this.plugin.error("Could not register '" + enchant.getId() + "': Such enchantment already registered."); + return; + } + + if (!this.registerEnchantType(enchant)) { + return; + } + + //if (Version.isAtLeast(Version.V1_20_R3)) { + this.plugin.getEnchantNMS().registerEnchantment(enchant); + //} + //else { + //Enchantment.registerEnchantment(enchant); + //} + + REGISTRY_MAP.put(enchant.getKey(), enchant); + enchant.loadSettings(); + enchant.getConfig().saveChanges(); + enchant.registerListeners(); + this.plugin.info("Registered enchantment: " + enchant.getId()); + } + + @NotNull + public static Set getPeriodicEnchants() { + return getEnchantments(PassiveEnchant.class); + } + + @NotNull + public static Set getEnchantments(@NotNull Class clazz) { + Set enchants = new HashSet<>(); + + ENCHANTS_MAP.getOrDefault(clazz, Collections.emptySet()).forEach(talent -> { + enchants.add(clazz.cast(talent)); + }); + return enchants; + //Set set = new HashSet<>(ENCHANTS_MAP.getOrDefault(clazz, Collections.emptySet())); + //return (Set) set; + } + + @Nullable + public static ExcellentEnchant getById(@NotNull String id) { + return getByKey(EnchantUtils.createKey(id)); + } + + @Nullable + public static ExcellentEnchant getByKey(@NotNull NamespacedKey key) { + return REGISTRY_MAP.get(key); + } + + @NotNull + public static Collection getRegistered() { + return REGISTRY_MAP.values(); + } + + @NotNull + public static Set getOfTier(@NotNull Tier tier) { + return getRegistered().stream().filter(enchant -> enchant.getTier() == tier).collect(Collectors.toCollection(HashSet::new)); + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/registry/wrapper/DataGather.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/registry/wrapper/DataGather.java new file mode 100644 index 0000000..92c9679 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/registry/wrapper/DataGather.java @@ -0,0 +1,31 @@ +package su.nightexpress.excellentenchants.enchantment.registry.wrapper; + +import org.bukkit.entity.LivingEntity; +import org.bukkit.event.Event; +import org.bukkit.event.EventPriority; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import su.nightexpress.excellentenchants.api.enchantment.IEnchantment; +import su.nightexpress.excellentenchants.enchantment.util.EnchantUtils; + +import java.util.Map; + +public abstract class DataGather { + + @Nullable + public abstract LivingEntity getEntity(@NotNull E event); + + @NotNull + public abstract EquipmentSlot[] getEnchantSlots(@NotNull E event); + + public abstract boolean checkPriority(@NotNull T enchant, @NotNull EventPriority priority); + + @NotNull + public Map> getEnchants(@NotNull E event, @NotNull Class enchantClass, @NotNull LivingEntity entity) { + return EnchantUtils.getEquipped(entity, enchantClass, this.getEnchantSlots(event)); + } + + public abstract boolean useEnchant(@NotNull E event, @NotNull LivingEntity entity, @NotNull ItemStack item, @NotNull T enchant, int level); +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/registry/wrapper/DataGathers.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/registry/wrapper/DataGathers.java new file mode 100644 index 0000000..55658c9 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/registry/wrapper/DataGathers.java @@ -0,0 +1,378 @@ +package su.nightexpress.excellentenchants.enchantment.registry.wrapper; + +import org.bukkit.entity.Entity; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.event.EventPriority; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.event.block.BlockDropItemEvent; +import org.bukkit.event.entity.*; +import org.bukkit.event.player.PlayerFishEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import su.nexmedia.engine.Version; +import su.nightexpress.excellentenchants.api.enchantment.meta.Arrowed; +import su.nightexpress.excellentenchants.api.enchantment.type.*; +import su.nightexpress.excellentenchants.enchantment.util.EnchantUtils; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public class DataGathers { + + public static final DataGather BLOCK_BREAK = new DataGather<>() { + + @Override + @NotNull + public LivingEntity getEntity(@NotNull BlockBreakEvent event) { + return event.getPlayer(); + } + + @Override + public boolean checkPriority(@NotNull BlockBreakEnchant enchant, @NotNull EventPriority priority) { + return enchant.getBreakPriority() == priority; + } + + @NotNull + @Override + public EquipmentSlot[] getEnchantSlots(@NotNull BlockBreakEvent event) { + return new EquipmentSlot[]{EquipmentSlot.HAND}; + } + + @Override + public boolean useEnchant(@NotNull BlockBreakEvent event, @NotNull LivingEntity entity, @NotNull ItemStack item, @NotNull BlockBreakEnchant enchant, int level) { + return enchant.onBreak(event, entity, item, level); + } + }; + + public static final DataGather BLOCK_DROP = new DataGather<>() { + + @Override + @NotNull + public LivingEntity getEntity(@NotNull BlockDropItemEvent event) { + return event.getPlayer(); + } + + @Override + public boolean checkPriority(@NotNull BlockDropEnchant enchant, @NotNull EventPriority priority) { + return enchant.getDropPriority() == priority; + } + + @NotNull + @Override + public EquipmentSlot[] getEnchantSlots(@NotNull BlockDropItemEvent event) { + return new EquipmentSlot[]{EquipmentSlot.HAND}; + } + + @Override + public boolean useEnchant(@NotNull BlockDropItemEvent event, @NotNull LivingEntity entity, @NotNull ItemStack item, @NotNull BlockDropEnchant enchant, int level) { + return enchant.onDrop(event, entity, item, level); + } + }; + + public static final DataGather BOW_SHOOT = new DataGather<>() { + + @Override + @NotNull + public LivingEntity getEntity(@NotNull EntityShootBowEvent event) { + return event.getEntity(); + } + + @Override + public boolean checkPriority(@NotNull BowEnchant enchant, @NotNull EventPriority priority) { + return enchant.getShootPriority() == priority; + } + + @NotNull + @Override + public EquipmentSlot[] getEnchantSlots(@NotNull EntityShootBowEvent event) { + return new EquipmentSlot[]{event.getHand()}; + } + + @Override + public boolean useEnchant(@NotNull EntityShootBowEvent event, @NotNull LivingEntity entity, @NotNull ItemStack item, @NotNull BowEnchant enchant, int level) { + if (enchant.onShoot(event, entity, item, level)) { + if (enchant instanceof Arrowed arrowed && event.getProjectile() instanceof Projectile projectile) { + arrowed.addData(projectile); + arrowed.addTrail(projectile); + } + return true; + } + return false; + } + }; + + public static final DataGather PROJECTILE_HIT = new DataGather<>() { + + @Override + @Nullable + public LivingEntity getEntity(@NotNull ProjectileHitEvent event) { + return event.getEntity().getShooter() instanceof LivingEntity entity ? entity : null; + } + + @Override + public boolean checkPriority(@NotNull BowEnchant enchant, @NotNull EventPriority priority) { + return enchant.getHitPriority() == priority; + } + + @NotNull + @Override + public EquipmentSlot[] getEnchantSlots(@NotNull ProjectileHitEvent event) { + return new EquipmentSlot[0]; + } + + @NotNull + @Override + public Map> getEnchants(@NotNull ProjectileHitEvent event, @NotNull Class enchantClass, @NotNull LivingEntity entity) { + Map> map = new HashMap<>(); + ItemStack bow = EnchantUtils.getSourceWeapon(event.getEntity()); + if (bow != null) { + map.put(bow, EnchantUtils.getExcellents(bow, enchantClass)); + } + return map; + } + + @Override + public boolean useEnchant(@NotNull ProjectileHitEvent event, @NotNull LivingEntity entity, @NotNull ItemStack item, @NotNull BowEnchant enchant, int level) { + return enchant.onHit(event, entity, event.getEntity(), item, level); + } + }; + + public static final DataGather ENTITY_DAMAGE_SHOOT = new DataGather<>() { + + @Override + @Nullable + public LivingEntity getEntity(@NotNull EntityDamageByEntityEvent event) { + if (event.getDamager() instanceof Projectile projectile && projectile.getShooter() instanceof LivingEntity shooter) { + return shooter; + } + return null; + } + + @Override + public boolean checkPriority(@NotNull BowEnchant enchant, @NotNull EventPriority priority) { + return enchant.getDamagePriority() == priority; + } + + @NotNull + @Override + public EquipmentSlot[] getEnchantSlots(@NotNull EntityDamageByEntityEvent event) { + return new EquipmentSlot[0]; + } + + @NotNull + @Override + public Map> getEnchants(@NotNull EntityDamageByEntityEvent event, @NotNull Class enchantClass, @NotNull LivingEntity entity) { + if (!(event.getDamager() instanceof Projectile projectile)) return Collections.emptyMap(); + + Map> map = new HashMap<>(); + ItemStack bow = EnchantUtils.getSourceWeapon(projectile); + if (bow != null) { + map.put(bow, EnchantUtils.getExcellents(bow, enchantClass)); + } + return map; + } + + @Override + public boolean useEnchant(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull ItemStack item, @NotNull BowEnchant enchant, int level) { + if (!(event.getDamager() instanceof Projectile projectile)) return false; + if (!(event.getEntity() instanceof LivingEntity victim)) return false; + + return enchant.onDamage(event, projectile, damager, victim, item, level); + } + }; + + public static final DataGather ENTITY_DAMAGE_ATTACK = new DataGather<>() { + + @Override + @Nullable + public LivingEntity getEntity(@NotNull EntityDamageByEntityEvent event) { + if (event.getDamager() instanceof LivingEntity entity) { + return entity; + } + return null; + } + + @Override + public boolean checkPriority(@NotNull CombatEnchant enchant, @NotNull EventPriority priority) { + return enchant.getAttackPriority() == priority; + } + + @NotNull + @Override + public EquipmentSlot[] getEnchantSlots(@NotNull EntityDamageByEntityEvent event) { + return new EquipmentSlot[]{EquipmentSlot.HAND}; + } + + @Override + public boolean useEnchant(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity damager, @NotNull ItemStack item, @NotNull CombatEnchant enchant, int level) { + if (event.getCause() == EntityDamageEvent.DamageCause.THORNS) return false; + if (!(event.getEntity() instanceof LivingEntity victim)) return false; + + return enchant.onAttack(event, damager, victim, item, level); + } + }; + + public static final DataGather ENTITY_DAMAGE_DEFENSE = new DataGather<>() { + + @Override + @Nullable + public LivingEntity getEntity(@NotNull EntityDamageByEntityEvent event) { + Entity entity = event.getEntity(); + return entity instanceof Player player ? player : null; + } + + @Override + public boolean checkPriority(@NotNull CombatEnchant enchant, @NotNull EventPriority priority) { + return enchant.getProtectPriority() == priority; + } + + @NotNull + @Override + public EquipmentSlot[] getEnchantSlots(@NotNull EntityDamageByEntityEvent event) { + return new EquipmentSlot[] {EquipmentSlot.HEAD, EquipmentSlot.CHEST, EquipmentSlot.LEGS, EquipmentSlot.FEET}; + } + + @Override + public boolean useEnchant(@NotNull EntityDamageByEntityEvent event, @NotNull LivingEntity victim, @NotNull ItemStack item, @NotNull CombatEnchant enchant, int level) { + if (event.getCause() == EntityDamageEvent.DamageCause.THORNS) return false; + if (!(event.getDamager() instanceof LivingEntity damager)) return false; + + return enchant.onProtect(event, damager, victim, item, level); + } + }; + + public static final DataGather ENTITY_KILL = new DataGather<>() { + + @Override + @Nullable + public LivingEntity getEntity(@NotNull EntityDeathEvent event) { + return event.getEntity().getKiller(); + } + + @Override + public boolean checkPriority(@NotNull DeathEnchant enchant, @NotNull EventPriority priority) { + return enchant.getKillPriority() == priority; + } + + @NotNull + @Override + public EquipmentSlot[] getEnchantSlots(@NotNull EntityDeathEvent event) { + return new EquipmentSlot[] {EquipmentSlot.HAND}; + } + + @Override + public boolean useEnchant(@NotNull EntityDeathEvent event, @NotNull LivingEntity entity, @NotNull ItemStack item, @NotNull DeathEnchant enchant, int level) { + if (!(entity instanceof Player killer)) return false; + + return enchant.onKill(event, event.getEntity(), killer, item, level); + } + }; + + public static final DataGather ENTITY_DEATH = new DataGather<>() { + + @Override + @NotNull + public LivingEntity getEntity(@NotNull EntityDeathEvent event) { + return event.getEntity(); + } + + @Override + public boolean checkPriority(@NotNull DeathEnchant enchant, @NotNull EventPriority priority) { + return enchant.getDeathPriority() == priority; + } + + @NotNull + @Override + public EquipmentSlot[] getEnchantSlots(@NotNull EntityDeathEvent event) { + return EquipmentSlot.values(); + } + + @Override + public boolean useEnchant(@NotNull EntityDeathEvent event, @NotNull LivingEntity entity, @NotNull ItemStack item, @NotNull DeathEnchant enchant, int level) { + return enchant.onDeath(event, entity, item, level); + } + }; + + public static final DataGather ENTITY_RESURRECT = new DataGather<>() { + + @Override + @NotNull + public LivingEntity getEntity(@NotNull EntityResurrectEvent event) { + return event.getEntity(); + } + + @Override + public boolean checkPriority(@NotNull DeathEnchant enchant, @NotNull EventPriority priority) { + return enchant.getDeathPriority() == priority; + } + + @NotNull + @Override + public EquipmentSlot[] getEnchantSlots(@NotNull EntityResurrectEvent event) { + return EquipmentSlot.values(); + } + + @Override + public boolean useEnchant(@NotNull EntityResurrectEvent event, @NotNull LivingEntity entity, @NotNull ItemStack item, @NotNull DeathEnchant enchant, int level) { + return enchant.onResurrect(event, entity, item, level); + } + }; + + public static final DataGather FISHING = new DataGather<>() { + + @Override + @NotNull + public LivingEntity getEntity(@NotNull PlayerFishEvent event) { + return event.getPlayer(); + } + + @Override + public boolean checkPriority(@NotNull FishingEnchant enchant, @NotNull EventPriority priority) { + return enchant.getFishingPriority() == priority; + } + + @NotNull + @Override + public EquipmentSlot[] getEnchantSlots(@NotNull PlayerFishEvent event) { + if (Version.isBehind(Version.V1_19_R3)) return new EquipmentSlot[] {EquipmentSlot.HAND}; + + return event.getHand() == null ? new EquipmentSlot[] {EquipmentSlot.HAND} : new EquipmentSlot[]{event.getHand()}; + } + + @Override + public boolean useEnchant(@NotNull PlayerFishEvent event, @NotNull LivingEntity entity, @NotNull ItemStack item, @NotNull FishingEnchant enchant, int level) { + return enchant.onFishing(event, item, level); + } + }; + + public static final DataGather INTERACT = new DataGather<>() { + + @NotNull + @Override + public LivingEntity getEntity(@NotNull PlayerInteractEvent event) { + return event.getPlayer(); + } + + @Override + public boolean checkPriority(@NotNull InteractEnchant enchant, @NotNull EventPriority priority) { + return enchant.getInteractPriority() == priority; + } + + @NotNull + @Override + public EquipmentSlot[] getEnchantSlots(@NotNull PlayerInteractEvent event) { + return event.getHand() == null ? new EquipmentSlot[] {EquipmentSlot.HAND} : new EquipmentSlot[]{event.getHand()}; + } + + @Override + public boolean useEnchant(@NotNull PlayerInteractEvent event, @NotNull LivingEntity entity, @NotNull ItemStack item, @NotNull InteractEnchant enchant, int level) { + return enchant.onInteract(event, entity, item, level); + } + }; +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/registry/wrapper/WrappedEvent.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/registry/wrapper/WrappedEvent.java new file mode 100644 index 0000000..c210447 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/registry/wrapper/WrappedEvent.java @@ -0,0 +1,59 @@ +package su.nightexpress.excellentenchants.enchantment.registry.wrapper; + +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.plugin.EventExecutor; +import org.jetbrains.annotations.NotNull; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.api.enchantment.IEnchantment; +import su.nightexpress.excellentenchants.config.Config; +import su.nightexpress.excellentenchants.enchantment.util.EnchantUtils; + +public class WrappedEvent implements Listener, EventExecutor { + + //private final ExcellentEnchants plugin; + private final EventPriority priority; + private final Class eventClass; + private final Class enchantClass; + private final DataGather dataGather; + + public WrappedEvent(@NotNull ExcellentEnchants plugin, + @NotNull EventPriority priority, + @NotNull Class eventClass, + @NotNull Class enchantClass, + @NotNull DataGather dataGather) { + //this.plugin = plugin; + this.priority = priority; + this.eventClass = eventClass; + this.enchantClass = enchantClass; + this.dataGather = dataGather; + } + + @Override + public void execute(@NotNull Listener listener, @NotNull Event bukkitEvent) { + if (!this.eventClass.isAssignableFrom(bukkitEvent.getClass())) return; + + E event = this.eventClass.cast(bukkitEvent); + LivingEntity entity = this.dataGather.getEntity(event); + if (entity == null) return; + + Player player = entity instanceof Player p1 ? p1 : null; + + this.dataGather.getEnchants(event, this.enchantClass, entity).forEach((item, enchants) -> { + enchants.forEach(((enchant, level) -> { + if (!this.dataGather.checkPriority(enchant, this.priority)) return; + if (!enchant.isAvailableToUse(entity)) return; + if (enchant.isOutOfCharges(item)) return; + if (this.dataGather.useEnchant(event, entity, item, enchant, level)) { + enchant.consumeChargesNoUpdate(item, level); + } + })); + if (this.priority == EventPriority.MONITOR && Config.ENCHANTMENTS_CHARGES_ENABLED.get() && player != null) { + EnchantUtils.updateDisplay(item); + } + }); + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/task/ArrowTrailsTask.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/task/ArrowTrailsTask.java new file mode 100644 index 0000000..e5b8705 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/task/ArrowTrailsTask.java @@ -0,0 +1,38 @@ +package su.nightexpress.excellentenchants.enchantment.task; + +import org.bukkit.entity.Projectile; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.api.server.AbstractTask; +import su.nexmedia.engine.utils.values.UniParticle; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.config.Config; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +public class ArrowTrailsTask extends AbstractTask { + + private static final Map> TRAILS_MAP = new ConcurrentHashMap<>(); + + public ArrowTrailsTask(@NotNull ExcellentEnchants plugin) { + super(plugin, Config.TASKS_ARROW_TRAIL_TICKS_INTERVAL.get(), true); + TRAILS_MAP.clear(); + } + + @Override + public void action() { + TRAILS_MAP.keySet().removeIf(projectile -> !projectile.isValid() || projectile.isDead()); + + TRAILS_MAP.forEach((arrow, effects) -> { + effects.forEach(entry -> { + entry.play(arrow.getLocation(), 0f, 0f, 10); + }); + }); + } + + public static void add(@NotNull Projectile projectile, @NotNull UniParticle particle) { + TRAILS_MAP.computeIfAbsent(projectile, list -> new HashSet<>()).add(particle); + } +} \ No newline at end of file diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/task/PassiveEnchantsTask.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/task/PassiveEnchantsTask.java new file mode 100644 index 0000000..4ef6467 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/task/PassiveEnchantsTask.java @@ -0,0 +1,71 @@ +package su.nightexpress.excellentenchants.enchantment.task; + +import org.bukkit.entity.LivingEntity; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.api.server.AbstractTask; +import su.nexmedia.engine.utils.Pair; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.api.enchantment.type.PassiveEnchant; +import su.nightexpress.excellentenchants.config.Config; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.registry.EnchantRegistry; +import su.nightexpress.excellentenchants.enchantment.util.EnchantUtils; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +public class PassiveEnchantsTask extends AbstractTask { + + private final Set> enchants; + + public PassiveEnchantsTask(@NotNull ExcellentEnchants plugin) { + super(plugin, Config.TASKS_PASSIVE_ENCHANTS_TRIGGER_INTERVAL.get(), false); + this.enchants = new HashSet<>(); + + EnchantRegistry.getEnchantments(PassiveEnchant.class).forEach(enchant -> { + ExcellentEnchant excellent = EnchantRegistry.getByKey(enchant.getKey()); + if (excellent == null) return; + + this.enchants.add(Pair.of(enchant, excellent)); + }); + } + + @Override + public void action() { + if (this.enchants.isEmpty()) return; + + var entities = this.getEntities(); + + this.enchants.forEach(pair -> { + PassiveEnchant enchant = pair.getFirst(); + ExcellentEnchant excellent = pair.getSecond(); + if (!enchant.isTriggerTime()) return; + + for (LivingEntity entity : entities) { + EnchantUtils.getEquipped(entity, excellent).forEach((item, level) -> { + if (!enchant.isAvailableToUse(entity)) return; + if (enchant.isOutOfCharges(item)) return; + if (enchant.onTrigger(entity, item, level)) { + enchant.consumeCharges(item, level); + } + }); + } + + enchant.updateTriggerTime(); + }); + } + + @NotNull + private Collection getEntities() { + Set list = new HashSet<>(plugin.getServer().getOnlinePlayers()); + + if (Config.ENCHANTMENTS_ENTITY_PASSIVE_FOR_MOBS.get()) { + plugin.getServer().getWorlds().stream().filter(world -> !world.getPlayers().isEmpty()).forEach(world -> { + list.addAll(world.getEntitiesByClass(LivingEntity.class)); + }); + } + list.removeIf(entity -> entity.isDead() || !entity.isValid()); + return list; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/util/EnchantUtils.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/util/EnchantUtils.java new file mode 100644 index 0000000..9f18b3e --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/util/EnchantUtils.java @@ -0,0 +1,415 @@ +package su.nightexpress.excellentenchants.enchantment.util; + +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.Registry; +import org.bukkit.block.Block; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.enchantments.EnchantmentTarget; +import org.bukkit.entity.Item; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; +import org.bukkit.event.block.BlockDropItemEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.EnchantmentStorageMeta; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.metadata.FixedMetadataValue; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import su.nexmedia.engine.Version; +import su.nexmedia.engine.lang.LangManager; +import su.nexmedia.engine.utils.EntityUtil; +import su.nexmedia.engine.utils.ItemUtil; +import su.nexmedia.engine.utils.PDCUtil; +import su.nightexpress.excellentenchants.ExcellentEnchantsAPI; +import su.nightexpress.excellentenchants.api.enchantment.IEnchantment; +import su.nightexpress.excellentenchants.config.Config; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.registry.EnchantRegistry; + +import java.util.*; +import java.util.stream.Stream; + +public class EnchantUtils { + + public static final NamespacedKey KEY_LORE_SIZE = new NamespacedKey(ExcellentEnchantsAPI.PLUGIN, "lore_size"); + private static final String META_PROJECTILE_WEAPON = "sourceWeapon"; + + private static boolean busyBreak = false; + + public static void popResource(@NotNull BlockDropItemEvent event, @NotNull ItemStack itemStack) { + Item item = ExcellentEnchantsAPI.PLUGIN.getEnchantNMS().popResource(event.getBlock(), itemStack); + event.getItems().add(item); + } + + public static boolean isBusyByOthers() { + return false; + } + + public static boolean isBusyByEnchant() { + return busyBreak; + } + + public static boolean isBusy() { + return isBusyByEnchant() || isBusyByOthers(); + } + + public static void busyBreak(@NotNull Player player, @NotNull Block block) { + busyBreak = true; + player.breakBlock(block); + busyBreak = false; + } + + public static void safeBusyBreak(@NotNull Player player, @NotNull Block block) { + if (!isBusy()) { + busyBreak(player, block); + } + } + + @Nullable + public static Enchantment getEnchantment(@NotNull String id) { + return getEnchantment(createKey(id)); + } + + @Nullable + public static Enchantment getEnchantment(@NotNull NamespacedKey key) { + if (Version.isAtLeast(Version.V1_20_R3)) { + return Registry.ENCHANTMENT.get(key); + } + else { + return Enchantment.getByKey(key); + } + } + + @NotNull + public static NamespacedKey createKey(@NotNull String id) { + return NamespacedKey.minecraft(id.toLowerCase()); + } + + @Nullable + public static String getLocalized(@NotNull String keyRaw) { + Enchantment enchantment = getEnchantment(keyRaw); + return enchantment == null ? null : getLocalized(enchantment); + } + + @NotNull + public static String getLocalized(@NotNull Enchantment enchantment) { + ExcellentEnchant enchant = EnchantRegistry.getByKey(enchantment.getKey()); + if (enchant != null) { + return enchant.getDisplayName(); + } + return LangManager.getEnchantment(enchantment); + } + + public static boolean isEnchantable(@NotNull ItemStack item) { + if (item.getType().isAir()) return false; + + return item.getType() == Material.ENCHANTED_BOOK || Stream.of(EnchantmentTarget.values()).anyMatch(target -> target.includes(item)); + } + + public static boolean isBook(@NotNull ItemStack item) { + return item.getType() == Material.BOOK || item.getType() == Material.ENCHANTED_BOOK; + } + + public static boolean add(@NotNull ItemStack item, @NotNull Enchantment enchantment, int level, boolean force) { + if (!force && (!enchantment.canEnchantItem(item) && !isBook(item))) return false; + + remove(item, enchantment); + + ItemMeta meta = item.getItemMeta(); + if (meta == null) return false; + + if (meta instanceof EnchantmentStorageMeta storageMeta) { + if (!storageMeta.addStoredEnchant(enchantment, level, true)) return false; + } + else { + if (!meta.addEnchant(enchantment, level, true)) return false; + } + item.setItemMeta(meta); + + return true; + } + + public static void remove(@NotNull ItemStack item, @NotNull Enchantment enchantment) { + ItemMeta meta = item.getItemMeta(); + if (meta instanceof EnchantmentStorageMeta storageMeta) { + storageMeta.removeStoredEnchant(enchantment); + } + else { + meta.removeEnchant(enchantment); + } + item.setItemMeta(meta); + } + + public static void updateChargesDisplay(@NotNull ItemStack item) { + if (Config.ENCHANTMENTS_CHARGES_ENABLED.get()) { + updateDisplay(item); + } + } + + public static boolean canHaveDescription(@NotNull ItemStack item) { + if (!Config.ENCHANTMENTS_DESCRIPTION_ENABLED.get()) return false; + + if (Config.ENCHANTMENTS_DESCRIPTION_BOOKS_ONLY.get()) { + return item.getType() == Material.ENCHANTED_BOOK; + } + + return true; + } + + public static boolean updateDisplay(@NotNull ItemStack item) { + if (Config.ENCHANTMENTS_DISPLAY_MODE.get() != 1) return false; + + ItemMeta meta = item.getItemMeta(); + if (meta == null) return false; + + if (!isEnchantable(item)) { + PDCUtil.remove(item, KEY_LORE_SIZE); + return false; + } + + Map enchants = getExcellents(item); + + int sizeCached = PDCUtil.getInt(item, KEY_LORE_SIZE).orElse(0); + int sizeReal = enchants.size(); + if (sizeCached == 0 && sizeReal == 0) return false; + + List lore = meta.getLore() == null ? new ArrayList<>() : meta.getLore(); + for (int index = 0; index < sizeCached && !lore.isEmpty(); index++) { + lore.remove(0); + } + //lore.removeIf(str -> enchants.keySet().stream().anyMatch(enchant -> str.contains(enchant.getDisplayName()))); + + if (!meta.hasItemFlag(ItemFlag.HIDE_ENCHANTS)) { + if (canHaveDescription(item)) { + enchants.forEach((enchant, level) -> { + lore.addAll(0, enchant.formatDescription(level)); + }); + sizeReal += enchants.keySet().stream().map(ExcellentEnchant::getDescription).mapToInt(List::size).sum(); + } + enchants.forEach((enchant, level) -> { + lore.add(0, enchant.getNameFormatted(level, getCharges(meta, enchant))); + }); + } + else sizeReal = 0; + + meta.setLore(lore); + if (sizeReal > 0) { + PDCUtil.set(meta, KEY_LORE_SIZE, sizeReal); + } + else PDCUtil.remove(meta, KEY_LORE_SIZE); + item.setItemMeta(meta); + return true; + } + + @Nullable + public static ItemStack getFishingRod(@NotNull Player player) { + ItemStack main = player.getInventory().getItem(EquipmentSlot.HAND); + if (main != null && main.getType() == Material.FISHING_ROD) return main; + + ItemStack off = player.getInventory().getItem(EquipmentSlot.OFF_HAND); + if (off != null && off.getType() == Material.FISHING_ROD) return off; + + return null; + } + + @NotNull + public static Map getAll(@NotNull ItemStack item) { + ItemMeta meta = item.getItemMeta(); + return meta == null ? Collections.emptyMap() : getAll(meta); + } + + @NotNull + public static Map getAll(@NotNull ItemMeta meta) { + return (meta instanceof EnchantmentStorageMeta meta2) ? meta2.getStoredEnchants() : meta.getEnchants(); + } + + public static int getAmount(@NotNull ItemStack item) { + return getAll(item).size(); + } + + public static boolean contains(@NotNull ItemStack item, @NotNull String id) { + ExcellentEnchant enchant = EnchantRegistry.getById(id); + if (enchant == null) return false; + + return contains(item, enchant.getBackend()); + } + + public static boolean contains(@NotNull ItemStack item, @NotNull Enchantment enchantment) { + return getLevel(item, enchantment) > 0; + } + + public static int getLevel(@NotNull ItemStack item, @NotNull Enchantment enchant) { + return getAll(item).getOrDefault(enchant, 0); + } + + public static int getCharges(@NotNull ItemStack item, @NotNull ExcellentEnchant enchant) { + return enchant.isChargesEnabled() ? PDCUtil.getInt(item, enchant.getChargesKey()).orElse(0) : -1; + } + + public static int getCharges(@NotNull ItemMeta meta, @NotNull ExcellentEnchant enchant) { + return enchant.isChargesEnabled() ? PDCUtil.getInt(meta, enchant.getChargesKey()).orElse(0) : -1; + } + + public static boolean isOutOfCharges(@NotNull ItemStack item, @NotNull ExcellentEnchant enchant) { + return enchant.isChargesEnabled() && getCharges(item, enchant) == 0; + } + + public static boolean isFullOfCharges(@NotNull ItemStack item, @NotNull ExcellentEnchant enchant) { + if (!enchant.isChargesEnabled()) return true; + + int level = getLevel(item, enchant.getBackend()); + int max = enchant.getChargesMax(level); + return getCharges(item, enchant) == max; + } + + public static void consumeCharges(@NotNull ItemStack item, @NotNull ExcellentEnchant enchant, int level) { + int has = getCharges(item, enchant); + int use = enchant.getChargesConsumeAmount(level); + setCharges(item, enchant, level, has < use ? 0 : has - use); + } + + /*public static void restoreCharges(@NotNull ItemStack item, @NotNull ExcellentEnchant enchant) { + int level = getLevel(item, enchant); + restoreCharges(item, enchant, level); + }*/ + + public static void restoreCharges(@NotNull ItemStack item, @NotNull ExcellentEnchant enchant, int level) { + setCharges(item, enchant, level, Integer.MAX_VALUE); + } + + public static void rechargeCharges(@NotNull ItemStack item, @NotNull ExcellentEnchant enchant, int level) { + //if (!enchant.isChargesEnabled()) return; + + //int level = getLevel(item, enchant); + int recharge = enchant.getChargesRechargeAmount(level); + int has = getCharges(item, enchant); + int set = has + recharge; + setCharges(item, enchant, level, set); + } + + /*public static void setCharges(@NotNull ItemStack item, @NotNull ExcellentEnchant enchant, int charges) { + int level = getLevel(item, enchant); + setCharges(item, enchant, level, charges); + }*/ + + public static void setCharges(@NotNull ItemStack item, @NotNull ExcellentEnchant enchant, int level, int charges) { + if (!enchant.isChargesEnabled()) return; + + int max = enchant.getChargesMax(level); + int set = Math.min(Math.abs(charges), max); + PDCUtil.set(item, enchant.getChargesKey(), set); + } + + public static int getExcellentAmount(@NotNull ItemStack item) { + return getExcellents(item).size(); + } + + @NotNull + public static Map getExcellents(@NotNull ItemStack item) { + return getExcellents(getAll(item)); + } + + @NotNull + public static Map getExcellents(@NotNull ItemMeta meta) { + return getExcellents(getAll(meta)); + } + + @NotNull + private static Map getExcellents(@NotNull Map enchants) { + Map map = new HashMap<>(); + enchants.forEach((enchantment, level) -> { + ExcellentEnchant excellent = EnchantRegistry.getByKey(enchantment.getKey()); + if (excellent != null) { + map.put(excellent, level); + } + }); + return map; + } + + @NotNull + public static Map getExcellents(@NotNull ItemStack item, @NotNull Class clazz) { + Map map = new HashMap<>(); + getAll(item).forEach((enchantment, level) -> { + ExcellentEnchant excellent = EnchantRegistry.getByKey(enchantment.getKey()); + if (excellent == null || !clazz.isAssignableFrom(excellent.getClass())) return; + + map.put(clazz.cast(excellent), level); + }); + return map;//CollectionsUtil.sort(map, Comparator.comparing(p -> p.getKey().getPriority(), Comparator.reverseOrder())); + } + + @NotNull + public static Map getEnchantedEquipment(@NotNull LivingEntity entity) { + return getEnchantedEquipment(entity, EquipmentSlot.values()); + } + + @NotNull + public static Map getEnchantedEquipment(@NotNull LivingEntity entity, @NotNull EquipmentSlot... slots) { + Map equipment = EntityUtil.getEquippedItems(entity, slots); + equipment.entrySet().removeIf(entry -> { + ItemStack item = entry.getValue(); + EquipmentSlot slot = entry.getKey(); + if (item == null || item.getType().isAir() || item.getType() == Material.ENCHANTED_BOOK) return true; + if ((slot == EquipmentSlot.HAND || slot == EquipmentSlot.OFF_HAND) && ItemUtil.isArmor(item)) return true; + return !item.hasItemMeta(); + }); + return equipment; + } + + @NotNull + public static Map> getEquipped(@NotNull LivingEntity entity) { + Map> map = new HashMap<>(); + getEnchantedEquipment(entity).values().forEach(item -> { + map.computeIfAbsent(item, k -> new LinkedHashMap<>()).putAll(getExcellents(item)); + }); + return map; + } + + @NotNull + public static Map> getEquipped(@NotNull LivingEntity entity, + @NotNull Class clazz) { + return getEquipped(entity, clazz, EquipmentSlot.values()); + } + + @NotNull + public static Map> getEquipped(@NotNull LivingEntity entity, + @NotNull Class clazz, + @NotNull EquipmentSlot... slots) { + Map> map = new HashMap<>(); + getEnchantedEquipment(entity, slots).values().forEach(item -> { + map.computeIfAbsent(item, k -> new LinkedHashMap<>()).putAll(getExcellents(item, clazz)); + }); + return map; + } + + @NotNull + public static Map getEquipped(@NotNull LivingEntity entity, @NotNull ExcellentEnchant enchantment) { + Map map = new HashMap<>(); + getEnchantedEquipment(entity).values().forEach(item -> { + int level = getLevel(item, enchantment.getBackend()); + if (level > 0) { + map.put(item, level); + } + }); + return map; + } + + public static void setSourceWeapon(@NotNull Projectile projectile, @Nullable ItemStack item) { + if (item == null) return; + + projectile.setMetadata(META_PROJECTILE_WEAPON, new FixedMetadataValue(ExcellentEnchantsAPI.PLUGIN, item)); + } + + @Nullable + public static ItemStack getSourceWeapon(@NotNull Projectile projectile) { + return projectile.hasMetadata(META_PROJECTILE_WEAPON) ? (ItemStack) projectile.getMetadata(META_PROJECTILE_WEAPON).get(0).value() : null; + } + + public static void removeSourceWeapon(@NotNull Projectile projectile) { + projectile.removeMetadata(META_PROJECTILE_WEAPON, ExcellentEnchantsAPI.PLUGIN); + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/util/Modifier.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/util/Modifier.java new file mode 100644 index 0000000..5353180 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/util/Modifier.java @@ -0,0 +1,69 @@ +package su.nightexpress.excellentenchants.enchantment.util; + +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.api.config.JOption; +import su.nexmedia.engine.api.config.JYML; + +public class Modifier { + + private final double base; + private final double perLevel; + private final ModifierAction action; + + public Modifier(double base, double perLevel, @NotNull ModifierAction action) { + this.base = base; + this.perLevel = perLevel; + this.action = action; + } + + @NotNull + public static Modifier add(double base, double perLevel) { + return new Modifier(base, perLevel, ModifierAction.ADD); + } + + @NotNull + public static Modifier multiply(double base, double perLevel) { + return new Modifier(base, perLevel, ModifierAction.MULTIPLY); + } + + @NotNull + public static Modifier read(@NotNull JYML cfg, @NotNull String path, @NotNull Modifier def, @NotNull String... comments) { + return new JOption<>(path, + (cfg2, path2, def2) -> read(cfg2, path2), + def, + comments + ).setWriter((cfg2, path2, mod) -> mod.write(cfg2, path2)).read(cfg); + } + + @NotNull + public static Modifier read(@NotNull JYML cfg, @NotNull String path) { + double base = JOption.create(path + ".Base", 0D).read(cfg); + double perLevel = JOption.create(path + ".Per_Level", 0D).read(cfg); + ModifierAction action = JOption.create(path + ".Action", ModifierAction.class, ModifierAction.ADD).read(cfg); + + return new Modifier(base, perLevel, action); + } + + public void write(@NotNull JYML cfg, @NotNull String path) { + cfg.set(path + ".Base", this.getBase()); + cfg.set(path + ".Per_Level", this.getPerLevel()); + cfg.set(path + ".Action", this.getAction().name()); + } + + public double getValue(int level) { + return this.action.math(this.getBase(), this.getPerLevel() * level); + } + + public double getBase() { + return base; + } + + public double getPerLevel() { + return perLevel; + } + + @NotNull + public ModifierAction getAction() { + return action; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/util/ModifierAction.java b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/util/ModifierAction.java new file mode 100644 index 0000000..06b719d --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/enchantment/util/ModifierAction.java @@ -0,0 +1,21 @@ +package su.nightexpress.excellentenchants.enchantment.util; + +import org.jetbrains.annotations.NotNull; + +import java.util.function.BiFunction; + +public enum ModifierAction { + + ADD(Double::sum), + MULTIPLY((origin, target) -> origin * target); + + private final BiFunction function; + + ModifierAction(@NotNull BiFunction function) { + this.function = function; + } + + public double math(double origin, double target) { + return this.function.apply(origin, target); + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/hook/HookId.java b/Core/src/main/java/su/nightexpress/excellentenchants/hook/HookId.java new file mode 100644 index 0000000..574994a --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/hook/HookId.java @@ -0,0 +1,8 @@ +package su.nightexpress.excellentenchants.hook; + +public class HookId { + + public static final String MYTHIC_MOBS = "MythicMobs"; + public static final String NCP = "NoCheatPlus"; + public static final String PROTOCOL_LIB = "ProtocolLib"; +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/hook/impl/MythicMobsHook.java b/Core/src/main/java/su/nightexpress/excellentenchants/hook/impl/MythicMobsHook.java new file mode 100644 index 0000000..0841f8e --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/hook/impl/MythicMobsHook.java @@ -0,0 +1,14 @@ +package su.nightexpress.excellentenchants.hook.impl; + +import io.lumine.mythic.bukkit.MythicBukkit; +import org.bukkit.entity.Entity; +import org.jetbrains.annotations.NotNull; + +public class MythicMobsHook { + + private static final MythicBukkit MYTHIC_MOBS = MythicBukkit.inst(); + + public static boolean isMythicMob(@NotNull Entity entity) { + return MYTHIC_MOBS.getAPIHelper().isMythicMob(entity); + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/hook/impl/NoCheatPlusHook.java b/Core/src/main/java/su/nightexpress/excellentenchants/hook/impl/NoCheatPlusHook.java new file mode 100644 index 0000000..9cc6458 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/hook/impl/NoCheatPlusHook.java @@ -0,0 +1,24 @@ +package su.nightexpress.excellentenchants.hook.impl; + +import fr.neatmonster.nocheatplus.NCPAPIProvider; +import fr.neatmonster.nocheatplus.checks.CheckType; +import fr.neatmonster.nocheatplus.hooks.ExemptionContext; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.utils.EngineUtils; +import su.nightexpress.excellentenchants.hook.HookId; + +public class NoCheatPlusHook { + + public static void exemptBlocks(@NotNull Player player) { + if (!EngineUtils.hasPlugin(HookId.NCP)) return; + + NCPAPIProvider.getNoCheatPlusAPI().getPlayerDataManager().getPlayerData(player).exempt(CheckType.BLOCKBREAK, ExemptionContext.ANONYMOUS_NESTED); + } + + public static void unexemptBlocks(@NotNull Player player) { + if (!EngineUtils.hasPlugin(HookId.NCP)) return; + + NCPAPIProvider.getNoCheatPlusAPI().getPlayerDataManager().getPlayerData(player).unexempt(CheckType.BLOCKBREAK, ExemptionContext.ANONYMOUS_NESTED); + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/hook/impl/PlaceholderHook.java b/Core/src/main/java/su/nightexpress/excellentenchants/hook/impl/PlaceholderHook.java new file mode 100644 index 0000000..739e839 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/hook/impl/PlaceholderHook.java @@ -0,0 +1,97 @@ +package su.nightexpress.excellentenchants.hook.impl; + +import me.clip.placeholderapi.expansion.PlaceholderExpansion; +import org.bukkit.NamespacedKey; +import org.bukkit.entity.Player; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import su.nexmedia.engine.utils.StringUtil; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.ExcellentEnchantsAPI; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.registry.EnchantRegistry; + +public class PlaceholderHook { + + private static EnchantsExpansion expansion; + + public static void setup(@NotNull ExcellentEnchants plugin) { + if (expansion == null) { + expansion = new EnchantsExpansion(plugin); + expansion.register(); + } + } + + public static void shutdown() { + if (expansion != null) { + expansion.unregister(); + expansion = null; + } + } + + static class EnchantsExpansion extends PlaceholderExpansion { + + private final ExcellentEnchants plugin; + + public EnchantsExpansion(@NotNull ExcellentEnchants plugin) { + this.plugin = plugin; + } + + @Override + @NotNull + public String getIdentifier() { + return this.plugin.getName().toLowerCase(); + } + + @Override + @NotNull + public String getAuthor() { + return this.plugin.getDescription().getAuthors().get(0); + } + + @Override + @NotNull + public String getVersion() { + return this.plugin.getDescription().getVersion(); + } + + @Override + public boolean persist() { + return true; + } + + @Override + @Nullable + public String onPlaceholderRequest(Player player, @NotNull String params) { + if (params.startsWith("charges_remaining_")) { + String[] chargesSplit = params.substring("charges_remaining_".length()).split(":"); + if (chargesSplit.length < 2) return null; + + EquipmentSlot slot = StringUtil.getEnum(chargesSplit[0], EquipmentSlot.class).orElse(null); + if (slot == null) return null; + + ItemStack item = player.getInventory().getItem(slot); + if (item == null || item.getType().isAir()) return "-"; + + ExcellentEnchant enchant = EnchantRegistry.getByKey(NamespacedKey.minecraft(chargesSplit[1].toLowerCase())); + if (enchant == null) return null; + + return String.valueOf(enchant.getCharges(item)); + } + if (params.startsWith("charges_maximum_")) { + String[] chargesSplit = params.substring("charges_maximum_".length()).split(":"); + if (chargesSplit.length < 2) return null; + + ExcellentEnchant enchant = EnchantRegistry.getByKey(NamespacedKey.minecraft(chargesSplit[0].toLowerCase())); + if (enchant == null) return null; + + int level = StringUtil.getInteger(chargesSplit[1], 1); + + return String.valueOf(enchant.getChargesMax(level)); + } + return super.onPlaceholderRequest(player, params); + } + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/hook/impl/ProtocolHook.java b/Core/src/main/java/su/nightexpress/excellentenchants/hook/impl/ProtocolHook.java new file mode 100644 index 0000000..6e0fc84 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/hook/impl/ProtocolHook.java @@ -0,0 +1,117 @@ +package su.nightexpress.excellentenchants.hook.impl; + +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.ProtocolLibrary; +import com.comphenix.protocol.ProtocolManager; +import com.comphenix.protocol.events.PacketAdapter; +import com.comphenix.protocol.events.PacketContainer; +import com.comphenix.protocol.events.PacketEvent; +import org.bukkit.GameMode; +import org.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.MerchantRecipe; +import org.bukkit.inventory.meta.ItemMeta; +import org.jetbrains.annotations.Nullable; +import su.nightexpress.excellentenchants.ExcellentEnchantsAPI; +import su.nightexpress.excellentenchants.config.Config; +import su.nightexpress.excellentenchants.enchantment.impl.ExcellentEnchant; +import su.nightexpress.excellentenchants.enchantment.util.EnchantUtils; + +import java.util.*; +import java.util.stream.Collectors; + +public class ProtocolHook { + + private static boolean isRegistered = false; + + public static void setup() { + if (isRegistered) return; + + ProtocolManager manager = ProtocolLibrary.getProtocolManager(); + manager.addPacketListener(new PacketAdapter(ExcellentEnchantsAPI.PLUGIN, PacketType.Play.Server.SET_SLOT) { + @Override + public void onPacketSending(PacketEvent event) { + PacketContainer packet = event.getPacket(); + + ItemStack item = packet.getItemModifier().read(0); + boolean isCreative = event.getPlayer().getGameMode() == GameMode.CREATIVE; + packet.getItemModifier().write(0, update(item, isCreative)); + } + }); + + manager.addPacketListener(new PacketAdapter(ExcellentEnchantsAPI.PLUGIN, PacketType.Play.Server.WINDOW_ITEMS) { + @Override + public void onPacketSending(PacketEvent event) { + PacketContainer packet = event.getPacket(); + + List items = packet.getItemListModifier().readSafely(0); + boolean isCreative = event.getPlayer().getGameMode() == GameMode.CREATIVE; + + for (int index = 0; index < items.size(); index++) { + ItemStack item = items.get(index); + items.set(index, update(item, isCreative)); + } + packet.getItemListModifier().write(0, items); + } + }); + + manager.addPacketListener(new PacketAdapter(ExcellentEnchantsAPI.PLUGIN, PacketType.Play.Server.OPEN_WINDOW_MERCHANT) { + @Override + public void onPacketSending(PacketEvent event) { + PacketContainer packet = event.getPacket(); + + List list = new ArrayList<>(); + boolean isCreative = event.getPlayer().getGameMode() == GameMode.CREATIVE; + packet.getMerchantRecipeLists().read(0).forEach(recipe -> { + ItemStack result = update(recipe.getResult(), isCreative); + if (result == null) return; + + MerchantRecipe r2 = new MerchantRecipe(result, recipe.getUses(), recipe.getMaxUses(), recipe.hasExperienceReward(), recipe.getVillagerExperience(), recipe.getPriceMultiplier(), recipe.getDemand(), recipe.getSpecialPrice()); + r2.setIngredients(recipe.getIngredients()); + list.add(r2); + }); + packet.getMerchantRecipeLists().write(0, list); + } + }); + + isRegistered = true; + } + + @Nullable + public static ItemStack update(@Nullable ItemStack item, boolean isCreative) { + if (item == null || item.getType().isAir()) return item; + + ItemStack copy = new ItemStack(item); + ItemMeta meta = copy.getItemMeta(); + if (meta == null || meta.hasItemFlag(ItemFlag.HIDE_ENCHANTS)) return item; + + Map enchants = EnchantUtils.getExcellents(meta) + .entrySet().stream() + .sorted(Comparator.comparing(e -> e.getKey().getTier().getPriority())) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (old,nev) -> nev, LinkedHashMap::new)); + if (enchants.isEmpty()) return item; + + List lore = meta.getLore() == null ? new ArrayList<>() : meta.getLore(); + if (!lore.isEmpty()) { + enchants.keySet().forEach(enchant -> lore.removeIf(line -> line.contains(enchant.getDisplayName()))); + if (isCreative) { + enchants.forEach((enchant, level) -> { + lore.removeAll(enchant.formatDescription(level)); + }); + } + } + if (EnchantUtils.canHaveDescription(item) && !isCreative) { + enchants.forEach((enchant, level) -> { + lore.addAll(0, enchant.formatDescription(level)); + }); + } + enchants.forEach((enchant, level) -> { + int charges = EnchantUtils.getCharges(meta, enchant); + lore.add(0, enchant.getNameFormatted(level, charges)); + }); + + meta.setLore(lore); + copy.setItemMeta(meta); + return copy; + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/tier/Tier.java b/Core/src/main/java/su/nightexpress/excellentenchants/tier/Tier.java new file mode 100644 index 0000000..89bc372 --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/tier/Tier.java @@ -0,0 +1,84 @@ +package su.nightexpress.excellentenchants.tier; + +import org.jetbrains.annotations.NotNull; +import su.nexmedia.engine.api.config.JYML; +import su.nexmedia.engine.api.placeholder.PlaceholderMap; +import su.nexmedia.engine.utils.Colorizer; +import su.nexmedia.engine.utils.NumberUtil; +import su.nightexpress.excellentenchants.Placeholders; +import su.nightexpress.excellentenchants.api.enchantment.ITier; +import su.nightexpress.excellentenchants.api.enchantment.ObtainType; + +import java.util.Map; + +public class Tier implements ITier { + + private final String id; + private final int priority; + private final String name; + private final String color; + private final Map chance; + private final PlaceholderMap placeholderMap; + + public Tier(@NotNull String id, int priority, @NotNull String name, @NotNull String color, + @NotNull Map chance) { + this.id = id.toLowerCase(); + this.priority = priority; + this.name = Colorizer.apply(name); + this.color = Colorizer.apply(color); + this.chance = chance; + this.placeholderMap = new PlaceholderMap() + .add(Placeholders.TIER_ID, this::getId) + .add(Placeholders.TIER_NAME, this::getName) + .add(Placeholders.TIER_OBTAIN_CHANCE_ENCHANTING, () -> NumberUtil.format(this.getChance(ObtainType.ENCHANTING))) + .add(Placeholders.TIER_OBTAIN_CHANCE_VILLAGER, () -> NumberUtil.format(this.getChance(ObtainType.VILLAGER))) + .add(Placeholders.TIER_OBTAIN_CHANCE_LOOT_GENERATION, () -> NumberUtil.format(this.getChance(ObtainType.LOOT_GENERATION))) + .add(Placeholders.TIER_OBTAIN_CHANCE_FISHING, () -> NumberUtil.format(this.getChance(ObtainType.FISHING))) + .add(Placeholders.TIER_OBTAIN_CHANCE_MOB_SPAWNING, () -> NumberUtil.format(this.getChance(ObtainType.MOB_SPAWNING))) + ; + } + + public void write(@NotNull JYML cfg, @NotNull String path) { + cfg.set(path + ".Name", this.getName()); + cfg.set(path + ".Color", this.getColor()); + cfg.set(path + ".Priority", this.getPriority()); + cfg.remove(path + ".Obtain_Chance"); + this.getChance().forEach((type, chance) -> { + cfg.set(path + ".Obtain_Chance." + type.name(), chance); + }); + } + + @Override + @NotNull + public PlaceholderMap getPlaceholders() { + return this.placeholderMap; + } + + @NotNull + public String getId() { + return this.id; + } + + public int getPriority() { + return priority; + } + + @NotNull + public String getName() { + return this.getColor() + this.name; + } + + @NotNull + public String getColor() { + return this.color; + } + + @NotNull + public Map getChance() { + return this.chance; + } + + public double getChance(@NotNull ObtainType obtainType) { + return this.getChance().getOrDefault(obtainType, 0D); + } +} diff --git a/Core/src/main/java/su/nightexpress/excellentenchants/tier/TierManager.java b/Core/src/main/java/su/nightexpress/excellentenchants/tier/TierManager.java new file mode 100644 index 0000000..8e0d7ae --- /dev/null +++ b/Core/src/main/java/su/nightexpress/excellentenchants/tier/TierManager.java @@ -0,0 +1,119 @@ +package su.nightexpress.excellentenchants.tier; + +import net.md_5.bungee.api.ChatColor; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import su.nexmedia.engine.api.config.JYML; +import su.nexmedia.engine.api.manager.AbstractManager; +import su.nexmedia.engine.utils.Colors2; +import su.nexmedia.engine.utils.random.Rnd; +import su.nightexpress.excellentenchants.ExcellentEnchants; +import su.nightexpress.excellentenchants.config.Config; +import su.nightexpress.excellentenchants.api.enchantment.ObtainType; + +import java.util.*; +import java.util.stream.Collectors; + +public class TierManager extends AbstractManager { + + public static final String FILE_NAME = "tiers.yml"; + + private final Map tierMap; + + public TierManager(@NotNull ExcellentEnchants plugin) { + super(plugin); + this.tierMap = new HashMap<>(); + } + + @Override + protected void onLoad() { + JYML config = this.getConfig(); + + if (config.getSection("").isEmpty()) { + Config.getDefaultTiers().forEach(tier -> tier.write(config, tier.getId())); + } + + for (String sId : config.getSection("")) { + String path = sId + "."; + + int priority = config.getInt(path + "Priority"); + String name = config.getString(path + "Name", sId); + String color = config.getString(path + "Color", Colors2.WHITE); + + ChatColor chatColor = ChatColor.of(color); + if (chatColor != null) { + color = chatColor.toString(); + } + + Map chance = new HashMap<>(); + for (ObtainType obtainType : ObtainType.values()) { + config.addMissing(path + "Obtain_Chance." + obtainType.name(), 50D); + + double chanceType = config.getDouble(path + "Obtain_Chance." + obtainType.name()); + chance.put(obtainType, chanceType); + } + + Tier tier = new Tier(sId, priority, name, color, chance); + this.tierMap.put(tier.getId(), tier); + } + config.saveChanges(); + + this.plugin.info("Tiers Loaded: " + this.tierMap.size()); + } + + @Override + protected void onShutdown() { + this.tierMap.clear(); + } + + @NotNull + public JYML getConfig() { + return JYML.loadOrExtract(plugin, FILE_NAME); + } + + @NotNull + public Map getTierMap() { + return tierMap; + } + + @NotNull + public Collection getTiers() { + return this.getTierMap().values(); + } + + @Nullable + public Tier getTierById(@NotNull String id) { + return this.getTierMap().get(id.toLowerCase()); + } + + @NotNull + public List getTierIds() { + return new ArrayList<>(this.getTierMap().keySet()); + } + + @Nullable + public Tier getTierByChance(@NotNull ObtainType obtainType) { + Map map = this.getTiers().stream() + .filter(tier -> tier.getChance(obtainType) > 0D) + .collect(Collectors.toMap(k -> k, v -> v.getChance(obtainType), (o, n) -> n, HashMap::new)); + if (map.isEmpty()) return null; + + return Rnd.getByWeight(map); + } + + @NotNull + public Tier getMostCommon() { + return this.getTiers().stream().min(Comparator.comparingInt(Tier::getPriority)).orElseThrow(); + } + + @NotNull + public Tier getByRarityModifier(double point) { + int minPriority = this.getTiers().stream().mapToInt(Tier::getPriority).min().orElse(0); + int maxPriority = this.getTiers().stream().mapToInt(Tier::getPriority).max().orElse(0); + + int threshold = (int) Math.ceil(minPriority + (maxPriority - minPriority) * point); + + return this.getTiers().stream().filter(tier -> tier.getPriority() <= threshold) + .max(Comparator.comparingInt(tier -> tier.getPriority() - threshold)).orElse(this.getMostCommon()); + } +} diff --git a/Core/src/main/resources/config.yml b/Core/src/main/resources/config.yml new file mode 100644 index 0000000..9fa4a0a --- /dev/null +++ b/Core/src/main/resources/config.yml @@ -0,0 +1,2 @@ +Plugin: + Command_Aliases: 'excellentenchants,eenchants' \ No newline at end of file diff --git a/Core/src/main/resources/lang/messages_cn.yml b/Core/src/main/resources/lang/messages_cn.yml new file mode 100644 index 0000000..ae013c2 --- /dev/null +++ b/Core/src/main/resources/lang/messages_cn.yml @@ -0,0 +1,18 @@ +# Tranlsated by @qsefthuopq +# Last updated:2021-03-15 +Command: + Enchant: + Usage: <附魔名> <等级> + Desc: 附魔你手中的物品。 + Done: '&a附魔成功!' + Book: + Usage: <玩家> <附魔名> <等级> + Desc: 给予自定义附魔书。 + Done: 已给予&6%player%&6%enchant%&7附魔书。 + TierBook: + Usage: <玩家> <品质> <等级> + Desc: 给予自定义品质的附魔书 + Error: '&c无效品质!' + Done: 已给予&6%player&6%enchant%&7附魔书。 +Error: + NoEnchant: '&c没有这种附魔' \ No newline at end of file diff --git a/Core/src/main/resources/lang/messages_es.yml b/Core/src/main/resources/lang/messages_es.yml new file mode 100644 index 0000000..9337d33 --- /dev/null +++ b/Core/src/main/resources/lang/messages_es.yml @@ -0,0 +1,18 @@ +Command: + List: + Desc: Lista de todos los encantamientos personalizados. + Enchant: + Usage: + Desc: Encanta el item de tu mano. + Done: '&a¡Encantado con éxito!' + Book: + Usage: + Desc: Da un libro encantado personalizado. + Done: Se ha dado un libro encantado de &6%enchant%&7 a &6%player%&7. + TierBook: + Usage: + Desc: Da un libro encantado. + Error: '&c¡Rareza inválida!' + Done: Se ha dado un libro encantado &6%tier%&7 a &6%player%&7. +Error: + NoEnchant: '&cNo hay tal encanto.' diff --git a/Core/src/main/resources/lang/messages_pl.yml b/Core/src/main/resources/lang/messages_pl.yml new file mode 100644 index 0000000..d70c887 --- /dev/null +++ b/Core/src/main/resources/lang/messages_pl.yml @@ -0,0 +1,37 @@ +Command: + List: + Desc: Lista wszystkich customowych enchantów. + Enchant: + Usage: + Desc: Zaklina przedmiot trzymany w łapce. + Done: '&aPomyślnie zaklęto!' + Book: + Usage: + Desc: Daje książkę z customowym enchantem. + Done: Przyznano zaklętą książkę &6%enchant%&7 dla &6%player_display_name%&7. + TierBook: + Usage: + Desc: Daje książkę z enchantem. + Error: '&cZły poziom!' + Done: Przyznano zaklętą książkę &6%tier_name%&7 dla &6%player_display_name%&7. +Error: + NoEnchant: '&cNiepoprawny enchant.' +FitItemType: + HELMET: Hełm + CHESTPLATE: Napierśnik + LEGGINGS: Spodnie + BOOTS: Buty + ELYTRA: Elytra + WEAPON: Broń + TOOL: Narzędzie + ARMOR: Zbroja + UNIVERSAL: Uniwersalny + SWORD: Miecz + TRIDENT: Trójząb + AXE: Siekiera + BOW: Łuk + CROSSBOW: Kusza + HOE: Motyka + PICKAXE: Kilof + SHOVEL: Łopata + FISHING_ROD: Wędka \ No newline at end of file diff --git a/Core/src/main/resources/lang/messages_ru.yml b/Core/src/main/resources/lang/messages_ru.yml new file mode 100644 index 0000000..b216be8 --- /dev/null +++ b/Core/src/main/resources/lang/messages_ru.yml @@ -0,0 +1,36 @@ +Command: + List: + Desc: 'Меню дополнительных зачарований.' + Enchant: + Usage: '<зачарование> <уровень> [игрок] [слот]' + Desc: 'Зачарование предмета в руке.' + Book: + Usage: '<игрок> <зачарование> <уровень>' + Desc: 'Выдача книги с указанным зачарованием.' + Done: 'Выдана книга с зачарованием &6%enchant%&7 игроку &6%player_name%&7.' + TierBook: + Usage: '<игрок> <ранг> <уровень>' + Desc: 'Выдача книги с произвольным зачарованием указанного ранга.' + Error: '&cТакого ранга не существует!' + Done: 'Выдана книга с зачарованием ранга &6%tier_name%&7 игроку &6%player_name%&7.' +Error: + NoEnchant: '&cТакого зачарования не существует.' +FitItemType: + HELMET: 'Шлем' + CHESTPLATE: 'Нагрудник' + LEGGINGS: 'Поножи' + BOOTS: 'Ботинки' + ELYTRA: 'Элитры' + WEAPON: 'Оружие' + TOOL: 'Инструмент' + ARMOR: 'Броня' + SWORD: 'Меч' + TRIDENT: 'Трезубец' + AXE: 'Топор' + BOW: 'Лук' + CROSSBOW: 'Арбалет' + HOE: 'Мотыга' + PICKAXE: 'Кирка' + SHOVEL: 'Лопата' + FISHING_ROD: 'Удочка' + UNIVERSAL: 'Универсально' \ No newline at end of file diff --git a/Core/src/main/resources/plugin.yml b/Core/src/main/resources/plugin.yml new file mode 100644 index 0000000..f7eea7d --- /dev/null +++ b/Core/src/main/resources/plugin.yml @@ -0,0 +1,9 @@ +main: su.nightexpress.excellentenchants.ExcellentEnchants +version: '${project.version}' +name: ExcellentEnchants +author: NightExpress +desciption: Vanilla-like enchants for your server. +depend: [ NexEngine ] +softdepend: [ ProtocolLib, NoCheatPlus, PlaceholderAPI, MythicMobs ] +api-version: 1.18 +load: STARTUP \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/NMS/pom.xml b/NMS/pom.xml new file mode 100644 index 0000000..7771457 --- /dev/null +++ b/NMS/pom.xml @@ -0,0 +1,39 @@ + + + + ExcellentEnchants + su.nightexpress.excellentenchants + 3.6.5 + + 4.0.0 + + NMS + + + 17 + 17 + + + + + md_5-releases + https://repo.md-5.net/content/repositories/releases/ + + + + + + org.spigotmc + spigot-api + 1.20.4-R0.1-SNAPSHOT + + + su.nightexpress.excellentenchants + API + 3.6.5 + + + + \ No newline at end of file diff --git a/NMS/src/main/java/su/nightexpress/excellentenchants/nms/EnchantNMS.java b/NMS/src/main/java/su/nightexpress/excellentenchants/nms/EnchantNMS.java new file mode 100644 index 0000000..ac6c176 --- /dev/null +++ b/NMS/src/main/java/su/nightexpress/excellentenchants/nms/EnchantNMS.java @@ -0,0 +1,34 @@ +package su.nightexpress.excellentenchants.nms; + +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.entity.FishHook; +import org.bukkit.entity.Item; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import su.nightexpress.excellentenchants.api.enchantment.IEnchantment; + +import java.util.Set; + +public interface EnchantNMS { + + void unfreezeRegistry(); + + void freezeRegistry(); + + void registerEnchantment(@NotNull IEnchantment enchantment); + + void sendAttackPacket(@NotNull Player player, int id); + + void retrieveHook(@NotNull FishHook hook, @NotNull ItemStack item); + + @Nullable + @Deprecated ItemStack getSpawnEgg(@NotNull LivingEntity entity); + + @NotNull Set handleFlameWalker(@NotNull LivingEntity entity, @NotNull Location location, int level); + + @NotNull Item popResource(@NotNull Block block, @NotNull ItemStack item); +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..6d411e7 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# ExcellentEnchants-spigot diff --git a/V1_18_R2/pom.xml b/V1_18_R2/pom.xml new file mode 100644 index 0000000..e957864 --- /dev/null +++ b/V1_18_R2/pom.xml @@ -0,0 +1,71 @@ + + + + ExcellentEnchants + su.nightexpress.excellentenchants + 3.6.5 + + 4.0.0 + + V1_18_R2 + + + 16 + 16 + + + + + org.spigotmc + spigot + 1.18.2-R0.1-SNAPSHOT + remapped-mojang + + + su.nightexpress.excellentenchants + NMS + 3.6.5 + + + + + + + net.md-5 + specialsource-maven-plugin + 1.2.3 + + + package + + remap + + remap-obf + + org.spigotmc:minecraft-server:1.18.2-R0.1-SNAPSHOT:txt:maps-mojang + true + org.spigotmc:spigot:1.18.2-R0.1-SNAPSHOT:jar:remapped-mojang + true + remapped-obf + + + + package + + remap + + remap-spigot + + ${project.build.directory}/${project.artifactId}-${project.version}-remapped-obf.jar + org.spigotmc:minecraft-server:1.18.2-R0.1-SNAPSHOT:csrg:maps-spigot + org.spigotmc:spigot:1.18.2-R0.1-SNAPSHOT:jar:remapped-obf + + + + + + + + \ No newline at end of file diff --git a/V1_18_R2/src/main/java/su/nightexpress/excellentenchants/nms/v1_18_R2/V1_18_R2.java b/V1_18_R2/src/main/java/su/nightexpress/excellentenchants/nms/v1_18_R2/V1_18_R2.java new file mode 100644 index 0000000..d0fcb92 --- /dev/null +++ b/V1_18_R2/src/main/java/su/nightexpress/excellentenchants/nms/v1_18_R2/V1_18_R2.java @@ -0,0 +1,134 @@ +package su.nightexpress.excellentenchants.nms.v1_18_R2; + +import net.minecraft.core.BlockPos; +import net.minecraft.network.protocol.game.ClientboundAnimatePacket; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.entity.projectile.FishingHook; +import net.minecraft.world.item.SpawnEggItem; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.LiquidBlock; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.shapes.CollisionContext; +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.craftbukkit.v1_18_R2.CraftWorld; +import org.bukkit.craftbukkit.v1_18_R2.block.CraftBlock; +import org.bukkit.craftbukkit.v1_18_R2.entity.CraftFishHook; +import org.bukkit.craftbukkit.v1_18_R2.entity.CraftLivingEntity; +import org.bukkit.craftbukkit.v1_18_R2.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_18_R2.event.CraftEventFactory; +import org.bukkit.craftbukkit.v1_18_R2.inventory.CraftItemStack; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.FishHook; +import org.bukkit.entity.Item; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import su.nexmedia.engine.utils.Reflex; +import su.nightexpress.excellentenchants.api.enchantment.IEnchantment; +import su.nightexpress.excellentenchants.nms.EnchantNMS; + +import java.util.HashSet; +import java.util.Set; + +public class V1_18_R2 implements EnchantNMS { + + @Override + public void unfreezeRegistry() { + Reflex.setFieldValue(Enchantment.class, "acceptingNew", true); + } + + @Override + public void freezeRegistry() { + Enchantment.stopAcceptingRegistrations(); + } + + @Override + public void registerEnchantment(@NotNull IEnchantment enchantment) { + Enchantment.registerEnchantment((Enchantment) enchantment); + } + + @Override + public void sendAttackPacket(@NotNull Player player, int id) { + CraftPlayer craftPlayer = (CraftPlayer) player; + Entity entity = craftPlayer.getHandle(); + ClientboundAnimatePacket packet = new ClientboundAnimatePacket(entity, id); + craftPlayer.getHandle().connection.send(packet); + } + + @Override + public void retrieveHook(@NotNull FishHook hook, @NotNull ItemStack item) { + CraftFishHook craftFishHook = (CraftFishHook) hook; + FishingHook handle = craftFishHook.getHandle(); + handle.retrieve(CraftItemStack.asNMSCopy(item)); + } + + @Override + @Nullable + public ItemStack getSpawnEgg(@NotNull LivingEntity entity) { + CraftLivingEntity craftLivingEntity = (CraftLivingEntity) entity; + net.minecraft.world.entity.LivingEntity livingEntity = craftLivingEntity.getHandle(); + + SpawnEggItem eggItem = SpawnEggItem.byId(livingEntity.getType()); + if (eggItem == null) return null; + + return CraftItemStack.asBukkitCopy(eggItem.getDefaultInstance()); + } + + @Override + @NotNull + public Set handleFlameWalker(@NotNull LivingEntity bukkitEntity, @NotNull Location location, int level) { + Entity entity = ((CraftLivingEntity) bukkitEntity).getHandle(); + BlockPos pos = new BlockPos(location.getX(), location.getY(), location.getZ()); + ServerLevel world = ((CraftWorld) bukkitEntity.getWorld()).getHandle(); + + float radius = Math.min(16F, 2F + level); + BlockState bStone = Blocks.MAGMA_BLOCK.defaultBlockState(); + BlockPos.MutableBlockPos posAbove = new BlockPos.MutableBlockPos(); + + Set blocks = new HashSet<>(); + for (BlockPos posNear : BlockPos.betweenClosed(pos.offset(-radius, -1.0, -radius), pos.offset(radius, -1.0, radius))) { + if (!posNear.closerThan(entity.blockPosition(), radius)) continue; + + posAbove.set(posNear.getX(), posNear.getY() + 1, posNear.getZ()); + + BlockState bLavaAbove = world.getBlockState(posAbove); + BlockState bLava = world.getBlockState(posNear); + + if (!bLavaAbove.isAir()) continue; + if (!bLava.getBlock().equals(Blocks.LAVA)) continue; + if (bLava.getValue(LiquidBlock.LEVEL) != 0) continue; + if (!bStone.canSurvive(world, posNear)) continue; + if (!world.isUnobstructed(bStone, posNear, CollisionContext.empty())) continue; + if (!CraftEventFactory.handleBlockFormEvent(world, posNear, bStone, entity)) continue; + //world.scheduleTick(posNear, Blocks.STONE, Rnd.get(60, 120)); + + Location bukkitLoc = new Location(world.getWorld(), posNear.getX(), posNear.getY(), posNear.getZ()); + blocks.add(bukkitLoc.getBlock()); + } + return blocks; + } + + @NotNull + public Item popResource(@NotNull Block block, @NotNull ItemStack item) { + Level world = ((CraftWorld)block.getWorld()).getHandle(); + BlockPos pos = ((CraftBlock)block).getPosition(); + net.minecraft.world.item.ItemStack itemstack = CraftItemStack.asNMSCopy(item); + + float yMod = EntityType.ITEM.getHeight() / 2.0F; + double x = (pos.getX() + 0.5F) + Mth.nextDouble(world.random, -0.25D, 0.25D); + double y = (pos.getY() + 0.5F) + Mth.nextDouble(world.random, -0.25D, 0.25D) - yMod; + double z = (pos.getZ() + 0.5F) + Mth.nextDouble(world.random, -0.25D, 0.25D); + + ItemEntity itemEntity = new ItemEntity(world, x, y, z, itemstack); + itemEntity.setDefaultPickUpDelay(); + return (Item) itemEntity.getBukkitEntity(); + } +} diff --git a/V1_19_R3/pom.xml b/V1_19_R3/pom.xml new file mode 100644 index 0000000..15d35ae --- /dev/null +++ b/V1_19_R3/pom.xml @@ -0,0 +1,71 @@ + + + + ExcellentEnchants + su.nightexpress.excellentenchants + 3.6.5 + + 4.0.0 + + V1_19_R3 + + + 16 + 16 + + + + + org.spigotmc + spigot + 1.19.4-R0.1-SNAPSHOT + remapped-mojang + + + su.nightexpress.excellentenchants + NMS + 3.6.5 + + + + + + + net.md-5 + specialsource-maven-plugin + 1.2.4 + + + package + + remap + + remap-obf + + org.spigotmc:minecraft-server:1.19.4-R0.1-SNAPSHOT:txt:maps-mojang + true + org.spigotmc:spigot:1.19.4-R0.1-SNAPSHOT:jar:remapped-mojang + true + remapped-obf + + + + package + + remap + + remap-spigot + + ${project.build.directory}/${project.artifactId}-${project.version}-remapped-obf.jar + org.spigotmc:minecraft-server:1.19.4-R0.1-SNAPSHOT:csrg:maps-spigot + org.spigotmc:spigot:1.19.4-R0.1-SNAPSHOT:jar:remapped-obf + + + + + + + + \ No newline at end of file diff --git a/V1_19_R3/src/main/java/su/nightexpress/excellentenchants/nms/v1_19_R3/V1_19_R3.java b/V1_19_R3/src/main/java/su/nightexpress/excellentenchants/nms/v1_19_R3/V1_19_R3.java new file mode 100644 index 0000000..f0ce0a6 --- /dev/null +++ b/V1_19_R3/src/main/java/su/nightexpress/excellentenchants/nms/v1_19_R3/V1_19_R3.java @@ -0,0 +1,134 @@ +package su.nightexpress.excellentenchants.nms.v1_19_R3; + +import net.minecraft.core.BlockPos; +import net.minecraft.network.protocol.game.ClientboundAnimatePacket; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.entity.projectile.FishingHook; +import net.minecraft.world.item.SpawnEggItem; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.LiquidBlock; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.shapes.CollisionContext; +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.craftbukkit.v1_19_R3.CraftWorld; +import org.bukkit.craftbukkit.v1_19_R3.block.CraftBlock; +import org.bukkit.craftbukkit.v1_19_R3.entity.CraftFishHook; +import org.bukkit.craftbukkit.v1_19_R3.entity.CraftLivingEntity; +import org.bukkit.craftbukkit.v1_19_R3.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_19_R3.event.CraftEventFactory; +import org.bukkit.craftbukkit.v1_19_R3.inventory.CraftItemStack; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.FishHook; +import org.bukkit.entity.Item; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import su.nexmedia.engine.utils.Reflex; +import su.nightexpress.excellentenchants.api.enchantment.IEnchantment; +import su.nightexpress.excellentenchants.nms.EnchantNMS; + +import java.util.HashSet; +import java.util.Set; + +public class V1_19_R3 implements EnchantNMS { + + @Override + public void unfreezeRegistry() { + Reflex.setFieldValue(Enchantment.class, "acceptingNew", true); + } + + @Override + public void freezeRegistry() { + Enchantment.stopAcceptingRegistrations(); + } + + @Override + public void registerEnchantment(@NotNull IEnchantment enchantment) { + Enchantment.registerEnchantment((Enchantment) enchantment); + } + + @Override + public void sendAttackPacket(@NotNull Player player, int id) { + CraftPlayer craftPlayer = (CraftPlayer) player; + Entity entity = craftPlayer.getHandle(); + ClientboundAnimatePacket packet = new ClientboundAnimatePacket(entity, id); + craftPlayer.getHandle().connection.send(packet); + } + + @Override + public void retrieveHook(@NotNull FishHook hook, @NotNull ItemStack item) { + CraftFishHook craftFishHook = (CraftFishHook) hook; + FishingHook handle = craftFishHook.getHandle(); + handle.retrieve(CraftItemStack.asNMSCopy(item)); + } + + @Override + @Nullable + public ItemStack getSpawnEgg(@NotNull LivingEntity entity) { + CraftLivingEntity craftLivingEntity = (CraftLivingEntity) entity; + net.minecraft.world.entity.LivingEntity livingEntity = craftLivingEntity.getHandle(); + + SpawnEggItem eggItem = SpawnEggItem.byId(livingEntity.getType()); + if (eggItem == null) return null; + + return CraftItemStack.asBukkitCopy(eggItem.getDefaultInstance()); + } + + @Override + @NotNull + public Set handleFlameWalker(@NotNull LivingEntity bukkitEntity, @NotNull Location location, int level) { + Entity entity = ((CraftLivingEntity) bukkitEntity).getHandle(); + BlockPos pos = new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()); + ServerLevel world = ((CraftWorld) bukkitEntity.getWorld()).getHandle(); + + int radius = Math.min(16, 2 + level); + BlockState bStone = Blocks.MAGMA_BLOCK.defaultBlockState(); + BlockPos.MutableBlockPos posAbove = new BlockPos.MutableBlockPos(); + + Set blocks = new HashSet<>(); + for (BlockPos posNear : BlockPos.betweenClosed(pos.offset(-radius, -1, -radius), pos.offset(radius, -1, radius))) { + if (!posNear.closerThan(entity.blockPosition(), radius)) continue; + + posAbove.set(posNear.getX(), posNear.getY() + 1, posNear.getZ()); + + BlockState bLavaAbove = world.getBlockState(posAbove); + BlockState bLava = world.getBlockState(posNear); + + if (!bLavaAbove.isAir()) continue; + if (!bLava.getBlock().equals(Blocks.LAVA)) continue; + if (bLava.getValue(LiquidBlock.LEVEL) != 0) continue; + if (!bStone.canSurvive(world, posNear)) continue; + if (!world.isUnobstructed(bStone, posNear, CollisionContext.empty())) continue; + if (!CraftEventFactory.handleBlockFormEvent(world, posNear, bStone, entity)) continue; + //world.scheduleTick(posNear, Blocks.STONE, Rnd.get(60, 120)); + + Location bukkitLoc = new Location(world.getWorld(), posNear.getX(), posNear.getY(), posNear.getZ()); + blocks.add(bukkitLoc.getBlock()); + } + return blocks; + } + + @NotNull + public Item popResource(@NotNull Block block, @NotNull ItemStack item) { + Level world = ((CraftWorld)block.getWorld()).getHandle(); + BlockPos pos = ((CraftBlock)block).getPosition(); + net.minecraft.world.item.ItemStack itemstack = CraftItemStack.asNMSCopy(item); + + float yMod = EntityType.ITEM.getHeight() / 2.0F; + double x = (pos.getX() + 0.5F) + Mth.nextDouble(world.random, -0.25D, 0.25D); + double y = (pos.getY() + 0.5F) + Mth.nextDouble(world.random, -0.25D, 0.25D) - yMod; + double z = (pos.getZ() + 0.5F) + Mth.nextDouble(world.random, -0.25D, 0.25D); + + ItemEntity itemEntity = new ItemEntity(world, x, y, z, itemstack); + itemEntity.setDefaultPickUpDelay(); + return (Item) itemEntity.getBukkitEntity(); + } +} diff --git a/V1_20_R1/pom.xml b/V1_20_R1/pom.xml new file mode 100644 index 0000000..0a2254b --- /dev/null +++ b/V1_20_R1/pom.xml @@ -0,0 +1,71 @@ + + + + ExcellentEnchants + su.nightexpress.excellentenchants + 3.6.5 + + 4.0.0 + + V1_20_R1 + + + 17 + 17 + + + + + org.spigotmc + spigot + 1.20.1-R0.1-SNAPSHOT + remapped-mojang + + + su.nightexpress.excellentenchants + NMS + 3.6.5 + + + + + + + net.md-5 + specialsource-maven-plugin + 1.2.4 + + + package + + remap + + remap-obf + + org.spigotmc:minecraft-server:1.20.1-R0.1-SNAPSHOT:txt:maps-mojang + true + org.spigotmc:spigot:1.20.1-R0.1-SNAPSHOT:jar:remapped-mojang + true + remapped-obf + + + + package + + remap + + remap-spigot + + ${project.build.directory}/${project.artifactId}-${project.version}-remapped-obf.jar + org.spigotmc:minecraft-server:1.20.1-R0.1-SNAPSHOT:csrg:maps-spigot + org.spigotmc:spigot:1.20.1-R0.1-SNAPSHOT:jar:remapped-obf + + + + + + + + \ No newline at end of file diff --git a/V1_20_R1/src/main/java/su/nightexpress/excellentenchants/nms/v1_20_R1/V1_20_R1.java b/V1_20_R1/src/main/java/su/nightexpress/excellentenchants/nms/v1_20_R1/V1_20_R1.java new file mode 100644 index 0000000..fada7a4 --- /dev/null +++ b/V1_20_R1/src/main/java/su/nightexpress/excellentenchants/nms/v1_20_R1/V1_20_R1.java @@ -0,0 +1,165 @@ +package su.nightexpress.excellentenchants.nms.v1_20_R1; + +import net.minecraft.core.BlockPos; +import net.minecraft.network.protocol.game.ClientboundAnimatePacket; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.entity.projectile.FishingHook; +import net.minecraft.world.item.SpawnEggItem; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.LiquidBlock; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.shapes.CollisionContext; +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.craftbukkit.v1_20_R1.CraftWorld; +import org.bukkit.craftbukkit.v1_20_R1.block.CraftBlock; +import org.bukkit.craftbukkit.v1_20_R1.entity.CraftFishHook; +import org.bukkit.craftbukkit.v1_20_R1.entity.CraftLivingEntity; +import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_20_R1.event.CraftEventFactory; +import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftItemStack; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.FishHook; +import org.bukkit.entity.Item; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import su.nexmedia.engine.utils.Reflex; +import su.nightexpress.excellentenchants.api.enchantment.IEnchantment; +import su.nightexpress.excellentenchants.nms.EnchantNMS; + +import java.util.HashSet; +import java.util.Set; + +public class V1_20_R1 implements EnchantNMS { + + @Override + public void unfreezeRegistry() { + Reflex.setFieldValue(Enchantment.class, "acceptingNew", true); + } + + @Override + public void freezeRegistry() { + Enchantment.stopAcceptingRegistrations(); + } + + @Override + public void registerEnchantment(@NotNull IEnchantment enchantment) { + Enchantment.registerEnchantment((Enchantment) enchantment); + } + + @Override + public void sendAttackPacket(@NotNull Player player, int id) { + CraftPlayer craftPlayer = (CraftPlayer) player; + ServerPlayer entity = craftPlayer.getHandle(); + ClientboundAnimatePacket packet = new ClientboundAnimatePacket(entity, id); + craftPlayer.getHandle().connection.send(packet); + } + + @Override + public void retrieveHook(@NotNull FishHook hook, @NotNull ItemStack item) { + CraftFishHook craftFishHook = (CraftFishHook) hook; + FishingHook handle = craftFishHook.getHandle(); + handle.retrieve(CraftItemStack.asNMSCopy(item)); + } + + @Override + @Nullable + public ItemStack getSpawnEgg(@NotNull LivingEntity entity) { + CraftLivingEntity craftLivingEntity = (CraftLivingEntity) entity; + net.minecraft.world.entity.LivingEntity livingEntity = craftLivingEntity.getHandle(); + + SpawnEggItem eggItem = SpawnEggItem.byId(livingEntity.getType()); + if (eggItem == null) return null; + + return CraftItemStack.asBukkitCopy(eggItem.getDefaultInstance()); + } + + @Override + @NotNull + public Set handleFlameWalker(@NotNull LivingEntity bukkitEntity, @NotNull Location location, int level) { + Entity entity = ((CraftLivingEntity) bukkitEntity).getHandle(); + BlockPos pos = new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()); + ServerLevel world = ((CraftWorld) bukkitEntity.getWorld()).getHandle(); + + int radius = Math.min(16, 2 + level); + BlockState bStone = Blocks.MAGMA_BLOCK.defaultBlockState(); + BlockPos.MutableBlockPos posAbove = new BlockPos.MutableBlockPos(); + + Set blocks = new HashSet<>(); + for (BlockPos posNear : BlockPos.betweenClosed(pos.offset(-radius, -1, -radius), pos.offset(radius, -1, radius))) { + if (!posNear.closerThan(entity.blockPosition(), radius)) continue; + + posAbove.set(posNear.getX(), posNear.getY() + 1, posNear.getZ()); + + BlockState bLavaAbove = world.getBlockState(posAbove); + BlockState bLava = world.getBlockState(posNear); + + if (!bLavaAbove.isAir()) continue; + if (!bLava.getBlock().equals(Blocks.LAVA)) continue; + if (bLava.getValue(LiquidBlock.LEVEL) != 0) continue; + if (!bStone.canSurvive(world, posNear)) continue; + if (!world.isUnobstructed(bStone, posNear, CollisionContext.empty())) continue; + if (!CraftEventFactory.handleBlockFormEvent(world, posNear, bStone, entity)) continue; + //world.scheduleTick(posNear, Blocks.STONE, Rnd.get(60, 120)); + + Location bukkitLoc = new Location(world.getWorld(), posNear.getX(), posNear.getY(), posNear.getZ()); + blocks.add(bukkitLoc.getBlock()); + } + return blocks; + } + + @NotNull + public Item popResource(@NotNull Block block, @NotNull ItemStack item) { + Level world = ((CraftWorld)block.getWorld()).getHandle(); + BlockPos pos = ((CraftBlock)block).getPosition(); + net.minecraft.world.item.ItemStack itemstack = CraftItemStack.asNMSCopy(item); + + float yMod = EntityType.ITEM.getHeight() / 2.0F; + double x = (pos.getX() + 0.5F) + Mth.nextDouble(world.random, -0.25D, 0.25D); + double y = (pos.getY() + 0.5F) + Mth.nextDouble(world.random, -0.25D, 0.25D) - yMod; + double z = (pos.getZ() + 0.5F) + Mth.nextDouble(world.random, -0.25D, 0.25D); + + ItemEntity itemEntity = new ItemEntity(world, x, y, z, itemstack); + itemEntity.setDefaultPickUpDelay(); + return (Item) itemEntity.getBukkitEntity(); + } + + /*public static void popResourceFromFace(Level world, BlockPos blockposition, Direction enumdirection, ItemStack itemstack) { + int i = enumdirection.getStepX(); + int j = enumdirection.getStepY(); + int k = enumdirection.getStepZ(); + float f = EntityType.ITEM.getWidth() / 2.0F; + float f1 = EntityType.ITEM.getHeight() / 2.0F; + double d0 = (double)((float)blockposition.getX() + 0.5F) + (i == 0 ? Mth.nextDouble(world.random, -0.25D, 0.25D) : (double)((float)i * (0.5F + f))); + double d1 = (double)((float)blockposition.getY() + 0.5F) + (j == 0 ? Mth.nextDouble(world.random, -0.25D, 0.25D) : (double)((float)j * (0.5F + f1))) - (double)f1; + double d2 = (double)((float)blockposition.getZ() + 0.5F) + (k == 0 ? Mth.nextDouble(world.random, -0.25D, 0.25D) : (double)((float)k * (0.5F + f))); + double d3 = i == 0 ? Mth.nextDouble(world.random, -0.1D, 0.1D) : (double)i * 0.1D; + double d4 = j == 0 ? Mth.nextDouble(world.random, 0.0D, 0.1D) : (double)j * 0.1D + 0.1D; + double d5 = k == 0 ? Mth.nextDouble(world.random, -0.1D, 0.1D) : (double)k * 0.1D; + popResource(world, () -> { + return new ItemEntity(world, d0, d1, d2, itemstack, d3, d4, d5); + }, itemstack); + } + + private static void popResource(Level world, Supplier supplier, ItemStack itemstack) { + if (!world.isClientSide && !itemstack.isEmpty() && world.getGameRules().getBoolean(GameRules.RULE_DOBLOCKDROPS)) { + ItemEntity entityitem = (ItemEntity)supplier.get(); + entityitem.setDefaultPickUpDelay(); + if (world.captureDrops != null) { + world.captureDrops.add(entityitem); + } else { + world.addFreshEntity(entityitem); + } + } + + }*/ +} diff --git a/V1_20_R2/pom.xml b/V1_20_R2/pom.xml new file mode 100644 index 0000000..4ef93cd --- /dev/null +++ b/V1_20_R2/pom.xml @@ -0,0 +1,71 @@ + + + + ExcellentEnchants + su.nightexpress.excellentenchants + 3.6.5 + + 4.0.0 + + V1_20_R2 + + + 17 + 17 + + + + + org.spigotmc + spigot + 1.20.2-R0.1-SNAPSHOT + remapped-mojang + + + su.nightexpress.excellentenchants + NMS + 3.6.5 + + + + + + + net.md-5 + specialsource-maven-plugin + 1.2.4 + + + package + + remap + + remap-obf + + org.spigotmc:minecraft-server:1.20.2-R0.1-SNAPSHOT:txt:maps-mojang + true + org.spigotmc:spigot:1.20.2-R0.1-SNAPSHOT:jar:remapped-mojang + true + remapped-obf + + + + package + + remap + + remap-spigot + + ${project.build.directory}/${project.artifactId}-${project.version}-remapped-obf.jar + org.spigotmc:minecraft-server:1.20.2-R0.1-SNAPSHOT:csrg:maps-spigot + org.spigotmc:spigot:1.20.2-R0.1-SNAPSHOT:jar:remapped-obf + + + + + + + + \ No newline at end of file diff --git a/V1_20_R2/src/main/java/su/nightexpress/excellentenchants/nms/v1_20_R2/V1_20_R2.java b/V1_20_R2/src/main/java/su/nightexpress/excellentenchants/nms/v1_20_R2/V1_20_R2.java new file mode 100644 index 0000000..e925b8a --- /dev/null +++ b/V1_20_R2/src/main/java/su/nightexpress/excellentenchants/nms/v1_20_R2/V1_20_R2.java @@ -0,0 +1,155 @@ +package su.nightexpress.excellentenchants.nms.v1_20_R2; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Registry; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.network.protocol.game.ClientboundAnimatePacket; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.entity.projectile.FishingHook; +import net.minecraft.world.item.SpawnEggItem; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.LiquidBlock; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.shapes.CollisionContext; +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.craftbukkit.v1_20_R2.CraftWorld; +import org.bukkit.craftbukkit.v1_20_R2.block.CraftBlock; +import org.bukkit.craftbukkit.v1_20_R2.enchantments.CraftEnchantment; +import org.bukkit.craftbukkit.v1_20_R2.entity.CraftFishHook; +import org.bukkit.craftbukkit.v1_20_R2.entity.CraftLivingEntity; +import org.bukkit.craftbukkit.v1_20_R2.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_20_R2.event.CraftEventFactory; +import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftItemStack; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.FishHook; +import org.bukkit.entity.Item; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import su.nexmedia.engine.utils.Reflex; +import su.nightexpress.excellentenchants.api.enchantment.IEnchantment; +import su.nightexpress.excellentenchants.api.enchantment.Rarity; +import su.nightexpress.excellentenchants.nms.EnchantNMS; + +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.Set; + +public class V1_20_R2 implements EnchantNMS { + + @Override + public void unfreezeRegistry() { + Reflex.setFieldValue(BuiltInRegistries.ENCHANTMENT, "l", false); + Reflex.setFieldValue(BuiltInRegistries.ENCHANTMENT, "m", new IdentityHashMap<>()); + Reflex.setFieldValue(Enchantment.class, "acceptingNew", true); + } + + @Override + public void freezeRegistry() { + Enchantment.stopAcceptingRegistrations(); + BuiltInRegistries.ENCHANTMENT.freeze(); + } + + @Override + public void registerEnchantment(@NotNull IEnchantment enchantment) { + XEnchantment xEnchantment = new XEnchantment(enchantment, net.minecraft.world.item.enchantment.Enchantment.Rarity.COMMON); + Registry.register(BuiltInRegistries.ENCHANTMENT, enchantment.getId(), xEnchantment); + + Enchantment.registerEnchantment(new CraftEnchantment(xEnchantment)); + } + + public net.minecraft.world.item.enchantment.Enchantment.Rarity getNMSRarity(@NotNull Rarity rarity) { + return switch (rarity) { + case RARE -> net.minecraft.world.item.enchantment.Enchantment.Rarity.RARE; + case COMMON -> net.minecraft.world.item.enchantment.Enchantment.Rarity.COMMON; + case UNCOMMON -> net.minecraft.world.item.enchantment.Enchantment.Rarity.UNCOMMON; + case VERY_RARE -> net.minecraft.world.item.enchantment.Enchantment.Rarity.VERY_RARE; + }; + } + + @Override + public void sendAttackPacket(@NotNull Player player, int id) { + CraftPlayer craftPlayer = (CraftPlayer) player; + ServerPlayer entity = craftPlayer.getHandle(); + ClientboundAnimatePacket packet = new ClientboundAnimatePacket(entity, id); + craftPlayer.getHandle().connection.send(packet); + } + + @Override + public void retrieveHook(@NotNull FishHook hook, @NotNull ItemStack item) { + CraftFishHook craftFishHook = (CraftFishHook) hook; + FishingHook handle = craftFishHook.getHandle(); + handle.retrieve(CraftItemStack.asNMSCopy(item)); + } + + @Override + @Nullable + public ItemStack getSpawnEgg(@NotNull LivingEntity entity) { + CraftLivingEntity craftLivingEntity = (CraftLivingEntity) entity; + net.minecraft.world.entity.LivingEntity livingEntity = craftLivingEntity.getHandle(); + + SpawnEggItem eggItem = SpawnEggItem.byId(livingEntity.getType()); + if (eggItem == null) return null; + + return CraftItemStack.asBukkitCopy(eggItem.getDefaultInstance()); + } + + @Override + @NotNull + public Set handleFlameWalker(@NotNull LivingEntity bukkitEntity, @NotNull Location location, int level) { + Entity entity = ((CraftLivingEntity) bukkitEntity).getHandle(); + BlockPos pos = new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()); + ServerLevel world = ((CraftWorld) bukkitEntity.getWorld()).getHandle(); + + int radius = Math.min(16, 2 + level); + BlockState bStone = Blocks.MAGMA_BLOCK.defaultBlockState(); + BlockPos.MutableBlockPos posAbove = new BlockPos.MutableBlockPos(); + + Set blocks = new HashSet<>(); + for (BlockPos posNear : BlockPos.betweenClosed(pos.offset(-radius, -1, -radius), pos.offset(radius, -1, radius))) { + if (!posNear.closerThan(entity.blockPosition(), radius)) continue; + + posAbove.set(posNear.getX(), posNear.getY() + 1, posNear.getZ()); + + BlockState bLavaAbove = world.getBlockState(posAbove); + BlockState bLava = world.getBlockState(posNear); + + if (!bLavaAbove.isAir()) continue; + if (!bLava.getBlock().equals(Blocks.LAVA)) continue; + if (bLava.getValue(LiquidBlock.LEVEL) != 0) continue; + if (!bStone.canSurvive(world, posNear)) continue; + if (!world.isUnobstructed(bStone, posNear, CollisionContext.empty())) continue; + if (!CraftEventFactory.handleBlockFormEvent(world, posNear, bStone, entity)) continue; + //world.scheduleTick(posNear, Blocks.STONE, Rnd.get(60, 120)); + + Location bukkitLoc = new Location(world.getWorld(), posNear.getX(), posNear.getY(), posNear.getZ()); + blocks.add(bukkitLoc.getBlock()); + } + return blocks; + } + + @NotNull + public Item popResource(@NotNull Block block, @NotNull ItemStack item) { + Level world = ((CraftWorld)block.getWorld()).getHandle(); + BlockPos pos = ((CraftBlock)block).getPosition(); + net.minecraft.world.item.ItemStack itemstack = CraftItemStack.asNMSCopy(item); + + float yMod = EntityType.ITEM.getHeight() / 2.0F; + double x = (pos.getX() + 0.5F) + Mth.nextDouble(world.random, -0.25D, 0.25D); + double y = (pos.getY() + 0.5F) + Mth.nextDouble(world.random, -0.25D, 0.25D) - yMod; + double z = (pos.getZ() + 0.5F) + Mth.nextDouble(world.random, -0.25D, 0.25D); + + ItemEntity itemEntity = new ItemEntity(world, x, y, z, itemstack); + itemEntity.setDefaultPickUpDelay(); + return (Item) itemEntity.getBukkitEntity(); + } +} diff --git a/V1_20_R2/src/main/java/su/nightexpress/excellentenchants/nms/v1_20_R2/XEnchantment.java b/V1_20_R2/src/main/java/su/nightexpress/excellentenchants/nms/v1_20_R2/XEnchantment.java new file mode 100644 index 0000000..aad09bc --- /dev/null +++ b/V1_20_R2/src/main/java/su/nightexpress/excellentenchants/nms/v1_20_R2/XEnchantment.java @@ -0,0 +1,127 @@ +package su.nightexpress.excellentenchants.nms.v1_20_R2; + +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.enchantment.Enchantment; +import net.minecraft.world.item.enchantment.EnchantmentCategory; +import org.bukkit.NamespacedKey; +import org.bukkit.craftbukkit.v1_20_R2.CraftEquipmentSlot; +import org.bukkit.craftbukkit.v1_20_R2.inventory.CraftItemStack; +import org.bukkit.craftbukkit.v1_20_R2.util.CraftNamespacedKey; +import org.jetbrains.annotations.NotNull; +import su.nightexpress.excellentenchants.api.enchantment.IEnchantment; + +public class XEnchantment extends Enchantment { + + private final IEnchantment enchantment; + + public XEnchantment(@NotNull IEnchantment enchantment, @NotNull Rarity rarity) { + super(rarity, nmsCategory(enchantment), nmsSlots(enchantment)); + this.enchantment = enchantment; + } + + @Override + public int getMinLevel() { + return this.enchantment.getStartLevel(); + } + + public int getMaxLevel() { + return this.enchantment.getMaxLevel(); + } + + public int getMinCost(int level) { + return 1 + level * 10; // TODO + } + + public int getMaxCost(int level) { + return this.getMinCost(level) + 5; // TODO + } + + protected boolean checkCompatibility(Enchantment other) { + ResourceLocation location = BuiltInRegistries.ENCHANTMENT.getKey(other); + if (location == null) return false; + + NamespacedKey key = CraftNamespacedKey.fromMinecraft(location); + String id = key.getKey(); + + return !this.enchantment.getConflicts().contains(id); + + //return !this.enchantment.conflictsWith(bukkit); + } + + /*public Component getFullname(int level) { + return this.enchantment.getNameFormatted(level); + + MutableComponent var1 = Component.translatable(this.getDescriptionId()); + if (this.isCurse()) { + var1.withStyle(ChatFormatting.RED); + } else { + var1.withStyle(ChatFormatting.GRAY); + } + + if (level != 1 || this.getMaxLevel() != 1) { + var1.append(CommonComponents.SPACE).append(Component.translatable("enchantment.level." + level)); + } + + return var1; + }*/ + + public boolean canEnchant(ItemStack item) { + org.bukkit.inventory.ItemStack bukkitStack = CraftItemStack.asBukkitCopy(item); + if (this.enchantment.checkEnchantCategory(bukkitStack)) return true; + if (this.enchantment.checkItemCategory(bukkitStack)) return true; + + return super.canEnchant(item); + } + + public boolean isTreasureOnly() { + return this.enchantment.isTreasure(); + } + + public boolean isCurse() { + return this.enchantment.isCurse(); + } + + public boolean isTradeable() { + return this.enchantment.isTradeable(); + } + + public boolean isDiscoverable() { + return this.enchantment.isDiscoverable(); + } + + @NotNull + public static EnchantmentCategory nmsCategory(@NotNull IEnchantment enchantment) { + return switch (enchantment.getCategory()) { + case WEAPON -> EnchantmentCategory.WEAPON; + case TOOL -> EnchantmentCategory.DIGGER; + case ARMOR -> EnchantmentCategory.ARMOR; + case BOW -> EnchantmentCategory.BOW; + case TRIDENT -> EnchantmentCategory.TRIDENT; + case CROSSBOW -> EnchantmentCategory.CROSSBOW; + case WEARABLE -> EnchantmentCategory.WEARABLE; + case BREAKABLE -> EnchantmentCategory.BREAKABLE; + case ARMOR_FEET -> EnchantmentCategory.ARMOR_FEET; + case ARMOR_HEAD -> EnchantmentCategory.ARMOR_HEAD; + case ARMOR_LEGS -> EnchantmentCategory.ARMOR_LEGS; + case ARMOR_TORSO -> EnchantmentCategory.ARMOR_CHEST; + case VANISHABLE -> EnchantmentCategory.VANISHABLE; + case FISHING_ROD -> EnchantmentCategory.FISHING_ROD; + default -> throw new IllegalStateException("Unexpected value: " + enchantment.getCategory()); + }; + } + + public static EquipmentSlot[] nmsSlots(@NotNull IEnchantment enchantment) { + org.bukkit.inventory.EquipmentSlot[] slots = enchantment.getSlots(); + EquipmentSlot[] nmsSlots = new EquipmentSlot[slots.length]; + + for (int index = 0; index < nmsSlots.length; index++) { + org.bukkit.inventory.EquipmentSlot bukkitSlot = slots[index]; + nmsSlots[index] = CraftEquipmentSlot.getNMS(bukkitSlot); + } + + return nmsSlots; + } +} diff --git a/V1_20_R3/pom.xml b/V1_20_R3/pom.xml new file mode 100644 index 0000000..b9d2137 --- /dev/null +++ b/V1_20_R3/pom.xml @@ -0,0 +1,76 @@ + + + + ExcellentEnchants + su.nightexpress.excellentenchants + 3.6.5 + + 4.0.0 + + V1_20_R3 + + + 17 + 17 + + + + + org.spigotmc + spigot + 1.20.4-R0.1-SNAPSHOT + remapped-mojang + + + su.nightexpress.excellentenchants + API + 3.6.5 + + + su.nightexpress.excellentenchants + NMS + 3.6.5 + + + + + + + net.md-5 + specialsource-maven-plugin + 1.2.4 + + + package + + remap + + remap-obf + + org.spigotmc:minecraft-server:1.20.4-R0.1-SNAPSHOT:txt:maps-mojang + true + org.spigotmc:spigot:1.20.4-R0.1-SNAPSHOT:jar:remapped-mojang + true + remapped-obf + + + + package + + remap + + remap-spigot + + ${project.build.directory}/${project.artifactId}-${project.version}-remapped-obf.jar + org.spigotmc:minecraft-server:1.20.4-R0.1-SNAPSHOT:csrg:maps-spigot + org.spigotmc:spigot:1.20.4-R0.1-SNAPSHOT:jar:remapped-obf + + + + + + + + \ No newline at end of file diff --git a/V1_20_R3/src/main/java/su/nightexpress/excellentenchants/nms/v1_20_R3/V1_20_R3.java b/V1_20_R3/src/main/java/su/nightexpress/excellentenchants/nms/v1_20_R3/V1_20_R3.java new file mode 100644 index 0000000..be294fb --- /dev/null +++ b/V1_20_R3/src/main/java/su/nightexpress/excellentenchants/nms/v1_20_R3/V1_20_R3.java @@ -0,0 +1,148 @@ +package su.nightexpress.excellentenchants.nms.v1_20_R3; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Registry; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.network.protocol.game.ClientboundAnimatePacket; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.entity.projectile.FishingHook; +import net.minecraft.world.item.SpawnEggItem; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.block.LiquidBlock; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.shapes.CollisionContext; +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.craftbukkit.v1_20_R3.CraftWorld; +import org.bukkit.craftbukkit.v1_20_R3.block.CraftBlock; +import org.bukkit.craftbukkit.v1_20_R3.entity.CraftFishHook; +import org.bukkit.craftbukkit.v1_20_R3.entity.CraftLivingEntity; +import org.bukkit.craftbukkit.v1_20_R3.entity.CraftPlayer; +import org.bukkit.craftbukkit.v1_20_R3.event.CraftEventFactory; +import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftItemStack; +import org.bukkit.entity.FishHook; +import org.bukkit.entity.Item; +import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import su.nexmedia.engine.utils.Reflex; +import su.nightexpress.excellentenchants.api.enchantment.IEnchantment; +import su.nightexpress.excellentenchants.api.enchantment.Rarity; +import su.nightexpress.excellentenchants.nms.EnchantNMS; + +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.Set; + +public class V1_20_R3 implements EnchantNMS { + + @Override + public void unfreezeRegistry() { + Reflex.setFieldValue(BuiltInRegistries.ENCHANTMENT, "l", false); + Reflex.setFieldValue(BuiltInRegistries.ENCHANTMENT, "m", new IdentityHashMap<>()); + } + + @Override + public void freezeRegistry() { + BuiltInRegistries.ENCHANTMENT.freeze(); + } + + public void registerEnchantment(@NotNull IEnchantment enchantment) { + XEnchantment xEnchantment = new XEnchantment(enchantment, getNMSRarity(Rarity.COMMON)); + Registry.register(BuiltInRegistries.ENCHANTMENT, enchantment.getId(), xEnchantment); + } + + public net.minecraft.world.item.enchantment.Enchantment.Rarity getNMSRarity(@NotNull Rarity rarity) { + return switch (rarity) { + case RARE -> net.minecraft.world.item.enchantment.Enchantment.Rarity.RARE; + case COMMON -> net.minecraft.world.item.enchantment.Enchantment.Rarity.COMMON; + case UNCOMMON -> net.minecraft.world.item.enchantment.Enchantment.Rarity.UNCOMMON; + case VERY_RARE -> net.minecraft.world.item.enchantment.Enchantment.Rarity.VERY_RARE; + }; + } + + @Override + public void sendAttackPacket(@NotNull Player player, int id) { + CraftPlayer craftPlayer = (CraftPlayer) player; + ServerPlayer entity = craftPlayer.getHandle(); + ClientboundAnimatePacket packet = new ClientboundAnimatePacket(entity, id); + craftPlayer.getHandle().connection.send(packet); + } + + @Override + public void retrieveHook(@NotNull FishHook hook, @NotNull ItemStack item) { + CraftFishHook craftFishHook = (CraftFishHook) hook; + FishingHook handle = craftFishHook.getHandle(); + handle.retrieve(CraftItemStack.asNMSCopy(item)); + } + + @Override + @Nullable + public ItemStack getSpawnEgg(@NotNull LivingEntity entity) { + CraftLivingEntity craftLivingEntity = (CraftLivingEntity) entity; + net.minecraft.world.entity.LivingEntity livingEntity = craftLivingEntity.getHandle(); + + SpawnEggItem eggItem = SpawnEggItem.byId(livingEntity.getType()); + if (eggItem == null) return null; + + return CraftItemStack.asBukkitCopy(eggItem.getDefaultInstance()); + } + + @Override + @NotNull + public Set handleFlameWalker(@NotNull LivingEntity bukkitEntity, @NotNull Location location, int level) { + Entity entity = ((CraftLivingEntity) bukkitEntity).getHandle(); + BlockPos pos = new BlockPos(location.getBlockX(), location.getBlockY(), location.getBlockZ()); + ServerLevel world = ((CraftWorld) bukkitEntity.getWorld()).getHandle(); + + int radius = Math.min(16, 2 + level); + BlockState bStone = Blocks.MAGMA_BLOCK.defaultBlockState(); + BlockPos.MutableBlockPos posAbove = new BlockPos.MutableBlockPos(); + + Set blocks = new HashSet<>(); + for (BlockPos posNear : BlockPos.betweenClosed(pos.offset(-radius, -1, -radius), pos.offset(radius, -1, radius))) { + if (!posNear.closerThan(entity.blockPosition(), radius)) continue; + + posAbove.set(posNear.getX(), posNear.getY() + 1, posNear.getZ()); + + BlockState bLavaAbove = world.getBlockState(posAbove); + BlockState bLava = world.getBlockState(posNear); + + if (!bLavaAbove.isAir()) continue; + if (!bLava.getBlock().equals(Blocks.LAVA)) continue; + if (bLava.getValue(LiquidBlock.LEVEL) != 0) continue; + if (!bStone.canSurvive(world, posNear)) continue; + if (!world.isUnobstructed(bStone, posNear, CollisionContext.empty())) continue; + if (!CraftEventFactory.handleBlockFormEvent(world, posNear, bStone, entity)) continue; + //world.scheduleTick(posNear, Blocks.STONE, Rnd.get(60, 120)); + + Location bukkitLoc = new Location(world.getWorld(), posNear.getX(), posNear.getY(), posNear.getZ()); + blocks.add(bukkitLoc.getBlock()); + } + return blocks; + } + + @NotNull + public Item popResource(@NotNull Block block, @NotNull ItemStack item) { + Level world = ((CraftWorld)block.getWorld()).getHandle(); + BlockPos pos = ((CraftBlock)block).getPosition(); + net.minecraft.world.item.ItemStack itemstack = CraftItemStack.asNMSCopy(item); + + float yMod = EntityType.ITEM.getHeight() / 2.0F; + double x = (pos.getX() + 0.5F) + Mth.nextDouble(world.random, -0.25D, 0.25D); + double y = (pos.getY() + 0.5F) + Mth.nextDouble(world.random, -0.25D, 0.25D) - yMod; + double z = (pos.getZ() + 0.5F) + Mth.nextDouble(world.random, -0.25D, 0.25D); + + ItemEntity itemEntity = new ItemEntity(world, x, y, z, itemstack); + itemEntity.setDefaultPickUpDelay(); + return (Item) itemEntity.getBukkitEntity(); + } +} diff --git a/V1_20_R3/src/main/java/su/nightexpress/excellentenchants/nms/v1_20_R3/XEnchantment.java b/V1_20_R3/src/main/java/su/nightexpress/excellentenchants/nms/v1_20_R3/XEnchantment.java new file mode 100644 index 0000000..7431f33 --- /dev/null +++ b/V1_20_R3/src/main/java/su/nightexpress/excellentenchants/nms/v1_20_R3/XEnchantment.java @@ -0,0 +1,115 @@ +package su.nightexpress.excellentenchants.nms.v1_20_R3; + +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.EquipmentSlot; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.enchantment.Enchantment; +import net.minecraft.world.item.enchantment.EnchantmentCategory; +import org.bukkit.NamespacedKey; +import org.bukkit.craftbukkit.v1_20_R3.CraftEquipmentSlot; +import org.bukkit.craftbukkit.v1_20_R3.inventory.CraftItemStack; +import org.bukkit.craftbukkit.v1_20_R3.util.CraftNamespacedKey; +import org.jetbrains.annotations.NotNull; +import su.nightexpress.excellentenchants.api.enchantment.IEnchantment; + +public class XEnchantment extends Enchantment { + + private final IEnchantment enchantment; + + public XEnchantment(@NotNull IEnchantment enchantment, @NotNull Rarity rarity) { + super(rarity, nmsCategory(enchantment), nmsSlots(enchantment)); + this.enchantment = enchantment; + } + + @Override + public int getMinLevel() { + return this.enchantment.getStartLevel(); + } + + public int getMaxLevel() { + return this.enchantment.getMaxLevel(); + } + + public int getMinCost(int level) { + return 1 + level * 10; // TODO + } + + public int getMaxCost(int level) { + return this.getMinCost(level) + 5; // TODO + } + + protected boolean checkCompatibility(Enchantment other) { + ResourceLocation location = BuiltInRegistries.ENCHANTMENT.getKey(other); + if (location == null) return false; + + NamespacedKey key = CraftNamespacedKey.fromMinecraft(location); + String id = key.getKey(); + + return !this.enchantment.getConflicts().contains(id); + + //return !this.enchantment.conflictsWith(bukkit); + } + + public boolean canEnchant(ItemStack item) { + org.bukkit.inventory.ItemStack bukkitStack = CraftItemStack.asBukkitCopy(item); + if (this.enchantment.checkEnchantCategory(bukkitStack)) return true; + if (this.enchantment.checkItemCategory(bukkitStack)) return true; + + return super.canEnchant(item); + + /*if (!this.enchantment.checkEnchantCategory(CraftItemStack.asBukkitCopy(item))) { + return false; + } + return super.canEnchant(item);*/ + } + + public boolean isTreasureOnly() { + return this.enchantment.isTreasure() || this.isCurse(); + } + + public boolean isCurse() { + return this.enchantment.isCurse(); + } + + public boolean isTradeable() { + return this.enchantment.isTradeable(); + } + + public boolean isDiscoverable() { + return this.enchantment.isDiscoverable(); + } + + @NotNull + public static EnchantmentCategory nmsCategory(@NotNull IEnchantment enchantment) { + return switch (enchantment.getCategory()) { + case WEAPON -> EnchantmentCategory.WEAPON; + case TOOL -> EnchantmentCategory.DIGGER; + case ARMOR -> EnchantmentCategory.ARMOR; + case BOW -> EnchantmentCategory.BOW; + case TRIDENT -> EnchantmentCategory.TRIDENT; + case CROSSBOW -> EnchantmentCategory.CROSSBOW; + case WEARABLE -> EnchantmentCategory.WEARABLE; + case BREAKABLE -> EnchantmentCategory.BREAKABLE; + case ARMOR_FEET -> EnchantmentCategory.ARMOR_FEET; + case ARMOR_HEAD -> EnchantmentCategory.ARMOR_HEAD; + case ARMOR_LEGS -> EnchantmentCategory.ARMOR_LEGS; + case ARMOR_TORSO -> EnchantmentCategory.ARMOR_CHEST; + case VANISHABLE -> EnchantmentCategory.VANISHABLE; + case FISHING_ROD -> EnchantmentCategory.FISHING_ROD; + default -> throw new IllegalStateException("Unexpected value: " + enchantment.getCategory()); + }; + } + + public static EquipmentSlot[] nmsSlots(@NotNull IEnchantment enchantment) { + org.bukkit.inventory.EquipmentSlot[] slots = enchantment.getSlots(); + EquipmentSlot[] nmsSlots = new EquipmentSlot[slots.length]; + + for (int index = 0; index < nmsSlots.length; index++) { + org.bukkit.inventory.EquipmentSlot bukkitSlot = slots[index]; + nmsSlots[index] = CraftEquipmentSlot.getNMS(bukkitSlot); + } + + return nmsSlots; + } +} diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..6b68f48 --- /dev/null +++ b/pom.xml @@ -0,0 +1,37 @@ + + + 4.0.0 + + su.nightexpress.excellentenchants + ExcellentEnchants + pom + 3.6.5 + + Core + NMS + V1_18_R2 + V1_19_R3 + V1_20_R1 + V1_20_R2 + V1_20_R3 + API + + + + 16 + 16 + UTF-8 + UTF-8 + + + + + su.nexmedia + NexEngine + 2.2.12 + + + + \ No newline at end of file