mirror of
				https://github.com/mcMMO-Dev/mcMMO.git
				synced 2025-11-04 11:03:43 +01:00 
			
		
		
		
	Fixed possible NPE in our events, and BrewingStands now remember who last interacted with them, this allows for hoppers to be used with Alchemy Fixes #5004 Fixes #4958 Fixes #4641
This commit is contained in:
		@@ -2,11 +2,13 @@ package com.gmail.nossr50.util;
 | 
			
		||||
 | 
			
		||||
import com.gmail.nossr50.config.experience.ExperienceConfig;
 | 
			
		||||
import com.gmail.nossr50.datatypes.meta.BonusDropMeta;
 | 
			
		||||
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.mcMMO;
 | 
			
		||||
import com.gmail.nossr50.skills.repair.Repair;
 | 
			
		||||
import com.gmail.nossr50.skills.salvage.Salvage;
 | 
			
		||||
import com.gmail.nossr50.util.player.UserManager;
 | 
			
		||||
import com.gmail.nossr50.util.random.ProbabilityUtil;
 | 
			
		||||
import org.bukkit.Material;
 | 
			
		||||
import org.bukkit.World;
 | 
			
		||||
@@ -16,9 +18,12 @@ import org.bukkit.block.data.Ageable;
 | 
			
		||||
import org.bukkit.block.data.BlockData;
 | 
			
		||||
import org.bukkit.entity.Player;
 | 
			
		||||
import org.jetbrains.annotations.NotNull;
 | 
			
		||||
import org.jetbrains.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
import java.util.HashSet;
 | 
			
		||||
 | 
			
		||||
import static java.util.Objects.requireNonNull;
 | 
			
		||||
 | 
			
		||||
public final class BlockUtils {
 | 
			
		||||
 | 
			
		||||
    public static final String SHORT_GRASS = "SHORT_GRASS";
 | 
			
