mcMMO 2.2.000-RC1 candidate for release

This commit is contained in:
nossr50
2024-03-30 06:16:10 -07:00
142 changed files with 3589 additions and 1994 deletions

View File

@ -7,8 +7,7 @@ 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 com.gmail.nossr50.util.random.ProbabilityUtil;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
@ -98,7 +97,7 @@ public final class BlockUtils {
*/
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 RandomChanceUtil.checkRandomChanceExecutionSuccess(new RandomChanceSkill(player, subSkillType, true));
return ProbabilityUtil.isSkillRNGSuccessful(subSkillType, player);
}
return false;
@ -231,22 +230,22 @@ public final class BlockUtils {
return mcMMO.getMaterialMapStore().isTreeFellerDestructible(material);
}
/**
* Determine if a given block should be affected by Flux Mining
*
* @param blockState The {@link BlockState} of the block to check
* @return true if the block should affected by Flux Mining, false otherwise
*/
public static boolean affectedByFluxMining(BlockState blockState) {
switch (blockState.getType()) {
case IRON_ORE:
case GOLD_ORE:
return true;
default:
return false;
}
}
// /**
// * Determine if a given block should be affected by Flux Mining
// *
// * @param blockState The {@link BlockState} of the block to check
// * @return true if the block should affected by Flux Mining, false otherwise
// */
// public static boolean affectedByFluxMining(BlockState blockState) {
// switch (blockState.getType()) {
// case IRON_ORE:
// case GOLD_ORE:
// return true;
//
// default:
// return false;
// }
// }
/**
* Determine if a given block can activate Herbalism abilities

View File

@ -183,8 +183,7 @@ public final class EventUtils {
* @param subSkillType target subskill
* @return the event after it has been fired
*/
@Deprecated
public static @NotNull SubSkillEvent callSubSkillEvent(Player player, SubSkillType subSkillType) {
public static @NotNull SubSkillEvent callSubSkillEvent(@NotNull Player player, @NotNull SubSkillType subSkillType) {
SubSkillEvent event = new SubSkillEvent(player, subSkillType);
mcMMO.p.getServer().getPluginManager().callEvent(event);
@ -399,7 +398,7 @@ public final class EventUtils {
McMMOPlayer mmoPlayer = UserManager.getPlayer(player);
if(mmoPlayer == null)
return true;
McMMOPlayerXpGainEvent event = new McMMOPlayerXpGainEvent(player, skill, xpGained, xpGainReason);
mcMMO.p.getServer().getPluginManager().callEvent(event);

View File

@ -36,14 +36,26 @@ public final class ItemUtils {
* @param item Item to check
* @return true if the item is a bow, false otherwise
*/
// TODO: Unit tests
public static boolean isBow(@NotNull ItemStack item) {
return mcMMO.getMaterialMapStore().isBow(item.getType().getKey().getKey());
}
// TODO: Unit tests
public static boolean isCrossbow(@NotNull ItemStack item) {
return mcMMO.getMaterialMapStore().isCrossbow(item.getType().getKey().getKey());
}
// TODO: Unit tests
public static boolean isBowOrCrossbow(@NotNull ItemStack item) {
return isBow(item) || isCrossbow(item);
}
// TODO: Unit tests
public static boolean isTrident(@NotNull ItemStack item) {
return mcMMO.getMaterialMapStore().isTrident(item.getType().getKey().getKey());
}
public static boolean hasItemInEitherHand(@NotNull Player player, Material material) {
return player.getInventory().getItemInMainHand().getType() == material || player.getInventory().getItemInOffHand().getType() == material;
}

View File

@ -10,9 +10,6 @@ import java.util.Locale;
/**
* Stores hash tables for item and block names
* This allows for better support across multiple versions of Minecraft
*
* This is a temporary class, mcMMO is spaghetti and I'l clean it up later
*
*/
public class MaterialMapStore {
@ -52,7 +49,6 @@ public class MaterialMapStore {
private final @NotNull HashSet<String> bows;
private final @NotNull HashSet<String> crossbows;
private final @NotNull HashSet<String> tools;
private final @NotNull HashSet<String> enchantables;
private final @NotNull HashSet<String> ores;
@ -820,6 +816,14 @@ public class MaterialMapStore {
return crossbows.contains(id);
}
public boolean isTrident(@NotNull Material material) {
return isTrident(material.getKey().getKey());
}
public boolean isTrident(@NotNull String id) {
return tridents.contains(id);
}
public boolean isLeatherArmor(@NotNull Material material) {
return isLeatherArmor(material.getKey().getKey());
}

View File

@ -14,6 +14,9 @@ public class MetadataConstants {
* Take great care if you ever modify the value of these keys
*/
public static final @NotNull String METADATA_KEY_REPLANT = "mcMMO: Recently Replanted";
public static final @NotNull String METADATA_KEY_SPAWNED_ARROW = "mcMMO: Spawned Arrow";
public static final @NotNull String METADATA_KEY_MULTI_SHOT_ARROW = "mcMMO: Multi-shot Arrow";
public static final @NotNull String METADATA_KEY_BOUNCE_COUNT = "mcMMO: Arrow Bounce Count";
public static final @NotNull String METADATA_KEY_EXPLOSION_FROM_RUPTURE = "mcMMO: Rupture Explosion";
public static final @NotNull String METADATA_KEY_FISH_HOOK_REF = "mcMMO: Fish Hook Tracker";
public static final @NotNull String METADATA_KEY_DODGE_TRACKER = "mcMMO: Dodge Tracker";

View File

@ -343,4 +343,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);
// }
}

View File

@ -26,7 +26,7 @@ public final class MobHealthbarUtils {
EntityDamageEvent lastDamageCause = player.getLastDamageCause();
String replaceString = lastDamageCause instanceof EntityDamageByEntityEvent ? StringUtils.getPrettyEntityTypeString(((EntityDamageByEntityEvent) lastDamageCause).getDamager().getType()) : "a mob";
return deathMessage.replaceAll("(?:(\u00A7(?:[0-9A-FK-ORa-fk-or]))*(?:[\u2764\u25A0]{1,10})){1,2}", replaceString);
return deathMessage.replaceAll("(?:(§(?:[0-9A-FK-ORa-fk-or]))*(?:[❤■]{1,10})){1,2}", replaceString);
}
/**

View File

@ -5,16 +5,18 @@ import com.gmail.nossr50.datatypes.skills.ItemType;
import com.gmail.nossr50.datatypes.skills.MaterialType;
import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
import com.gmail.nossr50.datatypes.skills.SubSkillType;
import com.gmail.nossr50.datatypes.skills.subskills.AbstractSubSkill;
import com.gmail.nossr50.mcMMO;
import com.gmail.nossr50.util.skills.RankUtils;
import org.bukkit.Material;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.permissions.Permissible;
import org.bukkit.permissions.Permission;
import org.bukkit.permissions.PermissionDefault;
import org.bukkit.plugin.PluginManager;
import org.jetbrains.annotations.NotNull;
import java.util.Locale;
@ -164,10 +166,14 @@ public final class Permissions {
* SKILLS
*/
public static boolean skillEnabled(Permissible permissible, PrimarySkillType skill) {return permissible.hasPermission("mcmmo.skills." + skill.toString().toLowerCase(Locale.ENGLISH)); }
public static boolean skillEnabled(Permissible permissible, PrimarySkillType skill) {
return permissible.hasPermission("mcmmo.skills." + skill.toString().toLowerCase(Locale.ENGLISH));
}
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) { return permissible.hasPermission(subSkillType.getPermissionNodeAddress()); }
public static boolean isSubSkillEnabled(Permissible permissible, AbstractSubSkill abstractSubSkill) { return permissible.hasPermission(abstractSubSkill.getPermissionNode()); }
public static boolean isSubSkillEnabled(Permissible permissible, SubSkillType subSkillType) {
return permissible.hasPermission(subSkillType.getPermissionNodeAddress());
}
/* ACROBATICS */
public static boolean dodge(Permissible permissible) { return permissible.hasPermission("mcmmo.ability.acrobatics.dodge"); }
@ -179,6 +185,7 @@ public final class Permissions {
public static boolean concoctions(Permissible permissible) { return permissible.hasPermission("mcmmo.ability.alchemy.concoctions"); }
/* ARCHERY */
public static boolean explosiveShot(Permissible permissible) { return permissible.hasPermission("mcmmo.ability.archery.explosiveshot"); }
public static boolean arrowRetrieval(Permissible permissible) { return permissible.hasPermission("mcmmo.ability.archery.trackarrows"); }
public static boolean daze(Permissible permissible) { return permissible.hasPermission("mcmmo.ability.archery.daze"); }
@ -225,6 +232,20 @@ public final class Permissions {
/* WOODCUTTING */
public static boolean treeFeller(Permissible permissible) { return permissible.hasPermission("mcmmo.ability.woodcutting.treefeller"); }
/* CROSSBOWS */
public static boolean superShotgun(Permissible permissible) {
return permissible.hasPermission("mcmmo.ability.crossbows.supershotgun");
}
public static boolean trickShot(Permissible permissible) { return permissible.hasPermission("mcmmo.ability.crossbows.trickshot"); }
public static boolean poweredShot(Permissible permissible) { return permissible.hasPermission("mcmmo.ability.crossbows.poweredshot"); }
/* TRIDENTS */
public static boolean tridentsSuper(Permissible permissible) {
return false;
// return permissible.hasPermission("mcmmo.ability.tridents.superability");
}
public static boolean tridentsLimitBreak(Permissible permissible) { return permissible.hasPermission("mcmmo.ability.tridents.superability"); }
/*
* PARTY
*/
@ -256,4 +277,15 @@ public final class Permissions {
permission.setDefault(permissionDefault);
pluginManager.addPermission(permission);
}
/**
* Checks if a player can use a skill
*
* @param player target player
* @param subSkillType target subskill
* @return true if the player has permission and has the skill unlocked
*/
public static boolean canUseSubSkill(@NotNull Player player, @NotNull SubSkillType subSkillType) {
return isSubSkillEnabled(player, subSkillType) && RankUtils.hasUnlockedSubskill(player, subSkillType);
}
}

View File

@ -61,6 +61,9 @@ public final class CommandRegistrationManager {
case AXES:
command.setExecutor(new AxesCommand());
break;
case CROSSBOWS:
command.setExecutor(new CrossbowsCommand());
break;
case EXCAVATION:
command.setExecutor(new ExcavationCommand());
@ -97,6 +100,9 @@ public final class CommandRegistrationManager {
case TAMING:
command.setExecutor(new TamingCommand());
break;
case TRIDENTS:
command.setExecutor(new TridentsCommand());
break;
case UNARMED:
command.setExecutor(new UnarmedCommand());

View File

@ -1,5 +0,0 @@
package com.gmail.nossr50.util.random;
public class InvalidActivationException extends Exception {
//Weee
}

View File

@ -0,0 +1,65 @@
package com.gmail.nossr50.util.random;
import org.jetbrains.annotations.NotNull;
import java.util.concurrent.ThreadLocalRandom;
public interface Probability {
/**
* The value of this Probability
* Should return a result between 0 and 1 (inclusive)
* 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
*
* @return the value of 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
*
* @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);
}
/**
* Simulates a "roll of the dice"
* If the value passed is higher than the "random" value, than it is a successful roll
*
* @param probabilityValue probability value
* @return true for succeeding, false for failing
*/
static private boolean isSuccessfulRoll(double probabilityValue) {
return (probabilityValue) >= ThreadLocalRandom.current().nextDouble(1D);
}
/**
* Simulate an outcome on a probability and return true or false for the result of that outcome
*
* @return true if the probability succeeded, false if it failed
*/
default boolean evaluate() {
return isSuccessfulRoll(getValue());
}
/**
* Modify and then Simulate an outcome on a probability and return true or false for the result of that outcome
*
* @param probabilityMultiplier probability will be multiplied by this before success is checked
* @return true if the probability succeeded, false if it failed
*/
default boolean evaluate(double probabilityMultiplier) {
double probabilityValue = getValue() * probabilityMultiplier;
return isSuccessfulRoll(probabilityValue);
}
}

View File

@ -0,0 +1,62 @@
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 percentage the percentage value of the probability
*/
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!");
}
// 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;
}
@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);
}
}

View File

@ -0,0 +1,226 @@
package com.gmail.nossr50.util.random;
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.events.skills.secondaryabilities.SubSkillEvent;
import com.gmail.nossr50.mcMMO;
import com.gmail.nossr50.util.EventUtils;
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;
import java.text.DecimalFormat;
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
*
* @param player target player
* @param subSkillType target subskill
* @param isLucky whether to apply luck modifiers
*
* @return "percentage" representation of success
*/
public static double chanceOfSuccessPercentage(@NotNull Player player,
@NotNull SubSkillType subSkillType,
boolean isLucky) {
Probability probability = 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(); //Doesn't need to be scaled
//Apply lucky modifier
if(isLucky) {
percentageValue *= LUCKY_MODIFIER;
}
return percentageValue;
}
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();
//Apply lucky modifier
if(isLucky) {
percentageValue *= LUCKY_MODIFIER;
}
return percentageValue;
}
static Probability getStaticRandomChance(@NotNull SubSkillType subSkillType) throws InvalidStaticChance {
return switch (subSkillType) {
case AXES_ARMOR_IMPACT -> Probability.ofPercent(mcMMO.p.getAdvancedConfig().getImpactChance());
case AXES_GREATER_IMPACT -> Probability.ofPercent(mcMMO.p.getAdvancedConfig().getGreaterImpactChance());
case TAMING_FAST_FOOD_SERVICE -> Probability.ofPercent(mcMMO.p.getAdvancedConfig().getFastFoodChance());
default -> throw new InvalidStaticChance();
};
}
static SkillProbabilityType getProbabilityType(@NotNull SubSkillType subSkillType) {
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 skillProbabilityType;
}
static @NotNull Probability ofSubSkill(@Nullable Player player,
@NotNull SubSkillType subSkillType) {
switch (getProbabilityType(subSkillType)) {
case DYNAMIC_CONFIGURABLE:
double probabilityCeiling;
double xCeiling;
double xPos;
if (player != null) {
McMMOPlayer mmoPlayer = UserManager.getPlayer(player);
if (mmoPlayer == null) {
return Probability.ofPercent(0);
}
xPos = mmoPlayer.getSkillLevel(subSkillType.getParentSkill());
} else {
xPos = 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);
case STATIC_CONFIGURABLE:
try {
return getStaticRandomChance(subSkillType);
} catch (InvalidStaticChance invalidStaticChance) {
invalidStaticChance.printStackTrace();
}
default:
throw new RuntimeException("No case in switch statement for Skill Probability Type!");
}
}
/**
* 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 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, @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);
//Luck
boolean isLucky = Permissions.lucky(player, subSkillType.getParentSkill());
if(isLucky) {
return probability.evaluate(LUCKY_MODIFIER);
} else {
return probability.evaluate();
}
}
/**
* 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 = Probability.ofPercent(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 probability.evaluate(LUCKY_MODIFIER);
} else {
return probability.evaluate();
}
}
/**
* Skills activate without RNG, this allows other plugins to prevent that activation
* @param subSkillType target subskill
* @param player target player
* @return true if the skill succeeds (wasn't cancelled by any other plugin)
*/
public static boolean isNonRNGSkillActivationSuccessful(@NotNull SubSkillType subSkillType, @NotNull Player player) {
return !EventUtils.callSubSkillEvent(player, subSkillType).isCancelled();
}
/**
* 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
*/
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);
return new String[]{percent.format(firstValue), percent.format(secondValue)};
}
public static @NotNull String[] getRNGDisplayValues(@NotNull Probability probability) {
double firstValue = chanceOfSuccessPercentage(probability, false);
double secondValue = chanceOfSuccessPercentage(probability, true);
return new String[]{percent.format(firstValue), percent.format(secondValue)};
}
}

