mirror of
https://github.com/mcMMO-Dev/mcMMO.git
synced 2025-06-28 03:34:43 +02:00
Endgame Update (#4840)
General Added Crossbows Skill, this skill is a WIP and feedback on discord is appreciated. Added Tridents Skill, this skill is a WIP and feedback on discord is appreciated. Added the "endgame" triple drop subskill 'Mother Lode' to Mining Added the "endgame" triple drop subskill 'Clean Cuts' to Woodcutting Added the "endgame" triple drop subskill 'Verdant Bounty' to Herbalism Added /mmopower command which simply shows your power level (aliases /mmopowerlevel /powerlevel) Config Added 'Send_To_Console' settings to chat.yml to toggle sending party or admin chat messages to console. Replaced 'Experience_Formula.Modifier' in experience.yml with 'Experience_Formula.Skill_Multiplier' which is easier to understand and less prone to divide by zero bugs. child.yml config is gone now, feel free to delete it. Tweaks Tree Feller now drops 90% less non-wood block rewards (leaves/etc) on average from Knock on Wood. Treasure drop rate from Shake, Fishing, Hylian, and Excavation now benefit from the Luck perk. Updated advanced.yml with entries for the new skills Permission nodes Added 'mcmmo.commands.mmopower' permission node for the new /mmopower command Added 'mcmmo.commands.crossbows' permission node Added 'mcmmo.ability.crossbows.crossbowslimitbreak' permission node Added 'mcmmo.ability.crossbows.trickshot' permission node Added 'mcmmo.ability.herbalism.verdantbounty' permission node Added 'mcmmo.ability.mining.motherlode' permission node Added 'mcmmo.ability.woodcutting.cleancuts' permission node Locale Added locale entries for motherlode, cleancuts, and verdant bounty. Codebase Major rewrite for how random chance was handled in the code. Many skills with RNG elements now send out a SubSkillEvent (which can be used to modify probability or cancel the results), some skills without RNG still send out this event when activated, this event is cancellable so it can be used to make a skill fail. A lot of new unit tests were added to help keep mcMMO stable as part of this update, of course, more could always be added. NOTES: One feature of this update is to provide an endgame benefits to some skills that you can grind for a long time, ideally for a long while. I will likely expand upon this idea in future updates. A few skills have these endgame-oriented subskills, these new subskills provide a small benefit at first that grows and scales up to level 10,000 (or 1,000 for Standard mode which no one uses) and does not have ranks (other than the initial rank to unlock it). These endgame sub skills unlock at level 1000 for users with default mcMMO settings, or 100 for those using the optional Standard scaling. You can tweak the benefits of these skills in advanced.yml, the default settings are meant to be a good starting point. Crossbows and Tridents are WIP skills, I would like feedback on discord about them. More info on the new Triple Drop skills (Mother Lode, Clean Cuts, Verdant Bounty): Currently these start at about 5% chance and can reach a maximum 50% chance if a player acquired 10,000 skill, you can adjust this in advanced.yml These skills respect double drop settings from config.yml just like the corresponding Double Drop skills do, if a double drop is disabled for an item, then it's disabled for triple drops too. I added a new Power Level Command, for now this just shows you your current power level. If I ever add features based on power level, this command will likely display output related to those features. Regarding Maces, I will likely add that as a WIP skill when the next Minecraft update drops.
This commit is contained in:

committed by
GitHub

parent
bead5feb14
commit
2594dc1bca
@ -7,8 +7,7 @@ import com.gmail.nossr50.datatypes.skills.SubSkillType;
|
||||
import com.gmail.nossr50.mcMMO;
|
||||
import com.gmail.nossr50.skills.repair.Repair;
|
||||
import com.gmail.nossr50.skills.salvage.Salvage;
|
||||
import com.gmail.nossr50.util.random.RandomChanceSkill;
|
||||
import com.gmail.nossr50.util.random.RandomChanceUtil;
|
||||
import com.gmail.nossr50.util.random.ProbabilityUtil;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Block;
|
||||
@ -98,7 +97,7 @@ public final class BlockUtils {
|
||||
*/
|
||||
public static boolean checkDoubleDrops(Player player, BlockState blockState, PrimarySkillType skillType, SubSkillType subSkillType) {
|
||||
if (mcMMO.p.getGeneralConfig().getDoubleDropsEnabled(skillType, blockState.getType()) && Permissions.isSubSkillEnabled(player, subSkillType)) {
|
||||
return RandomChanceUtil.checkRandomChanceExecutionSuccess(new RandomChanceSkill(player, subSkillType, true));
|
||||
return ProbabilityUtil.isSkillRNGSuccessful(subSkillType, player);
|
||||
}
|
||||
|
||||
return false;
|
||||
@ -231,22 +230,22 @@ public final class BlockUtils {
|
||||
return mcMMO.getMaterialMapStore().isTreeFellerDestructible(material);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a given block should be affected by Flux Mining
|
||||
*
|
||||
* @param blockState The {@link BlockState} of the block to check
|
||||
* @return true if the block should affected by Flux Mining, false otherwise
|
||||
*/
|
||||
public static boolean affectedByFluxMining(BlockState blockState) {
|
||||
switch (blockState.getType()) {
|
||||
case IRON_ORE:
|
||||
case GOLD_ORE:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// /**
|
||||
// * Determine if a given block should be affected by Flux Mining
|
||||
// *
|
||||
// * @param blockState The {@link BlockState} of the block to check
|
||||
// * @return true if the block should affected by Flux Mining, false otherwise
|
||||
// */
|
||||
// public static boolean affectedByFluxMining(BlockState blockState) {
|
||||
// switch (blockState.getType()) {
|
||||
// case IRON_ORE:
|
||||
// case GOLD_ORE:
|
||||
// return true;
|
||||
//
|
||||
// default:
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* Determine if a given block can activate Herbalism abilities
|
||||
|
@ -183,8 +183,7 @@ public final class EventUtils {
|
||||
* @param subSkillType target subskill
|
||||
* @return the event after it has been fired
|
||||
*/
|
||||
@Deprecated
|
||||
public static @NotNull SubSkillEvent callSubSkillEvent(Player player, SubSkillType subSkillType) {
|
||||
public static @NotNull SubSkillEvent callSubSkillEvent(@NotNull Player player, @NotNull SubSkillType subSkillType) {
|
||||
SubSkillEvent event = new SubSkillEvent(player, subSkillType);
|
||||
mcMMO.p.getServer().getPluginManager().callEvent(event);
|
||||
|
||||
@ -399,7 +398,7 @@ public final class EventUtils {
|
||||
McMMOPlayer mmoPlayer = UserManager.getPlayer(player);
|
||||
if(mmoPlayer == null)
|
||||
return true;
|
||||
|
||||
|
||||
McMMOPlayerXpGainEvent event = new McMMOPlayerXpGainEvent(player, skill, xpGained, xpGainReason);
|
||||
mcMMO.p.getServer().getPluginManager().callEvent(event);
|
||||
|
||||
|
@ -36,14 +36,26 @@ public final class ItemUtils {
|
||||
* @param item Item to check
|
||||
* @return true if the item is a bow, false otherwise
|
||||
*/
|
||||
// TODO: Unit tests
|
||||
public static boolean isBow(@NotNull ItemStack item) {
|
||||
return mcMMO.getMaterialMapStore().isBow(item.getType().getKey().getKey());
|
||||
}
|
||||
|
||||
// TODO: Unit tests
|
||||
public static boolean isCrossbow(@NotNull ItemStack item) {
|
||||
return mcMMO.getMaterialMapStore().isCrossbow(item.getType().getKey().getKey());
|
||||
}
|
||||
|
||||
// TODO: Unit tests
|
||||
public static boolean isBowOrCrossbow(@NotNull ItemStack item) {
|
||||
return isBow(item) || isCrossbow(item);
|
||||
}
|
||||
|
||||
// TODO: Unit tests
|
||||
public static boolean isTrident(@NotNull ItemStack item) {
|
||||
return mcMMO.getMaterialMapStore().isTrident(item.getType().getKey().getKey());
|
||||
}
|
||||
|
||||
public static boolean hasItemInEitherHand(@NotNull Player player, Material material) {
|
||||
return player.getInventory().getItemInMainHand().getType() == material || player.getInventory().getItemInOffHand().getType() == material;
|
||||
}
|
||||
|
@ -10,9 +10,6 @@ import java.util.Locale;
|
||||
/**
|
||||
* Stores hash tables for item and block names
|
||||
* This allows for better support across multiple versions of Minecraft
|
||||
*
|
||||
* This is a temporary class, mcMMO is spaghetti and I'l clean it up later
|
||||
*
|
||||
*/
|
||||
public class MaterialMapStore {
|
||||
|
||||
@ -52,7 +49,6 @@ public class MaterialMapStore {
|
||||
private final @NotNull HashSet<String> bows;
|
||||
private final @NotNull HashSet<String> crossbows;
|
||||
private final @NotNull HashSet<String> tools;
|
||||
|
||||
private final @NotNull HashSet<String> enchantables;
|
||||
|
||||
private final @NotNull HashSet<String> ores;
|
||||
@ -820,6 +816,14 @@ public class MaterialMapStore {
|
||||
return crossbows.contains(id);
|
||||
}
|
||||
|
||||
public boolean isTrident(@NotNull Material material) {
|
||||
return isTrident(material.getKey().getKey());
|
||||
}
|
||||
|
||||
public boolean isTrident(@NotNull String id) {
|
||||
return tridents.contains(id);
|
||||
}
|
||||
|
||||
public boolean isLeatherArmor(@NotNull Material material) {
|
||||
return isLeatherArmor(material.getKey().getKey());
|
||||
}
|
||||
|
@ -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";
|
||||
|
@ -343,4 +343,26 @@ public final class Misc {
|
||||
experienceOrb.setExperience(orbExpValue);
|
||||
}
|
||||
}
|
||||
|
||||
// public static void hackyUnitTest(@NotNull McMMOPlayer normalPlayer) {
|
||||
// mcMMO.p.getLogger().info("Starting hacky unit test...");
|
||||
// int iterations = 1000000;
|
||||
// double ratioDivisor = 10000; //10000 because we run the test 1,000,000 times
|
||||
// double expectedFailRate = 100.0D - RandomChanceUtil.getRandomChanceExecutionSuccess(normalPlayer.getPlayer(), SubSkillType.MINING_MOTHER_LODE, true);
|
||||
//
|
||||
// double win = 0, loss = 0;
|
||||
// for(int x = 0; x < iterations; x++) {
|
||||
// if(RandomChanceUtil.checkRandomChanceExecutionSuccess(normalPlayer.getPlayer(), SubSkillType.MINING_MOTHER_LODE, true)) {
|
||||
// win++;
|
||||
// } else {
|
||||
// loss++;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// double lossRatio = (loss / ratioDivisor);
|
||||
// mcMMO.p.getLogger().info("Expected Fail Rate: "+expectedFailRate);
|
||||
// mcMMO.p.getLogger().info("Loss Ratio for hacky test: "+lossRatio);
|
||||
//// Assert.assertEquals(lossRatio, expectedFailRate, 0.01D);
|
||||
// }
|
||||
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ public final class MobHealthbarUtils {
|
||||
EntityDamageEvent lastDamageCause = player.getLastDamageCause();
|
||||
String replaceString = lastDamageCause instanceof EntityDamageByEntityEvent ? StringUtils.getPrettyEntityTypeString(((EntityDamageByEntityEvent) lastDamageCause).getDamager().getType()) : "a mob";
|
||||
|
||||
return deathMessage.replaceAll("(?:(\u00A7(?:[0-9A-FK-ORa-fk-or]))*(?:[\u2764\u25A0]{1,10})){1,2}", replaceString);
|
||||
return deathMessage.replaceAll("(?:(§(?:[0-9A-FK-ORa-fk-or]))*(?:[❤■]{1,10})){1,2}", replaceString);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -5,16 +5,18 @@ import com.gmail.nossr50.datatypes.skills.ItemType;
|
||||
import com.gmail.nossr50.datatypes.skills.MaterialType;
|
||||
import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
|
||||
import com.gmail.nossr50.datatypes.skills.SubSkillType;
|
||||
import com.gmail.nossr50.datatypes.skills.subskills.AbstractSubSkill;
|
||||
import com.gmail.nossr50.mcMMO;
|
||||
import com.gmail.nossr50.util.skills.RankUtils;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.Server;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.EntityType;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.permissions.Permissible;
|
||||
import org.bukkit.permissions.Permission;
|
||||
import org.bukkit.permissions.PermissionDefault;
|
||||
import org.bukkit.plugin.PluginManager;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
@ -164,10 +166,14 @@ public final class Permissions {
|
||||
* SKILLS
|
||||
*/
|
||||
|
||||
public static boolean skillEnabled(Permissible permissible, PrimarySkillType skill) {return permissible.hasPermission("mcmmo.skills." + skill.toString().toLowerCase(Locale.ENGLISH)); }
|
||||
public static boolean skillEnabled(Permissible permissible, PrimarySkillType skill) {
|
||||
return permissible.hasPermission("mcmmo.skills." + skill.toString().toLowerCase(Locale.ENGLISH));
|
||||
}
|
||||
|
||||
public static boolean vanillaXpBoost(Permissible permissible, PrimarySkillType skill) { return permissible.hasPermission("mcmmo.ability." + skill.toString().toLowerCase(Locale.ENGLISH) + ".vanillaxpboost"); }
|
||||
public static boolean isSubSkillEnabled(Permissible permissible, SubSkillType subSkillType) { return permissible.hasPermission(subSkillType.getPermissionNodeAddress()); }
|
||||
public static boolean isSubSkillEnabled(Permissible permissible, AbstractSubSkill abstractSubSkill) { return permissible.hasPermission(abstractSubSkill.getPermissionNode()); }
|
||||
public static boolean isSubSkillEnabled(Permissible permissible, SubSkillType subSkillType) {
|
||||
return permissible.hasPermission(subSkillType.getPermissionNodeAddress());
|
||||
}
|
||||
|
||||
/* ACROBATICS */
|
||||
public static boolean dodge(Permissible permissible) { return permissible.hasPermission("mcmmo.ability.acrobatics.dodge"); }
|
||||
@ -179,6 +185,7 @@ public final class Permissions {
|
||||
public static boolean concoctions(Permissible permissible) { return permissible.hasPermission("mcmmo.ability.alchemy.concoctions"); }
|
||||
|
||||
/* ARCHERY */
|
||||
public static boolean explosiveShot(Permissible permissible) { return permissible.hasPermission("mcmmo.ability.archery.explosiveshot"); }
|
||||
public static boolean arrowRetrieval(Permissible permissible) { return permissible.hasPermission("mcmmo.ability.archery.trackarrows"); }
|
||||
public static boolean daze(Permissible permissible) { return permissible.hasPermission("mcmmo.ability.archery.daze"); }
|
||||
|
||||
@ -225,6 +232,20 @@ public final class Permissions {
|
||||
/* WOODCUTTING */
|
||||
public static boolean treeFeller(Permissible permissible) { return permissible.hasPermission("mcmmo.ability.woodcutting.treefeller"); }
|
||||
|
||||
/* CROSSBOWS */
|
||||
public static boolean superShotgun(Permissible permissible) {
|
||||
return permissible.hasPermission("mcmmo.ability.crossbows.supershotgun");
|
||||
}
|
||||
public static boolean trickShot(Permissible permissible) { return permissible.hasPermission("mcmmo.ability.crossbows.trickshot"); }
|
||||
public static boolean poweredShot(Permissible permissible) { return permissible.hasPermission("mcmmo.ability.crossbows.poweredshot"); }
|
||||
|
||||
/* TRIDENTS */
|
||||
public static boolean tridentsSuper(Permissible permissible) {
|
||||
return false;
|
||||
// return permissible.hasPermission("mcmmo.ability.tridents.superability");
|
||||
}
|
||||
public static boolean tridentsLimitBreak(Permissible permissible) { return permissible.hasPermission("mcmmo.ability.tridents.superability"); }
|
||||
|
||||
/*
|
||||
* PARTY
|
||||
*/
|
||||
@ -256,4 +277,15 @@ public final class Permissions {
|
||||
permission.setDefault(permissionDefault);
|
||||
pluginManager.addPermission(permission);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a player can use a skill
|
||||
*
|
||||
* @param player target player
|
||||
* @param subSkillType target subskill
|
||||
* @return true if the player has permission and has the skill unlocked
|
||||
*/
|
||||
public static boolean canUseSubSkill(@NotNull Player player, @NotNull SubSkillType subSkillType) {
|
||||
return isSubSkillEnabled(player, subSkillType) && RankUtils.hasUnlockedSubskill(player, subSkillType);
|
||||
}
|
||||
}
|
||||
|
@ -61,6 +61,9 @@ public final class CommandRegistrationManager {
|
||||
case AXES:
|
||||
command.setExecutor(new AxesCommand());
|
||||
break;
|
||||
case CROSSBOWS:
|
||||
command.setExecutor(new CrossbowsCommand());
|
||||
break;
|
||||
|
||||
case EXCAVATION:
|
||||
command.setExecutor(new ExcavationCommand());
|
||||
@ -97,6 +100,9 @@ public final class CommandRegistrationManager {
|
||||
case TAMING:
|
||||
command.setExecutor(new TamingCommand());
|
||||
break;
|
||||
case TRIDENTS:
|
||||
command.setExecutor(new TridentsCommand());
|
||||
break;
|
||||
|
||||
case UNARMED:
|
||||
command.setExecutor(new UnarmedCommand());
|
||||
|
@ -1,5 +0,0 @@
|
||||
package com.gmail.nossr50.util.random;
|
||||
|
||||
public class InvalidActivationException extends Exception {
|
||||
//Weee
|
||||
}
|
65
src/main/java/com/gmail/nossr50/util/random/Probability.java
Normal file
65
src/main/java/com/gmail/nossr50/util/random/Probability.java
Normal file
@ -0,0 +1,65 @@
|
||||
package com.gmail.nossr50.util.random;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public interface Probability {
|
||||
/**
|
||||
* The value of this Probability
|
||||
* Should return a result between 0 and 1 (inclusive)
|
||||
* A value of 1 or greater represents something that will always succeed
|
||||
* A value of around 0.5 represents something that succeeds around half the time
|
||||
* A value of 0 represents something that will always fail
|
||||
*
|
||||
* @return the value of probability
|
||||
*/
|
||||
double getValue();
|
||||
|
||||
/**
|
||||
* Create a new Probability with the given value
|
||||
* A value of 100 would represent 100% chance of success
|
||||
* A value of 50 would represent 50% chance of success
|
||||
* A value of 0 would represent 0% chance of success
|
||||
* A value of 1 would represent 1% chance of success
|
||||
* A value of 0.5 would represent 0.5% chance of success
|
||||
* A value of 0.01 would represent 0.01% chance of success
|
||||
*
|
||||
* @param percentage the value of the probability
|
||||
* @return a new Probability with the given value
|
||||
*/
|
||||
static @NotNull Probability ofPercent(double percentage) {
|
||||
return new ProbabilityImpl(percentage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulates a "roll of the dice"
|
||||
* If the value passed is higher than the "random" value, than it is a successful roll
|
||||
*
|
||||
* @param probabilityValue probability value
|
||||
* @return true for succeeding, false for failing
|
||||
*/
|
||||
static private boolean isSuccessfulRoll(double probabilityValue) {
|
||||
return (probabilityValue) >= ThreadLocalRandom.current().nextDouble(1D);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate an outcome on a probability and return true or false for the result of that outcome
|
||||
*
|
||||
* @return true if the probability succeeded, false if it failed
|
||||
*/
|
||||
default boolean evaluate() {
|
||||
return isSuccessfulRoll(getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify and then Simulate an outcome on a probability and return true or false for the result of that outcome
|
||||
*
|
||||
* @param probabilityMultiplier probability will be multiplied by this before success is checked
|
||||
* @return true if the probability succeeded, false if it failed
|
||||
*/
|
||||
default boolean evaluate(double probabilityMultiplier) {
|
||||
double probabilityValue = getValue() * probabilityMultiplier;
|
||||
return isSuccessfulRoll(probabilityValue);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
226
src/main/java/com/gmail/nossr50/util/random/ProbabilityUtil.java
Normal file
226
src/main/java/com/gmail/nossr50/util/random/ProbabilityUtil.java
Normal file
@ -0,0 +1,226 @@
|
||||
package com.gmail.nossr50.util.random;
|
||||
|
||||
import com.gmail.nossr50.datatypes.player.McMMOPlayer;
|
||||
import com.gmail.nossr50.datatypes.skills.PrimarySkillType;
|
||||
import com.gmail.nossr50.datatypes.skills.SubSkillType;
|
||||
import com.gmail.nossr50.events.skills.secondaryabilities.SubSkillEvent;
|
||||
import com.gmail.nossr50.mcMMO;
|
||||
import com.gmail.nossr50.util.EventUtils;
|
||||
import com.gmail.nossr50.util.Permissions;
|
||||
import com.gmail.nossr50.util.player.UserManager;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
|
||||
public class ProbabilityUtil {
|
||||
public static final @NotNull DecimalFormat percent = new DecimalFormat("##0.00%");
|
||||
public static final double LUCKY_MODIFIER = 1.333D;
|
||||
|
||||
/**
|
||||
* Return a chance of success in "percentage" format, show to the player in UI elements
|
||||
*
|
||||
* @param player target player
|
||||
* @param subSkillType target subskill
|
||||
* @param isLucky whether to apply luck modifiers
|
||||
*
|
||||
* @return "percentage" representation of success
|
||||
*/
|
||||
public static double chanceOfSuccessPercentage(@NotNull Player player,
|
||||
@NotNull SubSkillType subSkillType,
|
||||
boolean isLucky) {
|
||||
Probability probability = getSubSkillProbability(subSkillType, player);
|
||||
//Probability values are on a 0-1 scale and need to be "transformed" into a 1-100 scale
|
||||
double percentageValue = probability.getValue(); //Doesn't need to be scaled
|
||||
|
||||
//Apply lucky modifier
|
||||
if(isLucky) {
|
||||
percentageValue *= LUCKY_MODIFIER;
|
||||
}
|
||||
|
||||
return percentageValue;
|
||||
}
|
||||
|
||||
public static double chanceOfSuccessPercentage(@NotNull Probability probability, boolean isLucky) {
|
||||
//Probability values are on a 0-1 scale and need to be "transformed" into a 1-100 scale
|
||||
double percentageValue = probability.getValue();
|
||||
|
||||
//Apply lucky modifier
|
||||
if(isLucky) {
|
||||
percentageValue *= LUCKY_MODIFIER;
|
||||
}
|
||||
|
||||
return percentageValue;
|
||||
}
|
||||
|
||||
static Probability getStaticRandomChance(@NotNull SubSkillType subSkillType) throws InvalidStaticChance {
|
||||
return switch (subSkillType) {
|
||||
case AXES_ARMOR_IMPACT -> Probability.ofPercent(mcMMO.p.getAdvancedConfig().getImpactChance());
|
||||
case AXES_GREATER_IMPACT -> Probability.ofPercent(mcMMO.p.getAdvancedConfig().getGreaterImpactChance());
|
||||
case TAMING_FAST_FOOD_SERVICE -> Probability.ofPercent(mcMMO.p.getAdvancedConfig().getFastFoodChance());
|
||||
default -> throw new InvalidStaticChance();
|
||||
};
|
||||
}
|
||||
|
||||
static SkillProbabilityType getProbabilityType(@NotNull SubSkillType subSkillType) {
|
||||
SkillProbabilityType skillProbabilityType = SkillProbabilityType.DYNAMIC_CONFIGURABLE;
|
||||
|
||||
if(subSkillType == SubSkillType.TAMING_FAST_FOOD_SERVICE
|
||||
|| subSkillType == SubSkillType.AXES_ARMOR_IMPACT
|
||||
|| subSkillType == SubSkillType.AXES_GREATER_IMPACT)
|
||||
skillProbabilityType = SkillProbabilityType.STATIC_CONFIGURABLE;
|
||||
|
||||
return skillProbabilityType;
|
||||
}
|
||||
|
||||
static @NotNull Probability ofSubSkill(@Nullable Player player,
|
||||
@NotNull SubSkillType subSkillType) {
|
||||
switch (getProbabilityType(subSkillType)) {
|
||||
case DYNAMIC_CONFIGURABLE:
|
||||
double probabilityCeiling;
|
||||
double xCeiling;
|
||||
double xPos;
|
||||
|
||||
if (player != null) {
|
||||
McMMOPlayer mmoPlayer = UserManager.getPlayer(player);
|
||||
if (mmoPlayer == null) {
|
||||
return Probability.ofPercent(0);
|
||||
}
|
||||
xPos = mmoPlayer.getSkillLevel(subSkillType.getParentSkill());
|
||||
} else {
|
||||
xPos = 0;
|
||||
}
|
||||
|
||||
//Probability ceiling is configurable in this type
|
||||
probabilityCeiling = mcMMO.p.getAdvancedConfig().getMaximumProbability(subSkillType);
|
||||
//The xCeiling is configurable in this type
|
||||
xCeiling = mcMMO.p.getAdvancedConfig().getMaxBonusLevel(subSkillType);
|
||||
return new ProbabilityImpl(xPos, xCeiling, probabilityCeiling);
|
||||
case STATIC_CONFIGURABLE:
|
||||
try {
|
||||
return getStaticRandomChance(subSkillType);
|
||||
} catch (InvalidStaticChance invalidStaticChance) {
|
||||
invalidStaticChance.printStackTrace();
|
||||
}
|
||||
default:
|
||||
throw new RuntimeException("No case in switch statement for Skill Probability Type!");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is one of several Skill RNG check methods
|
||||
* This helper method is for specific {@link SubSkillType}, which help mcMMO understand where the RNG values used in our calculations come from this {@link SubSkillType}
|
||||
* <p>
|
||||
* 1) Determine where the RNG values come from for the passed {@link SubSkillType}
|
||||
* NOTE: In the config file, there are values which are static and which are more dynamic, this is currently a bit hardcoded and will need to be updated manually
|
||||
* <p>
|
||||
* 2) Determine whether to use Lucky multiplier and influence the outcome
|
||||
* <p>
|
||||
* 3) Creates a {@link Probability} and pipes it to {@link ProbabilityUtil} which processes the result and returns it
|
||||
* <p>
|
||||
* This also calls a {@link SubSkillEvent} which can be cancelled, if it is cancelled this will return false
|
||||
* The outcome of the probability can also be modified by this event that is called
|
||||
*
|
||||
* @param subSkillType target subskill
|
||||
* @param player target player, can be null (null players are given odds equivalent to a player with no levels or luck)
|
||||
* @return true if the Skill RNG succeeds, false if it fails
|
||||
*/
|
||||
public static boolean isSkillRNGSuccessful(@NotNull SubSkillType subSkillType, @NotNull Player player) {
|
||||
//Process probability
|
||||
Probability probability = getSubSkillProbability(subSkillType, player);
|
||||
|
||||
//Send out event
|
||||
SubSkillEvent subSkillEvent = EventUtils.callSubSkillEvent(player, subSkillType);
|
||||
|
||||
if(subSkillEvent.isCancelled()) {
|
||||
return false; //Event got cancelled so this doesn't succeed
|
||||
}
|
||||
|
||||
//Result modifier
|
||||
double resultModifier = subSkillEvent.getResultModifier();
|
||||
|
||||
//Mutate probability
|
||||
if(resultModifier != 1.0D)
|
||||
probability = Probability.ofPercent(probability.getValue() * resultModifier);
|
||||
|
||||
//Luck
|
||||
boolean isLucky = Permissions.lucky(player, subSkillType.getParentSkill());
|
||||
|
||||
if(isLucky) {
|
||||
return probability.evaluate(LUCKY_MODIFIER);
|
||||
} else {
|
||||
return probability.evaluate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is one of several Skill RNG check methods
|
||||
* This helper method is specific to static value RNG, which can be influenced by a player's Luck
|
||||
*
|
||||
* @param primarySkillType the related primary skill
|
||||
* @param player the target player, can be null (null players have the worst odds)
|
||||
* @param probabilityPercentage the probability of this player succeeding in "percentage" format (0-100 inclusive)
|
||||
* @return true if the RNG succeeds, false if it fails
|
||||
*/
|
||||
public static boolean isStaticSkillRNGSuccessful(@NotNull PrimarySkillType primarySkillType, @Nullable Player player, double probabilityPercentage) {
|
||||
//Grab a probability converted from a "percentage" value
|
||||
Probability probability = Probability.ofPercent(probabilityPercentage);
|
||||
|
||||
return isStaticSkillRNGSuccessful(primarySkillType, player, probability);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is one of several Skill RNG check methods
|
||||
* This helper method is specific to static value RNG, which can be influenced by a player's Luck
|
||||
*
|
||||
* @param primarySkillType the related primary skill
|
||||
* @param player the target player, can be null (null players have the worst odds)
|
||||
* @param probability the probability of this player succeeding
|
||||
* @return true if the RNG succeeds, false if it fails
|
||||
*/
|
||||
public static boolean isStaticSkillRNGSuccessful(@NotNull PrimarySkillType primarySkillType, @Nullable Player player, @NotNull Probability probability) {
|
||||
boolean isLucky = player != null && Permissions.lucky(player, primarySkillType);
|
||||
|
||||
if(isLucky) {
|
||||
return probability.evaluate(LUCKY_MODIFIER);
|
||||
} else {
|
||||
return probability.evaluate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Skills activate without RNG, this allows other plugins to prevent that activation
|
||||
* @param subSkillType target subskill
|
||||
* @param player target player
|
||||
* @return true if the skill succeeds (wasn't cancelled by any other plugin)
|
||||
*/
|
||||
public static boolean isNonRNGSkillActivationSuccessful(@NotNull SubSkillType subSkillType, @NotNull Player player) {
|
||||
return !EventUtils.callSubSkillEvent(player, subSkillType).isCancelled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Grab the {@link Probability} for a specific {@link SubSkillType} for a specific {@link Player}
|
||||
*
|
||||
* @param subSkillType target subskill
|
||||
* @param player target player
|
||||
* @return the Probability of this skill succeeding
|
||||
*/
|
||||
public static @NotNull Probability getSubSkillProbability(@NotNull SubSkillType subSkillType, @Nullable Player player) {
|
||||
return ProbabilityUtil.ofSubSkill(player, subSkillType);
|
||||
}
|
||||
|
||||
public static @NotNull String[] getRNGDisplayValues(@NotNull Player player, @NotNull SubSkillType subSkill) {
|
||||
double firstValue = chanceOfSuccessPercentage(player, subSkill, false);
|
||||
double secondValue = chanceOfSuccessPercentage(player, subSkill, true);
|
||||
|
||||
return new String[]{percent.format(firstValue), percent.format(secondValue)};
|
||||
}
|
||||
|
||||
public static @NotNull String[] getRNGDisplayValues(@NotNull Probability probability) {
|
||||
double firstValue = chanceOfSuccessPercentage(probability, false);
|
||||
double secondValue = chanceOfSuccessPercentage(probability, true);
|
||||
|
||||
return new String[]{percent.format(firstValue), percent.format(secondValue)};
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
package com.gmail.nossr50.util.random;
|
||||
|
||||
public interface RandomChanceExecution {
|
||||
/**
|
||||
* Gets the XPos used in the formula for success
|
||||
*
|
||||
* @return value of x for our success probability graph
|
||||
*/
|
||||
double getXPos();
|
||||
|
||||
/**
|
||||
* The maximum odds for this RandomChanceExecution
|
||||
* For example, if this value is 10, then 10% odds would be the maximum and would be achieved only when xPos equaled the LinearCurvePeak
|
||||
*
|
||||
* @return maximum probability odds from 0.00 (no chance of ever happened) to 100.0 (probability can be guaranteed)
|
||||
*/
|
||||
double getProbabilityCap();
|
||||
}
|
@ -1,176 +0,0 @@
|
||||
package com.gmail.nossr50.util.random;
|
||||
|
||||
import com.gmail.nossr50.datatypes.player.McMMOPlayer;
|
||||
import com.gmail.nossr50.datatypes.skills.SubSkillType;
|
||||
import com.gmail.nossr50.util.Permissions;
|
||||
import com.gmail.nossr50.util.player.UserManager;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class RandomChanceSkill implements RandomChanceExecution {
|
||||
protected final double probabilityCap;
|
||||
protected final boolean isLucky;
|
||||
protected int skillLevel;
|
||||
protected final double resultModifier;
|
||||
protected final double maximumBonusLevelCap;
|
||||
|
||||
public RandomChanceSkill(@Nullable Player player, @NotNull SubSkillType subSkillType, double resultModifier) {
|
||||
this.probabilityCap = RandomChanceUtil.LINEAR_CURVE_VAR;
|
||||
|
||||
final McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player);
|
||||
if (player != null && mcMMOPlayer != null) {
|
||||
this.skillLevel = mcMMOPlayer.getSkillLevel(subSkillType.getParentSkill());
|
||||
} else {
|
||||
this.skillLevel = 0;
|
||||
}
|
||||
|
||||
if (player != null)
|
||||
isLucky = Permissions.lucky(player, subSkillType.getParentSkill());
|
||||
else
|
||||
isLucky = false;
|
||||
|
||||
this.resultModifier = resultModifier;
|
||||
this.maximumBonusLevelCap = RandomChanceUtil.getMaxBonusLevelCap(subSkillType);
|
||||
}
|
||||
|
||||
public RandomChanceSkill(@Nullable Player player, @NotNull SubSkillType subSkillType) {
|
||||
this.probabilityCap = RandomChanceUtil.LINEAR_CURVE_VAR;
|
||||
|
||||
final McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player);
|
||||
if (player != null && mcMMOPlayer != null) {
|
||||
this.skillLevel = mcMMOPlayer.getSkillLevel(subSkillType.getParentSkill());
|
||||
} else {
|
||||
this.skillLevel = 0;
|
||||
}
|
||||
|
||||
if (player != null)
|
||||
isLucky = Permissions.lucky(player, subSkillType.getParentSkill());
|
||||
else
|
||||
isLucky = false;
|
||||
|
||||
this.resultModifier = 1.0D;
|
||||
this.maximumBonusLevelCap = RandomChanceUtil.getMaxBonusLevelCap(subSkillType);
|
||||
}
|
||||
|
||||
public RandomChanceSkill(@Nullable Player player, @NotNull SubSkillType subSkillType, boolean hasCap) {
|
||||
if (hasCap)
|
||||
this.probabilityCap = RandomChanceUtil.getMaximumProbability(subSkillType);
|
||||
else
|
||||
this.probabilityCap = RandomChanceUtil.LINEAR_CURVE_VAR;
|
||||
|
||||
final McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player);
|
||||
if (player != null && mcMMOPlayer != null) {
|
||||
this.skillLevel = mcMMOPlayer.getSkillLevel(subSkillType.getParentSkill());
|
||||
} else {
|
||||
this.skillLevel = 0;
|
||||
}
|
||||
|
||||
if (player != null)
|
||||
isLucky = Permissions.lucky(player, subSkillType.getParentSkill());
|
||||
else
|
||||
isLucky = false;
|
||||
|
||||
this.resultModifier = 1.0D;
|
||||
this.maximumBonusLevelCap = RandomChanceUtil.getMaxBonusLevelCap(subSkillType);
|
||||
}
|
||||
|
||||
public RandomChanceSkill(@Nullable Player player, @NotNull SubSkillType subSkillType, boolean hasCap, boolean luckyOverride) {
|
||||
if (hasCap)
|
||||
this.probabilityCap = RandomChanceUtil.getMaximumProbability(subSkillType);
|
||||
else
|
||||
this.probabilityCap = RandomChanceUtil.LINEAR_CURVE_VAR;
|
||||
|
||||
final McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player);
|
||||
if (player != null && mcMMOPlayer != null) {
|
||||
this.skillLevel = mcMMOPlayer.getSkillLevel(subSkillType.getParentSkill());
|
||||
} else {
|
||||
this.skillLevel = 0;
|
||||
}
|
||||
|
||||
isLucky = luckyOverride;
|
||||
|
||||
this.resultModifier = 1.0D;
|
||||
this.maximumBonusLevelCap = RandomChanceUtil.getMaxBonusLevelCap(subSkillType);
|
||||
}
|
||||
|
||||
public RandomChanceSkill(@Nullable Player player, @NotNull SubSkillType subSkillType, boolean hasCap, double resultModifier) {
|
||||
if (hasCap)
|
||||
this.probabilityCap = RandomChanceUtil.getMaximumProbability(subSkillType);
|
||||
else
|
||||
this.probabilityCap = RandomChanceUtil.LINEAR_CURVE_VAR;
|
||||
|
||||
final McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player);
|
||||
if (player != null && mcMMOPlayer != null) {
|
||||
this.skillLevel = mcMMOPlayer.getSkillLevel(subSkillType.getParentSkill());
|
||||
} else {
|
||||
this.skillLevel = 0;
|
||||
}
|
||||
|
||||
if (player != null)
|
||||
isLucky = Permissions.lucky(player, subSkillType.getParentSkill());
|
||||
else
|
||||
isLucky = false;
|
||||
|
||||
this.resultModifier = resultModifier;
|
||||
this.maximumBonusLevelCap = RandomChanceUtil.getMaxBonusLevelCap(subSkillType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the skill level of the player who owns this RandomChanceSkill
|
||||
*
|
||||
* @return the current skill level relating to this RandomChanceSkill
|
||||
*/
|
||||
public int getSkillLevel() {
|
||||
return skillLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify the skill level used for this skill's RNG calculations
|
||||
*
|
||||
* @param newSkillLevel new skill level
|
||||
*/
|
||||
public void setSkillLevel(int newSkillLevel) {
|
||||
skillLevel = newSkillLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum bonus level for this skill
|
||||
* This is when the skills level no longer increases the odds of success
|
||||
* For example, a value of 25 will mean the success chance no longer grows after skill level 25
|
||||
*
|
||||
* @return the maximum bonus from skill level for this skill
|
||||
*/
|
||||
public double getMaximumBonusLevelCap() {
|
||||
return maximumBonusLevelCap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the XPos used in the formula for success
|
||||
*
|
||||
* @return value of x for our success probability graph
|
||||
*/
|
||||
@Override
|
||||
public double getXPos() {
|
||||
return getSkillLevel();
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum odds for this RandomChanceExecution
|
||||
* For example, if this value is 10, then 10% odds would be the maximum and would be achieved only when xPos equaled the LinearCurvePeak
|
||||
*
|
||||
* @return maximum probability odds from 0.00 (no chance of ever happened) to 100.0 (probability can be guaranteed)
|
||||
*/
|
||||
@Override
|
||||
public double getProbabilityCap() {
|
||||
return probabilityCap;
|
||||
}
|
||||
|
||||
public boolean isLucky() {
|
||||
return isLucky;
|
||||
}
|
||||
|
||||
public double getResultModifier() {
|
||||
return resultModifier;
|
||||
}
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
package com.gmail.nossr50.util.random;
|
||||
|
||||
import com.gmail.nossr50.datatypes.skills.SubSkillType;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class RandomChanceSkillStatic extends RandomChanceSkill {
|
||||
private final double xPos;
|
||||
|
||||
public RandomChanceSkillStatic(double xPos, @Nullable Player player, @NotNull SubSkillType subSkillType) {
|
||||
super(player, subSkillType);
|
||||
|
||||
this.xPos = xPos;
|
||||
}
|
||||
|
||||
public RandomChanceSkillStatic(double xPos, @Nullable Player player, @NotNull SubSkillType subSkillType, boolean luckyOverride) {
|
||||
super(player, subSkillType, false, luckyOverride);
|
||||
|
||||
this.xPos = xPos;
|
||||
}
|
||||
|
||||
public RandomChanceSkillStatic(double xPos, @Nullable Player player, @NotNull SubSkillType subSkillType, double resultModifier) {
|
||||
super(player, subSkillType, resultModifier);
|
||||
|
||||
this.xPos = xPos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the XPos used in the formula for success
|
||||
*
|
||||
* @return value of x for our success probability graph
|
||||
*/
|
||||
@Override
|
||||
public double getXPos() {
|
||||
return xPos;
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum odds for this RandomChanceExecution
|
||||
* For example, if this value is 10, then 10% odds would be the maximum and would be achieved only when xPos equaled the LinearCurvePeak
|
||||
*
|
||||
* @return maximum probability odds from 0.00 (no chance of ever happened) to 100.0 (probability can be guaranteed)
|
||||
*/
|
||||
@Override
|
||||
public double getProbabilityCap() {
|
||||
return probabilityCap;
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum bonus level for this skill
|
||||
* This is when the skills level no longer increases the odds of success
|
||||
* For example, a value of 25 will mean the success chance no longer grows after skill level 25
|
||||
*
|
||||
* @return the maximum bonus from skill level for this skill
|
||||
*/
|
||||
@Override
|
||||
public double getMaximumBonusLevelCap() {
|
||||
return 100;
|
||||
}
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
package com.gmail.nossr50.util.random;
|
||||
|
||||
public class RandomChanceStatic implements RandomChanceExecution {
|
||||
private final double xPos;
|
||||
private final double probabilityCap;
|
||||
private final boolean isLucky;
|
||||
|
||||
public RandomChanceStatic(double xPos, 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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
@ -11,7 +11,6 @@ import com.gmail.nossr50.events.scoreboard.ScoreboardEventReason;
|
||||
import com.gmail.nossr50.events.scoreboard.ScoreboardObjectiveEventReason;
|
||||
import com.gmail.nossr50.locale.LocaleLoader;
|
||||
import com.gmail.nossr50.mcMMO;
|
||||
import com.gmail.nossr50.skills.child.FamilyTree;
|
||||
import com.gmail.nossr50.util.LogUtils;
|
||||
import com.gmail.nossr50.util.Misc;
|
||||
import com.gmail.nossr50.util.player.NotificationManager;
|
||||
@ -495,7 +494,7 @@ public class ScoreboardWrapper {
|
||||
sidebarObjective.getScore(ScoreboardManager.LABEL_REMAINING_XP).setScore(mcMMOPlayer.getXpToLevel(targetSkill) - currentXP);
|
||||
}
|
||||
else {
|
||||
for (PrimarySkillType parentSkill : FamilyTree.getParents(targetSkill)) {
|
||||
for (PrimarySkillType parentSkill : mcMMO.p.getSkillTools().getChildSkillParents(targetSkill)) {
|
||||
sidebarObjective.getScore(ScoreboardManager.skillLabels.get(parentSkill)).setScore(mcMMOPlayer.getSkillLevel(parentSkill));
|
||||
}
|
||||
}
|
||||
|
@ -10,13 +10,13 @@ import com.gmail.nossr50.datatypes.skills.SubSkillType;
|
||||
import com.gmail.nossr50.mcMMO;
|
||||
import com.gmail.nossr50.metadata.MobMetaFlagType;
|
||||
import com.gmail.nossr50.metadata.MobMetadataService;
|
||||
import com.gmail.nossr50.party.PartyManager;
|
||||
import com.gmail.nossr50.runnables.skills.AwardCombatXpTask;
|
||||
import com.gmail.nossr50.skills.acrobatics.AcrobaticsManager;
|
||||
import com.gmail.nossr50.skills.archery.ArcheryManager;
|
||||
import com.gmail.nossr50.skills.axes.AxesManager;
|
||||
import com.gmail.nossr50.skills.swords.SwordsManager;
|
||||
import com.gmail.nossr50.skills.taming.TamingManager;
|
||||
import com.gmail.nossr50.skills.tridents.TridentsManager;
|
||||
import com.gmail.nossr50.skills.unarmed.UnarmedManager;
|
||||
import com.gmail.nossr50.util.*;
|
||||
import com.gmail.nossr50.util.player.NotificationManager;
|
||||
@ -45,7 +45,6 @@ public final class CombatUtils {
|
||||
return mcMMO.getMetadataService().getMobMetadataService();
|
||||
}
|
||||
|
||||
//Likely.. because who knows what plugins are throwing around
|
||||
public static boolean isDamageLikelyFromNormalCombat(@NotNull DamageCause damageCause) {
|
||||
return switch (damageCause) {
|
||||
case ENTITY_ATTACK, ENTITY_SWEEP_ATTACK, PROJECTILE -> true;
|
||||
@ -112,6 +111,78 @@ public final class CombatUtils {
|
||||
}
|
||||
}
|
||||
}
|
||||
private static void processTridentCombat(@NotNull LivingEntity target, @NotNull Player player, @NotNull EntityDamageByEntityEvent event) {
|
||||
if (event.getCause() == DamageCause.THORNS) {
|
||||
return;
|
||||
}
|
||||
|
||||
double boostedDamage = event.getDamage();
|
||||
|
||||
McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player);
|
||||
|
||||
//Make sure the profiles been loaded
|
||||
if(mcMMOPlayer == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
TridentsManager tridentsManager = mcMMOPlayer.getTridentsManager();
|
||||
|
||||
if (tridentsManager.canActivateAbility()) {
|
||||
mcMMOPlayer.checkAbilityActivation(PrimarySkillType.TRIDENTS);
|
||||
}
|
||||
|
||||
if (SkillUtils.canUseSubskill(player, SubSkillType.TRIDENTS_IMPALE)) {
|
||||
boostedDamage += (tridentsManager.impaleDamageBonus() * mcMMOPlayer.getAttackStrength());
|
||||
}
|
||||
|
||||
if(canUseLimitBreak(player, target, SubSkillType.TRIDENTS_TRIDENTS_LIMIT_BREAK)) {
|
||||
boostedDamage += (getLimitBreakDamage(player, target, SubSkillType.TRIDENTS_TRIDENTS_LIMIT_BREAK) * mcMMOPlayer.getAttackStrength());
|
||||
}
|
||||
|
||||
event.setDamage(boostedDamage);
|
||||
processCombatXP(mcMMOPlayer, target, PrimarySkillType.TRIDENTS);
|
||||
|
||||
printFinalDamageDebug(player, event, mcMMOPlayer);
|
||||
}
|
||||
|
||||
private static void processCrossbowsCombat(@NotNull LivingEntity target, @NotNull Player player,
|
||||
@NotNull EntityDamageByEntityEvent event, @NotNull Arrow arrow) {
|
||||
double initialDamage = event.getDamage();
|
||||
|
||||
McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player);
|
||||
|
||||
//Make sure the profiles been loaded
|
||||
if(mcMMOPlayer == null) {
|
||||
delayArrowMetaCleanup(arrow);
|
||||
return;
|
||||
}
|
||||
|
||||
double boostedDamage = event.getDamage();
|
||||
|
||||
if (SkillUtils.canUseSubskill(player, SubSkillType.CROSSBOWS_POWERED_SHOT)) {
|
||||
//Not Additive
|
||||
boostedDamage = mcMMOPlayer.getCrossbowsManager().poweredShot(initialDamage);
|
||||
}
|
||||
|
||||
if(canUseLimitBreak(player, target, SubSkillType.CROSSBOWS_CROSSBOWS_LIMIT_BREAK)) {
|
||||
boostedDamage+=getLimitBreakDamage(player, target, SubSkillType.CROSSBOWS_CROSSBOWS_LIMIT_BREAK);
|
||||
}
|
||||
|
||||
double distanceMultiplier = ArcheryManager.distanceXpBonusMultiplier(target, arrow);
|
||||
double forceMultiplier = 1.0;
|
||||
|
||||
event.setDamage(boostedDamage);
|
||||
processCombatXP(mcMMOPlayer, target, PrimarySkillType.CROSSBOWS, forceMultiplier * distanceMultiplier);
|
||||
|
||||
printFinalDamageDebug(player, event, mcMMOPlayer,
|
||||
"Distance Multiplier: "+distanceMultiplier,
|
||||
"Force Multiplier: "+forceMultiplier,
|
||||
"Initial Damage: "+initialDamage,
|
||||
"Final Damage: "+boostedDamage);
|
||||
|
||||
//Clean data
|
||||
delayArrowMetaCleanup(arrow);
|
||||
}
|
||||
|
||||
private static void processAxeCombat(@NotNull LivingEntity target, @NotNull Player player, @NotNull EntityDamageByEntityEvent event) {
|
||||
if (event.getCause() == DamageCause.THORNS) {
|
||||
@ -240,14 +311,15 @@ public final class CombatUtils {
|
||||
|
||||
}
|
||||
|
||||
private static void processArcheryCombat(@NotNull LivingEntity target, @NotNull Player player, @NotNull EntityDamageByEntityEvent event, @NotNull Projectile arrow) {
|
||||
private static void processArcheryCombat(@NotNull LivingEntity target, @NotNull Player player,
|
||||
@NotNull EntityDamageByEntityEvent event, @NotNull Arrow arrow) {
|
||||
double initialDamage = event.getDamage();
|
||||
|
||||
McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player);
|
||||
|
||||
//Make sure the profiles been loaded
|
||||
if(mcMMOPlayer == null) {
|
||||
cleanupArrowMetadata(arrow);
|
||||
delayArrowMetaCleanup(arrow);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -273,7 +345,7 @@ public final class CombatUtils {
|
||||
boostedDamage+=getLimitBreakDamage(player, target, SubSkillType.ARCHERY_ARCHERY_LIMIT_BREAK);
|
||||
}
|
||||
|
||||
double distanceMultiplier = archeryManager.distanceXpBonusMultiplier(target, arrow);
|
||||
double distanceMultiplier = ArcheryManager.distanceXpBonusMultiplier(target, arrow);
|
||||
double forceMultiplier = 1.0; //Hacky Fix - some plugins spawn arrows and assign them to players after the ProjectileLaunchEvent fires
|
||||
|
||||
if(arrow.hasMetadata(MetadataConstants.METADATA_KEY_BOW_FORCE))
|
||||
@ -288,7 +360,7 @@ public final class CombatUtils {
|
||||
"Initial Damage: "+initialDamage,
|
||||
"Final Damage: "+boostedDamage);
|
||||
//Clean data
|
||||
cleanupArrowMetadata(arrow);
|
||||
delayArrowMetaCleanup(arrow);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -383,6 +455,15 @@ public final class CombatUtils {
|
||||
processUnarmedCombat(target, player, event);
|
||||
}
|
||||
}
|
||||
else if (ItemUtils.isTrident(heldItem)) {
|
||||
if (!mcMMO.p.getSkillTools().canCombatSkillsTrigger(PrimarySkillType.TRIDENTS, target)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mcMMO.p.getSkillTools().doesPlayerHaveSkillPermission(player, PrimarySkillType.TRIDENTS)) {
|
||||
processTridentCombat(target, player, event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else if (entityType == EntityType.WOLF) {
|
||||
@ -396,20 +477,25 @@ public final class CombatUtils {
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (entityType == EntityType.ARROW || entityType == EntityType.SPECTRAL_ARROW) {
|
||||
Projectile arrow = (Projectile) painSource;
|
||||
else if (painSource instanceof Arrow arrow) {
|
||||
ProjectileSource projectileSource = arrow.getShooter();
|
||||
boolean isCrossbow = arrow.isShotFromCrossbow();
|
||||
if (projectileSource instanceof Player player) {
|
||||
|
||||
if (projectileSource instanceof Player player && mcMMO.p.getSkillTools().canCombatSkillsTrigger(PrimarySkillType.ARCHERY, target)) {
|
||||
|
||||
if (!Misc.isNPCEntityExcludingVillagers(player) && mcMMO.p.getSkillTools().doesPlayerHaveSkillPermission(player, PrimarySkillType.ARCHERY)) {
|
||||
processArcheryCombat(target, player, event, arrow);
|
||||
if (!Misc.isNPCEntityExcludingVillagers(player)) {
|
||||
if(!isCrossbow && mcMMO.p.getSkillTools().canCombatSkillsTrigger(PrimarySkillType.ARCHERY, target)) {
|
||||
processArcheryCombat(target, player, event, arrow);
|
||||
} else if(isCrossbow && mcMMO.p.getSkillTools().canCombatSkillsTrigger(PrimarySkillType.CROSSBOWS, target)) {
|
||||
processCrossbowsCombat(target, player, event, arrow);
|
||||
}
|
||||
} else {
|
||||
//Cleanup Arrow
|
||||
cleanupArrowMetadata(arrow);
|
||||
delayArrowMetaCleanup(arrow);
|
||||
}
|
||||
|
||||
if (target.getType() != EntityType.CREEPER && !Misc.isNPCEntityExcludingVillagers(player) && mcMMO.p.getSkillTools().doesPlayerHaveSkillPermission(player, PrimarySkillType.TAMING)) {
|
||||
if (target.getType() != EntityType.CREEPER
|
||||
&& !Misc.isNPCEntityExcludingVillagers(player)
|
||||
&& mcMMO.p.getSkillTools().doesPlayerHaveSkillPermission(player, PrimarySkillType.TAMING)) {
|
||||
McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player);
|
||||
|
||||
if(mcMMOPlayer == null)
|
||||
@ -420,7 +506,6 @@ public final class CombatUtils {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -621,7 +706,7 @@ public final class CombatUtils {
|
||||
}
|
||||
|
||||
public static boolean hasIgnoreDamageMetadata(@NotNull LivingEntity target) {
|
||||
return target.getMetadata(MetadataConstants.METADATA_KEY_CUSTOM_DAMAGE).size() != 0;
|
||||
return target.hasMetadata(MetadataConstants.METADATA_KEY_CUSTOM_DAMAGE);
|
||||
}
|
||||
|
||||
public static void dealNoInvulnerabilityTickDamageRupture(@NotNull LivingEntity target, double damage, Entity attacker, int toolTier) {
|
||||
@ -630,35 +715,6 @@ public final class CombatUtils {
|
||||
}
|
||||
|
||||
dealNoInvulnerabilityTickDamage(target, damage, attacker);
|
||||
|
||||
// //IFrame storage
|
||||
//// int noDamageTicks = target.getNoDamageTicks();
|
||||
//
|
||||
//// String debug = "BLEED DMG RESULT: INC DMG:"+damage+", HP-Before:"+target.getHealth()+", HP-After:";
|
||||
//
|
||||
//// double incDmg = getFakeDamageFinalResult(attacker, target, DamageCause.ENTITY_ATTACK, damage);
|
||||
//
|
||||
//// double newHealth = Math.max(0, target.getHealth() - incDmg);
|
||||
//
|
||||
// //Don't kill things with a stone or wooden weapon
|
||||
//// if(toolTier < 3 && newHealth == 0)
|
||||
//// return;
|
||||
//
|
||||
// target.setMetadata(mcMMO.CUSTOM_DAMAGE_METAKEY, mcMMO.metadataValue);
|
||||
//
|
||||
// if(newHealth == 0 && !(target instanceof Player))
|
||||
// {
|
||||
// target.damage(99999, attacker);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
//// Vector beforeRuptureVec = new Vector(target.getVelocity().getX(), target.getVelocity().getY(), target.getVelocity().getZ()); ;
|
||||
// target.damage(damage, attacker);
|
||||
//// debug+=target.getHealth();
|
||||
// Bukkit.broadcastMessage(debug);
|
||||
//// target.setNoDamageTicks(noDamageTicks); //Do not add additional IFrames
|
||||
//// target.setVelocity(beforeRuptureVec);
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
@ -741,14 +797,16 @@ public final class CombatUtils {
|
||||
XPGainReason xpGainReason;
|
||||
|
||||
if (target instanceof Player defender) {
|
||||
if (!ExperienceConfig.getInstance().getExperienceGainsPlayerVersusPlayerEnabled() ||
|
||||
if (!ExperienceConfig.getInstance().getExperienceGainsPlayerVersusPlayerEnabled()
|
||||
||
|
||||
(mcMMO.p.getPartyConfig().isPartyEnabled() && mcMMO.p.getPartyManager().inSameParty(mcMMOPlayer.getPlayer(), (Player) target))) {
|
||||
return;
|
||||
}
|
||||
|
||||
xpGainReason = XPGainReason.PVP;
|
||||
|
||||
if (defender.isOnline() && SkillUtils.cooldownExpired(mcMMOPlayer.getRespawnATS(), Misc.PLAYER_RESPAWN_COOLDOWN_SECONDS)) {
|
||||
if (defender.isOnline()
|
||||
&& SkillUtils.cooldownExpired(mcMMOPlayer.getRespawnATS(), Misc.PLAYER_RESPAWN_COOLDOWN_SECONDS)) {
|
||||
baseXP = 20 * ExperienceConfig.getInstance().getPlayerVersusPlayerXP();
|
||||
}
|
||||
}
|
||||
@ -807,7 +865,7 @@ public final class CombatUtils {
|
||||
|
||||
baseXP *= multiplier;
|
||||
|
||||
if (baseXP != 0) {
|
||||
if (baseXP > 0) {
|
||||
mcMMO.p.getFoliaLib().getImpl().runAtEntity(mcMMOPlayer.getPlayer(), new AwardCombatXpTask(mcMMOPlayer, primarySkillType, baseXP, target, xpGainReason));
|
||||
}
|
||||
}
|
||||
@ -945,31 +1003,12 @@ public final class CombatUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up metadata from a projectile
|
||||
*
|
||||
* @param entity projectile
|
||||
*/
|
||||
public static void cleanupArrowMetadata(@NotNull Projectile entity) {
|
||||
if(entity.hasMetadata(MetadataConstants.METADATA_KEY_INF_ARROW)) {
|
||||
entity.removeMetadata(MetadataConstants.METADATA_KEY_INF_ARROW, mcMMO.p);
|
||||
}
|
||||
|
||||
if(entity.hasMetadata(MetadataConstants.METADATA_KEY_BOW_FORCE)) {
|
||||
entity.removeMetadata(MetadataConstants.METADATA_KEY_BOW_FORCE, mcMMO.p);
|
||||
}
|
||||
|
||||
if(entity.hasMetadata(MetadataConstants.METADATA_KEY_ARROW_DISTANCE)) {
|
||||
entity.removeMetadata(MetadataConstants.METADATA_KEY_ARROW_DISTANCE, mcMMO.p);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up metadata from a projectile after a minute has passed
|
||||
*
|
||||
* @param entity the projectile
|
||||
* @param arrow the projectile
|
||||
*/
|
||||
public static void delayArrowMetaCleanup(@NotNull Projectile entity) {
|
||||
mcMMO.p.getFoliaLib().getImpl().runLater(() -> cleanupArrowMetadata(entity), 20*60);
|
||||
public static void delayArrowMetaCleanup(@NotNull Arrow arrow) {
|
||||
mcMMO.p.getFoliaLib().getImpl().runLater(() -> ProjectileUtils.cleanupProjectileMetadata(arrow), 20*120);
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
@ -24,7 +24,7 @@ public class RankUtils {
|
||||
*
|
||||
* @param plugin plugin instance ref
|
||||
* @param mcMMOPlayer target player
|
||||
* @param primarySkillType
|
||||
* @param primarySkillType the skill to check
|
||||
* @param newLevel the new level of this skill
|
||||
*/
|
||||
public static void executeSkillUnlockNotifications(Plugin plugin, McMMOPlayer mcMMOPlayer, PrimarySkillType primarySkillType, int newLevel)
|
||||
@ -55,6 +55,9 @@ public class RankUtils {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the interval between skill unlock notifications
|
||||
*/
|
||||
public static void resetUnlockDelayTimer()
|
||||
{
|
||||
count = 0;
|
||||
|
@ -30,6 +30,8 @@ public class SkillTools {
|
||||
public final @NotNull ImmutableSet<String> EXACT_SUBSKILL_NAMES;
|
||||
public final @NotNull ImmutableList<PrimarySkillType> CHILD_SKILLS;
|
||||
public final static @NotNull ImmutableList<PrimarySkillType> NON_CHILD_SKILLS;
|
||||
public final static @NotNull ImmutableList<PrimarySkillType> SALVAGE_PARENTS;
|
||||
public final static @NotNull ImmutableList<PrimarySkillType> SMELTING_PARENTS;
|
||||
public final @NotNull ImmutableList<PrimarySkillType> COMBAT_SKILLS;
|
||||
public final @NotNull ImmutableList<PrimarySkillType> GATHERING_SKILLS;
|
||||
public final @NotNull ImmutableList<PrimarySkillType> MISC_SKILLS;
|
||||
@ -50,9 +52,11 @@ public class SkillTools {
|
||||
}
|
||||
|
||||
NON_CHILD_SKILLS = ImmutableList.copyOf(tempNonChildSkills);
|
||||
SALVAGE_PARENTS = ImmutableList.of(PrimarySkillType.REPAIR, PrimarySkillType.FISHING);
|
||||
SMELTING_PARENTS = ImmutableList.of(PrimarySkillType.MINING, PrimarySkillType.REPAIR);
|
||||
}
|
||||
|
||||
public SkillTools(@NotNull mcMMO pluginRef) {
|
||||
public SkillTools(@NotNull mcMMO pluginRef) throws InvalidSkillException {
|
||||
this.pluginRef = pluginRef;
|
||||
|
||||
/*
|
||||
@ -140,26 +144,38 @@ public class SkillTools {
|
||||
*/
|
||||
|
||||
List<PrimarySkillType> childSkills = new ArrayList<>();
|
||||
// List<PrimarySkillType> nonChildSkills = new ArrayList<>();
|
||||
|
||||
for (PrimarySkillType primarySkillType : PrimarySkillType.values()) {
|
||||
if (isChildSkill(primarySkillType))
|
||||
childSkills.add(primarySkillType);
|
||||
// } {
|
||||
// nonChildSkills.add(primarySkillType);
|
||||
// }
|
||||
}
|
||||
|
||||
CHILD_SKILLS = ImmutableList.copyOf(childSkills);
|
||||
// NON_CHILD_SKILLS = ImmutableList.copyOf(nonChildSkills);
|
||||
|
||||
/*
|
||||
* Build categorized skill lists
|
||||
*/
|
||||
|
||||
COMBAT_SKILLS = ImmutableList.of(PrimarySkillType.ARCHERY, PrimarySkillType.AXES, PrimarySkillType.SWORDS, PrimarySkillType.TAMING, PrimarySkillType.UNARMED);
|
||||
GATHERING_SKILLS = ImmutableList.of(PrimarySkillType.EXCAVATION, PrimarySkillType.FISHING, PrimarySkillType.HERBALISM, PrimarySkillType.MINING, PrimarySkillType.WOODCUTTING);
|
||||
MISC_SKILLS = ImmutableList.of(PrimarySkillType.ACROBATICS, PrimarySkillType.ALCHEMY, PrimarySkillType.REPAIR, PrimarySkillType.SALVAGE, PrimarySkillType.SMELTING);
|
||||
COMBAT_SKILLS = ImmutableList.of(
|
||||
PrimarySkillType.ARCHERY,
|
||||
PrimarySkillType.AXES,
|
||||
PrimarySkillType.CROSSBOWS,
|
||||
PrimarySkillType.SWORDS,
|
||||
PrimarySkillType.TAMING,
|
||||
PrimarySkillType.TRIDENTS,
|
||||
PrimarySkillType.UNARMED);
|
||||
GATHERING_SKILLS = ImmutableList.of(
|
||||
PrimarySkillType.EXCAVATION,
|
||||
PrimarySkillType.FISHING,
|
||||
PrimarySkillType.HERBALISM,
|
||||
PrimarySkillType.MINING,
|
||||
PrimarySkillType.WOODCUTTING);
|
||||
MISC_SKILLS = ImmutableList.of(
|
||||
PrimarySkillType.ACROBATICS,
|
||||
PrimarySkillType.ALCHEMY,
|
||||
PrimarySkillType.REPAIR,
|
||||
PrimarySkillType.SALVAGE,
|
||||
PrimarySkillType.SMELTING);
|
||||
|
||||
/*
|
||||
* Build formatted/localized/etc string lists
|
||||
@ -171,25 +187,18 @@ public class SkillTools {
|
||||
}
|
||||
|
||||
private @NotNull PrimarySkillType getSuperAbilityParent(SuperAbilityType superAbilityType) throws InvalidSkillException {
|
||||
switch(superAbilityType) {
|
||||
case BERSERK:
|
||||
return PrimarySkillType.UNARMED;
|
||||
case GREEN_TERRA:
|
||||
return PrimarySkillType.HERBALISM;
|
||||
case TREE_FELLER:
|
||||
return PrimarySkillType.WOODCUTTING;
|
||||
case SUPER_BREAKER:
|
||||
case BLAST_MINING:
|
||||
return PrimarySkillType.MINING;
|
||||
case SKULL_SPLITTER:
|
||||
return PrimarySkillType.AXES;
|
||||
case SERRATED_STRIKES:
|
||||
return PrimarySkillType.SWORDS;
|
||||
case GIGA_DRILL_BREAKER:
|
||||
return PrimarySkillType.EXCAVATION;
|
||||
default:
|
||||
throw new InvalidSkillException("No parent defined for super ability! "+superAbilityType.toString());
|
||||
}
|
||||
return switch (superAbilityType) {
|
||||
case BERSERK -> PrimarySkillType.UNARMED;
|
||||
case GREEN_TERRA -> PrimarySkillType.HERBALISM;
|
||||
case TREE_FELLER -> PrimarySkillType.WOODCUTTING;
|
||||
case SUPER_BREAKER, BLAST_MINING -> PrimarySkillType.MINING;
|
||||
case SKULL_SPLITTER -> PrimarySkillType.AXES;
|
||||
case SERRATED_STRIKES -> PrimarySkillType.SWORDS;
|
||||
case GIGA_DRILL_BREAKER -> PrimarySkillType.EXCAVATION;
|
||||
case SUPER_SHOTGUN -> PrimarySkillType.CROSSBOWS;
|
||||
case TRIDENTS_SUPER_ABILITY -> PrimarySkillType.TRIDENTS;
|
||||
case EXPLOSIVE_SHOT -> PrimarySkillType.ARCHERY;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -319,7 +328,6 @@ public class SkillTools {
|
||||
}
|
||||
|
||||
public Set<SubSkillType> getSubSkills(PrimarySkillType primarySkillType) {
|
||||
//TODO: Cache this!
|
||||
return primarySkillChildrenMap.get(primarySkillType);
|
||||
}
|
||||
|
||||
@ -329,14 +337,10 @@ public class SkillTools {
|
||||
|
||||
// TODO: This is a little "hacky", we probably need to add something to distinguish child skills in the enum, or to use another enum for them
|
||||
public static boolean isChildSkill(PrimarySkillType primarySkillType) {
|
||||
switch (primarySkillType) {
|
||||
case SALVAGE:
|
||||
case SMELTING:
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return switch (primarySkillType) {
|
||||
case SALVAGE, SMELTING -> true;
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -401,34 +405,7 @@ public class SkillTools {
|
||||
* @return true if the player has permissions, false otherwise
|
||||
*/
|
||||
public boolean superAbilityPermissionCheck(SuperAbilityType superAbilityType, Player player) {
|
||||
switch (superAbilityType) {
|
||||
case BERSERK:
|
||||
return Permissions.berserk(player);
|
||||
|
||||
case BLAST_MINING:
|
||||
return Permissions.remoteDetonation(player);
|
||||
|
||||
case GIGA_DRILL_BREAKER:
|
||||
return Permissions.gigaDrillBreaker(player);
|
||||
|
||||
case GREEN_TERRA:
|
||||
return Permissions.greenTerra(player);
|
||||
|
||||
case SERRATED_STRIKES:
|
||||
return Permissions.serratedStrikes(player);
|
||||
|
||||
case SKULL_SPLITTER:
|
||||
return Permissions.skullSplitter(player);
|
||||
|
||||
case SUPER_BREAKER:
|
||||
return Permissions.superBreaker(player);
|
||||
|
||||
case TREE_FELLER:
|
||||
return Permissions.treeFeller(player);
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return superAbilityType.getPermissions(player);
|
||||
}
|
||||
|
||||
public @NotNull List<PrimarySkillType> getChildSkills() {
|
||||
@ -450,4 +427,17 @@ public class SkillTools {
|
||||
public @NotNull ImmutableList<PrimarySkillType> getMiscSkills() {
|
||||
return MISC_SKILLS;
|
||||
}
|
||||
|
||||
public @NotNull ImmutableList<PrimarySkillType> getChildSkillParents(PrimarySkillType childSkill)
|
||||
throws IllegalArgumentException {
|
||||
switch (childSkill) {
|
||||
case SALVAGE -> {
|
||||
return SALVAGE_PARENTS;
|
||||
}
|
||||
case SMELTING -> {
|
||||
return SMELTING_PARENTS;
|
||||
}
|
||||
default -> throw new IllegalArgumentException("Skill " + childSkill + " is not a child skill");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ import com.gmail.nossr50.mcMMO;
|
||||
import com.gmail.nossr50.metadata.ItemMetadataService;
|
||||
import com.gmail.nossr50.util.ItemUtils;
|
||||
import com.gmail.nossr50.util.Misc;
|
||||
import com.gmail.nossr50.util.Permissions;
|
||||
import com.gmail.nossr50.util.player.NotificationManager;
|
||||
import com.gmail.nossr50.util.player.UserManager;
|
||||
import com.gmail.nossr50.util.text.StringUtils;
|
||||
@ -33,6 +34,8 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
public final class SkillUtils {
|
||||
/**
|
||||
* This is a static utility class, therefore we don't want any instances of
|
||||
@ -264,8 +267,8 @@ public final class SkillUtils {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Modify the durability of an ItemStack, using Armor specific formula for unbreaking enchant damage reduction
|
||||
*
|
||||
@ -352,4 +355,14 @@ public final class SkillUtils {
|
||||
|
||||
return quantity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a player can use a skill
|
||||
* @param player target player
|
||||
* @param subSkillType target subskill
|
||||
* @return true if the player has permission and has the skill unlocked
|
||||
*/
|
||||
public static boolean canUseSubskill(Player player, @NotNull SubSkillType subSkillType) {
|
||||
return Permissions.isSubSkillEnabled(player, subSkillType) && RankUtils.hasUnlockedSubskill(player, subSkillType);
|
||||
}
|
||||
}
|
||||
|
@ -133,38 +133,37 @@ public class TextComponentFactory {
|
||||
TextComponent.Builder webTextComponent;
|
||||
|
||||
switch (webLinks) {
|
||||
case WEBSITE:
|
||||
case WEBSITE -> {
|
||||
webTextComponent = Component.text().content(LocaleLoader.getString("JSON.Hover.AtSymbolURL"));
|
||||
TextUtils.addChildWebComponent(webTextComponent, "Web");
|
||||
webTextComponent.clickEvent(getUrlClickEvent(McMMOUrl.urlWebsite));
|
||||
break;
|
||||
case SPIGOT:
|
||||
}
|
||||
case SPIGOT -> {
|
||||
webTextComponent = Component.text().content(LocaleLoader.getString("JSON.Hover.AtSymbolURL"));
|
||||
TextUtils.addChildWebComponent(webTextComponent, "Spigot");
|
||||
webTextComponent.clickEvent(getUrlClickEvent(McMMOUrl.urlSpigot));
|
||||
break;
|
||||
case DISCORD:
|
||||
}
|
||||
case DISCORD -> {
|
||||
webTextComponent = Component.text().content(LocaleLoader.getString("JSON.Hover.AtSymbolURL"));
|
||||
TextUtils.addChildWebComponent(webTextComponent, "Discord");
|
||||
webTextComponent.clickEvent(getUrlClickEvent(McMMOUrl.urlDiscord));
|
||||
break;
|
||||
case PATREON:
|
||||
}
|
||||
case PATREON -> {
|
||||
webTextComponent = Component.text().content(LocaleLoader.getString("JSON.Hover.AtSymbolURL"));
|
||||
TextUtils.addChildWebComponent(webTextComponent, "Patreon");
|
||||
webTextComponent.clickEvent(getUrlClickEvent(McMMOUrl.urlPatreon));
|
||||
break;
|
||||
case WIKI:
|
||||
}
|
||||
case WIKI -> {
|
||||
webTextComponent = Component.text().content(LocaleLoader.getString("JSON.Hover.AtSymbolURL"));
|
||||
TextUtils.addChildWebComponent(webTextComponent, "Wiki");
|
||||
webTextComponent.clickEvent(getUrlClickEvent(McMMOUrl.urlWiki));
|
||||
break;
|
||||
case HELP_TRANSLATE:
|
||||
}
|
||||
case HELP_TRANSLATE -> {
|
||||
webTextComponent = Component.text().content(LocaleLoader.getString("JSON.Hover.AtSymbolURL"));
|
||||
TextUtils.addChildWebComponent(webTextComponent, "Lang");
|
||||
webTextComponent.clickEvent(getUrlClickEvent(McMMOUrl.urlTranslate));
|
||||
break;
|
||||
default:
|
||||
webTextComponent = Component.text().content("NOT DEFINED");
|
||||
}
|
||||
default -> webTextComponent = Component.text().content("NOT DEFINED");
|
||||
}
|
||||
|
||||
TextUtils.addNewHoverComponentToTextComponent(webTextComponent, getUrlHoverEvent(webLinks));
|
||||
@ -177,44 +176,45 @@ public class TextComponentFactory {
|
||||
TextComponent.Builder componentBuilder = Component.text().content(webLinks.getNiceTitle());
|
||||
|
||||
switch (webLinks) {
|
||||
case WEBSITE:
|
||||
case WEBSITE -> {
|
||||
addUrlHeaderHover(webLinks, componentBuilder);
|
||||
componentBuilder.append(Component.newline()).append(Component.newline());
|
||||
componentBuilder.append(Component.text(webLinks.getLocaleDescription(), NamedTextColor.GREEN));
|
||||
componentBuilder.append(Component.text("\nDev Blogs, and information related to mcMMO can be found here", NamedTextColor.GRAY));
|
||||
break;
|
||||
case SPIGOT:
|
||||
}
|
||||
case SPIGOT -> {
|
||||
addUrlHeaderHover(webLinks, componentBuilder);
|
||||
componentBuilder.append(Component.newline()).append(Component.newline());
|
||||
componentBuilder.append(Component.text(webLinks.getLocaleDescription(), NamedTextColor.GREEN));
|
||||
componentBuilder.append(Component.text("\nI post regularly in the discussion thread here!", NamedTextColor.GRAY));
|
||||
break;
|
||||
case PATREON:
|
||||
}
|
||||
case PATREON -> {
|
||||
addUrlHeaderHover(webLinks, componentBuilder);
|
||||
componentBuilder.append(Component.newline()).append(Component.newline());
|
||||
componentBuilder.append(Component.text(webLinks.getLocaleDescription(), NamedTextColor.GREEN));
|
||||
componentBuilder.append(Component.newline());
|
||||
componentBuilder.append(Component.text("Show support by buying me a coffee :)", NamedTextColor.GRAY));
|
||||
break;
|
||||
case WIKI:
|
||||
}
|
||||
case WIKI -> {
|
||||
addUrlHeaderHover(webLinks, componentBuilder);
|
||||
componentBuilder.append(Component.newline()).append(Component.newline());
|
||||
componentBuilder.append(Component.text(webLinks.getLocaleDescription(), NamedTextColor.GREEN));
|
||||
componentBuilder.append(Component.newline());
|
||||
componentBuilder.append(Component.text("I'm looking for more wiki staff, contact me on our discord!", NamedTextColor.DARK_GRAY));
|
||||
break;
|
||||
case DISCORD:
|
||||
}
|
||||
case DISCORD -> {
|
||||
addUrlHeaderHover(webLinks, componentBuilder);
|
||||
componentBuilder.append(Component.newline()).append(Component.newline());
|
||||
componentBuilder.append(Component.text(webLinks.getLocaleDescription(), NamedTextColor.GREEN));
|
||||
break;
|
||||
case HELP_TRANSLATE:
|
||||
}
|
||||
case HELP_TRANSLATE -> {
|
||||
addUrlHeaderHover(webLinks, componentBuilder);
|
||||
componentBuilder.append(Component.newline()).append(Component.newline());
|
||||
componentBuilder.append(Component.text(webLinks.getLocaleDescription(), NamedTextColor.GREEN));
|
||||
componentBuilder.append(Component.newline());
|
||||
componentBuilder.append(Component.text("You can use this website to help translate mcMMO into your language!" +
|
||||
"\nIf you want to know more contact me in discord.", NamedTextColor.DARK_GRAY));
|
||||
}
|
||||
}
|
||||
|
||||
return componentBuilder.build();
|
||||
@ -474,7 +474,7 @@ public class TextComponentFactory {
|
||||
/* NEW SKILL SYSTEM */
|
||||
for (AbstractSubSkill abstractSubSkill : InteractionManager.getSubSkillList()) {
|
||||
if (abstractSubSkill.getPrimarySkill() == parentSkill) {
|
||||
if (Permissions.isSubSkillEnabled(player, abstractSubSkill))
|
||||
if (Permissions.isSubSkillEnabled(player, abstractSubSkill.getSubSkillType()))
|
||||
textComponents.add(TextComponentFactory.getSubSkillTextComponent(player, abstractSubSkill));
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user