ores;
@@ -820,6 +816,14 @@ public boolean isCrossbow(@NotNull String id) {
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());
}
diff --git a/src/main/java/com/gmail/nossr50/util/MetadataConstants.java b/src/main/java/com/gmail/nossr50/util/MetadataConstants.java
index 090e04d17c..39f9479cea 100644
--- a/src/main/java/com/gmail/nossr50/util/MetadataConstants.java
+++ b/src/main/java/com/gmail/nossr50/util/MetadataConstants.java
@@ -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";
diff --git a/src/main/java/com/gmail/nossr50/util/Misc.java b/src/main/java/com/gmail/nossr50/util/Misc.java
index bbd9bb9990..2467a55403 100644
--- a/src/main/java/com/gmail/nossr50/util/Misc.java
+++ b/src/main/java/com/gmail/nossr50/util/Misc.java
@@ -343,4 +343,26 @@ public void run() {
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);
+// }
+
}
diff --git a/src/main/java/com/gmail/nossr50/util/MobHealthbarUtils.java b/src/main/java/com/gmail/nossr50/util/MobHealthbarUtils.java
index 3d956afe7a..5da29a6566 100644
--- a/src/main/java/com/gmail/nossr50/util/MobHealthbarUtils.java
+++ b/src/main/java/com/gmail/nossr50/util/MobHealthbarUtils.java
@@ -26,7 +26,7 @@ public static String fixDeathMessage(String deathMessage, Player player) {
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);
}
/**
diff --git a/src/main/java/com/gmail/nossr50/util/Permissions.java b/src/main/java/com/gmail/nossr50/util/Permissions.java
index c405f84ab7..6dcd9b800c 100644
--- a/src/main/java/com/gmail/nossr50/util/Permissions.java
+++ b/src/main/java/com/gmail/nossr50/util/Permissions.java
@@ -5,16 +5,18 @@
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 static boolean customXpBoost(Permissible permissible, PrimarySkillType sk
* 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 static boolean customXpBoost(Permissible permissible, PrimarySkillType sk
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 static boolean customXpBoost(Permissible permissible, PrimarySkillType sk
/* 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 @@ private static void addDynamicPermission(String permissionName, PermissionDefaul
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);
+ }
}
diff --git a/src/main/java/com/gmail/nossr50/util/commands/CommandRegistrationManager.java b/src/main/java/com/gmail/nossr50/util/commands/CommandRegistrationManager.java
index 371124b068..d272eeb76b 100644
--- a/src/main/java/com/gmail/nossr50/util/commands/CommandRegistrationManager.java
+++ b/src/main/java/com/gmail/nossr50/util/commands/CommandRegistrationManager.java
@@ -61,6 +61,9 @@ private static void registerSkillCommands() {
case AXES:
command.setExecutor(new AxesCommand());
break;
+ case CROSSBOWS:
+ command.setExecutor(new CrossbowsCommand());
+ break;
case EXCAVATION:
command.setExecutor(new ExcavationCommand());
@@ -97,6 +100,9 @@ private static void registerSkillCommands() {
case TAMING:
command.setExecutor(new TamingCommand());
break;
+ case TRIDENTS:
+ command.setExecutor(new TridentsCommand());
+ break;
case UNARMED:
command.setExecutor(new UnarmedCommand());
diff --git a/src/main/java/com/gmail/nossr50/util/random/InvalidActivationException.java b/src/main/java/com/gmail/nossr50/util/random/InvalidActivationException.java
deleted file mode 100644
index 677d3e63ea..0000000000
--- a/src/main/java/com/gmail/nossr50/util/random/InvalidActivationException.java
+++ /dev/null
@@ -1,5 +0,0 @@
-package com.gmail.nossr50.util.random;
-
-public class InvalidActivationException extends Exception {
- //Weee
-}
diff --git a/src/main/java/com/gmail/nossr50/util/random/Probability.java b/src/main/java/com/gmail/nossr50/util/random/Probability.java
new file mode 100644
index 0000000000..4bb9dacc95
--- /dev/null
+++ b/src/main/java/com/gmail/nossr50/util/random/Probability.java
@@ -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);
+ }
+}
diff --git a/src/main/java/com/gmail/nossr50/util/random/ProbabilityImpl.java b/src/main/java/com/gmail/nossr50/util/random/ProbabilityImpl.java
new file mode 100644
index 0000000000..e240f2f072
--- /dev/null
+++ b/src/main/java/com/gmail/nossr50/util/random/ProbabilityImpl.java
@@ -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);
+ }
+}
diff --git a/src/main/java/com/gmail/nossr50/util/random/ProbabilityUtil.java b/src/main/java/com/gmail/nossr50/util/random/ProbabilityUtil.java
new file mode 100644
index 0000000000..bc757506c7
--- /dev/null
+++ b/src/main/java/com/gmail/nossr50/util/random/ProbabilityUtil.java
@@ -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}
+ *
+ * 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 to use Lucky multiplier and influence the outcome
+ *
+ * 3) Creates a {@link Probability} and pipes it to {@link ProbabilityUtil} which processes the result and returns it
+ *
+ * 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)};
+ }
+}
diff --git a/src/main/java/com/gmail/nossr50/util/random/RandomChanceExecution.java b/src/main/java/com/gmail/nossr50/util/random/RandomChanceExecution.java
deleted file mode 100644
index e5d51d7401..0000000000
--- a/src/main/java/com/gmail/nossr50/util/random/RandomChanceExecution.java
+++ /dev/null
@@ -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();
-}
diff --git a/src/main/java/com/gmail/nossr50/util/random/RandomChanceSkill.java b/src/main/java/com/gmail/nossr50/util/random/RandomChanceSkill.java
deleted file mode 100644
index 92d91ec831..0000000000
--- a/src/main/java/com/gmail/nossr50/util/random/RandomChanceSkill.java
+++ /dev/null
@@ -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;
- }
-}
diff --git a/src/main/java/com/gmail/nossr50/util/random/RandomChanceSkillStatic.java b/src/main/java/com/gmail/nossr50/util/random/RandomChanceSkillStatic.java
deleted file mode 100644
index c96b71d6b7..0000000000
--- a/src/main/java/com/gmail/nossr50/util/random/RandomChanceSkillStatic.java
+++ /dev/null
@@ -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;
- }
-}
diff --git a/src/main/java/com/gmail/nossr50/util/random/RandomChanceStatic.java b/src/main/java/com/gmail/nossr50/util/random/RandomChanceStatic.java
deleted file mode 100644
index 0b09a4a3c9..0000000000
--- a/src/main/java/com/gmail/nossr50/util/random/RandomChanceStatic.java
+++ /dev/null
@@ -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;
- }
-}
diff --git a/src/main/java/com/gmail/nossr50/util/random/RandomChanceUtil.java b/src/main/java/com/gmail/nossr50/util/random/RandomChanceUtil.java
deleted file mode 100644
index 0191172c1b..0000000000
--- a/src/main/java/com/gmail/nossr50/util/random/RandomChanceUtil.java
+++ /dev/null
@@ -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);
- }
-}
diff --git a/src/main/java/com/gmail/nossr50/util/random/SkillProbabilityType.java b/src/main/java/com/gmail/nossr50/util/random/SkillProbabilityType.java
new file mode 100644
index 0000000000..95814f6a1d
--- /dev/null
+++ b/src/main/java/com/gmail/nossr50/util/random/SkillProbabilityType.java
@@ -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)
+}
diff --git a/src/main/java/com/gmail/nossr50/util/scoreboards/ScoreboardWrapper.java b/src/main/java/com/gmail/nossr50/util/scoreboards/ScoreboardWrapper.java
index 1237ce086c..4d2d515602 100644
--- a/src/main/java/com/gmail/nossr50/util/scoreboards/ScoreboardWrapper.java
+++ b/src/main/java/com/gmail/nossr50/util/scoreboards/ScoreboardWrapper.java
@@ -11,7 +11,6 @@
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 @@ private void updateSidebar() {
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));
}
}
diff --git a/src/main/java/com/gmail/nossr50/util/skills/CombatUtils.java b/src/main/java/com/gmail/nossr50/util/skills/CombatUtils.java
index 1d5eea33ff..a361595e0a 100644
--- a/src/main/java/com/gmail/nossr50/util/skills/CombatUtils.java
+++ b/src/main/java/com/gmail/nossr50/util/skills/CombatUtils.java
@@ -10,13 +10,13 @@
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 @@ private 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 @@ private static void printFinalDamageDebug(@NotNull Player player, @NotNull Entit
}
}
}
+ 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 @@ private static void processTamingCombat(@NotNull LivingEntity target, @Nullable
}
- 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 @@ private static void processArcheryCombat(@NotNull LivingEntity target, @NotNull
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 @@ private static void processArcheryCombat(@NotNull LivingEntity target, @NotNull
"Initial Damage: "+initialDamage,
"Final Damage: "+boostedDamage);
//Clean data
- cleanupArrowMetadata(arrow);
+ delayArrowMetaCleanup(arrow);
}
/**
@@ -383,6 +455,15 @@ else if (ItemUtils.isUnarmed(heldItem)) {
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) {
@@ -396,20 +477,25 @@ else if (entityType == EntityType.WOLF) {
}
}
}
- else if (entityType == EntityType.ARROW || entityType == EntityType.SPECTRAL_ARROW) {
- Projectile arrow = (Projectile) painSource;
+ else if (painSource instanceof Arrow arrow) {
ProjectileSource projectileSource = arrow.getShooter();
-
- 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);
+ boolean isCrossbow = arrow.isShotFromCrossbow();
+ if (projectileSource instanceof Player player) {
+
+ 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)
@@ -420,7 +506,6 @@ else if (entityType == EntityType.ARROW || entityType == EntityType.SPECTRAL_ARR
}
}
}
-
}
/**
@@ -621,7 +706,7 @@ public static void applyIgnoreDamageMetadata(@NotNull LivingEntity target) {
}
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) {
@@ -630,35 +715,6 @@ public static void dealNoInvulnerabilityTickDamageRupture(@NotNull LivingEntity
}
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);
-// }
}
/**
@@ -741,14 +797,16 @@ public static void processCombatXP(@NotNull McMMOPlayer mcMMOPlayer,
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();
}
}
@@ -807,7 +865,7 @@ else if (target instanceof Monster)
baseXP *= multiplier;
- if (baseXP != 0) {
+ if (baseXP > 0) {
mcMMO.p.getFoliaLib().getImpl().runAtEntity(mcMMOPlayer.getPlayer(), new AwardCombatXpTask(mcMMOPlayer, primarySkillType, baseXP, target, xpGainReason));
}
}
@@ -945,31 +1003,12 @@ public static void modifyMoveSpeed(@NotNull LivingEntity livingEntity, double mu
}
}
- /**
- * 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);
}
}
diff --git a/src/main/java/com/gmail/nossr50/util/skills/ProjectileUtils.java b/src/main/java/com/gmail/nossr50/util/skills/ProjectileUtils.java
new file mode 100644
index 0000000000..f64bac7a3e
--- /dev/null
+++ b/src/main/java/com/gmail/nossr50/util/skills/ProjectileUtils.java
@@ -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));
+ }
+ }
+}
diff --git a/src/main/java/com/gmail/nossr50/util/skills/RankUtils.java b/src/main/java/com/gmail/nossr50/util/skills/RankUtils.java
index ec4d56481e..e064971285 100644
--- a/src/main/java/com/gmail/nossr50/util/skills/RankUtils.java
+++ b/src/main/java/com/gmail/nossr50/util/skills/RankUtils.java
@@ -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 static void executeSkillUnlockNotifications(Plugin plugin, McMMOPlayer mc
}
}
+ /**
+ * Reset the interval between skill unlock notifications
+ */
public static void resetUnlockDelayTimer()
{
count = 0;
diff --git a/src/main/java/com/gmail/nossr50/util/skills/SkillTools.java b/src/main/java/com/gmail/nossr50/util/skills/SkillTools.java
index 416926fe46..985f93bce6 100644
--- a/src/main/java/com/gmail/nossr50/util/skills/SkillTools.java
+++ b/src/main/java/com/gmail/nossr50/util/skills/SkillTools.java
@@ -30,6 +30,8 @@ public class SkillTools {
public final @NotNull ImmutableSet EXACT_SUBSKILL_NAMES;
public final @NotNull ImmutableList CHILD_SKILLS;
public final static @NotNull ImmutableList NON_CHILD_SKILLS;
+ public final static @NotNull ImmutableList SALVAGE_PARENTS;
+ public final static @NotNull ImmutableList SMELTING_PARENTS;
public final @NotNull ImmutableList COMBAT_SKILLS;
public final @NotNull ImmutableList GATHERING_SKILLS;
public final @NotNull ImmutableList 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 SkillTools(@NotNull mcMMO pluginRef) {
*/
List childSkills = new ArrayList<>();
-// List 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 SkillTools(@NotNull mcMMO pluginRef) {
}
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 ToolType getPrimarySkillToolType(PrimarySkillType primarySkillType) {
}
public Set getSubSkills(PrimarySkillType primarySkillType) {
- //TODO: Cache this!
return primarySkillChildrenMap.get(primarySkillType);
}
@@ -329,14 +337,10 @@ public double getXpModifier(PrimarySkillType primarySkillType) {
// 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 int getLevelCap(@NotNull PrimarySkillType primarySkillType) {
* @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 getChildSkills() {
@@ -450,4 +427,17 @@ public boolean superAbilityPermissionCheck(SuperAbilityType superAbilityType, Pl
public @NotNull ImmutableList getMiscSkills() {
return MISC_SKILLS;
}
+
+ public @NotNull ImmutableList 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");
+ }
+ }
}
diff --git a/src/main/java/com/gmail/nossr50/util/skills/SkillUtils.java b/src/main/java/com/gmail/nossr50/util/skills/SkillUtils.java
index 6a15963efb..2012d60715 100644
--- a/src/main/java/com/gmail/nossr50/util/skills/SkillUtils.java
+++ b/src/main/java/com/gmail/nossr50/util/skills/SkillUtils.java
@@ -13,6 +13,7 @@
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 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 @@ private static boolean isLocalizedSkill(String skillName) {
return false;
}
-
-
+
+
/**
* Modify the durability of an ItemStack, using Armor specific formula for unbreaking enchant damage reduction
*
@@ -352,4 +355,14 @@ public static int getRepairAndSalvageQuantities(Material itemMaterial, Material
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);
+ }
}
diff --git a/src/main/java/com/gmail/nossr50/util/text/TextComponentFactory.java b/src/main/java/com/gmail/nossr50/util/text/TextComponentFactory.java
index 81ac8da280..65edc3d9e9 100644
--- a/src/main/java/com/gmail/nossr50/util/text/TextComponentFactory.java
+++ b/src/main/java/com/gmail/nossr50/util/text/TextComponentFactory.java
@@ -133,38 +133,37 @@ private static Component getWebLinkTextComponent(McMMOWebLinks webLinks) {
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 @@ private static Component getUrlHoverEvent(McMMOWebLinks webLinks) {
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 static void getSubSkillTextComponents(Player player, List text
/* 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));
}
}
diff --git a/src/main/resources/advanced.yml b/src/main/resources/advanced.yml
index 4e37db678e..7d56c4e37a 100644
--- a/src/main/resources/advanced.yml
+++ b/src/main/resources/advanced.yml
@@ -5,7 +5,7 @@
# For advanced users only! There is no need to change anything here.
#
# You can customize almost every aspect of every skill here.
-# Its mainly here if you've customized the experience formula.
+# It's mainly here if you've customized the experience formula.
# Configure at what level you get better with certain abilities.
#
#####
@@ -20,7 +20,7 @@ Feedback:
PlayerTips: true
SkillCommand:
BlankLinesAboveHeader: true
- # If sendtitles is true messages will be sent using the title api (BIG TEXT ON SCREEN)
+ # If sendtitles is true, messages will be sent using the title api (BIG TEXT ON SCREEN)
Events:
XP:
SendTitles: true
@@ -264,6 +264,11 @@ Skills:
MaxBonusLevel:
Standard: 100
RetroMode: 1000
+ VerdantBounty:
+ ChanceMax: 50.0
+ MaxBonusLevel:
+ Standard: 1000
+ RetroMode: 10000
HylianLuck:
# ChanceMax: Maximum chance of Hylian Luck when on or higher
@@ -284,6 +289,11 @@ Skills:
# Settings for Mining
###
Mining:
+ MotherLode:
+ MaxBonusLevel:
+ Standard: 1000
+ RetroMode: 10000
+ ChanceMax: 50.0
SuperBreaker:
AllowTripleDrops: true
DoubleDrops:
@@ -608,9 +618,16 @@ Skills:
Knock_On_Wood:
Add_XP_Orbs_To_Drops: true
+ # Triple Drops
+ CleanCuts:
+ # ChanceMax: Maximum chance of receiving triple drops (100 = 100%)
+ # MaxBonusLevel: Level when the maximum chance of receiving triple drops is reached
+ ChanceMax: 50.0
+ MaxBonusLevel:
+ Standard: 1000
+ RetroMode: 10000
# Double Drops
HarvestLumber:
- # ChanceMax & MaxBonusLevel are only used for Classic, I'll make that more clear in the future.
# ChanceMax: Maximum chance of receiving double drops (100 = 100%)
# MaxBonusLevel: Level when the maximum chance of receiving double drops is reached
ChanceMax: 100.0
diff --git a/src/main/resources/chat.yml b/src/main/resources/chat.yml
index 34f812a07f..febf9c9f68 100644
--- a/src/main/resources/chat.yml
+++ b/src/main/resources/chat.yml
@@ -8,10 +8,12 @@ Chat:
Enable: true
# Whether to use the current display name of a player
Use_Display_Names: true
+ Send_To_Console: true
Spies:
# Whether players with the chat spy permission join the server with chat spying toggled on
Automatically_Enable_Spying: false
Admin:
+ Send_To_Console: true
# Enable or disable admin chat
Enable: true
# Whether to use the current display name of a player
diff --git a/src/main/resources/child.yml b/src/main/resources/child.yml
deleted file mode 100644
index 685a6ce714..0000000000
--- a/src/main/resources/child.yml
+++ /dev/null
@@ -1,16 +0,0 @@
-#
-# mcMMO child skill configuration
-# Last updated on ${project.version}-b${BUILD_NUMBER}
-#
-# You do not need to modify this file except to change parents of child skills
-#
-# If you wish a child skill to be the parent of another child skill, you must also make your changes to the child.yml within the jar
-# WARNING: THIS IS NOT SUPPORTED, IF YOU DO SO YOU ARE RESPONSIBLE FOR THE ISSUES THAT MAY ARISE. That said, watch out for circular dependencies, those are bad.
-#
-#####
-Salvage:
- - Fishing
- - Repair
-Smelting:
- - Mining
- - Repair
\ No newline at end of file
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
index 789729cc67..4df9d2eb0b 100644
--- a/src/main/resources/config.yml
+++ b/src/main/resources/config.yml
@@ -49,6 +49,10 @@ General:
RetroMode:
Enabled: true
Locale: en_US
+ PowerLevel:
+ Skill_Mastery:
+ Enabled: true
+ AprilFoolsEvent: true
MOTD_Enabled: true
EventBroadcasts: true
EventInfoOnPlayerJoin: true
diff --git a/src/main/resources/experience.yml b/src/main/resources/experience.yml
index 00e72f01d4..e83fd37700 100644
--- a/src/main/resources/experience.yml
+++ b/src/main/resources/experience.yml
@@ -77,6 +77,10 @@ Experience_Bars:
Enable: true
Color: BLUE
BarStyle: SEGMENTED_6
+ Crossbows:
+ Enable: true
+ Color: BLUE
+ BarStyle: SEGMENTED_6
Excavation:
Enable: true
Color: YELLOW
@@ -113,6 +117,10 @@ Experience_Bars:
Enable: true
Color: RED
BarStyle: SEGMENTED_6
+ Tridents:
+ Enable: true
+ Color: BLUE
+ BarStyle: SEGMENTED_6
Unarmed:
Enable: true
Color: BLUE
@@ -159,8 +167,10 @@ Experience_Formula:
Breeding:
Multiplier: 1.0
- # Experience gained will get divided by these values. 1.0 by default, 2.0 means two times less XP gained.
- Modifier:
+ # Experience gained will get multiplied by these values. 1.0 by default, 0.5 means half XP gained. This happens right before multiplying the XP by the global multiplier.
+ Skill_Multiplier:
+ Crossbows: 1.0
+ Tridents: 1.0
Swords: 1.0
Taming: 1.0
Acrobatics: 1.0
diff --git a/src/main/resources/locale/locale_en_US.properties b/src/main/resources/locale/locale_en_US.properties
index 27c45d0d91..84316e4c5e 100644
--- a/src/main/resources/locale/locale_en_US.properties
+++ b/src/main/resources/locale/locale_en_US.properties
@@ -21,6 +21,7 @@ JSON.Acrobatics=Acrobatics
JSON.Alchemy=Alchemy
JSON.Archery=Archery
JSON.Axes=Axes
+JSON.Crossbows=Crossbows
JSON.Excavation=Excavation
JSON.Fishing=Fishing
JSON.Herbalism=Herbalism
@@ -29,6 +30,7 @@ JSON.Repair=Repair
JSON.Salvage=Salvage
JSON.Swords=Swords
JSON.Taming=Taming
+JSON.Tridents=Tridents
JSON.Unarmed=Unarmed
JSON.Woodcutting=Woodcutting
JSON.URL.Website=The official mcMMO Website!
@@ -88,6 +90,7 @@ Overhaul.Name.Acrobatics=Acrobatics
Overhaul.Name.Alchemy=Alchemy
Overhaul.Name.Archery=Archery
Overhaul.Name.Axes=Axes
+Overhaul.Name.Crossbows=Crossbows
Overhaul.Name.Excavation=Excavation
Overhaul.Name.Fishing=Fishing
Overhaul.Name.Herbalism=Herbalism
@@ -97,6 +100,7 @@ Overhaul.Name.Salvage=Salvage
Overhaul.Name.Smelting=Smelting
Overhaul.Name.Swords=Swords
Overhaul.Name.Taming=Taming
+Overhaul.Name.Tridents=Tridents
Overhaul.Name.Unarmed=Unarmed
Overhaul.Name.Woodcutting=Woodcutting
# /mcMMO Command Style Stuff
@@ -112,6 +116,7 @@ XPBar.Acrobatics=Acrobatics Lv.&6{0}
XPBar.Alchemy=Alchemy Lv.&6{0}
XPBar.Archery=Archery Lv.&6{0}
XPBar.Axes=Axes Lv.&6{0}
+XPBar.Crossbows=Crossbows Lv.&6{0}
XPBar.Excavation=Excavation Lv.&6{0}
XPBar.Fishing=Fishing Lv.&6{0}
XPBar.Herbalism=Herbalism Lv.&6{0}
@@ -121,6 +126,7 @@ XPBar.Salvage=Salvage Lv.&6{0}
XPBar.Smelting=Smelting Lv.&6{0}
XPBar.Swords=Swords Lv.&6{0}
XPBar.Taming=Taming Lv.&6{0}
+XPBar.Tridents=Tridents Lv.&6{0}
XPBar.Unarmed=Unarmed Lv.&6{0}
XPBar.Woodcutting=Woodcutting Lv.&6{0}
#This is just a preset template that gets used if the 'ExtraDetails' setting is turned on in experience.yml (off by default), you can ignore this template and just edit the strings above
@@ -176,6 +182,13 @@ Archery.SubSkill.ArcheryLimitBreak.Description=Breaking your limits. Increased d
Archery.SubSkill.ArcheryLimitBreak.Stat=Limit Break Max DMG
Archery.Listener=Archery:
Archery.SkillName=ARCHERY
+Archery.SubSkill.ExplosiveShot.Name=Explosive Shot
+Archery.SubSkill.ExplosiveShot.Description=Fire an explosive arrow
+Archery.Skills.ExplosiveShot.Off=
+Archery.Skills.ExplosiveShot.On=&a**EXPLOSIVE SHOT ACTIVATED**
+Archery.Skills.ExplosiveShot.Other.Off=Explosive Shot&a has worn off for &e{0}
+Archery.Skills.ExplosiveShot.Other.On=&a{0}&2 has used &cExplosive Shot!
+Archery.Skills.ExplosiveShot.Refresh=&aYour &Explosive Shot &ability is refreshed!
#AXES
Axes.Ability.Bonus.0=Axe Mastery
Axes.Ability.Bonus.1=Bonus {0} damage
@@ -285,8 +298,11 @@ Herbalism.SubSkill.FarmersDiet.Name=Farmer's Diet
Herbalism.SubSkill.FarmersDiet.Description=Improves hunger restored from farmed foods
Herbalism.SubSkill.FarmersDiet.Stat=Farmer's Diet: &aRank {0}
Herbalism.SubSkill.DoubleDrops.Name=Double Drops
-Herbalism.SubSkill.DoubleDrops.Description=Double the normal loot
+Herbalism.SubSkill.DoubleDrops.Description=Skillfully harvest double the loot
Herbalism.SubSkill.DoubleDrops.Stat=Double Drop Chance
+Herbalism.SubSkill.VerdantBounty.Name=Verdant Bounty
+Herbalism.SubSkill.VerdantBounty.Description=Masterfully harvest triple the loot
+Herbalism.SubSkill.VerdantBounty.Stat=Triple Drop Chance
Herbalism.SubSkill.HylianLuck.Name=Hylian Luck
Herbalism.SubSkill.HylianLuck.Description=Gives a small chance of finding rare items
Herbalism.SubSkill.HylianLuck.Stat=Hylian Luck Chance
@@ -311,8 +327,11 @@ Mining.SubSkill.SuperBreaker.Name=Super Breaker
Mining.SubSkill.SuperBreaker.Description=Speed+, Triple Drop Chance
Mining.SubSkill.SuperBreaker.Stat=Super Breaker Length
Mining.SubSkill.DoubleDrops.Name=Double Drops
-Mining.SubSkill.DoubleDrops.Description=Double the normal loot
+Mining.SubSkill.DoubleDrops.Description=Skillfully mine double the loot
Mining.SubSkill.DoubleDrops.Stat=Double Drop Chance
+Mining.SubSkill.MotherLode.Name=Mother Lode
+Mining.SubSkill.MotherLode.Description=Masterfully mine triple the loot
+Mining.SubSkill.MotherLode.Stat=Triple Drop Chance
Mining.SubSkill.BlastMining.Name=Blast Mining
Mining.SubSkill.BlastMining.Description=Bonuses to mining with TNT
Mining.SubSkill.BlastMining.Stat=Blast Mining:&a Rank {0}/{1} &7({2})
@@ -395,7 +414,7 @@ Salvage.Skills.Adept.Level=You must be level &e{0}&c to salvage &e{1}
Salvage.Skills.TooDamaged=&4This item is too damaged to be salvaged.
Salvage.Skills.ArcaneFailed=&cYou were unable to extract the knowledge contained within this item.
Salvage.Skills.ArcanePartial=&cYou were only able to extract some of the knowledge contained within this item.
-Salvage.Skills.ArcaneSuccess=&aYou able to extract all of the knowledge contained within this item!
+Salvage.Skills.ArcaneSuccess=&aYou were able to extract all the knowledge contained within this item!
Salvage.Listener.Anvil=&4You have placed a Salvage anvil, use this to Salvage tools and armor.
Salvage.Listener=Salvage:
Salvage.SkillName=SALVAGE
@@ -404,6 +423,51 @@ Salvage.Skills.Lottery.Perfect=&a&lPerfect!&r&6 You salvaged &3{1}&6 effortlessl
Salvage.Skills.Lottery.Untrained=&7You aren't properly trained in salvaging. You were only able to recover &c{0}&7 materials from &a{1}&7.
#Anvil (Shared between SALVAGE and REPAIR)
Anvil.Unbreakable=This item is unbreakable!
+#CROSSBOWS
+Crossbows.SkillName=CROSSBOWS
+Crossbows.Ability.Lower=&7You lower your crossbow.
+Crossbows.Ability.Ready=&3You &6ready&3 your Crossbow.
+Crossbows.Skills.SSG.Refresh=&aYour &eSuper Shotgun &aability is refreshed!
+Crossbows.Skills.SSG.Other.On=&a{0}&2 used &Super Shotgun!
+Crossbows.SubSkill.PoweredShot.Name=Powered Shot
+Crossbows.SubSkill.PoweredShot.Description=Increases damage done with crossbows
+Crossbows.SubSkill.PoweredShot.Stat=Powered Shot Bonus Damage
+Crossbows.SubSkill.CrossbowsLimitBreak.Name=Crossbows Limit Break
+Crossbows.SubSkill.CrossbowsLimitBreak.Description=Breaking your limits. Increased damage against tough opponents. Intended for PVP, up to server settings for whether it will boost damage in PVE.
+Crossbows.SubSkill.CrossbowsLimitBreak.Stat=Limit Break Max DMG
+Crossbows.SubSkill.TrickShot.Name=Trick Shot
+Crossbows.SubSkill.TrickShot.Description=Richochet arrows with steep angles
+Crossbows.SubSkill.TrickShot.Stat=Trick Shot Max Bounces
+Crossbows.SubSkill.TrickShot.Stat.Extra=Trick Shot Max Bounces: &a{0}
+Crossbows.SubSkill.TrickShot.Stat.Extra2=Trick Shot Reduced DMG per Bounce: &a{0}
+Crossbows.SubSkill.SuperShotgun.Name=Super Shotgun
+Crossbows.Listener=Crossbows:
+
+#TRIDENTS
+Tridents.SkillName=TRIDENTS
+Tridents.Ability.Lower=&7You lower your trident.
+Tridents.Ability.Ready=&3You &6ready&3 your Trident.
+Tridents.SubSkill.Impale.Name=Impale
+Tridents.SubSkill.Impale.Description=Increases damage done with tridents
+Tridents.SubSkill.Impale.Stat=Impale Bonus Damage
+Tridents.SubSkill.TridentsLimitBreak.Name=Tridents Limit Break
+Tridents.SubSkill.TridentsLimitBreak.Description=Breaking your limits. Increased damage against tough opponents. Intended for PVP, up to server settings for whether it will boost damage in PVE.
+Tridents.SubSkill.TridentsLimitBreak.Stat=Limit Break Max DMG
+Tridents.SubSkill.TridentAbility.Name=WIP
+Tridents.Listener=Tridents:
+
+#MACES
+Maces.SkillName=MACES
+Maces.Ability.Lower=&7You lower your mace.
+Maces.Ability.Ready=&3You &6ready&3 your Mace.
+Maces.Skills.MaceSmash.Refresh=&aYour &eGiga Smash &aability is refreshed!
+Maces.Skills.MaceSmash.Other.On=&a{0}&2 used &cGiga Smash!
+Maces.SubSkill.GigaSmash.Name=Giga Smash
+Maces.SubSkill.MacesLimitBreak.Name=Maces Limit Break
+Maces.SubSkill.MacesLimitBreak.Description=Breaking your limits. Increased damage against tough opponents. Intended for PVP, up to server settings for whether it will boost damage in PVE.
+Maces.SubSkill.MacesLimitBreak.Stat=Limit Break Max DMG
+Maces.Listener=Maces:
+
#SWORDS
Swords.Ability.Lower=&7You lower your sword.
Swords.Ability.Ready=&3You &6ready&3 your Sword.
@@ -542,8 +606,11 @@ Woodcutting.SubSkill.KnockOnWood.Stat=Knock on Wood
Woodcutting.SubSkill.KnockOnWood.Loot.Normal=Standard loot from trees
Woodcutting.SubSkill.KnockOnWood.Loot.Rank2=Standard loot from trees and experience orbs
Woodcutting.SubSkill.HarvestLumber.Name=Harvest Lumber
-Woodcutting.SubSkill.HarvestLumber.Description=Skillfully extract more Lumber
+Woodcutting.SubSkill.HarvestLumber.Description=Skillfully extract up to double the Lumber
Woodcutting.SubSkill.HarvestLumber.Stat=Double Drop Chance
+Woodcutting.SubSkill.CleanCuts.Name=Clean Cuts
+Woodcutting.SubSkill.CleanCuts.Description=Masterfully extract up to triple the Lumber
+Woodcutting.SubSkill.CleanCuts.Stat=Triple Drop Chance
Woodcutting.SubSkill.Splinter.Name=Splinter
Woodcutting.SubSkill.Splinter.Description=Cut down trees more efficiently.
Woodcutting.SubSkill.BarkSurgeon.Name=Bark Surgeon
@@ -830,6 +897,7 @@ Commands.XPGain.Alchemy=Brewing Potions
Commands.XPGain.Archery=Attacking Monsters
Commands.XPGain.Axes=Attacking Monsters
Commands.XPGain.Child=Gains levels from Parent Skills
+Commands.XPGain.Crossbows=Attacking Monsters
Commands.XPGain.Excavation=Digging and finding treasures
Commands.XPGain.Fishing=Fishing (Go figure!)
Commands.XPGain.Herbalism=Harvesting Herbs
@@ -837,6 +905,7 @@ Commands.XPGain.Mining=Mining Stone & Ore
Commands.XPGain.Repair=Repairing
Commands.XPGain.Swords=Attacking Monsters
Commands.XPGain.Taming=Animal Taming, or combat w/ your wolves
+Commands.XPGain.Tridents=Attacking Monsters
Commands.XPGain.Unarmed=Attacking Monsters
Commands.XPGain.Woodcutting=Chopping down trees
Commands.XPGain=&8XP GAIN: &f{0}
@@ -970,6 +1039,13 @@ Guides.Woodcutting.Section.0=&3About Woodcutting:\n&eWoodcutting is all about ch
Guides.Woodcutting.Section.1=&3How does Tree Feller work?\n&eTree Feller is an active ability, you can right-click\n&ewhile holding an ax to activate Tree Feller. This will\n&ecause the entire tree to break instantly, dropping all\n&eof its logs at once.
Guides.Woodcutting.Section.2=&3How does Leaf Blower work?\n&eLeaf Blower is a passive ability that will cause leaf\n&eblocks to break instantly when hit with an axe. By default,\nðis ability unlocks at level 100.
Guides.Woodcutting.Section.3=&3How do Double Drops work?\n&eThis passive ability gives you a chance to obtain an extra\n&eblock for every log you chop.
+# Crossbows
+Guides.Crossbows.Section.0=&3About Crossbows:\n&eCrossbows is all about shooting with your crossbow.\n\n&3XP GAIN:\n&eXP is gained whenever you shoot mobs with a crossbow.
+Guides.Crossbows.Section.1=&3How does Trickshot work?\n&eTrickshot is an passive ability, you shoot your bolts at a shallow angle with a crossbow to attempt a Trickshot. This will cause the arrow to ricochet off of blocks and potentially hit a target. The number of potential bounces from a ricochet depend on the rank of Trickshot.
+# Tridents
+Guides.Tridents.Section.0=&3About Tridents:\n&eTridents skill involves impaling foes with your trident.\n\n&3XP GAIN:\n&eXP is gained whenever you hit mobs with a trident.
+
+
#INSPECT
Inspect.Offline= &cYou do not have permission to inspect offline players!
Inspect.OfflineStats=mcMMO Stats for Offline Player &e{0}
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index a8b56712c8..198d465fc1 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -20,9 +20,6 @@ folia-supported: true
api-version: 1.13
commands:
-# mmodroptreasures:
-# description: An admin command used to spawn treasure drops
-# permission: mcmmo.commands.droptreasures
mmoxpbar:
aliases: xpbarsettings
description: Change XP bar settings
@@ -124,12 +121,18 @@ commands:
archery:
description: Detailed mcMMO skill info
permission: mcmmo.commands.archery
+ crossbows:
+ description: Detailed mcMMO skill info
+ permission: mcmmo.commands.crossbows
swords:
description: Detailed mcMMO skill info
permission: mcmmo.commands.swords
taming:
description: Detailed mcMMO skill info
permission: mcmmo.commands.taming
+ tridents:
+ description: Detailed mcMMO skill info
+ permission: mcmmo.commands.tridents
unarmed:
description: Detailed mcMMO skill info
permission: mcmmo.commands.unarmed
@@ -151,6 +154,10 @@ commands:
salvage:
description: Detailed mcMMO skill info
permission: mcmmo.commands.salvage
+ mmopower:
+ description: Shows skill mastery and power level info
+ permission: mcmmo.commands.mmopower
+ aliases: [mmopowerlevel, powerlevel]
adminchat:
aliases: [ac, a]
description: Toggle Admin chat or send admin chat messages
@@ -234,6 +241,7 @@ permissions:
mcmmo.ability.alchemy.all: true
mcmmo.ability.archery.all: true
mcmmo.ability.axes.all: true
+ mcmmo.ability.crossbows.all: true
mcmmo.ability.excavation.all: true
mcmmo.ability.fishing.all: true
mcmmo.ability.herbalism.all: true
@@ -243,6 +251,7 @@ permissions:
mcmmo.ability.smelting.all: true
mcmmo.ability.swords.all: true
mcmmo.ability.taming.all: true
+ mcmmo.ability.tridents.all: true
mcmmo.ability.unarmed.all: true
mcmmo.ability.woodcutting.all: true
mcmmo.ability.acrobatics.*:
@@ -281,12 +290,15 @@ permissions:
mcmmo.ability.archery.all:
description: Allows access to all Archery abilities
children:
+ mcmmo.ability.archery.explosiveshot: true
mcmmo.ability.archery.skillshot: true
mcmmo.ability.archery.daze: true
mcmmo.ability.archery.arrowretrieval: true
mcmmo.ability.archery.archerylimitbreak: true
+ mcmmo.ability.archery.explosiveshot:
+ description: Allows access to the Explosive Shot super ability for Archery
mcmmo.ability.archery.archerylimitbreak:
- description: Adds damage to bows and crossbows
+ description: Adds damage to bows
mcmmo.ability.archery.skillshot:
description: Allows bonus damage from the Archery SkillShot ability
mcmmo.ability.archery.daze:
@@ -319,6 +331,22 @@ permissions:
description: Allows access to the Impact ability
mcmmo.ability.axes.skullsplitter:
description: Allows access to the Skull Splitter ability
+ mcmmo.ability.crossbows.*:
+ description: Allows access to all Crossbows abilities
+ children:
+ mcmmo.ability.crossbows.all: true
+ mcmmo.ability.crossbows.all:
+ description: Allows access to all Crossbows abilities
+ children:
+ mcmmo.ability.crossbows.trickshot: true
+ mcmmo.ability.crossbows.poweredshot: true
+ mcmmo.ability.crossbows.crossbowslimitbreak: true
+ mcmmo.ability.crossbows.crossbowslimitbreak:
+ description: Adds damage to crossbows
+ mcmmo.ability.crossbows.trickshot:
+ description: Allows access to the Trick Shot ability
+ mcmmo.ability.crossbows.poweredshot:
+ description: Allows access to the Powered Shot ability
mcmmo.ability.excavation.*:
default: false
description: Allows access to all Excavation abilities
@@ -376,6 +404,9 @@ permissions:
mcmmo.ability.herbalism.greenthumb.all: true
mcmmo.ability.herbalism.hylianluck: true
mcmmo.ability.herbalism.shroomthumb: true
+ mcmmo.ability.herbalism.verdantbounty: true
+ mcmmo.ability.herbalism.verdantbounty:
+ description: Allows access to end game progression for Herbalism
mcmmo.ability.herbalism.doubledrops:
description: Allows double drop chance from Herbalism
mcmmo.ability.herbalism.farmersdiet:
@@ -456,6 +487,7 @@ permissions:
mcmmo.ability.mining.blastmining.all: true
mcmmo.ability.mining.doubledrops: true
mcmmo.ability.mining.superbreaker: true
+ mcmmo.ability.mining.motherlode: true
mcmmo.ability.mining.blastmining.*:
default: false
description: Allows access to all Blast Mining abilities
@@ -473,6 +505,8 @@ permissions:
description: Allows access to the Demolitions Expertise ability
mcmmo.ability.mining.blastmining.detonate:
description: Allows for remote TNT detonation
+ mcmmo.ability.mining.motherlode:
+ description: Allows access to mother lode subskill
mcmmo.ability.mining.doubledrops:
description: Allows double drop chance when mining
mcmmo.ability.mining.superbreaker:
@@ -677,6 +711,20 @@ permissions:
description: Allows access to the Thick Fur ability
mcmmo.ability.taming.pummel:
description: Allows access to the Pummel ability
+ mcmmo.ability.tridents.*:
+ default: false
+ description: Allows access to all Trident abilities
+ children:
+ mcmmo.ability.tridents.all: true
+ mcmmo.ability.tridents.all:
+ description: Allows access to all Trident abilities
+ children:
+ mcmmo.ability.tridents.impale: true
+ mcmmo.ability.tridents.tridentslimitbreak: true
+ mcmmo.ability.tridents.impale:
+ description: Allows access to tridents Impale ability
+ mcmmo.ability.tridents.tridentslimitbreak:
+ description: Adds damage to tridents
mcmmo.ability.unarmed.*:
default: false
description: Allows access to all Unarmed abilities
@@ -721,6 +769,9 @@ permissions:
mcmmo.ability.woodcutting.knockonwood: true
mcmmo.ability.woodcutting.leafblower: true
mcmmo.ability.woodcutting.treefeller: true
+ mcmmo.ability.woodcutting.cleancuts: true
+ mcmmo.ability.woodcutting.cleancuts:
+ description: Allows access to end game progression for Woodcutting
mcmmo.ability.woodcutting.knockonwood:
description: Allows access to Knock on Wood subskill
mcmmo.ability.woodcutting.splinter:
@@ -802,6 +853,8 @@ permissions:
mcmmo.commands.alchemy: true
mcmmo.commands.archery: true
mcmmo.commands.axes: true
+ mcmmo.commands.crossbows: true
+ mcmmo.commands.tridents: true
mcmmo.commands.excavation: true
mcmmo.commands.fishing: true
mcmmo.commands.herbalism: true
@@ -824,6 +877,7 @@ permissions:
mcmmo.commands.taming: true
mcmmo.commands.unarmed: true
mcmmo.commands.woodcutting: true
+ mcmmo.commands.mmopower: true
mcmmo.commands.defaultsop:
description: Implies all default op mcmmo.commands permissions.
children:
@@ -871,29 +925,16 @@ permissions:
description: Allows access to the archery command
mcmmo.commands.axes:
description: Allows access to the axes command
+ mcmmo.commands.crossbows:
+ description: Allows access to the crossbows command
mcmmo.commands.excavation:
description: Allows access to the excavation command
mcmmo.commands.fishing:
description: Allows access to the fishing command
-# mcmmo.commands.hardcore.*:
-# default: false
-# description: Implies access to all mcmmo.commands.hardcore permissions
-# children:
-# mcmmo.commands.hardcore.all: true
-# mcmmo.commands.hardcore.all:
-# description: Implies access to all mcmmo.commands.hardcore permissions
-# children:
-# mcmmo.commands.hardcore: true
-# mcmmo.commands.hardcore.modify: true
-# mcmmo.commands.hardcore.toggle: true
-# mcmmo.commands.hardcore:
-# description: Allows access to the hardcore command
-# mcmmo.commands.hardcore.modify:
-# description: Allows access to the hardcore command to modify the hardcore rate
-# mcmmo.commands.hardcore.toggle:
-# description: Allows access to the hardcore command to toggle hardcore on/off
mcmmo.commands.herbalism:
description: Allows access to the herbalism command
+ mcmmo.commands.tridents:
+ description: Allows access to the tridents command
mcmmo.commands.inspect.*:
default: false
description: Implies access to all mcmmo.commands.inspect permissions
@@ -999,6 +1040,7 @@ permissions:
mcmmo.commands.mctop.alchemy: true
mcmmo.commands.mctop.archery: true
mcmmo.commands.mctop.axes: true
+ mcmmo.commands.mctop.crossbows: true
mcmmo.commands.mctop.excavation: true
mcmmo.commands.mctop.fishing: true
mcmmo.commands.mctop.herbalism: true
@@ -1008,6 +1050,7 @@ permissions:
mcmmo.commands.mctop.smelting: true
mcmmo.commands.mctop.swords: true
mcmmo.commands.mctop.taming: true
+ mcmmo.commands.mctop.tridents: true
mcmmo.commands.mctop.unarmed: true
mcmmo.commands.mctop.woodcutting: true
mcmmo.commands.mctop:
@@ -1020,6 +1063,8 @@ permissions:
description: Allows access to the mctop command for archery
mcmmo.commands.mctop.axes:
description: Allows access to the mctop command for axes
+ mcmmo.commands.mctop.crossbows:
+ description: Allows access to the mctop command for crossbows
mcmmo.commands.mctop.excavation:
description: Allows access to the mctop command for excavation
mcmmo.commands.mctop.fishing:
@@ -1038,6 +1083,8 @@ permissions:
description: Allows access to the mctop command for swords
mcmmo.commands.mctop.taming:
description: Allows access to the mctop command for taming
+ mcmmo.commands.mctop.tridents:
+ description: Allows access to the mctop command for tridents
mcmmo.commands.mctop.unarmed:
description: Allows access to the mctop command for unarmed
mcmmo.commands.mctop.woodcutting:
@@ -1452,6 +1499,9 @@ permissions:
mcmmo.perks.lucky.axes:
default: false
description: Gives Axes abilities & skills a 33.3% better chance to activate.
+ mcmmo.perks.lucky.crossbows:
+ default: false
+ description: Gives Crossbows abilities & skills a 33.3% better chance to activate.
mcmmo.perks.lucky.excavation:
default: false
description: Gives Excavation abilities & skills a 33.3% better chance to activate.
@@ -1479,6 +1529,9 @@ permissions:
mcmmo.perks.lucky.taming:
default: false
description: Gives Taming abilities & skills a 33.3% better chance to activate.
+ mcmmo.perks.lucky.tridents:
+ default: false
+ description: Gives Tridents abilities & skills a 33.3% better chance to activate.
mcmmo.perks.lucky.unarmed:
default: false
description: Gives Unarmed abilities & skills a 33.3% better chance to activate.
@@ -1520,6 +1573,7 @@ permissions:
mcmmo.perks.xp.150percentboost.alchemy: true
mcmmo.perks.xp.150percentboost.archery: true
mcmmo.perks.xp.150percentboost.axes: true
+ mcmmo.perks.xp.150percentboost.crossbows: true
mcmmo.perks.xp.150percentboost.excavation: true
mcmmo.perks.xp.150percentboost.fishing: true
mcmmo.perks.xp.150percentboost.herbalism: true
@@ -1528,6 +1582,7 @@ permissions:
mcmmo.perks.xp.150percentboost.smelting: true
mcmmo.perks.xp.150percentboost.swords: true
mcmmo.perks.xp.150percentboost.taming: true
+ mcmmo.perks.xp.150percentboost.tridents: true
mcmmo.perks.xp.150percentboost.unarmed: true
mcmmo.perks.xp.150percentboost.woodcutting: true
mcmmo.perks.xp.150percentboost.acrobatics:
@@ -1542,6 +1597,9 @@ permissions:
mcmmo.perks.xp.150percentboost.axes:
default: false
description: Multiplies incoming Axes XP by 2.5
+ mcmmo.perks.xp.150percentboost.crossbows:
+ default: false
+ description: Multiplies incoming Crossbows XP by 2.5
mcmmo.perks.xp.150percentboost.excavation:
default: false
description: Multiplies incoming Excavation XP by 2.5
@@ -1566,6 +1624,9 @@ permissions:
mcmmo.perks.xp.150percentboost.taming:
default: false
description: Multiplies incoming Taming XP by 2.5
+ mcmmo.perks.xp.150percentboost.tridents:
+ default: false
+ description: Multiplies incoming Tridents XP by 2.5
mcmmo.perks.xp.150percentboost.unarmed:
default: false
description: Multiplies incoming Unarmed XP by 2.5
@@ -1590,6 +1651,7 @@ permissions:
mcmmo.perks.xp.50percentboost.alchemy: true
mcmmo.perks.xp.50percentboost.archery: true
mcmmo.perks.xp.50percentboost.axes: true
+ mcmmo.perks.xp.50percentboost.crossbows: true
mcmmo.perks.xp.50percentboost.excavation: true
mcmmo.perks.xp.50percentboost.fishing: true
mcmmo.perks.xp.50percentboost.herbalism: true
@@ -1598,6 +1660,7 @@ permissions:
mcmmo.perks.xp.50percentboost.smelting: true
mcmmo.perks.xp.50percentboost.swords: true
mcmmo.perks.xp.50percentboost.taming: true
+ mcmmo.perks.xp.50percentboost.tridents: true
mcmmo.perks.xp.50percentboost.unarmed: true
mcmmo.perks.xp.50percentboost.woodcutting: true
mcmmo.perks.xp.50percentboost.acrobatics:
@@ -1612,6 +1675,9 @@ permissions:
mcmmo.perks.xp.50percentboost.axes:
default: false
description: Multiplies incoming Axes XP by 1.5
+ mcmmo.perks.xp.50percentboost.crossbows:
+ default: false
+ description: Multiplies incoming Crossbows XP by 1.5
mcmmo.perks.xp.50percentboost.excavation:
default: false
description: Multiplies incoming Excavation XP by 1.5
@@ -1636,6 +1702,9 @@ permissions:
mcmmo.perks.xp.50percentboost.taming:
default: false
description: Multiplies incoming Taming XP by 1.5
+ mcmmo.perks.xp.50percentboost.tridents:
+ default: false
+ description: Multiplies incoming Tridents XP by 1.5
mcmmo.perks.xp.50percentboost.unarmed:
default: false
description: Multiplies incoming Unarmed XP by 1.5
@@ -1656,20 +1725,22 @@ permissions:
default: false
description: Multiplies incoming XP by 1.25
children:
- mcmmo.perks.xp.25percentboost.acrobatics: true
- mcmmo.perks.xp.25percentboost.alchemy: true
- mcmmo.perks.xp.25percentboost.archery: true
- mcmmo.perks.xp.25percentboost.axes: true
- mcmmo.perks.xp.25percentboost.excavation: true
- mcmmo.perks.xp.25percentboost.fishing: true
- mcmmo.perks.xp.25percentboost.herbalism: true
- mcmmo.perks.xp.25percentboost.mining: true
- mcmmo.perks.xp.25percentboost.repair: true
- mcmmo.perks.xp.25percentboost.smelting: true
- mcmmo.perks.xp.25percentboost.swords: true
- mcmmo.perks.xp.25percentboost.taming: true
- mcmmo.perks.xp.25percentboost.unarmed: true
- mcmmo.perks.xp.25percentboost.woodcutting: true
+ mcmmo.perks.xp.25percentboost.acrobatics: true
+ mcmmo.perks.xp.25percentboost.alchemy: true
+ mcmmo.perks.xp.25percentboost.archery: true
+ mcmmo.perks.xp.25percentboost.axes: true
+ mcmmo.perks.xp.25percentboost.crossbows: true
+ mcmmo.perks.xp.25percentboost.excavation: true
+ mcmmo.perks.xp.25percentboost.fishing: true
+ mcmmo.perks.xp.25percentboost.herbalism: true
+ mcmmo.perks.xp.25percentboost.mining: true
+ mcmmo.perks.xp.25percentboost.repair: true
+ mcmmo.perks.xp.25percentboost.smelting: true
+ mcmmo.perks.xp.25percentboost.swords: true
+ mcmmo.perks.xp.25percentboost.taming: true
+ mcmmo.perks.xp.25percentboost.tridents: true
+ mcmmo.perks.xp.25percentboost.unarmed: true
+ mcmmo.perks.xp.25percentboost.woodcutting: true
mcmmo.perks.xp.25percentboost.acrobatics:
default: false
description: Multiplies incoming Acrobatics XP by 1.25
@@ -1682,6 +1753,9 @@ permissions:
mcmmo.perks.xp.25percentboost.axes:
default: false
description: Multiplies incoming Axes XP by 1.25
+ mcmmo.perks.xp.25percentboost.crossbows:
+ default: false
+ description: Multiplies incoming Crossbows XP by 1.25
mcmmo.perks.xp.25percentboost.excavation:
default: false
description: Multiplies incoming Excavation XP by 1.25
@@ -1706,6 +1780,9 @@ permissions:
mcmmo.perks.xp.25percentboost.taming:
default: false
description: Multiplies incoming Taming XP by 1.25
+ mcmmo.perks.xp.25percentboost.tridents:
+ default: false
+ description: Multiplies incoming Tridents XP by 1.25
mcmmo.perks.xp.25percentboost.unarmed:
default: false
description: Multiplies incoming Unarmed XP by 1.5
@@ -1730,6 +1807,7 @@ permissions:
mcmmo.perks.xp.10percentboost.alchemy: true
mcmmo.perks.xp.10percentboost.archery: true
mcmmo.perks.xp.10percentboost.axes: true
+ mcmmo.perks.xp.10percentboost.crossbows: true
mcmmo.perks.xp.10percentboost.excavation: true
mcmmo.perks.xp.10percentboost.fishing: true
mcmmo.perks.xp.10percentboost.herbalism: true
@@ -1738,6 +1816,7 @@ permissions:
mcmmo.perks.xp.10percentboost.smelting: true
mcmmo.perks.xp.10percentboost.swords: true
mcmmo.perks.xp.10percentboost.taming: true
+ mcmmo.perks.xp.10percentboost.tridents: true
mcmmo.perks.xp.10percentboost.unarmed: true
mcmmo.perks.xp.10percentboost.woodcutting: true
mcmmo.perks.xp.10percentboost.acrobatics:
@@ -1752,6 +1831,9 @@ permissions:
mcmmo.perks.xp.10percentboost.axes:
default: false
description: Multiplies incoming Axes XP by 1.1
+ mcmmo.perks.xp.10percentboost.crossbows:
+ default: false
+ description: Multiplies incoming Crossbows XP by 1.1
mcmmo.perks.xp.10percentboost.excavation:
default: false
description: Multiplies incoming Excavation XP by 1.1
@@ -1776,6 +1858,9 @@ permissions:
mcmmo.perks.xp.10percentboost.taming:
default: false
description: Multiplies incoming Taming XP by 1.1
+ mcmmo.perks.xp.10percentboost.tridents:
+ default: false
+ description: Multiplies incoming Tridents XP by 1.1
mcmmo.perks.xp.10percentboost.unarmed:
default: false
description: Multiplies incoming Unarmed XP by 1.1
@@ -1800,6 +1885,7 @@ permissions:
mcmmo.perks.xp.customboost.alchemy: true
mcmmo.perks.xp.customboost.archery: true
mcmmo.perks.xp.customboost.axes: true
+ mcmmo.perks.xp.customboost.crossbows: true
mcmmo.perks.xp.customboost.excavation: true
mcmmo.perks.xp.customboost.fishing: true
mcmmo.perks.xp.customboost.herbalism: true
@@ -1808,6 +1894,7 @@ permissions:
mcmmo.perks.xp.customboost.smelting: true
mcmmo.perks.xp.customboost.swords: true
mcmmo.perks.xp.customboost.taming: true
+ mcmmo.perks.xp.customboost.tridents: true
mcmmo.perks.xp.customboost.unarmed: true
mcmmo.perks.xp.customboost.woodcutting: true
mcmmo.perks.xp.customboost.acrobatics:
@@ -1822,6 +1909,9 @@ permissions:
mcmmo.perks.xp.customboost.axes:
default: false
description: Multiplies incoming Axes XP by the boost amount defined in the experience config
+ mcmmo.perks.xp.customboost.crossbows:
+ default: false
+ description: Multiplies incoming Crossbows XP by the boost amount defined in the experience config
mcmmo.perks.xp.customboost.excavation:
default: false
description: Multiplies incoming Excavation XP by the boost amount defined in the experience config
@@ -1846,6 +1936,9 @@ permissions:
mcmmo.perks.xp.customboost.taming:
default: false
description: Multiplies incoming Taming XP by the boost amount defined in the experience config
+ mcmmo.perks.xp.customboost.tridents:
+ default: false
+ description: Multiplies incoming Tridents XP by the boost amount defined in the experience config
mcmmo.perks.xp.customboost.unarmed:
default: false
description: Multiplies incoming Unarmed XP by the boost amount defined in the experience config
@@ -1870,6 +1963,7 @@ permissions:
mcmmo.perks.xp.double.alchemy: true
mcmmo.perks.xp.double.archery: true
mcmmo.perks.xp.double.axes: true
+ mcmmo.perks.xp.double.crossbows: true
mcmmo.perks.xp.double.excavation: true
mcmmo.perks.xp.double.fishing: true
mcmmo.perks.xp.double.herbalism: true
@@ -1878,6 +1972,7 @@ permissions:
mcmmo.perks.xp.double.smelting: true
mcmmo.perks.xp.double.swords: true
mcmmo.perks.xp.double.taming: true
+ mcmmo.perks.xp.double.tridents: true
mcmmo.perks.xp.double.unarmed: true
mcmmo.perks.xp.double.woodcutting: true
mcmmo.perks.xp.double.acrobatics:
@@ -1892,6 +1987,9 @@ permissions:
mcmmo.perks.xp.double.axes:
default: false
description: Doubles incoming Axes XP
+ mcmmo.perks.xp.double.crossbows:
+ default: false
+ description: Doubles incoming Crossbows XP
mcmmo.perks.xp.double.excavation:
default: false
description: Doubles incoming Excavation XP
@@ -1916,6 +2014,9 @@ permissions:
mcmmo.perks.xp.double.taming:
default: false
description: Doubles incoming Taming XP
+ mcmmo.perks.xp.double.tridents:
+ default: false
+ description: Doubles incoming Tridents XP
mcmmo.perks.xp.double.unarmed:
default: false
description: Doubles incoming Unarmed XP
@@ -1940,6 +2041,7 @@ permissions:
mcmmo.perks.xp.quadruple.alchemy: true
mcmmo.perks.xp.quadruple.archery: true
mcmmo.perks.xp.quadruple.axes: true
+ mcmmo.perks.xp.quadruple.crossbows: true
mcmmo.perks.xp.quadruple.excavation: true
mcmmo.perks.xp.quadruple.fishing: true
mcmmo.perks.xp.quadruple.herbalism: true
@@ -1948,6 +2050,7 @@ permissions:
mcmmo.perks.xp.quadruple.smelting: true
mcmmo.perks.xp.quadruple.swords: true
mcmmo.perks.xp.quadruple.taming: true
+ mcmmo.perks.xp.quadruple.tridents: true
mcmmo.perks.xp.quadruple.unarmed: true
mcmmo.perks.xp.quadruple.woodcutting: true
mcmmo.perks.xp.quadruple.acrobatics:
@@ -1962,6 +2065,9 @@ permissions:
mcmmo.perks.xp.quadruple.axes:
default: false
description: Quadruples incoming Axes XP
+ mcmmo.perks.xp.quadruple.crossbows:
+ default: false
+ description: Quadruples incoming Crossbows XP
mcmmo.perks.xp.quadruple.excavation:
default: false
description: Quadruples incoming Excavation XP
@@ -1986,6 +2092,9 @@ permissions:
mcmmo.perks.xp.quadruple.taming:
default: false
description: Quadruples incoming Taming XP
+ mcmmo.perks.xp.quadruple.tridents:
+ default: false
+ description: Quadruples incoming Tridents XP
mcmmo.perks.xp.quadruple.unarmed:
default: false
description: Quadruples incoming Unarmed XP
@@ -2010,6 +2119,7 @@ permissions:
mcmmo.perks.xp.triple.alchemy: true
mcmmo.perks.xp.triple.archery: true
mcmmo.perks.xp.triple.axes: true
+ mcmmo.perks.xp.triple.crossbows: true
mcmmo.perks.xp.triple.excavation: true
mcmmo.perks.xp.triple.fishing: true
mcmmo.perks.xp.triple.herbalism: true
@@ -2018,6 +2128,7 @@ permissions:
mcmmo.perks.xp.triple.smelting: true
mcmmo.perks.xp.triple.swords: true
mcmmo.perks.xp.triple.taming: true
+ mcmmo.perks.xp.triple.tridents: true
mcmmo.perks.xp.triple.unarmed: true
mcmmo.perks.xp.triple.woodcutting: true
mcmmo.perks.xp.triple.acrobatics:
@@ -2032,6 +2143,9 @@ permissions:
mcmmo.perks.xp.triple.axes:
default: false
description: Triples incoming Axes XP
+ mcmmo.perks.xp.triple.crossbows:
+ default: false
+ description: Triples incoming Crossbows XP
mcmmo.perks.xp.triple.excavation:
default: false
description: Triples incoming Excavation XP
@@ -2056,6 +2170,9 @@ permissions:
mcmmo.perks.xp.triple.taming:
default: false
description: Triples incoming Taming XP
+ mcmmo.perks.xp.triple.tridents:
+ default: false
+ description: Triples incoming Tridents XP
mcmmo.perks.xp.triple.unarmed:
default: false
description: Triples incoming Unarmed XP
@@ -2091,6 +2208,8 @@ permissions:
mcmmo.skills.taming: true
mcmmo.skills.unarmed: true
mcmmo.skills.woodcutting: true
+ mcmmo.skills.crossbows: true
+ mcmmo.skills.tridents: true
mcmmo.skills.acrobatics:
description: Allows access to the Acrobatics skill
children:
@@ -2111,6 +2230,11 @@ permissions:
children:
mcmmo.ability.axes.all: true
mcmmo.commands.axes: true
+ mcmmo.skills.crossbows:
+ description: Allows access to the Crossbows skill
+ children:
+ mcmmo.ability.crossbows.all: true
+ mcmmo.commands.crossbows: true
mcmmo.skills.excavation:
description: Allows access to the Excavation skill
children:
@@ -2156,6 +2280,11 @@ permissions:
children:
mcmmo.ability.taming.all: true
mcmmo.commands.taming: true
+ mcmmo.skills.tridents:
+ description: Allows access to the Tridents skill
+ children:
+ mcmmo.ability.tridents.all: true
+ mcmmo.commands.tridents: true
mcmmo.skills.unarmed:
description: Allows access to the Unarmed skill
children:
@@ -2169,16 +2298,3 @@ permissions:
mcmmo.showversion:
default: true
description: Show mcMMO version number in /mcmmo and motd
- mcmmo.tools.*:
- default: false
- description: Implies all mcmmo.tools permissions.
- children:
- mcmmo.tools.all: true
- mcmmo.tools.all:
- default: false
- description: Implies all mcmmo.tools permissions.
- children:
- mcmmo.tools.updatecheck: true
- mcmmo.tools.updatecheck:
- default: false
- description: Notifies admins if there is a new version of mcMMO available
diff --git a/src/main/resources/skillranks.yml b/src/main/resources/skillranks.yml
index 1a305921b1..590ddde118 100644
--- a/src/main/resources/skillranks.yml
+++ b/src/main/resources/skillranks.yml
@@ -201,6 +201,129 @@ Axes:
Rank_2: 100
Rank_3: 150
Rank_4: 200
+Crossbows:
+ TrickShot:
+ Standard:
+ Rank_1: 5
+ Rank_2: 20
+ Rank_3: 40
+ RetroMode:
+ Rank_1: 50
+ Rank_2: 200
+ Rank_3: 400
+ PoweredShot:
+ Standard:
+ Rank_1: 1
+ Rank_2: 10
+ Rank_3: 15
+ Rank_4: 20
+ Rank_5: 25
+ Rank_6: 30
+ Rank_7: 35
+ Rank_8: 40
+ Rank_9: 45
+ Rank_10: 50
+ Rank_11: 55
+ Rank_12: 60
+ Rank_13: 65
+ Rank_14: 70
+ Rank_15: 75
+ Rank_16: 80
+ Rank_17: 85
+ Rank_18: 90
+ Rank_19: 95
+ Rank_20: 100
+ RetroMode:
+ Rank_1: 1
+ Rank_2: 100
+ Rank_3: 150
+ Rank_4: 200
+ Rank_5: 250
+ Rank_6: 300
+ Rank_7: 350
+ Rank_8: 400
+ Rank_9: 450
+ Rank_10: 500
+ Rank_11: 550
+ Rank_12: 600
+ Rank_13: 650
+ Rank_14: 700
+ Rank_15: 750
+ Rank_16: 800
+ Rank_17: 850
+ Rank_18: 900
+ Rank_19: 950
+ Rank_20: 1000
+ CrossbowsLimitBreak:
+ Standard:
+ Rank_1: 10
+ Rank_2: 20
+ Rank_3: 30
+ Rank_4: 40
+ Rank_5: 50
+ Rank_6: 60
+ Rank_7: 70
+ Rank_8: 80
+ Rank_9: 90
+ Rank_10: 100
+ RetroMode:
+ Rank_1: 100
+ Rank_2: 200
+ Rank_3: 300
+ Rank_4: 400
+ Rank_5: 500
+ Rank_6: 600
+ Rank_7: 700
+ Rank_8: 800
+ Rank_9: 900
+ Rank_10: 1000
+Tridents:
+ TridentsLimitBreak:
+ Standard:
+ Rank_1: 10
+ Rank_2: 20
+ Rank_3: 30
+ Rank_4: 40
+ Rank_5: 50
+ Rank_6: 60
+ Rank_7: 70
+ Rank_8: 80
+ Rank_9: 90
+ Rank_10: 100
+ RetroMode:
+ Rank_1: 100
+ Rank_2: 200
+ Rank_3: 300
+ Rank_4: 400
+ Rank_5: 500
+ Rank_6: 600
+ Rank_7: 700
+ Rank_8: 800
+ Rank_9: 900
+ Rank_10: 1000
+ Impale:
+ Standard:
+ Rank_1: 5
+ Rank_2: 15
+ Rank_3: 25
+ Rank_4: 35
+ Rank_5: 45
+ Rank_6: 55
+ Rank_7: 65
+ Rank_8: 75
+ Rank_9: 85
+ Rank_10: 100
+ RetroMode:
+ Rank_1: 50
+ Rank_2: 150
+ Rank_3: 250
+ Rank_4: 350
+ Rank_5: 450
+ Rank_6: 550
+ Rank_7: 650
+ Rank_8: 750
+ Rank_9: 850
+ Rank_10: 1000
Taming:
BeastLore:
Standard:
@@ -321,6 +444,11 @@ Salvage:
Rank_7: 850
Rank_8: 1000
Mining:
+ MotherLode:
+ Standard:
+ Rank_1: 100
+ RetroMode:
+ Rank_1: 1000
DoubleDrops:
Standard:
Rank_1: 1
@@ -368,6 +496,11 @@ Herbalism:
Rank_1: 1
RetroMode:
Rank_1: 1
+ VerdantBounty:
+ Standard:
+ Rank_1: 100
+ RetroMode:
+ Rank_1: 1000
GreenTerra:
Standard:
Rank_1: 5
@@ -398,6 +531,11 @@ Herbalism:
Rank_4: 800
Rank_5: 1000
Fishing:
+ Mastery:
+ Standard:
+ Rank_1: 100
+ RetroMode:
+ Rank_1: 1000
MagicHunter:
Standard:
Rank_1: 20
@@ -637,6 +775,11 @@ Woodcutting:
Rank_1: 1
RetroMode:
Rank_1: 1
+ CleanCuts:
+ Standard:
+ Rank_1: 100
+ RetroMode:
+ Rank_1: 1000
KnockOnWood:
Standard:
Rank_1: 30
diff --git a/src/test/java/com/gmail/nossr50/MMOTestEnvironment.java b/src/test/java/com/gmail/nossr50/MMOTestEnvironment.java
new file mode 100644
index 0000000000..d995ed569f
--- /dev/null
+++ b/src/test/java/com/gmail/nossr50/MMOTestEnvironment.java
@@ -0,0 +1,216 @@
+package com.gmail.nossr50;
+
+import com.gmail.nossr50.api.exceptions.InvalidSkillException;
+import com.gmail.nossr50.config.AdvancedConfig;
+import com.gmail.nossr50.config.ChatConfig;
+import com.gmail.nossr50.config.GeneralConfig;
+import com.gmail.nossr50.config.RankConfig;
+import com.gmail.nossr50.config.experience.ExperienceConfig;
+import com.gmail.nossr50.config.party.PartyConfig;
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
+import com.gmail.nossr50.datatypes.player.PlayerProfile;
+import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
+import com.gmail.nossr50.util.*;
+import com.gmail.nossr50.util.blockmeta.ChunkManager;
+import com.gmail.nossr50.util.player.UserManager;
+import com.gmail.nossr50.util.skills.RankUtils;
+import com.gmail.nossr50.util.skills.SkillTools;
+import org.bukkit.Location;
+import org.bukkit.Material;
+import org.bukkit.Server;
+import org.bukkit.World;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.PlayerInventory;
+import org.bukkit.plugin.PluginManager;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+
+import java.util.UUID;
+import java.util.logging.Logger;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+public abstract class MMOTestEnvironment {
+ protected MockedStatic mockedMcMMO;
+ protected MockedStatic mockedChatConfig;
+ protected MockedStatic experienceConfig;
+ protected MockedStatic mockedPermissions;
+ protected MockedStatic mockedRankUtils;
+ protected MockedStatic mockedUserManager;
+ protected MockedStatic mockedMisc;
+ protected MockedStatic mockedSkillTools;
+ protected MockedStatic mockedEventUtils;
+ protected TransientEntityTracker transientEntityTracker;
+ protected AdvancedConfig advancedConfig;
+ protected PartyConfig partyConfig;
+ protected GeneralConfig generalConfig;
+ protected RankConfig rankConfig;
+ protected SkillTools skillTools;
+ protected Server server;
+ protected PluginManager pluginManager;
+ protected World world;
+
+ /* Mocks */
+ protected Player player;
+
+ protected UUID playerUUID = UUID.randomUUID();
+ protected ItemStack itemInMainHand;
+
+ protected PlayerInventory playerInventory;
+ protected PlayerProfile playerProfile;
+ protected McMMOPlayer mmoPlayer;
+ protected String playerName = "testPlayer";
+
+ protected ChunkManager chunkManager;
+ protected MaterialMapStore materialMapStore;
+
+ protected void mockBaseEnvironment(Logger logger) throws InvalidSkillException {
+ mockedMcMMO = Mockito.mockStatic(mcMMO.class);
+ mcMMO.p = Mockito.mock(mcMMO.class);
+ when(mcMMO.p.getLogger()).thenReturn(logger);
+
+ // place store
+ chunkManager = Mockito.mock(ChunkManager.class);
+ when(mcMMO.getPlaceStore()).thenReturn(chunkManager);
+
+ // shut off mod manager for woodcutting
+ when(mcMMO.getModManager()).thenReturn(Mockito.mock(ModManager.class));
+ when(mcMMO.getModManager().isCustomLog(any())).thenReturn(false);
+
+ // chat config
+ mockedChatConfig = Mockito.mockStatic(ChatConfig.class);
+ when(ChatConfig.getInstance()).thenReturn(Mockito.mock(ChatConfig.class));
+
+ // general config
+ mockGeneralConfig();
+
+ // party config
+ mockPartyConfig();
+
+ // rank config
+ mockRankConfig();
+
+ // wire advanced config
+ mockAdvancedConfig();
+
+ // wire experience config
+ mockExperienceConfig();
+
+ // wire skill tools
+ this.skillTools = new SkillTools(mcMMO.p);
+ when(mcMMO.p.getSkillTools()).thenReturn(skillTools);
+
+ this.transientEntityTracker = new TransientEntityTracker();
+ when(mcMMO.getTransientEntityTracker()).thenReturn(transientEntityTracker);
+
+ mockPermissions();
+
+ mockedRankUtils = Mockito.mockStatic(RankUtils.class);
+
+ // wire server
+ this.server = Mockito.mock(Server.class);
+ when(mcMMO.p.getServer()).thenReturn(server);
+
+ // wire plugin manager
+ this.pluginManager = Mockito.mock(PluginManager.class);
+ when(server.getPluginManager()).thenReturn(pluginManager);
+
+ // wire world
+ this.world = Mockito.mock(World.class);
+
+ // wire Misc
+ this.mockedMisc = Mockito.mockStatic(Misc.class);
+ when(Misc.getBlockCenter(any())).thenReturn(new Location(world, 0, 0, 0));
+
+ // setup player and player related mocks after everything else
+ this.player = Mockito.mock(Player.class);
+ when(player.getUniqueId()).thenReturn(playerUUID);
+
+ // wire inventory
+ this.playerInventory = Mockito.mock(PlayerInventory.class);
+ when(player.getInventory()).thenReturn(playerInventory);
+
+ // PlayerProfile and McMMOPlayer are partially mocked
+ playerProfile = new PlayerProfile("testPlayer", player.getUniqueId(), 0);
+ mmoPlayer = Mockito.spy(new McMMOPlayer(player, playerProfile));
+
+ // wire user manager
+ this.mockedUserManager = Mockito.mockStatic(UserManager.class);
+ when(UserManager.getPlayer(player)).thenReturn(mmoPlayer);
+
+ this.materialMapStore = new MaterialMapStore();
+ when(mcMMO.getMaterialMapStore()).thenReturn(materialMapStore);
+ }
+
+ private void mockPermissions() {
+ mockedPermissions = Mockito.mockStatic(Permissions.class);
+ when(Permissions.isSubSkillEnabled(any(Player.class), any(SubSkillType.class))).thenReturn(true);
+ when(Permissions.canUseSubSkill(any(Player.class), any(SubSkillType.class))).thenReturn(true);
+ when(Permissions.isSubSkillEnabled(any(Player.class), any(SubSkillType.class))).thenReturn(true);
+ when(Permissions.canUseSubSkill(any(Player.class), any(SubSkillType.class))).thenReturn(true);
+ when(Permissions.lucky(player, PrimarySkillType.WOODCUTTING)).thenReturn(false); // player is not lucky
+ }
+
+ private void mockRankConfig() {
+ rankConfig = Mockito.mock(RankConfig.class);
+ }
+
+ private void mockAdvancedConfig() {
+ this.advancedConfig = Mockito.mock(AdvancedConfig.class);
+ when(mcMMO.p.getAdvancedConfig()).thenReturn(advancedConfig);
+ }
+
+ private void mockGeneralConfig() {
+ generalConfig = Mockito.mock(GeneralConfig.class);
+ when(generalConfig.getTreeFellerThreshold()).thenReturn(100);
+ when(generalConfig.getDoubleDropsEnabled(PrimarySkillType.WOODCUTTING, Material.OAK_LOG)).thenReturn(true);
+ when(generalConfig.getLocale()).thenReturn("en_US");
+ when(mcMMO.p.getGeneralConfig()).thenReturn(generalConfig);
+ }
+
+ private void mockPartyConfig() {
+ partyConfig = Mockito.mock(PartyConfig.class);
+ when(partyConfig.isPartyEnabled()).thenReturn(false);
+ when(mcMMO.p.getPartyConfig()).thenReturn(partyConfig);
+ }
+
+ private void mockExperienceConfig() {
+ experienceConfig = Mockito.mockStatic(ExperienceConfig.class);
+
+ when(ExperienceConfig.getInstance()).thenReturn(Mockito.mock(ExperienceConfig.class));
+
+ // Combat
+ when(ExperienceConfig.getInstance().getCombatXP("Cow")).thenReturn(1D);
+ }
+
+ protected void cleanupBaseEnvironment() {
+ // Clean up resources here if needed.
+ if (mockedMcMMO != null) {
+ mockedMcMMO.close();
+ }
+ if (experienceConfig != null) {
+ experienceConfig.close();
+ }
+ if (mockedChatConfig != null) {
+ mockedChatConfig.close();
+ }
+ if (mockedPermissions != null) {
+ mockedPermissions.close();
+ }
+ if (mockedRankUtils != null) {
+ mockedRankUtils.close();
+ }
+ if (mockedUserManager != null) {
+ mockedUserManager.close();
+ }
+ if (mockedMisc != null) {
+ mockedMisc.close();
+ }
+ if (mockedEventUtils != null) {
+ mockedEventUtils.close();
+ }
+ }
+}
diff --git a/src/test/java/com/gmail/nossr50/database/FlatFileDatabaseManagerTest.java b/src/test/java/com/gmail/nossr50/database/FlatFileDatabaseManagerTest.java
index f626002c19..23463cbdb0 100644
--- a/src/test/java/com/gmail/nossr50/database/FlatFileDatabaseManagerTest.java
+++ b/src/test/java/com/gmail/nossr50/database/FlatFileDatabaseManagerTest.java
@@ -31,7 +31,6 @@
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.mock;
-//This class uses JUnit5/Jupiter
class FlatFileDatabaseManagerTest {
public static final @NotNull String TEST_FILE_NAME = "test.mcmmo.users";
@@ -39,7 +38,7 @@ class FlatFileDatabaseManagerTest {
public static final @NotNull String BAD_DATA_FILE_LINE_TWENTY_THREE = "nossr51:baddata:::baddata:baddata:640:baddata:1000:1000:1000:baddata:baddata:baddata:baddata:16:0:500:20273:0:0:0:0::1000:0:0:baddata:1593543012:0:0:0:0::1000:0:0:baddata:IGNORED:1000:0:588fe472-1c82-4c4e-9aa1-7eefccb277e3:1:0:";
public static final @NotNull String DB_BADDATA = "baddatadb.users";
public static final @NotNull String DB_HEALTHY = "healthydb.users";
- public static final @NotNull String HEALTHY_DB_LINE_1 = "nossr50:1:IGNORED:IGNORED:10:2:20:3:4:5:6:7:8:9:10:30:40:50:60:70:80:90:100:IGNORED:11:110:111:222:333:444:555:666:777:IGNORED:12:120:888:IGNORED:HEARTS:13:130:588fe472-1c82-4c4e-9aa1-7eefccb277e3:1111:999:2020:";
+ public static final @NotNull String HEALTHY_DB_LINE_1 = "nossr50:1:IGNORED:IGNORED:10:2:20:3:4:5:6:7:8:9:10:30:40:50:60:70:80:90:100:IGNORED:11:110:111:222:333:444:555:666:777:IGNORED:12:120:888:IGNORED:HEARTS:13:130:588fe472-1c82-4c4e-9aa1-7eefccb277e3:1111:999:2020:140:14:150:15:1111:2222:3333";
public static final @NotNull String HEALTHY_DB_LINE_ONE_UUID_STR = "588fe472-1c82-4c4e-9aa1-7eefccb277e3";
public static final String DB_MISSING_LAST_LOGIN = "missinglastlogin.users";
public static final String LINE_TWO_FROM_MISSING_DB = "nossr50:1:IGNORED:IGNORED:10:2:20:3:4:5:6:7:8:9:10:30:40:50:60:70:80:90:100:IGNORED:11:110:111:222:333:444:555:666:777:IGNORED:12:120:888:0:HEARTS:13:130:588fe472-1c82-4c4e-9aa1-7eefccb277e3:1111:999:";
@@ -52,16 +51,19 @@ class FlatFileDatabaseManagerTest {
int expectedLvlMining = 1, expectedLvlWoodcutting = 2, expectedLvlRepair = 3,
expectedLvlUnarmed = 4, expectedLvlHerbalism = 5, expectedLvlExcavation = 6,
expectedLvlArchery = 7, expectedLvlSwords = 8, expectedLvlAxes = 9, expectedLvlAcrobatics = 10,
- expectedLvlTaming = 11, expectedLvlFishing = 12, expectedLvlAlchemy = 13;
+ expectedLvlTaming = 11, expectedLvlFishing = 12, expectedLvlAlchemy = 13, expectedLvlCrossbows = 14,
+ expectedLvlTridents = 15;
float expectedExpMining = 10, expectedExpWoodcutting = 20, expectedExpRepair = 30,
expectedExpUnarmed = 40, expectedExpHerbalism = 50, expectedExpExcavation = 60,
expectedExpArchery = 70, expectedExpSwords = 80, expectedExpAxes = 90, expectedExpAcrobatics = 100,
- expectedExpTaming = 110, expectedExpFishing = 120, expectedExpAlchemy = 130;
+ expectedExpTaming = 110, expectedExpFishing = 120, expectedExpAlchemy = 130, expectedExpCrossbows = 140,
+ expectedExpTridents = 150;
long expectedBerserkCd = 111, expectedGigaDrillBreakerCd = 222, expectedTreeFellerCd = 333,
expectedGreenTerraCd = 444, expectedSerratedStrikesCd = 555, expectedSkullSplitterCd = 666,
- expectedSuperBreakerCd = 777, expectedBlastMiningCd = 888, expectedChimaeraWingCd = 999;
+ expectedSuperBreakerCd = 777, expectedBlastMiningCd = 888, expectedChimaeraWingCd = 999,
+ expectedSuperShotgunCd = 1111, expectedTridentSuperCd = 2222, expectedExplosiveShotCd = 3333;
int expectedScoreboardTips = 1111;
Long expectedLastLogin = 2020L;
@@ -226,7 +228,6 @@ void testLoadByName() {
logger.info("File Path: "+healthyDB.getAbsolutePath());
assertArrayEquals(HEALTHY_DB_LINE_1.split(":"), dataFromFile.get(0));
assertEquals(dataFromFile.get(0)[FlatFileDatabaseManager.UUID_INDEX], HEALTHY_DB_LINE_ONE_UUID_STR);
- UUID healthDBEntryOneUUID = UUID.fromString(HEALTHY_DB_LINE_ONE_UUID_STR);
db = new FlatFileDatabaseManager(healthyDB, logger, PURGE_TIME, 0, true);
List flagsFound = db.checkFileHealthAndStructure();
@@ -451,14 +452,13 @@ private void testHealthyDataProfileValues(@NotNull String playerName, @NotNull U
if(SkillTools.isChildSkill(primarySkillType))
continue;
-// logger.info("Checking expected values for: "+primarySkillType);
-// logger.info("Profile Level Value: "+profile.getSkillLevel(primarySkillType));
-// logger.info("Expected Lvl Value: "+getExpectedLevelHealthyDBEntryOne(primarySkillType));
-// logger.info("Profile Exp Value: "+profile.getSkillXpLevelRaw(primarySkillType));
-// logger.info("Expected Exp Value: "+getExpectedExperienceHealthyDBEntryOne(primarySkillType));
+ int expectedLevelHealthyDBEntryOne = getExpectedLevelHealthyDBEntryOne(primarySkillType);
+ int skillLevel = profile.getSkillLevel(primarySkillType);
+ assertEquals(expectedLevelHealthyDBEntryOne, skillLevel);
- assertEquals(getExpectedLevelHealthyDBEntryOne(primarySkillType), profile.getSkillLevel(primarySkillType));
- assertEquals(getExpectedExperienceHealthyDBEntryOne(primarySkillType), profile.getSkillXpLevelRaw(primarySkillType), 0);
+ float expectedExperienceHealthyDBEntryOne = getExpectedExperienceHealthyDBEntryOne(primarySkillType);
+ float skillXpLevelRaw = profile.getSkillXpLevelRaw(primarySkillType);
+ assertEquals(expectedExperienceHealthyDBEntryOne, skillXpLevelRaw, 0);
}
//Check the other things
@@ -472,29 +472,24 @@ private void testHealthyDataProfileValues(@NotNull String playerName, @NotNull U
}
private long getExpectedSuperAbilityDATS(@NotNull SuperAbilityType superAbilityType) {
- switch(superAbilityType) {
- case BERSERK:
- return expectedBerserkCd;
- case SUPER_BREAKER:
- return expectedSuperBreakerCd;
- case GIGA_DRILL_BREAKER:
- return expectedGigaDrillBreakerCd;
- case GREEN_TERRA:
- return expectedGreenTerraCd;
- case SKULL_SPLITTER:
- return expectedSkullSplitterCd;
- case TREE_FELLER:
- return expectedTreeFellerCd;
- case SERRATED_STRIKES:
- return expectedSerratedStrikesCd;
- case BLAST_MINING:
- return expectedBlastMiningCd;
- }
+ return switch (superAbilityType) {
+ case BERSERK -> expectedBerserkCd;
+ case SUPER_BREAKER -> expectedSuperBreakerCd;
+ case GIGA_DRILL_BREAKER -> expectedGigaDrillBreakerCd;
+ case GREEN_TERRA -> expectedGreenTerraCd;
+ case SKULL_SPLITTER -> expectedSkullSplitterCd;
+ case SUPER_SHOTGUN -> expectedSuperShotgunCd;
+ case TREE_FELLER -> expectedTreeFellerCd;
+ case SERRATED_STRIKES -> expectedSerratedStrikesCd;
+ case BLAST_MINING -> expectedBlastMiningCd;
+ case TRIDENTS_SUPER_ABILITY -> expectedTridentSuperCd;
+ case EXPLOSIVE_SHOT -> expectedExplosiveShotCd;
+ default -> throw new RuntimeException("Values not defined for super ability please add " +
+ "values for " + superAbilityType.toString() + " to the test");
+ };
- return -1;
}
- //TODO: Why is this stuff a float?
private float getExpectedExperienceHealthyDBEntryOne(@NotNull PrimarySkillType primarySkillType) {
switch(primarySkillType) {
case ACROBATICS:
@@ -505,6 +500,8 @@ private float getExpectedExperienceHealthyDBEntryOne(@NotNull PrimarySkillType p
return expectedExpArchery;
case AXES:
return expectedExpAxes;
+ case CROSSBOWS:
+ return expectedExpCrossbows;
case EXCAVATION:
return expectedExpExcavation;
case FISHING:
@@ -522,13 +519,15 @@ private float getExpectedExperienceHealthyDBEntryOne(@NotNull PrimarySkillType p
return expectedExpSwords;
case TAMING:
return expectedExpTaming;
+ case TRIDENTS:
+ return expectedExpTridents;
case UNARMED:
return expectedExpUnarmed;
case WOODCUTTING:
return expectedExpWoodcutting;
}
- return -1;
+ throw new RuntimeException("Values for skill not defined, please add values for " + primarySkillType.toString() + " to the test");
}
private int getExpectedLevelHealthyDBEntryOne(@NotNull PrimarySkillType primarySkillType) {
@@ -541,6 +540,8 @@ private int getExpectedLevelHealthyDBEntryOne(@NotNull PrimarySkillType primaryS
return expectedLvlArchery;
case AXES:
return expectedLvlAxes;
+ case CROSSBOWS:
+ return expectedLvlCrossbows;
case EXCAVATION:
return expectedLvlExcavation;
case FISHING:
@@ -558,13 +559,15 @@ private int getExpectedLevelHealthyDBEntryOne(@NotNull PrimarySkillType primaryS
return expectedLvlSwords;
case TAMING:
return expectedLvlTaming;
+ case TRIDENTS:
+ return expectedLvlTridents;
case UNARMED:
return expectedLvlUnarmed;
case WOODCUTTING:
return expectedLvlWoodcutting;
}
- return -1;
+ throw new RuntimeException("Values for skill not defined, please add values for " + primarySkillType.toString() + " to the test");
}
@Test
diff --git a/src/test/java/com/gmail/nossr50/database/SQLDatabaseManagerTest.java b/src/test/java/com/gmail/nossr50/database/SQLDatabaseManagerTest.java
new file mode 100644
index 0000000000..9a183f2e40
--- /dev/null
+++ b/src/test/java/com/gmail/nossr50/database/SQLDatabaseManagerTest.java
@@ -0,0 +1,245 @@
+package com.gmail.nossr50.database;
+
+import com.gmail.nossr50.config.AdvancedConfig;
+import com.gmail.nossr50.config.GeneralConfig;
+import com.gmail.nossr50.datatypes.MobHealthbarType;
+import com.gmail.nossr50.datatypes.player.PlayerProfile;
+import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
+import com.gmail.nossr50.mcMMO;
+import com.gmail.nossr50.util.compat.CompatibilityManager;
+import com.gmail.nossr50.util.platform.MinecraftGameVersion;
+import com.gmail.nossr50.util.skills.SkillTools;
+import com.gmail.nossr50.util.upgrade.UpgradeManager;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+import org.junit.jupiter.api.*;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+
+import java.util.logging.Logger;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+class SQLDatabaseManagerTest {
+ private final static @NotNull Logger logger = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME);
+ static MockedStatic mockedMcMMO;
+ SQLDatabaseManager sqlDatabaseManager;
+ static GeneralConfig generalConfig;
+ static AdvancedConfig advancedConfig;
+ static UpgradeManager upgradeManager;
+ static CompatibilityManager compatibilityManager;
+ static SkillTools skillTools;
+
+ @BeforeAll
+ static void setUpAll() {
+ // stub mcMMO.p
+ mockedMcMMO = Mockito.mockStatic(mcMMO.class);
+ mcMMO.p = Mockito.mock(mcMMO.class);
+ when(mcMMO.p.getLogger()).thenReturn(logger);
+
+ // general config mock
+ mockGeneralConfig();
+
+ // advanced config mock
+ advancedConfig = Mockito.mock(AdvancedConfig.class);
+ when(mcMMO.p.getAdvancedConfig()).thenReturn(advancedConfig);
+
+ // starting level
+ when(mcMMO.p.getAdvancedConfig().getStartingLevel()).thenReturn(0);
+
+ // wire skill tools
+ skillTools = new SkillTools(mcMMO.p);
+ when(mcMMO.p.getSkillTools()).thenReturn(skillTools);
+
+ // compatibility manager mock
+ compatibilityManager = Mockito.mock(CompatibilityManager.class);
+ when(mcMMO.getCompatibilityManager()).thenReturn(compatibilityManager);
+ when(compatibilityManager.getMinecraftGameVersion()).thenReturn(new MinecraftGameVersion(1, 20, 4));
+
+ // upgrade manager mock
+ upgradeManager = Mockito.mock(UpgradeManager.class);
+ when(mcMMO.getUpgradeManager()).thenReturn(upgradeManager);
+
+ // don't trigger upgrades
+ when(mcMMO.getUpgradeManager().shouldUpgrade(any())).thenReturn(false);
+ }
+
+ private static void mockGeneralConfig() {
+ generalConfig = Mockito.mock(GeneralConfig.class);
+ when(generalConfig.getLocale()).thenReturn("en_US");
+ when(mcMMO.p.getGeneralConfig()).thenReturn(generalConfig);
+
+ // max pool size
+ when(mcMMO.p.getGeneralConfig().getMySQLMaxPoolSize(SQLDatabaseManager.PoolIdentifier.MISC))
+ .thenReturn(10);
+ when(mcMMO.p.getGeneralConfig().getMySQLMaxPoolSize(SQLDatabaseManager.PoolIdentifier.LOAD))
+ .thenReturn(20);
+ when(mcMMO.p.getGeneralConfig().getMySQLMaxPoolSize(SQLDatabaseManager.PoolIdentifier.SAVE))
+ .thenReturn(20);
+
+ // max connections
+ when(mcMMO.p.getGeneralConfig().getMySQLMaxConnections(SQLDatabaseManager.PoolIdentifier.MISC))
+ .thenReturn(30);
+ when(mcMMO.p.getGeneralConfig().getMySQLMaxConnections(SQLDatabaseManager.PoolIdentifier.LOAD))
+ .thenReturn(30);
+ when(mcMMO.p.getGeneralConfig().getMySQLMaxConnections(SQLDatabaseManager.PoolIdentifier.SAVE))
+ .thenReturn(30);
+
+ // table prefix
+ when(mcMMO.p.getGeneralConfig().getMySQLTablePrefix()).thenReturn("mcmmo_");
+
+ // public key retrieval
+ when(mcMMO.p.getGeneralConfig().getMySQLPublicKeyRetrieval()).thenReturn(true);
+
+ // debug
+ when(mcMMO.p.getGeneralConfig().getMySQLDebug()).thenReturn(true);
+
+ // use mysql
+ when(mcMMO.p.getGeneralConfig().getUseMySQL()).thenReturn(true);
+
+ // use ssl
+ when(mcMMO.p.getGeneralConfig().getMySQLSSL()).thenReturn(true);
+
+ // username
+ when(mcMMO.p.getGeneralConfig().getMySQLUserName()).thenReturn("sa");
+
+ // password
+ when(mcMMO.p.getGeneralConfig().getMySQLUserPassword()).thenReturn("");
+
+ // host
+ when(mcMMO.p.getGeneralConfig().getMySQLServerName()).thenReturn("localhost");
+
+ // unused mob health bar thingy
+ when(mcMMO.p.getGeneralConfig().getMobHealthbarDefault()).thenReturn(MobHealthbarType.HEARTS);
+ }
+
+ @BeforeEach
+ void setUp() {
+ assertNull(sqlDatabaseManager);
+ sqlDatabaseManager = new SQLDatabaseManager(logger, "org.h2.Driver", true);
+ }
+
+ @AfterEach
+ void tearDown() {
+ sqlDatabaseManager = null;
+ }
+
+ @AfterAll
+ static void tearDownAll() {
+ mockedMcMMO.close();
+ }
+
+ @Test
+ void testGetConnectionMisc() throws Exception {
+ assertNotNull(sqlDatabaseManager.getConnection(SQLDatabaseManager.PoolIdentifier.MISC));
+ }
+
+ @Test
+ void testGetConnectionLoad() throws Exception {
+ assertNotNull(sqlDatabaseManager.getConnection(SQLDatabaseManager.PoolIdentifier.LOAD));
+ }
+
+ @Test
+ void testGetConnectionSave() throws Exception {
+ assertNotNull(sqlDatabaseManager.getConnection(SQLDatabaseManager.PoolIdentifier.SAVE));
+ }
+
+ @Test
+ void testNewUser() {
+ Player player = Mockito.mock(Player.class);
+ when(player.getUniqueId()).thenReturn(java.util.UUID.randomUUID());
+ when(player.getName()).thenReturn("nossr50");
+ sqlDatabaseManager.newUser(player);
+ }
+
+ @Test
+ void testNewUserGetSkillLevel() {
+ Player player = Mockito.mock(Player.class);
+ when(player.getUniqueId()).thenReturn(java.util.UUID.randomUUID());
+ when(player.getName()).thenReturn("nossr50");
+ PlayerProfile playerProfile = sqlDatabaseManager.newUser(player);
+
+ for (PrimarySkillType primarySkillType : PrimarySkillType.values()) {
+ assertEquals(0, playerProfile.getSkillLevel(primarySkillType));
+ }
+ }
+
+ @Test
+ void testNewUserGetSkillXpLevel() {
+ Player player = Mockito.mock(Player.class);
+ when(player.getUniqueId()).thenReturn(java.util.UUID.randomUUID());
+ when(player.getName()).thenReturn("nossr50");
+ PlayerProfile playerProfile = sqlDatabaseManager.newUser(player);
+
+ for (PrimarySkillType primarySkillType : PrimarySkillType.values()) {
+ assertEquals(0, playerProfile.getSkillXpLevel(primarySkillType));
+ }
+ }
+
+ @Test
+ void testSaveSkillLevelValues() {
+ Player player = Mockito.mock(Player.class);
+ when(player.getUniqueId()).thenReturn(java.util.UUID.randomUUID());
+ when(player.getName()).thenReturn("nossr50");
+ PlayerProfile playerProfile = sqlDatabaseManager.newUser(player);
+
+ // Validate values are starting from zero
+ for (PrimarySkillType primarySkillType : PrimarySkillType.values()) {
+ assertEquals(0, playerProfile.getSkillXpLevel(primarySkillType));
+ }
+
+ // Change values
+ for (PrimarySkillType primarySkillType : PrimarySkillType.values()) {
+ playerProfile.modifySkill(primarySkillType, 1 + primarySkillType.ordinal());
+ }
+
+ boolean saveSuccess = sqlDatabaseManager.saveUser(playerProfile);
+ assertTrue(saveSuccess);
+
+ PlayerProfile retrievedUser = sqlDatabaseManager.loadPlayerProfile(player.getName());
+
+ // Check that values got saved
+ for (PrimarySkillType primarySkillType : PrimarySkillType.values()) {
+ if (primarySkillType == PrimarySkillType.SALVAGE || primarySkillType == PrimarySkillType.SMELTING) {
+ // Child skills are not saved, but calculated
+ continue;
+ }
+
+ assertEquals(1 + primarySkillType.ordinal(), retrievedUser.getSkillLevel(primarySkillType));
+ }
+ }
+
+ @Test
+ void testSaveSkillXpValues() {
+ Player player = Mockito.mock(Player.class);
+ when(player.getUniqueId()).thenReturn(java.util.UUID.randomUUID());
+ when(player.getName()).thenReturn("nossr50");
+ PlayerProfile playerProfile = sqlDatabaseManager.newUser(player);
+
+ // Validate values are starting from zero
+ for (PrimarySkillType primarySkillType : PrimarySkillType.values()) {
+ assertEquals(0, playerProfile.getSkillXpLevel(primarySkillType));
+ }
+
+ // Change values
+ for (PrimarySkillType primarySkillType : PrimarySkillType.values()) {
+ playerProfile.setSkillXpLevel(primarySkillType, 1 + primarySkillType.ordinal());
+ }
+
+ sqlDatabaseManager.saveUser(playerProfile);
+
+ PlayerProfile retrievedUser = sqlDatabaseManager.loadPlayerProfile(player.getName());
+
+ // Check that values got saved
+ for (PrimarySkillType primarySkillType : PrimarySkillType.values()) {
+ if (primarySkillType == PrimarySkillType.SALVAGE || primarySkillType == PrimarySkillType.SMELTING) {
+ // Child skills are not saved, but calculated
+ continue;
+ }
+
+ assertEquals(1 + primarySkillType.ordinal(), retrievedUser.getSkillXpLevel(primarySkillType));
+ }
+ }
+}
diff --git a/src/test/java/com/gmail/nossr50/party/PartyManagerTest.java b/src/test/java/com/gmail/nossr50/party/PartyManagerTest.java
index bd4e2d629d..0d8f01ccb6 100644
--- a/src/test/java/com/gmail/nossr50/party/PartyManagerTest.java
+++ b/src/test/java/com/gmail/nossr50/party/PartyManagerTest.java
@@ -1,45 +1,46 @@
package com.gmail.nossr50.party;
+import com.gmail.nossr50.MMOTestEnvironment;
import com.gmail.nossr50.datatypes.player.McMMOPlayer;
-import com.gmail.nossr50.locale.LocaleLoader;
import com.gmail.nossr50.mcMMO;
-import org.bukkit.Server;
import org.bukkit.entity.Player;
-import org.bukkit.plugin.PluginManager;
-import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
import java.util.UUID;
+import java.util.logging.Logger;
import static org.junit.jupiter.api.Assertions.assertThrows;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
-class PartyManagerTest {
+class PartyManagerTest extends MMOTestEnvironment {
+ private static final Logger logger = Logger.getLogger(PartyManagerTest.class.getName());
- static mcMMO mockMcMMO;
+ @BeforeEach
+ public void setUp() {
+ mockBaseEnvironment(logger);
- @BeforeAll
- public static void setup() {
- // create a static stub for LocaleLoader.class
- mockStatic(LocaleLoader.class);
- when(LocaleLoader.getString(anyString())).thenReturn("");
+ // currently unnecessary, but may be needed for future tests
+ Mockito.when(partyConfig.isPartyEnabled()).thenReturn(true);
+ }
- mockMcMMO = mock(mcMMO.class);
- final Server mockServer = mock(Server.class);
- when(mockMcMMO.getServer()).thenReturn(mockServer);
- when(mockServer.getPluginManager()).thenReturn(mock(PluginManager.class));
+ @AfterEach
+ public void tearDown() {
+ cleanupBaseEnvironment();
- // TODO: Add cleanup for static mock
+ // disable parties in config for other tests
+ Mockito.when(partyConfig.isPartyEnabled()).thenReturn(false);
}
@Test
public void createPartyWithoutPasswordShouldSucceed() {
// Given
- PartyManager partyManager = new PartyManager(mockMcMMO);
+ PartyManager partyManager = new PartyManager(mcMMO.p);
String partyName = "TestParty";
- // TODO: Update this with utils from the other dev branches in the future
Player player = mock(Player.class);
McMMOPlayer mmoPlayer = mock(McMMOPlayer.class);
when(mmoPlayer.getPlayer()).thenReturn(player);
@@ -52,11 +53,10 @@ public void createPartyWithoutPasswordShouldSucceed() {
@Test
public void createPartyWithPasswordShouldSucceed() {
// Given
- PartyManager partyManager = new PartyManager(mockMcMMO);
+ PartyManager partyManager = new PartyManager(mcMMO.p);
String partyName = "TestParty";
String partyPassword = "somePassword";
- // TODO: Update this with utils from the other dev branches in the future
Player player = mock(Player.class);
McMMOPlayer mmoPlayer = mock(McMMOPlayer.class);
when(mmoPlayer.getPlayer()).thenReturn(player);
@@ -69,10 +69,9 @@ public void createPartyWithPasswordShouldSucceed() {
@Test
public void createPartyWithoutNameShouldFail() {
// Given
- PartyManager partyManager = new PartyManager(mockMcMMO);
+ PartyManager partyManager = new PartyManager(mcMMO.p);
String partyPassword = "somePassword";
- // TODO: Update this with utils from the other dev branches in the future
Player player = mock(Player.class);
McMMOPlayer mmoPlayer = mock(McMMOPlayer.class);
when(mmoPlayer.getPlayer()).thenReturn(player);
@@ -86,7 +85,7 @@ public void createPartyWithoutNameShouldFail() {
@Test
public void createPartyWithoutPlayerShouldFail() {
// Given
- PartyManager partyManager = new PartyManager(mockMcMMO);
+ PartyManager partyManager = new PartyManager(mcMMO.p);
String partyName = "TestParty";
String partyPassword = "somePassword";
diff --git a/src/test/java/com/gmail/nossr50/skills/excavation/ExcavationTest.java b/src/test/java/com/gmail/nossr50/skills/excavation/ExcavationTest.java
new file mode 100644
index 0000000000..631696f0a9
--- /dev/null
+++ b/src/test/java/com/gmail/nossr50/skills/excavation/ExcavationTest.java
@@ -0,0 +1,120 @@
+package com.gmail.nossr50.skills.excavation;
+
+import com.gmail.nossr50.MMOTestEnvironment;
+import com.gmail.nossr50.api.exceptions.InvalidSkillException;
+import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
+import com.gmail.nossr50.datatypes.treasure.ExcavationTreasure;
+import com.gmail.nossr50.util.skills.RankUtils;
+import org.bukkit.Location;
+import org.bukkit.Material;
+import org.bukkit.block.Block;
+import org.bukkit.block.BlockState;
+import org.bukkit.block.data.BlockData;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.PlayerInventory;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.*;
+
+class ExcavationTest extends MMOTestEnvironment {
+ private static final java.util.logging.Logger logger = java.util.logging.Logger.getLogger(ExcavationTest.class.getName());
+
+
+ @BeforeEach
+ void setUp() throws InvalidSkillException {
+ mockBaseEnvironment(logger);
+ when(rankConfig.getSubSkillUnlockLevel(SubSkillType.EXCAVATION_ARCHAEOLOGY, 1)).thenReturn(1);
+ when(rankConfig.getSubSkillUnlockLevel(SubSkillType.EXCAVATION_GIGA_DRILL_BREAKER, 1)).thenReturn(1);
+
+ // wire advanced config
+
+ when(RankUtils.getRankUnlockLevel(SubSkillType.EXCAVATION_ARCHAEOLOGY, 1)).thenReturn(1); // needed?
+ when(RankUtils.getRankUnlockLevel(SubSkillType.EXCAVATION_GIGA_DRILL_BREAKER, 1)).thenReturn(1); // needed?
+ when(RankUtils.hasReachedRank(eq(1), any(Player.class), eq(SubSkillType.EXCAVATION_ARCHAEOLOGY))).thenReturn(true);
+ when(RankUtils.hasReachedRank(eq(1), any(Player.class), eq(SubSkillType.EXCAVATION_GIGA_DRILL_BREAKER))).thenReturn(true);
+
+ // setup player and player related mocks after everything else
+ this.player = Mockito.mock(Player.class);
+ when(player.getUniqueId()).thenReturn(playerUUID);
+
+ // wire inventory
+ this.playerInventory = Mockito.mock(PlayerInventory.class);
+ this.itemInMainHand = new ItemStack(Material.DIAMOND_SHOVEL);
+ when(player.getInventory()).thenReturn(playerInventory);
+ when(playerInventory.getItemInMainHand()).thenReturn(itemInMainHand);
+
+ // Set up spy for Excavation Manager
+
+ }
+
+ @AfterEach
+ void tearDown() {
+ cleanupBaseEnvironment();
+ }
+
+ @Test
+ void excavationShouldHaveTreasureDrops() {
+ mmoPlayer.modifySkill(PrimarySkillType.EXCAVATION, 1000);
+
+ // Wire block
+ BlockState blockState = Mockito.mock(BlockState.class);
+ BlockData blockData = Mockito.mock(BlockData.class);
+ Block block = Mockito.mock(Block.class);
+ when(blockState.getBlockData()).thenReturn(blockData);
+ when(blockState.getType()).thenReturn(Material.SAND);
+ when(blockData.getMaterial()).thenReturn(Material.SAND);
+ when(blockState.getBlock()).thenReturn(block);
+ when(blockState.getBlock().getDrops(any())).thenReturn(null);
+
+ ExcavationManager excavationManager = Mockito.spy(new ExcavationManager(mmoPlayer));
+ doReturn(getGuaranteedTreasureDrops()).when(excavationManager).getTreasures(blockState);
+ excavationManager.excavationBlockCheck(blockState);
+
+ // verify ExcavationManager.processExcavationBonusesOnBlock was called
+ verify(excavationManager, atLeastOnce()).processExcavationBonusesOnBlock(any(BlockState.class), any(ExcavationTreasure.class), any(Location.class));
+ }
+
+ @Test
+ void excavationShouldNotDropTreasure() {
+ mmoPlayer.modifySkill(PrimarySkillType.EXCAVATION, 1000);
+
+ // Wire block
+ BlockState blockState = Mockito.mock(BlockState.class);
+ BlockData blockData = Mockito.mock(BlockData.class);
+ Block block = Mockito.mock(Block.class);
+ when(blockState.getBlockData()).thenReturn(blockData);
+ when(blockState.getType()).thenReturn(Material.SAND);
+ when(blockData.getMaterial()).thenReturn(Material.SAND);
+ when(blockState.getBlock()).thenReturn(block);
+ when(blockState.getBlock().getDrops(any())).thenReturn(null);
+
+ ExcavationManager excavationManager = Mockito.spy(new ExcavationManager(mmoPlayer));
+ doReturn(getImpossibleTreasureDrops()).when(excavationManager).getTreasures(blockState);
+ excavationManager.excavationBlockCheck(blockState);
+
+ // verify ExcavationManager.processExcavationBonusesOnBlock was called
+ verify(excavationManager, never()).processExcavationBonusesOnBlock(any(BlockState.class), any(ExcavationTreasure.class), any(Location.class));
+ }
+
+ private List getGuaranteedTreasureDrops() {
+ List treasures = new ArrayList<>();;
+ treasures.add(new ExcavationTreasure(new ItemStack(Material.CAKE), 1, 100, 1));
+ return treasures;
+ }
+
+ private List getImpossibleTreasureDrops() {
+ List treasures = new ArrayList<>();;
+ treasures.add(new ExcavationTreasure(new ItemStack(Material.CAKE), 1, 0, 1));
+ return treasures;
+ }
+}
diff --git a/src/test/java/com/gmail/nossr50/skills/tridents/TridentsTest.java b/src/test/java/com/gmail/nossr50/skills/tridents/TridentsTest.java
new file mode 100644
index 0000000000..199c8e5541
--- /dev/null
+++ b/src/test/java/com/gmail/nossr50/skills/tridents/TridentsTest.java
@@ -0,0 +1,39 @@
+package com.gmail.nossr50.skills.tridents;
+
+import com.gmail.nossr50.MMOTestEnvironment;
+import com.gmail.nossr50.api.exceptions.InvalidSkillException;
+import org.bukkit.Material;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.PlayerInventory;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.mockito.Mockito;
+
+class TridentsTest extends MMOTestEnvironment {
+ private static final java.util.logging.Logger logger = java.util.logging.Logger.getLogger(TridentsTest.class.getName());
+
+ TridentsManager tridentsManager;
+ ItemStack trident;
+ @BeforeEach
+ void setUp() throws InvalidSkillException {
+ mockBaseEnvironment(logger);
+
+ // setup player and player related mocks after everything else
+ this.player = Mockito.mock(Player.class);
+ Mockito.when(player.getUniqueId()).thenReturn(playerUUID);
+
+ // wire inventory
+ this.playerInventory = Mockito.mock(PlayerInventory.class);
+ this.trident = new ItemStack(Material.TRIDENT);
+ Mockito.when(playerInventory.getItemInMainHand()).thenReturn(trident);
+
+ // Set up spy for manager
+ tridentsManager = Mockito.spy(new TridentsManager(mmoPlayer));
+ }
+
+ @AfterEach
+ void tearDown() {
+ cleanupBaseEnvironment();
+ }
+}
diff --git a/src/test/java/com/gmail/nossr50/skills/woodcutting/WoodcuttingTest.java b/src/test/java/com/gmail/nossr50/skills/woodcutting/WoodcuttingTest.java
new file mode 100644
index 0000000000..2715ead651
--- /dev/null
+++ b/src/test/java/com/gmail/nossr50/skills/woodcutting/WoodcuttingTest.java
@@ -0,0 +1,108 @@
+package com.gmail.nossr50.skills.woodcutting;
+
+import com.gmail.nossr50.MMOTestEnvironment;
+import com.gmail.nossr50.api.exceptions.InvalidSkillException;
+import com.gmail.nossr50.config.experience.ExperienceConfig;
+import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
+import com.gmail.nossr50.util.skills.RankUtils;
+import org.bukkit.Material;
+import org.bukkit.block.Block;
+import org.bukkit.block.BlockState;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.PlayerInventory;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+
+class WoodcuttingTest extends MMOTestEnvironment {
+ private static final java.util.logging.Logger logger = java.util.logging.Logger.getLogger(WoodcuttingTest.class.getName());
+
+ WoodcuttingManager woodcuttingManager;
+ @BeforeEach
+ void setUp() throws InvalidSkillException {
+ mockBaseEnvironment(logger);
+ Mockito.when(rankConfig.getSubSkillUnlockLevel(SubSkillType.WOODCUTTING_HARVEST_LUMBER, 1)).thenReturn(1);
+
+ // wire advanced config
+ Mockito.when(advancedConfig.getMaximumProbability(SubSkillType.WOODCUTTING_HARVEST_LUMBER)).thenReturn(100D);
+ Mockito.when(advancedConfig.getMaximumProbability(SubSkillType.WOODCUTTING_CLEAN_CUTS)).thenReturn(10D);
+ Mockito.when(advancedConfig.getMaxBonusLevel(SubSkillType.WOODCUTTING_HARVEST_LUMBER)).thenReturn(1000);
+ Mockito.when(advancedConfig.getMaxBonusLevel(SubSkillType.WOODCUTTING_CLEAN_CUTS)).thenReturn(10000);
+
+ Mockito.when(RankUtils.getRankUnlockLevel(SubSkillType.WOODCUTTING_HARVEST_LUMBER, 1)).thenReturn(1); // needed?
+ Mockito.when(RankUtils.getRankUnlockLevel(SubSkillType.WOODCUTTING_CLEAN_CUTS, 1)).thenReturn(1000); // needed?
+ Mockito.when(RankUtils.hasReachedRank(eq(1), any(Player.class), eq(SubSkillType.WOODCUTTING_HARVEST_LUMBER))).thenReturn(true);
+ Mockito.when(RankUtils.hasReachedRank(eq(1), any(Player.class), eq(SubSkillType.WOODCUTTING_CLEAN_CUTS))).thenReturn(true);
+
+ // setup player and player related mocks after everything else
+ this.player = Mockito.mock(Player.class);
+ Mockito.when(player.getUniqueId()).thenReturn(playerUUID);
+
+ // wire inventory
+ this.playerInventory = Mockito.mock(PlayerInventory.class);
+ this.itemInMainHand = new ItemStack(Material.DIAMOND_AXE);
+ Mockito.when(player.getInventory()).thenReturn(playerInventory);
+ Mockito.when(playerInventory.getItemInMainHand()).thenReturn(itemInMainHand);
+
+ // Set up spy for WoodcuttingManager
+ woodcuttingManager = Mockito.spy(new WoodcuttingManager(mmoPlayer));
+ }
+
+ @AfterEach
+ void tearDown() {
+ cleanupBaseEnvironment();
+ }
+
+ @Test
+ void harvestLumberShouldDoubleDrop() {
+ mmoPlayer.modifySkill(PrimarySkillType.WOODCUTTING, 1000);
+
+ BlockState blockState = Mockito.mock(BlockState.class);
+ Block block = Mockito.mock(Block.class);
+ // wire block
+ Mockito.when(blockState.getBlock()).thenReturn(block);
+
+ Mockito.when(blockState.getBlock().getDrops(any())).thenReturn(null);
+ Mockito.when(blockState.getType()).thenReturn(Material.OAK_LOG);
+ woodcuttingManager.processBonusDropCheck(blockState);
+
+ // verify bonus drops were spawned
+ // TODO: Can fail if triple drops happen, need to update test
+ Mockito.verify(woodcuttingManager, Mockito.times(1)).spawnHarvestLumberBonusDrops(blockState);
+ }
+
+ @Test
+ void harvestLumberShouldNotDoubleDrop() {
+ mmoPlayer.modifySkill(PrimarySkillType.WOODCUTTING, 0);
+
+ BlockState blockState = Mockito.mock(BlockState.class);
+ Block block = Mockito.mock(Block.class);
+ // wire block
+ Mockito.when(blockState.getBlock()).thenReturn(block);
+
+ Mockito.when(blockState.getBlock().getDrops(any())).thenReturn(null);
+ Mockito.when(blockState.getType()).thenReturn(Material.OAK_LOG);
+ woodcuttingManager.processBonusDropCheck(blockState);
+
+ // verify bonus drops were not spawned
+ Mockito.verify(woodcuttingManager, Mockito.times(0)).spawnHarvestLumberBonusDrops(blockState);
+ }
+
+ @Test
+ void testProcessWoodcuttingBlockXP() {
+ BlockState targetBlock = Mockito.mock(BlockState.class);
+ Mockito.when(targetBlock.getType()).thenReturn(Material.OAK_LOG);
+ // wire XP
+ Mockito.when(ExperienceConfig.getInstance().getXp(PrimarySkillType.WOODCUTTING, Material.OAK_LOG)).thenReturn(5);
+
+ // Verify XP increased by 5 when processing XP
+ woodcuttingManager.processWoodcuttingBlockXP(targetBlock);
+ Mockito.verify(mmoPlayer, Mockito.times(1)).beginXpGain(eq(PrimarySkillType.WOODCUTTING), eq(5F), any(), any());
+ }
+}
diff --git a/src/test/java/com/gmail/nossr50/util/random/ProbabilityTest.java b/src/test/java/com/gmail/nossr50/util/random/ProbabilityTest.java
new file mode 100644
index 0000000000..a2b6cc416e
--- /dev/null
+++ b/src/test/java/com/gmail/nossr50/util/random/ProbabilityTest.java
@@ -0,0 +1,105 @@
+package com.gmail.nossr50.util.random;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.stream.Stream;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class ProbabilityTest {
+
+ private static Stream provideProbabilitiesForWithinExpectations() {
+ return Stream.of(
+ // static probability, % of time for success
+ Arguments.of(new ProbabilityImpl(5), 5),
+ Arguments.of(new ProbabilityImpl(10), 10),
+ Arguments.of(new ProbabilityImpl(15), 15),
+ Arguments.of(new ProbabilityImpl(20), 20),
+ Arguments.of(new ProbabilityImpl(25), 25),
+ Arguments.of(new ProbabilityImpl(50), 50),
+ Arguments.of(new ProbabilityImpl(75), 75),
+ Arguments.of(new ProbabilityImpl(90), 90),
+ Arguments.of(new ProbabilityImpl(99.9), 99.9),
+ Arguments.of(new ProbabilityImpl(0.05), 0.05),
+ Arguments.of(new ProbabilityImpl(0.1), 0.1),
+ Arguments.of(new ProbabilityImpl(500), 100),
+ Arguments.of(new ProbabilityImpl(1000), 100)
+ );
+ }
+
+ private static Stream provideOfPercentageProbabilitiesForWithinExpectations() {
+ return Stream.of(
+ // static probability, % of time for success
+ Arguments.of(Probability.ofPercent(5), 5),
+ Arguments.of(Probability.ofPercent(10), 10),
+ Arguments.of(Probability.ofPercent(15), 15),
+ Arguments.of(Probability.ofPercent(20), 20),
+ Arguments.of(Probability.ofPercent(25), 25),
+ Arguments.of(Probability.ofPercent(50), 50),
+ Arguments.of(Probability.ofPercent(75), 75),
+ Arguments.of(Probability.ofPercent(90), 90),
+ Arguments.of(Probability.ofPercent(99.9), 99.9),
+ Arguments.of(Probability.ofPercent(0.05), 0.05),
+ Arguments.of(Probability.ofPercent(0.1), 0.1),
+ Arguments.of(Probability.ofPercent(500), 100),
+ Arguments.of(Probability.ofPercent(1000), 100)
+ );
+ }
+ @Test
+ void testAlwaysWinConstructor() {
+ for (int i = 0; i < 100000; i++) {
+ assertTrue(new ProbabilityImpl(100).evaluate());
+ }
+ }
+
+ @Test
+ void testAlwaysLoseConstructor() {
+ for (int i = 0; i < 100000; i++) {
+ assertFalse(new ProbabilityImpl(0).evaluate());
+ }
+ }
+
+ @Test
+ void testAlwaysWinOfPercent() {
+ for (int i = 0; i < 100000; i++) {
+ assertTrue(Probability.ofPercent(100).evaluate());
+ }
+ }
+
+ @Test
+ void testAlwaysLoseOfPercent() {
+ for (int i = 0; i < 100000; i++) {
+ assertFalse(Probability.ofPercent(0).evaluate());
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource("provideProbabilitiesForWithinExpectations")
+ void testOddsExpectationsConstructor(Probability probability, double expectedWinPercent) {
+ assertExpectations(probability, expectedWinPercent);
+ }
+
+ @ParameterizedTest
+ @MethodSource("provideOfPercentageProbabilitiesForWithinExpectations")
+ void testOddsExpectationsOfPercent(Probability probability, double expectedWinPercent) {
+ assertExpectations(probability, expectedWinPercent);
+ }
+
+ private static void assertExpectations(Probability probability, double expectedWinPercent) {
+ double iterations = 2.0e7;
+ double winCount = 0;
+
+ for (int i = 0; i < iterations; i++) {
+ if(probability.evaluate()) {
+ winCount++;
+ }
+ }
+
+ double successPercent = (winCount / iterations) * 100;
+ System.out.println(successPercent + ", " + expectedWinPercent);
+ assertEquals(expectedWinPercent, successPercent, 0.05D);
+ }
+}
diff --git a/src/test/java/com/gmail/nossr50/util/random/ProbabilityUtilTest.java b/src/test/java/com/gmail/nossr50/util/random/ProbabilityUtilTest.java
new file mode 100644
index 0000000000..35f134da5c
--- /dev/null
+++ b/src/test/java/com/gmail/nossr50/util/random/ProbabilityUtilTest.java
@@ -0,0 +1,66 @@
+package com.gmail.nossr50.util.random;
+
+import com.gmail.nossr50.config.AdvancedConfig;
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
+import com.gmail.nossr50.mcMMO;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.util.stream.Stream;
+
+import static com.gmail.nossr50.datatypes.skills.SubSkillType.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class ProbabilityUtilTest {
+ mcMMO mmoInstance;
+ AdvancedConfig advancedConfig;
+
+ final static double impactChance = 11D;
+ final static double greaterImpactChance = 0.007D;
+ final static double fastFoodChance = 45.5D;
+
+ @BeforeEach
+ public void setupMocks() throws NoSuchFieldException, IllegalAccessException {
+ this.mmoInstance = mock(mcMMO.class);
+ mcMMO.class.getField("p").set(null, mmoInstance);
+ this.advancedConfig = mock(AdvancedConfig.class);
+ when(mmoInstance.getAdvancedConfig()).thenReturn(advancedConfig);
+ when(advancedConfig.getImpactChance()).thenReturn(impactChance);
+ when(advancedConfig.getGreaterImpactChance()).thenReturn(greaterImpactChance);
+ when(advancedConfig.getFastFoodChance()).thenReturn(fastFoodChance);
+ }
+
+ private static Stream staticChanceSkills() {
+ return Stream.of(
+ // static probability, % of time for success
+ Arguments.of(AXES_ARMOR_IMPACT, impactChance),
+ Arguments.of(AXES_GREATER_IMPACT, greaterImpactChance),
+ Arguments.of(TAMING_FAST_FOOD_SERVICE, fastFoodChance)
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource("staticChanceSkills")
+ void testStaticChanceSkills(SubSkillType subSkillType, double expectedWinPercent) throws InvalidStaticChance {
+ Probability staticRandomChance = ProbabilityUtil.getStaticRandomChance(subSkillType);
+ assertProbabilityExpectations(expectedWinPercent, staticRandomChance);
+ }
+
+ private static void assertProbabilityExpectations(double expectedWinPercent, Probability probability) {
+ double iterations = 2.0e7;
+ double winCount = 0;
+ for (int i = 0; i < iterations; i++) {
+ if(probability.evaluate()) {
+ winCount++;
+ }
+ }
+
+ double successPercent = (winCount / iterations) * 100;
+ System.out.println(successPercent + ", " + expectedWinPercent);
+ assertEquals(expectedWinPercent, successPercent, 0.05D);
+ }
+}
diff --git a/src/test/java/com/gmail/nossr50/util/random/RandomChanceTest.java b/src/test/java/com/gmail/nossr50/util/random/RandomChanceTest.java
deleted file mode 100644
index f28e7e8427..0000000000
--- a/src/test/java/com/gmail/nossr50/util/random/RandomChanceTest.java
+++ /dev/null
@@ -1,116 +0,0 @@
-//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.util.Permissions;
-//import com.gmail.nossr50.util.player.UserManager;
-//import org.bukkit.entity.Player;
-//import org.jetbrains.annotations.NotNull;
-//import org.junit.Assert;
-//import org.junit.Before;
-//import org.junit.Test;
-//import org.junit.runner.RunWith;
-//import org.mockito.Mockito;
-//import org.powermock.api.mockito.PowerMockito;
-//import org.powermock.core.classloader.annotations.PrepareForTest;
-//import org.powermock.modules.junit4.PowerMockRunner;
-//
-//import static org.mockito.Mockito.mock;
-//
-////TODO: Rewrite the entire com.gmail.nossr50.util.random package, it was written in haste and it disgusts me
-////TODO: Add more tests for the other types of random dice rolls
-//@RunWith(PowerMockRunner.class)
-//@PrepareForTest({RandomChanceUtil.class, UserManager.class})
-//public class RandomChanceTest {
-//
-// private Player luckyPlayer;
-// private McMMOPlayer mmoPlayerLucky;
-//
-// private Player normalPlayer;
-// private McMMOPlayer mmoPlayerNormal;
-//
-// private SubSkillType subSkillType;
-// private PrimarySkillType primarySkillType;
-//
-// private final String testASCIIHeader = "---- mcMMO Tests ----";
-//
-// @Before
-// public void setUpMock() {
-// primarySkillType = PrimarySkillType.HERBALISM;
-// subSkillType = SubSkillType.HERBALISM_GREEN_THUMB;
-//
-// //TODO: Likely needs to be changed per skill if more tests were added
-// PowerMockito.stub(PowerMockito.method(RandomChanceUtil.class, "getMaximumProbability", subSkillType.getClass())).toReturn(100D);
-// PowerMockito.stub(PowerMockito.method(RandomChanceUtil.class, "getMaxBonusLevelCap", subSkillType.getClass())).toReturn(1000D);
-//
-// normalPlayer = mock(Player.class);
-// luckyPlayer = mock(Player.class);
-//
-// mmoPlayerNormal = mock(McMMOPlayer.class);
-// mmoPlayerLucky = mock(McMMOPlayer.class);
-//
-// PowerMockito.mockStatic(UserManager.class);
-// Mockito.when(UserManager.getPlayer(normalPlayer)).thenReturn(mmoPlayerNormal);
-// Mockito.when(UserManager.getPlayer(luckyPlayer)).thenReturn(mmoPlayerLucky);
-//
-// Mockito.when(mmoPlayerNormal.getPlayer()).thenReturn(normalPlayer);
-// Mockito.when(mmoPlayerLucky.getPlayer()).thenReturn(luckyPlayer);
-//
-// //Lucky player has the lucky permission
-// //Normal player doesn't have any lucky permission
-// Mockito.when(Permissions.lucky(luckyPlayer, primarySkillType)).thenReturn(true);
-// Mockito.when(Permissions.lucky(normalPlayer, primarySkillType)).thenReturn(false);
-//
-// Mockito.when(mmoPlayerNormal.getSkillLevel(primarySkillType)).thenReturn(800);
-// Mockito.when(mmoPlayerLucky.getSkillLevel(primarySkillType)).thenReturn(800);
-// }
-//
-// @Test
-// public void testLuckyChance() {
-// System.out.println(testASCIIHeader);
-// System.out.println("Testing success odds to fall within expected values...");
-// assertEquals(80D, getSuccessChance(mmoPlayerNormal),0D);
-// assertEquals(80D * RandomChanceUtil.LUCKY_MODIFIER, getSuccessChance(mmoPlayerLucky),0D);
-// }
-//
-// @Test
-// public void testNeverFailsSuccessLuckyPlayer() {
-// System.out.println(testASCIIHeader);
-// System.out.println("Test - Lucky Player with 80% base success should never fail (10,000 iterations)");
-// for(int x = 0; x < 10000; x++) {
-// Assert.assertTrue(RandomChanceUtil.checkRandomChanceExecutionSuccess(luckyPlayer, SubSkillType.HERBALISM_GREEN_THUMB, true));
-// if(x == 10000-1)
-// System.out.println("They never failed!");
-// }
-// }
-//
-// @Test
-// public void testFailsAboutExpected() {
-// System.out.println(testASCIIHeader);
-// System.out.println("Test - Player with 800 skill should fail about 20% of the time (100,000 iterations)");
-// double ratioDivisor = 1000; //1000 because we run the test 100,000 times
-// double expectedFailRate = 20D;
-//
-// double win = 0, loss = 0;
-// for(int x = 0; x < 100000; x++) {
-// if(RandomChanceUtil.checkRandomChanceExecutionSuccess(normalPlayer, SubSkillType.HERBALISM_GREEN_THUMB, true)) {
-// win++;
-// } else {
-// loss++;
-// }
-// }
-//
-// double lossRatio = (loss / ratioDivisor);
-// Assert.assertEquals(lossRatio, expectedFailRate, 1D);
-// }
-//
-// private double getSuccessChance(@NotNull McMMOPlayer mmoPlayer) {
-// RandomChanceSkill randomChanceSkill = new RandomChanceSkill(mmoPlayer.getPlayer(), subSkillType, true);
-// return RandomChanceUtil.calculateChanceOfSuccess(randomChanceSkill);
-// }
-//
-// private void assertEquals(double expected, double actual, double delta) {
-// Assert.assertEquals(expected, actual, delta);
-// }
-//}
diff --git a/src/test/java/com/gmail/nossr50/util/skills/SkillToolsTest.java b/src/test/java/com/gmail/nossr50/util/skills/SkillToolsTest.java
deleted file mode 100644
index 4a295d5d31..0000000000
--- a/src/test/java/com/gmail/nossr50/util/skills/SkillToolsTest.java
+++ /dev/null
@@ -1,16 +0,0 @@
-//package com.gmail.nossr50.util.skills;
-//
-//import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
-//import com.google.common.collect.ImmutableList;
-//import org.junit.Before;
-//import org.junit.Test;
-//import org.junit.runner.RunWith;
-//import org.powermock.core.classloader.annotations.PrepareForTest;
-//import org.powermock.core.classloader.annotations.SuppressStaticInitializationFor;
-//import org.powermock.modules.junit4.PowerMockRunner;
-//
-//@RunWith(PowerMockRunner.class)
-//@PrepareForTest(SkillTools.class)
-//public class SkillToolsTest {
-//
-//}
\ No newline at end of file
diff --git a/src/test/java/com/gmail/nossr50/util/text/TextUtilsTest.java b/src/test/java/com/gmail/nossr50/util/text/TextUtilsTest.java
index cefbe70102..c581571791 100644
--- a/src/test/java/com/gmail/nossr50/util/text/TextUtilsTest.java
+++ b/src/test/java/com/gmail/nossr50/util/text/TextUtilsTest.java
@@ -7,7 +7,7 @@
/**
* This Unit Test checks if Adventure was set up correctly and works as expected.
- * Normally we can rely on this to be the case. However sometimes our dependencies
+ * Normally, we can rely on this to be the case. However sometimes our dependencies
* lack so far behind that things stop working correctly.
* This test ensures that basic functionality is guaranteed to work as we would expect.
*
diff --git a/src/test/resources/healthydb.users b/src/test/resources/healthydb.users
index 7ce5ccbad1..79a2c7e703 100644
--- a/src/test/resources/healthydb.users
+++ b/src/test/resources/healthydb.users
@@ -1,3 +1,3 @@
-nossr50:1:IGNORED:IGNORED:10:2:20:3:4:5:6:7:8:9:10:30:40:50:60:70:80:90:100:IGNORED:11:110:111:222:333:444:555:666:777:IGNORED:12:120:888:IGNORED:HEARTS:13:130:588fe472-1c82-4c4e-9aa1-7eefccb277e3:1111:999:2020:
-mrfloris:2420:::0:2452:0:1983:1937:1790:3042:1138:3102:2408:3411:0:0:0:0:0:0:0:0::642:0:1617583171:0:1617165043:0:1617583004:1617563189:1616785408::2184:0:0:1617852413:HEARTS:415:0:631e3896-da2a-4077-974b-d047859d76bc:5:1600906906:3030:
-powerless:0:::0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0::0:0:0:0:0:0:0:0:0::0:0:0:1337:HEARTS:0:0:e0d07db8-f7e8-43c7-9ded-864dfc6f3b7c:5:1600906906:4040:
\ No newline at end of file
+nossr50:1:IGNORED:IGNORED:10:2:20:3:4:5:6:7:8:9:10:30:40:50:60:70:80:90:100:IGNORED:11:110:111:222:333:444:555:666:777:IGNORED:12:120:888:IGNORED:HEARTS:13:130:588fe472-1c82-4c4e-9aa1-7eefccb277e3:1111:999:2020:140:14:150:15:1111:2222:3333:
+mrfloris:2420:::0:2452:0:1983:1937:1790:3042:1138:3102:2408:3411:0:0:0:0:0:0:0:0::642:0:1617583171:0:1617165043:0:1617583004:1617563189:1616785408::2184:0:0:1617852413:HEARTS:415:0:631e3896-da2a-4077-974b-d047859d76bc:5:1600906906:3030:0:0:0:0:0:0:0:
+powerless:0:::0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0::0:0:0:0:0:0:0:0:0::0:0:0:1337:HEARTS:0:0:e0d07db8-f7e8-43c7-9ded-864dfc6f3b7c:5:1600906906:4040:0:0:0:0:0:0:0:
\ No newline at end of file
diff --git a/src/test/resources/olderdb.users b/src/test/resources/olderdb.users
new file mode 100644
index 0000000000..7ce5ccbad1
--- /dev/null
+++ b/src/test/resources/olderdb.users
@@ -0,0 +1,3 @@
+nossr50:1:IGNORED:IGNORED:10:2:20:3:4:5:6:7:8:9:10:30:40:50:60:70:80:90:100:IGNORED:11:110:111:222:333:444:555:666:777:IGNORED:12:120:888:IGNORED:HEARTS:13:130:588fe472-1c82-4c4e-9aa1-7eefccb277e3:1111:999:2020:
+mrfloris:2420:::0:2452:0:1983:1937:1790:3042:1138:3102:2408:3411:0:0:0:0:0:0:0:0::642:0:1617583171:0:1617165043:0:1617583004:1617563189:1616785408::2184:0:0:1617852413:HEARTS:415:0:631e3896-da2a-4077-974b-d047859d76bc:5:1600906906:3030:
+powerless:0:::0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0::0:0:0:0:0:0:0:0:0::0:0:0:1337:HEARTS:0:0:e0d07db8-f7e8-43c7-9ded-864dfc6f3b7c:5:1600906906:4040:
\ No newline at end of file