View File

@ -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();
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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, double probabilityCap, boolean isLucky) {
this.xPos = xPos;
this.probabilityCap = probabilityCap;
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;
}
}

View File

@ -1,337 +0,0 @@
package com.gmail.nossr50.util.random;
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.mcMMO;
import com.gmail.nossr50.util.EventUtils;
import com.gmail.nossr50.util.Permissions;
import com.gmail.nossr50.util.skills.SkillActivationType;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.text.DecimalFormat;
import java.util.concurrent.ThreadLocalRandom;
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
*
* @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
*/
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;
}
}
/**
* Checks whether or not the random chance succeeds
*
* @return true if the random chance succeeds
*/
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);
}
/**
* Used for stuff like Excavation, Fishing, etc...
*
* @param randomChance
* @return
*/
public static boolean checkRandomChanceExecutionSuccess(@NotNull RandomChanceSkillStatic randomChance, double resultModifier) {
double chanceOfSuccess = calculateChanceOfSuccess(randomChance);
//Check the odds
return rollDice(chanceOfSuccess, 100, resultModifier);
}
/**
* Used for stuff like Excavation, Fishing, etc...
*
* @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);
}
/*public static double getRandomChanceExecutionChance(RandomChanceSkill randomChance)
{
double chanceOfSuccess = calculateChanceOfSuccess(randomChance);
return chanceOfSuccess;
}*/
/**
* Gets the Static Chance for something to activate
*
* @param randomChance
* @return
*/
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;
}
/*private static double calculateChanceOfSuccess(RandomChanceStatic randomChance) {
double chanceOfSuccess = getChanceOfSuccess(randomChance.getXPos(), randomChance.getProbabilityCap());
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) {
try {
return getRandomChanceExecutionChance(new RandomChanceSkillStatic(getStaticRandomChance(subSkillType), player, subSkillType, luckyOverride));
} 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 mcMMO.p.getAdvancedConfig().getImpactChance();
case AXES_GREATER_IMPACT:
return mcMMO.p.getAdvancedConfig().getGreaterImpactChance();
case TAMING_FAST_FOOD_SERVICE:
return mcMMO.p.getAdvancedConfig().getFastFoodChance();
default:
throw new InvalidStaticChance();
}
}
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, LINEAR_CURVE_VAR, false);
double successChance = getRandomChanceExecutionChance(rcs);
RandomChanceStatic rcs_lucky = new RandomChanceStatic(chance, LINEAR_CURVE_VAR, 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 mcMMO.p.getAdvancedConfig().getMaximumProbability(subSkillType);
}
public static double getMaxBonusLevelCap(@NotNull SubSkillType subSkillType) {
return mcMMO.p.getAdvancedConfig().getMaxBonusLevel(subSkillType);
}
}

View File

@ -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)
}

View File

@ -11,7 +11,6 @@ import com.gmail.nossr50.events.scoreboard.ScoreboardEventReason;
import com.gmail.nossr50.events.scoreboard.ScoreboardObjectiveEventReason;
import com.gmail.nossr50.locale.LocaleLoader;
import com.gmail.nossr50.mcMMO;
import com.gmail.nossr50.skills.child.FamilyTree;
import com.gmail.nossr50.util.LogUtils;
import com.gmail.nossr50.util.Misc;
import com.gmail.nossr50.util.player.NotificationManager;
@ -495,7 +494,7 @@ public class ScoreboardWrapper {
sidebarObjective.getScore(ScoreboardManager.LABEL_REMAINING_XP).setScore(mcMMOPlayer.getXpToLevel(targetSkill) - currentXP);
}
else {
for (PrimarySkillType parentSkill : FamilyTree.getParents(targetSkill)) {
for (PrimarySkillType parentSkill : mcMMO.p.getSkillTools().getChildSkillParents(targetSkill)) {
sidebarObjective.getScore(ScoreboardManager.skillLabels.get(parentSkill)).setScore(mcMMOPlayer.getSkillLevel(parentSkill));
}
}

View File

@ -10,13 +10,13 @@ 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.party.PartyManager;
import com.gmail.nossr50.runnables.skills.AwardCombatXpTask;
import com.gmail.nossr50.skills.acrobatics.AcrobaticsManager;
import com.gmail.nossr50.skills.archery.ArcheryManager;
import com.gmail.nossr50.skills.axes.AxesManager;
import com.gmail.nossr50.skills.swords.SwordsManager;
import com.gmail.nossr50.skills.taming.TamingManager;
import com.gmail.nossr50.skills.tridents.TridentsManager;
import com.gmail.nossr50.skills.unarmed.UnarmedManager;
import com.gmail.nossr50.util.*;
import com.gmail.nossr50.util.player.NotificationManager;
@ -45,7 +45,6 @@ public final class CombatUtils {
return mcMMO.getMetadataService().getMobMetadataService();
}
//Likely.. because who knows what plugins are throwing around
public static boolean isDamageLikelyFromNormalCombat(@NotNull DamageCause damageCause) {
return switch (damageCause) {
case ENTITY_ATTACK, ENTITY_SWEEP_ATTACK, PROJECTILE -> true;
@ -112,6 +111,78 @@ public final class CombatUtils {
}
}
}
private static void processTridentCombat(@NotNull LivingEntity target, @NotNull Player player, @NotNull EntityDamageByEntityEvent event) {
if (event.getCause() == DamageCause.THORNS) {
return;
}
double boostedDamage = event.getDamage();
McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player);
//Make sure the profiles been loaded
if(mcMMOPlayer == null) {
return;
}
TridentsManager tridentsManager = mcMMOPlayer.getTridentsManager();
if (tridentsManager.canActivateAbility()) {
mcMMOPlayer.checkAbilityActivation(PrimarySkillType.TRIDENTS);
}
if (SkillUtils.canUseSubskill(player, SubSkillType.TRIDENTS_IMPALE)) {
boostedDamage += (tridentsManager.impaleDamageBonus() * mcMMOPlayer.getAttackStrength());
}
if(canUseLimitBreak(player, target, SubSkillType.TRIDENTS_TRIDENTS_LIMIT_BREAK)) {
boostedDamage += (getLimitBreakDamage(player, target, SubSkillType.TRIDENTS_TRIDENTS_LIMIT_BREAK) * mcMMOPlayer.getAttackStrength());
}
event.setDamage(boostedDamage);
processCombatXP(mcMMOPlayer, target, PrimarySkillType.TRIDENTS);
printFinalDamageDebug(player, event, mcMMOPlayer);
}
private static void processCrossbowsCombat(@NotNull LivingEntity target, @NotNull Player player,
@NotNull EntityDamageByEntityEvent event, @NotNull Arrow arrow) {
double initialDamage = event.getDamage();
McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player);
//Make sure the profiles been loaded
if(mcMMOPlayer == null) {
delayArrowMetaCleanup(arrow);
return;
}
double boostedDamage = event.getDamage();
if (SkillUtils.canUseSubskill(player, SubSkillType.CROSSBOWS_POWERED_SHOT)) {
//Not Additive
boostedDamage = mcMMOPlayer.getCrossbowsManager().poweredShot(initialDamage);
}
if(canUseLimitBreak(player, target, SubSkillType.CROSSBOWS_CROSSBOWS_LIMIT_BREAK)) {
boostedDamage+=getLimitBreakDamage(player, target, SubSkillType.CROSSBOWS_CROSSBOWS_LIMIT_BREAK);
}
double distanceMultiplier = ArcheryManager.distanceXpBonusMultiplier(target, arrow);
double forceMultiplier = 1.0;
event.setDamage(boostedDamage);
processCombatXP(mcMMOPlayer, target, PrimarySkillType.CROSSBOWS, forceMultiplier * distanceMultiplier);
printFinalDamageDebug(player, event, mcMMOPlayer,
"Distance Multiplier: "+distanceMultiplier,
"Force Multiplier: "+forceMultiplier,
"Initial Damage: "+initialDamage,
"Final Damage: "+boostedDamage);
//Clean data
delayArrowMetaCleanup(arrow);
}
private static void processAxeCombat(@NotNull LivingEntity target, @NotNull Player player, @NotNull EntityDamageByEntityEvent event) {
if (event.getCause() == DamageCause.THORNS) {
@ -240,14 +311,15 @@ public final class CombatUtils {
}
private static void processArcheryCombat(@NotNull LivingEntity target, @NotNull Player player, @NotNull EntityDamageByEntityEvent event, @NotNull Projectile arrow) {
private static void processArcheryCombat(@NotNull LivingEntity target, @NotNull Player player,
@NotNull EntityDamageByEntityEvent event, @NotNull Arrow arrow) {
double initialDamage = event.getDamage();
McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player);
//Make sure the profiles been loaded
if(mcMMOPlayer == null) {
cleanupArrowMetadata(arrow);
delayArrowMetaCleanup(arrow);
return;
}
@ -273,7 +345,7 @@ public final class CombatUtils {
boostedDamage+=getLimitBreakDamage(player, target, SubSkillType.ARCHERY_ARCHERY_LIMIT_BREAK);
}
double distanceMultiplier = archeryManager.distanceXpBonusMultiplier(target, arrow);
double distanceMultiplier = ArcheryManager.distanceXpBonusMultiplier(target, arrow);
double forceMultiplier = 1.0; //Hacky Fix - some plugins spawn arrows and assign them to players after the ProjectileLaunchEvent fires
if(arrow.hasMetadata(MetadataConstants.METADATA_KEY_BOW_FORCE))
@ -288,7 +360,7 @@ public final class CombatUtils {
"Initial Damage: "+initialDamage,
"Final Damage: "+boostedDamage);
//Clean data
cleanupArrowMetadata(arrow);
delayArrowMetaCleanup(arrow);
}
/**
@ -387,6 +459,15 @@ public final class CombatUtils {
processUnarmedCombat(target, player, event);
}
}
else if (ItemUtils.isTrident(heldItem)) {
if (!mcMMO.p.getSkillTools().canCombatSkillsTrigger(PrimarySkillType.TRIDENTS, target)) {
return;
}
if (mcMMO.p.getSkillTools().doesPlayerHaveSkillPermission(player, PrimarySkillType.TRIDENTS)) {
processTridentCombat(target, player, event);
}
}
}
else if (entityType == EntityType.WOLF) {
@ -400,20 +481,25 @@ public final class CombatUtils {
}
}
}
else if (entityType == EntityType.ARROW || entityType == EntityType.SPECTRAL_ARROW) {
Projectile arrow = (Projectile) painSource;
else if (painSource instanceof Arrow arrow) {
ProjectileSource projectileSource = arrow.getShooter();
boolean isCrossbow = arrow.isShotFromCrossbow();
if (projectileSource instanceof Player player) {
if (projectileSource instanceof Player player && mcMMO.p.getSkillTools().canCombatSkillsTrigger(PrimarySkillType.ARCHERY, target)) {
if (!Misc.isNPCEntityExcludingVillagers(player) && mcMMO.p.getSkillTools().doesPlayerHaveSkillPermission(player, PrimarySkillType.ARCHERY)) {
processArcheryCombat(target, player, event, arrow);
if (!Misc.isNPCEntityExcludingVillagers(player)) {
if(!isCrossbow && mcMMO.p.getSkillTools().canCombatSkillsTrigger(PrimarySkillType.ARCHERY, target)) {
processArcheryCombat(target, player, event, arrow);
} else if(isCrossbow && mcMMO.p.getSkillTools().canCombatSkillsTrigger(PrimarySkillType.CROSSBOWS, target)) {
processCrossbowsCombat(target, player, event, arrow);
}
} else {
//Cleanup Arrow
cleanupArrowMetadata(arrow);
delayArrowMetaCleanup(arrow);
}
if (target.getType() != EntityType.CREEPER && !Misc.isNPCEntityExcludingVillagers(player) && mcMMO.p.getSkillTools().doesPlayerHaveSkillPermission(player, PrimarySkillType.TAMING)) {
if (target.getType() != EntityType.CREEPER
&& !Misc.isNPCEntityExcludingVillagers(player)
&& mcMMO.p.getSkillTools().doesPlayerHaveSkillPermission(player, PrimarySkillType.TAMING)) {
McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player);
if(mcMMOPlayer == null)
@ -424,7 +510,6 @@ public final class CombatUtils {
}
}
}
}
/**
@ -625,7 +710,7 @@ public final class CombatUtils {
}
public static boolean hasIgnoreDamageMetadata(@NotNull LivingEntity target) {
return target.getMetadata(MetadataConstants.METADATA_KEY_CUSTOM_DAMAGE).size() != 0;
return target.hasMetadata(MetadataConstants.METADATA_KEY_CUSTOM_DAMAGE);
}
public static void dealNoInvulnerabilityTickDamageRupture(@NotNull LivingEntity target, double damage, Entity attacker, int toolTier) {
@ -634,35 +719,6 @@ public final class CombatUtils {
}
dealNoInvulnerabilityTickDamage(target, damage, attacker);
// //IFrame storage
//// int noDamageTicks = target.getNoDamageTicks();
//
//// String debug = "BLEED DMG RESULT: INC DMG:"+damage+", HP-Before:"+target.getHealth()+", HP-After:";
//
//// double incDmg = getFakeDamageFinalResult(attacker, target, DamageCause.ENTITY_ATTACK, damage);
//
//// double newHealth = Math.max(0, target.getHealth() - incDmg);
//
// //Don't kill things with a stone or wooden weapon
//// if(toolTier < 3 && newHealth == 0)
//// return;
//
// target.setMetadata(mcMMO.CUSTOM_DAMAGE_METAKEY, mcMMO.metadataValue);
//
// if(newHealth == 0 && !(target instanceof Player))
// {
// target.damage(99999, attacker);
// }
// else
// {
//// Vector beforeRuptureVec = new Vector(target.getVelocity().getX(), target.getVelocity().getY(), target.getVelocity().getZ()); ;
// target.damage(damage, attacker);
//// debug+=target.getHealth();
// Bukkit.broadcastMessage(debug);
//// target.setNoDamageTicks(noDamageTicks); //Do not add additional IFrames
//// target.setVelocity(beforeRuptureVec);
// }
}
/**
@ -745,14 +801,16 @@ public final class CombatUtils {
XPGainReason xpGainReason;
if (target instanceof Player defender) {
if (!ExperienceConfig.getInstance().getExperienceGainsPlayerVersusPlayerEnabled() ||
if (!ExperienceConfig.getInstance().getExperienceGainsPlayerVersusPlayerEnabled()
||
(mcMMO.p.getPartyConfig().isPartyEnabled() && mcMMO.p.getPartyManager().inSameParty(mcMMOPlayer.getPlayer(), (Player) target))) {
return;
}
xpGainReason = XPGainReason.PVP;
if (defender.isOnline() && SkillUtils.cooldownExpired(mcMMOPlayer.getRespawnATS(), Misc.PLAYER_RESPAWN_COOLDOWN_SECONDS)) {
if (defender.isOnline()
&& SkillUtils.cooldownExpired(mcMMOPlayer.getRespawnATS(), Misc.PLAYER_RESPAWN_COOLDOWN_SECONDS)) {
baseXP = 20 * ExperienceConfig.getInstance().getPlayerVersusPlayerXP();
}
}
@ -811,7 +869,7 @@ public final class CombatUtils {
baseXP *= multiplier;
if (baseXP != 0) {
if (baseXP > 0) {
mcMMO.p.getFoliaLib().getImpl().runAtEntity(mcMMOPlayer.getPlayer(), new AwardCombatXpTask(mcMMOPlayer, primarySkillType, baseXP, target, xpGainReason));
}
}
@ -949,31 +1007,12 @@ public final class CombatUtils {
}
}
/**
* Clean up metadata from a projectile
*
* @param entity projectile
*/
public static void cleanupArrowMetadata(@NotNull Projectile entity) {
if(entity.hasMetadata(MetadataConstants.METADATA_KEY_INF_ARROW)) {
entity.removeMetadata(MetadataConstants.METADATA_KEY_INF_ARROW, mcMMO.p);
}
if(entity.hasMetadata(MetadataConstants.METADATA_KEY_BOW_FORCE)) {
entity.removeMetadata(MetadataConstants.METADATA_KEY_BOW_FORCE, mcMMO.p);
}
if(entity.hasMetadata(MetadataConstants.METADATA_KEY_ARROW_DISTANCE)) {
entity.removeMetadata(MetadataConstants.METADATA_KEY_ARROW_DISTANCE, mcMMO.p);
}
}
/**
* Clean up metadata from a projectile after a minute has passed
*
* @param entity the projectile
* @param arrow the projectile
*/
public static void delayArrowMetaCleanup(@NotNull Projectile entity) {
mcMMO.p.getFoliaLib().getImpl().runLater(() -> cleanupArrowMetadata(entity), 20*60);
public static void delayArrowMetaCleanup(@NotNull Arrow arrow) {
mcMMO.p.getFoliaLib().getImpl().runLater(() -> ProjectileUtils.cleanupProjectileMetadata(arrow), 20*120);
}
}

