mirror of
				https://github.com/mcMMO-Dev/mcMMO.git
				synced 2025-11-04 11:03:43 +01:00 
			
		
		
		
	fix probability being unbounded
This commit is contained in:
		@@ -1,10 +1,21 @@
 | 
			
		||||
package com.gmail.nossr50.util.random;
 | 
			
		||||
 | 
			
		||||
import com.gmail.nossr50.api.exceptions.ValueOutOfBoundsException;
 | 
			
		||||
import org.jetbrains.annotations.NotNull;
 | 
			
		||||
 | 
			
		||||
import java.util.concurrent.ThreadLocalRandom;
 | 
			
		||||
 | 
			
		||||
public interface Probability {
 | 
			
		||||
    /**
 | 
			
		||||
     * A Probability that always fails.
 | 
			
		||||
     */
 | 
			
		||||
    Probability ALWAYS_FAILS = () -> 0;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * A Probability that always succeeds.
 | 
			
		||||
     */
 | 
			
		||||
    Probability ALWAYS_SUCCEEDS = () -> 1;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The value of this Probability
 | 
			
		||||
     * Should return a result between 0 and 1 (inclusive)
 | 
			
		||||
@@ -17,19 +28,40 @@ public interface Probability {
 | 
			
		||||
    double getValue();
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a new Probability with the given value
 | 
			
		||||
     * A value of 100 would represent 100% chance of success
 | 
			
		||||
     * A value of 50 would represent 50% chance of success
 | 
			
		||||
     * A value of 0 would represent 0% chance of success
 | 
			
		||||
     * A value of 1 would represent 1% chance of success
 | 
			
		||||
     * A value of 0.5 would represent 0.5% chance of success
 | 
			
		||||
     * A value of 0.01 would represent 0.01% chance of success
 | 
			
		||||
     * Create a new Probability of a percentage.
 | 
			
		||||
     * This method takes a percentage and creates a Probability of equivalent odds.
 | 
			
		||||
     *
 | 
			
		||||
     * A value of 100 would represent 100% chance of success,
 | 
			
		||||
     * A value of 50 would represent 50% chance of success,
 | 
			
		||||
     * A value of 0 would represent 0% chance of success,
 | 
			
		||||
     * A value of 1 would represent 1% chance of success,
 | 
			
		||||
     * A value of 0.5 would represent 0.5% chance of success,
 | 
			
		||||
     * A value of 0.01 would represent 0.01% chance of success.
 | 
			
		||||
     *
 | 
			
		||||
     * @param percentage the value of the probability
 | 
			
		||||
     * @return a new Probability with the given value
 | 
			
		||||
     */
 | 
			
		||||
    static @NotNull Probability ofPercent(double percentage) {
 | 
			
		||||
        return new ProbabilityImpl(percentage);
 | 
			
		||||
        if (percentage < 0) {
 | 
			
		||||
            throw new ValueOutOfBoundsException("Value should never be negative for Probability! This suggests a coding mistake, contact the devs!");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Convert to a 0-1 floating point representation
 | 
			
		||||
        double probabilityValue = percentage / 100.0D;
 | 
			
		||||
        return new ProbabilityImpl(probabilityValue);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a new Probability of a value.
 | 
			
		||||
     * This method takes a value between 0 and 1 and creates a Probability of equivalent odds.
 | 
			
		||||
     * A value of 1 or greater represents something that will always succeed.
 | 
			
		||||
     * A value of around 0.5 represents something that succeeds around half the time.
 | 
			
		||||
     * A value of 0 represents something that will always fail.
 | 
			
		||||
     * @param value the value of the probability
 | 
			
		||||
     * @return a new Probability with the given value
 | 
			
		||||
     */
 | 
			
		||||
    static @NotNull Probability ofValue(double value) {
 | 
			
		||||
        return new ProbabilityImpl(value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -8,31 +8,22 @@ public class ProbabilityImpl implements Probability {
 | 
			
		||||
    private final double probabilityValue;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Create a probability with a static value
 | 
			
		||||
     * Create a probability from a static value.
 | 
			
		||||
     * A value of 0 represents a 0% chance of success,
 | 
			
		||||
     * A value of 1 represents a 100% chance of success.
 | 
			
		||||
     * A value of 0.5 represents a 50% chance of success.
 | 
			
		||||
     * A value of 0.01 represents a 1% chance of success.
 | 
			
		||||
     * And so on.
 | 
			
		||||
     *
 | 
			
		||||
     * @param percentage the percentage value of the probability
 | 
			
		||||
     * @param value the value of the probability between 0 and 100
 | 
			
		||||
     */
 | 
			
		||||
    ProbabilityImpl(double percentage) throws ValueOutOfBoundsException {
 | 
			
		||||
        if (percentage < 0) {
 | 
			
		||||
            throw new ValueOutOfBoundsException("Value should never be negative for Probability! This suggests a coding mistake, contact the devs!");
 | 
			
		||||
    public ProbabilityImpl(double value) throws ValueOutOfBoundsException {
 | 
			
		||||
        if (value < 0) {
 | 
			
		||||
            throw new ValueOutOfBoundsException("Value should never be negative for Probability!" +
 | 
			
		||||
                    " This suggests a coding mistake, contact the devs!");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Convert to a 0-1 floating point representation
 | 
			
		||||
        probabilityValue = percentage / 100.0D;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ProbabilityImpl(double xPos, double xCeiling, double probabilityCeiling) throws ValueOutOfBoundsException {
 | 
			
		||||
        if(probabilityCeiling > 100) {
 | 
			
		||||
            throw new ValueOutOfBoundsException("Probability Ceiling should never be above 100!");
 | 
			
		||||
        } else if (probabilityCeiling < 0) {
 | 
			
		||||
            throw new ValueOutOfBoundsException("Probability Ceiling should never be below 0!");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        //Get the percent success, this will be from 0-100
 | 
			
		||||
        double probabilityPercent = (probabilityCeiling * (xPos / xCeiling));
 | 
			
		||||
 | 
			
		||||
        //Convert to a 0-1 floating point representation
 | 
			
		||||
        this.probabilityValue = probabilityPercent / 100.0D;
 | 
			
		||||
        probabilityValue = value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
 
 | 
			
		||||
@@ -79,24 +79,24 @@ public class ProbabilityUtil {
 | 
			
		||||
        switch (getProbabilityType(subSkillType)) {
 | 
			
		||||
            case DYNAMIC_CONFIGURABLE:
 | 
			
		||||
                double probabilityCeiling;
 | 
			
		||||
                double xCeiling;
 | 
			
		||||
                double xPos;
 | 
			
		||||
                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);
 | 
			
		||||
                    }
 | 
			
		||||
                    xPos = mmoPlayer.getSkillLevel(subSkillType.getParentSkill());
 | 
			
		||||
                    skillLevel = mmoPlayer.getSkillLevel(subSkillType.getParentSkill());
 | 
			
		||||
                } else {
 | 
			
		||||
                    xPos = 0;
 | 
			
		||||
                    skillLevel = 0;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                //Probability ceiling is configurable in this type
 | 
			
		||||
                probabilityCeiling = mcMMO.p.getAdvancedConfig().getMaximumProbability(subSkillType);
 | 
			
		||||
                //The xCeiling is configurable in this type
 | 
			
		||||
                xCeiling = mcMMO.p.getAdvancedConfig().getMaxBonusLevel(subSkillType);
 | 
			
		||||
                return new ProbabilityImpl(xPos, xCeiling, probabilityCeiling);
 | 
			
		||||
                maxBonusLevel = mcMMO.p.getAdvancedConfig().getMaxBonusLevel(subSkillType);
 | 
			
		||||
                return calculateCurrentSkillProbability(skillLevel, 0, probabilityCeiling, maxBonusLevel);
 | 
			
		||||
            case STATIC_CONFIGURABLE:
 | 
			
		||||
                try {
 | 
			
		||||
                    return getStaticRandomChance(subSkillType);
 | 
			
		||||
@@ -127,22 +127,7 @@ public class ProbabilityUtil {
 | 
			
		||||
     * @return true if the Skill RNG succeeds, false if it fails
 | 
			
		||||
     */
 | 
			
		||||
    public static boolean isSkillRNGSuccessful(@NotNull SubSkillType subSkillType, @NotNull Player player) {
 | 
			
		||||
        //Process probability
 | 
			
		||||
        Probability probability = getSubSkillProbability(subSkillType, player);
 | 
			
		||||
 | 
			
		||||
        //Send out event
 | 
			
		||||
        SubSkillEvent subSkillEvent = EventUtils.callSubSkillEvent(player, subSkillType);
 | 
			
		||||
 | 
			
		||||
        if(subSkillEvent.isCancelled()) {
 | 
			
		||||
            return false; //Event got cancelled so this doesn't succeed
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        //Result modifier
 | 
			
		||||
        double resultModifier = subSkillEvent.getResultModifier();
 | 
			
		||||
 | 
			
		||||
        //Mutate probability
 | 
			
		||||
        if(resultModifier != 1.0D)
 | 
			
		||||
            probability = Probability.ofPercent(probability.getValue() * resultModifier);
 | 
			
		||||
        final Probability probability = getSkillProbability(subSkillType, player);
 | 
			
		||||
 | 
			
		||||
        //Luck
 | 
			
		||||
        boolean isLucky = Permissions.lucky(player, subSkillType.getParentSkill());
 | 
			
		||||
@@ -154,12 +139,42 @@ 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.
 | 
			
		||||
     *
 | 
			
		||||
     * @param subSkillType the target subskill
 | 
			
		||||
     * @param player the target player
 | 
			
		||||
     * @return the probability for this skill
 | 
			
		||||
     */
 | 
			
		||||
    public static Probability getSkillProbability(@NotNull SubSkillType subSkillType, @NotNull Player player) {
 | 
			
		||||
        //Process probability
 | 
			
		||||
        Probability probability = getSubSkillProbability(subSkillType, player);
 | 
			
		||||
 | 
			
		||||
        //Send out event
 | 
			
		||||
        SubSkillEvent subSkillEvent = EventUtils.callSubSkillEvent(player, subSkillType);
 | 
			
		||||
 | 
			
		||||
        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);
 | 
			
		||||
 | 
			
		||||
        return 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 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
 | 
			
		||||
     */
 | 
			
		||||
@@ -223,4 +238,31 @@ public class ProbabilityUtil {
 | 
			
		||||
 | 
			
		||||
        return new String[]{percent.format(firstValue), percent.format(secondValue)};
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Helper function to calculate what probability a given skill has at a certain level
 | 
			
		||||
     * @param skillLevel the skill level currently between the floor and the ceiling
 | 
			
		||||
     * @param floor the minimum odds this skill can have
 | 
			
		||||
     * @param ceiling the maximum odds this skill can have
 | 
			
		||||
     * @param maxBonusLevel the maximum level this skill can have to reach the ceiling
 | 
			
		||||
     *
 | 
			
		||||
     * @return the probability of success for this skill at this level
 | 
			
		||||
     */
 | 
			
		||||
    public static Probability calculateCurrentSkillProbability(double skillLevel, double floor,
 | 
			
		||||
                                                               double ceiling, double maxBonusLevel) {
 | 
			
		||||
        // The odds of success are between the value of the floor and the value of the ceiling.
 | 
			
		||||
        // If the skill has a maxBonusLevel of 500 on this skill, then at skill level 500 you would have the full odds,
 | 
			
		||||
        // at skill level 250 it would be half odds.
 | 
			
		||||
 | 
			
		||||
        if (skillLevel >= maxBonusLevel || maxBonusLevel <= 0) {
 | 
			
		||||
            // Avoid divide by zero bugs
 | 
			
		||||
            // Max benefit has been reached, should always succeed
 | 
			
		||||
            return Probability.ofPercent(ceiling);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        double odds = (skillLevel / maxBonusLevel) * 100D;
 | 
			
		||||
 | 
			
		||||
        // make sure the odds aren't lower or higher than the floor or ceiling
 | 
			
		||||
        return Probability.ofPercent(Math.min(Math.max(floor, odds), ceiling));
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user