mirror of
https://github.com/mcMMO-Dev/mcMMO.git
synced 2025-08-01 20:15:28 +02:00
RandomChanceUtil refactor part 1
This commit is contained in:
@@ -8,7 +8,6 @@ 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.random.RandomChanceSkill;
|
||||
import com.gmail.nossr50.util.random.RandomChanceUtil;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.block.Block;
|
||||
@@ -55,7 +54,7 @@ public final class BlockUtils {
|
||||
*/
|
||||
public static boolean checkDoubleDrops(Player player, BlockState blockState, PrimarySkillType skillType, SubSkillType subSkillType) {
|
||||
if (Config.getInstance().getDoubleDropsEnabled(skillType, blockState.getType()) && Permissions.isSubSkillEnabled(player, subSkillType)) {
|
||||
return RandomChanceUtil.checkRandomChanceExecutionSuccess(new RandomChanceSkill(player, subSkillType, true));
|
||||
return RandomChanceUtil.processProbabilityResults(new SkillProbabilityWrapper(player, subSkillType, true));
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@@ -344,4 +344,26 @@ public final class Misc {
|
||||
experienceOrb.setExperience(orbExpValue);
|
||||
}
|
||||
}
|
||||
|
||||
// public static void hackyUnitTest(@NotNull McMMOPlayer normalPlayer) {
|
||||
// mcMMO.p.getLogger().info("Starting hacky unit test...");
|
||||
// int iterations = 1000000;
|
||||
// double ratioDivisor = 10000; //10000 because we run the test 1,000,000 times
|
||||
// double expectedFailRate = 100.0D - RandomChanceUtil.getRandomChanceExecutionSuccess(normalPlayer.getPlayer(), SubSkillType.MINING_MOTHER_LODE, true);
|
||||
//
|
||||
// double win = 0, loss = 0;
|
||||
// for(int x = 0; x < iterations; x++) {
|
||||
// if(RandomChanceUtil.checkRandomChanceExecutionSuccess(normalPlayer.getPlayer(), SubSkillType.MINING_MOTHER_LODE, true)) {
|
||||
// win++;
|
||||
// } else {
|
||||
// loss++;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// double lossRatio = (loss / ratioDivisor);
|
||||
// mcMMO.p.getLogger().info("Expected Fail Rate: "+expectedFailRate);
|
||||
// mcMMO.p.getLogger().info("Loss Ratio for hacky test: "+lossRatio);
|
||||
//// Assert.assertEquals(lossRatio, expectedFailRate, 0.01D);
|
||||
// }
|
||||
|
||||
}
|
||||
|
14
src/main/java/com/gmail/nossr50/util/random/Probability.java
Normal file
14
src/main/java/com/gmail/nossr50/util/random/Probability.java
Normal file
@@ -0,0 +1,14 @@
|
||||
package com.gmail.nossr50.util.random;
|
||||
|
||||
public interface Probability {
|
||||
/**
|
||||
* The value of this Probability
|
||||
* Should return a result between 0 and 1 (inclusive)
|
||||
* 1 should represent something that will always succeed
|
||||
* 0.5 should represent something that succeeds around half the time
|
||||
* etc
|
||||
*
|
||||
* @return the value of probability
|
||||
*/
|
||||
double getValue();
|
||||
}
|
@@ -0,0 +1,77 @@
|
||||
package com.gmail.nossr50.util.random;
|
||||
|
||||
import com.gmail.nossr50.config.AdvancedConfig;
|
||||
import com.gmail.nossr50.datatypes.player.McMMOPlayer;
|
||||
import com.gmail.nossr50.datatypes.skills.SubSkillType;
|
||||
import com.gmail.nossr50.util.player.UserManager;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class ProbabilityFactory {
|
||||
|
||||
public static @NotNull Probability ofPercentageValue(double percentageValue) {
|
||||
return new ProbabilityImpl(probabilityFromPercent(percentageValue));
|
||||
}
|
||||
|
||||
public static @NotNull Probability ofSubSkill(@Nullable Player player,
|
||||
@NotNull SubSkillType subSkillType,
|
||||
@NotNull SkillProbabilityType skillProbabilityType) throws InvalidStaticChance, RuntimeException {
|
||||
|
||||
switch (skillProbabilityType) {
|
||||
case DYNAMIC_CONFIGURABLE:
|
||||
double probabilityCeiling;
|
||||
double xCeiling;
|
||||
double xPos;
|
||||
|
||||
if (player != null) {
|
||||
McMMOPlayer mmoPlayer = UserManager.getPlayer(player);
|
||||
if(mmoPlayer != null)
|
||||
xPos = mmoPlayer.getSkillLevel(subSkillType.getParentSkill());
|
||||
else
|
||||
xPos = 0;
|
||||
} else {
|
||||
xPos = 0;
|
||||
}
|
||||
|
||||
//Probability ceiling is configurable in this type
|
||||
probabilityCeiling = AdvancedConfig.getInstance().getMaximumProbability(subSkillType);
|
||||
//The xCeiling is configurable in this type
|
||||
xCeiling = AdvancedConfig.getInstance().getMaxBonusLevel(subSkillType);
|
||||
return new ProbabilityImpl(xPos, xCeiling, probabilityCeiling);
|
||||
case STATIC_CONFIGURABLE:
|
||||
return ofPercentageValue(getStaticRandomChance(subSkillType));
|
||||
default:
|
||||
throw new RuntimeException("No case in switch statement for Skill Probability Type!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a probability from a percentage
|
||||
* @param percentage value to convert
|
||||
* @return 0 -> 1 inclusive representation of probability
|
||||
*/
|
||||
public static double probabilityFromPercent(double percentage) {
|
||||
return percentage / 100;
|
||||
}
|
||||
|
||||
/**
|
||||
* Grabs static activation rolls for Secondary Abilities
|
||||
*
|
||||
* @param subSkillType The secondary ability to grab properties of
|
||||
* @return The static activation roll involved in the RNG calculation
|
||||
* @throws InvalidStaticChance if the skill has no defined static chance this exception will be thrown and you should know you're a naughty boy
|
||||
*/
|
||||
private static double getStaticRandomChance(@NotNull SubSkillType subSkillType) throws InvalidStaticChance {
|
||||
switch (subSkillType) {
|
||||
case AXES_ARMOR_IMPACT:
|
||||
return AdvancedConfig.getInstance().getImpactChance();
|
||||
case AXES_GREATER_IMPACT:
|
||||
return AdvancedConfig.getInstance().getGreaterImpactChance();
|
||||
case TAMING_FAST_FOOD_SERVICE:
|
||||
return AdvancedConfig.getInstance().getFastFoodChance();
|
||||
default:
|
||||
throw new InvalidStaticChance();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,63 @@
|
||||
package com.gmail.nossr50.util.random;
|
||||
|
||||
import com.gmail.nossr50.api.exceptions.ValueOutOfBoundsException;
|
||||
import com.google.common.base.Objects;
|
||||
|
||||
public class ProbabilityImpl implements Probability {
|
||||
|
||||
private final double probabilityValue;
|
||||
|
||||
/**
|
||||
* Create a probability with a static value
|
||||
*
|
||||
* @param staticProbability the value to assign to this probability
|
||||
*/
|
||||
public ProbabilityImpl(double staticProbability) throws ValueOutOfBoundsException {
|
||||
if(staticProbability > 1) {
|
||||
throw new ValueOutOfBoundsException("Value should never be above 1 for Probability! This suggests a coding mistake, contact the devs!");
|
||||
} else if (staticProbability < 0) {
|
||||
throw new ValueOutOfBoundsException("Value should never be negative for Probability! This suggests a coding mistake, contact the devs!");
|
||||
}
|
||||
|
||||
probabilityValue = staticProbability;
|
||||
}
|
||||
|
||||
public 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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getValue() {
|
||||
return probabilityValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ProbabilityImpl{" +
|
||||
"probabilityValue=" + probabilityValue +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
ProbabilityImpl that = (ProbabilityImpl) o;
|
||||
return Double.compare(that.probabilityValue, probabilityValue) == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(probabilityValue);
|
||||
}
|
||||
}
|
@@ -1,18 +0,0 @@
|
||||
package com.gmail.nossr50.util.random;
|
||||
|
||||
public interface RandomChanceExecution {
|
||||
/**
|
||||
* Gets the XPos used in the formula for success
|
||||
*
|
||||
* @return value of x for our success probability graph
|
||||
*/
|
||||
double getXPos();
|
||||
|
||||
/**
|
||||
* The maximum odds for this RandomChanceExecution
|
||||
* For example, if this value is 10, then 10% odds would be the maximum and would be achieved only when xPos equaled the LinearCurvePeak
|
||||
*
|
||||
* @return maximum probability odds from 0.00 (no chance of ever happened) to 100.0 (probability can be guaranteed)
|
||||
*/
|
||||
double getProbabilityCap();
|
||||
}
|
@@ -1,176 +0,0 @@
|
||||
package com.gmail.nossr50.util.random;
|
||||
|
||||
import com.gmail.nossr50.datatypes.player.McMMOPlayer;
|
||||
import com.gmail.nossr50.datatypes.skills.SubSkillType;
|
||||
import com.gmail.nossr50.util.Permissions;
|
||||
import com.gmail.nossr50.util.player.UserManager;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class RandomChanceSkill implements RandomChanceExecution {
|
||||
protected final double probabilityCap;
|
||||
protected final boolean isLucky;
|
||||
protected int skillLevel;
|
||||
protected final double resultModifier;
|
||||
protected final double maximumBonusLevelCap;
|
||||
|
||||
public RandomChanceSkill(@Nullable Player player, @NotNull SubSkillType subSkillType, double resultModifier) {
|
||||
this.probabilityCap = RandomChanceUtil.LINEAR_CURVE_VAR;
|
||||
|
||||
final McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player);
|
||||
if (player != null && mcMMOPlayer != null) {
|
||||
this.skillLevel = mcMMOPlayer.getSkillLevel(subSkillType.getParentSkill());
|
||||
} else {
|
||||
this.skillLevel = 0;
|
||||
}
|
||||
|
||||
if (player != null)
|
||||
isLucky = Permissions.lucky(player, subSkillType.getParentSkill());
|
||||
else
|
||||
isLucky = false;
|
||||
|
||||
this.resultModifier = resultModifier;
|
||||
this.maximumBonusLevelCap = RandomChanceUtil.getMaxBonusLevelCap(subSkillType);
|
||||
}
|
||||
|
||||
public RandomChanceSkill(@Nullable Player player, @NotNull SubSkillType subSkillType) {
|
||||
this.probabilityCap = RandomChanceUtil.LINEAR_CURVE_VAR;
|
||||
|
||||
final McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player);
|
||||
if (player != null && mcMMOPlayer != null) {
|
||||
this.skillLevel = mcMMOPlayer.getSkillLevel(subSkillType.getParentSkill());
|
||||
} else {
|
||||
this.skillLevel = 0;
|
||||
}
|
||||
|
||||
if (player != null)
|
||||
isLucky = Permissions.lucky(player, subSkillType.getParentSkill());
|
||||
else
|
||||
isLucky = false;
|
||||
|
||||
this.resultModifier = 1.0D;
|
||||
this.maximumBonusLevelCap = RandomChanceUtil.getMaxBonusLevelCap(subSkillType);
|
||||
}
|
||||
|
||||
public RandomChanceSkill(@Nullable Player player, @NotNull SubSkillType subSkillType, boolean hasCap) {
|
||||
if (hasCap)
|
||||
this.probabilityCap = RandomChanceUtil.getMaximumProbability(subSkillType);
|
||||
else
|
||||
this.probabilityCap = RandomChanceUtil.LINEAR_CURVE_VAR;
|
||||
|
||||
final McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player);
|
||||
if (player != null && mcMMOPlayer != null) {
|
||||
this.skillLevel = mcMMOPlayer.getSkillLevel(subSkillType.getParentSkill());
|
||||
} else {
|
||||
this.skillLevel = 0;
|
||||
}
|
||||
|
||||
if (player != null)
|
||||
isLucky = Permissions.lucky(player, subSkillType.getParentSkill());
|
||||
else
|
||||
isLucky = false;
|
||||
|
||||
this.resultModifier = 1.0D;
|
||||
this.maximumBonusLevelCap = RandomChanceUtil.getMaxBonusLevelCap(subSkillType);
|
||||
}
|
||||
|
||||
public RandomChanceSkill(@Nullable Player player, @NotNull SubSkillType subSkillType, boolean hasCap, boolean luckyOverride) {
|
||||
if (hasCap)
|
||||
this.probabilityCap = RandomChanceUtil.getMaximumProbability(subSkillType);
|
||||
else
|
||||
this.probabilityCap = RandomChanceUtil.LINEAR_CURVE_VAR;
|
||||
|
||||
final McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player);
|
||||
if (player != null && mcMMOPlayer != null) {
|
||||
this.skillLevel = mcMMOPlayer.getSkillLevel(subSkillType.getParentSkill());
|
||||
} else {
|
||||
this.skillLevel = 0;
|
||||
}
|
||||
|
||||
isLucky = luckyOverride;
|
||||
|
||||
this.resultModifier = 1.0D;
|
||||
this.maximumBonusLevelCap = RandomChanceUtil.getMaxBonusLevelCap(subSkillType);
|
||||
}
|
||||
|
||||
public RandomChanceSkill(@Nullable Player player, @NotNull SubSkillType subSkillType, boolean hasCap, double resultModifier) {
|
||||
if (hasCap)
|
||||
this.probabilityCap = RandomChanceUtil.getMaximumProbability(subSkillType);
|
||||
else
|
||||
this.probabilityCap = RandomChanceUtil.LINEAR_CURVE_VAR;
|
||||
|
||||
final McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player);
|
||||
if (player != null && mcMMOPlayer != null) {
|
||||
this.skillLevel = mcMMOPlayer.getSkillLevel(subSkillType.getParentSkill());
|
||||
} else {
|
||||
this.skillLevel = 0;
|
||||
}
|
||||
|
||||
if (player != null)
|
||||
isLucky = Permissions.lucky(player, subSkillType.getParentSkill());
|
||||
else
|
||||
isLucky = false;
|
||||
|
||||
this.resultModifier = resultModifier;
|
||||
this.maximumBonusLevelCap = RandomChanceUtil.getMaxBonusLevelCap(subSkillType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the skill level of the player who owns this RandomChanceSkill
|
||||
*
|
||||
* @return the current skill level relating to this RandomChanceSkill
|
||||
*/
|
||||
public int getSkillLevel() {
|
||||
return skillLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify the skill level used for this skill's RNG calculations
|
||||
*
|
||||
* @param newSkillLevel new skill level
|
||||
*/
|
||||
public void setSkillLevel(int newSkillLevel) {
|
||||
skillLevel = newSkillLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum bonus level for this skill
|
||||
* This is when the skills level no longer increases the odds of success
|
||||
* For example, a value of 25 will mean the success chance no longer grows after skill level 25
|
||||
*
|
||||
* @return the maximum bonus from skill level for this skill
|
||||
*/
|
||||
public double getMaximumBonusLevelCap() {
|
||||
return maximumBonusLevelCap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the XPos used in the formula for success
|
||||
*
|
||||
* @return value of x for our success probability graph
|
||||
*/
|
||||
@Override
|
||||
public double getXPos() {
|
||||
return getSkillLevel();
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum odds for this RandomChanceExecution
|
||||
* For example, if this value is 10, then 10% odds would be the maximum and would be achieved only when xPos equaled the LinearCurvePeak
|
||||
*
|
||||
* @return maximum probability odds from 0.00 (no chance of ever happened) to 100.0 (probability can be guaranteed)
|
||||
*/
|
||||
@Override
|
||||
public double getProbabilityCap() {
|
||||
return probabilityCap;
|
||||
}
|
||||
|
||||
public boolean isLucky() {
|
||||
return isLucky;
|
||||
}
|
||||
|
||||
public double getResultModifier() {
|
||||
return resultModifier;
|
||||
}
|
||||
}
|
@@ -1,61 +0,0 @@
|
||||
package com.gmail.nossr50.util.random;
|
||||
|
||||
import com.gmail.nossr50.datatypes.skills.SubSkillType;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class RandomChanceSkillStatic extends RandomChanceSkill {
|
||||
private final double xPos;
|
||||
|
||||
public RandomChanceSkillStatic(double xPos, @Nullable Player player, @NotNull SubSkillType subSkillType) {
|
||||
super(player, subSkillType);
|
||||
|
||||
this.xPos = xPos;
|
||||
}
|
||||
|
||||
public RandomChanceSkillStatic(double xPos, @Nullable Player player, @NotNull SubSkillType subSkillType, boolean luckyOverride) {
|
||||
super(player, subSkillType, false, luckyOverride);
|
||||
|
||||
this.xPos = xPos;
|
||||
}
|
||||
|
||||
public RandomChanceSkillStatic(double xPos, @Nullable Player player, @NotNull SubSkillType subSkillType, double resultModifier) {
|
||||
super(player, subSkillType, resultModifier);
|
||||
|
||||
this.xPos = xPos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the XPos used in the formula for success
|
||||
*
|
||||
* @return value of x for our success probability graph
|
||||
*/
|
||||
@Override
|
||||
public double getXPos() {
|
||||
return xPos;
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum odds for this RandomChanceExecution
|
||||
* For example, if this value is 10, then 10% odds would be the maximum and would be achieved only when xPos equaled the LinearCurvePeak
|
||||
*
|
||||
* @return maximum probability odds from 0.00 (no chance of ever happened) to 100.0 (probability can be guaranteed)
|
||||
*/
|
||||
@Override
|
||||
public double getProbabilityCap() {
|
||||
return probabilityCap;
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum bonus level for this skill
|
||||
* This is when the skills level no longer increases the odds of success
|
||||
* For example, a value of 25 will mean the success chance no longer grows after skill level 25
|
||||
*
|
||||
* @return the maximum bonus from skill level for this skill
|
||||
*/
|
||||
@Override
|
||||
public double getMaximumBonusLevelCap() {
|
||||
return 100;
|
||||
}
|
||||
}
|
@@ -1,38 +0,0 @@
|
||||
package com.gmail.nossr50.util.random;
|
||||
|
||||
public class RandomChanceStatic implements RandomChanceExecution {
|
||||
private final double xPos;
|
||||
private final double probabilityCap;
|
||||
private final boolean isLucky;
|
||||
|
||||
public RandomChanceStatic(double xPos, boolean isLucky) {
|
||||
this.xPos = xPos;
|
||||
this.probabilityCap = xPos;
|
||||
this.isLucky = isLucky;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the XPos used in the formula for success
|
||||
*
|
||||
* @return value of x for our success probability graph
|
||||
*/
|
||||
@Override
|
||||
public double getXPos() {
|
||||
return xPos;
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum odds for this RandomChanceExecution
|
||||
* For example, if this value is 10, then 10% odds would be the maximum and would be achieved only when xPos equaled the LinearCurvePeak
|
||||
*
|
||||
* @return maximum probability odds from 0.00 (no chance of ever happened) to 100.0 (probability can be guaranteed)
|
||||
*/
|
||||
@Override
|
||||
public double getProbabilityCap() {
|
||||
return probabilityCap;
|
||||
}
|
||||
|
||||
public boolean isLucky() {
|
||||
return isLucky;
|
||||
}
|
||||
}
|
@@ -1,325 +1,78 @@
|
||||
package com.gmail.nossr50.util.random;
|
||||
|
||||
import com.gmail.nossr50.config.AdvancedConfig;
|
||||
import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
|
||||
import com.gmail.nossr50.datatypes.skills.SubSkillType;
|
||||
import com.gmail.nossr50.events.skills.secondaryabilities.SubSkillEvent;
|
||||
import com.gmail.nossr50.events.skills.secondaryabilities.SubSkillRandomCheckEvent;
|
||||
import com.gmail.nossr50.util.EventUtils;
|
||||
import com.gmail.nossr50.util.Permissions;
|
||||
import com.gmail.nossr50.util.skills.SkillActivationType;
|
||||
import com.gmail.nossr50.util.skills.SkillUtils;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
//TODO: Normalize chance values
|
||||
//TODO: Test the 2 types of SkillProbabilityTypes
|
||||
//TODO: Update calls to this class and its members
|
||||
public class RandomChanceUtil {
|
||||
public static final @NotNull DecimalFormat percent = new DecimalFormat("##0.00%");
|
||||
//public static final DecimalFormat decimal = new DecimalFormat("##0.00");
|
||||
public static final double LINEAR_CURVE_VAR = 100.0D;
|
||||
public static final double LUCKY_MODIFIER = 1.333D;
|
||||
|
||||
/**
|
||||
* This method is the final step in determining if a Sub-Skill / Secondary Skill in mcMMO successfully activates either from chance or otherwise
|
||||
* Random skills check for success based on numbers and then fire a cancellable event, if that event is not cancelled they succeed
|
||||
* non-RNG skills just fire the cancellable event and succeed if they go uncancelled
|
||||
* Simulate an outcome on a probability and return true or false for the result of that outcome
|
||||
*
|
||||
* @param skillActivationType this value represents what kind of activation procedures this sub-skill uses
|
||||
* @param subSkillType The identifier for this specific sub-skill
|
||||
* @param player The owner of this sub-skill
|
||||
* @return returns true if all conditions are met and the event is not cancelled
|
||||
* @param probability target probability
|
||||
* @return true if the probability succeeded, false if it failed
|
||||
*/
|
||||
public static boolean isActivationSuccessful(@NotNull SkillActivationType skillActivationType, @NotNull SubSkillType subSkillType, @Nullable Player player) {
|
||||
switch (skillActivationType) {
|
||||
case RANDOM_LINEAR_100_SCALE_WITH_CAP:
|
||||
return checkRandomChanceExecutionSuccess(player, subSkillType, true);
|
||||
case RANDOM_STATIC_CHANCE:
|
||||
return checkRandomStaticChanceExecutionSuccess(player, subSkillType);
|
||||
case ALWAYS_FIRES:
|
||||
SubSkillEvent event = EventUtils.callSubSkillEvent(player, subSkillType);
|
||||
return !event.isCancelled();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static double getActivationChance(@NotNull SkillActivationType skillActivationType, @NotNull SubSkillType subSkillType, @Nullable Player player, boolean luckyOverride) {
|
||||
switch (skillActivationType) {
|
||||
case RANDOM_LINEAR_100_SCALE_WITH_CAP:
|
||||
return getRandomChanceExecutionSuccess(player, subSkillType, true, luckyOverride);
|
||||
case RANDOM_STATIC_CHANCE:
|
||||
return getRandomStaticChanceExecutionSuccess(player, subSkillType, luckyOverride);
|
||||
default:
|
||||
return 0.1337;
|
||||
}
|
||||
public static boolean processProbability(@NotNull Probability probability) {
|
||||
return isSuccessfulRoll(probability.getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not the random chance succeeds
|
||||
* Modify and then Simulate an outcome on a probability and return true or false for the result of that outcome
|
||||
*
|
||||
* @return true if the random chance succeeds
|
||||
* @param probability target probability
|
||||
* @param probabilityMultiplier probability will be multiplied by this before success is checked
|
||||
* @return true if the probability succeeded, false if it failed
|
||||
*/
|
||||
public static boolean checkRandomChanceExecutionSuccess(@NotNull Player player, @NotNull PrimarySkillType primarySkillType, double chance) {
|
||||
//Check the odds
|
||||
chance *= 100;
|
||||
|
||||
chance = addLuck(player, primarySkillType, chance);
|
||||
|
||||
/*
|
||||
* Stuff like treasures can specify a drop chance from 0.05 to 100
|
||||
* Because of that we need to use a large int bound and multiply the chance by 100
|
||||
*/
|
||||
return rollDice(chance, 10000);
|
||||
}
|
||||
|
||||
public static boolean rollDice(double chanceOfSuccess, int bound) {
|
||||
return rollDice(chanceOfSuccess, bound, 1.0F);
|
||||
}
|
||||
|
||||
public static boolean rollDice(double chanceOfSuccess, int bound, double resultModifier) {
|
||||
return chanceOfSuccess > (ThreadLocalRandom.current().nextInt(bound) * resultModifier);
|
||||
public static boolean processProbability(@NotNull Probability probability, double probabilityMultiplier) {
|
||||
double probabilityValue = probability.getValue() * probabilityMultiplier;
|
||||
return isSuccessfulRoll(probabilityValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for stuff like Excavation, Fishing, etc...
|
||||
* Simulates a "roll of the dice"
|
||||
* If the value passed is higher than the "random" value, than it is a successful roll
|
||||
*
|
||||
* @param randomChance
|
||||
* @return
|
||||
* @param probabilityValue probability value
|
||||
* @return true for succeeding, false for failing
|
||||
*/
|
||||
public static boolean checkRandomChanceExecutionSuccess(@NotNull RandomChanceSkillStatic randomChance, double resultModifier) {
|
||||
double chanceOfSuccess = calculateChanceOfSuccess(randomChance);
|
||||
|
||||
//Check the odds
|
||||
return rollDice(chanceOfSuccess, 100, resultModifier);
|
||||
private static boolean isSuccessfulRoll(double probabilityValue) {
|
||||
return probabilityValue >= ThreadLocalRandom.current().nextDouble(1.0D);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used for stuff like Excavation, Fishing, etc...
|
||||
* Return a chance of success in "percentage" format, show to the player in UI elements
|
||||
*
|
||||
* @param randomChance
|
||||
* @return
|
||||
*/
|
||||
public static boolean checkRandomChanceExecutionSuccess(@NotNull RandomChanceSkillStatic randomChance) {
|
||||
return checkRandomChanceExecutionSuccess(randomChance, 1.0F);
|
||||
}
|
||||
|
||||
public static boolean checkRandomChanceExecutionSuccess(@NotNull RandomChanceSkill randomChance) {
|
||||
double chanceOfSuccess = calculateChanceOfSuccess(randomChance);
|
||||
|
||||
//Check the odds
|
||||
return rollDice(chanceOfSuccess, 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Static Chance for something to activate
|
||||
* @param player target player
|
||||
* @param subSkillType target subskill
|
||||
* @param isLucky whether or not to apply luck modifiers
|
||||
*
|
||||
* @param randomChance
|
||||
* @return
|
||||
* @return "percentage" representation of success
|
||||
*/
|
||||
public static double getRandomChanceExecutionChance(@NotNull RandomChanceExecution randomChance) {
|
||||
return getChanceOfSuccess(randomChance.getXPos(), randomChance.getProbabilityCap(), LINEAR_CURVE_VAR);
|
||||
}
|
||||
|
||||
public static double getRandomChanceExecutionChance(@NotNull RandomChanceExecution randomChance, boolean luckyOverride) {
|
||||
return getChanceOfSuccess(randomChance.getXPos(), randomChance.getProbabilityCap(), LINEAR_CURVE_VAR);
|
||||
}
|
||||
|
||||
public static double getRandomChanceExecutionChance(@NotNull RandomChanceStatic randomChance) {
|
||||
double chanceOfSuccess = getChanceOfSuccess(randomChance.getXPos(), randomChance.getProbabilityCap(), LINEAR_CURVE_VAR);
|
||||
|
||||
chanceOfSuccess = addLuck(randomChance.isLucky(), chanceOfSuccess);
|
||||
|
||||
return chanceOfSuccess;
|
||||
}
|
||||
|
||||
public static double calculateChanceOfSuccess(@NotNull RandomChanceSkill randomChance) {
|
||||
double skillLevel = randomChance.getSkillLevel();
|
||||
double maximumProbability = randomChance.getProbabilityCap();
|
||||
double maximumBonusLevel = randomChance.getMaximumBonusLevelCap();
|
||||
|
||||
double chanceOfSuccess;
|
||||
|
||||
if (skillLevel >= maximumBonusLevel) {
|
||||
//Chance of success is equal to the maximum probability if the maximum bonus level has been reached
|
||||
chanceOfSuccess = maximumProbability;
|
||||
} else {
|
||||
//Get chance of success
|
||||
chanceOfSuccess = getChanceOfSuccess(randomChance.getXPos(), maximumProbability, maximumBonusLevel);
|
||||
}
|
||||
|
||||
//Add Luck
|
||||
chanceOfSuccess = addLuck(randomChance.isLucky(), chanceOfSuccess);
|
||||
|
||||
return chanceOfSuccess;
|
||||
}
|
||||
|
||||
public static double calculateChanceOfSuccess(@NotNull RandomChanceSkillStatic randomChance) {
|
||||
double chanceOfSuccess = getChanceOfSuccess(randomChance.getXPos(), 100, 100);
|
||||
|
||||
//Add Luck
|
||||
chanceOfSuccess = addLuck(randomChance.isLucky(), chanceOfSuccess);
|
||||
|
||||
return chanceOfSuccess;
|
||||
}
|
||||
|
||||
/**
|
||||
* The formula for RNG success is determined like this
|
||||
* maximum probability * ( x / maxlevel )
|
||||
*
|
||||
* @return the chance of success from 0-100 (100 = guaranteed)
|
||||
*/
|
||||
private static int getChanceOfSuccess(double skillLevel, double maxProbability, double maxLevel) {
|
||||
//return (int) (x / (y / LINEAR_CURVE_VAR));
|
||||
return (int) (maxProbability * (skillLevel / maxLevel));
|
||||
// max probability * (weight/maxlevel) = chance of success
|
||||
}
|
||||
|
||||
private static int getChanceOfSuccess(double x, double y) {
|
||||
return (int) (x / (y / LINEAR_CURVE_VAR));
|
||||
// max probability * (weight/maxlevel) = chance of success
|
||||
}
|
||||
|
||||
public static double getRandomChanceExecutionSuccess(@Nullable Player player, @NotNull SubSkillType subSkillType, boolean hasCap) {
|
||||
RandomChanceSkill rcs = new RandomChanceSkill(player, subSkillType, hasCap);
|
||||
return calculateChanceOfSuccess(rcs);
|
||||
}
|
||||
|
||||
public static double getRandomChanceExecutionSuccess(@Nullable Player player, @NotNull SubSkillType subSkillType, boolean hasCap, boolean luckyOverride) {
|
||||
RandomChanceSkill rcs = new RandomChanceSkill(player, subSkillType, hasCap, luckyOverride);
|
||||
return calculateChanceOfSuccess(rcs);
|
||||
}
|
||||
|
||||
public static double getRandomStaticChanceExecutionSuccess(@Nullable Player player, @NotNull SubSkillType subSkillType, boolean luckyOverride) {
|
||||
private static double chanceOfSuccessPercentage(@NotNull Player player, @NotNull SubSkillType subSkillType, boolean isLucky) {
|
||||
try {
|
||||
return getRandomChanceExecutionChance(new RandomChanceSkillStatic(getStaticRandomChance(subSkillType), player, subSkillType, luckyOverride));
|
||||
Probability probability = SkillUtils.getSubSkillProbability(subSkillType, player);
|
||||
//Probability values are on a 0-1 scale and need to be "transformed" into a 1-100 scale
|
||||
double percentageValue = probability.getValue() * 100;
|
||||
|
||||
//Apply lucky modifier
|
||||
if(isLucky) {
|
||||
percentageValue *= LUCKY_MODIFIER;
|
||||
}
|
||||
|
||||
return percentageValue;
|
||||
} catch (InvalidStaticChance invalidStaticChance) {
|
||||
//Catch invalid static skills
|
||||
invalidStaticChance.printStackTrace();
|
||||
}
|
||||
|
||||
return 0.1337; //Puts on shades
|
||||
}
|
||||
|
||||
public static boolean checkRandomChanceExecutionSuccess(@Nullable Player player, @NotNull SubSkillType subSkillType, boolean hasCap) {
|
||||
return checkRandomChanceExecutionSuccess(new RandomChanceSkill(player, subSkillType, hasCap));
|
||||
}
|
||||
|
||||
public static boolean checkRandomChanceExecutionSuccess(@Nullable Player player, @NotNull SubSkillType subSkillType) {
|
||||
return checkRandomChanceExecutionSuccess(new RandomChanceSkill(player, subSkillType));
|
||||
}
|
||||
|
||||
public static boolean checkRandomChanceExecutionSuccess(@Nullable Player player, @NotNull SubSkillType subSkillType, boolean hasCap, double resultModifier) {
|
||||
return checkRandomChanceExecutionSuccess(new RandomChanceSkill(player, subSkillType, hasCap, resultModifier));
|
||||
}
|
||||
|
||||
public static boolean checkRandomChanceExecutionSuccess(@Nullable Player player, @NotNull SubSkillType subSkillType, double resultModifier) {
|
||||
return checkRandomChanceExecutionSuccess(new RandomChanceSkill(player, subSkillType, resultModifier));
|
||||
}
|
||||
|
||||
|
||||
public static boolean checkRandomStaticChanceExecutionSuccess(@Nullable Player player, @NotNull SubSkillType subSkillType) {
|
||||
try {
|
||||
return checkRandomChanceExecutionSuccess(new RandomChanceSkillStatic(getStaticRandomChance(subSkillType), player, subSkillType));
|
||||
} catch (InvalidStaticChance invalidStaticChance) {
|
||||
//Catch invalid static skills
|
||||
invalidStaticChance.printStackTrace();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Grabs static activation rolls for Secondary Abilities
|
||||
*
|
||||
* @param subSkillType The secondary ability to grab properties of
|
||||
* @return The static activation roll involved in the RNG calculation
|
||||
* @throws InvalidStaticChance if the skill has no defined static chance this exception will be thrown and you should know you're a naughty boy
|
||||
*/
|
||||
public static double getStaticRandomChance(@NotNull SubSkillType subSkillType) throws InvalidStaticChance {
|
||||
switch (subSkillType) {
|
||||
case AXES_ARMOR_IMPACT:
|
||||
return AdvancedConfig.getInstance().getImpactChance();
|
||||
case AXES_GREATER_IMPACT:
|
||||
return AdvancedConfig.getInstance().getGreaterImpactChance();
|
||||
case TAMING_FAST_FOOD_SERVICE:
|
||||
return AdvancedConfig.getInstance().getFastFoodChance();
|
||||
default:
|
||||
throw new InvalidStaticChance();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean sendSkillEvent(Player player, SubSkillType subSkillType, double activationChance) {
|
||||
SubSkillRandomCheckEvent event = new SubSkillRandomCheckEvent(player, subSkillType, activationChance);
|
||||
return !event.isCancelled();
|
||||
}
|
||||
|
||||
public static String @NotNull [] calculateAbilityDisplayValues(@NotNull SkillActivationType skillActivationType, @NotNull Player player, @NotNull SubSkillType subSkillType) {
|
||||
double successChance = getActivationChance(skillActivationType, subSkillType, player, false);
|
||||
double successChanceLucky = getActivationChance(skillActivationType, subSkillType, player, true);
|
||||
|
||||
String[] displayValues = new String[2];
|
||||
|
||||
boolean isLucky = Permissions.lucky(player, subSkillType.getParentSkill());
|
||||
|
||||
displayValues[0] = percent.format(Math.min(successChance, 100.0D) / 100.0D);
|
||||
displayValues[1] = isLucky ? percent.format(Math.min(successChanceLucky, 100.0D) / 100.0D) : null;
|
||||
|
||||
return displayValues;
|
||||
}
|
||||
|
||||
public static String @NotNull [] calculateAbilityDisplayValuesStatic(@NotNull Player player, @NotNull PrimarySkillType primarySkillType, double chance) {
|
||||
RandomChanceStatic rcs = new RandomChanceStatic(chance, false);
|
||||
double successChance = getRandomChanceExecutionChance(rcs);
|
||||
|
||||
RandomChanceStatic rcs_lucky = new RandomChanceStatic(chance, true);
|
||||
double successChance_lucky = getRandomChanceExecutionChance(rcs_lucky);
|
||||
|
||||
String[] displayValues = new String[2];
|
||||
|
||||
boolean isLucky = Permissions.lucky(player, primarySkillType);
|
||||
|
||||
displayValues[0] = percent.format(Math.min(successChance, 100.0D) / 100.0D);
|
||||
displayValues[1] = isLucky ? percent.format(Math.min(successChance_lucky, 100.0D) / 100.0D) : null;
|
||||
|
||||
return displayValues;
|
||||
}
|
||||
|
||||
public static String @NotNull [] calculateAbilityDisplayValuesCustom(@NotNull SkillActivationType skillActivationType, @NotNull Player player, @NotNull SubSkillType subSkillType, double multiplier) {
|
||||
double successChance = getActivationChance(skillActivationType, subSkillType, player, false);
|
||||
double successChanceLucky = getActivationChance(skillActivationType, subSkillType, player, true);
|
||||
//TODO: Most likely incorrectly displays the value for graceful roll but gonna ignore for now...
|
||||
successChance *= multiplier; //Currently only used for graceful roll
|
||||
String[] displayValues = new String[2];
|
||||
|
||||
boolean isLucky = Permissions.lucky(player, subSkillType.getParentSkill());
|
||||
|
||||
displayValues[0] = percent.format(Math.min(successChance, 100.0D) / 100.0D);
|
||||
displayValues[1] = isLucky ? percent.format(Math.min(successChanceLucky, 100.0D) / 100.0D) : null;
|
||||
|
||||
return displayValues;
|
||||
}
|
||||
|
||||
public static double addLuck(@NotNull Player player, @NotNull PrimarySkillType primarySkillType, double chance) {
|
||||
if (Permissions.lucky(player, primarySkillType))
|
||||
return chance * LUCKY_MODIFIER;
|
||||
else
|
||||
return chance;
|
||||
}
|
||||
|
||||
public static double addLuck(boolean isLucky, double chance) {
|
||||
if (isLucky)
|
||||
return chance * LUCKY_MODIFIER;
|
||||
else
|
||||
return chance;
|
||||
}
|
||||
|
||||
public static double getMaximumProbability(@NotNull SubSkillType subSkillType) {
|
||||
return AdvancedConfig.getInstance().getMaximumProbability(subSkillType);
|
||||
}
|
||||
|
||||
public static double getMaxBonusLevelCap(@NotNull SubSkillType subSkillType) {
|
||||
return AdvancedConfig.getInstance().getMaxBonusLevel(subSkillType);
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,6 @@
|
||||
package com.gmail.nossr50.util.random;
|
||||
|
||||
public enum SkillProbabilityType {
|
||||
DYNAMIC_CONFIGURABLE, //Has multiple values used for calculation (taken from config files)
|
||||
STATIC_CONFIGURABLE, //A single value used for calculations (taken from config files)
|
||||
}
|
@@ -14,9 +14,11 @@ import com.gmail.nossr50.locale.LocaleLoader;
|
||||
import com.gmail.nossr50.mcMMO;
|
||||
import com.gmail.nossr50.util.ItemUtils;
|
||||
import com.gmail.nossr50.util.Misc;
|
||||
import com.gmail.nossr50.util.Permissions;
|
||||
import com.gmail.nossr50.util.compat.layers.persistentdata.AbstractPersistentDataLayer;
|
||||
import com.gmail.nossr50.util.player.NotificationManager;
|
||||
import com.gmail.nossr50.util.player.UserManager;
|
||||
import com.gmail.nossr50.util.random.*;
|
||||
import com.gmail.nossr50.util.text.StringUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
@@ -334,4 +336,92 @@ public final class SkillUtils {
|
||||
|
||||
return quantity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 from this {@link SubSkillType}
|
||||
*
|
||||
* 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
|
||||
*
|
||||
* 2) Determine whether or not to use Lucky multiplier and influence the outcome
|
||||
*
|
||||
* 3) Creates a {@link Probability} and pipes it to {@link RandomChanceUtil} which processes the result and returns it
|
||||
*
|
||||
* @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)
|
||||
* @return true if the Skill RNG succeeds, false if it fails
|
||||
*/
|
||||
public static boolean isSkillRNGSuccessful(@NotNull SubSkillType subSkillType, @Nullable Player player) {
|
||||
try {
|
||||
//Process probability
|
||||
Probability probability = getSubSkillProbability(subSkillType, player);
|
||||
//Player can be null
|
||||
boolean isLucky = player != null && Permissions.lucky(player, subSkillType.getParentSkill());
|
||||
|
||||
if(isLucky) {
|
||||
return RandomChanceUtil.processProbability(probability, RandomChanceUtil.LUCKY_MODIFIER);
|
||||
} else {
|
||||
return RandomChanceUtil.processProbability(probability);
|
||||
}
|
||||
|
||||
} catch (RuntimeException | InvalidStaticChance e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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, double probabilityPercentage) {
|
||||
//Grab a probability converted from a "percentage" value
|
||||
Probability probability = ProbabilityFactory.ofPercentageValue(probabilityPercentage);
|
||||
|
||||
return isStaticSkillRNGSuccessful(primarySkillType, 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 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
|
||||
*/
|
||||
public static boolean isStaticSkillRNGSuccessful(@NotNull PrimarySkillType primarySkillType, @Nullable Player player, @NotNull Probability probability) {
|
||||
boolean isLucky = player != null && Permissions.lucky(player, primarySkillType);
|
||||
|
||||
if(isLucky) {
|
||||
return RandomChanceUtil.processProbability(probability, RandomChanceUtil.LUCKY_MODIFIER);
|
||||
} else {
|
||||
return RandomChanceUtil.processProbability(probability);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Grab the {@link Probability} for a specific {@link SubSkillType} for a specific {@link Player}
|
||||
*
|
||||
* @param subSkillType target subskill
|
||||
* @param player target player
|
||||
* @return the Probability of this skill succeeding
|
||||
* @throws InvalidStaticChance when a skill that does not have a hard coded static chance and it is asked for
|
||||
* @throws RuntimeException
|
||||
*/
|
||||
public static @NotNull Probability getSubSkillProbability(@NotNull SubSkillType subSkillType, @Nullable Player player) throws InvalidStaticChance, RuntimeException {
|
||||
SkillProbabilityType skillProbabilityType = SkillProbabilityType.DYNAMIC_CONFIGURABLE;
|
||||
|
||||
if(subSkillType == SubSkillType.TAMING_FAST_FOOD_SERVICE || subSkillType == SubSkillType.AXES_ARMOR_IMPACT || subSkillType == SubSkillType.AXES_GREATER_IMPACT)
|
||||
skillProbabilityType = SkillProbabilityType.STATIC_CONFIGURABLE;
|
||||
|
||||
return ProbabilityFactory.ofSubSkill(player, subSkillType, skillProbabilityType);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user