View File

@ -0,0 +1,84 @@
package com.gmail.nossr50.util.skills;
import com.gmail.nossr50.mcMMO;
import com.gmail.nossr50.util.MetadataConstants;
import org.bukkit.block.BlockFace;
import org.bukkit.entity.Arrow;
import org.bukkit.metadata.FixedMetadataValue;
import org.bukkit.plugin.Plugin;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
public class ProjectileUtils {
public static Vector getNormal(BlockFace blockFace) {
return switch (blockFace) {
case UP -> new Vector(0, 1, 0);
case DOWN -> new Vector(0, -1, 0);
case NORTH -> new Vector(0, 0, -1);
case SOUTH -> new Vector(0, 0, 1);
case EAST -> new Vector(1, 0, 0);
case WEST -> new Vector(-1, 0, 0);
default -> new Vector(0, 0, 0);
};
}
/**
* Clean up all possible mcMMO related metadata for a projectile
*
* @param arrow projectile
*/
// TODO: Add test
public static void cleanupProjectileMetadata(@NotNull Arrow arrow) {
if(arrow.hasMetadata(MetadataConstants.METADATA_KEY_INF_ARROW)) {
arrow.removeMetadata(MetadataConstants.METADATA_KEY_INF_ARROW, mcMMO.p);
}
if(arrow.hasMetadata(MetadataConstants.METADATA_KEY_BOW_FORCE)) {
arrow.removeMetadata(MetadataConstants.METADATA_KEY_BOW_FORCE, mcMMO.p);
}
if(arrow.hasMetadata(MetadataConstants.METADATA_KEY_ARROW_DISTANCE)) {
arrow.removeMetadata(MetadataConstants.METADATA_KEY_ARROW_DISTANCE, mcMMO.p);
}
if(arrow.hasMetadata(MetadataConstants.METADATA_KEY_SPAWNED_ARROW)) {
arrow.removeMetadata(MetadataConstants.METADATA_KEY_SPAWNED_ARROW, mcMMO.p);
}
if(arrow.hasMetadata(MetadataConstants.METADATA_KEY_MULTI_SHOT_ARROW)) {
arrow.removeMetadata(MetadataConstants.METADATA_KEY_MULTI_SHOT_ARROW, mcMMO.p);
}
if(arrow.hasMetadata(MetadataConstants.METADATA_KEY_BOUNCE_COUNT)) {
arrow.removeMetadata(MetadataConstants.METADATA_KEY_BOUNCE_COUNT, mcMMO.p);
}
}
public static void copyArrowMetadata(@NotNull Plugin pluginRef, @NotNull Arrow arrowToCopy, @NotNull Arrow newArrow) {
if(arrowToCopy.hasMetadata(MetadataConstants.METADATA_KEY_INF_ARROW)) {
newArrow.setMetadata(MetadataConstants.METADATA_KEY_INF_ARROW,
arrowToCopy.getMetadata(MetadataConstants.METADATA_KEY_INF_ARROW).get(0));
}
if(arrowToCopy.hasMetadata(MetadataConstants.METADATA_KEY_BOW_FORCE)) {
newArrow.setMetadata(MetadataConstants.METADATA_KEY_BOW_FORCE,
new FixedMetadataValue(pluginRef,
arrowToCopy.getMetadata(MetadataConstants.METADATA_KEY_BOW_FORCE).get(0).asDouble()));
}
if(arrowToCopy.hasMetadata(MetadataConstants.METADATA_KEY_ARROW_DISTANCE)) {
newArrow.setMetadata(MetadataConstants.METADATA_KEY_ARROW_DISTANCE,
arrowToCopy.getMetadata(MetadataConstants.METADATA_KEY_ARROW_DISTANCE).get(0));
}
if(arrowToCopy.hasMetadata(MetadataConstants.METADATA_KEY_SPAWNED_ARROW)) {
newArrow.setMetadata(MetadataConstants.METADATA_KEY_SPAWNED_ARROW,
arrowToCopy.getMetadata(MetadataConstants.METADATA_KEY_SPAWNED_ARROW).get(0));
}
if(arrowToCopy.hasMetadata(MetadataConstants.METADATA_KEY_MULTI_SHOT_ARROW)) {
newArrow.setMetadata(MetadataConstants.METADATA_KEY_MULTI_SHOT_ARROW,
arrowToCopy.getMetadata(MetadataConstants.METADATA_KEY_MULTI_SHOT_ARROW).get(0));
}
}
}