		||||
@@ -94,10 +99,28 @@ public final class BlockUtils {
 | 
			
		||||
     *
 | 
			
		||||
     * @param blockState the blockstate
 | 
			
		||||
     * @return true if the player succeeded in the check
 | 
			
		||||
     * @deprecated Use {@link #checkDoubleDrops(McMMOPlayer, BlockState, SubSkillType)} instead
 | 
			
		||||
     */
 | 
			
		||||
    public static boolean checkDoubleDrops(Player player, BlockState blockState, PrimarySkillType skillType, SubSkillType subSkillType) {
 | 
			
		||||
        if (mcMMO.p.getGeneralConfig().getDoubleDropsEnabled(skillType, blockState.getType()) && Permissions.isSubSkillEnabled(player, subSkillType)) {
 | 
			
		||||
            return ProbabilityUtil.isSkillRNGSuccessful(subSkillType, player);
 | 
			
		||||
    @Deprecated(forRemoval = true, since = "2.2.010")
 | 
			
		||||
    public static boolean checkDoubleDrops(Player player, BlockState blockState, PrimarySkillType ignored, SubSkillType subSkillType) {
 | 
			
		||||
        return checkDoubleDrops(UserManager.getPlayer(player), blockState, subSkillType);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Checks if a player successfully passed the double drop check
 | 
			
		||||
     *
 | 
			
		||||
     * @param mmoPlayer    the player involved in the check
 | 
			
		||||
     * @param blockState   the blockstate of the block
 | 
			
		||||
     * @param subSkillType the subskill involved
 | 
			
		||||
     * @return true if the player succeeded in the check
 | 
			
		||||
     */
 | 
			
		||||
    public static boolean checkDoubleDrops(@Nullable McMMOPlayer mmoPlayer, @NotNull BlockState blockState,
 | 
			
		||||
                                           @NotNull SubSkillType subSkillType) {
 | 
			
		||||
        requireNonNull(blockState, "blockState cannot be null");
 | 
			
		||||
        requireNonNull(subSkillType, "subSkillType cannot be null");
 | 
			
		||||
        if (mcMMO.p.getGeneralConfig().getDoubleDropsEnabled(subSkillType.getParentSkill(), blockState.getType())
 | 
			
		||||
                && Permissions.isSubSkillEnabled(mmoPlayer, subSkillType)) {
 | 
			
		||||
            return ProbabilityUtil.isSkillRNGSuccessful(subSkillType, mmoPlayer);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										116
									
								
								src/main/java/com/gmail/nossr50/util/ContainerMetadataUtils.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								src/main/java/com/gmail/nossr50/util/ContainerMetadataUtils.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,116 @@
 | 
			
		||||
package com.gmail.nossr50.util;
 | 
			
		||||
 | 
			
		||||
import com.gmail.nossr50.datatypes.player.McMMOPlayer;
 | 
			
		||||
import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
 | 
			
		||||
import com.gmail.nossr50.mcMMO;
 | 
			
		||||
import com.gmail.nossr50.util.player.UserManager;
 | 
			
		||||
import org.bukkit.Bukkit;
 | 
			
		||||
import org.bukkit.ChatColor;
 | 
			
		||||
import org.bukkit.OfflinePlayer;
 | 
			
		||||
import org.bukkit.block.BlockState;
 | 
			
		||||
import org.bukkit.entity.Player;
 | 
			
		||||
import org.bukkit.persistence.PersistentDataContainer;
 | 
			
		||||
import org.bukkit.persistence.PersistentDataHolder;
 | 
			
		||||
import org.bukkit.persistence.PersistentDataType;
 | 
			
		||||
import org.jetbrains.annotations.NotNull;
 | 
			
		||||
import org.jetbrains.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
 | 
			
		||||
import static com.gmail.nossr50.util.MetadataService.NSK_CONTAINER_UUID_LEAST_SIG;
 | 
			
		||||
import static com.gmail.nossr50.util.MetadataService.NSK_CONTAINER_UUID_MOST_SIG;
 | 
			
		||||
import static java.util.Objects.requireNonNull;
 | 
			
		||||
 | 
			
		||||
public class ContainerMetadataUtils {
 | 
			
		||||
 | 
			
		||||
    public static void changeContainerOwnership(@NotNull BlockState blockState, @NotNull Player player) {
 | 
			
		||||
        requireNonNull(blockState, "blockState cannot be null");
 | 
			
		||||
        requireNonNull(player, "Player cannot be null");
 | 
			
		||||
 | 
			
		||||
        final McMMOPlayer mmoPlayer = UserManager.getPlayer(player);
 | 
			
		||||
 | 
			
		||||
        /*
 | 
			
		||||
            Debug output
 | 
			
		||||
         */
 | 
			
		||||
        printOwnershipGainDebug(blockState, mmoPlayer);
 | 
			
		||||
        printOwnershipLossDebug(blockState);
 | 
			
		||||
        setOwner(blockState, player.getUniqueId());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void printOwnershipGainDebug(@NotNull BlockState blockState, @Nullable McMMOPlayer mmoPlayer) {
 | 
			
		||||
        if(mmoPlayer != null && mmoPlayer.isDebugMode()) {
 | 
			
		||||
            mmoPlayer.getPlayer().sendMessage("Container ownership " +
 | 
			
		||||
                    ChatColor.GREEN +"gained " + ChatColor.RESET +
 | 
			
		||||
                    "at location: " + blockState.getLocation().toString());
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void printOwnershipLossDebug(BlockState blockState) {
 | 
			
		||||
        OfflinePlayer containerOwner = getContainerOwner(blockState);
 | 
			
		||||
 | 
			
		||||
        if(containerOwner != null && containerOwner.isOnline()) {
 | 
			
		||||
            final McMMOPlayer mmoContainerOwner = UserManager.getPlayer(containerOwner.getPlayer());
 | 
			
		||||
 | 
			
		||||
            if(mmoContainerOwner != null) {
 | 
			
		||||
                if(mmoContainerOwner.isDebugMode()) {
 | 
			
		||||
                    mmoContainerOwner.getPlayer().sendMessage("Container ownership " +
 | 
			
		||||
                            ChatColor.RED + "lost " + ChatColor.RESET +
 | 
			
		||||
                            "at location: " + blockState.getLocation().toString());
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static @Nullable OfflinePlayer getContainerOwner(BlockState container) {
 | 
			
		||||
        if (container instanceof PersistentDataHolder persistentDataHolder) {
 | 
			
		||||
            final UUID uuid = getOwner(persistentDataHolder);
 | 
			
		||||
 | 
			
		||||
            if(uuid != null) {
 | 
			
		||||
                return Bukkit.getOfflinePlayer(uuid);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public static boolean isContainerOwned(BlockState blockState) {
 | 
			
		||||
        return getContainerOwner(blockState) != null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void processContainerOwnership(BlockState blockState, Player player) {
 | 
			
		||||
        if(!mcMMO.p.getSkillTools().doesPlayerHaveSkillPermission(player, PrimarySkillType.SMELTING))
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        if(getContainerOwner(blockState) != null) {
 | 
			
		||||
            if(getContainerOwner(blockState).getUniqueId().equals(player.getUniqueId()))
 | 
			
		||||
                return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        changeContainerOwnership(blockState, player);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static @Nullable UUID getOwner(@NotNull PersistentDataHolder persistentDataHolder) {
 | 
			
		||||
        //Get container from entity
 | 
			
		||||
        final PersistentDataContainer dataContainer = persistentDataHolder.getPersistentDataContainer();
 | 
			
		||||
 | 
			
		||||
        //Too lazy to make a custom data type for this stuff
 | 
			
		||||
        final Long mostSigBits = dataContainer.get(NSK_CONTAINER_UUID_MOST_SIG, PersistentDataType.LONG);
 | 
			
		||||
        final Long leastSigBits = dataContainer.get(NSK_CONTAINER_UUID_LEAST_SIG, PersistentDataType.LONG);
 | 
			
		||||
 | 
			
		||||
        if (mostSigBits != null && leastSigBits != null) {
 | 
			
		||||
            return new UUID(mostSigBits, leastSigBits);
 | 
			
		||||
        } else {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void setOwner(@NotNull BlockState blockState, @NotNull UUID uuid) {
 | 
			
		||||
        PersistentDataContainer dataContainer = ((PersistentDataHolder) blockState).getPersistentDataContainer();
 | 
			
		||||
 | 
			
		||||
        dataContainer.set(NSK_CONTAINER_UUID_MOST_SIG, PersistentDataType.LONG, uuid.getMostSignificantBits());
 | 
			
		||||
        dataContainer.set(NSK_CONTAINER_UUID_LEAST_SIG, PersistentDataType.LONG, uuid.getLeastSignificantBits());
 | 
			
		||||
 | 
			
		||||
        blockState.update();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -10,7 +10,6 @@ import com.gmail.nossr50.datatypes.player.PlayerProfile;
 | 
			
		||||
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.datatypes.skills.subskills.AbstractSubSkill;
 | 
			
		||||
import com.gmail.nossr50.events.experience.McMMOPlayerLevelChangeEvent;
 | 
			
		||||
import com.gmail.nossr50.events.experience.McMMOPlayerLevelDownEvent;
 | 
			
		||||
import com.gmail.nossr50.events.experience.McMMOPlayerLevelUpEvent;
 | 
			
		||||
@@ -59,6 +58,8 @@ import org.jetbrains.annotations.NotNull;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
import static java.util.Objects.requireNonNull;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This class is meant to help make event related code less boilerplate
 | 
			
		||||
 */
 | 
			
		||||
@@ -163,8 +164,17 @@ public final class EventUtils {
 | 
			
		||||
     * Others
 | 
			
		||||
     */
 | 
			
		||||
 | 
			
		||||
    public static @NotNull McMMOPlayerAbilityActivateEvent callPlayerAbilityActivateEvent(@NotNull Player player, @NotNull PrimarySkillType skill) {
 | 
			
		||||
        McMMOPlayerAbilityActivateEvent event = new McMMOPlayerAbilityActivateEvent(player, skill);
 | 
			
		||||
    @Deprecated(forRemoval = true, since = "2.2.010")
 | 
			
		||||
    public static @NotNull McMMOPlayerAbilityActivateEvent callPlayerAbilityActivateEvent(@NotNull Player player,
 | 
			
		||||
                                                                                          @NotNull PrimarySkillType skill) {
 | 
			
		||||
        return callPlayerAbilityActivateEvent(requireNonNull(UserManager.getPlayer(player)), skill);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static @NotNull McMMOPlayerAbilityActivateEvent callPlayerAbilityActivateEvent(@NotNull McMMOPlayer mmoPlayer,
 | 
			
		||||
                                                                                          @NotNull PrimarySkillType skill) {
 | 
			
		||||
        requireNonNull(mmoPlayer, "mmoPlayer cannot be null");
 | 
			
		||||
        requireNonNull(skill, "skill cannot be null");
 | 
			
		||||
        McMMOPlayerAbilityActivateEvent event = new McMMOPlayerAbilityActivateEvent(mmoPlayer, skill);
 | 
			
		||||
        mcMMO.p.getServer().getPluginManager().callEvent(event);
 | 
			
		||||
 | 
			
		||||
        return event;
 | 
			
		||||
@@ -183,8 +193,21 @@ public final class EventUtils {
 | 
			
		||||
     * @param subSkillType target subskill
 | 
			
		||||
     * @return the event after it has been fired
 | 
			
		||||
     */
 | 
			
		||||
    @Deprecated(forRemoval = true, since = "2.2.010")
 | 
			
		||||
    public static @NotNull SubSkillEvent callSubSkillEvent(@NotNull Player player, @NotNull SubSkillType subSkillType) {
 | 
			
		||||
        SubSkillEvent event = new SubSkillEvent(player, subSkillType);
 | 
			
		||||
        return callSubSkillEvent(requireNonNull(UserManager.getPlayer(player)), subSkillType);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Calls a new SubSkillEvent for this SubSkill and then returns it
 | 
			
		||||
     * @param mmoPlayer target mmoPlayer
 | 
			
		||||
     * @param subSkillType target subskill
 | 
			
		||||
     * @return the event after it has been fired
 | 
			
		||||
     */
 | 
			
		||||
    public static @NotNull SubSkillEvent callSubSkillEvent(@NotNull McMMOPlayer mmoPlayer, @NotNull SubSkillType subSkillType) {
 | 
			
		||||
        requireNonNull(mmoPlayer, "mmoPlayer cannot be null");
 | 
			
		||||
        requireNonNull(subSkillType, "subSkillType cannot be null");
 | 
			
		||||
        final SubSkillEvent event = new SubSkillEvent(mmoPlayer, subSkillType);
 | 
			
		||||
        mcMMO.p.getServer().getPluginManager().callEvent(event);
 | 
			
		||||
 | 
			
		||||
        return event;
 | 
			
		||||
@@ -204,26 +227,6 @@ public final class EventUtils {
 | 
			
		||||
        return event;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Calls a new SubSkillEvent for this SubSkill and then returns it
 | 
			
		||||
     * @param player target player
 | 
			
		||||
     * @param abstractSubSkill target subskill
 | 
			
		||||
     * @return the event after it has been fired
 | 
			
		||||
     */
 | 
			
		||||
    public static SubSkillEvent callSubSkillEvent(Player player, AbstractSubSkill abstractSubSkill) {
 | 
			
		||||
        SubSkillEvent event = new SubSkillEvent(player, abstractSubSkill);
 | 
			
		||||
        mcMMO.p.getServer().getPluginManager().callEvent(event);
 | 
			
		||||
 | 
			
		||||
        return event;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
//    public static Event callFakeArmSwingEvent(@NotNull Player player) {
 | 
			
		||||
//        PlayerAnimationEvent event = new FakePlayerAnimationEvent(player, PlayerAnimationType.ARM_SWING);
 | 
			
		||||
//        mcMMO.p.getServer().getPluginManager().callEvent(event);
 | 
			
		||||
//
 | 
			
		||||
//        return event;
 | 
			
		||||
//    }
 | 
			
		||||
 | 
			
		||||
    public static boolean tryLevelChangeEvent(Player player, PrimarySkillType skill, int levelsChanged, float xpRemoved, boolean isLevelUp, XPGainReason xpGainReason) {
 | 
			
		||||
        McMMOPlayerLevelChangeEvent event = isLevelUp ? new McMMOPlayerLevelUpEvent(player, skill, levelsChanged, xpGainReason) : new McMMOPlayerLevelDownEvent(player, skill, levelsChanged, xpGainReason);
 | 
			
		||||
        mcMMO.p.getServer().getPluginManager().callEvent(event);
 | 
			
		||||
@@ -497,15 +500,25 @@ public final class EventUtils {
 | 
			
		||||
        return !isCancelled;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Deprecated(forRemoval = true, since = "2.2.010")
 | 
			
		||||
    public static McMMOPlayerAbilityDeactivateEvent callAbilityDeactivateEvent(Player player, SuperAbilityType ability) {
 | 
			
		||||
        McMMOPlayerAbilityDeactivateEvent event = new McMMOPlayerAbilityDeactivateEvent(player, mcMMO.p.getSkillTools().getPrimarySkillBySuperAbility(ability));
 | 
			
		||||
        return callAbilityDeactivateEvent(requireNonNull(UserManager.getPlayer(player)), ability);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static McMMOPlayerAbilityDeactivateEvent callAbilityDeactivateEvent(@NotNull McMMOPlayer mmoPlayer, @NotNull SuperAbilityType ability) {
 | 
			
		||||
        final McMMOPlayerAbilityDeactivateEvent event = new McMMOPlayerAbilityDeactivateEvent(mmoPlayer, mcMMO.p.getSkillTools().getPrimarySkillBySuperAbility(ability));
 | 
			
		||||
        mcMMO.p.getServer().getPluginManager().callEvent(event);
 | 
			
		||||
 | 
			
		||||
        return event;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Deprecated(forRemoval = true, since = "2.2.010")
 | 
			
		||||
    public static McMMOPlayerFishingTreasureEvent callFishingTreasureEvent(Player player, ItemStack treasureDrop, int treasureXp, Map<Enchantment, Integer> enchants) {
 | 
			
		||||
        McMMOPlayerFishingTreasureEvent event = enchants.isEmpty() ? new McMMOPlayerFishingTreasureEvent(player, treasureDrop, treasureXp) : new McMMOPlayerMagicHunterEvent(player, treasureDrop, treasureXp, enchants);
 | 
			
		||||
        return callFishingTreasureEvent(requireNonNull(UserManager.getPlayer(player)), treasureDrop, treasureXp, enchants);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static McMMOPlayerFishingTreasureEvent callFishingTreasureEvent(McMMOPlayer mmoPlayer, ItemStack treasureDrop, int treasureXp, Map<Enchantment, Integer> enchants) {
 | 
			
		||||
        final McMMOPlayerFishingTreasureEvent event = enchants.isEmpty() ? new McMMOPlayerFishingTreasureEvent(mmoPlayer, treasureDrop, treasureXp) : new McMMOPlayerMagicHunterEvent(mmoPlayer, treasureDrop, treasureXp, enchants);
 | 
			
		||||
        mcMMO.p.getServer().getPluginManager().callEvent(event);
 | 
			
		||||
 | 
			
		||||
        return event;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										104
									
								
								src/main/java/com/gmail/nossr50/util/ItemMetadataUtils.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								src/main/java/com/gmail/nossr50/util/ItemMetadataUtils.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,104 @@
 | 
			
		||||
package com.gmail.nossr50.util;
 | 
			
		||||
 | 
			
		||||
import com.gmail.nossr50.mcMMO;
 | 
			
		||||
import org.bukkit.inventory.ItemStack;
 | 
			
		||||
import org.bukkit.inventory.meta.ItemMeta;
 | 
			
		||||
import org.bukkit.persistence.PersistentDataContainer;
 | 
			
		||||
import org.bukkit.persistence.PersistentDataType;
 | 
			
		||||
import org.jetbrains.annotations.NotNull;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
import static com.gmail.nossr50.util.MetadataService.NSK_SUPER_ABILITY_BOOSTED_ITEM;
 | 
			
		||||
 | 
			
		||||
public final class ItemMetadataUtils {
 | 
			
		||||
 | 
			
		||||
    public static final @NotNull String LEGACY_ABILITY_TOOL_LORE = "mcMMO Ability Tool";
 | 
			
		||||
 | 
			
		||||
    private ItemMetadataUtils() {
 | 
			
		||||
        // private ctor
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void setSuperAbilityBoostedItem(@NotNull ItemStack itemStack, int originalDigSpeed) {
 | 
			
		||||
        if (itemStack.getItemMeta() == null) {
 | 
			
		||||
            mcMMO.p.getLogger().severe("Can not assign persistent data to an item with null item metadata");
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        ItemMeta itemMeta = itemStack.getItemMeta();
 | 
			
		||||
        PersistentDataContainer dataContainer = itemMeta.getPersistentDataContainer();
 | 
			
		||||
 | 
			
		||||
        dataContainer.set(NSK_SUPER_ABILITY_BOOSTED_ITEM, PersistentDataType.INTEGER, originalDigSpeed);
 | 
			
		||||
 | 
			
		||||
        itemStack.setItemMeta(itemMeta);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static boolean isSuperAbilityBoosted(@NotNull ItemStack itemStack) {
 | 
			
		||||
        if (itemStack.getItemMeta() == null)
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        ItemMeta itemMeta = itemStack.getItemMeta();
 | 
			
		||||
        //Get container from entity
 | 
			
		||||
        PersistentDataContainer dataContainer = itemMeta.getPersistentDataContainer();
 | 
			
		||||
 | 
			
		||||
        //If this value isn't null, then the tool can be considered dig speed boosted
 | 
			
		||||
        Integer boostValue = dataContainer.get(NSK_SUPER_ABILITY_BOOSTED_ITEM, PersistentDataType.INTEGER);
 | 
			
		||||
 | 
			
		||||
        return boostValue != null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static int getSuperAbilityToolOriginalDigSpeed(@NotNull ItemStack itemStack) {
 | 
			
		||||
        //Get container from entity
 | 
			
		||||
        ItemMeta itemMeta = itemStack.getItemMeta();
 | 
			
		||||
 | 
			
		||||
        if (itemMeta == null)
 | 
			
		||||
            return 0;
 | 
			
		||||
 | 
			
		||||
        PersistentDataContainer dataContainer = itemMeta.getPersistentDataContainer();
 | 
			
		||||
 | 
			
		||||
        if (dataContainer.get(NSK_SUPER_ABILITY_BOOSTED_ITEM, PersistentDataType.INTEGER) == null) {
 | 
			
		||||
            mcMMO.p.getLogger().severe("Value should never be null for a boosted item");
 | 
			
		||||
            return 0;
 | 
			
		||||
        } else {
 | 
			
		||||
            //Too lazy to make a custom data type for this stuff
 | 
			
		||||
            Integer boostValue = dataContainer.get(NSK_SUPER_ABILITY_BOOSTED_ITEM, PersistentDataType.INTEGER);
 | 
			
		||||
            return Math.max(boostValue, 0);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static void removeBonusDigSpeedOnSuperAbilityTool(@NotNull ItemStack itemStack) {
 | 
			
		||||
        int originalSpeed = getSuperAbilityToolOriginalDigSpeed(itemStack);
 | 
			
		||||
        ItemMeta itemMeta = itemStack.getItemMeta();
 | 
			
		||||
 | 
			
		||||
        if(itemMeta != null) {
 | 
			
		||||
            //TODO: can be optimized
 | 
			
		||||
            if (itemMeta.hasEnchant(mcMMO.p.getEnchantmentMapper().getEfficiency())) {
 | 
			
		||||
                itemMeta.removeEnchant(mcMMO.p.getEnchantmentMapper().getEfficiency());
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (originalSpeed > 0) {
 | 
			
		||||
                itemMeta.addEnchant(mcMMO.p.getEnchantmentMapper().getEfficiency(), originalSpeed, true);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            PersistentDataContainer dataContainer = itemMeta.getPersistentDataContainer();
 | 
			
		||||
            dataContainer.remove(NSK_SUPER_ABILITY_BOOSTED_ITEM); //Remove persistent data
 | 
			
		||||
 | 
			
		||||
            //TODO: needed?
 | 
			
		||||
            itemStack.setItemMeta(itemMeta);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static boolean isLegacyAbilityTool(@NotNull ItemStack itemStack) {
 | 
			
		||||
        ItemMeta itemMeta = itemStack.getItemMeta();
 | 
			
		||||
 | 
			
		||||
        if (itemMeta == null)
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        List<String> lore = itemMeta.getLore();
 | 
			
		||||
 | 
			
		||||
        if (lore == null || lore.isEmpty())
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        return lore.contains(LEGACY_ABILITY_TOOL_LORE);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -34,8 +34,9 @@ public class MetadataConstants {
 | 
			
		||||
    public static final @NotNull String METADATA_KEY_DISARMED_ITEM = "mcMMO: Disarmed Item";
 | 
			
		||||
    public static final @NotNull String METADATA_KEY_PLAYER_DATA = "mcMMO: Player Data";
 | 
			
		||||
    public static final @NotNull String METADATA_KEY_DATABASE_COMMAND = "mcMMO: Processing Database Command";
 | 
			
		||||
    public static final @NotNull String METADATA_KEY_FURNACE_UUID_MOST_SIG = "furnace_uuid_most_sig";
 | 
			
		||||
    public static final @NotNull String METADATA_KEY_FURNACE_UUID_LEAST_SIG = "furnace_uuid_least_sig";
 | 
			
		||||
    // the value of these two keys have "furnace" to keep supporting legacy data
 | 
			
		||||
    public static final @NotNull String METADATA_KEY_CONTAINER_UUID_MOST_SIG = "furnace_uuid_most_sig";
 | 
			
		||||
    public static final @NotNull String METADATA_KEY_CONTAINER_UUID_LEAST_SIG = "furnace_uuid_least_sig";
 | 
			
		||||
    public static final @NotNull String METADATA_KEY_SUPER_ABILITY_BOOSTED_ITEM = "super_ability_boosted";
 | 
			
		||||
    public static final @NotNull String METADATA_KEY_MOB_SPAWNER_MOB = "mcmmo_mob_spawner_mob";
 | 
			
		||||
    public static final @NotNull String METADATA_KEY_EGG_MOB = "mcmmo_egg_mob";
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										48
									
								
								src/main/java/com/gmail/nossr50/util/MetadataService.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/main/java/com/gmail/nossr50/util/MetadataService.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,48 @@
 | 
			
		||||
package com.gmail.nossr50.util;
 | 
			
		||||
 | 
			
		||||
import com.gmail.nossr50.mcMMO;
 | 
			
		||||
import org.bukkit.NamespacedKey;
 | 
			
		||||
import org.jetbrains.annotations.NotNull;
 | 
			
		||||
 | 
			
		||||
public final class MetadataService {
 | 
			
		||||
    static final @NotNull NamespacedKey NSK_SUPER_ABILITY_BOOSTED_ITEM;
 | 
			
		||||
    static final @NotNull NamespacedKey NSK_MOB_SPAWNER_MOB;
 | 
			
		||||
    static final @NotNull NamespacedKey NSK_EGG_MOB;
 | 
			
		||||
    static final @NotNull NamespacedKey NSK_NETHER_GATE_MOB;
 | 
			
		||||
    static final @NotNull NamespacedKey NSK_COTW_SUMMONED_MOB;
 | 
			
		||||
    static final @NotNull NamespacedKey NSK_PLAYER_BRED_MOB;
 | 
			
		||||
    static final @NotNull NamespacedKey NSK_PLAYER_TAMED_MOB;
 | 
			
		||||
    static final @NotNull NamespacedKey NSK_VILLAGER_TRADE_ORIGIN_ITEM;
 | 
			
		||||
    static final @NotNull NamespacedKey NSK_EXPLOITED_ENDERMEN;
 | 
			
		||||
    static final @NotNull NamespacedKey NSK_CONTAINER_UUID_MOST_SIG;
 | 
			
		||||
    static final @NotNull NamespacedKey NSK_CONTAINER_UUID_LEAST_SIG;
 | 
			
		||||
 | 
			
		||||
    private MetadataService() {
 | 
			
		||||
        // private ctor
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static {
 | 
			
		||||
        NSK_SUPER_ABILITY_BOOSTED_ITEM = getNamespacedKey(MetadataConstants.METADATA_KEY_SUPER_ABILITY_BOOSTED_ITEM);
 | 
			
		||||
        NSK_MOB_SPAWNER_MOB = getNamespacedKey(MetadataConstants.METADATA_KEY_MOB_SPAWNER_MOB);
 | 
			
		||||
        NSK_EGG_MOB = getNamespacedKey(MetadataConstants.METADATA_KEY_EGG_MOB);
 | 
			
		||||
        NSK_NETHER_GATE_MOB = getNamespacedKey(MetadataConstants.METADATA_KEY_NETHER_PORTAL_MOB);
 | 
			
		||||
        NSK_COTW_SUMMONED_MOB = getNamespacedKey(MetadataConstants.METADATA_KEY_COTW_SUMMONED_MOB);
 | 
			
		||||
        NSK_PLAYER_BRED_MOB = getNamespacedKey(MetadataConstants.METADATA_KEY_PLAYER_BRED_MOB);
 | 
			
		||||
        NSK_PLAYER_TAMED_MOB = getNamespacedKey(MetadataConstants.METADATA_KEY_PLAYER_TAMED_MOB);
 | 
			
		||||
        NSK_VILLAGER_TRADE_ORIGIN_ITEM = getNamespacedKey(MetadataConstants.METADATA_KEY_VILLAGER_TRADE_ORIGIN_ITEM);
 | 
			
		||||
        NSK_EXPLOITED_ENDERMEN = getNamespacedKey(MetadataConstants.METADATA_KEY_EXPLOITED_ENDERMEN);
 | 
			
		||||
        NSK_CONTAINER_UUID_MOST_SIG = getNamespacedKey(MetadataConstants.METADATA_KEY_CONTAINER_UUID_MOST_SIG);
 | 
			
		||||
        NSK_CONTAINER_UUID_LEAST_SIG = getNamespacedKey(MetadataConstants.METADATA_KEY_CONTAINER_UUID_LEAST_SIG);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Helper method to simplify generating namespaced keys
 | 
			
		||||
     *
 | 
			
		||||
     * @param key the {@link String} value of the key
 | 
			
		||||
     *
 | 
			
		||||
     * @return the generated {@link NamespacedKey}
 | 
			
		||||
     */
 | 
			
		||||
    public static @NotNull NamespacedKey getNamespacedKey(@NotNull String key) {
 | 
			
		||||
        return new NamespacedKey(mcMMO.p, key);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										178
									
								
								src/main/java/com/gmail/nossr50/util/MobMetadataUtils.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								src/main/java/com/gmail/nossr50/util/MobMetadataUtils.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,178 @@
 | 
			
		||||
package com.gmail.nossr50.util;
 | 
			
		||||
 | 
			
		||||
import com.gmail.nossr50.api.exceptions.IncompleteNamespacedKeyRegister;
 | 
			
		||||
import com.gmail.nossr50.config.PersistentDataConfig;
 | 
			
		||||
import com.gmail.nossr50.metadata.MobMetaFlagType;
 | 
			
		||||
import org.bukkit.NamespacedKey;
 | 
			
		||||
import org.bukkit.entity.Entity;
 | 
			
		||||
import org.bukkit.entity.LivingEntity;
 | 
			
		||||
import org.bukkit.persistence.PersistentDataContainer;
 | 
			
		||||
import org.bukkit.persistence.PersistentDataType;
 | 
			
		||||
import org.jetbrains.annotations.NotNull;
 | 
			
		||||
 | 
			
		||||
import java.util.EnumMap;
 | 
			
		||||
import java.util.HashSet;
 | 
			
		||||
import java.util.WeakHashMap;
 | 
			
		||||
 | 
			
		||||
import static com.gmail.nossr50.util.MetadataService.*;
 | 
			
		||||
 | 
			
		||||
//TODO: Use SpawnReason where appropriate instead of MobMetaFlagType
 | 
			
		||||
public final class MobMetadataUtils {
 | 
			
		||||
    private static final @NotNull WeakHashMap<Entity, HashSet<MobMetaFlagType>> mobRegistry; //transient data
 | 
			
		||||
    private static final @NotNull EnumMap<MobMetaFlagType, NamespacedKey> mobFlagKeyMap; //used for persistent data
 | 
			
		||||
    private static boolean isUsingPersistentData = false;
 | 
			
		||||
 | 
			
		||||
    private MobMetadataUtils() {
 | 
			
		||||
        // private ctor
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static {
 | 
			
		||||
        mobFlagKeyMap = new EnumMap<>(MobMetaFlagType.class);
 | 
			
		||||
        mobRegistry = new WeakHashMap<>();
 | 
			
		||||
        initMobFlagKeyMap();
 | 
			
		||||
 | 
			
		||||
        for (MobMetaFlagType metaFlagType : MobMetaFlagType.values()) {
 | 
			
		||||
            if (PersistentDataConfig.getInstance().isMobPersistent(metaFlagType))
 | 
			
		||||
                isUsingPersistentData = true;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Registers the namespaced keys required by the API (CB/Spigot)
 | 
			
		||||
     * Used primarily for persistent data
 | 
			
		||||
     */
 | 
			
		||||
    private static void initMobFlagKeyMap() throws IncompleteNamespacedKeyRegister {
 | 
			
		||||
        for (MobMetaFlagType mobMetaFlagType : MobMetaFlagType.values()) {
 | 
			
		||||
            switch (mobMetaFlagType) {
 | 
			
		||||
                case MOB_SPAWNER_MOB -> mobFlagKeyMap.put(mobMetaFlagType, NSK_MOB_SPAWNER_MOB);
 | 
			
		||||
                case EGG_MOB -> mobFlagKeyMap.put(mobMetaFlagType, NSK_EGG_MOB);
 | 
			
		||||
                case NETHER_PORTAL_MOB -> mobFlagKeyMap.put(mobMetaFlagType, NSK_NETHER_GATE_MOB);
 | 
			
		||||
                case COTW_SUMMONED_MOB -> mobFlagKeyMap.put(mobMetaFlagType, NSK_COTW_SUMMONED_MOB);
 | 
			
		||||
                case PLAYER_BRED_MOB -> mobFlagKeyMap.put(mobMetaFlagType, NSK_PLAYER_BRED_MOB);
 | 
			
		||||
                case EXPLOITED_ENDERMEN -> mobFlagKeyMap.put(mobMetaFlagType, NSK_EXPLOITED_ENDERMEN);
 | 
			
		||||
                case PLAYER_TAMED_MOB -> mobFlagKeyMap.put(mobMetaFlagType, NSK_PLAYER_TAMED_MOB);
 | 
			
		||||
                default -> throw new IncompleteNamespacedKeyRegister("missing namespaced key register for type: " + mobMetaFlagType);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Whether a target {@link LivingEntity} has a specific mcMMO mob flags
 | 
			
		||||
     *
 | 
			
		||||
     * @param flag         the type of mob flag to check for
 | 
			
		||||
     * @param livingEntity the living entity to check for metadata
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if the mob has metadata values for target {@link MobMetaFlagType}
 | 
			
		||||
     */
 | 
			
		||||
    public static boolean hasMobFlag(@NotNull MobMetaFlagType flag, @NotNull LivingEntity livingEntity) {
 | 
			
		||||
        if (PersistentDataConfig.getInstance().isMobPersistent(flag)) {
 | 
			
		||||
            return livingEntity.getPersistentDataContainer().has(mobFlagKeyMap.get(flag), PersistentDataType.BYTE);
 | 
			
		||||
        } else {
 | 
			
		||||
            if (mobRegistry.containsKey(livingEntity)) {
 | 
			
		||||
                return mobRegistry.get(livingEntity).contains(flag);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Whether a target {@link LivingEntity} has any mcMMO mob flags
 | 
			
		||||
     *
 | 
			
		||||
     * @param livingEntity the living entity to check for metadata
 | 
			
		||||
     *
 | 
			
		||||
     * @return true if the mob has any mcMMO mob related metadata values
 | 
			
		||||
     */
 | 
			
		||||
    public static boolean hasMobFlags(@NotNull LivingEntity livingEntity) {
 | 
			
		||||
        if (isUsingPersistentData) {
 | 
			
		||||
            for (MobMetaFlagType metaFlagType : MobMetaFlagType.values()) {
 | 
			
		||||
                if (hasMobFlag(metaFlagType, livingEntity))
 | 
			
		||||
                    return true;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return false;
 | 
			
		||||
        } else {
 | 
			
		||||
            return mobRegistry.containsKey(livingEntity) && mobRegistry.get(livingEntity).size() > 0;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Copies all mcMMO mob flags from one {@link LivingEntity} to another {@link LivingEntity}
 | 
			
		||||
     * This does not clear existing mcMMO mob flags on the target
 | 
			
		||||
     *
 | 
			
		||||
     * @param sourceEntity entity to copy from
 | 
			
		||||
     * @param targetEntity entity to copy to
 | 
			
		||||
     */
 | 
			
		||||
    public static void addMobFlags(@NotNull LivingEntity sourceEntity, @NotNull LivingEntity targetEntity) {
 | 
			
		||||
        if (!hasMobFlags(sourceEntity))
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        if (isUsingPersistentData) {
 | 
			
		||||
            for (MobMetaFlagType flag : MobMetaFlagType.values()) {
 | 
			
		||||
                if (hasMobFlag(flag, sourceEntity)) {
 | 
			
		||||
                    flagMetadata(flag, targetEntity);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            HashSet<MobMetaFlagType> flags = new HashSet<>(mobRegistry.get(sourceEntity));
 | 
			
		||||
            mobRegistry.put(targetEntity, flags);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Adds a mob flag to a {@link LivingEntity} which effectively acts a true/false boolean
 | 
			
		||||
     * Existence of the flag can be considered a true value, non-existence can be considered false for all intents and purposes
 | 
			
		||||
     *
 | 
			
		||||
     * @param flag         the desired flag to assign
 | 
			
		||||
     * @param livingEntity the target living entity
 | 
			
		||||
     */
 | 
			
		||||
    public static void flagMetadata(@NotNull MobMetaFlagType flag, @NotNull LivingEntity livingEntity) {
 | 
			
		||||
        if (PersistentDataConfig.getInstance().isMobPersistent(flag)) {
 | 
			
		||||
            if (!hasMobFlag(flag, livingEntity)) {
 | 
			
		||||
                PersistentDataContainer persistentDataContainer = livingEntity.getPersistentDataContainer();
 | 
			
		||||
                persistentDataContainer.set(mobFlagKeyMap.get(flag), PersistentDataType.BYTE, MetadataConstants.SIMPLE_FLAG_VALUE);
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            HashSet<MobMetaFlagType> flags = mobRegistry.getOrDefault(livingEntity, new HashSet<>());
 | 
			
		||||
            flags.add(flag); // add the new flag
 | 
			
		||||
            mobRegistry.put(livingEntity, flags); //update registry
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Removes a specific mob flag from target {@link LivingEntity}
 | 
			
		||||
     *
 | 
			
		||||
     * @param flag         desired flag to remove
 | 
			
		||||
     * @param livingEntity the target living entity
 | 
			
		||||
     */
 | 
			
		||||
    public static void removeMobFlag(@NotNull MobMetaFlagType flag, @NotNull LivingEntity livingEntity) {
 | 
			
		||||
        if (PersistentDataConfig.getInstance().isMobPersistent(flag)) {
 | 
			
		||||
            if (hasMobFlag(flag, livingEntity)) {
 | 
			
		||||
                PersistentDataContainer persistentDataContainer = livingEntity.getPersistentDataContainer();
 | 
			
		||||
                persistentDataContainer.remove(mobFlagKeyMap.get(flag));
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            if (mobRegistry.containsKey(livingEntity)) {
 | 
			
		||||
                mobRegistry.get(livingEntity).remove(flag);
 | 
			
		||||
 | 
			
		||||
                if (mobRegistry.get(livingEntity).size() == 0)
 | 
			
		||||
                    mobRegistry.remove(livingEntity);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Remove all mcMMO related mob flags from the target {@link LivingEntity}
 | 
			
		||||
     *
 | 
			
		||||
     * @param livingEntity target entity
 | 
			
		||||
     */
 | 
			
		||||
    public static void removeMobFlags(@NotNull LivingEntity livingEntity) {
 | 
			
		||||
        if (isUsingPersistentData) {
 | 
			
		||||
            for (MobMetaFlagType flag : MobMetaFlagType.values()) {
 | 
			
		||||
                removeMobFlag(flag, livingEntity);
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            mobRegistry.remove(livingEntity);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
package com.gmail.nossr50.util;
 | 
			
		||||
 | 
			
		||||
import com.gmail.nossr50.commands.party.PartySubcommandType;
 | 
			
		||||
import com.gmail.nossr50.datatypes.player.McMMOPlayer;
 | 
			
		||||
import com.gmail.nossr50.datatypes.skills.ItemType;
 | 
			
		||||
import com.gmail.nossr50.datatypes.skills.MaterialType;
 | 
			
		||||
import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
 | 
			
		||||
@@ -17,6 +18,7 @@ import org.bukkit.permissions.Permission;
 | 
			
		||||
import org.bukkit.permissions.PermissionDefault;
 | 
			
		||||
import org.bukkit.plugin.PluginManager;
 | 
			
		||||
import org.jetbrains.annotations.NotNull;
 | 
			
		||||
import org.jetbrains.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
import java.util.Locale;
 | 
			
		||||
 | 
			
		||||
@@ -171,10 +173,19 @@ public final class Permissions {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static boolean vanillaXpBoost(Permissible permissible, PrimarySkillType skill) { return permissible.hasPermission("mcmmo.ability." + skill.toString().toLowerCase(Locale.ENGLISH) + ".vanillaxpboost"); }
 | 
			
		||||
    public static boolean isSubSkillEnabled(Permissible permissible, SubSkillType subSkillType) {
 | 
			
		||||
    public static boolean isSubSkillEnabled(@Nullable Permissible permissible, @NotNull SubSkillType subSkillType) {
 | 
			
		||||
        if (permissible == null)
 | 
			
		||||
            return false;
 | 
			
		||||
        return permissible.hasPermission(subSkillType.getPermissionNodeAddress());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static boolean isSubSkillEnabled(@Nullable McMMOPlayer permissible, @NotNull SubSkillType subSkillType) {
 | 
			
		||||
        if (permissible == null)
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        return isSubSkillEnabled(permissible.getPlayer(), subSkillType);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /* ACROBATICS */
 | 
			
		||||
    public static boolean dodge(Permissible permissible) { return permissible.hasPermission("mcmmo.ability.acrobatics.dodge"); }
 | 
			
		||||
    public static boolean gracefulRoll(Permissible permissible) { return permissible.hasPermission("mcmmo.ability.acrobatics.gracefulroll"); }
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,8 @@ import org.jetbrains.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
import java.util.*;
 | 
			
		||||
 | 
			
		||||
import static com.gmail.nossr50.util.MobMetadataUtils.removeMobFlags;
 | 
			
		||||
 | 
			
		||||
public class TransientEntityTracker {
 | 
			
		||||
    //These two are updated in step with each other
 | 
			
		||||
    private final @NotNull HashMap<UUID, HashMap<CallOfTheWildType, HashSet<TrackedTamingEntity>>> perPlayerTransientEntityMap;
 | 
			
		||||
@@ -273,7 +275,7 @@ public class TransientEntityTracker {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        //Remove our metadata
 | 
			
		||||
        mcMMO.getMetadataService().getMobMetadataService().removeMobFlags(livingEntity);
 | 
			
		||||
        removeMobFlags(livingEntity);
 | 
			
		||||
 | 
			
		||||
        //Clean from trackers
 | 
			
		||||
        unregisterEntity(livingEntity);
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,8 @@ import com.gmail.nossr50.mcMMO;
 | 
			
		||||
import org.bukkit.entity.LivingEntity;
 | 
			
		||||
import org.jetbrains.annotations.NotNull;
 | 
			
		||||
 | 
			
		||||
import static com.gmail.nossr50.util.MobMetadataUtils.removeMobFlags;
 | 
			
		||||
 | 
			
		||||
public class TransientMetadataTools {
 | 
			
		||||
    private final mcMMO pluginRef;
 | 
			
		||||
 | 
			
		||||
@@ -30,7 +32,7 @@ public class TransientMetadataTools {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        //Cleanup mob metadata
 | 
			
		||||
        mcMMO.getMetadataService().getMobMetadataService().removeMobFlags(entity);
 | 
			
		||||
        removeMobFlags(entity);
 | 
			
		||||
 | 
			
		||||
        //TODO: This loop has some redundancy, this whole method needs to be rewritten
 | 
			
		||||
        for(String key : MetadataConstants.MOB_METADATA_KEYS) {
 | 
			
		||||
 
 | 
			
		||||
@@ -11,26 +11,37 @@ import com.gmail.nossr50.util.player.UserManager;
 | 
			
		||||
import org.bukkit.entity.Player;
 | 
			
		||||
import org.jetbrains.annotations.NotNull;
 | 
			
		||||
import org.jetbrains.annotations.Nullable;
 | 
			
		||||
import org.jetbrains.annotations.VisibleForTesting;
 | 
			
		||||
 | 
			
		||||
import java.text.DecimalFormat;
 | 
			
		||||
 | 
			
		||||
import static java.util.Objects.requireNonNull;
 | 
			
		||||
 | 
			
		||||
public class ProbabilityUtil {
 | 
			
		||||
    public static final @NotNull DecimalFormat percent = new DecimalFormat("##0.00%");
 | 
			
		||||
    public static final double LUCKY_MODIFIER = 1.333D;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Return a chance of success in "percentage" format, show to the player in UI elements
 | 
			
		||||
     * Return a chance of success in "percentage" format, shown to the player in UI elements
 | 
			
		||||
     *
 | 
			
		||||
     * @param player target player
 | 
			
		||||
     * @param subSkillType target subskill
 | 
			
		||||
     * @param isLucky whether to apply luck modifiers
 | 
			
		||||
     *
 | 
			
		||||
     * @return "percentage" representation of success
 | 
			
		||||
     * @deprecated use {@link #chanceOfSuccessPercentage(McMMOPlayer, SubSkillType, boolean)} instead
 | 
			
		||||
     */
 | 
			
		||||
    public static double chanceOfSuccessPercentage(@NotNull Player player,
 | 
			
		||||
    @Deprecated(forRemoval = true, since = "2.2.010")
 | 
			
		||||
    public static double chanceOfSuccessPercentage(@Nullable Player player,
 | 
			
		||||
                                                   @NotNull SubSkillType subSkillType,
 | 
			
		||||
                                                   boolean isLucky) {
 | 
			
		||||
        Probability probability = getSubSkillProbability(subSkillType, player);
 | 
			
		||||
        return chanceOfSuccessPercentage(requireNonNull(UserManager.getPlayer(player)), subSkillType, isLucky);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static double chanceOfSuccessPercentage(@Nullable McMMOPlayer mmoPlayer,
 | 
			
		||||
                                                   @NotNull SubSkillType subSkillType,
 | 
			
		||||
                                                   boolean isLucky) {
 | 
			
		||||
        Probability probability = getSubSkillProbability(subSkillType, mmoPlayer);
 | 
			
		||||
        //Probability values are on a 0-1 scale and need to be "transformed" into a 1-100 scale
 | 
			
		||||
        double percentageValue = probability.getValue(); //Doesn't need to be scaled
 | 
			
		||||
 | 
			
		||||
@@ -42,6 +53,13 @@ public class ProbabilityUtil {
 | 
			
		||||
        return percentageValue;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Return a chance of success as a double representing a "percentage".
 | 
			
		||||
     *
 | 
			
		||||
     * @param probability the probability of success
 | 
			
		||||
     * @param isLucky whether to apply luck modifiers
 | 
			
		||||
     * @return a double as a "percentage" representation of success
 | 
			
		||||
     */
 | 
			
		||||
    public static double chanceOfSuccessPercentage(@NotNull Probability probability, boolean isLucky) {
 | 
			
		||||
        //Probability values are on a 0-1 scale and need to be "transformed" into a 1-100 scale
 | 
			
		||||
        double percentageValue = probability.getValue();
 | 
			
		||||
@@ -54,6 +72,7 @@ public class ProbabilityUtil {
 | 
			
		||||
        return percentageValue;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @VisibleForTesting
 | 
			
		||||
    static Probability getStaticRandomChance(@NotNull SubSkillType subSkillType) throws InvalidStaticChance {
 | 
			
		||||
        return switch (subSkillType) {
 | 
			
		||||
            case AXES_ARMOR_IMPACT -> Probability.ofPercent(mcMMO.p.getAdvancedConfig().getImpactChance());
 | 
			
		||||
@@ -74,19 +93,20 @@ public class ProbabilityUtil {
 | 
			
		||||
        return skillProbabilityType;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static @NotNull Probability ofSubSkill(@Nullable Player player,
 | 
			
		||||
                                           @NotNull SubSkillType subSkillType) {
 | 
			
		||||
    @Deprecated(forRemoval = true, since = "2.2.010")
 | 
			
		||||
    private static @NotNull Probability ofSubSkill(@Nullable Player player, @NotNull SubSkillType subSkillType) {
 | 
			
		||||
        // no null check needed here
 | 
			
		||||
        return ofSubSkill(UserManager.getPlayer(player), subSkillType);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static @NotNull Probability ofSubSkill(@Nullable McMMOPlayer mmoPlayer, @NotNull SubSkillType subSkillType) {
 | 
			
		||||
        switch (getProbabilityType(subSkillType)) {
 | 
			
		||||
            case DYNAMIC_CONFIGURABLE:
 | 
			
		||||
                double probabilityCeiling;
 | 
			
		||||
                double skillLevel;
 | 
			
		||||
                double maxBonusLevel; // If a skill level is equal to the cap, it has the full probability
 | 
			
		||||
 | 
			
		||||
                if (player != null) {
 | 
			
		||||
                    McMMOPlayer mmoPlayer = UserManager.getPlayer(player);
 | 
			
		||||
                    if (mmoPlayer == null) {
 | 
			
		||||
                        return Probability.ofPercent(0);
 | 
			
		||||
                    }
 | 
			
		||||
                if (mmoPlayer != null) {
 | 
			
		||||
                    skillLevel = mmoPlayer.getSkillLevel(subSkillType.getParentSkill());
 | 
			
		||||
                } else {
 | 
			
		||||
                    skillLevel = 0;
 | 
			
		||||
@@ -101,10 +121,10 @@ public class ProbabilityUtil {
 | 
			
		||||
                try {
 | 
			
		||||
                    return getStaticRandomChance(subSkillType);
 | 
			
		||||
                } catch (InvalidStaticChance invalidStaticChance) {
 | 
			
		||||
                    invalidStaticChance.printStackTrace();
 | 
			
		||||
                    throw new RuntimeException(invalidStaticChance);
 | 
			
		||||
                }
 | 
			
		||||
            default:
 | 
			
		||||
                throw new RuntimeException("No case in switch statement for Skill Probability Type!");
 | 
			
		||||
                throw new IllegalStateException("No case in switch statement for Skill Probability Type!");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -123,14 +143,43 @@ public class ProbabilityUtil {
 | 
			
		||||
     * The outcome of the probability can also be modified by this event that is called
 | 
			
		||||
     *
 | 
			
		||||
     * @param subSkillType target subskill
 | 
			
		||||
     * @param player target player, can be null (null players are given odds equivalent to a player with no levels or luck)
 | 
			
		||||
     * @param player target player
 | 
			
		||||
     *              can be null (null players are given odds equivalent to a player with no levels or luck)
 | 
			
		||||
     * @return true if the Skill RNG succeeds, false if it fails
 | 
			
		||||
     * @deprecated use {@link #isSkillRNGSuccessful(SubSkillType, McMMOPlayer)} instead
 | 
			
		||||
     */
 | 
			
		||||
    @Deprecated(forRemoval = true, since = "2.2.010")
 | 
			
		||||
    public static boolean isSkillRNGSuccessful(@NotNull SubSkillType subSkillType, @Nullable Player player) {
 | 
			
		||||
        return isSkillRNGSuccessful(subSkillType, UserManager.getPlayer(player));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * This is one of several Skill RNG check methods
 | 
			
		||||
     * This helper method is for specific {@link SubSkillType},
 | 
			
		||||
     * which help mcMMO understand where the RNG values used in our calculations come from this {@link SubSkillType}
 | 
			
		||||
     * <p>
 | 
			
		||||
     * 1) Determine where the RNG values come from for the passed {@link SubSkillType}
 | 
			
		||||
     *  NOTE: In the config file, there are values which are static and which are more dynamic,
 | 
			
		||||
     *  this is currently a bit hardcoded and will need to be updated manually
 | 
			
		||||
     * <p>
 | 
			
		||||
     * 2) Determine whether to use Lucky multiplier and influence the outcome
 | 
			
		||||
     * <p>
 | 
			
		||||
     * 3)
 | 
			
		||||
     * Creates a {@link Probability} and pipes it to {@link ProbabilityUtil} which processes the result and returns it
 | 
			
		||||
     * <p>
 | 
			
		||||
     * This also calls a {@link SubSkillEvent} which can be cancelled, if it is cancelled this will return false
 | 
			
		||||
     * The outcome of the probability can also be modified by this event that is called
 | 
			
		||||
     *
 | 
			
		||||
     * @param subSkillType target subskill
 | 
			
		||||
     * @param mmoPlayer target player
 | 
			
		||||
     *                  can be null (null players are given odds equivalent to a player with no levels or luck)
 | 
			
		||||
     * @return true if the Skill RNG succeeds, false if it fails
 | 
			
		||||
     */
 | 
			
		||||
    public static boolean isSkillRNGSuccessful(@NotNull SubSkillType subSkillType, @NotNull Player player) {
 | 
			
		||||
        final Probability probability = getSkillProbability(subSkillType, player);
 | 
			
		||||
    public static boolean isSkillRNGSuccessful(@NotNull SubSkillType subSkillType, @Nullable McMMOPlayer mmoPlayer) {
 | 
			
		||||
        final Probability probability = getSkillProbability(subSkillType, mmoPlayer);
 | 
			
		||||
 | 
			
		||||
        //Luck
 | 
			
		||||
        boolean isLucky = Permissions.lucky(player, subSkillType.getParentSkill());
 | 
			
		||||
        boolean isLucky = mmoPlayer != null && Permissions.lucky(mmoPlayer.getPlayer(), subSkillType.getParentSkill());
 | 
			
		||||
 | 
			
		||||
        if(isLucky) {
 | 
			
		||||
            return probability.evaluate(LUCKY_MODIFIER);
 | 
			
		||||
@@ -143,29 +192,50 @@ public class ProbabilityUtil {
 | 
			
		||||
     * Returns the {@link Probability} for a specific {@link SubSkillType} for a specific {@link Player}.
 | 
			
		||||
     * This does not take into account perks such as lucky for the player.
 | 
			
		||||
     * This is affected by other plugins who can listen to the {@link SubSkillEvent} and cancel it or mutate it.
 | 
			
		||||
     * Null players will be treated as zero skill players.
 | 
			
		||||
     *
 | 
			
		||||
     * @param subSkillType the target subskill
 | 
			
		||||
     * @param player the target player
 | 
			
		||||
     *               can be null (null players have the worst odds)
 | 
			
		||||
     * @return the probability for this skill
 | 
			
		||||
     * @deprecated use {@link #getSkillProbability(SubSkillType, McMMOPlayer)} instead
 | 
			
		||||
     */
 | 
			
		||||
    @Deprecated(forRemoval = true)
 | 
			
		||||
    public static Probability getSkillProbability(@NotNull SubSkillType subSkillType, @Nullable Player player) {
 | 
			
		||||
        return getSkillProbability(subSkillType, UserManager.getPlayer(player));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the {@link Probability} for a specific {@link SubSkillType} for a specific {@link Player}.
 | 
			
		||||
     * This does not take into account perks such as lucky for the player.
 | 
			
		||||
     * This is affected by other plugins who can listen to the {@link SubSkillEvent} and cancel it or mutate it.
 | 
			
		||||
     * Null players will be treated as zero skill players.
 | 
			
		||||
     *
 | 
			
		||||
     * @param subSkillType the target subskill
 | 
			
		||||
     * @param mmoPlayer the target player
 | 
			
		||||
     *               can be null (null players have the worst odds)
 | 
			
		||||
     * @return the probability for this skill
 | 
			
		||||
     */
 | 
			
		||||
    public static Probability getSkillProbability(@NotNull SubSkillType subSkillType, @NotNull Player player) {
 | 
			
		||||
        //Process probability
 | 
			
		||||
        Probability probability = getSubSkillProbability(subSkillType, player);
 | 
			
		||||
    public static Probability getSkillProbability(@NotNull SubSkillType subSkillType, @Nullable McMMOPlayer mmoPlayer) {
 | 
			
		||||
        // Process probability
 | 
			
		||||
        Probability probability = getSubSkillProbability(subSkillType, mmoPlayer);
 | 
			
		||||
 | 
			
		||||
        //Send out event
 | 
			
		||||
        SubSkillEvent subSkillEvent = EventUtils.callSubSkillEvent(player, subSkillType);
 | 
			
		||||
        // Send out event
 | 
			
		||||
        if (mmoPlayer != null) {
 | 
			
		||||
            SubSkillEvent subSkillEvent = EventUtils.callSubSkillEvent(mmoPlayer, subSkillType);
 | 
			
		||||
 | 
			
		||||
        if(subSkillEvent.isCancelled()) {
 | 
			
		||||
            return Probability.ALWAYS_FAILS;
 | 
			
		||||
            if(subSkillEvent.isCancelled()) {
 | 
			
		||||
                return Probability.ALWAYS_FAILS;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Result modifier
 | 
			
		||||
            double resultModifier = subSkillEvent.getResultModifier();
 | 
			
		||||
 | 
			
		||||
            // Mutate probability
 | 
			
		||||
            if(resultModifier != 1.0D)
 | 
			
		||||
                probability = Probability.ofPercent(probability.getValue() * resultModifier);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        //Result modifier
 | 
			
		||||
        double resultModifier = subSkillEvent.getResultModifier();
 | 
			
		||||
 | 
			
		||||
        //Mutate probability
 | 
			
		||||
        if(resultModifier != 1.0D)
 | 
			
		||||
            probability = Probability.ofPercent(probability.getValue() * resultModifier);
 | 
			
		||||
 | 
			
		||||
        return probability;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -177,12 +247,11 @@ public class ProbabilityUtil {
 | 
			
		||||
     * @param player the target player can be null (null players have the worst odds)
 | 
			
		||||
     * @param probabilityPercentage the probability of this player succeeding in "percentage" format (0-100 inclusive)
 | 
			
		||||
     * @return true if the RNG succeeds, false if it fails
 | 
			
		||||
     * @deprecated use {@link #isStaticSkillRNGSuccessful(PrimarySkillType, McMMOPlayer, double)} instead
 | 
			
		||||
     */
 | 
			
		||||
    @Deprecated(forRemoval = true, since = "2.2.010")
 | 
			
		||||
    public static boolean isStaticSkillRNGSuccessful(@NotNull PrimarySkillType primarySkillType, @Nullable Player player, double probabilityPercentage) {
 | 
			
		||||
        //Grab a probability converted from a "percentage" value
 | 
			
		||||
        Probability probability = Probability.ofPercent(probabilityPercentage);
 | 
			
		||||
 | 
			
		||||
        return isStaticSkillRNGSuccessful(primarySkillType, player, probability);
 | 
			
		||||
        return isStaticSkillRNGSuccessful(primarySkillType, player, Probability.ofPercent(probabilityPercentage));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -190,12 +259,49 @@ public class ProbabilityUtil {
 | 
			
		||||
     * This helper method is specific to static value RNG, which can be influenced by a player's Luck
 | 
			
		||||
     *
 | 
			
		||||
     * @param primarySkillType the related primary skill
 | 
			
		||||
     * @param player the target player, can be null (null players have the worst odds)
 | 
			
		||||
     * @param probability the probability of this player succeeding
 | 
			
		||||
     * @param mmoPlayer the target player can be null (null players have the worst odds)
 | 
			
		||||
     * @param probabilityPercentage the probability of this player succeeding in "percentage" format (0-100 inclusive)
 | 
			
		||||
     * @return true if the RNG succeeds, false if it fails
 | 
			
		||||
     */
 | 
			
		||||
    public static boolean isStaticSkillRNGSuccessful(@NotNull PrimarySkillType primarySkillType, @Nullable Player player, @NotNull Probability probability) {
 | 
			
		||||
        boolean isLucky = player != null && Permissions.lucky(player, primarySkillType);
 | 
			
		||||
    public static boolean isStaticSkillRNGSuccessful(@NotNull PrimarySkillType primarySkillType,
 | 
			
		||||
                                                     @Nullable McMMOPlayer mmoPlayer, double probabilityPercentage) {
 | 
			
		||||
        //Grab a probability converted from a "percentage" value
 | 
			
		||||
        final Probability probability = Probability.ofPercent(probabilityPercentage);
 | 
			
		||||
 | 
			
		||||
        return isStaticSkillRNGSuccessful(primarySkillType, mmoPlayer, probability);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * This is one of several Skill RNG check methods
 | 
			
		||||
     * This helper method is specific to static value RNG, which can be influenced by a player's Luck
 | 
			
		||||
     *
 | 
			
		||||
     * @param primarySkillType the related primary skill
 | 
			
		||||
     * @param player the target player
 | 
			
		||||
     *              can be null (null players have the worst odds)
 | 
			
		||||
     * @param probability the probability of this player succeeding
 | 
			
		||||
     * @return true if the RNG succeeds, false if it fails
 | 
			
		||||
     * @deprecated use {@link #isStaticSkillRNGSuccessful(PrimarySkillType, McMMOPlayer, Probability)} instead, this
 | 
			
		||||
     * method is redundant and will be removed.
 | 
			
		||||
     */
 | 
			
		||||
    @Deprecated(forRemoval = true, since = "2.2.010")
 | 
			
		||||
    public static boolean isStaticSkillRNGSuccessful(@NotNull PrimarySkillType primarySkillType,
 | 
			
		||||
                                                     @Nullable Player player, @NotNull Probability probability) {
 | 
			
		||||
        return isStaticSkillRNGSuccessful(primarySkillType, UserManager.getPlayer(player), probability);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * This is one of several Skill RNG check methods
 | 
			
		||||
     * This helper method is specific to static value RNG, which can be influenced by a mmoPlayer's Luck
 | 
			
		||||
     *
 | 
			
		||||
     * @param primarySkillType the related primary skill
 | 
			
		||||
     * @param mmoPlayer the target mmoPlayer
 | 
			
		||||
     *              can be null (null players have the worst odds)
 | 
			
		||||
     * @param probability the probability of this mmoPlayer succeeding
 | 
			
		||||
     * @return true if the RNG succeeds, false if it fails
 | 
			
		||||
     */
 | 
			
		||||
    public static boolean isStaticSkillRNGSuccessful(@NotNull PrimarySkillType primarySkillType,
 | 
			
		||||
                                                     @Nullable McMMOPlayer mmoPlayer, @NotNull Probability probability) {
 | 
			
		||||
        boolean isLucky = mmoPlayer != null && Permissions.lucky(mmoPlayer.getPlayer(), primarySkillType);
 | 
			
		||||
 | 
			
		||||
        if(isLucky) {
 | 
			
		||||
            return probability.evaluate(LUCKY_MODIFIER);
 | 
			
		||||
@@ -209,25 +315,57 @@ public class ProbabilityUtil {
 | 
			
		||||
     * @param subSkillType target subskill
 | 
			
		||||
     * @param player target player
 | 
			
		||||
     * @return true if the skill succeeds (wasn't cancelled by any other plugin)
 | 
			
		||||
     * @deprecated use {@link #isNonRNGSkillActivationSuccessful(SubSkillType, McMMOPlayer)} instead
 | 
			
		||||
     */
 | 
			
		||||
    @Deprecated(forRemoval = true, since = "2.2.010")
 | 
			
		||||
    public static boolean isNonRNGSkillActivationSuccessful(@NotNull SubSkillType subSkillType, @NotNull Player player) {
 | 
			
		||||
        return !EventUtils.callSubSkillEvent(player, subSkillType).isCancelled();
 | 
			
		||||
        return isNonRNGSkillActivationSuccessful(subSkillType, requireNonNull(UserManager.getPlayer(player)));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Grab the {@link Probability} for a specific {@link SubSkillType} for a specific {@link Player}
 | 
			
		||||
     *
 | 
			
		||||
     * Skills activate without RNG, this allows other plugins to prevent that activation
 | 
			
		||||
     * @param subSkillType target subskill
 | 
			
		||||
     * @param player target player
 | 
			
		||||
     * @return the Probability of this skill succeeding
 | 
			
		||||
     * @param mmoPlayer target player
 | 
			
		||||
     * @return true if the skill succeeds (wasn't cancelled by any other plugin)
 | 
			
		||||
     */
 | 
			
		||||
    public static @NotNull Probability getSubSkillProbability(@NotNull SubSkillType subSkillType, @Nullable Player player) {
 | 
			
		||||
    public static boolean isNonRNGSkillActivationSuccessful(@NotNull SubSkillType subSkillType,
 | 
			
		||||
                                                            @NotNull McMMOPlayer mmoPlayer) {
 | 
			
		||||
        return !EventUtils.callSubSkillEvent(mmoPlayer, subSkillType).isCancelled();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Retrieves the {@link Probability} of success for a specified {@link SubSkillType} for a given {@link Player}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param subSkillType The targeted subskill.
 | 
			
		||||
     * @param player The player in question.
 | 
			
		||||
     *               If null, the method treats it as a player with no levels or luck and calculates the probability
 | 
			
		||||
     *               accordingly.
 | 
			
		||||
     * @return The probability that the specified skill will succeed.
 | 
			
		||||
     * @deprecated use {@link #getSubSkillProbability(SubSkillType, McMMOPlayer)} instead
 | 
			
		||||
     */
 | 
			
		||||
    @Deprecated(forRemoval = true, since = "2.2.010")
 | 
			
		||||
    public static @NotNull Probability getSubSkillProbability(@NotNull SubSkillType subSkillType,
 | 
			
		||||
                                                              @Nullable Player player) {
 | 
			
		||||
        return ProbabilityUtil.ofSubSkill(player, subSkillType);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static @NotNull String[] getRNGDisplayValues(@NotNull Player player, @NotNull SubSkillType subSkill) {
 | 
			
		||||
        double firstValue = chanceOfSuccessPercentage(player, subSkill, false);
 | 
			
		||||
        double secondValue = chanceOfSuccessPercentage(player, subSkill, true);
 | 
			
		||||
    /**
 | 
			
		||||
     * Retrieves the {@link Probability} of success for a specified {@link SubSkillType} for a given {@link Player}.
 | 
			
		||||
     *
 | 
			
		||||
     * @param subSkillType The targeted subskill.
 | 
			
		||||
     * @param mmoPlayer The player in question.
 | 
			
		||||
     *               If null, the method treats it as a player with no levels or luck and calculates the probability
 | 
			
		||||
     *               accordingly.
 | 
			
		||||
     * @return The probability that the specified skill will succeed.
 | 
			
		||||
     */
 | 
			
		||||
    public static @NotNull Probability getSubSkillProbability(@NotNull SubSkillType subSkillType,
 | 
			
		||||
                                                              @Nullable McMMOPlayer mmoPlayer) {
 | 
			
		||||
        return ProbabilityUtil.ofSubSkill(mmoPlayer, subSkillType);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static @NotNull String[] getRNGDisplayValues(@Nullable McMMOPlayer mmoPlayer, @NotNull SubSkillType subSkill) {
 | 
			
		||||
        double firstValue = chanceOfSuccessPercentage(mmoPlayer, subSkill, false);
 | 
			
		||||
        double secondValue = chanceOfSuccessPercentage(mmoPlayer, subSkill, true);
 | 
			
		||||
 | 
			
		||||
        return new String[]{percent.format(firstValue), percent.format(secondValue)};
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,6 @@ import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
 | 
			
		||||
import com.gmail.nossr50.datatypes.skills.SubSkillType;
 | 
			
		||||
import com.gmail.nossr50.mcMMO;
 | 
			
		||||
import com.gmail.nossr50.metadata.MobMetaFlagType;
 | 
			
		||||
import com.gmail.nossr50.metadata.MobMetadataService;
 | 
			
		||||
import com.gmail.nossr50.runnables.skills.AwardCombatXpTask;
 | 
			
		||||
import com.gmail.nossr50.skills.acrobatics.AcrobaticsManager;
 | 
			
		||||
import com.gmail.nossr50.skills.archery.ArcheryManager;
 | 
			
		||||
@@ -37,14 +36,12 @@ import org.jetbrains.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
import static com.gmail.nossr50.util.MobMetadataUtils.hasMobFlag;
 | 
			
		||||
 | 
			
		||||
public final class CombatUtils {
 | 
			
		||||
 | 
			
		||||
    private CombatUtils() {}
 | 
			
		||||
 | 
			
		||||
    private static @NotNull MobMetadataService getMobMetadataService() {
 | 
			
		||||
        return mcMMO.getMetadataService().getMobMetadataService();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static boolean isDamageLikelyFromNormalCombat(@NotNull DamageCause damageCause) {
 | 
			
		||||
        return switch (damageCause) {
 | 
			
		||||
            case ENTITY_ATTACK, ENTITY_SWEEP_ATTACK, PROJECTILE -> true;
 | 
			
		||||
@@ -926,17 +923,17 @@ public final class CombatUtils {
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if(getMobMetadataService().hasMobFlag(MobMetaFlagType.COTW_SUMMONED_MOB, target)) {
 | 
			
		||||
            if(hasMobFlag(MobMetaFlagType.COTW_SUMMONED_MOB, target)) {
 | 
			
		||||
                baseXP = 0;
 | 
			
		||||
            } else if(getMobMetadataService().hasMobFlag(MobMetaFlagType.MOB_SPAWNER_MOB, target) || target.hasMetadata("ES")) {
 | 
			
		||||
            } else if(hasMobFlag(MobMetaFlagType.MOB_SPAWNER_MOB, target) || target.hasMetadata("ES")) {
 | 
			
		||||
                baseXP *= ExperienceConfig.getInstance().getSpawnedMobXpMultiplier();
 | 
			
		||||
            } else if(getMobMetadataService().hasMobFlag(MobMetaFlagType.NETHER_PORTAL_MOB, target)) {
 | 
			
		||||
            } else if(hasMobFlag(MobMetaFlagType.NETHER_PORTAL_MOB, target)) {
 | 
			
		||||
                baseXP *= ExperienceConfig.getInstance().getNetherPortalXpMultiplier();
 | 
			
		||||
            } else if(getMobMetadataService().hasMobFlag(MobMetaFlagType.EGG_MOB, target)) {
 | 
			
		||||
            } else if(hasMobFlag(MobMetaFlagType.EGG_MOB, target)) {
 | 
			
		||||
                baseXP *= ExperienceConfig.getInstance().getEggXpMultiplier();
 | 
			
		||||
            } else if (getMobMetadataService().hasMobFlag(MobMetaFlagType.PLAYER_BRED_MOB, target)) {
 | 
			
		||||
            } else if (hasMobFlag(MobMetaFlagType.PLAYER_BRED_MOB, target)) {
 | 
			
		||||
                baseXP *= ExperienceConfig.getInstance().getBredMobXpMultiplier();
 | 
			
		||||
            } else if(getMobMetadataService().hasMobFlag(MobMetaFlagType.PLAYER_TAMED_MOB, target)) {
 | 
			
		||||
            } else if(hasMobFlag(MobMetaFlagType.PLAYER_TAMED_MOB, target)) {
 | 
			
		||||
                baseXP *= ExperienceConfig.getInstance().getTamedMobXpMultiplier();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
package com.gmail.nossr50.util.skills;
 | 
			
		||||
 | 
			
		||||
import com.gmail.nossr50.config.experience.ExperienceConfig;
 | 
			
		||||
import com.gmail.nossr50.datatypes.player.McMMOPlayer;
 | 
			
		||||
import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
 | 
			
		||||
import com.gmail.nossr50.events.skills.SkillActivationPerkEvent;
 | 
			
		||||
import com.gmail.nossr50.util.Permissions;
 | 
			
		||||
@@ -106,4 +107,19 @@ public final class PerksUtils {
 | 
			
		||||
 | 
			
		||||
        return NORMAL_SKILL_ACTIVATION_CHANCE;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Calculate activation chance for a skill.
 | 
			
		||||
     *
 | 
			
		||||
     * @param mmoPlayer Player to check the activation chance for
 | 
			
		||||
     * @param skill PrimarySkillType to check the activation chance of
 | 
			
		||||
     * @return the activation chance with "lucky perk" accounted for
 | 
			
		||||
     */
 | 
			
		||||
    public static int handleLuckyPerks(McMMOPlayer mmoPlayer, PrimarySkillType skill) {
 | 
			
		||||
        if (Permissions.lucky(mmoPlayer.getPlayer(), skill)) {
 | 
			
		||||
            return LUCKY_SKILL_ACTIVATION_CHANCE;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return NORMAL_SKILL_ACTIVATION_CHANCE;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@ 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.metadata.ItemMetadataService;
 | 
			
		||||
import com.gmail.nossr50.util.ItemMetadataUtils;
 | 
			
		||||
import com.gmail.nossr50.util.ItemUtils;
 | 
			
		||||
import com.gmail.nossr50.util.Misc;
 | 
			
		||||
import com.gmail.nossr50.util.Permissions;
 | 
			
		||||
@@ -32,6 +32,7 @@ import org.jetbrains.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
import java.util.Iterator;
 | 
			
		||||
 | 
			
		||||
import static com.gmail.nossr50.util.ItemMetadataUtils.*;
 | 
			
		||||
import static com.gmail.nossr50.util.PotionEffectMapper.getHaste;
 | 
			
		||||
 | 
			
		||||
public final class SkillUtils {
 | 
			
		||||
@@ -152,7 +153,7 @@ public final class SkillUtils {
 | 
			
		||||
            ItemUtils.addDigSpeedToItem(heldItem, heldItem.getEnchantmentLevel(mcMMO.p.getEnchantmentMapper().getEfficiency()));
 | 
			
		||||
 | 
			
		||||
            //1.13.2+ will have persistent metadata for this item
 | 
			
		||||
            mcMMO.getMetadataService().getItemMetadataService().setSuperAbilityBoostedItem(heldItem, originalDigSpeed);
 | 
			
		||||
            ItemMetadataUtils.setSuperAbilityBoostedItem(heldItem, originalDigSpeed);
 | 
			
		||||
        } else {
 | 
			
		||||
            int duration = 0;
 | 
			
		||||
            int amplifier = 0;
 | 
			
		||||
@@ -209,9 +210,7 @@ public final class SkillUtils {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        //1.13.2+ will have persistent metadata for this itemStack
 | 
			
		||||
        ItemMetadataService itemMetadataService = mcMMO.getMetadataService().getItemMetadataService();
 | 
			
		||||
 | 
			
		||||
        if(itemMetadataService.isLegacyAbilityTool(itemStack)) {
 | 
			
		||||
        if(isLegacyAbilityTool(itemStack)) {
 | 
			
		||||
            ItemMeta itemMeta = itemStack.getItemMeta();
 | 
			
		||||
 | 
			
		||||
            if(itemMeta != null) {
 | 
			
		||||
@@ -223,8 +222,8 @@ public final class SkillUtils {
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(itemMetadataService.isSuperAbilityBoosted(itemStack)) {
 | 
			
		||||
            itemMetadataService.removeBonusDigSpeedOnSuperAbilityTool(itemStack);
 | 
			
		||||
        if(isSuperAbilityBoosted(itemStack)) {
 | 
			
		||||
            removeBonusDigSpeedOnSuperAbilityTool(itemStack);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,102 +0,0 @@
 | 
			
		||||
package com.gmail.nossr50.util.skills;
 | 
			
		||||
 | 
			
		||||
import com.gmail.nossr50.datatypes.player.McMMOPlayer;
 | 
			
		||||
import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
 | 
			
		||||
import com.gmail.nossr50.mcMMO;
 | 
			
		||||
import com.gmail.nossr50.util.player.UserManager;
 | 
			
		||||
import org.bukkit.Bukkit;
 | 
			
		||||
import org.bukkit.ChatColor;
 | 
			
		||||
import org.bukkit.OfflinePlayer;
 | 
			
		||||
import org.bukkit.block.Furnace;
 | 
			
		||||
import org.bukkit.entity.Player;
 | 
			
		||||
import org.bukkit.inventory.FurnaceInventory;
 | 
			
		||||
import org.bukkit.inventory.Inventory;
 | 
			
		||||
import org.jetbrains.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
import java.util.UUID;
 | 
			
		||||
 | 
			
		||||
public class SmeltingTracker {
 | 
			
		||||
 | 
			
		||||
//    private final HashMap<Furnace, OfflinePlayer> furnaceOwners;
 | 
			
		||||
 | 
			
		||||
    private void changeFurnaceOwnership(Furnace furnace, Player player) {
 | 
			
		||||
 | 
			
		||||
        McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player);
 | 
			
		||||
 | 
			
		||||
        /*
 | 
			
		||||
            Debug output
 | 
			
		||||
         */
 | 
			
		||||
        printOwnershipGainDebug(furnace, mcMMOPlayer);
 | 
			
		||||
 | 
			
		||||
        printOwnershipLossDebug(furnace);
 | 
			
		||||
 | 
			
		||||
        setFurnaceOwner(furnace, player);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void setFurnaceOwner(Furnace furnace, Player player) {
 | 
			
		||||
        mcMMO.getMetadataService().getBlockMetadataService().setFurnaceOwner(furnace, player.getUniqueId());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void printOwnershipGainDebug(Furnace furnace, McMMOPlayer mcMMOPlayer) {
 | 
			
		||||
        if(mcMMOPlayer != null) {
 | 
			
		||||
            if(mcMMOPlayer.isDebugMode()) {
 | 
			
		||||
                mcMMOPlayer.getPlayer().sendMessage("Furnace ownership " +
 | 
			
		||||
                        ChatColor.GREEN +"gained " + ChatColor.RESET +
 | 
			
		||||
                        "at location: " + furnace.getLocation().toString());
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void printOwnershipLossDebug(Furnace furnace) {
 | 
			
		||||
        OfflinePlayer furnaceOwner = getFurnaceOwner(furnace);
 | 
			
		||||
 | 
			
		||||
        if(furnaceOwner != null && furnaceOwner.isOnline()) {
 | 
			
		||||
            McMMOPlayer furnaceOwnerProfile = UserManager.getPlayer(furnaceOwner.getPlayer());
 | 
			
		||||
 | 
			
		||||
            if(furnaceOwnerProfile != null) {
 | 
			
		||||
                if(furnaceOwnerProfile.isDebugMode()) {
 | 
			
		||||
                    furnaceOwnerProfile.getPlayer().sendMessage("Furnace ownership " +
 | 
			
		||||
                            ChatColor.RED + "lost " + ChatColor.RESET +
 | 
			
		||||
                            "at location: " + furnace.getLocation().toString());
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public @Nullable OfflinePlayer getFurnaceOwner(Furnace furnace) {
 | 
			
		||||
        UUID uuid = mcMMO.getMetadataService().getBlockMetadataService().getFurnaceOwner(furnace);
 | 
			
		||||
 | 
			
		||||
        if(uuid != null) {
 | 
			
		||||
            return Bukkit.getOfflinePlayer(uuid);
 | 
			
		||||
        } else {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Nullable
 | 
			
		||||
    public Furnace getFurnaceFromInventory(Inventory inventory) {
 | 
			
		||||
        if (!(inventory instanceof FurnaceInventory)) {
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return (Furnace) inventory.getHolder();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public boolean isFurnaceOwned(Furnace furnace) {
 | 
			
		||||
        return getFurnaceOwner(furnace) != null;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public void processFurnaceOwnership(Furnace furnace, Player player) {
 | 
			
		||||
        if(!mcMMO.p.getSkillTools().doesPlayerHaveSkillPermission(player, PrimarySkillType.SMELTING))
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        //Don't swap ownership if its the same player
 | 
			
		||||
        if(getFurnaceOwner(furnace) != null) {
 | 
			
		||||
            if(getFurnaceOwner(furnace).getUniqueId().equals(player.getUniqueId()))
 | 
			
		||||
                return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        changeFurnaceOwnership(furnace, player);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -10,6 +10,7 @@ import com.gmail.nossr50.listeners.InteractionManager;
 | 
			
		||||
import com.gmail.nossr50.locale.LocaleLoader;
 | 
			
		||||
import com.gmail.nossr50.mcMMO;
 | 
			
		||||
import com.gmail.nossr50.util.Permissions;
 | 
			
		||||
import com.gmail.nossr50.util.player.UserManager;
 | 
			
		||||
import com.gmail.nossr50.util.skills.RankUtils;
 | 
			
		||||
import net.kyori.adventure.audience.Audience;
 | 
			
		||||
import net.kyori.adventure.text.Component;
 | 
			
		||||
@@ -345,7 +346,8 @@ public class TextComponentFactory {
 | 
			
		||||
            componentBuilder.append(Component.newline());
 | 
			
		||||
 | 
			
		||||
            //Finally, add details to the tooltip
 | 
			
		||||
            abstractSubSkill.addStats(componentBuilder, player);
 | 
			
		||||
            // TODO: pass in McMMOPlayer instead
 | 
			
		||||
            abstractSubSkill.addStats(componentBuilder, UserManager.getPlayer(player));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return componentBuilder.build();
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user