package com.gmail.nossr50.util.skills; import com.gmail.nossr50.config.HiddenConfig; import com.gmail.nossr50.datatypes.experience.XPGainReason; import com.gmail.nossr50.datatypes.experience.XPGainSource; import com.gmail.nossr50.datatypes.interactions.NotificationType; import com.gmail.nossr50.datatypes.player.McMMOPlayer; import com.gmail.nossr50.datatypes.skills.PrimarySkillType; import com.gmail.nossr50.datatypes.skills.SubSkillType; import com.gmail.nossr50.datatypes.skills.SuperAbilityType; import com.gmail.nossr50.locale.LocaleLoader; import com.gmail.nossr50.mcMMO; import com.gmail.nossr50.util.ItemMetadataUtils; import com.gmail.nossr50.util.ItemUtils; import com.gmail.nossr50.util.Misc; import com.gmail.nossr50.util.Permissions; import com.gmail.nossr50.util.player.NotificationManager; import com.gmail.nossr50.util.player.UserManager; import com.gmail.nossr50.util.text.StringUtils; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.Recipe; import org.bukkit.inventory.ShapedRecipe; import org.bukkit.inventory.ShapelessRecipe; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.potion.PotionEffect; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Iterator; import static com.gmail.nossr50.util.ItemMetadataUtils.*; import static com.gmail.nossr50.util.PotionEffectUtil.getHastePotionEffectType; public final class SkillUtils { /** * This is a static utility class, therefore we don't want any instances of * this class. Making the constructor private prevents accidents like that. */ private SkillUtils() {} public static void applyXpGain(McMMOPlayer mcMMOPlayer, PrimarySkillType skill, float xp, XPGainReason xpGainReason) { mcMMOPlayer.beginXpGain(skill, xp, xpGainReason, XPGainSource.SELF); } public static void applyXpGain(McMMOPlayer mcMMOPlayer, PrimarySkillType skill, float xp, XPGainReason xpGainReason, XPGainSource xpGainSource) { mcMMOPlayer.beginXpGain(skill, xp, xpGainReason, xpGainSource); } /* * Skill Stat Calculations */ public static String[] calculateLengthDisplayValues(Player player, float skillValue, PrimarySkillType skill) { int maxLength = mcMMO.p.getSkillTools().getSuperAbilityMaxLength(mcMMO.p.getSkillTools().getSuperAbility(skill)); int abilityLengthVar = mcMMO.p.getAdvancedConfig().getAbilityLength(); int abilityLengthCap = mcMMO.p.getAdvancedConfig().getAbilityLengthCap(); int length; if (abilityLengthCap > 0) { length = (int) Math.min(abilityLengthCap, 2 + (skillValue / abilityLengthVar)); } else { length = 2 + (int) (skillValue / abilityLengthVar); } int enduranceLength = PerksUtils.handleActivationPerks(player, length, maxLength); if (maxLength != 0) { length = Math.min(length, maxLength); } return new String[] { String.valueOf(length), String.valueOf(enduranceLength) }; } /* * Others */ public static int handleFoodSkills(Player player, int eventFoodLevel, SubSkillType subSkillType) { int curRank = RankUtils.getRank(player, subSkillType); int currentFoodLevel = player.getFoodLevel(); int foodChange = eventFoodLevel - currentFoodLevel; foodChange+=curRank; return currentFoodLevel + foodChange; } /** * Calculate the time remaining until the cooldown expires. * * @param deactivatedTimeStamp Time of deactivation * @param cooldown The length of the cooldown * @param player The Player to check for cooldown perks * * @return the number of seconds remaining before the cooldown expires */ public static int calculateTimeLeft(long deactivatedTimeStamp, int cooldown, Player player) { return (int) (((deactivatedTimeStamp + (PerksUtils.handleCooldownPerks(player, cooldown) * Misc.TIME_CONVERSION_FACTOR)) - System.currentTimeMillis()) / Misc.TIME_CONVERSION_FACTOR); } /** * Check if the cooldown has expired. * This does NOT account for cooldown perks! * * @param deactivatedTimeStamp Time of deactivation in seconds * @param cooldown The length of the cooldown in seconds * * @return true if the cooldown is expired */ public static boolean cooldownExpired(long deactivatedTimeStamp, int cooldown) { return System.currentTimeMillis() >= (deactivatedTimeStamp + cooldown) * Misc.TIME_CONVERSION_FACTOR; } /** * Checks if the given string represents a valid skill * * @param skillName The name of the skill to check * @return true if this is a valid skill, false otherwise */ public static boolean isSkill(String skillName) { return mcMMO.p.getGeneralConfig().getLocale().equalsIgnoreCase("en_US") ? mcMMO.p.getSkillTools().matchSkill(skillName) != null : isLocalizedSkill(skillName); } public static void sendSkillMessage(Player player, NotificationType notificationType, String key) { Location location = player.getLocation(); for (Player otherPlayer : player.getWorld().getPlayers()) { if (otherPlayer != player && Misc.isNear(location, otherPlayer.getLocation(), Misc.SKILL_MESSAGE_MAX_SENDING_DISTANCE)) { NotificationManager.sendNearbyPlayersInformation(otherPlayer, notificationType, key, player.getName()); } } } public static void handleAbilitySpeedIncrease(Player player) { if (HiddenConfig.getInstance().useEnchantmentBuffs()) { ItemStack heldItem = player.getInventory().getItemInMainHand(); if (heldItem == null) return; if (!ItemUtils.canBeSuperAbilityDigBoosted(heldItem)) { return; } int originalDigSpeed = heldItem.getEnchantmentLevel(mcMMO.p.getEnchantmentMapper().getEfficiency()); ItemUtils.addDigSpeedToItem(heldItem, heldItem.getEnchantmentLevel(mcMMO.p.getEnchantmentMapper().getEfficiency())); //1.13.2+ will have persistent metadata for this item ItemMetadataUtils.setSuperAbilityBoostedItem(heldItem, originalDigSpeed); } else { int duration = 0; int amplifier = 0; if (player.hasPotionEffect(getHastePotionEffectType())) { for (PotionEffect effect : player.getActivePotionEffects()) { if (effect.getType() == getHastePotionEffectType()) { duration = effect.getDuration(); amplifier = effect.getAmplifier(); break; } } } McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player); //Not Loaded if (mcMMOPlayer == null) return; PrimarySkillType skill = mcMMOPlayer.getAbilityMode(SuperAbilityType.SUPER_BREAKER) ? PrimarySkillType.MINING : PrimarySkillType.EXCAVATION; int abilityLengthVar = mcMMO.p.getAdvancedConfig().getAbilityLength(); int abilityLengthCap = mcMMO.p.getAdvancedConfig().getAbilityLengthCap(); int ticks; if (abilityLengthCap > 0) { ticks = PerksUtils.handleActivationPerks(player, Math.min(abilityLengthCap, 2 + (mcMMOPlayer.getSkillLevel(skill) / abilityLengthVar)), mcMMO.p.getSkillTools().getSuperAbilityMaxLength(mcMMO.p.getSkillTools().getSuperAbility(skill))) * Misc.TICK_CONVERSION_FACTOR; } else { ticks = PerksUtils.handleActivationPerks(player, 2 + ((mcMMOPlayer.getSkillLevel(skill)) / abilityLengthVar), mcMMO.p.getSkillTools().getSuperAbilityMaxLength(mcMMO.p.getSkillTools().getSuperAbility(skill))) * Misc.TICK_CONVERSION_FACTOR; } PotionEffect abilityBuff = new PotionEffect(getHastePotionEffectType(), duration + ticks, amplifier + 10); player.addPotionEffect(abilityBuff, true); } } public static void removeAbilityBoostsFromInventory(@NotNull Player player) { for (ItemStack itemStack : player.getInventory().getContents()) { removeAbilityBuff(itemStack); } } public static void removeAbilityBuff(@Nullable ItemStack itemStack) { if (itemStack == null) return; if (!ItemUtils.canBeSuperAbilityDigBoosted(itemStack)) return; //1.13.2+ will have persistent metadata for this itemStack if (isLegacyAbilityTool(itemStack)) { ItemMeta itemMeta = itemStack.getItemMeta(); if (itemMeta != null) { // This is safe to call without prior checks. itemMeta.removeEnchant(mcMMO.p.getEnchantmentMapper().getEfficiency()); itemStack.setItemMeta(itemMeta); ItemUtils.removeAbilityLore(itemStack); } } if (isSuperAbilityBoosted(itemStack)) { removeBonusDigSpeedOnSuperAbilityTool(itemStack); } } public static void handleDurabilityChange(ItemStack itemStack, int durabilityModifier) { handleDurabilityChange(itemStack, durabilityModifier, 1.0); } /** * Modify the durability of an ItemStack, using Tools specific formula for unbreaking enchant damage reduction * * @param itemStack The ItemStack which durability should be modified * @param durabilityModifier the amount to modify the durability by * @param maxDamageModifier the amount to adjust the max damage by */ public static void handleDurabilityChange(ItemStack itemStack, double durabilityModifier, double maxDamageModifier) { if (itemStack.hasItemMeta() && itemStack.getItemMeta().isUnbreakable()) { return; } Material type = itemStack.getType(); short maxDurability = mcMMO.getRepairableManager().isRepairable(type) ? mcMMO.getRepairableManager().getRepairable(type).getMaximumDurability() : type.getMaxDurability(); durabilityModifier = (int) Math.min(durabilityModifier / (itemStack.getEnchantmentLevel(mcMMO.p.getEnchantmentMapper().getUnbreaking()) + 1), maxDurability * maxDamageModifier); itemStack.setDurability((short) Math.min(itemStack.getDurability() + durabilityModifier, maxDurability)); } private static boolean isLocalizedSkill(String skillName) { for (PrimarySkillType skill : PrimarySkillType.values()) { if (skillName.equalsIgnoreCase(LocaleLoader.getString(StringUtils.getCapitalized(skill.toString()) + ".SkillName"))) { return true; } } return false; } /** * Modify the durability of an ItemStack, using Armor specific formula for unbreaking enchant damage reduction * * @param itemStack The ItemStack which durability should be modified * @param durabilityModifier the amount to modify the durability by * @param maxDamageModifier the amount to adjust the max damage by */ public static void handleArmorDurabilityChange(ItemStack itemStack, double durabilityModifier, double maxDamageModifier) { if (itemStack.hasItemMeta() && itemStack.getItemMeta().isUnbreakable()) { return; } Material type = itemStack.getType(); short maxDurability = mcMMO.getRepairableManager().isRepairable(type) ? mcMMO.getRepairableManager().getRepairable(type).getMaximumDurability() : type.getMaxDurability(); durabilityModifier = (int) Math.min(durabilityModifier * (0.6 + 0.4/ (itemStack.getEnchantmentLevel(mcMMO.p.getEnchantmentMapper().getUnbreaking()) + 1)), maxDurability * maxDamageModifier); itemStack.setDurability((short) Math.min(itemStack.getDurability() + durabilityModifier, maxDurability)); } @Nullable public static Material getRepairAndSalvageItem(@NotNull ItemStack inHand) { if (ItemUtils.isPrismarineTool(inHand)) { return Material.PRISMARINE_CRYSTALS; } else if (ItemUtils.isNetheriteTool(inHand) || ItemUtils.isNetheriteArmor(inHand)) { return Material.NETHERITE_SCRAP; } else if (ItemUtils.isDiamondTool(inHand) || ItemUtils.isDiamondArmor(inHand)) { return Material.DIAMOND; } else if (ItemUtils.isGoldTool(inHand) || ItemUtils.isGoldArmor(inHand)) { return Material.GOLD_INGOT; } else if (ItemUtils.isIronTool(inHand) || ItemUtils.isIronArmor(inHand)) { return Material.IRON_INGOT; } else if (ItemUtils.isStoneTool(inHand)) { return Material.COBBLESTONE; } else if (ItemUtils.isWoodTool(inHand)) { return Material.OAK_WOOD; } else if (ItemUtils.isLeatherArmor(inHand)) { return Material.LEATHER; } else if (ItemUtils.isStringTool(inHand)) { return Material.STRING; } else { return null; } } public static int getRepairAndSalvageQuantities(ItemStack item) { return getRepairAndSalvageQuantities(item.getType(), getRepairAndSalvageItem(item)); } public static int getRepairAndSalvageQuantities(Material itemMaterial, Material recipeMaterial) { int quantity = 0; if (mcMMO.getMaterialMapStore().isPrismarineTool(itemMaterial)) { return 16; } if (mcMMO.getMaterialMapStore().isNetheriteTool(itemMaterial) || mcMMO.getMaterialMapStore().isNetheriteArmor(itemMaterial)) { //One netherite bar requires 4 netherite scraps return 4; } for(Iterator recipeIterator = Bukkit.getServer().recipeIterator(); recipeIterator.hasNext();) { Recipe bukkitRecipe = recipeIterator.next(); if (bukkitRecipe.getResult().getType() != itemMaterial) continue; if (bukkitRecipe instanceof ShapelessRecipe) { for (ItemStack ingredient : ((ShapelessRecipe) bukkitRecipe).getIngredientList()) { if (ingredient != null && (recipeMaterial == null || ingredient.getType() == recipeMaterial) && (ingredient.getType() == recipeMaterial)) { quantity += ingredient.getAmount(); } } } else if (bukkitRecipe instanceof ShapedRecipe) { for (ItemStack ingredient : ((ShapedRecipe) bukkitRecipe).getIngredientMap().values()) { if (ingredient != null && (recipeMaterial == null || ingredient.getType() == recipeMaterial) && (ingredient.getType() == recipeMaterial)) { quantity += ingredient.getAmount(); } } } } return quantity; } /** * Checks if a player can use a skill * @param player target player * @param subSkillType target subskill * @return true if the player has permission and has the skill unlocked */ public static boolean canUseSubskill(Player player, @NotNull SubSkillType subSkillType) { return Permissions.isSubSkillEnabled(player, subSkillType) && RankUtils.hasUnlockedSubskill(player, subSkillType); } }