View File

@ -24,7 +24,7 @@ public class RankUtils {
*
* @param plugin plugin instance ref
* @param mcMMOPlayer target player
* @param primarySkillType
* @param primarySkillType the skill to check
* @param newLevel the new level of this skill
*/
public static void executeSkillUnlockNotifications(Plugin plugin, McMMOPlayer mcMMOPlayer, PrimarySkillType primarySkillType, int newLevel)
@ -55,6 +55,9 @@ public class RankUtils {
}
}
/**
* Reset the interval between skill unlock notifications
*/
public static void resetUnlockDelayTimer()
{
count = 0;

View File

@ -30,6 +30,8 @@ public class SkillTools {
public final @NotNull ImmutableSet<String> EXACT_SUBSKILL_NAMES;
public final @NotNull ImmutableList<PrimarySkillType> CHILD_SKILLS;
public final static @NotNull ImmutableList<PrimarySkillType> NON_CHILD_SKILLS;
public final static @NotNull ImmutableList<PrimarySkillType> SALVAGE_PARENTS;
public final static @NotNull ImmutableList<PrimarySkillType> SMELTING_PARENTS;
public final @NotNull ImmutableList<PrimarySkillType> COMBAT_SKILLS;
public final @NotNull ImmutableList<PrimarySkillType> GATHERING_SKILLS;
public final @NotNull ImmutableList<PrimarySkillType> MISC_SKILLS;
@ -50,9 +52,11 @@ public class SkillTools {
}
NON_CHILD_SKILLS = ImmutableList.copyOf(tempNonChildSkills);
SALVAGE_PARENTS = ImmutableList.of(PrimarySkillType.REPAIR, PrimarySkillType.FISHING);
SMELTING_PARENTS = ImmutableList.of(PrimarySkillType.MINING, PrimarySkillType.REPAIR);
}
public SkillTools(@NotNull mcMMO pluginRef) {
public SkillTools(@NotNull mcMMO pluginRef) throws InvalidSkillException {
this.pluginRef = pluginRef;
/*
@ -140,26 +144,38 @@ public class SkillTools {
*/
List<PrimarySkillType> childSkills = new ArrayList<>();
// List<PrimarySkillType> nonChildSkills = new ArrayList<>();
for (PrimarySkillType primarySkillType : PrimarySkillType.values()) {
if (isChildSkill(primarySkillType))
childSkills.add(primarySkillType);
// } {
// nonChildSkills.add(primarySkillType);
// }
}
CHILD_SKILLS = ImmutableList.copyOf(childSkills);
// NON_CHILD_SKILLS = ImmutableList.copyOf(nonChildSkills);
/*
* Build categorized skill lists
*/
COMBAT_SKILLS = ImmutableList.of(PrimarySkillType.ARCHERY, PrimarySkillType.AXES, PrimarySkillType.SWORDS, PrimarySkillType.TAMING, PrimarySkillType.UNARMED);
GATHERING_SKILLS = ImmutableList.of(PrimarySkillType.EXCAVATION, PrimarySkillType.FISHING, PrimarySkillType.HERBALISM, PrimarySkillType.MINING, PrimarySkillType.WOODCUTTING);
MISC_SKILLS = ImmutableList.of(PrimarySkillType.ACROBATICS, PrimarySkillType.ALCHEMY, PrimarySkillType.REPAIR, PrimarySkillType.SALVAGE, PrimarySkillType.SMELTING);
COMBAT_SKILLS = ImmutableList.of(
PrimarySkillType.ARCHERY,
PrimarySkillType.AXES,
PrimarySkillType.CROSSBOWS,
PrimarySkillType.SWORDS,
PrimarySkillType.TAMING,
PrimarySkillType.TRIDENTS,
PrimarySkillType.UNARMED);
GATHERING_SKILLS = ImmutableList.of(
PrimarySkillType.EXCAVATION,
PrimarySkillType.FISHING,
PrimarySkillType.HERBALISM,
PrimarySkillType.MINING,
PrimarySkillType.WOODCUTTING);
MISC_SKILLS = ImmutableList.of(
PrimarySkillType.ACROBATICS,
PrimarySkillType.ALCHEMY,
PrimarySkillType.REPAIR,
PrimarySkillType.SALVAGE,
PrimarySkillType.SMELTING);
/*
* Build formatted/localized/etc string lists
@ -171,25 +187,18 @@ public class SkillTools {
}
private @NotNull PrimarySkillType getSuperAbilityParent(SuperAbilityType superAbilityType) throws InvalidSkillException {
switch(superAbilityType) {
case BERSERK:
return PrimarySkillType.UNARMED;
case GREEN_TERRA:
return PrimarySkillType.HERBALISM;
case TREE_FELLER:
return PrimarySkillType.WOODCUTTING;
case SUPER_BREAKER:
case BLAST_MINING:
return PrimarySkillType.MINING;
case SKULL_SPLITTER:
return PrimarySkillType.AXES;
case SERRATED_STRIKES:
return PrimarySkillType.SWORDS;
case GIGA_DRILL_BREAKER:
return PrimarySkillType.EXCAVATION;
default:
throw new InvalidSkillException("No parent defined for super ability! "+superAbilityType.toString());
}
return switch (superAbilityType) {
case BERSERK -> PrimarySkillType.UNARMED;
case GREEN_TERRA -> PrimarySkillType.HERBALISM;
case TREE_FELLER -> PrimarySkillType.WOODCUTTING;
case SUPER_BREAKER, BLAST_MINING -> PrimarySkillType.MINING;
case SKULL_SPLITTER -> PrimarySkillType.AXES;
case SERRATED_STRIKES -> PrimarySkillType.SWORDS;
case GIGA_DRILL_BREAKER -> PrimarySkillType.EXCAVATION;
case SUPER_SHOTGUN -> PrimarySkillType.CROSSBOWS;
case TRIDENTS_SUPER_ABILITY -> PrimarySkillType.TRIDENTS;
case EXPLOSIVE_SHOT -> PrimarySkillType.ARCHERY;
};
}
/**
@ -319,7 +328,6 @@ public class SkillTools {
}
public Set<SubSkillType> getSubSkills(PrimarySkillType primarySkillType) {
//TODO: Cache this!
return primarySkillChildrenMap.get(primarySkillType);
}
@ -329,14 +337,10 @@ public class SkillTools {
// TODO: This is a little "hacky", we probably need to add something to distinguish child skills in the enum, or to use another enum for them
public static boolean isChildSkill(PrimarySkillType primarySkillType) {
switch (primarySkillType) {
case SALVAGE:
case SMELTING:
return true;
default:
return false;
}
return switch (primarySkillType) {
case SALVAGE, SMELTING -> true;
default -> false;
};
}
/**
@ -401,34 +405,7 @@ public class SkillTools {
* @return true if the player has permissions, false otherwise
*/
public boolean superAbilityPermissionCheck(SuperAbilityType superAbilityType, Player player) {
switch (superAbilityType) {
case BERSERK:
return Permissions.berserk(player);
case BLAST_MINING:
return Permissions.remoteDetonation(player);
case GIGA_DRILL_BREAKER:
return Permissions.gigaDrillBreaker(player);
case GREEN_TERRA:
return Permissions.greenTerra(player);
case SERRATED_STRIKES:
return Permissions.serratedStrikes(player);
case SKULL_SPLITTER:
return Permissions.skullSplitter(player);
case SUPER_BREAKER:
return Permissions.superBreaker(player);
case TREE_FELLER:
return Permissions.treeFeller(player);
default:
return false;
}
return superAbilityType.getPermissions(player);
}
public @NotNull List<PrimarySkillType> getChildSkills() {
@ -450,4 +427,17 @@ public class SkillTools {
public @NotNull ImmutableList<PrimarySkillType> getMiscSkills() {
return MISC_SKILLS;
}
public @NotNull ImmutableList<PrimarySkillType> getChildSkillParents(PrimarySkillType childSkill)
throws IllegalArgumentException {
switch (childSkill) {
case SALVAGE -> {
return SALVAGE_PARENTS;
}
case SMELTING -> {
return SMELTING_PARENTS;
}
default -> throw new IllegalArgumentException("Skill " + childSkill + " is not a child skill");
}
}
}

View File

@ -13,6 +13,7 @@ import com.gmail.nossr50.mcMMO;
import com.gmail.nossr50.metadata.ItemMetadataService;
import com.gmail.nossr50.util.ItemUtils;
import com.gmail.nossr50.util.Misc;
import com.gmail.nossr50.util.Permissions;
import com.gmail.nossr50.util.player.NotificationManager;
import com.gmail.nossr50.util.player.UserManager;
import com.gmail.nossr50.util.text.StringUtils;
@ -33,6 +34,8 @@ import org.jetbrains.annotations.Nullable;
import java.util.Iterator;
import static java.util.Objects.requireNonNull;
public final class SkillUtils {
/**
* This is a static utility class, therefore we don't want any instances of
@ -264,8 +267,8 @@ public final class SkillUtils {
return false;
}
/**
* Modify the durability of an ItemStack, using Armor specific formula for unbreaking enchant damage reduction
*
@ -352,4 +355,14 @@ public final class SkillUtils {
return quantity;
}
/**
* Checks if a player can use a skill
* @param player target player
* @param subSkillType target subskill
* @return true if the player has permission and has the skill unlocked
*/
public static boolean canUseSubskill(Player player, @NotNull SubSkillType subSkillType) {
return Permissions.isSubSkillEnabled(player, subSkillType) && RankUtils.hasUnlockedSubskill(player, subSkillType);
}
}

View File

@ -133,38 +133,37 @@ public class TextComponentFactory {
TextComponent.Builder webTextComponent;
switch (webLinks) {
case WEBSITE:
case WEBSITE -> {
webTextComponent = Component.text().content(LocaleLoader.getString("JSON.Hover.AtSymbolURL"));
TextUtils.addChildWebComponent(webTextComponent, "Web");
webTextComponent.clickEvent(getUrlClickEvent(McMMOUrl.urlWebsite));
break;
case SPIGOT:
}
case SPIGOT -> {
webTextComponent = Component.text().content(LocaleLoader.getString("JSON.Hover.AtSymbolURL"));
TextUtils.addChildWebComponent(webTextComponent, "Spigot");
webTextComponent.clickEvent(getUrlClickEvent(McMMOUrl.urlSpigot));
break;
case DISCORD:
}
case DISCORD -> {
webTextComponent = Component.text().content(LocaleLoader.getString("JSON.Hover.AtSymbolURL"));
TextUtils.addChildWebComponent(webTextComponent, "Discord");
webTextComponent.clickEvent(getUrlClickEvent(McMMOUrl.urlDiscord));
break;
case PATREON:
}
case PATREON -> {
webTextComponent = Component.text().content(LocaleLoader.getString("JSON.Hover.AtSymbolURL"));
TextUtils.addChildWebComponent(webTextComponent, "Patreon");
webTextComponent.clickEvent(getUrlClickEvent(McMMOUrl.urlPatreon));
break;
case WIKI:
}
case WIKI -> {
webTextComponent = Component.text().content(LocaleLoader.getString("JSON.Hover.AtSymbolURL"));
TextUtils.addChildWebComponent(webTextComponent, "Wiki");
webTextComponent.clickEvent(getUrlClickEvent(McMMOUrl.urlWiki));
break;
case HELP_TRANSLATE:
}
case HELP_TRANSLATE -> {
webTextComponent = Component.text().content(LocaleLoader.getString("JSON.Hover.AtSymbolURL"));
TextUtils.addChildWebComponent(webTextComponent, "Lang");
webTextComponent.clickEvent(getUrlClickEvent(McMMOUrl.urlTranslate));
break;
default:
webTextComponent = Component.text().content("NOT DEFINED");
}
default -> webTextComponent = Component.text().content("NOT DEFINED");
}
TextUtils.addNewHoverComponentToTextComponent(webTextComponent, getUrlHoverEvent(webLinks));
@ -177,44 +176,45 @@ public class TextComponentFactory {
TextComponent.Builder componentBuilder = Component.text().content(webLinks.getNiceTitle());
switch (webLinks) {
case WEBSITE:
case WEBSITE -> {
addUrlHeaderHover(webLinks, componentBuilder);
componentBuilder.append(Component.newline()).append(Component.newline());
componentBuilder.append(Component.text(webLinks.getLocaleDescription(), NamedTextColor.GREEN));
componentBuilder.append(Component.text("\nDev Blogs, and information related to mcMMO can be found here", NamedTextColor.GRAY));
break;
case SPIGOT:
}
case SPIGOT -> {
addUrlHeaderHover(webLinks, componentBuilder);
componentBuilder.append(Component.newline()).append(Component.newline());
componentBuilder.append(Component.text(webLinks.getLocaleDescription(), NamedTextColor.GREEN));
componentBuilder.append(Component.text("\nI post regularly in the discussion thread here!", NamedTextColor.GRAY));
break;
case PATREON:
}
case PATREON -> {
addUrlHeaderHover(webLinks, componentBuilder);
componentBuilder.append(Component.newline()).append(Component.newline());
componentBuilder.append(Component.text(webLinks.getLocaleDescription(), NamedTextColor.GREEN));
componentBuilder.append(Component.newline());
componentBuilder.append(Component.text("Show support by buying me a coffee :)", NamedTextColor.GRAY));
break;
case WIKI:
}
case WIKI -> {
addUrlHeaderHover(webLinks, componentBuilder);
componentBuilder.append(Component.newline()).append(Component.newline());
componentBuilder.append(Component.text(webLinks.getLocaleDescription(), NamedTextColor.GREEN));
componentBuilder.append(Component.newline());
componentBuilder.append(Component.text("I'm looking for more wiki staff, contact me on our discord!", NamedTextColor.DARK_GRAY));
break;
case DISCORD:
}
case DISCORD -> {
addUrlHeaderHover(webLinks, componentBuilder);
componentBuilder.append(Component.newline()).append(Component.newline());
componentBuilder.append(Component.text(webLinks.getLocaleDescription(), NamedTextColor.GREEN));
break;
case HELP_TRANSLATE:
}
case HELP_TRANSLATE -> {
addUrlHeaderHover(webLinks, componentBuilder);
componentBuilder.append(Component.newline()).append(Component.newline());
componentBuilder.append(Component.text(webLinks.getLocaleDescription(), NamedTextColor.GREEN));
componentBuilder.append(Component.newline());
componentBuilder.append(Component.text("You can use this website to help translate mcMMO into your language!" +
"\nIf you want to know more contact me in discord.", NamedTextColor.DARK_GRAY));
}
}
return componentBuilder.build();
@ -474,7 +474,7 @@ public class TextComponentFactory {
/* NEW SKILL SYSTEM */
for (AbstractSubSkill abstractSubSkill : InteractionManager.getSubSkillList()) {
if (abstractSubSkill.getPrimarySkill() == parentSkill) {
if (Permissions.isSubSkillEnabled(player, abstractSubSkill))
if (Permissions.isSubSkillEnabled(player, abstractSubSkill.getSubSkillType()))
textComponents.add(TextComponentFactory.getSubSkillTextComponent(player, abstractSubSkill));
}
}