فهرست منبع

4 days of work and I'm still not done ;_;

nossr50 6 سال پیش
والد
کامیت
362d036b16
95فایلهای تغییر یافته به همراه2779 افزوده شده و 906 حذف شده
  1. 5 0
      Changelog.txt
  2. 0 3
      crowdin.yml
  3. 10 29
      src/main/java/com/gmail/nossr50/commands/skills/AcrobaticsCommand.java
  4. 5 5
      src/main/java/com/gmail/nossr50/commands/skills/AlchemyCommand.java
  5. 8 8
      src/main/java/com/gmail/nossr50/commands/skills/ArcheryCommand.java
  6. 8 8
      src/main/java/com/gmail/nossr50/commands/skills/AxesCommand.java
  7. 4 4
      src/main/java/com/gmail/nossr50/commands/skills/ExcavationCommand.java
  8. 9 9
      src/main/java/com/gmail/nossr50/commands/skills/FishingCommand.java
  9. 11 11
      src/main/java/com/gmail/nossr50/commands/skills/HerbalismCommand.java
  10. 23 0
      src/main/java/com/gmail/nossr50/commands/skills/McMMOWebLinks.java
  11. 5 5
      src/main/java/com/gmail/nossr50/commands/skills/MiningCommand.java
  12. 7 7
      src/main/java/com/gmail/nossr50/commands/skills/RepairCommand.java
  13. 5 5
      src/main/java/com/gmail/nossr50/commands/skills/SalvageCommand.java
  14. 54 32
      src/main/java/com/gmail/nossr50/commands/skills/SkillCommand.java
  15. 7 7
      src/main/java/com/gmail/nossr50/commands/skills/SmeltingCommand.java
  16. 8 8
      src/main/java/com/gmail/nossr50/commands/skills/SwordsCommand.java
  17. 12 12
      src/main/java/com/gmail/nossr50/commands/skills/TamingCommand.java
  18. 10 10
      src/main/java/com/gmail/nossr50/commands/skills/UnarmedCommand.java
  19. 10 10
      src/main/java/com/gmail/nossr50/commands/skills/WoodcuttingCommand.java
  20. 211 63
      src/main/java/com/gmail/nossr50/config/AdvancedConfig.java
  21. 59 0
      src/main/java/com/gmail/nossr50/config/CoreSkillsConfig.java
  22. 13 0
      src/main/java/com/gmail/nossr50/datatypes/interactions/NotificationType.java
  23. 10 0
      src/main/java/com/gmail/nossr50/datatypes/json/CustomBaseComponent.java
  24. 33 0
      src/main/java/com/gmail/nossr50/datatypes/json/McMMOUrl.java
  25. 24 24
      src/main/java/com/gmail/nossr50/datatypes/skills/PrimarySkill.java
  26. 12 5
      src/main/java/com/gmail/nossr50/datatypes/skills/SubSkillFlags.java
  27. 16 37
      src/main/java/com/gmail/nossr50/datatypes/skills/SubSkillType.java
  28. 11 0
      src/main/java/com/gmail/nossr50/datatypes/skills/interfaces/ChildSkill.java
  29. 17 0
      src/main/java/com/gmail/nossr50/datatypes/skills/interfaces/CoreSkill.java
  30. 19 0
      src/main/java/com/gmail/nossr50/datatypes/skills/interfaces/Localized.java
  31. 17 0
      src/main/java/com/gmail/nossr50/datatypes/skills/interfaces/Skill.java
  32. 20 0
      src/main/java/com/gmail/nossr50/datatypes/skills/interfaces/Toolable.java
  33. 17 0
      src/main/java/com/gmail/nossr50/datatypes/skills/progression/Progression.java
  34. 43 0
      src/main/java/com/gmail/nossr50/datatypes/skills/subskills/AbstractSubSkill.java
  35. 116 0
      src/main/java/com/gmail/nossr50/datatypes/skills/subskills/acrobatics/AcrobaticsSubSkill.java
  36. 322 0
      src/main/java/com/gmail/nossr50/datatypes/skills/subskills/acrobatics/Roll.java
  37. 14 0
      src/main/java/com/gmail/nossr50/datatypes/skills/subskills/interfaces/InteractType.java
  38. 27 0
      src/main/java/com/gmail/nossr50/datatypes/skills/subskills/interfaces/Interaction.java
  39. 17 0
      src/main/java/com/gmail/nossr50/datatypes/skills/subskills/interfaces/RandomChance.java
  40. 21 0
      src/main/java/com/gmail/nossr50/datatypes/skills/subskills/interfaces/Rank.java
  41. 58 0
      src/main/java/com/gmail/nossr50/datatypes/skills/subskills/interfaces/SubSkill.java
  42. 9 0
      src/main/java/com/gmail/nossr50/datatypes/skills/subskills/interfaces/SubSkillProperties.java
  43. 79 0
      src/main/java/com/gmail/nossr50/events/skills/McMMOPlayerNotificationEvent.java
  44. 23 11
      src/main/java/com/gmail/nossr50/events/skills/secondaryabilities/SubSkillEvent.java
  45. 9 2
      src/main/java/com/gmail/nossr50/events/skills/secondaryabilities/SubSkillWeightedActivationCheckEvent.java
  46. 23 27
      src/main/java/com/gmail/nossr50/listeners/EntityListener.java
  47. 113 0
      src/main/java/com/gmail/nossr50/listeners/InteractionManager.java
  48. 4 4
      src/main/java/com/gmail/nossr50/listeners/InventoryListener.java
  49. 27 1
      src/main/java/com/gmail/nossr50/mcMMO.java
  50. 2 2
      src/main/java/com/gmail/nossr50/runnables/skills/AlchemyBrewTask.java
  51. 1 5
      src/main/java/com/gmail/nossr50/skills/acrobatics/Acrobatics.java
  52. 6 101
      src/main/java/com/gmail/nossr50/skills/acrobatics/AcrobaticsManager.java
  53. 2 2
      src/main/java/com/gmail/nossr50/skills/alchemy/AlchemyPotionBrewer.java
  54. 2 2
      src/main/java/com/gmail/nossr50/skills/archery/Archery.java
  55. 8 8
      src/main/java/com/gmail/nossr50/skills/archery/ArcheryManager.java
  56. 2 3
      src/main/java/com/gmail/nossr50/skills/axes/Axes.java
  57. 9 9
      src/main/java/com/gmail/nossr50/skills/axes/AxesManager.java
  58. 2 2
      src/main/java/com/gmail/nossr50/skills/excavation/ExcavationManager.java
  59. 7 7
      src/main/java/com/gmail/nossr50/skills/fishing/FishingManager.java
  60. 10 10
      src/main/java/com/gmail/nossr50/skills/herbalism/HerbalismManager.java
  61. 4 4
      src/main/java/com/gmail/nossr50/skills/mining/MiningManager.java
  62. 6 6
      src/main/java/com/gmail/nossr50/skills/repair/RepairManager.java
  63. 5 5
      src/main/java/com/gmail/nossr50/skills/smelting/SmeltingManager.java
  64. 7 7
      src/main/java/com/gmail/nossr50/skills/swords/SwordsManager.java
  65. 13 13
      src/main/java/com/gmail/nossr50/skills/taming/TamingManager.java
  66. 11 11
      src/main/java/com/gmail/nossr50/skills/unarmed/UnarmedManager.java
  67. 4 4
      src/main/java/com/gmail/nossr50/skills/woodcutting/WoodcuttingManager.java
  68. 125 7
      src/main/java/com/gmail/nossr50/util/EventUtils.java
  69. 4 3
      src/main/java/com/gmail/nossr50/util/Permissions.java
  70. 0 200
      src/main/java/com/gmail/nossr50/util/SkillTextComponentFactory.java
  71. 0 1
      src/main/java/com/gmail/nossr50/util/StringUtils.java
  72. 531 0
      src/main/java/com/gmail/nossr50/util/TextComponentFactory.java
  73. 0 30
      src/main/java/com/gmail/nossr50/util/player/FeedbackManager.java
  74. 0 7
      src/main/java/com/gmail/nossr50/util/player/VisualFeedbackType.java
  75. 111 37
      src/main/java/com/gmail/nossr50/util/skills/RankUtils.java
  76. 1 1
      src/main/java/com/gmail/nossr50/util/skills/SkillActivationType.java
  77. 139 20
      src/main/java/com/gmail/nossr50/util/skills/SkillUtils.java
  78. 96 0
      src/main/resources/advanced.yml
  79. 9 0
      src/main/resources/coreskills.yml
  80. 3 3
      src/main/resources/locale/locale_cs_CZ.properties
  81. 3 3
      src/main/resources/locale/locale_cy.properties
  82. 3 3
      src/main/resources/locale/locale_da.properties
  83. 4 4
      src/main/resources/locale/locale_de.properties
  84. 35 7
      src/main/resources/locale/locale_en_US.properties
  85. 3 3
      src/main/resources/locale/locale_es.properties
  86. 3 3
      src/main/resources/locale/locale_fr.properties
  87. 3 3
      src/main/resources/locale/locale_it.properties
  88. 3 3
      src/main/resources/locale/locale_ko.properties
  89. 2 2
      src/main/resources/locale/locale_nl.properties
  90. 3 3
      src/main/resources/locale/locale_pl.properties
  91. 3 3
      src/main/resources/locale/locale_ru.properties
  92. 3 3
      src/main/resources/locale/locale_th_TH.properties
  93. 3 3
      src/main/resources/locale/locale_zh_CN.properties
  94. 3 3
      src/main/resources/locale/locale_zh_TW.properties
  95. 0 3
      src/main/resources/plugin.yml

+ 5 - 0
Changelog.txt

@@ -10,6 +10,8 @@ Key:
 Version 2.1.0
  + Added JSON integration to all Skill Commands
  + Added config setting to enable or disable classic mcMMO skill scaling
+ + You can now disable specific skills in coreskills.yml without the need for permissions
+ + (Config) New config file added coreskills.yml
  + (Config) Added rank settings for the new Woodcutting skill
  + (Config) Added configurable parameters for the new Tree Feller
  + (Config) Added classic toggle for Tree Feller
@@ -26,6 +28,8 @@ Version 2.1.0
  - (Locale) Removed localizations with the following codes for being almost empty: id, HR_hr, et_EE, lv, lt, no, pl_PL, pt_PT, tr_TR
  - (Config) Removed SkillShot's IncreaseLevel & IncreasePercentage (replaced by RankDamageMultiplier)
  - (Config) Removed AxeMastery's MaxBonus & MaxBonusLevel (replaced by RankDamageMultiplier)
+ ! (Skills) Acrobatics' Roll exploit detection was tweaked to still allow for Roll to trigger even if it rewards no XP
+ ! (Skills) Acrobatics' Roll & Gracefull Roll are now considered the same skill (both mechanics are still there)
  ! (Skills) Woodcutting's Double Drop subskill is now named Harvest Lumber
  ! (Skills) Archery's Skill Shot now uses a rank system
  ! (Skills) Axe's Axe Mastery now uses a rank system
@@ -45,6 +49,7 @@ Version 2.1.0
  ! (Locale) SubSkill locale keys are now located at {ParentSkill}.SubSkill.SubSkillName
  ! (Locale) Super Abilities no longer have (ABILITY) in their Skill.Effect strings
  ! (API) mcMMO is now built against Spigot-API instead of Bukkit
+ ! (API) Moved a lot of methods from SkillCommand to SkillUtils
  ! (API) SkillType is now PrimarySkill
  ! (API) SecondarySkill is now SubSkill
  ! (API) AbilityType is now SuperAbility

+ 0 - 3
crowdin.yml

@@ -1,3 +0,0 @@
-files:
-  - source: /src/main/resources/locale/locale_en_US.properties
-    translation: /src/main/resources/locale/locale_%locale_with_underscore%.properties

+ 10 - 29
src/main/java/com/gmail/nossr50/commands/skills/AcrobaticsCommand.java

@@ -3,8 +3,9 @@ package com.gmail.nossr50.commands.skills;
 import java.util.ArrayList;
 import java.util.List;
 
-import com.gmail.nossr50.datatypes.skills.SubSkill;
-import com.gmail.nossr50.util.SkillTextComponentFactory;
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
+import com.gmail.nossr50.util.TextComponentFactory;
+import com.gmail.nossr50.util.skills.SkillUtils;
 import net.md_5.bungee.api.chat.TextComponent;
 import org.bukkit.entity.Player;
 
@@ -15,14 +16,9 @@ import com.gmail.nossr50.util.Permissions;
 public class AcrobaticsCommand extends SkillCommand {
     private String dodgeChance;
     private String dodgeChanceLucky;
-    private String rollChance;
-    private String rollChanceLucky;
-    private String gracefulRollChance;
-    private String gracefulRollChanceLucky;
 
     private boolean canDodge;
     private boolean canRoll;
-    private boolean canGracefulRoll;
 
     public AcrobaticsCommand() {
         super(PrimarySkill.ACROBATICS);
@@ -32,31 +28,16 @@ public class AcrobaticsCommand extends SkillCommand {
     protected void dataCalculations(Player player, float skillValue, boolean isLucky) {
         // ACROBATICS_DODGE
         if (canDodge) {
-            String[] dodgeStrings = calculateAbilityDisplayValues(skillValue, SubSkill.ACROBATICS_DODGE, isLucky);
+            String[] dodgeStrings = SkillUtils.calculateAbilityDisplayValues(skillValue, SubSkillType.ACROBATICS_DODGE, isLucky);
             dodgeChance = dodgeStrings[0];
             dodgeChanceLucky = dodgeStrings[1];
         }
-
-        // ACROBATICS_ROLL
-        if (canRoll) {
-            String[] rollStrings = calculateAbilityDisplayValues(skillValue, SubSkill.ACROBATICS_ROLL, isLucky);
-            rollChance = rollStrings[0];
-            rollChanceLucky = rollStrings[1];
-        }
-
-        // GRACEFUL ACROBATICS_ROLL
-        if (canGracefulRoll) {
-            String[] gracefulRollStrings = calculateAbilityDisplayValues(skillValue, SubSkill.ACROBATICS_GRACEFUL_ROLL, isLucky);
-            gracefulRollChance = gracefulRollStrings[0];
-            gracefulRollChanceLucky = gracefulRollStrings[1];
-        }
     }
 
     @Override
     protected void permissionsCheck(Player player) {
-        canDodge = Permissions.isSubSkillEnabled(player, SubSkill.ACROBATICS_DODGE);
-        canRoll = Permissions.isSubSkillEnabled(player, SubSkill.ACROBATICS_ROLL);
-        canGracefulRoll = Permissions.isSubSkillEnabled(player, SubSkill.ACROBATICS_GRACEFUL_ROLL);
+        canDodge = Permissions.isSubSkillEnabled(player, SubSkillType.ACROBATICS_DODGE);
+        canRoll = Permissions.isSubSkillEnabled(player, SubSkillType.ACROBATICS_ROLL);
     }
 
     @Override
@@ -65,9 +46,6 @@ public class AcrobaticsCommand extends SkillCommand {
 
         if (canRoll) {
             messages.add(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Acrobatics.Effect.0"), LocaleLoader.getString("Acrobatics.Effect.1")));
-        }
-
-        if (canGracefulRoll) {
             messages.add(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Acrobatics.Effect.2"), LocaleLoader.getString("Acrobatics.Effect.3")));
         }
 
@@ -82,6 +60,7 @@ public class AcrobaticsCommand extends SkillCommand {
     protected List<String> statsDisplay(Player player, float skillValue, boolean hasEndurance, boolean isLucky) {
         List<String> messages = new ArrayList<String>();
 
+        /*
         if (canRoll) {
             messages.add(LocaleLoader.getString("Acrobatics.Roll.Chance", rollChance) + (isLucky ? LocaleLoader.getString("Perks.Lucky.Bonus", rollChanceLucky) : ""));
         }
@@ -89,9 +68,11 @@ public class AcrobaticsCommand extends SkillCommand {
         if (canGracefulRoll) {
             messages.add(LocaleLoader.getString("Acrobatics.Roll.GraceChance", gracefulRollChance) + (isLucky ? LocaleLoader.getString("Perks.Lucky.Bonus", gracefulRollChanceLucky) : ""));
         }
+        */
 
         if (canDodge) {
             messages.add(LocaleLoader.getString("Acrobatics.DodgeChance", dodgeChance) + (isLucky ? LocaleLoader.getString("Perks.Lucky.Bonus", dodgeChanceLucky) : ""));
+
         }
 
         return messages;
@@ -101,7 +82,7 @@ public class AcrobaticsCommand extends SkillCommand {
     protected List<TextComponent> getTextComponents(Player player) {
         List<TextComponent> textComponents = new ArrayList<>();
 
-        SkillTextComponentFactory.getSubSkillTextComponents(player, textComponents, PrimarySkill.ACROBATICS);
+        TextComponentFactory.getSubSkillTextComponents(player, textComponents, PrimarySkill.ACROBATICS);
 
         return textComponents;
     }

+ 5 - 5
src/main/java/com/gmail/nossr50/commands/skills/AlchemyCommand.java

@@ -4,8 +4,8 @@ import java.util.ArrayList;
 import java.util.List;
 
 import com.gmail.nossr50.datatypes.skills.PrimarySkill;
-import com.gmail.nossr50.datatypes.skills.SubSkill;
-import com.gmail.nossr50.util.SkillTextComponentFactory;
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
+import com.gmail.nossr50.util.TextComponentFactory;
 import net.md_5.bungee.api.chat.TextComponent;
 import org.bukkit.entity.Player;
 
@@ -61,8 +61,8 @@ public class AlchemyCommand extends SkillCommand {
 
     @Override
     protected void permissionsCheck(Player player) {
-        canCatalysis = Permissions.isSubSkillEnabled(player, SubSkill.ALCHEMY_CATALYSIS);
-        canConcoctions = Permissions.isSubSkillEnabled(player, SubSkill.ALCHEMY_CONCOCTIONS);
+        canCatalysis = Permissions.isSubSkillEnabled(player, SubSkillType.ALCHEMY_CATALYSIS);
+        canConcoctions = Permissions.isSubSkillEnabled(player, SubSkillType.ALCHEMY_CONCOCTIONS);
     }
 
     @Override
@@ -107,7 +107,7 @@ public class AlchemyCommand extends SkillCommand {
     protected List<TextComponent> getTextComponents(Player player) {
         List<TextComponent> textComponents = new ArrayList<>();
 
-        SkillTextComponentFactory.getSubSkillTextComponents(player, textComponents, PrimarySkill.ALCHEMY);
+        TextComponentFactory.getSubSkillTextComponents(player, textComponents, PrimarySkill.ALCHEMY);
 
         return textComponents;
     }

+ 8 - 8
src/main/java/com/gmail/nossr50/commands/skills/ArcheryCommand.java

@@ -4,8 +4,8 @@ import java.util.ArrayList;
 import java.util.List;
 
 import com.gmail.nossr50.datatypes.skills.PrimarySkill;
-import com.gmail.nossr50.datatypes.skills.SubSkill;
-import com.gmail.nossr50.util.SkillTextComponentFactory;
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
+import com.gmail.nossr50.util.TextComponentFactory;
 import net.md_5.bungee.api.chat.TextComponent;
 import org.bukkit.entity.Player;
 
@@ -38,14 +38,14 @@ public class ArcheryCommand extends SkillCommand {
 
         // ARCHERY_DAZE
         if (canDaze) {
-            String[] dazeStrings = calculateAbilityDisplayValues(skillValue, SubSkill.ARCHERY_DAZE, isLucky);
+            String[] dazeStrings = calculateAbilityDisplayValues(skillValue, SubSkillType.ARCHERY_DAZE, isLucky);
             dazeChance = dazeStrings[0];
             dazeChanceLucky = dazeStrings[1];
         }
 
         // ARCHERY_ARROW_RETRIEVAL
         if (canRetrieve) {
-            String[] retrieveStrings = calculateAbilityDisplayValues(skillValue, SubSkill.ARCHERY_ARROW_RETRIEVAL, isLucky);
+            String[] retrieveStrings = calculateAbilityDisplayValues(skillValue, SubSkillType.ARCHERY_ARROW_RETRIEVAL, isLucky);
             retrieveChance = retrieveStrings[0];
             retrieveChanceLucky = retrieveStrings[1];
         }
@@ -53,9 +53,9 @@ public class ArcheryCommand extends SkillCommand {
 
     @Override
     protected void permissionsCheck(Player player) {
-        canSkillShot = Permissions.isSubSkillEnabled(player, SubSkill.ARCHERY_SKILL_SHOT);
-        canDaze = Permissions.isSubSkillEnabled(player, SubSkill.ARCHERY_DAZE);
-        canRetrieve = Permissions.isSubSkillEnabled(player, SubSkill.ARCHERY_ARROW_RETRIEVAL);
+        canSkillShot = Permissions.isSubSkillEnabled(player, SubSkillType.ARCHERY_SKILL_SHOT);
+        canDaze = Permissions.isSubSkillEnabled(player, SubSkillType.ARCHERY_DAZE);
+        canRetrieve = Permissions.isSubSkillEnabled(player, SubSkillType.ARCHERY_ARROW_RETRIEVAL);
     }
 
     @Override
@@ -100,7 +100,7 @@ public class ArcheryCommand extends SkillCommand {
     protected List<TextComponent> getTextComponents(Player player) {
         List<TextComponent> textComponents = new ArrayList<>();
 
-        SkillTextComponentFactory.getSubSkillTextComponents(player, textComponents, PrimarySkill.ARCHERY);
+        TextComponentFactory.getSubSkillTextComponents(player, textComponents, PrimarySkill.ARCHERY);
 
         return textComponents;
     }

+ 8 - 8
src/main/java/com/gmail/nossr50/commands/skills/AxesCommand.java

@@ -4,8 +4,8 @@ import java.util.ArrayList;
 import java.util.List;
 
 import com.gmail.nossr50.datatypes.skills.PrimarySkill;
-import com.gmail.nossr50.datatypes.skills.SubSkill;
-import com.gmail.nossr50.util.SkillTextComponentFactory;
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
+import com.gmail.nossr50.util.TextComponentFactory;
 import net.md_5.bungee.api.chat.TextComponent;
 import org.bukkit.entity.Player;
 
@@ -47,7 +47,7 @@ public class AxesCommand extends SkillCommand {
 
         // CRITICAL HIT
         if (canCritical) {
-            String[] criticalHitStrings = calculateAbilityDisplayValues(skillValue, SubSkill.AXES_CRITICAL_STRIKES, isLucky);
+            String[] criticalHitStrings = calculateAbilityDisplayValues(skillValue, SubSkillType.AXES_CRITICAL_STRIKES, isLucky);
             critChance = criticalHitStrings[0];
             critChanceLucky = criticalHitStrings[1];
         }
@@ -61,10 +61,10 @@ public class AxesCommand extends SkillCommand {
     @Override
     protected void permissionsCheck(Player player) {
         canSkullSplitter = Permissions.skullSplitter(player);
-        canCritical = Permissions.isSubSkillEnabled(player, SubSkill.AXES_CRITICAL_STRIKES);
-        canAxeMastery = Permissions.isSubSkillEnabled(player, SubSkill.AXES_AXE_MASTERY);
-        canImpact = Permissions.isSubSkillEnabled(player, SubSkill.AXES_ARMOR_IMPACT);
-        canGreaterImpact = Permissions.isSubSkillEnabled(player, SubSkill.AXES_GREATER_IMPACT);
+        canCritical = Permissions.isSubSkillEnabled(player, SubSkillType.AXES_CRITICAL_STRIKES);
+        canAxeMastery = Permissions.isSubSkillEnabled(player, SubSkillType.AXES_AXE_MASTERY);
+        canImpact = Permissions.isSubSkillEnabled(player, SubSkillType.AXES_ARMOR_IMPACT);
+        canGreaterImpact = Permissions.isSubSkillEnabled(player, SubSkillType.AXES_GREATER_IMPACT);
     }
 
     @Override
@@ -125,7 +125,7 @@ public class AxesCommand extends SkillCommand {
     protected List<TextComponent> getTextComponents(Player player) {
         List<TextComponent> textComponents = new ArrayList<>();
 
-        SkillTextComponentFactory.getSubSkillTextComponents(player, textComponents, PrimarySkill.AXES);
+        TextComponentFactory.getSubSkillTextComponents(player, textComponents, PrimarySkill.AXES);
 
         return textComponents;
     }

+ 4 - 4
src/main/java/com/gmail/nossr50/commands/skills/ExcavationCommand.java

@@ -3,11 +3,11 @@ package com.gmail.nossr50.commands.skills;
 import java.util.ArrayList;
 import java.util.List;
 
-import com.gmail.nossr50.util.SkillTextComponentFactory;
+import com.gmail.nossr50.util.TextComponentFactory;
 import net.md_5.bungee.api.chat.TextComponent;
 import org.bukkit.entity.Player;
 
-import com.gmail.nossr50.datatypes.skills.SubSkill;
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
 import com.gmail.nossr50.datatypes.skills.PrimarySkill;
 import com.gmail.nossr50.locale.LocaleLoader;
 import com.gmail.nossr50.util.Permissions;
@@ -36,7 +36,7 @@ public class ExcavationCommand extends SkillCommand {
     @Override
     protected void permissionsCheck(Player player) {
         canGigaDrill = Permissions.gigaDrillBreaker(player);
-        canTreasureHunt = Permissions.isSubSkillEnabled(player, SubSkill.EXCAVATION_TREASURE_HUNTER);
+        canTreasureHunt = Permissions.isSubSkillEnabled(player, SubSkillType.EXCAVATION_TREASURE_HUNTER);
     }
 
     @Override
@@ -69,7 +69,7 @@ public class ExcavationCommand extends SkillCommand {
     protected List<TextComponent> getTextComponents(Player player) {
         List<TextComponent> textComponents = new ArrayList<>();
 
-        SkillTextComponentFactory.getSubSkillTextComponents(player, textComponents, PrimarySkill.EXCAVATION);
+        TextComponentFactory.getSubSkillTextComponents(player, textComponents, PrimarySkill.EXCAVATION);
 
         return textComponents;
     }

+ 9 - 9
src/main/java/com/gmail/nossr50/commands/skills/FishingCommand.java

@@ -3,8 +3,8 @@ package com.gmail.nossr50.commands.skills;
 import java.util.ArrayList;
 import java.util.List;
 
-import com.gmail.nossr50.datatypes.skills.SubSkill;
-import com.gmail.nossr50.util.SkillTextComponentFactory;
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
+import com.gmail.nossr50.util.TextComponentFactory;
 import net.md_5.bungee.api.chat.TextComponent;
 import org.bukkit.Location;
 import org.bukkit.entity.EntityType;
@@ -113,12 +113,12 @@ public class FishingCommand extends SkillCommand {
 
     @Override
     protected void permissionsCheck(Player player) {
-        canTreasureHunt = Permissions.isSubSkillEnabled(player, SubSkill.FISHING_TREASURE_HUNTER);
-        canMagicHunt = Permissions.isSubSkillEnabled(player, SubSkill.FISHING_MAGIC_HUNTER);
-        canShake = Permissions.isSubSkillEnabled(player, SubSkill.FISHING_SHAKE);
-        canFishermansDiet = Permissions.isSubSkillEnabled(player, SubSkill.FISHING_FISHERMANS_DIET);
-        canMasterAngler = Permissions.isSubSkillEnabled(player, SubSkill.FISHING_MASTER_ANGLER);
-        canIceFish = Permissions.isSubSkillEnabled(player, SubSkill.FISHING_ICE_FISHING);
+        canTreasureHunt = Permissions.isSubSkillEnabled(player, SubSkillType.FISHING_TREASURE_HUNTER);
+        canMagicHunt = Permissions.isSubSkillEnabled(player, SubSkillType.FISHING_MAGIC_HUNTER);
+        canShake = Permissions.isSubSkillEnabled(player, SubSkillType.FISHING_SHAKE);
+        canFishermansDiet = Permissions.isSubSkillEnabled(player, SubSkillType.FISHING_FISHERMANS_DIET);
+        canMasterAngler = Permissions.isSubSkillEnabled(player, SubSkillType.FISHING_MASTER_ANGLER);
+        canIceFish = Permissions.isSubSkillEnabled(player, SubSkillType.FISHING_ICE_FISHING);
     }
 
     @Override
@@ -209,7 +209,7 @@ public class FishingCommand extends SkillCommand {
     protected List<TextComponent> getTextComponents(Player player) {
         List<TextComponent> textComponents = new ArrayList<>();
 
-        SkillTextComponentFactory.getSubSkillTextComponents(player, textComponents, PrimarySkill.FISHING);
+        TextComponentFactory.getSubSkillTextComponents(player, textComponents, PrimarySkill.FISHING);
 
         return textComponents;
     }

+ 11 - 11
src/main/java/com/gmail/nossr50/commands/skills/HerbalismCommand.java

@@ -1,11 +1,11 @@
 package com.gmail.nossr50.commands.skills;
 
 import com.gmail.nossr50.datatypes.skills.PrimarySkill;
-import com.gmail.nossr50.datatypes.skills.SubSkill;
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
 import com.gmail.nossr50.locale.LocaleLoader;
 import com.gmail.nossr50.skills.herbalism.Herbalism;
 import com.gmail.nossr50.util.Permissions;
-import com.gmail.nossr50.util.SkillTextComponentFactory;
+import com.gmail.nossr50.util.TextComponentFactory;
 import net.md_5.bungee.api.chat.TextComponent;
 import org.bukkit.Material;
 import org.bukkit.entity.Player;
@@ -57,28 +57,28 @@ public class HerbalismCommand extends SkillCommand {
         if (canGreenThumbBlocks || canGreenThumbPlants) {
             greenThumbStage = calculateRank(skillValue, Herbalism.greenThumbStageMaxLevel, Herbalism.greenThumbStageChangeLevel);
 
-            String[] greenThumbStrings = calculateAbilityDisplayValues(skillValue, SubSkill.HERBALISM_GREEN_THUMB, isLucky);
+            String[] greenThumbStrings = calculateAbilityDisplayValues(skillValue, SubSkillType.HERBALISM_GREEN_THUMB, isLucky);
             greenThumbChance = greenThumbStrings[0];
             greenThumbChanceLucky = greenThumbStrings[1];
         }
 
         // DOUBLE DROPS
         if (canDoubleDrop) {
-            String[] doubleDropStrings = calculateAbilityDisplayValues(skillValue, SubSkill.HERBALISM_DOUBLE_DROPS, isLucky);
+            String[] doubleDropStrings = calculateAbilityDisplayValues(skillValue, SubSkillType.HERBALISM_DOUBLE_DROPS, isLucky);
             doubleDropChance = doubleDropStrings[0];
             doubleDropChanceLucky = doubleDropStrings[1];
         }
 
         // HYLIAN LUCK
         if (hasHylianLuck) {
-            String[] hylianLuckStrings = calculateAbilityDisplayValues(skillValue, SubSkill.HERBALISM_HYLIAN_LUCK, isLucky);
+            String[] hylianLuckStrings = calculateAbilityDisplayValues(skillValue, SubSkillType.HERBALISM_HYLIAN_LUCK, isLucky);
             hylianLuckChance = hylianLuckStrings[0];
             hylianLuckChanceLucky = hylianLuckStrings[1];
         }
 
         // SHROOM THUMB
         if (canShroomThumb) {
-            String[] shroomThumbStrings = calculateAbilityDisplayValues(skillValue, SubSkill.HERBALISM_SHROOM_THUMB, isLucky);
+            String[] shroomThumbStrings = calculateAbilityDisplayValues(skillValue, SubSkillType.HERBALISM_SHROOM_THUMB, isLucky);
             shroomThumbChance = shroomThumbStrings[0];
             shroomThumbChanceLucky = shroomThumbStrings[1];
         }
@@ -86,13 +86,13 @@ public class HerbalismCommand extends SkillCommand {
 
     @Override
     protected void permissionsCheck(Player player) {
-        hasHylianLuck = Permissions.isSubSkillEnabled(player, SubSkill.HERBALISM_HYLIAN_LUCK);
+        hasHylianLuck = Permissions.isSubSkillEnabled(player, SubSkillType.HERBALISM_HYLIAN_LUCK);
         canGreenTerra = Permissions.greenTerra(player);
         canGreenThumbPlants = Permissions.greenThumbPlant(player, Material.WHEAT) || Permissions.greenThumbPlant(player, Material.CARROT) || Permissions.greenThumbPlant(player, Material.POTATO) || Permissions.greenThumbPlant(player, Material.BEETROOT) || Permissions.greenThumbPlant(player, Material.NETHER_WART) || Permissions.greenThumbPlant(player, Material.COCOA);
         canGreenThumbBlocks = Permissions.greenThumbBlock(player, Material.DIRT) || Permissions.greenThumbBlock(player, Material.COBBLESTONE) || Permissions.greenThumbBlock(player, Material.COBBLESTONE_WALL) || Permissions.greenThumbBlock(player, Material.STONE_BRICKS);
-        canFarmersDiet = Permissions.isSubSkillEnabled(player, SubSkill.HERBALISM_FARMERS_DIET);
-        canDoubleDrop = Permissions.isSubSkillEnabled(player, SubSkill.HERBALISM_DOUBLE_DROPS) && !skill.getDoubleDropsDisabled();
-        canShroomThumb = Permissions.isSubSkillEnabled(player, SubSkill.HERBALISM_SHROOM_THUMB);
+        canFarmersDiet = Permissions.isSubSkillEnabled(player, SubSkillType.HERBALISM_FARMERS_DIET);
+        canDoubleDrop = Permissions.isSubSkillEnabled(player, SubSkillType.HERBALISM_DOUBLE_DROPS) && !skill.getDoubleDropsDisabled();
+        canShroomThumb = Permissions.isSubSkillEnabled(player, SubSkillType.HERBALISM_SHROOM_THUMB);
     }
 
     @Override
@@ -169,7 +169,7 @@ public class HerbalismCommand extends SkillCommand {
     protected List<TextComponent> getTextComponents(Player player) {
         List<TextComponent> textComponents = new ArrayList<>();
 
-        SkillTextComponentFactory.getSubSkillTextComponents(player, textComponents, PrimarySkill.HERBALISM);
+        TextComponentFactory.getSubSkillTextComponents(player, textComponents, PrimarySkill.HERBALISM);
 
         return textComponents;
     }

+ 23 - 0
src/main/java/com/gmail/nossr50/commands/skills/McMMOWebLinks.java

@@ -0,0 +1,23 @@
+package com.gmail.nossr50.commands.skills;
+
+import com.gmail.nossr50.datatypes.json.McMMOUrl;
+import com.gmail.nossr50.util.StringUtils;
+
+public enum McMMOWebLinks {
+    WEBSITE,
+    DISCORD,
+    PATREON,
+    SPIGOT,
+    HELP_TRANSLATE,
+    WIKI;
+
+    public String getUrl()
+    {
+        return McMMOUrl.getUrl(this);
+    }
+
+    public String getNiceTitle()
+    {
+        return StringUtils.getCapitalized(toString());
+    }
+}

+ 5 - 5
src/main/java/com/gmail/nossr50/commands/skills/MiningCommand.java

@@ -3,8 +3,8 @@ package com.gmail.nossr50.commands.skills;
 import java.util.ArrayList;
 import java.util.List;
 
-import com.gmail.nossr50.datatypes.skills.SubSkill;
-import com.gmail.nossr50.util.SkillTextComponentFactory;
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
+import com.gmail.nossr50.util.TextComponentFactory;
 import net.md_5.bungee.api.chat.TextComponent;
 import org.bukkit.entity.Player;
 
@@ -51,7 +51,7 @@ public class MiningCommand extends SkillCommand {
 
         // DOUBLE DROPS
         if (canDoubleDrop) {
-            String[] doubleDropStrings = calculateAbilityDisplayValues(skillValue, SubSkill.MINING_DOUBLE_DROPS, isLucky);
+            String[] doubleDropStrings = calculateAbilityDisplayValues(skillValue, SubSkillType.MINING_DOUBLE_DROPS, isLucky);
             doubleDropChance = doubleDropStrings[0];
             doubleDropChanceLucky = doubleDropStrings[1];
         }
@@ -74,7 +74,7 @@ public class MiningCommand extends SkillCommand {
         canBiggerBombs = Permissions.biggerBombs(player);
         canBlast = Permissions.remoteDetonation(player);
         canDemoExpert = Permissions.demolitionsExpertise(player);
-        canDoubleDrop = Permissions.isSubSkillEnabled(player, SubSkill.MINING_DOUBLE_DROPS) && !skill.getDoubleDropsDisabled();
+        canDoubleDrop = Permissions.isSubSkillEnabled(player, SubSkillType.MINING_DOUBLE_DROPS) && !skill.getDoubleDropsDisabled();
         canSuperBreaker = Permissions.superBreaker(player);
     }
 
@@ -157,7 +157,7 @@ public class MiningCommand extends SkillCommand {
     protected List<TextComponent> getTextComponents(Player player) {
         List<TextComponent> textComponents = new ArrayList<>();
 
-        SkillTextComponentFactory.getSubSkillTextComponents(player, textComponents, PrimarySkill.MINING);
+        TextComponentFactory.getSubSkillTextComponents(player, textComponents, PrimarySkill.MINING);
 
         return textComponents;
     }

+ 7 - 7
src/main/java/com/gmail/nossr50/commands/skills/RepairCommand.java

@@ -1,7 +1,7 @@
 package com.gmail.nossr50.commands.skills;
 
 import com.gmail.nossr50.datatypes.skills.MaterialType;
-import com.gmail.nossr50.datatypes.skills.SubSkill;
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
 import com.gmail.nossr50.datatypes.skills.PrimarySkill;
 import com.gmail.nossr50.locale.LocaleLoader;
 import com.gmail.nossr50.mcMMO;
@@ -11,7 +11,7 @@ import com.gmail.nossr50.skills.repair.Repair;
 import com.gmail.nossr50.skills.repair.RepairManager;
 import com.gmail.nossr50.skills.repair.repairables.Repairable;
 import com.gmail.nossr50.util.Permissions;
-import com.gmail.nossr50.util.SkillTextComponentFactory;
+import com.gmail.nossr50.util.TextComponentFactory;
 import com.gmail.nossr50.util.player.UserManager;
 import net.md_5.bungee.api.chat.TextComponent;
 import org.bukkit.Material;
@@ -67,7 +67,7 @@ public class RepairCommand extends SkillCommand {
 
         // SUPER REPAIR
         if (canSuperRepair) {
-            String[] superRepairStrings = calculateAbilityDisplayValues(skillValue, SubSkill.REPAIR_SUPER_REPAIR, isLucky);
+            String[] superRepairStrings = calculateAbilityDisplayValues(skillValue, SubSkillType.REPAIR_SUPER_REPAIR, isLucky);
             superRepairChance = superRepairStrings[0];
             superRepairChanceLucky = superRepairStrings[1];
         }
@@ -75,9 +75,9 @@ public class RepairCommand extends SkillCommand {
 
     @Override
     protected void permissionsCheck(Player player) {
-        canSuperRepair = Permissions.isSubSkillEnabled(player, SubSkill.REPAIR_SUPER_REPAIR);
-        canMasterRepair = Permissions.isSubSkillEnabled(player, SubSkill.REPAIR_REPAIR_MASTERY);
-        canArcaneForge = Permissions.isSubSkillEnabled(player, SubSkill.REPAIR_ARCANE_FORGING);
+        canSuperRepair = Permissions.isSubSkillEnabled(player, SubSkillType.REPAIR_SUPER_REPAIR);
+        canMasterRepair = Permissions.isSubSkillEnabled(player, SubSkillType.REPAIR_REPAIR_MASTERY);
+        canArcaneForge = Permissions.isSubSkillEnabled(player, SubSkillType.REPAIR_ARCANE_FORGING);
         canRepairDiamond = Permissions.repairMaterialType(player, MaterialType.DIAMOND);
         canRepairGold = Permissions.repairMaterialType(player, MaterialType.GOLD);
         canRepairIron = Permissions.repairMaterialType(player, MaterialType.IRON);
@@ -162,7 +162,7 @@ public class RepairCommand extends SkillCommand {
     protected List<TextComponent> getTextComponents(Player player) {
         List<TextComponent> textComponents = new ArrayList<>();
 
-        SkillTextComponentFactory.getSubSkillTextComponents(player, textComponents, PrimarySkill.REPAIR);
+        TextComponentFactory.getSubSkillTextComponents(player, textComponents, PrimarySkill.REPAIR);
 
         return textComponents;
     }

+ 5 - 5
src/main/java/com/gmail/nossr50/commands/skills/SalvageCommand.java

@@ -4,11 +4,11 @@ import java.util.ArrayList;
 import java.util.List;
 
 import com.gmail.nossr50.datatypes.skills.PrimarySkill;
-import com.gmail.nossr50.util.SkillTextComponentFactory;
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
+import com.gmail.nossr50.util.TextComponentFactory;
 import net.md_5.bungee.api.chat.TextComponent;
 import org.bukkit.entity.Player;
 
-import com.gmail.nossr50.datatypes.skills.SubSkill;
 import com.gmail.nossr50.locale.LocaleLoader;
 import com.gmail.nossr50.skills.salvage.Salvage;
 import com.gmail.nossr50.skills.salvage.SalvageManager;
@@ -31,8 +31,8 @@ public class SalvageCommand extends SkillCommand {
 
     @Override
     protected void permissionsCheck(Player player) {
-        canAdvancedSalvage = Permissions.isSubSkillEnabled(player, SubSkill.SALVAGE_ADVANCED_SALVAGE);
-        canArcaneSalvage = Permissions.isSubSkillEnabled(player, SubSkill.SALVAGE_ARCANE_SALVAGE);
+        canAdvancedSalvage = Permissions.isSubSkillEnabled(player, SubSkillType.SALVAGE_ADVANCED_SALVAGE);
+        canArcaneSalvage = Permissions.isSubSkillEnabled(player, SubSkillType.SALVAGE_ARCANE_SALVAGE);
     }
 
     @Override
@@ -83,7 +83,7 @@ public class SalvageCommand extends SkillCommand {
     protected List<TextComponent> getTextComponents(Player player) {
         List<TextComponent> textComponents = new ArrayList<>();
 
-        SkillTextComponentFactory.getSubSkillTextComponents(player, textComponents, PrimarySkill.SALVAGE);
+        TextComponentFactory.getSubSkillTextComponents(player, textComponents, PrimarySkill.SALVAGE);
 
         return textComponents;
     }

+ 54 - 32
src/main/java/com/gmail/nossr50/commands/skills/SkillCommand.java

@@ -2,19 +2,16 @@ package com.gmail.nossr50.commands.skills;
 
 import java.text.DecimalFormat;
 import java.util.List;
-import java.util.Set;
 
 import com.gmail.nossr50.datatypes.skills.PrimarySkill;
-import com.gmail.nossr50.util.SkillTextComponentFactory;
 
 import com.gmail.nossr50.config.AdvancedConfig;
 import com.gmail.nossr50.config.Config;
 import com.gmail.nossr50.datatypes.player.McMMOPlayer;
-import com.gmail.nossr50.datatypes.skills.SubSkill;
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
 import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.skills.child.FamilyTree;
-import com.gmail.nossr50.util.Motd;
 import com.gmail.nossr50.util.Permissions;
+import com.gmail.nossr50.util.TextComponentFactory;
 import com.gmail.nossr50.util.StringUtils;
 import com.gmail.nossr50.util.commands.CommandUtils;
 import com.gmail.nossr50.util.player.UserManager;
@@ -76,19 +73,30 @@ public abstract class SkillCommand implements TabExecutor {
                 //Make JSON text components
                 List<TextComponent> subskillTextComponents = getTextComponents(player);
 
-
                 //Subskills Header
-                player.sendMessage(LocaleLoader.getString("Skills.Header", LocaleLoader.getString("Effects.SubSkills")));
+                player.sendMessage(LocaleLoader.getString("Skills.Overhaul.Header", LocaleLoader.getString("Effects.SubSkills.Overhaul")));
 
                 //Send JSON text components
-                for(TextComponent tc : subskillTextComponents)
+
+                TextComponentFactory.sendPlayerSubSkillList(player, subskillTextComponents);
+
+                /*for(TextComponent tc : subskillTextComponents)
                 {
                     player.spigot().sendMessage(new TextComponent[]{tc, new TextComponent(": TESTING")});
-                }
+                }*/
 
                 //Stats
                 getStatMessages(player, isLucky, hasEndurance, skillValue);
 
+                ChatColor hd1 = ChatColor.DARK_AQUA;
+                ChatColor c1 = ChatColor.GOLD;
+                ChatColor c2 = ChatColor.RED;
+
+                //Header
+                player.sendMessage(hd1+"[]=====[]"+c1+" mcMMO "+c2+"Overhaul"+c1+" Era "+hd1+"[]=====[]");
+                //Link Header
+                TextComponentFactory.sendPlayerUrlHeader(player);
+
                 return true;
 
             default:
@@ -100,7 +108,7 @@ public abstract class SkillCommand implements TabExecutor {
         List<String> statsMessages = statsDisplay(player, skillValue, hasEndurance, isLucky);
 
         if (!statsMessages.isEmpty()) {
-            player.sendMessage(LocaleLoader.getString("Skills.Header", LocaleLoader.getString("Commands.Stats.Self")));
+            player.sendMessage(LocaleLoader.getString("Skills.Overhaul.Header", LocaleLoader.getString("Commands.Stats.Self.Overhaul")));
 
             for (String message : statsMessages) {
                 player.sendMessage(message);
@@ -111,6 +119,40 @@ public abstract class SkillCommand implements TabExecutor {
     }
 
     private void sendSkillCommandHeader(Player player, McMMOPlayer mcMMOPlayer, int skillValue) {
+
+        if(!skill.isChildSkill())
+        {
+            ChatColor hd1 = ChatColor.DARK_AQUA;
+            ChatColor c1 = ChatColor.GOLD;
+            ChatColor c2 = ChatColor.RED;
+
+            player.sendMessage(hd1+"[]=====[]"+c1+" "+skillName+" "+hd1+"[]=====[]");
+
+            //XP GAIN METHOD
+            player.sendMessage(LocaleLoader.getString("Commands.XPGain.Overhaul", LocaleLoader.getString("Commands.XPGain." + StringUtils.getCapitalized(skill.toString()))));
+
+            //LEVEL
+            player.sendMessage(LocaleLoader.getString("Effects.Level.Overhaul", skillValue, mcMMOPlayer.getSkillXpLevel(skill), mcMMOPlayer.getXpToLevel(skill)));
+
+        } else {
+            ChatColor hd1 = ChatColor.DARK_AQUA;
+            ChatColor c1 = ChatColor.GOLD;
+            ChatColor c2 = ChatColor.DARK_PURPLE;
+            //Header
+            player.sendMessage(hd1+"[]=====[]"+c1+" mcMMO "+c2+"Overhaul"+c1+" Era "+hd1+"[]=====[]");
+            //Link Header
+            TextComponentFactory.sendPlayerUrlHeader(player);
+            player.sendMessage(hd1+"[]=====[]"+c1+" "+skillName+" "+hd1+"[]=====[]");
+
+            //XP GAIN METHOD
+            player.sendMessage(LocaleLoader.getString("Commands.XPGain", LocaleLoader.getString("Commands.XPGain." + StringUtils.getCapitalized(skill.toString()))));
+
+            //LEVEL
+            player.sendMessage(LocaleLoader.getString("Effects.Level", skillValue, mcMMOPlayer.getSkillXpLevel(skill), mcMMOPlayer.getXpToLevel(skill)));
+
+        }
+
+        /*
         if (!skill.isChildSkill()) {
             player.sendMessage(LocaleLoader.getString("Skills.Header", skillName));
             player.sendMessage(LocaleLoader.getString("Commands.XPGain", LocaleLoader.getString("Commands.XPGain." + StringUtils.getCapitalized(skill.toString()))));
@@ -127,27 +169,7 @@ public abstract class SkillCommand implements TabExecutor {
                 player.sendMessage(parent.getName() + " - " + LocaleLoader.getString("Effects.Level", mcMMOPlayer.getSkillLevel(parent), mcMMOPlayer.getSkillXpLevel(parent), mcMMOPlayer.getXpToLevel(parent)));
             }
         }
-    }
-
-    private void displayOldSkillCommand(Player player, McMMOPlayer mcMMOPlayer, boolean isLucky, boolean hasEndurance, float skillValue) {
-        //Send headers
-        sendSkillCommandHeader(player, mcMMOPlayer, (int) skillValue);
-
-        List<String> effectMessages = effectsDisplay();
-
-        if (!effectMessages.isEmpty()) {
-            player.sendMessage(LocaleLoader.getString("Skills.Header", LocaleLoader.getString("Effects.Effects")));
-
-            if (isLucky) {
-                player.sendMessage(Motd.PERK_PREFIX + LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Perks.Lucky.Name"), LocaleLoader.getString("Perks.Lucky.Desc", skillName)));
-            }
-
-            for (String message : effectMessages) {
-                player.sendMessage(message);
-            }
-        }
-
-        getStatMessages(player, isLucky, hasEndurance, skillValue);
+        */
     }
 
     @Override
@@ -173,7 +195,7 @@ public abstract class SkillCommand implements TabExecutor {
         return displayValues;
     }
 
-    protected String[] calculateAbilityDisplayValues(float skillValue, SubSkill subSkill, boolean isLucky) {
+    protected String[] calculateAbilityDisplayValues(float skillValue, SubSkillType subSkill, boolean isLucky) {
         int maxBonusLevel = AdvancedConfig.getInstance().getMaxBonusLevel(subSkill);
 
         return calculateAbilityDisplayValues((AdvancedConfig.getInstance().getMaxChance(subSkill) / maxBonusLevel) * Math.min(skillValue, maxBonusLevel), isLucky);

+ 7 - 7
src/main/java/com/gmail/nossr50/commands/skills/SmeltingCommand.java

@@ -3,8 +3,8 @@ package com.gmail.nossr50.commands.skills;
 import java.util.ArrayList;
 import java.util.List;
 
-import com.gmail.nossr50.datatypes.skills.SubSkill;
-import com.gmail.nossr50.util.SkillTextComponentFactory;
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
+import com.gmail.nossr50.util.TextComponentFactory;
 import net.md_5.bungee.api.chat.TextComponent;
 import org.bukkit.entity.Player;
 
@@ -41,7 +41,7 @@ public class SmeltingCommand extends SkillCommand {
 
         // SECOND SMELT
         if (canSecondSmelt) {
-            String[] secondSmeltStrings = calculateAbilityDisplayValues(skillValue, SubSkill.SMELTING_SECOND_SMELT, isLucky);
+            String[] secondSmeltStrings = calculateAbilityDisplayValues(skillValue, SubSkillType.SMELTING_SECOND_SMELT, isLucky);
             secondSmeltChance = secondSmeltStrings[0];
             secondSmeltChanceLucky = secondSmeltStrings[1];
         }
@@ -56,9 +56,9 @@ public class SmeltingCommand extends SkillCommand {
 
     @Override
     protected void permissionsCheck(Player player) {
-        canFuelEfficiency = Permissions.isSubSkillEnabled(player, SubSkill.SMELTING_FUEL_EFFICIENCY);
-        canSecondSmelt = Permissions.isSubSkillEnabled(player, SubSkill.SMELTING_SECOND_SMELT);
-        canFluxMine = Permissions.isSubSkillEnabled(player, SubSkill.SMELTING_FLUX_MINING);
+        canFuelEfficiency = Permissions.isSubSkillEnabled(player, SubSkillType.SMELTING_FUEL_EFFICIENCY);
+        canSecondSmelt = Permissions.isSubSkillEnabled(player, SubSkillType.SMELTING_SECOND_SMELT);
+        canFluxMine = Permissions.isSubSkillEnabled(player, SubSkillType.SMELTING_FLUX_MINING);
         canVanillaXPBoost = Permissions.vanillaXpBoost(player, skill);
     }
 
@@ -124,7 +124,7 @@ public class SmeltingCommand extends SkillCommand {
     protected List<TextComponent> getTextComponents(Player player) {
         List<TextComponent> textComponents = new ArrayList<>();
 
-        SkillTextComponentFactory.getSubSkillTextComponents(player, textComponents, PrimarySkill.SWORDS);
+        TextComponentFactory.getSubSkillTextComponents(player, textComponents, PrimarySkill.SWORDS);
 
         return textComponents;
     }

+ 8 - 8
src/main/java/com/gmail/nossr50/commands/skills/SwordsCommand.java

@@ -4,8 +4,8 @@ import java.util.ArrayList;
 import java.util.List;
 
 import com.gmail.nossr50.datatypes.skills.PrimarySkill;
-import com.gmail.nossr50.datatypes.skills.SubSkill;
-import com.gmail.nossr50.util.SkillTextComponentFactory;
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
+import com.gmail.nossr50.util.TextComponentFactory;
 import net.md_5.bungee.api.chat.TextComponent;
 import org.bukkit.entity.Player;
 
@@ -42,16 +42,16 @@ public class SwordsCommand extends SkillCommand {
 
         // SWORDS_BLEED
         if (canBleed) {
-            bleedLength = (skillValue >= AdvancedConfig.getInstance().getMaxBonusLevel(SubSkill.SWORDS_BLEED)) ? Swords.bleedMaxTicks : Swords.bleedBaseTicks;
+            bleedLength = (skillValue >= AdvancedConfig.getInstance().getMaxBonusLevel(SubSkillType.SWORDS_BLEED)) ? Swords.bleedMaxTicks : Swords.bleedBaseTicks;
 
-            String[] bleedStrings = calculateAbilityDisplayValues(skillValue, SubSkill.SWORDS_BLEED, isLucky);
+            String[] bleedStrings = calculateAbilityDisplayValues(skillValue, SubSkillType.SWORDS_BLEED, isLucky);
             bleedChance = bleedStrings[0];
             bleedChanceLucky = bleedStrings[1];
         }
 
         // SWORDS_COUNTER_ATTACK
         if (canCounter) {
-            String[] counterStrings = calculateAbilityDisplayValues(skillValue, SubSkill.SWORDS_COUNTER_ATTACK, isLucky);
+            String[] counterStrings = calculateAbilityDisplayValues(skillValue, SubSkillType.SWORDS_COUNTER_ATTACK, isLucky);
             counterChance = counterStrings[0];
             counterChanceLucky = counterStrings[1];
         }
@@ -59,8 +59,8 @@ public class SwordsCommand extends SkillCommand {
 
     @Override
     protected void permissionsCheck(Player player) {
-        canBleed = Permissions.isSubSkillEnabled(player, SubSkill.SWORDS_BLEED);
-        canCounter = Permissions.isSubSkillEnabled(player, SubSkill.SWORDS_COUNTER_ATTACK);
+        canBleed = Permissions.isSubSkillEnabled(player, SubSkillType.SWORDS_BLEED);
+        canCounter = Permissions.isSubSkillEnabled(player, SubSkillType.SWORDS_COUNTER_ATTACK);
         canSerratedStrike = Permissions.serratedStrikes(player);
     }
 
@@ -109,7 +109,7 @@ public class SwordsCommand extends SkillCommand {
     protected List<TextComponent> getTextComponents(Player player) {
         List<TextComponent> textComponents = new ArrayList<>();
 
-        SkillTextComponentFactory.getSubSkillTextComponents(player, textComponents, PrimarySkill.SWORDS);
+        TextComponentFactory.getSubSkillTextComponents(player, textComponents, PrimarySkill.SWORDS);
 
         return textComponents;
     }

+ 12 - 12
src/main/java/com/gmail/nossr50/commands/skills/TamingCommand.java

@@ -4,8 +4,8 @@ import java.util.ArrayList;
 import java.util.List;
 
 import com.gmail.nossr50.datatypes.skills.PrimarySkill;
-import com.gmail.nossr50.datatypes.skills.SubSkill;
-import com.gmail.nossr50.util.SkillTextComponentFactory;
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
+import com.gmail.nossr50.util.TextComponentFactory;
 import net.md_5.bungee.api.chat.TextComponent;
 import org.bukkit.entity.EntityType;
 import org.bukkit.entity.Player;
@@ -37,7 +37,7 @@ public class TamingCommand extends SkillCommand {
     @Override
     protected void dataCalculations(Player player, float skillValue, boolean isLucky) {
         if (canGore) {
-            String[] goreStrings = calculateAbilityDisplayValues(skillValue, SubSkill.TAMING_GORE, isLucky);
+            String[] goreStrings = calculateAbilityDisplayValues(skillValue, SubSkillType.TAMING_GORE, isLucky);
             goreChance = goreStrings[0];
             goreChanceLucky = goreStrings[1];
         }
@@ -45,15 +45,15 @@ public class TamingCommand extends SkillCommand {
 
     @Override
     protected void permissionsCheck(Player player) {
-        canBeastLore = Permissions.isSubSkillEnabled(player, SubSkill.TAMING_BEAST_LORE);
+        canBeastLore = Permissions.isSubSkillEnabled(player, SubSkillType.TAMING_BEAST_LORE);
         canCallWild = Permissions.callOfTheWild(player, EntityType.HORSE) || Permissions.callOfTheWild(player, EntityType.WOLF) || Permissions.callOfTheWild(player, EntityType.OCELOT);
-        canEnvironmentallyAware = Permissions.isSubSkillEnabled(player, SubSkill.TAMING_ENVIRONMENTALLY_AWARE);
-        canFastFood = Permissions.isSubSkillEnabled(player, SubSkill.TAMING_FAST_FOOD_SERVICE);
-        canGore = Permissions.isSubSkillEnabled(player, SubSkill.TAMING_GORE);
-        canSharpenedClaws = Permissions.isSubSkillEnabled(player, SubSkill.TAMING_SHARPENED_CLAWS);
-        canShockProof = Permissions.isSubSkillEnabled(player, SubSkill.TAMING_SHOCK_PROOF);
-        canThickFur = Permissions.isSubSkillEnabled(player, SubSkill.TAMING_THICK_FUR);
-        canHolyHound = Permissions.isSubSkillEnabled(player, SubSkill.TAMING_HOLY_HOUND);
+        canEnvironmentallyAware = Permissions.isSubSkillEnabled(player, SubSkillType.TAMING_ENVIRONMENTALLY_AWARE);
+        canFastFood = Permissions.isSubSkillEnabled(player, SubSkillType.TAMING_FAST_FOOD_SERVICE);
+        canGore = Permissions.isSubSkillEnabled(player, SubSkillType.TAMING_GORE);
+        canSharpenedClaws = Permissions.isSubSkillEnabled(player, SubSkillType.TAMING_SHARPENED_CLAWS);
+        canShockProof = Permissions.isSubSkillEnabled(player, SubSkillType.TAMING_SHOCK_PROOF);
+        canThickFur = Permissions.isSubSkillEnabled(player, SubSkillType.TAMING_THICK_FUR);
+        canHolyHound = Permissions.isSubSkillEnabled(player, SubSkillType.TAMING_HOLY_HOUND);
     }
 
     @Override
@@ -175,7 +175,7 @@ public class TamingCommand extends SkillCommand {
     protected List<TextComponent> getTextComponents(Player player) {
         List<TextComponent> textComponents = new ArrayList<>();
 
-        SkillTextComponentFactory.getSubSkillTextComponents(player, textComponents, PrimarySkill.TAMING);
+        TextComponentFactory.getSubSkillTextComponents(player, textComponents, PrimarySkill.TAMING);
 
         return textComponents;
     }

+ 10 - 10
src/main/java/com/gmail/nossr50/commands/skills/UnarmedCommand.java

@@ -4,8 +4,8 @@ import java.util.ArrayList;
 import java.util.List;
 
 import com.gmail.nossr50.datatypes.skills.PrimarySkill;
-import com.gmail.nossr50.datatypes.skills.SubSkill;
-import com.gmail.nossr50.util.SkillTextComponentFactory;
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
+import com.gmail.nossr50.util.TextComponentFactory;
 import net.md_5.bungee.api.chat.TextComponent;
 import org.bukkit.entity.Player;
 
@@ -45,14 +45,14 @@ public class UnarmedCommand extends SkillCommand {
 
         // UNARMED_DISARM
         if (canDisarm) {
-            String[] disarmStrings = calculateAbilityDisplayValues(skillValue, SubSkill.UNARMED_DISARM, isLucky);
+            String[] disarmStrings = calculateAbilityDisplayValues(skillValue, SubSkillType.UNARMED_DISARM, isLucky);
             disarmChance = disarmStrings[0];
             disarmChanceLucky = disarmStrings[1];
         }
 
         // UNARMED_ARROW_DEFLECT
         if (canDeflect) {
-            String[] deflectStrings = calculateAbilityDisplayValues(skillValue, SubSkill.UNARMED_ARROW_DEFLECT, isLucky);
+            String[] deflectStrings = calculateAbilityDisplayValues(skillValue, SubSkillType.UNARMED_ARROW_DEFLECT, isLucky);
             deflectChance = deflectStrings[0];
             deflectChanceLucky = deflectStrings[1];
         }
@@ -64,7 +64,7 @@ public class UnarmedCommand extends SkillCommand {
 
         // IRON GRIP
         if (canIronGrip) {
-            String[] ironGripStrings = calculateAbilityDisplayValues(skillValue, SubSkill.UNARMED_IRON_GRIP, isLucky);
+            String[] ironGripStrings = calculateAbilityDisplayValues(skillValue, SubSkillType.UNARMED_IRON_GRIP, isLucky);
             ironGripChance = ironGripStrings[0];
             ironGripChanceLucky = ironGripStrings[1];
         }
@@ -73,10 +73,10 @@ public class UnarmedCommand extends SkillCommand {
     @Override
     protected void permissionsCheck(Player player) {
         canBerserk = Permissions.berserk(player);
-        canIronArm = Permissions.isSubSkillEnabled(player, SubSkill.UNARMED_IRON_ARM_STYLE);
-        canDeflect = Permissions.isSubSkillEnabled(player, SubSkill.UNARMED_ARROW_DEFLECT);
-        canDisarm = Permissions.isSubSkillEnabled(player, SubSkill.UNARMED_DISARM);
-        canIronGrip = Permissions.isSubSkillEnabled(player, SubSkill.UNARMED_IRON_GRIP);
+        canIronArm = Permissions.isSubSkillEnabled(player, SubSkillType.UNARMED_IRON_ARM_STYLE);
+        canDeflect = Permissions.isSubSkillEnabled(player, SubSkillType.UNARMED_ARROW_DEFLECT);
+        canDisarm = Permissions.isSubSkillEnabled(player, SubSkillType.UNARMED_DISARM);
+        canIronGrip = Permissions.isSubSkillEnabled(player, SubSkillType.UNARMED_IRON_GRIP);
         // TODO: Apparently we forgot about block cracker?
     }
 
@@ -139,7 +139,7 @@ public class UnarmedCommand extends SkillCommand {
     protected List<TextComponent> getTextComponents(Player player) {
         List<TextComponent> textComponents = new ArrayList<>();
 
-        SkillTextComponentFactory.getSubSkillTextComponents(player, textComponents, PrimarySkill.UNARMED);
+        TextComponentFactory.getSubSkillTextComponents(player, textComponents, PrimarySkill.UNARMED);
 
         return textComponents;
     }

+ 10 - 10
src/main/java/com/gmail/nossr50/commands/skills/WoodcuttingCommand.java

@@ -4,8 +4,8 @@ import java.util.ArrayList;
 import java.util.List;
 
 import com.gmail.nossr50.datatypes.skills.PrimarySkill;
-import com.gmail.nossr50.datatypes.skills.SubSkill;
-import com.gmail.nossr50.util.SkillTextComponentFactory;
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
+import com.gmail.nossr50.util.TextComponentFactory;
 import net.md_5.bungee.api.chat.TextComponent;
 import org.bukkit.entity.Player;
 
@@ -41,7 +41,7 @@ public class WoodcuttingCommand extends SkillCommand {
 
         // DOUBLE DROPS
         if (canDoubleDrop) {
-            if(AdvancedConfig.getInstance().isSubSkillClassic(SubSkill.WOODCUTTING_HARVEST_LUMBER))
+            if(AdvancedConfig.getInstance().isSubSkillClassic(SubSkillType.WOODCUTTING_HARVEST_LUMBER))
                 setDoubleDropClassicChanceStrings(skillValue, isLucky);
             else
             {
@@ -51,7 +51,7 @@ public class WoodcuttingCommand extends SkillCommand {
     }
 
     private void setDoubleDropClassicChanceStrings(float skillValue, boolean isLucky) {
-        String[] doubleDropStrings = calculateAbilityDisplayValues(skillValue, SubSkill.WOODCUTTING_HARVEST_LUMBER, isLucky);
+        String[] doubleDropStrings = calculateAbilityDisplayValues(skillValue, SubSkillType.WOODCUTTING_HARVEST_LUMBER, isLucky);
         doubleDropChance = doubleDropStrings[0];
         doubleDropChanceLucky = doubleDropStrings[1];
     }
@@ -59,11 +59,11 @@ public class WoodcuttingCommand extends SkillCommand {
     @Override
     protected void permissionsCheck(Player player) {
         canTreeFell = Permissions.treeFeller(player);
-        canDoubleDrop = Permissions.isSubSkillEnabled(player, SubSkill.WOODCUTTING_HARVEST_LUMBER) && !skill.getDoubleDropsDisabled();
-        canLeafBlow = Permissions.isSubSkillEnabled(player, SubSkill.WOODCUTTING_LEAF_BLOWER);
-        canSplinter = Permissions.isSubSkillEnabled(player, SubSkill.WOODCUTTING_SPLINTER);
-        canBarkSurgeon = Permissions.isSubSkillEnabled(player, SubSkill.WOODCUTTING_BARK_SURGEON);
-        canNaturesBounty = Permissions.isSubSkillEnabled(player, SubSkill.WOODCUTTING_NATURES_BOUNTY);
+        canDoubleDrop = Permissions.isSubSkillEnabled(player, SubSkillType.WOODCUTTING_HARVEST_LUMBER) && !skill.getDoubleDropsDisabled();
+        canLeafBlow = Permissions.isSubSkillEnabled(player, SubSkillType.WOODCUTTING_LEAF_BLOWER);
+        canSplinter = Permissions.isSubSkillEnabled(player, SubSkillType.WOODCUTTING_SPLINTER);
+        canBarkSurgeon = Permissions.isSubSkillEnabled(player, SubSkillType.WOODCUTTING_BARK_SURGEON);
+        canNaturesBounty = Permissions.isSubSkillEnabled(player, SubSkillType.WOODCUTTING_NATURES_BOUNTY);
     }
 
     @Override
@@ -129,7 +129,7 @@ public class WoodcuttingCommand extends SkillCommand {
     protected List<TextComponent> getTextComponents(Player player) {
         List<TextComponent> textComponents = new ArrayList<>();
 
-        SkillTextComponentFactory.getSubSkillTextComponents(player, textComponents, PrimarySkill.WOODCUTTING);
+        TextComponentFactory.getSubSkillTextComponents(player, textComponents, PrimarySkill.WOODCUTTING);
 
         return textComponents;
     }

+ 211 - 63
src/main/java/com/gmail/nossr50/config/AdvancedConfig.java

@@ -4,14 +4,18 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
+import com.gmail.nossr50.datatypes.interactions.NotificationType;
 import com.gmail.nossr50.datatypes.skills.PrimarySkill;
-import com.gmail.nossr50.datatypes.skills.SubSkill;
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
+import com.gmail.nossr50.datatypes.skills.subskills.AbstractSubSkill;
+import com.gmail.nossr50.locale.LocaleLoader;
 import com.gmail.nossr50.skills.alchemy.Alchemy;
 import com.gmail.nossr50.skills.fishing.Fishing;
 import com.gmail.nossr50.skills.mining.BlastMining;
 import com.gmail.nossr50.skills.repair.ArcaneForging;
 import com.gmail.nossr50.skills.salvage.Salvage;
 import com.gmail.nossr50.skills.smelting.Smelting;
+import net.md_5.bungee.api.ChatColor;
 
 public class AdvancedConfig extends AutoUpdateConfigLoader {
     private static AdvancedConfig instance;
@@ -49,11 +53,11 @@ public class AdvancedConfig extends AutoUpdateConfigLoader {
         }
 
         /* ACROBATICS */
-        if (getMaxChance(SubSkill.ACROBATICS_DODGE) < 1) {
+        if (getMaxChance(SubSkillType.ACROBATICS_DODGE) < 1) {
             reason.add("Skills.Acrobatics.Dodge.ChanceMax should be at least 1!");
         }
 
-        if (getMaxBonusLevel(SubSkill.ACROBATICS_DODGE) < 1) {
+        if (getMaxBonusLevel(SubSkillType.ACROBATICS_DODGE) < 1) {
             reason.add("Skills.Acrobatics.Dodge.MaxBonusLevel should be at least 1!");
         }
 
@@ -61,11 +65,11 @@ public class AdvancedConfig extends AutoUpdateConfigLoader {
             reason.add("Skills.Acrobatics.Dodge.DamageModifier should be greater than 1!");
         }
 
-        if (getMaxChance(SubSkill.ACROBATICS_ROLL) < 1) {
+        if (getMaxChance(SubSkillType.ACROBATICS_ROLL) < 1) {
             reason.add("Skills.Acrobatics.Roll.ChanceMax should be at least 1!");
         }
 
-        if (getMaxBonusLevel(SubSkill.ACROBATICS_ROLL) < 1) {
+        if (getMaxBonusLevel(SubSkillType.ACROBATICS_ROLL) < 1) {
             reason.add("Skills.Acrobatics.Roll.MaxBonusLevel should be at least 1!");
         }
 
@@ -73,14 +77,6 @@ public class AdvancedConfig extends AutoUpdateConfigLoader {
             reason.add("Skills.Acrobatics.Roll.DamageThreshold should be at least 0!");
         }
 
-        if (getMaxChance(SubSkill.ACROBATICS_GRACEFUL_ROLL) < 1) {
-            reason.add("Skills.Acrobatics.GracefulRoll.ChanceMax should be at least 1!");
-        }
-
-        if (getMaxBonusLevel(SubSkill.ACROBATICS_GRACEFUL_ROLL) < 1) {
-            reason.add("Skills.Acrobatics.GracefulRoll.MaxBonusLevel should be at least 1!");
-        }
-
         if (getGracefulRollDamageThreshold() < 0) {
             reason.add("Skills.Acrobatics.GracefulRoll.DamageThreshold should be at least 0!");
         }
@@ -123,11 +119,11 @@ public class AdvancedConfig extends AutoUpdateConfigLoader {
             reason.add("Skills.Archery.SkillShot.RankDamageMultiplier should be greater than 0!");
         }
 
-        if (getMaxChance(SubSkill.ARCHERY_DAZE) < 1) {
+        if (getMaxChance(SubSkillType.ARCHERY_DAZE) < 1) {
             reason.add("Skills.Archery.Daze.ChanceMax should be at least 1!");
         }
 
-        if (getMaxBonusLevel(SubSkill.ARCHERY_DAZE) < 1) {
+        if (getMaxBonusLevel(SubSkillType.ARCHERY_DAZE) < 1) {
             reason.add("Skills.Archery.Daze.MaxBonusLevel should be at least 1!");
         }
 
@@ -135,11 +131,11 @@ public class AdvancedConfig extends AutoUpdateConfigLoader {
             reason.add("Skills.Archery.Daze.BonusDamage should be at least 0!");
         }
 
-        if (getMaxChance(SubSkill.ARCHERY_ARROW_RETRIEVAL) < 1) {
+        if (getMaxChance(SubSkillType.ARCHERY_ARROW_RETRIEVAL) < 1) {
             reason.add("Skills.Archery.Retrieve.ChanceMax should be at least 1!");
         }
 
-        if (getMaxBonusLevel(SubSkill.ARCHERY_ARROW_RETRIEVAL) < 1) {
+        if (getMaxBonusLevel(SubSkillType.ARCHERY_ARROW_RETRIEVAL) < 1) {
             reason.add("Skills.Archery.Retrieve.MaxBonusLevel should be at least 1!");
         }
 
@@ -153,11 +149,11 @@ public class AdvancedConfig extends AutoUpdateConfigLoader {
             reason.add("Skills.Axes.AxeMastery.RankDamageMultiplier should be at least 0!");
         }
 
-        if (getMaxChance(SubSkill.AXES_CRITICAL_STRIKES) < 1) {
+        if (getMaxChance(SubSkillType.AXES_CRITICAL_STRIKES) < 1) {
             reason.add("Skills.Axes.CriticalHit.ChanceMax should be at least 1!");
         }
 
-        if (getMaxBonusLevel(SubSkill.AXES_CRITICAL_STRIKES) < 1) {
+        if (getMaxBonusLevel(SubSkillType.AXES_CRITICAL_STRIKES) < 1) {
             reason.add("Skills.Axes.CriticalHit.MaxBonusLevel should be at least 1!");
         }
 
@@ -259,44 +255,44 @@ public class AdvancedConfig extends AutoUpdateConfigLoader {
             reason.add("Skills.Herbalism.GreenThumb.StageChange should be at least 1!");
         }
 
-        if (getMaxChance(SubSkill.HERBALISM_GREEN_THUMB) < 1) {
+        if (getMaxChance(SubSkillType.HERBALISM_GREEN_THUMB) < 1) {
             reason.add("Skills.Herbalism.GreenThumb.ChanceMax should be at least 1!");
         }
 
-        if (getMaxBonusLevel(SubSkill.HERBALISM_GREEN_THUMB) < 1) {
+        if (getMaxBonusLevel(SubSkillType.HERBALISM_GREEN_THUMB) < 1) {
             reason.add("Skills.Herbalism.GreenThumb.MaxBonusLevel should be at least 1!");
         }
 
-        if (getMaxChance(SubSkill.HERBALISM_DOUBLE_DROPS) < 1) {
+        if (getMaxChance(SubSkillType.HERBALISM_DOUBLE_DROPS) < 1) {
             reason.add("Skills.Herbalism.DoubleDrops.ChanceMax should be at least 1!");
         }
 
-        if (getMaxBonusLevel(SubSkill.HERBALISM_DOUBLE_DROPS) < 1) {
+        if (getMaxBonusLevel(SubSkillType.HERBALISM_DOUBLE_DROPS) < 1) {
             reason.add("Skills.Herbalism.DoubleDrops.MaxBonusLevel should be at least 1!");
         }
 
-        if (getMaxChance(SubSkill.HERBALISM_HYLIAN_LUCK) < 1) {
+        if (getMaxChance(SubSkillType.HERBALISM_HYLIAN_LUCK) < 1) {
             reason.add("Skills.Herbalism.HylianLuck.ChanceMax should be at least 1!");
         }
 
-        if (getMaxBonusLevel(SubSkill.HERBALISM_HYLIAN_LUCK) < 1) {
+        if (getMaxBonusLevel(SubSkillType.HERBALISM_HYLIAN_LUCK) < 1) {
             reason.add("Skills.Herbalism.HylianLuck.MaxBonusLevel should be at least 1!");
         }
 
-        if (getMaxChance(SubSkill.HERBALISM_SHROOM_THUMB) < 1) {
+        if (getMaxChance(SubSkillType.HERBALISM_SHROOM_THUMB) < 1) {
             reason.add("Skills.Herbalism.ShroomThumb.ChanceMax should be at least 1!");
         }
 
-        if (getMaxBonusLevel(SubSkill.HERBALISM_SHROOM_THUMB) < 1) {
+        if (getMaxBonusLevel(SubSkillType.HERBALISM_SHROOM_THUMB) < 1) {
             reason.add("Skills.Herbalism.ShroomThumb.MaxBonusLevel should be at least 1!");
         }
 
         /* MINING */
-        if (getMaxChance(SubSkill.MINING_DOUBLE_DROPS) < 1) {
+        if (getMaxChance(SubSkillType.MINING_DOUBLE_DROPS) < 1) {
             reason.add("Skills.Mining.DoubleDrops.ChanceMax should be at least 1!");
         }
 
-        if (getMaxBonusLevel(SubSkill.MINING_DOUBLE_DROPS) < 1) {
+        if (getMaxBonusLevel(SubSkillType.MINING_DOUBLE_DROPS) < 1) {
             reason.add("Skills.Mining.DoubleDrops.MaxBonusLevel should be at least 1!");
         }
 
@@ -365,11 +361,11 @@ public class AdvancedConfig extends AutoUpdateConfigLoader {
             reason.add("Skills.Repair.RepairMastery.MaxBonusLevel should be at least 1!");
         }
 
-        if (getMaxChance(SubSkill.REPAIR_SUPER_REPAIR) < 1) {
+        if (getMaxChance(SubSkillType.REPAIR_SUPER_REPAIR) < 1) {
             reason.add("Skills.Repair.SuperRepair.ChanceMax should be at least 1!");
         }
 
-        if (getMaxBonusLevel(SubSkill.REPAIR_SUPER_REPAIR) < 1) {
+        if (getMaxBonusLevel(SubSkillType.REPAIR_SUPER_REPAIR) < 1) {
             reason.add("Skills.Repair.SuperRepair.MaxBonusLevel should be at least 1!");
         }
 
@@ -459,11 +455,11 @@ public class AdvancedConfig extends AutoUpdateConfigLoader {
             reason.add("Skills.Smelting.FuelEfficiency.Multiplier should be at least 1!");
         }
 
-        if (getMaxBonusLevel(SubSkill.SMELTING_SECOND_SMELT) < 1) {
+        if (getMaxBonusLevel(SubSkillType.SMELTING_SECOND_SMELT) < 1) {
             reason.add("Skills.Smelting.SecondSmelt.MaxBonusLevel should be at least 1!");
         }
 
-        if (getMaxChance(SubSkill.SMELTING_SECOND_SMELT) < 1) {
+        if (getMaxChance(SubSkillType.SMELTING_SECOND_SMELT) < 1) {
             reason.add("Skills.Smelting.SecondSmelt.ChanceMax should be at least 1!");
         }
 
@@ -500,11 +496,11 @@ public class AdvancedConfig extends AutoUpdateConfigLoader {
         }
 
         /* SWORDS */
-        if (getMaxChance(SubSkill.SWORDS_BLEED) < 1) {
+        if (getMaxChance(SubSkillType.SWORDS_BLEED) < 1) {
             reason.add("Skills.Swords.Bleed.ChanceMax should be at least 1!");
         }
 
-        if (getMaxBonusLevel(SubSkill.SWORDS_BLEED) < 1) {
+        if (getMaxBonusLevel(SubSkillType.SWORDS_BLEED) < 1) {
             reason.add("Skills.Swords.Bleed.MaxBonusLevel should be at least 1!");
         }
 
@@ -520,11 +516,11 @@ public class AdvancedConfig extends AutoUpdateConfigLoader {
             reason.add("Skills.Swords.Bleed.BaseTicks should be at least 1!");
         }
 
-        if (getMaxChance(SubSkill.SWORDS_COUNTER_ATTACK) < 1) {
+        if (getMaxChance(SubSkillType.SWORDS_COUNTER_ATTACK) < 1) {
             reason.add("Skills.Swords.CounterAttack.ChanceMax should be at least 1!");
         }
 
-        if (getMaxBonusLevel(SubSkill.SWORDS_COUNTER_ATTACK) < 1) {
+        if (getMaxBonusLevel(SubSkillType.SWORDS_COUNTER_ATTACK) < 1) {
             reason.add("Skills.Swords.CounterAttack.MaxBonusLevel should be at least 1!");
         }
 
@@ -542,11 +538,11 @@ public class AdvancedConfig extends AutoUpdateConfigLoader {
 
         /* TAMING */
 
-        if (getMaxChance(SubSkill.TAMING_GORE) < 1) {
+        if (getMaxChance(SubSkillType.TAMING_GORE) < 1) {
             reason.add("Skills.Taming.Gore.ChanceMax should be at least 1!");
         }
 
-        if (getMaxBonusLevel(SubSkill.TAMING_GORE) < 1) {
+        if (getMaxBonusLevel(SubSkillType.TAMING_GORE) < 1) {
             reason.add("Skills.Taming.Gore.MaxBonusLevel should be at least 1!");
         }
 
@@ -603,27 +599,27 @@ public class AdvancedConfig extends AutoUpdateConfigLoader {
         }
 
         /* UNARMED */
-        if (getMaxChance(SubSkill.UNARMED_DISARM) < 1) {
+        if (getMaxChance(SubSkillType.UNARMED_DISARM) < 1) {
             reason.add("Skills.Unarmed.Disarm.ChanceMax should be at least 1!");
         }
 
-        if (getMaxBonusLevel(SubSkill.UNARMED_DISARM) < 1) {
+        if (getMaxBonusLevel(SubSkillType.UNARMED_DISARM) < 1) {
             reason.add("Skills.Unarmed.Disarm.MaxBonusLevel should be at least 1!");
         }
 
-        if (getMaxChance(SubSkill.UNARMED_ARROW_DEFLECT) < 1) {
+        if (getMaxChance(SubSkillType.UNARMED_ARROW_DEFLECT) < 1) {
             reason.add("Skills.Unarmed.ArrowDeflect.ChanceMax should be at least 1!");
         }
 
-        if (getMaxBonusLevel(SubSkill.UNARMED_ARROW_DEFLECT) < 1) {
+        if (getMaxBonusLevel(SubSkillType.UNARMED_ARROW_DEFLECT) < 1) {
             reason.add("Skills.Unarmed.ArrowDeflect.MaxBonusLevel should be at least 1!");
         }
 
-        if (getMaxChance(SubSkill.UNARMED_IRON_GRIP) < 1) {
+        if (getMaxChance(SubSkillType.UNARMED_IRON_GRIP) < 1) {
             reason.add("Skills.Unarmed.IronGrip.ChanceMax should be at least 1!");
         }
 
-        if (getMaxBonusLevel(SubSkill.UNARMED_IRON_GRIP) < 1) {
+        if (getMaxBonusLevel(SubSkillType.UNARMED_IRON_GRIP) < 1) {
             reason.add("Skills.Unarmed.IronGrip.MaxBonusLevel should be at least 1!");
         }
 
@@ -649,11 +645,11 @@ public class AdvancedConfig extends AutoUpdateConfigLoader {
             reason.add("Skills.Woodcutting.LeafBlower.UnlockLevel should be at least 0!");
         }
 
-        if (getMaxChance(SubSkill.WOODCUTTING_HARVEST_LUMBER) < 1) {
+        if (getMaxChance(SubSkillType.WOODCUTTING_HARVEST_LUMBER) < 1) {
             reason.add("Skills.Woodcutting.HarvestLumber.ChanceMax should be at least 1!");
         }
 
-        if (getMaxBonusLevel(SubSkill.WOODCUTTING_HARVEST_LUMBER) < 1) {
+        if (getMaxBonusLevel(SubSkillType.WOODCUTTING_HARVEST_LUMBER) < 1) {
             reason.add("Skills.Woodcutting.HarvestLumber.MaxBonusLevel should be at least 1!");
         }
 
@@ -684,19 +680,168 @@ public class AdvancedConfig extends AutoUpdateConfigLoader {
     public int getAbilityLength() { return config.getInt("Skills.General.Ability.IncreaseLevel", 50); }
     public int getEnchantBuff() { return config.getInt("Skills.General.Ability.EnchantBuff", 5); }
 
-    public int getMaxBonusLevel(SubSkill subSkill) { return config.getInt(subSkill.getAdvConfigAddress() + ".MaxBonusLevel"); }
-    public double getMaxChance(SubSkill subSkill) { return config.getDouble(subSkill.getAdvConfigAddress() + ".ChanceMax", 100.0D);}
+    public int getMaxBonusLevel(SubSkillType subSkillType) { return config.getInt(subSkillType.getAdvConfigAddress() + ".MaxBonusLevel"); }
+    public double getMaxChance(SubSkillType subSkillType) { return config.getDouble(subSkillType.getAdvConfigAddress() + ".ChanceMax", 100.0D);}
+
+    public int getMaxBonusLevel(AbstractSubSkill abstractSubSkill) {
+        return config.getInt("Skills."+abstractSubSkill.getPrimaryKeyName()+"."+abstractSubSkill.getConfigKeyName()+".MaxBonusLevel");
+    }
+
+    public double getMaxChance(AbstractSubSkill abstractSubSkill)
+    {
+        return config.getDouble("Skills."+abstractSubSkill.getPrimaryKeyName()+"."+abstractSubSkill.getConfigKeyName()+".ChanceMax", 100.0D);
+    }
+
+    /* Interaction Settings */
+    public boolean doesNotificationUseActionBar(NotificationType notificationType)
+    {
+        String key = "";
+
+        switch(notificationType)
+        {
+            case XP_GAIN:
+                key = "ExperienceGain";
+                break;
+            case LEVEL_UP_MESSAGE:
+                key = "LevelUp";
+                break;
+            case SUBSKILL_MESSAGE:
+                key = "SubSkillInteraction";
+                break;
+            case SUBSKILL_UNLOCKED:
+                key = "SubSkillUnlocked";
+                break;
+            case TOOL_READY:
+                key = "ToolReady";
+                break;
+            case SUPER_ABILITY:
+                key = "SuperAbilityInteraction";
+                break;
+        }
+
+        return config.getBoolean("Skills.FeedBack.ActionBarNotifications."+key, true);
+    }
+
+    /*
+     * JSON Style Settings
+     */
+
+
+    /*public ChatColor getJSONStatHoverElementColor(StatType statType, boolean isPrefix)
+    {
+        String keyAddress = isPrefix ? "Prefix" : "Value";
+        String keyLocation = "Style.JSON.Hover.Details." + StringUtils.getCapitalized(statType.toString()) +"."+keyAddress+".Color";
+
+        return getChatColorFromKey(keyLocation);
+    }*/
+
+    /**
+     * Used to color our details header in our JSON Hover Object tooltips
+     * @return the ChatColor for this element
+     */
+    public ChatColor getJSONStatHoverDetailsColor()
+    {
+        String keyLocation = "Style.JSON.Hover.Details.Header.Color";
+        return getChatColorFromKey(keyLocation);
+    }
+
+    public boolean isJSONDetailsHeaderBold()
+    {
+        return config.getBoolean("Style.JSON.Hover.Details.Header.Bold");
+    }
+
+    public boolean isJSONDetailsHeaderItalic()
+    {
+        return config.getBoolean("Style.JSON.Hover.Details.Header.Italics");
+    }
+
+    public boolean isJSONDetailsHeaderUnderlined()
+    {
+        return config.getBoolean("Style.JSON.Hover.Details.Header.Underlined");
+    }
+
+    public ChatColor getJSONStatHoverDescriptionColor()
+    {
+        String keyLocation = "Style.JSON.Hover.Details.Description.Color";
+        return getChatColorFromKey(keyLocation);
+    }
+
+    public boolean isJSONDetailsDescriptionBold()
+    {
+        return config.getBoolean("Style.JSON.Hover.Details.Description.Bold");
+    }
+
+    public boolean isJSONDetailsDescriptionItalic()
+    {
+        return config.getBoolean("Style.JSON.Hover.Details.Description.Italics");
+    }
+
+    public boolean isJSONDetailsDescriptionUnderlined()
+    {
+        return config.getBoolean("Style.JSON.Hover.Details.Description.Underlined");
+    }
+
+    private ChatColor getChatColorFromKey(String keyLocation) {
+        String colorName = LocaleLoader.getString(keyLocation);
+
+        for (ChatColor chatColor : ChatColor.values()) {
+            if (colorName.equalsIgnoreCase(chatColor.toString()))
+                return chatColor;
+        }
+
+        //Invalid Color
+        System.out.println("[mcMMO] " + colorName + " is an invalid color value for key " + keyLocation);
+        return ChatColor.WHITE;
+    }
+
+    /*public boolean isJSONStatHoverElementBold(StatType statType, boolean isPrefix)
+    {
+        String keyAddress = isPrefix ? "Prefix" : "Value";
+        String keyLocation = "Style.JSON.Hover.Details." + StringUtils.getCapitalized(statType.toString()) +"."+keyAddress+".Bold";
+        return config.getBoolean(keyLocation);
+    }
+
+    public boolean isJSONStatHoverElementItalic(StatType statType, boolean isPrefix)
+    {
+        String keyAddress = isPrefix ? "Prefix" : "Value";
+        String keyLocation = "Style.JSON.Hover.Details." + StringUtils.getCapitalized(statType.toString()) +"."+keyAddress+".Italics";
+        return config.getBoolean(keyLocation);
+    }
+
+    public boolean isJSONStatHoverElementUnderlined(StatType statType, boolean isPrefix)
+    {
+        String keyAddress = isPrefix ? "Prefix" : "Value";
+        String keyLocation = "Style.JSON.Hover.Details." + StringUtils.getCapitalized(statType.toString()) +"."+keyAddress+".Underline";
+        return config.getBoolean(keyLocation);
+    }*/
 
 
     /**
      * Gets the level required to unlock a subskill at a given rank
-     * @param subSkill The subskill
+     * @param subSkillType The subskill
      * @param rank The rank of the skill
      * @return The level required to use this rank of the subskill
      * @deprecated Right now mcMMO is an overhaul process, this will only work for skills I have overhauled. I will be removing the deprecated tag when that is true.
      */
     @Deprecated
-    public int getSubSkillUnlockLevel(SubSkill subSkill, int rank)
+    public int getSubSkillUnlockLevel(SubSkillType subSkillType, int rank)
+    {
+        /*
+         * This is a bit messy but
+         *
+         * Some skills have per-rank settings as child nodes for Rank_x nodes
+         * If they do, we have to grab the child node named LevelReq from Rank_x for that skill
+         *
+         * Other skills which do not have complex per-rank settings will instead find their Level Requirement returned at Rank_x
+         */
+        if(config.get(subSkillType.getAdvConfigAddress() + ".Rank_Levels.Rank_"+rank+".LevelReq") != null)
+            return config.getInt(subSkillType.getAdvConfigAddress() + ".Rank_Levels.Rank_"+rank+".LevelReq");
+        else
+            return config.getInt(subSkillType.getAdvConfigAddress() + ".Rank_Levels.Rank_"+rank);
+    }
+
+    @Deprecated /* NEW VERSION */
+    public int getSubSkillUnlockLevel(AbstractSubSkill abstractSubSkill, int rank)
     {
         /*
          * This is a bit messy but
@@ -706,20 +851,23 @@ public class AdvancedConfig extends AutoUpdateConfigLoader {
          *
          * Other skills which do not have complex per-rank settings will instead find their Level Requirement returned at Rank_x
          */
-        if(config.get(subSkill.getAdvConfigAddress() + ".Rank_Levels.Rank_"+rank+".LevelReq") != null)
-            return config.getInt(subSkill.getAdvConfigAddress() + ".Rank_Levels.Rank_"+rank+".LevelReq");
+
+        String key = "Skills."+abstractSubSkill.getPrimaryKeyName()+"."+abstractSubSkill.getConfigKeyName();
+
+        if(config.get(key + ".Rank_Levels.Rank_"+rank+".LevelReq") != null)
+            return config.getInt(key + ".Rank_Levels.Rank_"+rank+".LevelReq");
         else
-            return config.getInt(subSkill.getAdvConfigAddress() + ".Rank_Levels.Rank_"+rank);
+            return config.getInt(key + ".Rank_Levels.Rank_"+rank);
     }
 
     /**
      * Some SubSkills have the ability to retain classic functionality
-     * @param subSkill SubSkill with classic functionality
+     * @param subSkillType SubSkillType with classic functionality
      * @return true if the subskill is in classic mode
      */
-    public boolean isSubSkillClassic(SubSkill subSkill)
+    public boolean isSubSkillClassic(SubSkillType subSkillType)
     {
-        return config.getBoolean(subSkill.getAdvConfigAddress()+".Classic");
+        return config.getBoolean(subSkillType.getAdvConfigAddress()+".Classic");
     }
 
     /* ACROBATICS */
@@ -894,29 +1042,29 @@ public class AdvancedConfig extends AutoUpdateConfigLoader {
     private void checkKeys(List<String> reasons)
     {
         //For now we will only check ranks of stuff I've overhauled
-        for(SubSkill subSkill : SubSkill.values())
+        for(SubSkillType subSkillType : SubSkillType.values())
         {
-            if(subSkill.getParentSkill() == PrimarySkill.WOODCUTTING)
+            if(subSkillType.getParentSkill() == PrimarySkill.WOODCUTTING)
             {
                 //Keeping track of the rank requirements and making sure there are no logical errors
                 int curRank = 0;
                 int prevRank = 0;
 
-                for(int x = 0; x < subSkill.getNumRanks(); x++)
+                for(int x = 0; x < subSkillType.getNumRanks(); x++)
                 {
                     if(curRank > 0)
                         prevRank = curRank;
 
-                    curRank = getSubSkillUnlockLevel(subSkill, x);
+                    curRank = getSubSkillUnlockLevel(subSkillType, x);
 
                     //Do we really care if its below 0? Probably not
                     if(curRank < 0)
-                        reasons.add(subSkill.getAdvConfigAddress() + ".Rank_Levels.Rank_"+curRank+".LevelReq should be above or equal to 0!");
+                        reasons.add(subSkillType.getAdvConfigAddress() + ".Rank_Levels.Rank_"+curRank+".LevelReq should be above or equal to 0!");
 
                     if(prevRank > curRank)
                     {
                         //We're going to allow this but we're going to warn them
-                        plugin.getLogger().info("You have the ranks for the subskill "+subSkill.toString()+" set up poorly, sequential ranks should have ascending requirements");
+                        plugin.getLogger().info("You have the ranks for the subskill "+ subSkillType.toString()+" set up poorly, sequential ranks should have ascending requirements");
                     }
                 }
             }

+ 59 - 0
src/main/java/com/gmail/nossr50/config/CoreSkillsConfig.java

@@ -0,0 +1,59 @@
+package com.gmail.nossr50.config;
+
+import com.gmail.nossr50.datatypes.skills.PrimarySkill;
+import com.gmail.nossr50.datatypes.skills.subskills.AbstractSubSkill;
+import com.gmail.nossr50.util.StringUtils;
+
+public class CoreSkillsConfig extends AutoUpdateConfigLoader {
+    private static CoreSkillsConfig instance;
+
+    public CoreSkillsConfig()
+    {
+        super("coreskills.yml");
+        validate();
+    }
+
+    @Override
+    protected void loadKeys() {
+
+    }
+
+    public static CoreSkillsConfig getInstance()
+    {
+        if(instance == null)
+            return new CoreSkillsConfig();
+
+        return instance;
+    }
+
+    @Override
+    protected boolean validateKeys() {
+
+        return true;
+    }
+
+    /*
+     * Skill Settings
+     */
+
+    /**
+     * Whether or not a skill is enabled
+     * Defaults true
+     * @param abstractSubSkill SubSkill definition to check
+     * @return true if subskill is enabled
+     */
+    public boolean isSkillEnabled(AbstractSubSkill abstractSubSkill)
+    {
+        return config.getBoolean(StringUtils.getCapitalized(abstractSubSkill.getPrimarySkill().toString())+"."+ abstractSubSkill.getConfigKeyName()+".Enabled", true);
+    }
+
+    /**
+     * Whether or not this primary skill is enabled
+     * @param primarySkill target primary skill
+     * @return true if enabled
+     */
+    public boolean isPrimarySkillEnabled(PrimarySkill primarySkill)
+    {
+        return config.getBoolean(StringUtils.getCapitalized(primarySkill.toString())+".Enabled", true);
+    }
+}

+ 13 - 0
src/main/java/com/gmail/nossr50/datatypes/interactions/NotificationType.java

@@ -0,0 +1,13 @@
+package com.gmail.nossr50.datatypes.interactions;
+
+/**
+ * This class helps define the types of information interactions we will have with players
+ */
+public enum NotificationType {
+    XP_GAIN,
+    SUBSKILL_UNLOCKED,
+    LEVEL_UP_MESSAGE,
+    SUBSKILL_MESSAGE,
+    TOOL_READY,
+    SUPER_ABILITY
+}

+ 10 - 0
src/main/java/com/gmail/nossr50/datatypes/json/CustomBaseComponent.java

@@ -0,0 +1,10 @@
+package com.gmail.nossr50.datatypes.json;
+
+import net.md_5.bungee.api.chat.BaseComponent;
+
+public class CustomBaseComponent extends BaseComponent {
+    @Override
+    public BaseComponent duplicate() {
+        return this;
+    }
+}

+ 33 - 0
src/main/java/com/gmail/nossr50/datatypes/json/McMMOUrl.java

@@ -0,0 +1,33 @@
+package com.gmail.nossr50.datatypes.json;
+
+import com.gmail.nossr50.commands.skills.McMMOWebLinks;
+
+public class McMMOUrl {
+    public static final String urlWebsite   = "https://www.mcmmo.org";
+    public static final String urlDiscord   = "https://discord.gg/bJ7pFS9";
+    public static final String urlPatreon   = "https://www.patreon.com/nossr50";
+    public static final String urlWiki      = "https://www.mcmmo.org/wiki/";
+    public static final String urlSpigot    = "http://spigot.mcmmo.org";
+    public static final String urlTranslate = "https://www.mcmmo.org/translate/";
+
+    public static String getUrl(McMMOWebLinks webLinks)
+    {
+        switch(webLinks)
+        {
+            case WIKI:
+                return urlWiki;
+            case PATREON:
+                return urlPatreon;
+            case SPIGOT:
+                return urlSpigot;
+            case DISCORD:
+                return urlDiscord;
+            case WEBSITE:
+                return urlWebsite;
+            case HELP_TRANSLATE:
+                return urlTranslate;
+            default:
+                return "https://www.mcmmo.org";
+        }
+    }
+}

+ 24 - 24
src/main/java/com/gmail/nossr50/datatypes/skills/PrimarySkill.java

@@ -36,27 +36,27 @@ import com.gmail.nossr50.util.skills.ParticleEffectUtils;
 import com.google.common.collect.ImmutableList;
 
 public enum PrimarySkill {
-    ACROBATICS(AcrobaticsManager.class, Color.WHITE, ImmutableList.of(SubSkill.ACROBATICS_DODGE, SubSkill.ACROBATICS_GRACEFUL_ROLL, SubSkill.ACROBATICS_ROLL)),
-    ALCHEMY(AlchemyManager.class, Color.FUCHSIA, ImmutableList.of(SubSkill.ALCHEMY_CATALYSIS, SubSkill.ALCHEMY_CONCOCTIONS)),
-    ARCHERY(ArcheryManager.class, Color.MAROON, ImmutableList.of(SubSkill.ARCHERY_DAZE, SubSkill.ARCHERY_ARROW_RETRIEVAL, SubSkill.ARCHERY_SKILL_SHOT)),
-    AXES(AxesManager.class, Color.AQUA, SuperAbility.SKULL_SPLITTER, ToolType.AXE, ImmutableList.of(SubSkill.AXES_SKULL_SPLITTER, SubSkill.AXES_ARMOR_IMPACT, SubSkill.AXES_AXE_MASTERY, SubSkill.AXES_CRITICAL_STRIKES, SubSkill.AXES_GREATER_IMPACT)),
-    EXCAVATION(ExcavationManager.class, Color.fromRGB(139, 69, 19), SuperAbility.GIGA_DRILL_BREAKER, ToolType.SHOVEL, ImmutableList.of(SubSkill.EXCAVATION_GIGA_DRILL_BREAKER, SubSkill.EXCAVATION_TREASURE_HUNTER)),
-    FISHING(FishingManager.class, Color.NAVY, ImmutableList.of(SubSkill.FISHING_FISHERMANS_DIET, SubSkill.FISHING_TREASURE_HUNTER, SubSkill.FISHING_ICE_FISHING, SubSkill.FISHING_MAGIC_HUNTER, SubSkill.FISHING_MASTER_ANGLER, SubSkill.FISHING_SHAKE)),
-    HERBALISM(HerbalismManager.class, Color.GREEN, SuperAbility.GREEN_TERRA, ToolType.HOE, ImmutableList.of(SubSkill.HERBALISM_FARMERS_DIET, SubSkill.HERBALISM_GREEN_THUMB, SubSkill.HERBALISM_DOUBLE_DROPS, SubSkill.HERBALISM_HYLIAN_LUCK, SubSkill.HERBALISM_SHROOM_THUMB)),
-    MINING(MiningManager.class, Color.GRAY, SuperAbility.SUPER_BREAKER, ToolType.PICKAXE, ImmutableList.of(SubSkill.MINING_SUPER_BREAKER, SubSkill.MINING_DEMOLITIONS_EXPERTISE, SubSkill.MINING_BIGGER_BOMBS, SubSkill.MINING_BLAST_MINING, SubSkill.MINING_DOUBLE_DROPS)),
-    REPAIR(RepairManager.class, Color.SILVER, ImmutableList.of(SubSkill.REPAIR_ARCANE_FORGING, SubSkill.REPAIR_REPAIR_MASTERY, SubSkill.REPAIR_SUPER_REPAIR)),
-    SALVAGE(SalvageManager.class, Color.ORANGE, ImmutableList.of(SubSkill.SALVAGE_ADVANCED_SALVAGE, SubSkill.SALVAGE_ARCANE_SALVAGE)),
-    SMELTING(SmeltingManager.class, Color.YELLOW, ImmutableList.of(SubSkill.SMELTING_FLUX_MINING, SubSkill.SMELTING_FUEL_EFFICIENCY, SubSkill.SMELTING_SECOND_SMELT)),
-    SWORDS(SwordsManager.class, Color.fromRGB(178, 34, 34), SuperAbility.SERRATED_STRIKES, ToolType.SWORD, ImmutableList.of(SubSkill.SWORDS_SERRATED_STRIKES, SubSkill.SWORDS_BLEED, SubSkill.SWORDS_COUNTER_ATTACK)),
-    TAMING(TamingManager.class, Color.PURPLE, ImmutableList.of(SubSkill.TAMING_BEAST_LORE, SubSkill.TAMING_CALL_OF_THE_WILD, SubSkill.TAMING_ENVIRONMENTALLY_AWARE, SubSkill.TAMING_FAST_FOOD_SERVICE, SubSkill.TAMING_GORE, SubSkill.TAMING_HOLY_HOUND, SubSkill.TAMING_SHARPENED_CLAWS, SubSkill.TAMING_SHOCK_PROOF, SubSkill.TAMING_THICK_FUR, SubSkill.TAMING_PUMMEL)),
-    UNARMED(UnarmedManager.class, Color.BLACK, SuperAbility.BERSERK, ToolType.FISTS, ImmutableList.of(SubSkill.UNARMED_BERSERK, SubSkill.UNARMED_BLOCK_CRACKER, SubSkill.UNARMED_ARROW_DEFLECT, SubSkill.UNARMED_DISARM, SubSkill.UNARMED_IRON_ARM_STYLE, SubSkill.UNARMED_IRON_GRIP)),
-    WOODCUTTING(WoodcuttingManager.class, Color.OLIVE, SuperAbility.TREE_FELLER, ToolType.AXE, ImmutableList.of(SubSkill.WOODCUTTING_LEAF_BLOWER, SubSkill.WOODCUTTING_BARK_SURGEON, SubSkill.WOODCUTTING_SPLINTER, SubSkill.WOODCUTTING_NATURES_BOUNTY, SubSkill.WOODCUTTING_TREE_FELLER, SubSkill.WOODCUTTING_HARVEST_LUMBER));
+    ACROBATICS(AcrobaticsManager.class, Color.WHITE, ImmutableList.of(SubSkillType.ACROBATICS_DODGE, SubSkillType.ACROBATICS_ROLL)),
+    ALCHEMY(AlchemyManager.class, Color.FUCHSIA, ImmutableList.of(SubSkillType.ALCHEMY_CATALYSIS, SubSkillType.ALCHEMY_CONCOCTIONS)),
+    ARCHERY(ArcheryManager.class, Color.MAROON, ImmutableList.of(SubSkillType.ARCHERY_DAZE, SubSkillType.ARCHERY_ARROW_RETRIEVAL, SubSkillType.ARCHERY_SKILL_SHOT)),
+    AXES(AxesManager.class, Color.AQUA, SuperAbility.SKULL_SPLITTER, ToolType.AXE, ImmutableList.of(SubSkillType.AXES_SKULL_SPLITTER, SubSkillType.AXES_ARMOR_IMPACT, SubSkillType.AXES_AXE_MASTERY, SubSkillType.AXES_CRITICAL_STRIKES, SubSkillType.AXES_GREATER_IMPACT)),
+    EXCAVATION(ExcavationManager.class, Color.fromRGB(139, 69, 19), SuperAbility.GIGA_DRILL_BREAKER, ToolType.SHOVEL, ImmutableList.of(SubSkillType.EXCAVATION_GIGA_DRILL_BREAKER, SubSkillType.EXCAVATION_TREASURE_HUNTER)),
+    FISHING(FishingManager.class, Color.NAVY, ImmutableList.of(SubSkillType.FISHING_FISHERMANS_DIET, SubSkillType.FISHING_TREASURE_HUNTER, SubSkillType.FISHING_ICE_FISHING, SubSkillType.FISHING_MAGIC_HUNTER, SubSkillType.FISHING_MASTER_ANGLER, SubSkillType.FISHING_SHAKE)),
+    HERBALISM(HerbalismManager.class, Color.GREEN, SuperAbility.GREEN_TERRA, ToolType.HOE, ImmutableList.of(SubSkillType.HERBALISM_FARMERS_DIET, SubSkillType.HERBALISM_GREEN_THUMB, SubSkillType.HERBALISM_DOUBLE_DROPS, SubSkillType.HERBALISM_HYLIAN_LUCK, SubSkillType.HERBALISM_SHROOM_THUMB)),
+    MINING(MiningManager.class, Color.GRAY, SuperAbility.SUPER_BREAKER, ToolType.PICKAXE, ImmutableList.of(SubSkillType.MINING_SUPER_BREAKER, SubSkillType.MINING_DEMOLITIONS_EXPERTISE, SubSkillType.MINING_BIGGER_BOMBS, SubSkillType.MINING_BLAST_MINING, SubSkillType.MINING_DOUBLE_DROPS)),
+    REPAIR(RepairManager.class, Color.SILVER, ImmutableList.of(SubSkillType.REPAIR_ARCANE_FORGING, SubSkillType.REPAIR_REPAIR_MASTERY, SubSkillType.REPAIR_SUPER_REPAIR)),
+    SALVAGE(SalvageManager.class, Color.ORANGE, ImmutableList.of(SubSkillType.SALVAGE_ADVANCED_SALVAGE, SubSkillType.SALVAGE_ARCANE_SALVAGE)),
+    SMELTING(SmeltingManager.class, Color.YELLOW, ImmutableList.of(SubSkillType.SMELTING_FLUX_MINING, SubSkillType.SMELTING_FUEL_EFFICIENCY, SubSkillType.SMELTING_SECOND_SMELT)),
+    SWORDS(SwordsManager.class, Color.fromRGB(178, 34, 34), SuperAbility.SERRATED_STRIKES, ToolType.SWORD, ImmutableList.of(SubSkillType.SWORDS_SERRATED_STRIKES, SubSkillType.SWORDS_BLEED, SubSkillType.SWORDS_COUNTER_ATTACK)),
+    TAMING(TamingManager.class, Color.PURPLE, ImmutableList.of(SubSkillType.TAMING_BEAST_LORE, SubSkillType.TAMING_CALL_OF_THE_WILD, SubSkillType.TAMING_ENVIRONMENTALLY_AWARE, SubSkillType.TAMING_FAST_FOOD_SERVICE, SubSkillType.TAMING_GORE, SubSkillType.TAMING_HOLY_HOUND, SubSkillType.TAMING_SHARPENED_CLAWS, SubSkillType.TAMING_SHOCK_PROOF, SubSkillType.TAMING_THICK_FUR, SubSkillType.TAMING_PUMMEL)),
+    UNARMED(UnarmedManager.class, Color.BLACK, SuperAbility.BERSERK, ToolType.FISTS, ImmutableList.of(SubSkillType.UNARMED_BERSERK, SubSkillType.UNARMED_BLOCK_CRACKER, SubSkillType.UNARMED_ARROW_DEFLECT, SubSkillType.UNARMED_DISARM, SubSkillType.UNARMED_IRON_ARM_STYLE, SubSkillType.UNARMED_IRON_GRIP)),
+    WOODCUTTING(WoodcuttingManager.class, Color.OLIVE, SuperAbility.TREE_FELLER, ToolType.AXE, ImmutableList.of(SubSkillType.WOODCUTTING_LEAF_BLOWER, SubSkillType.WOODCUTTING_BARK_SURGEON, SubSkillType.WOODCUTTING_SPLINTER, SubSkillType.WOODCUTTING_NATURES_BOUNTY, SubSkillType.WOODCUTTING_TREE_FELLER, SubSkillType.WOODCUTTING_HARVEST_LUMBER));
 
     private Class<? extends SkillManager> managerClass;
     private Color runescapeColor;
     private SuperAbility ability;
     private ToolType tool;
-    private List<SubSkill> subSkills;
+    private List<SubSkillType> subSkillTypes;
 
     public static final List<String> SKILL_NAMES;
 
@@ -90,16 +90,16 @@ public enum PrimarySkill {
         NON_CHILD_SKILLS = ImmutableList.copyOf(nonChildSkills);
     }
 
-    private PrimarySkill(Class<? extends SkillManager> managerClass, Color runescapeColor, List<SubSkill> subSkills) {
-        this(managerClass, runescapeColor, null, null, subSkills);
+    private PrimarySkill(Class<? extends SkillManager> managerClass, Color runescapeColor, List<SubSkillType> subSkillTypes) {
+        this(managerClass, runescapeColor, null, null, subSkillTypes);
     }
 
-    private PrimarySkill(Class<? extends SkillManager> managerClass, Color runescapeColor, SuperAbility ability, ToolType tool, List<SubSkill> subSkills) {
+    private PrimarySkill(Class<? extends SkillManager> managerClass, Color runescapeColor, SuperAbility ability, ToolType tool, List<SubSkillType> subSkillTypes) {
         this.managerClass = managerClass;
         this.runescapeColor = runescapeColor;
         this.ability = ability;
         this.tool = tool;
-        this.subSkills = subSkills;
+        this.subSkillTypes = subSkillTypes;
     }
 
     public Class<? extends SkillManager> getManagerClass() {
@@ -153,8 +153,8 @@ public enum PrimarySkill {
         return tool;
     }
 
-    public List<SubSkill> getSkillAbilities() {
-        return subSkills;
+    public List<SubSkillType> getSkillAbilities() {
+        return subSkillTypes;
     }
 
     public double getXpModifier() {
@@ -195,9 +195,9 @@ public enum PrimarySkill {
         }
     }
 
-    public static PrimarySkill bySecondaryAbility(SubSkill subSkill) {
+    public static PrimarySkill bySecondaryAbility(SubSkillType subSkillType) {
         for (PrimarySkill type : values()) {
-            if (type.getSkillAbilities().contains(subSkill)) {
+            if (type.getSkillAbilities().contains(subSkillType)) {
                 return type;
             }
         }

+ 12 - 5
src/main/java/com/gmail/nossr50/datatypes/skills/SubSkillFlags.java

@@ -3,10 +3,17 @@ package com.gmail.nossr50.datatypes.skills;
 public class SubSkillFlags {
     /*
      * Bitwise Flags
-     * These are so I can establish properties of each subskill quite easily
+     * These are so I can flag properties for subskills
+     * Flags are in the power of 2 because binary is a base-2 system
      */
-    public static final byte ACTIVE = 0x01; //Active subskills are ones that aren't passive
-    public static final byte SUPERABILITY = 0x02; //If the subskill is a super ability
-    public static final byte RNG = 0x04; //If the subskill makes use of RNG
-    public static final byte PVP = 0x08; //If the subskill is PVP specific
+    public static final int ACTIVE                      = 1; //Active subskills are ones that aren't passive
+    public static final int SUPERABILITY                = 2; // Super abilities are redundantly active
+    public static final int RNG                         = 4; //If the subskill makes use of RNG
+    public static final int PVP                         = 8; //If the subskill has properties that change in PVP conditions
+    public static final int TIMED                       = 16; //If the subskill has a duration or time component
+    public static final int TARGET_COLLECTION           = 32; //If the subskill has multiple target types
+    public static final int REWARD_COLLECTION           = 64; //If the subskill has multiple reward types
+    public static final int CHARGES                     = 128;
+    public static final int LIMITED                     = 256;
+    //public static final int RANDOM_ACTIVATION           = 128; //If the subskill has random activation
 }

+ 16 - 37
src/main/java/com/gmail/nossr50/datatypes/skills/SubSkill.java → src/main/java/com/gmail/nossr50/datatypes/skills/SubSkillType.java

@@ -2,26 +2,20 @@ package com.gmail.nossr50.datatypes.skills;
 
 import com.gmail.nossr50.locale.LocaleLoader;
 import com.gmail.nossr50.util.StringUtils;
-import static com.gmail.nossr50.datatypes.skills.SubSkillFlags.ACTIVE;
-import static com.gmail.nossr50.datatypes.skills.SubSkillFlags.SUPERABILITY;
-import static com.gmail.nossr50.datatypes.skills.SubSkillFlags.RNG;
-import static com.gmail.nossr50.datatypes.skills.SubSkillFlags.PVP;
 
-
-public enum SubSkill {
+public enum SubSkillType {
     /* !! Warning -- Do not let subskills share a name with any existing PrimarySkill as it will clash with the static import !! */
 
     /* ACROBATICS */
-    ACROBATICS_DODGE(0, RNG),
-    ACROBATICS_GRACEFUL_ROLL(0, ACTIVE | RNG),
-    ACROBATICS_ROLL(0, RNG),
+    ACROBATICS_DODGE,
+    ACROBATICS_ROLL,
 
     /* ALCHEMY */
     ALCHEMY_CATALYSIS,
     ALCHEMY_CONCOCTIONS(8),
 
     /* ARCHERY */
-    ARCHERY_DAZE(0, PVP),
+    ARCHERY_DAZE(),
     ARCHERY_ARROW_RETRIEVAL,
     ARCHERY_SKILL_SHOT(20),
 
@@ -30,11 +24,11 @@ public enum SubSkill {
     AXES_AXE_MASTERY(4),
     AXES_CRITICAL_STRIKES,
     AXES_GREATER_IMPACT,
-    AXES_SKULL_SPLITTER(0, ACTIVE | SUPERABILITY),
+    AXES_SKULL_SPLITTER(0),
 
     /* Excavation */
     EXCAVATION_TREASURE_HUNTER,
-    EXCAVATION_GIGA_DRILL_BREAKER(0, SUPERABILITY | ACTIVE),
+    EXCAVATION_GIGA_DRILL_BREAKER(0),
 
     /* Fishing */
     FISHING_FISHERMANS_DIET,
@@ -53,7 +47,7 @@ public enum SubSkill {
 
     /* Mining */
     MINING_DOUBLE_DROPS,
-    MINING_SUPER_BREAKER(0, SUPERABILITY | ACTIVE),
+    MINING_SUPER_BREAKER(0),
     MINING_BLAST_MINING,
     MINING_BIGGER_BOMBS,
     MINING_DEMOLITIONS_EXPERTISE,
@@ -95,48 +89,33 @@ public enum SubSkill {
     UNARMED_DISARM,
     UNARMED_IRON_ARM_STYLE,
     UNARMED_IRON_GRIP,
-    UNARMED_BERSERK(0, ACTIVE | SUPERABILITY),
+    UNARMED_BERSERK(0),
 
     /* Woodcutting */
-    WOODCUTTING_TREE_FELLER(5, ACTIVE | SUPERABILITY),
+    WOODCUTTING_TREE_FELLER(5),
     WOODCUTTING_LEAF_BLOWER(3),
-    WOODCUTTING_BARK_SURGEON(3, ACTIVE),
+    WOODCUTTING_BARK_SURGEON(3),
     WOODCUTTING_NATURES_BOUNTY(3),
     WOODCUTTING_SPLINTER(3),
-    WOODCUTTING_HARVEST_LUMBER(3, RNG);
+    WOODCUTTING_HARVEST_LUMBER(3);
 
     private final int numRanks;
     //TODO: SuperAbility should also contain flags for active by default? Not sure if it should work that way.
-    private final int flags;
 
     /**
-     * If our SubSkill has more than 1 rank define it
-     * @param numRanks The number of ranks our SubSkill has
+     * If our SubSkillType has more than 1 rank define it
+     * @param numRanks The number of ranks our SubSkillType has
      */
-    SubSkill(int numRanks, int flags)
-    {
-        this.numRanks = numRanks;
-        this.flags = flags;
-    }
-
-    SubSkill(int numRanks)
+    SubSkillType(int numRanks)
     {
         this.numRanks = numRanks;
-        this.flags = 0x00;
     }
 
-    SubSkill()
+    SubSkillType()
     {
         this.numRanks = 0;
-        this.flags = 0x00;
     }
 
-    /**
-     * Get the bit flags for this subskill
-     * @return The bit flags for this subskill
-     */
-    public final int getFlags() { return flags; }
-
     public int getNumRanks()
     {
         return numRanks;
@@ -217,7 +196,7 @@ public enum SubSkill {
     }
 
     /**
-     * This finds the substring index for our SubSkill's name after its parent name prefix
+     * This finds the substring index for our SubSkillType's name after its parent name prefix
      * @param subSkillName The name to process
      * @return The value of the substring index after our parent's prefix
      */

+ 11 - 0
src/main/java/com/gmail/nossr50/datatypes/skills/interfaces/ChildSkill.java

@@ -0,0 +1,11 @@
+package com.gmail.nossr50.datatypes.skills.interfaces;
+
+import com.gmail.nossr50.datatypes.skills.PrimarySkill;
+
+public interface ChildSkill extends Skill {
+    /**
+     * Get's the other parent for this Skill
+     * @return the other parent
+     */
+    PrimarySkill getSecondParent();
+}

+ 17 - 0
src/main/java/com/gmail/nossr50/datatypes/skills/interfaces/CoreSkill.java

@@ -0,0 +1,17 @@
+package com.gmail.nossr50.datatypes.skills.interfaces;
+
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
+
+/**
+ * This interface is mostly here to maintain backwards compatibility with other mcMMO plugins
+ * Only Core Skills will make use of this
+ * Previously in mcMMO subskills were basically defined by the SecondaryAbility ENUM
+ * In the new system which I'm gradually converting all the existing skills to, skills instead are unique instances of AbstractSubSkill
+ */
+public interface CoreSkill {
+    /**
+     * Gets the associated SubSkillType for this subskill
+     * @return the associated SubSkillType ENUM definition
+     */
+    SubSkillType getSubSkillType();
+}

+ 19 - 0
src/main/java/com/gmail/nossr50/datatypes/skills/interfaces/Localized.java

@@ -0,0 +1,19 @@
+package com.gmail.nossr50.datatypes.skills.interfaces;
+
+/**
+ * Localized interface represents skills which have localizations
+ * Skills with localizations will use their localization names/descriptions when being printed
+ */
+public interface Localized {
+    /**
+     * The translated name for this locale
+     * @return the translated name for this locale
+     */
+    String getLocaleName();
+
+    /**
+     * The translated name for this subskill description
+     * @return
+     */
+    String getLocaleDescription();
+}

+ 17 - 0
src/main/java/com/gmail/nossr50/datatypes/skills/interfaces/Skill.java

@@ -0,0 +1,17 @@
+package com.gmail.nossr50.datatypes.skills.interfaces;
+
+import com.gmail.nossr50.datatypes.skills.PrimarySkill;
+
+public interface Skill {
+    /**
+     * The primary skill
+     * @return this primary skill
+     */
+    PrimarySkill getPrimarySkill();
+
+    /**
+     * Returns the key name used for this skill in conjunction with config files
+     * @return config file key name
+     */
+    String getPrimaryKeyName();
+}

+ 20 - 0
src/main/java/com/gmail/nossr50/datatypes/skills/interfaces/Toolable.java

@@ -0,0 +1,20 @@
+package com.gmail.nossr50.datatypes.skills.interfaces;
+
+import org.bukkit.inventory.ItemStack;
+
+import java.util.Collection;
+
+public interface Toolable {
+    /**
+     * Whether or not this Skill requires a tool
+     * Not all skills will require a tool
+     * @return true if tool is required
+     */
+     boolean requiresTool();
+
+    /**
+     * The tools associated with this Skill
+     * @return the tools
+     */
+    Collection<ItemStack> getTools();
+}

+ 17 - 0
src/main/java/com/gmail/nossr50/datatypes/skills/progression/Progression.java

@@ -0,0 +1,17 @@
+package com.gmail.nossr50.datatypes.skills.progression;
+
+import com.gmail.nossr50.datatypes.skills.subskills.interfaces.InteractType;
+import org.bukkit.event.Event;
+
+public interface Progression {
+    /**
+     * The interaction vector for gaining XP
+     * @return the interaction vector for gaining XP
+     */
+    InteractType getXpGainInteractType();
+
+    /**
+     * Executes the interaction for gaining XP
+     */
+    void doXpGainInteraction(Event event);
+}

+ 43 - 0
src/main/java/com/gmail/nossr50/datatypes/skills/subskills/AbstractSubSkill.java

@@ -0,0 +1,43 @@
+package com.gmail.nossr50.datatypes.skills.subskills;
+
+import com.gmail.nossr50.config.CoreSkillsConfig;
+import com.gmail.nossr50.datatypes.skills.subskills.interfaces.Interaction;
+import com.gmail.nossr50.datatypes.skills.subskills.interfaces.Rank;
+import com.gmail.nossr50.datatypes.skills.subskills.interfaces.SubSkill;
+import com.gmail.nossr50.datatypes.skills.subskills.interfaces.SubSkillProperties;
+import com.gmail.nossr50.locale.LocaleLoader;
+
+public abstract class AbstractSubSkill implements SubSkill, Interaction, Rank, SubSkillProperties {
+    /*
+     * The name of the subskill is important is used to pull Locale strings and config settings
+     */
+    protected String configKeySubSkill;
+    protected String configKeyPrimary;
+
+    public AbstractSubSkill(String configKeySubSkill, String configKeyPrimary)
+    {
+        this.configKeySubSkill = configKeySubSkill;
+        this.configKeyPrimary = configKeyPrimary;
+    }
+
+    /**
+     * Returns the simple description of this subskill from the locale
+     *
+     * @return the simple description of this subskill from the locale
+     */
+    @Override
+    public String getDescription() {
+        return LocaleLoader.getString(getPrimaryKeyName()+".SubSkill."+getConfigKeyName()+".Description");
+    }
+
+    /**
+     * Whether or not this subskill is enabled
+     *
+     * @return true if enabled
+     */
+    @Override @Deprecated
+    public boolean isEnabled() {
+        //TODO: This might be troublesome...
+        return CoreSkillsConfig.getInstance().isSkillEnabled(this);
+    }
+}

+ 116 - 0
src/main/java/com/gmail/nossr50/datatypes/skills/subskills/acrobatics/AcrobaticsSubSkill.java

@@ -0,0 +1,116 @@
+package com.gmail.nossr50.datatypes.skills.subskills.acrobatics;
+
+import com.gmail.nossr50.datatypes.skills.PrimarySkill;
+import com.gmail.nossr50.datatypes.skills.subskills.AbstractSubSkill;
+import com.gmail.nossr50.datatypes.skills.subskills.interfaces.InteractType;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.mcMMO;
+import com.gmail.nossr50.util.StringUtils;
+import net.md_5.bungee.api.chat.ComponentBuilder;
+import org.bukkit.entity.Player;
+import org.bukkit.event.Event;
+import org.bukkit.event.EventPriority;
+
+public abstract class AcrobaticsSubSkill extends AbstractSubSkill {
+
+    protected EventPriority interactionPriority;
+
+    public AcrobaticsSubSkill(String configKeySub, EventPriority interactionPriority) {
+        super(configKeySub, "Acrobatics");
+
+        this.interactionPriority = interactionPriority;
+    }
+
+    /**
+     * The name of this subskill
+     * Core mcMMO skills will pull the name from Locale with this method
+     *
+     * @return the subskill name
+     */
+    @Override
+    public String getNiceName() {
+        return LocaleLoader.getString(getPrimaryKeyName()+".SubSkill."+getConfigKeyName()+".Name");
+    }
+
+    /**
+     * This is the name that represents our subskill in the config
+     *
+     * @return the config key name
+     */
+    @Override
+    public String getConfigKeyName() {
+        return configKeySubSkill;
+    }
+
+    /**
+     * Grabs tips for the subskill
+     *
+     * @return tips for the subskill
+     */
+    @Override
+    public String getTips() {
+        return LocaleLoader.getString("JSON."+StringUtils.getCapitalized(getPrimarySkill().toString())+".SubSkill."+getConfigKeyName()+".Details.Tips");
+    }
+
+    /**
+     * The name of the primary skill
+     *
+     * @return The name of the primary skill
+     */
+    @Override
+    public PrimarySkill getPrimarySkill() {
+        return PrimarySkill.ACROBATICS;
+    }
+
+    /**
+     * Returns the key name used for this skill in conjunction with config files
+     *
+     * @return config file key name
+     */
+    @Override
+    public String getPrimaryKeyName() {
+        return configKeyPrimary;
+    }
+
+    /**
+     * The type of event used for interaction in this subskill for Minecraft
+     *
+     * @return the event for interaction
+     */
+    @Override
+    public InteractType getInteractType() {
+        return InteractType.ON_ENTITY_DAMAGE;
+    }
+
+    /**
+     * Executes the interaction between this subskill and Minecraft
+     *
+     * @param event  the vector of interaction
+     * @param plugin the mcMMO plugin instance
+     * @return true if interaction wasn't cancelled
+     */
+    @Override
+    public boolean doInteraction(Event event, mcMMO plugin) {
+        return false;
+    }
+
+    /**
+     * The priority for this interaction
+     *
+     * @return the priority for interaction
+     */
+    @Override
+    public EventPriority getEventPriority() {
+        return interactionPriority;
+    }
+
+    /**
+     * Not all skills have ranks
+     *
+     * @return true if the skill has ranks
+     */
+    @Override
+    public boolean hasRanks() {
+        return (getNumRanks() > 0);
+    }
+}

+ 322 - 0
src/main/java/com/gmail/nossr50/datatypes/skills/subskills/acrobatics/Roll.java

@@ -0,0 +1,322 @@
+package com.gmail.nossr50.datatypes.skills.subskills.acrobatics;
+
+import com.gmail.nossr50.config.AdvancedConfig;
+import com.gmail.nossr50.config.Config;
+import com.gmail.nossr50.datatypes.interactions.NotificationType;
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
+import com.gmail.nossr50.datatypes.player.PlayerProfile;
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
+import com.gmail.nossr50.datatypes.skills.XPGainReason;
+import com.gmail.nossr50.datatypes.skills.subskills.interfaces.RandomChance;
+import com.gmail.nossr50.listeners.InteractionManager;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.mcMMO;
+import com.gmail.nossr50.skills.acrobatics.Acrobatics;
+import com.gmail.nossr50.util.EventUtils;
+import com.gmail.nossr50.util.Misc;
+import com.gmail.nossr50.util.Permissions;
+import com.gmail.nossr50.util.player.UserManager;
+import com.gmail.nossr50.util.skills.PerksUtils;
+import com.gmail.nossr50.util.skills.SkillActivationType;
+import com.gmail.nossr50.util.skills.SkillUtils;
+import net.md_5.bungee.api.chat.ComponentBuilder;
+import org.bukkit.Location;
+import org.bukkit.Material;
+import org.bukkit.enchantments.Enchantment;
+import org.bukkit.entity.Player;
+import org.bukkit.event.Event;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.entity.EntityDamageEvent;
+import org.bukkit.inventory.ItemStack;
+
+
+public class Roll extends AcrobaticsSubSkill implements RandomChance {
+    private int fallTries = 0;
+    protected Location lastFallLocation;
+
+    public Roll() {
+        super("Roll", EventPriority.HIGHEST);
+    }
+
+    /**
+     * Executes the interaction between this subskill and Minecraft
+     *
+     * @param event the vector of interaction
+     * @return true if interaction wasn't cancelled
+     */
+    @Override
+    public boolean doInteraction(Event event, mcMMO plugin) {
+        //TODO: Go through and API this up
+
+        /*
+         * Roll is a SubSkill which allows players to negate fall damage from certain heights with sufficient Acrobatics skill and luck
+         * Roll is activated when a player takes damage from a fall
+         * If a player holds shift, they double their odds at a successful roll and upon success are told they did a graceful roll.
+         */
+
+        //Casting
+        EntityDamageEvent entityDamageEvent = (EntityDamageEvent) event;
+
+        //Make sure a real player was damaged in this event
+        if(!EventUtils.isRealPlayerDamaged(entityDamageEvent))
+            return false;
+
+        switch (entityDamageEvent.getCause()) {
+            case FALL:
+
+                //Grab the player
+                McMMOPlayer mcMMOPlayer = EventUtils.getMcMMOPlayer(entityDamageEvent.getEntity());
+
+                /*
+                 * Check for success
+                 */
+                Player player = (Player) ((EntityDamageEvent) event).getEntity();
+                if (canRoll(player)) {
+                    if(Permissions.isSubSkillEnabled(player, SubSkillType.ACROBATICS_ROLL))
+                    entityDamageEvent.setDamage(rollCheck(player, mcMMOPlayer, entityDamageEvent.getDamage()));
+
+                    if (entityDamageEvent.getFinalDamage() == 0) {
+                        entityDamageEvent.setCancelled(true);
+                        return true;
+                    }
+                }
+                break;
+
+            default:
+                break;
+        }
+
+        return false;
+    }
+
+    /**
+     * Grabs the permission node for this skill
+     *
+     * @return permission node address
+     */
+    @Override
+    public String getPermissionNode() {
+        return ("mcmmo.ability."+getPrimaryKeyName()+"."+getConfigKeyName()).toLowerCase();
+    }
+
+    /**
+     * Checks if a player has permission to use this skill
+     *
+     * @param player target player
+     * @return true if player has permission
+     */
+    @Override
+    public boolean hasPermission(Player player) {
+        return Permissions.isSubSkillEnabled(player, this);
+    }
+
+    /**
+     * Adds detailed stats specific to this skill
+     *
+     * @param componentBuilder target component builder
+     * @param player target player
+     */
+    @Override
+    public void addStats(ComponentBuilder componentBuilder, Player player) {
+        String rollChance, rollChanceLucky, gracefulRollChance, gracefulRollChanceLucky;
+
+        /* Values related to the player */
+        PlayerProfile playerProfile = UserManager.getPlayer(player).getProfile();
+        float skillValue = playerProfile.getSkillLevel(getPrimarySkill());
+        boolean isLucky = Permissions.lucky(player, getPrimarySkill());
+
+        String[] rollStrings = SkillUtils.calculateAbilityDisplayValues(skillValue, SubSkillType.ACROBATICS_ROLL, isLucky);
+        rollChance = rollStrings[0];
+        rollChanceLucky = rollStrings[1];
+
+        String[] gracefulRollStrings = SkillUtils.calculateAbilityDisplayValues(skillValue, SubSkillType.ACROBATICS_ROLL, isLucky);
+        gracefulRollChance = gracefulRollStrings[0];
+        gracefulRollChanceLucky = gracefulRollStrings[1];
+
+        /*
+         *   Append the messages
+         */
+
+
+        /*componentBuilder.append(LocaleLoader.getString("Effects.Template", LocaleLoader.getString("Acrobatics.Effect.2"), LocaleLoader.getString("Acrobatics.Effect.3")));
+        componentBuilder.append("\n");*/
+
+        //Acrobatics.SubSkill.Roll.Chance
+        componentBuilder.append(LocaleLoader.getString("Acrobatics.SubSkill.Roll.Chance", rollChance) + (isLucky ? LocaleLoader.getString("Perks.Lucky.Bonus", rollChanceLucky) : ""));
+        componentBuilder.append("\n");
+        componentBuilder.append(LocaleLoader.getString("Acrobatics.SubSkill.Roll.GraceChance", gracefulRollChance) + (isLucky ? LocaleLoader.getString("Perks.Lucky.Bonus", gracefulRollChanceLucky) : ""));
+        //Activation Tips
+        componentBuilder.append("\n").append(LocaleLoader.getString("JSON.Hover.Tips")).append("\n");
+        componentBuilder.append(getTips());
+        componentBuilder.append("\n");
+        //Advanced
+
+        //Lucky Notice
+        if(isLucky)
+        {
+            componentBuilder.append(LocaleLoader.getString("JSON.JWrapper.Perks.Header"));
+            componentBuilder.append("\n");
+            componentBuilder.append(LocaleLoader.getString("JSON.JWrapper.Perks.Lucky", "33"));
+        }
+
+    }
+
+    /**
+     * Gets the maximum chance for this interaction to succeed
+     *
+     * @return maximum chance for this outcome to succeed
+     */
+    @Override
+    public double getRandomChanceMaxChance() {
+        return AdvancedConfig.getInstance().getMaxChance(this);
+    }
+
+    /**
+     * The maximum bonus level for this skill
+     * This is when the skills level no longer increases the odds of success
+     * For example, setting this to 25 will mean the RandomChance success chance no longer grows after 25
+     *
+     * @return the maximum bonus from skill level for this skill
+     */
+    @Override
+    public int getRandomChanceMaxBonus() {
+        return AdvancedConfig.getInstance().getMaxBonusLevel(this);
+    }
+
+    @Override
+    public boolean isSuperAbility() {
+        return false;
+    }
+
+    @Override
+    public boolean isActiveUse() {
+        return true;
+    }
+
+    @Override
+    public boolean isPassive() {
+        return true;
+    }
+
+    private boolean canRoll(Player player) {
+        return !exploitPrevention(player) && Permissions.isSubSkillEnabled(player, SubSkillType.ACROBATICS_ROLL);
+    }
+
+    /**
+     * Handle the damage reduction and XP gain from the Roll ability
+     *
+     * @param damage The amount of damage initially dealt by the event
+     * @return the modified event damage if the ability was successful, the original event damage otherwise
+     */
+    private double rollCheck(Player player, McMMOPlayer mcMMOPlayer, double damage) {
+
+        int skillLevel = mcMMOPlayer.getSkillLevel(getPrimarySkill());
+
+        if (player.isSneaking()) {
+            return gracefulRollCheck(player, mcMMOPlayer, damage, skillLevel);
+        }
+
+        double modifiedDamage = calculateModifiedRollDamage(damage, Acrobatics.rollThreshold);
+
+        if (!isFatal(player, modifiedDamage) && SkillUtils.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.ACROBATICS_ROLL, player, getPrimarySkill(), skillLevel, getActivationChance(mcMMOPlayer))) {
+            InteractionManager.sendPlayerInformation(player, NotificationType.SUBSKILL_MESSAGE, "Acrobatics.Roll.Text");
+            //player.sendMessage(LocaleLoader.getString("Acrobatics.Roll.Text"));
+
+            if (!SkillUtils.cooldownExpired((long) mcMMOPlayer.getTeleportATS(), Config.getInstance().getXPAfterTeleportCooldown())) {
+                SkillUtils.applyXpGain(mcMMOPlayer, getPrimarySkill(), calculateRollXP(player, damage, true), XPGainReason.PVE);
+            }
+
+            return modifiedDamage;
+        }
+        else if (!isFatal(player, damage)) {
+            if (!SkillUtils.cooldownExpired((long) mcMMOPlayer.getTeleportATS(), Config.getInstance().getXPAfterTeleportCooldown())) {
+                SkillUtils.applyXpGain(mcMMOPlayer, getPrimarySkill(), calculateRollXP(player, damage, false), XPGainReason.PVE);
+            }
+        }
+
+        lastFallLocation = player.getLocation();
+
+        return damage;
+    }
+
+    private int getActivationChance(McMMOPlayer mcMMOPlayer) {
+        return PerksUtils.handleLuckyPerks(mcMMOPlayer.getPlayer(), getPrimarySkill());
+    }
+
+    /**
+     * Handle the damage reduction and XP gain from the Graceful Roll ability
+     *
+     * @param damage The amount of damage initially dealt by the event
+     * @return the modified event damage if the ability was successful, the original event damage otherwise
+     */
+    private double gracefulRollCheck(Player player, McMMOPlayer mcMMOPlayer, double damage, int skillLevel) {
+        double modifiedDamage = calculateModifiedRollDamage(damage, Acrobatics.gracefulRollThreshold);
+
+        if (!isFatal(player, modifiedDamage) && SkillUtils.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.ACROBATICS_ROLL, player, getPrimarySkill(), skillLevel, getActivationChance(mcMMOPlayer))) {
+            InteractionManager.sendPlayerInformation(player, NotificationType.SUBSKILL_MESSAGE, "Acrobatics.Ability.Proc");
+            SkillUtils.applyXpGain(mcMMOPlayer, getPrimarySkill(), calculateRollXP(player, damage, true), XPGainReason.PVE);
+
+            return modifiedDamage;
+        }
+        else if (!isFatal(player, damage)) {
+            SkillUtils.applyXpGain(mcMMOPlayer, getPrimarySkill(), calculateRollXP(player, damage, false), XPGainReason.PVE);
+        }
+
+        return damage;
+    }
+
+    /**
+     * Check if the player is "farming" Acrobatics XP using
+     * exploits in the game.
+     *
+     * @return true if exploits are detected, false otherwise
+     */
+    private boolean exploitPrevention(Player player) {
+        if (!Config.getInstance().getAcrobaticsPreventAFK()) {
+            return false;
+        }
+
+        if (player.getInventory().getItemInMainHand().getType() == Material.ENDER_PEARL || player.isInsideVehicle()) {
+            return true;
+        }
+
+        Location fallLocation = player.getLocation();
+        int maxTries = Config.getInstance().getAcrobaticsAFKMaxTries();
+
+        boolean sameLocation = (lastFallLocation != null && Misc.isNear(lastFallLocation, fallLocation, 2));
+
+        fallTries = sameLocation ? Math.min(fallTries + 1, maxTries) : Math.max(fallTries - 1, 0);
+        lastFallLocation = fallLocation;
+
+        return fallTries + 1 > maxTries;
+    }
+
+    private float calculateRollXP(Player player, double damage, boolean isRoll) {
+        ItemStack boots = player.getInventory().getBoots();
+        float xp = (float) (damage * (isRoll ? Acrobatics.rollXpModifier : Acrobatics.fallXpModifier));
+
+        if (boots != null && boots.containsEnchantment(Enchantment.PROTECTION_FALL)) {
+            xp *= Acrobatics.featherFallXPModifier;
+        }
+
+        return xp;
+    }
+
+    protected static double calculateModifiedRollDamage(double damage, double damageThreshold) {
+        return Math.max(damage - damageThreshold, 0.0);
+    }
+
+    private boolean isFatal(Player player, double damage) {
+        return player.getHealth() - damage <= 0;
+    }
+
+    /**
+     * Gets the number of ranks for this subskill, 0 for no ranks
+     *
+     * @return the number of ranks for this subskill, 0 for no ranks
+     */
+    @Override
+    public int getNumRanks() {
+        return 0;
+    }
+}

+ 14 - 0
src/main/java/com/gmail/nossr50/datatypes/skills/subskills/interfaces/InteractType.java

@@ -0,0 +1,14 @@
+package com.gmail.nossr50.datatypes.skills.subskills.interfaces;
+
+/**
+ * This class is used to determine event registrations for SubSkill interactions
+ */
+public enum InteractType {
+    ON_BLOCK_BREAK,
+    ON_BLOCK_DAMAGE,
+    ON_PROJECTILE_LAUNCH,
+    ON_BLOCK_PLACE,
+    ON_ENTITY_DAMAGE,
+    ON_ENTITY_DAMAGE_BY_ENTITY,
+    ON_EXPLOSION_PRIME,
+}

+ 27 - 0
src/main/java/com/gmail/nossr50/datatypes/skills/subskills/interfaces/Interaction.java

@@ -0,0 +1,27 @@
+package com.gmail.nossr50.datatypes.skills.subskills.interfaces;
+
+import com.gmail.nossr50.mcMMO;
+import org.bukkit.event.Event;
+import org.bukkit.event.EventPriority;
+
+public interface Interaction {
+    /**
+     * The type of interaction this subskill has with Minecraft
+     * @return the interaction type
+     */
+    InteractType getInteractType();
+
+    /**
+     * Executes the interaction between this subskill and Minecraft
+     * @param event the vector of interaction
+     * @param plugin the mcMMO plugin instance
+     * @return true if interaction wasn't cancelled
+     */
+    boolean doInteraction(Event event, mcMMO plugin);
+
+    /**
+     * The priority for this interaction
+     * @return the priority for interaction
+     */
+    EventPriority getEventPriority();
+}

+ 17 - 0
src/main/java/com/gmail/nossr50/datatypes/skills/subskills/interfaces/RandomChance.java

@@ -0,0 +1,17 @@
+package com.gmail.nossr50.datatypes.skills.subskills.interfaces;
+
+public interface RandomChance {
+    /**
+     * Gets the maximum chance for this interaction to succeed
+     * @return maximum chance for this outcome to succeed
+     */
+    double getRandomChanceMaxChance();
+
+    /**
+     * The maximum bonus level for this skill
+     * This is when the skills level no longer increases the odds of success
+     * For example, setting this to 25 will mean the RandomChance success chance no longer grows after 25
+     * @return the maximum bonus from skill level for this skill
+     */
+    int getRandomChanceMaxBonus();
+}

+ 21 - 0
src/main/java/com/gmail/nossr50/datatypes/skills/subskills/interfaces/Rank.java

@@ -0,0 +1,21 @@
+package com.gmail.nossr50.datatypes.skills.subskills.interfaces;
+
+public interface Rank {
+    /**
+     * Gets the number of ranks for this subskill, 0 for no ranks
+     * @return the number of ranks for this subskill, 0 for no ranks
+     */
+    int getNumRanks();
+
+    /**
+     * Not all skills have ranks
+     * @return true if the skill has ranks
+     */
+    boolean hasRanks();
+
+    /**
+     * An sequential collection of rank level requirements
+     * @return level requirements
+     */
+    //Collection<Integer> getUnlockLevels();
+}

+ 58 - 0
src/main/java/com/gmail/nossr50/datatypes/skills/subskills/interfaces/SubSkill.java

@@ -0,0 +1,58 @@
+package com.gmail.nossr50.datatypes.skills.subskills.interfaces;
+
+import com.gmail.nossr50.datatypes.skills.interfaces.Skill;
+import net.md_5.bungee.api.chat.ComponentBuilder;
+import org.bukkit.entity.Player;
+
+public interface SubSkill extends Skill {
+    /**
+     * Grabs the permission node for this skill
+     * @return permission node address
+     */
+    String getPermissionNode();
+
+    /**
+     * Checks if a player has permission to use this skill
+     * @param player target player
+     * @return true if player has permission
+     */
+    boolean hasPermission(Player player);
+
+    /**
+     * The name of this subskill
+     * It's a good idea for this to return the localized name
+     * @return the subskill name
+     */
+    String getNiceName();
+
+    /**
+     * This is the name that represents our subskill in the config
+     * @return the config key name
+     */
+    String getConfigKeyName();
+
+    /**
+     * Returns the simple description of this subskill
+     * @return the simple description of this subskill
+     */
+    String getDescription();
+
+    /**
+     * Grabs tips for the subskill
+     * @return tips for the subskill
+     */
+    String getTips();
+
+    /**
+     * Adds detailed stats specific to this skill
+     * @param componentBuilder target component builder
+     * @param player owner of this skill
+     */
+    void addStats(ComponentBuilder componentBuilder, Player player);
+
+    /**
+     * Whether or not this subskill is enabled
+     * @return true if enabled
+     */
+    boolean isEnabled();
+}

+ 9 - 0
src/main/java/com/gmail/nossr50/datatypes/skills/subskills/interfaces/SubSkillProperties.java

@@ -0,0 +1,9 @@
+package com.gmail.nossr50.datatypes.skills.subskills.interfaces;
+
+public interface SubSkillProperties {
+    boolean isSuperAbility();
+
+    boolean isActiveUse();
+
+    boolean isPassive();
+}

+ 79 - 0
src/main/java/com/gmail/nossr50/events/skills/McMMOPlayerNotificationEvent.java

@@ -0,0 +1,79 @@
+package com.gmail.nossr50.events.skills;
+
+import com.gmail.nossr50.datatypes.interactions.NotificationType;
+import org.bukkit.entity.Player;
+import org.bukkit.event.Cancellable;
+import org.bukkit.event.HandlerList;
+import org.bukkit.event.player.PlayerEvent;
+
+/**
+ * This event is sent for when mcMMO informs a player about various important information
+ */
+public class McMMOPlayerNotificationEvent extends PlayerEvent implements Cancellable {
+    private boolean isCancelled;
+    private static final HandlerList handlers = new HandlerList();
+    protected String notificationMessage;
+    protected final NotificationType notificationType;
+
+    public McMMOPlayerNotificationEvent(Player who, NotificationType notificationType, String notificationMessage) {
+        super(who);
+        this.notificationType = notificationType;
+        this.notificationMessage = notificationMessage;
+        isCancelled = false;
+    }
+
+    /*
+     * Getters & Setters
+     */
+
+    /**
+     * The notification type for this event
+     * @return this event's notification type
+     */
+    public NotificationType getEventNotificationType() {
+        return notificationType;
+    }
+
+    /**
+     * The message delivered to players by this notification
+     * @return the message that will be delivered to the player
+     */
+    public String getNotificationMessage() {
+        return notificationMessage;
+    }
+
+    /**
+     * Change the notification message
+     * @param newMessage the new replacement message
+     */
+    public void setNotificationMessage(String newMessage) {
+        notificationMessage = newMessage;
+    }
+
+    /*
+     * Custom Event Boilerplate
+     */
+
+    @Override
+    public HandlerList getHandlers() {
+        return handlers;
+    }
+
+    public static HandlerList getHandlerList() {
+        return handlers;
+    }
+
+    /*
+     * Cancellable Interface Boilerplate
+     */
+
+    @Override
+    public boolean isCancelled() {
+        return isCancelled;
+    }
+
+    @Override
+    public void setCancelled(boolean b) {
+        isCancelled = b;
+    }
+}

+ 23 - 11
src/main/java/com/gmail/nossr50/events/skills/secondaryabilities/SubSkillEvent.java

@@ -1,6 +1,8 @@
 package com.gmail.nossr50.events.skills.secondaryabilities;
 
-import com.gmail.nossr50.datatypes.skills.SubSkill;
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
+import com.gmail.nossr50.datatypes.skills.subskills.AbstractSubSkill;
+import com.gmail.nossr50.datatypes.skills.subskills.interfaces.SubSkill;
 import org.bukkit.entity.Player;
 import org.bukkit.event.Cancellable;
 
@@ -8,21 +10,31 @@ import com.gmail.nossr50.datatypes.skills.PrimarySkill;
 import com.gmail.nossr50.events.skills.McMMOPlayerSkillEvent;
 
 public class SubSkillEvent extends McMMOPlayerSkillEvent implements Cancellable {
-    private SubSkill subSkill;
-    private boolean cancelled;
+    private SubSkillType subSkillType;
+    private boolean cancelled = false;
 
-    public SubSkillEvent(Player player, SubSkill subSkill) {
-        super(player, PrimarySkill.bySecondaryAbility(subSkill));
-        this.subSkill = subSkill;
-        cancelled = false;
+    /**
+     * Only skills using the old system will fire this event
+     * @param player target player
+     * @param subSkillType target subskill
+     */
+    @Deprecated
+    public SubSkillEvent(Player player, SubSkillType subSkillType) {
+        super(player, PrimarySkill.bySecondaryAbility(subSkillType));
+        this.subSkillType = subSkillType;
+    }
+
+    public SubSkillEvent(Player player, AbstractSubSkill abstractSubSkill)
+    {
+        super(player, abstractSubSkill.getPrimarySkill());
     }
 
     /**
-     * Gets the SubSkill involved in the event
-     * @return the SubSkill
+     * Gets the SubSkillType involved in the event
+     * @return the SubSkillType
      */
-    public SubSkill getSubSkill() {
-        return subSkill;
+    public SubSkillType getSubSkillType() {
+        return subSkillType;
     }
 
     public boolean isCancelled() {

+ 9 - 2
src/main/java/com/gmail/nossr50/events/skills/secondaryabilities/SubSkillWeightedActivationCheckEvent.java

@@ -1,16 +1,23 @@
 package com.gmail.nossr50.events.skills.secondaryabilities;
 
-import com.gmail.nossr50.datatypes.skills.SubSkill;
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
+import com.gmail.nossr50.datatypes.skills.subskills.AbstractSubSkill;
 import org.bukkit.entity.Player;
 
 public class SubSkillWeightedActivationCheckEvent extends SubSkillEvent {
     private double chance;
 
-    public SubSkillWeightedActivationCheckEvent(Player player, SubSkill ability, double chance) {
+    public SubSkillWeightedActivationCheckEvent(Player player, SubSkillType ability, double chance) {
         super(player, ability);
         this.chance = chance;
     }
 
+    public SubSkillWeightedActivationCheckEvent(Player player, AbstractSubSkill abstractSubSkill, double chance)
+    {
+        super(player, abstractSubSkill);
+        this.chance = chance;
+    }
+
     /**
      * Gets the activation chance of the ability 0D being no chance,  1.0D being 100% chance
      *

+ 23 - 27
src/main/java/com/gmail/nossr50/listeners/EntityListener.java

@@ -3,7 +3,8 @@ package com.gmail.nossr50.listeners;
 import com.gmail.nossr50.config.AdvancedConfig;
 import com.gmail.nossr50.config.Config;
 import com.gmail.nossr50.datatypes.player.McMMOPlayer;
-import com.gmail.nossr50.datatypes.skills.SubSkill;
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
+import com.gmail.nossr50.datatypes.skills.subskills.interfaces.InteractType;
 import com.gmail.nossr50.events.fake.FakeEntityDamageByEntityEvent;
 import com.gmail.nossr50.events.fake.FakeEntityDamageEvent;
 import com.gmail.nossr50.events.fake.FakeEntityTameEvent;
@@ -159,11 +160,14 @@ public class EntityListener implements Listener {
         }
 
 
+        /*
+        As far as I can tell at one point we registered meta-data about custom damage and we no longer do that.
 
         if (defender.hasMetadata(mcMMO.customDamageKey)) {
             defender.removeMetadata(mcMMO.customDamageKey, plugin);
             return;
         }
+        */
 
         if (Misc.isNPCEntity(defender) || !defender.isValid() || !(defender instanceof LivingEntity)) {
             return;
@@ -233,6 +237,16 @@ public class EntityListener implements Listener {
      */
     @EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
     public void onEntityDamage(EntityDamageEvent event) {
+        /*
+         * Process Registered Interactions
+         */
+
+        InteractionManager.processEvent(event, plugin, InteractType.ON_ENTITY_DAMAGE);
+
+        /*
+         * Old code
+         */
+
         if (event instanceof FakeEntityDamageEvent) {
             return;
         }
@@ -245,10 +259,13 @@ public class EntityListener implements Listener {
 
         Entity entity = event.getEntity();
 
+        /*
+        As far as I can tell at one point we registered meta-data about custom damage and we no longer do that.
         if (entity.hasMetadata(mcMMO.customDamageKey)) {
             entity.removeMetadata(mcMMO.customDamageKey, plugin);
             return;
         }
+        */
 
         if (Misc.isNPCEntity(entity) || !entity.isValid() || !(entity instanceof LivingEntity)) {
             return;
@@ -277,32 +294,11 @@ public class EntityListener implements Listener {
                 return;
             }
 
-            switch (cause) {
-                case FALL:
-                    if (!SkillUtils.cooldownExpired((long) mcMMOPlayer.getTeleportATS(), Config.getInstance().getXPAfterTeleportCooldown())) {
-                        return;
-                    }
-
-                    AcrobaticsManager acrobaticsManager = mcMMOPlayer.getAcrobaticsManager();
-
-                    if (acrobaticsManager.canRoll()) {
-                        event.setDamage(acrobaticsManager.rollCheck(event.getDamage()));
-
-                        if (event.getFinalDamage() == 0) {
-                            event.setCancelled(true);
-                            return;
-                        }
-                    }
-                    break;
-
-                default:
-                    break;
-            }
-
             if (event.getFinalDamage() >= 1) {
                 mcMMOPlayer.actualizeRecentlyHurt();
             }
         }
+
         else if (livingEntity instanceof Tameable) {
             Tameable pet = (Tameable) livingEntity;
             AnimalTamer owner = pet.getOwner();
@@ -583,7 +579,7 @@ public class EntityListener implements Listener {
                                * RESTORES 4 HUNGER - RESTORES 6 1/2 HUNGER @
                                * 1000
                                */
-                if (Permissions.isSubSkillEnabled(player, SubSkill.HERBALISM_FARMERS_DIET)) {
+                if (Permissions.isSubSkillEnabled(player, SubSkillType.HERBALISM_FARMERS_DIET)) {
                     event.setFoodLevel(UserManager.getPlayer(player).getHerbalismManager().farmersDiet(Herbalism.farmersDietRankLevel1, newFoodLevel));
                 }
                 return;
@@ -595,7 +591,7 @@ public class EntityListener implements Listener {
                                     * @ 1000
                                     */
             case POTATO: /* RESTORES 1/2 HUNGER - RESTORES 2 HUNGER @ 1000 */
-                if (Permissions.isSubSkillEnabled(player, SubSkill.HERBALISM_FARMERS_DIET)) {
+                if (Permissions.isSubSkillEnabled(player, SubSkillType.HERBALISM_FARMERS_DIET)) {
                     event.setFoodLevel(UserManager.getPlayer(player).getHerbalismManager().farmersDiet(Herbalism.farmersDietRankLevel2, newFoodLevel));
                 }
                 return;
@@ -604,13 +600,13 @@ public class EntityListener implements Listener {
                                * RESTORES 2 1/2 HUNGER - RESTORES 5 HUNGER @
                                * 1000
                                */
-                if (Permissions.isSubSkillEnabled(player, SubSkill.FISHING_FISHERMANS_DIET)) {
+                if (Permissions.isSubSkillEnabled(player, SubSkillType.FISHING_FISHERMANS_DIET)) {
                     event.setFoodLevel(UserManager.getPlayer(player).getFishingManager().handleFishermanDiet(Fishing.fishermansDietRankLevel1, newFoodLevel));
                 }
                 return;
 
             case SALMON: /* RESTORES 1 HUNGER - RESTORES 2 1/2 HUNGER @ 1000 */
-                if (Permissions.isSubSkillEnabled(player, SubSkill.FISHING_FISHERMANS_DIET)) {
+                if (Permissions.isSubSkillEnabled(player, SubSkillType.FISHING_FISHERMANS_DIET)) {
                     event.setFoodLevel(UserManager.getPlayer(player).getFishingManager().handleFishermanDiet(Fishing.fishermansDietRankLevel2, newFoodLevel));
                 }
                 return;

+ 113 - 0
src/main/java/com/gmail/nossr50/listeners/InteractionManager.java

@@ -0,0 +1,113 @@
+package com.gmail.nossr50.listeners;
+
+import com.gmail.nossr50.config.AdvancedConfig;
+import com.gmail.nossr50.datatypes.interactions.NotificationType;
+import com.gmail.nossr50.datatypes.skills.subskills.AbstractSubSkill;
+import com.gmail.nossr50.datatypes.skills.subskills.interfaces.InteractType;
+import com.gmail.nossr50.datatypes.skills.subskills.interfaces.Interaction;
+import com.gmail.nossr50.events.skills.McMMOPlayerNotificationEvent;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.mcMMO;
+import com.gmail.nossr50.util.TextComponentFactory;
+import net.md_5.bungee.api.ChatMessageType;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.event.Event;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+public class InteractionManager {
+    private static HashMap<InteractType, ArrayList<Interaction>> interactRegister;
+    private static ArrayList<AbstractSubSkill> subSkillList;
+
+    /**
+     * Registers subskills with the Interaction registration
+     * @param abstractSubSkill the target subskill to register
+     */
+    public static void registerSubSkill(AbstractSubSkill abstractSubSkill)
+    {
+        //Init map
+        if(interactRegister == null)
+            interactRegister = new HashMap<>();
+
+        if(subSkillList == null)
+            subSkillList = new ArrayList<>();
+
+        //Store a unique copy of each subskill
+        if(!subSkillList.contains(abstractSubSkill))
+            subSkillList.add(abstractSubSkill);
+
+        //Init ArrayList
+        if(interactRegister.get(abstractSubSkill.getInteractType()) == null)
+            interactRegister.put(abstractSubSkill.getInteractType(), new ArrayList<>());
+
+        //Registration array reference
+        ArrayList<Interaction> arrayRef = interactRegister.get(abstractSubSkill.getInteractType());
+
+        //Register skill
+        arrayRef.add(abstractSubSkill);
+        //TEST
+        //interactRegister.put(abstractSubSkill.getInteractType(), arrayRef);
+
+        System.out.println("[mcMMO] registered subskill: "+ abstractSubSkill.getConfigKeyName());
+    }
+
+    /**
+     * Processes the associated Interactions for this event
+     * @param event target event
+     * @param plugin instance of mcMMO plugin
+     * @param curInteractType the associated interaction type
+     */
+    public static void processEvent(Event event, mcMMO plugin, InteractType curInteractType)
+    {
+        for(Interaction interaction : interactRegister.get(curInteractType))
+        {
+            interaction.doInteraction(event, plugin);
+        }
+    }
+
+    /**
+     * Sends players notifications from mcMMO
+     * This does this by sending out an event so other plugins can cancel it
+     * @param player target player
+     * @param notificationType notifications defined type
+     * @param localeKey the locale key for the notifications defined message
+     */
+    public static void sendPlayerInformation(Player player, NotificationType notificationType, String localeKey)
+    {
+        //Init event
+        McMMOPlayerNotificationEvent customEvent = new McMMOPlayerNotificationEvent(player, notificationType, LocaleLoader.getString(localeKey));
+        //Call event
+        Bukkit.getServer().getPluginManager().callEvent(customEvent);
+
+        //Check to see if our custom event is cancelled
+        if(!customEvent.isCancelled())
+        {
+            if(AdvancedConfig.getInstance().doesNotificationUseActionBar(notificationType))
+            {
+                player.spigot().sendMessage(ChatMessageType.ACTION_BAR, TextComponentFactory.getNotificationTextComponent(customEvent.getNotificationMessage(), notificationType));
+            } else {
+                player.spigot().sendMessage(ChatMessageType.SYSTEM, TextComponentFactory.getNotificationTextComponent(customEvent.getNotificationMessage(), notificationType));
+            }
+        }
+    }
+
+    /**
+     * Returns the list that contains all unique instances of registered Interaction classes
+     * Interactions are extensions of abstract classes that represent modifying behaviours in Minecraft through events
+     * @return the unique collection of all registered Interaction classes
+     */
+    public static ArrayList<AbstractSubSkill> getSubSkillList()
+    {
+        return subSkillList;
+    }
+
+    /**
+     * Returns the associative map which contains all registered interactions
+     * @return the interact register
+     */
+    public static HashMap<InteractType, ArrayList<Interaction>> getInteractRegister() {
+        return interactRegister;
+    }
+}

+ 4 - 4
src/main/java/com/gmail/nossr50/listeners/InventoryListener.java

@@ -3,7 +3,7 @@ package com.gmail.nossr50.listeners;
 import java.util.List;
 
 import com.gmail.nossr50.datatypes.skills.PrimarySkill;
-import com.gmail.nossr50.datatypes.skills.SubSkill;
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
 import com.gmail.nossr50.events.fake.FakeBrewEvent;
 import org.bukkit.Location;
 import org.bukkit.Material;
@@ -87,7 +87,7 @@ public class InventoryListener implements Listener {
 
         Player player = getPlayerFromFurnace(furnaceBlock);
 
-        if (!UserManager.hasPlayerDataKey(player) || !Permissions.isSubSkillEnabled(player, SubSkill.SMELTING_FUEL_EFFICIENCY)) {
+        if (!UserManager.hasPlayerDataKey(player) || !Permissions.isSubSkillEnabled(player, SubSkillType.SMELTING_FUEL_EFFICIENCY)) {
             return;
         }
 
@@ -146,7 +146,7 @@ public class InventoryListener implements Listener {
 
         HumanEntity whoClicked = event.getWhoClicked();
 
-        if (!UserManager.hasPlayerDataKey(event.getWhoClicked()) || !Permissions.isSubSkillEnabled(whoClicked, SubSkill.ALCHEMY_CONCOCTIONS)) {
+        if (!UserManager.hasPlayerDataKey(event.getWhoClicked()) || !Permissions.isSubSkillEnabled(whoClicked, SubSkillType.ALCHEMY_CONCOCTIONS)) {
             return;
         }
 
@@ -245,7 +245,7 @@ public class InventoryListener implements Listener {
 
         HumanEntity whoClicked = event.getWhoClicked();
 
-        if (!UserManager.hasPlayerDataKey(event.getWhoClicked()) || !Permissions.isSubSkillEnabled(whoClicked, SubSkill.ALCHEMY_CONCOCTIONS)) {
+        if (!UserManager.hasPlayerDataKey(event.getWhoClicked()) || !Permissions.isSubSkillEnabled(whoClicked, SubSkillType.ALCHEMY_CONCOCTIONS)) {
             return;
         }
 

+ 27 - 1
src/main/java/com/gmail/nossr50/mcMMO.java

@@ -2,6 +2,7 @@ package com.gmail.nossr50;
 
 import com.gmail.nossr50.config.AdvancedConfig;
 import com.gmail.nossr50.config.Config;
+import com.gmail.nossr50.config.CoreSkillsConfig;
 import com.gmail.nossr50.config.HiddenConfig;
 import com.gmail.nossr50.config.experience.ExperienceConfig;
 import com.gmail.nossr50.config.mods.ArmorConfigManager;
@@ -14,6 +15,8 @@ import com.gmail.nossr50.config.skills.salvage.SalvageConfigManager;
 import com.gmail.nossr50.config.treasure.TreasureConfig;
 import com.gmail.nossr50.database.DatabaseManager;
 import com.gmail.nossr50.database.DatabaseManagerFactory;
+import com.gmail.nossr50.datatypes.skills.PrimarySkill;
+import com.gmail.nossr50.datatypes.skills.subskills.acrobatics.Roll;
 import com.gmail.nossr50.listeners.*;
 import com.gmail.nossr50.party.PartyManager;
 import com.gmail.nossr50.runnables.CheckDateTask;
@@ -104,7 +107,7 @@ public class mcMMO extends JavaPlugin {
     public final static String infiniteArrowKey    = "mcMMO: Infinite Arrow";
     public final static String bowForceKey         = "mcMMO: Bow Force";
     public final static String arrowDistanceKey    = "mcMMO: Arrow Distance";
-    public final static String customDamageKey     = "mcMMO: Custom Damage";
+    //public final static String customDamageKey     = "mcMMO: Custom Damage";
     public final static String disarmedItemKey     = "mcMMO: Disarmed Item";
     public final static String playerDataKey       = "mcMMO: Player Data";
     public final static String greenThumbDataKey   = "mcMMO: Green Thumb";
@@ -154,6 +157,7 @@ public class mcMMO extends JavaPlugin {
             databaseManager = DatabaseManagerFactory.getDatabaseManager();
 
             registerEvents();
+            registerCoreSkills();
             registerCustomRecipes();
 
             PartyManager.loadParties();
@@ -376,6 +380,8 @@ public class mcMMO extends JavaPlugin {
         HiddenConfig.getInstance();
         AdvancedConfig.getInstance();
         PotionConfig.getInstance();
+        CoreSkillsConfig.getInstance();
+
         new ChildConfig();
 
         List<Repairable> repairables = new ArrayList<Repairable>();
@@ -422,6 +428,26 @@ public class mcMMO extends JavaPlugin {
         pluginManager.registerEvents(new WorldListener(this), this);
     }
 
+    /**
+     * Registers core skills
+     * This enables the skills in the new skill system
+     */
+    private void registerCoreSkills() {
+        /*
+         * Acrobatics skills
+         */
+
+        if(CoreSkillsConfig.getInstance().isPrimarySkillEnabled(PrimarySkill.ACROBATICS))
+        {
+            System.out.println("[mcMMO]" + " enabling Acrobatics Skills");
+
+            //TODO: Should do this differently
+            Roll roll = new Roll();
+            CoreSkillsConfig.getInstance().isSkillEnabled(roll);
+            InteractionManager.registerSubSkill(new Roll());
+        }
+    }
+
     private void registerCustomRecipes() {
         getServer().getScheduler().scheduleSyncDelayedTask(this, () -> {
             if (Config.getInstance().getChimaeraEnabled()) {

+ 2 - 2
src/main/java/com/gmail/nossr50/runnables/skills/AlchemyBrewTask.java

@@ -1,5 +1,6 @@
 package com.gmail.nossr50.runnables.skills;
 
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
 import org.bukkit.Location;
 import org.bukkit.Material;
 import org.bukkit.block.BlockState;
@@ -8,7 +9,6 @@ import org.bukkit.entity.Player;
 import org.bukkit.scheduler.BukkitRunnable;
 
 import com.gmail.nossr50.mcMMO;
-import com.gmail.nossr50.datatypes.skills.SubSkill;
 import com.gmail.nossr50.datatypes.skills.PrimarySkill;
 import com.gmail.nossr50.events.skills.alchemy.McMMOPlayerBrewEvent;
 import com.gmail.nossr50.events.skills.alchemy.McMMOPlayerCatalysisEvent;
@@ -38,7 +38,7 @@ public class AlchemyBrewTask extends BukkitRunnable {
         brewSpeed = DEFAULT_BREW_SPEED;
         brewTimer = DEFAULT_BREW_TICKS;
 
-        if (player != null && !Misc.isNPCEntity(player) && Permissions.isSubSkillEnabled(player, SubSkill.ALCHEMY_CATALYSIS)) {
+        if (player != null && !Misc.isNPCEntity(player) && Permissions.isSubSkillEnabled(player, SubSkillType.ALCHEMY_CATALYSIS)) {
             double catalysis = UserManager.getPlayer(player).getAlchemyManager().calculateBrewSpeed(Permissions.lucky(player, PrimarySkill.ALCHEMY));
 
             McMMOPlayerCatalysisEvent event = new McMMOPlayerCatalysisEvent(player, catalysis);

+ 1 - 5
src/main/java/com/gmail/nossr50/skills/acrobatics/Acrobatics.java

@@ -17,13 +17,9 @@ public final class Acrobatics {
 
     public static boolean dodgeLightningDisabled = Config.getInstance().getDodgeLightningDisabled();
 
-    private Acrobatics() {};
+    private Acrobatics() {}
 
     protected static double calculateModifiedDodgeDamage(double damage, double damageModifier) {
         return Math.max(damage / damageModifier, 1.0);
     }
-
-    protected static double calculateModifiedRollDamage(double damage, double damageThreshold) {
-        return Math.max(damage - damageThreshold, 0.0);
-    }
 }

+ 6 - 101
src/main/java/com/gmail/nossr50/skills/acrobatics/AcrobaticsManager.java

@@ -1,7 +1,9 @@
 package com.gmail.nossr50.skills.acrobatics;
 
-import com.gmail.nossr50.datatypes.skills.SubSkill;
-import com.gmail.nossr50.util.skills.SubSkillActivationType;
+import com.gmail.nossr50.datatypes.interactions.NotificationType;
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
+import com.gmail.nossr50.listeners.InteractionManager;
+import com.gmail.nossr50.util.skills.SkillActivationType;
 import org.bukkit.Location;
 import org.bukkit.Material;
 import org.bukkit.enchantments.Enchantment;
@@ -22,19 +24,13 @@ import com.gmail.nossr50.util.skills.ParticleEffectUtils;
 import com.gmail.nossr50.util.skills.SkillUtils;
 
 public class AcrobaticsManager extends SkillManager {
-    private int fallTries = 0;
-    Location lastFallLocation;
 
     public AcrobaticsManager(McMMOPlayer mcMMOPlayer) {
         super(mcMMOPlayer, PrimarySkill.ACROBATICS);
     }
 
-    public boolean canRoll() {
-        return !exploitPrevention() && Permissions.isSubSkillEnabled(getPlayer(), SubSkill.ACROBATICS_ROLL);
-    }
-
     public boolean canDodge(Entity damager) {
-        if (Permissions.isSubSkillEnabled(getPlayer(), SubSkill.ACROBATICS_DODGE)) {
+        if (Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.ACROBATICS_DODGE)) {
             if (damager instanceof LightningStrike && Acrobatics.dodgeLightningDisabled) {
                 return false;
             }
@@ -55,7 +51,7 @@ public class AcrobaticsManager extends SkillManager {
         double modifiedDamage = Acrobatics.calculateModifiedDodgeDamage(damage, Acrobatics.dodgeDamageModifier);
         Player player = getPlayer();
 
-        if (!isFatal(modifiedDamage) && SkillUtils.isActivationSuccessful(SubSkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkill.ACROBATICS_DODGE, player, this.skill, getSkillLevel(), activationChance)) {
+        if (!isFatal(modifiedDamage) && SkillUtils.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.ACROBATICS_DODGE, player, this.skill, getSkillLevel(), activationChance)) {
             ParticleEffectUtils.playDodgeEffect(player);
 
             if (mcMMOPlayer.useChatNotifications()) {
@@ -73,98 +69,7 @@ public class AcrobaticsManager extends SkillManager {
         return damage;
     }
 
-    /**
-     * Handle the damage reduction and XP gain from the Roll ability
-     *
-     * @param damage The amount of damage initially dealt by the event
-     * @return the modified event damage if the ability was successful, the original event damage otherwise
-     */
-    public double rollCheck(double damage) {
-        Player player = getPlayer();
-
-        if (player.isSneaking() && Permissions.isSubSkillEnabled(player, SubSkill.ACROBATICS_GRACEFUL_ROLL)) {
-            return gracefulRollCheck(damage);
-        }
-
-        double modifiedDamage = Acrobatics.calculateModifiedRollDamage(damage, Acrobatics.rollThreshold);
-
-        if (!isFatal(modifiedDamage) && SkillUtils.isActivationSuccessful(SubSkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkill.ACROBATICS_ROLL, player, this.skill, getSkillLevel(), activationChance)) {
-            player.sendMessage(LocaleLoader.getString("Acrobatics.Roll.Text"));
-            applyXpGain(calculateRollXP(damage, true), XPGainReason.PVE);
-
-            return modifiedDamage;
-        }
-        else if (!isFatal(damage)) {
-            applyXpGain(calculateRollXP(damage, false), XPGainReason.PVE);
-        }
-
-        lastFallLocation = player.getLocation();
-
-        return damage;
-    }
-
-    /**
-     * Handle the damage reduction and XP gain from the Graceful Roll ability
-     *
-     * @param damage The amount of damage initially dealt by the event
-     * @return the modified event damage if the ability was successful, the original event damage otherwise
-     */
-    private double gracefulRollCheck(double damage) {
-        double modifiedDamage = Acrobatics.calculateModifiedRollDamage(damage, Acrobatics.gracefulRollThreshold);
-
-        if (!isFatal(modifiedDamage) && SkillUtils.isActivationSuccessful(SubSkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkill.ACROBATICS_GRACEFUL_ROLL, getPlayer(), this.skill, getSkillLevel(), activationChance)) {
-            getPlayer().sendMessage(LocaleLoader.getString("Acrobatics.Ability.Proc"));
-            applyXpGain(calculateRollXP(damage, true), XPGainReason.PVE);
-
-            return modifiedDamage;
-        }
-        else if (!isFatal(damage)) {
-            applyXpGain(calculateRollXP(damage, false), XPGainReason.PVE);
-        }
-
-        return damage;
-    }
-
-    /**
-     * Check if the player is "farming" Acrobatics XP using
-     * exploits in the game.
-     *
-     * @return true if exploits are detected, false otherwise
-     */
-    public boolean exploitPrevention() {
-        if (!Config.getInstance().getAcrobaticsPreventAFK()) {
-            return false;
-        }
-
-        Player player = getPlayer();
-
-        if (player.getInventory().getItemInMainHand().getType() == Material.ENDER_PEARL || player.isInsideVehicle()) {
-            return true;
-        }
-
-        Location fallLocation = player.getLocation();
-        int maxTries = Config.getInstance().getAcrobaticsAFKMaxTries();
-
-        boolean sameLocation = (lastFallLocation != null && Misc.isNear(lastFallLocation, fallLocation, 2));
-
-        fallTries = sameLocation ? Math.min(fallTries + 1, maxTries) : Math.max(fallTries - 1, 0);
-        lastFallLocation = fallLocation;
-
-        return fallTries + 1 > maxTries;
-    }
-
     private boolean isFatal(double damage) {
         return getPlayer().getHealth() - damage <= 0;
     }
-
-    private float calculateRollXP(double damage, boolean isRoll) {
-        ItemStack boots = getPlayer().getInventory().getBoots();
-        float xp = (float) (damage * (isRoll ? Acrobatics.rollXpModifier : Acrobatics.fallXpModifier));
-
-        if (boots != null && boots.containsEnchantment(Enchantment.PROTECTION_FALL)) {
-            xp *= Acrobatics.featherFallXPModifier;
-        }
-
-        return xp;
-    }
 }

+ 2 - 2
src/main/java/com/gmail/nossr50/skills/alchemy/AlchemyPotionBrewer.java

@@ -16,7 +16,7 @@ import org.bukkit.inventory.ItemStack;
 
 import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.config.skills.alchemy.PotionConfig;
-import com.gmail.nossr50.datatypes.skills.SubSkill;
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
 import com.gmail.nossr50.datatypes.skills.alchemy.AlchemyPotion;
 import com.gmail.nossr50.datatypes.skills.alchemy.PotionStage;
 import com.gmail.nossr50.events.fake.FakeBrewEvent;
@@ -94,7 +94,7 @@ public final class AlchemyPotionBrewer {
     }
 
     private static List<ItemStack> getValidIngredients(Player player) {
-        return PotionConfig.getInstance().getIngredients((player == null || !Permissions.isSubSkillEnabled(player, SubSkill.ALCHEMY_CONCOCTIONS)) ? 1 : UserManager.getPlayer(player).getAlchemyManager().getTier());
+        return PotionConfig.getInstance().getIngredients((player == null || !Permissions.isSubSkillEnabled(player, SubSkillType.ALCHEMY_CONCOCTIONS)) ? 1 : UserManager.getPlayer(player).getAlchemyManager().getTier());
     }
 
     public static void finishBrewing(BlockState brewingStand, Player player, boolean forced) {

+ 2 - 2
src/main/java/com/gmail/nossr50/skills/archery/Archery.java

@@ -4,7 +4,7 @@ import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
 
-import com.gmail.nossr50.datatypes.skills.SubSkill;
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
 import com.gmail.nossr50.util.skills.RankUtils;
 import org.bukkit.Material;
 import org.bukkit.entity.LivingEntity;
@@ -76,7 +76,7 @@ public class Archery {
      */
     public static double getSkillShotBonusDamage(Player player, double oldDamage)
     {
-        double damageBonusPercent = ((RankUtils.getRank(player, SubSkill.ARCHERY_SKILL_SHOT)) * Archery.skillShotIncreasePercentage) / 100.0D;
+        double damageBonusPercent = ((RankUtils.getRank(player, SubSkillType.ARCHERY_SKILL_SHOT)) * Archery.skillShotIncreasePercentage) / 100.0D;
         return Math.min(oldDamage * damageBonusPercent, Archery.skillShotMaxBonusDamage);
     }
 }

+ 8 - 8
src/main/java/com/gmail/nossr50/skills/archery/ArcheryManager.java

@@ -1,7 +1,7 @@
 package com.gmail.nossr50.skills.archery;
 
-import com.gmail.nossr50.datatypes.skills.SubSkill;
-import com.gmail.nossr50.util.skills.SubSkillActivationType;
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
+import com.gmail.nossr50.util.skills.SkillActivationType;
 import org.bukkit.Location;
 import org.bukkit.entity.Entity;
 import org.bukkit.entity.LivingEntity;
@@ -25,15 +25,15 @@ public class ArcheryManager extends SkillManager {
     }
 
     public boolean canDaze(LivingEntity target) {
-        return target instanceof Player && Permissions.isSubSkillEnabled(getPlayer(), SubSkill.ARCHERY_DAZE);
+        return target instanceof Player && Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.ARCHERY_DAZE);
     }
 
     public boolean canSkillShot() {
-        return getSkillLevel() >= Archery.skillShotIncreaseLevel && Permissions.isSubSkillEnabled(getPlayer(), SubSkill.ARCHERY_SKILL_SHOT);
+        return getSkillLevel() >= Archery.skillShotIncreaseLevel && Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.ARCHERY_SKILL_SHOT);
     }
 
     public boolean canRetrieveArrows() {
-        return Permissions.isSubSkillEnabled(getPlayer(), SubSkill.ARCHERY_ARROW_RETRIEVAL);
+        return Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.ARCHERY_ARROW_RETRIEVAL);
     }
 
     /**
@@ -59,7 +59,7 @@ public class ArcheryManager extends SkillManager {
      * @param target The {@link LivingEntity} damaged by the arrow
      */
     public void retrieveArrows(LivingEntity target) {
-        if (SkillUtils.isActivationSuccessful(SubSkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkill.ARCHERY_ARROW_RETRIEVAL, getPlayer(), this.skill, getSkillLevel(), activationChance)) {
+        if (SkillUtils.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.ARCHERY_ARROW_RETRIEVAL, getPlayer(), this.skill, getSkillLevel(), activationChance)) {
             Archery.incrementTrackerValue(target);
         }
     }
@@ -70,7 +70,7 @@ public class ArcheryManager extends SkillManager {
      * @param defender The {@link Player} being affected by the ability
      */
     public double daze(Player defender) {
-        if (!SkillUtils.isActivationSuccessful(SubSkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkill.ARCHERY_DAZE, getPlayer(), this.skill, getSkillLevel(), activationChance)) {
+        if (!SkillUtils.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.ARCHERY_DAZE, getPlayer(), this.skill, getSkillLevel(), activationChance)) {
             return 0;
         }
 
@@ -97,7 +97,7 @@ public class ArcheryManager extends SkillManager {
      * @param oldDamage The raw damage value of this arrow before we modify it
      */
     public double skillShot(double oldDamage) {
-        if (!SkillUtils.isActivationSuccessful(SubSkillActivationType.ALWAYS_FIRES, SubSkill.ARCHERY_SKILL_SHOT, getPlayer(), null, 0, 0)) {
+        if (!SkillUtils.isActivationSuccessful(SkillActivationType.ALWAYS_FIRES, SubSkillType.ARCHERY_SKILL_SHOT, getPlayer(), null, 0, 0)) {
             return oldDamage;
         }
 

+ 2 - 3
src/main/java/com/gmail/nossr50/skills/axes/Axes.java

@@ -1,7 +1,6 @@
 package com.gmail.nossr50.skills.axes;
 
-import com.gmail.nossr50.datatypes.player.McMMOPlayer;
-import com.gmail.nossr50.datatypes.skills.SubSkill;
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
 import com.gmail.nossr50.util.skills.RankUtils;
 import org.bukkit.entity.LivingEntity;
 import org.bukkit.entity.Player;
@@ -43,6 +42,6 @@ public class Axes {
      */
     public static double getAxeMasteryBonusDamage(Player player)
     {
-        return RankUtils.getRank(player, SubSkill.AXES_AXE_MASTERY) * Axes.axeMasteryRankDamageMultiplier;
+        return RankUtils.getRank(player, SubSkillType.AXES_AXE_MASTERY) * Axes.axeMasteryRankDamageMultiplier;
     }
 }

+ 9 - 9
src/main/java/com/gmail/nossr50/skills/axes/AxesManager.java

@@ -2,7 +2,7 @@ package com.gmail.nossr50.skills.axes;
 
 import java.util.Map;
 
-import com.gmail.nossr50.datatypes.skills.SubSkill;
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
 import com.gmail.nossr50.util.skills.*;
 import org.bukkit.entity.LivingEntity;
 import org.bukkit.entity.Player;
@@ -25,19 +25,19 @@ public class AxesManager extends SkillManager {
     }
 
     public boolean canUseAxeMastery() {
-        return Permissions.isSubSkillEnabled(getPlayer(), SubSkill.AXES_AXE_MASTERY);
+        return Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.AXES_AXE_MASTERY);
     }
 
     public boolean canCriticalHit(LivingEntity target) {
-        return target.isValid() && Permissions.isSubSkillEnabled(getPlayer(), SubSkill.AXES_CRITICAL_STRIKES);
+        return target.isValid() && Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.AXES_CRITICAL_STRIKES);
     }
 
     public boolean canImpact(LivingEntity target) {
-        return target.isValid() && Permissions.isSubSkillEnabled(getPlayer(), SubSkill.AXES_ARMOR_IMPACT) && Axes.hasArmor(target);
+        return target.isValid() && Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.AXES_ARMOR_IMPACT) && Axes.hasArmor(target);
     }
 
     public boolean canGreaterImpact(LivingEntity target) {
-        return target.isValid() && Permissions.isSubSkillEnabled(getPlayer(), SubSkill.AXES_GREATER_IMPACT) && !Axes.hasArmor(target);
+        return target.isValid() && Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.AXES_GREATER_IMPACT) && !Axes.hasArmor(target);
     }
 
     public boolean canUseSkullSplitter(LivingEntity target) {
@@ -52,7 +52,7 @@ public class AxesManager extends SkillManager {
      * Handle the effects of the Axe Mastery ability
      */
     public double axeMastery() {
-        if (!SkillUtils.isActivationSuccessful(SubSkillActivationType.ALWAYS_FIRES, SubSkill.AXES_AXE_MASTERY, getPlayer(), null, 0, 0)) {
+        if (!SkillUtils.isActivationSuccessful(SkillActivationType.ALWAYS_FIRES, SubSkillType.AXES_AXE_MASTERY, getPlayer(), null, 0, 0)) {
             return 0;
         }
 
@@ -66,7 +66,7 @@ public class AxesManager extends SkillManager {
      * @param damage The amount of damage initially dealt by the event
      */
     public double criticalHit(LivingEntity target, double damage) {
-        if (!SkillUtils.isActivationSuccessful(SubSkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkill.AXES_CRITICAL_STRIKES, getPlayer(), this.skill, getSkillLevel(), activationChance)) {
+        if (!SkillUtils.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.AXES_CRITICAL_STRIKES, getPlayer(), this.skill, getSkillLevel(), activationChance)) {
             return 0;
         }
 
@@ -102,7 +102,7 @@ public class AxesManager extends SkillManager {
 
         for (ItemStack armor : target.getEquipment().getArmorContents()) {
             if (armor != null && ItemUtils.isArmor(armor)) {
-                if (SkillUtils.isActivationSuccessful(SubSkillActivationType.RANDOM_STATIC_CHANCE, SubSkill.AXES_ARMOR_IMPACT, getPlayer(), this.skill, getSkillLevel(), activationChance)) {
+                if (SkillUtils.isActivationSuccessful(SkillActivationType.RANDOM_STATIC_CHANCE, SubSkillType.AXES_ARMOR_IMPACT, getPlayer(), this.skill, getSkillLevel(), activationChance)) {
                     SkillUtils.handleDurabilityChange(armor, durabilityDamage, Axes.impactMaxDurabilityModifier);
                 }
             }
@@ -116,7 +116,7 @@ public class AxesManager extends SkillManager {
      */
     public double greaterImpact(LivingEntity target) {
         //static chance (3rd param)
-        if (!SkillUtils.isActivationSuccessful(SubSkillActivationType.RANDOM_STATIC_CHANCE, SubSkill.AXES_GREATER_IMPACT, getPlayer(), this.skill, getSkillLevel(), activationChance)) {
+        if (!SkillUtils.isActivationSuccessful(SkillActivationType.RANDOM_STATIC_CHANCE, SubSkillType.AXES_GREATER_IMPACT, getPlayer(), this.skill, getSkillLevel(), activationChance)) {
             return 0;
         }
 

+ 2 - 2
src/main/java/com/gmail/nossr50/skills/excavation/ExcavationManager.java

@@ -8,7 +8,7 @@ import org.bukkit.block.BlockState;
 
 import com.gmail.nossr50.config.Config;
 import com.gmail.nossr50.datatypes.player.McMMOPlayer;
-import com.gmail.nossr50.datatypes.skills.SubSkill;
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
 import com.gmail.nossr50.datatypes.skills.XPGainReason;
 import com.gmail.nossr50.datatypes.treasure.ExcavationTreasure;
 import com.gmail.nossr50.skills.SkillManager;
@@ -29,7 +29,7 @@ public class ExcavationManager extends SkillManager {
     public void excavationBlockCheck(BlockState blockState) {
         int xp = Excavation.getBlockXP(blockState);
 
-        if (Permissions.isSubSkillEnabled(getPlayer(), SubSkill.EXCAVATION_TREASURE_HUNTER)) {
+        if (Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.EXCAVATION_TREASURE_HUNTER)) {
             List<ExcavationTreasure> treasures = Excavation.getTreasures(blockState);
 
             if (!treasures.isEmpty()) {

+ 7 - 7
src/main/java/com/gmail/nossr50/skills/fishing/FishingManager.java

@@ -6,7 +6,7 @@ import com.gmail.nossr50.config.experience.ExperienceConfig;
 import com.gmail.nossr50.config.treasure.TreasureConfig;
 import com.gmail.nossr50.datatypes.player.McMMOPlayer;
 import com.gmail.nossr50.datatypes.skills.PrimarySkill;
-import com.gmail.nossr50.datatypes.skills.SubSkill;
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
 import com.gmail.nossr50.datatypes.skills.XPGainReason;
 import com.gmail.nossr50.datatypes.treasure.EnchantmentTreasure;
 import com.gmail.nossr50.datatypes.treasure.FishingTreasure;
@@ -52,11 +52,11 @@ public class FishingManager extends SkillManager {
     }
 
     public boolean canShake(Entity target) {
-        return target instanceof LivingEntity && getSkillLevel() >= Tier.ONE.getLevel() && Permissions.isSubSkillEnabled(getPlayer(), SubSkill.FISHING_SHAKE);
+        return target instanceof LivingEntity && getSkillLevel() >= Tier.ONE.getLevel() && Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.FISHING_SHAKE);
     }
 
     public boolean canMasterAngler() {
-        return getSkillLevel() >= AdvancedConfig.getInstance().getMasterAnglerUnlockLevel() && Permissions.isSubSkillEnabled(getPlayer(), SubSkill.FISHING_MASTER_ANGLER);
+        return getSkillLevel() >= AdvancedConfig.getInstance().getMasterAnglerUnlockLevel() && Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.FISHING_MASTER_ANGLER);
     }
 
     public boolean unleashTheKraken() {
@@ -183,7 +183,7 @@ public class FishingManager extends SkillManager {
 
         Player player = getPlayer();
 
-        if (!Permissions.isSubSkillEnabled(getPlayer(), SubSkill.FISHING_ICE_FISHING)) {
+        if (!Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.FISHING_ICE_FISHING)) {
             return false;
         }
 
@@ -284,7 +284,7 @@ public class FishingManager extends SkillManager {
         Player player = getPlayer();
         FishingTreasure treasure = null;
 
-        if (Config.getInstance().getFishingDropsEnabled() && Permissions.isSubSkillEnabled(player, SubSkill.FISHING_TREASURE_HUNTER)) {
+        if (Config.getInstance().getFishingDropsEnabled() && Permissions.isSubSkillEnabled(player, SubSkillType.FISHING_TREASURE_HUNTER)) {
             treasure = getFishingTreasure();
             this.fishingCatch = null;
         }
@@ -293,7 +293,7 @@ public class FishingManager extends SkillManager {
             ItemStack treasureDrop = treasure.getDrop().clone(); // Not cloning is bad, m'kay?
             Map<Enchantment, Integer> enchants = new HashMap<Enchantment, Integer>();
 
-            if (Permissions.isSubSkillEnabled(player, SubSkill.FISHING_MAGIC_HUNTER) && ItemUtils.isEnchantable(treasureDrop)) {
+            if (Permissions.isSubSkillEnabled(player, SubSkillType.FISHING_MAGIC_HUNTER) && ItemUtils.isEnchantable(treasureDrop)) {
                 enchants = handleMagicHunter(treasureDrop);
             }
 
@@ -355,7 +355,7 @@ public class FishingManager extends SkillManager {
     public void shakeCheck(LivingEntity target) {
         fishingTries--; // Because autoclicking to shake is OK.
 
-        SubSkillWeightedActivationCheckEvent activationEvent = new SubSkillWeightedActivationCheckEvent(getPlayer(), SubSkill.FISHING_SHAKE, getShakeProbability() / activationChance);
+        SubSkillWeightedActivationCheckEvent activationEvent = new SubSkillWeightedActivationCheckEvent(getPlayer(), SubSkillType.FISHING_SHAKE, getShakeProbability() / activationChance);
         mcMMO.p.getServer().getPluginManager().callEvent(activationEvent);
         if ((activationEvent.getChance() * activationChance) > Misc.getRandom().nextInt(activationChance)) {
             List<ShakeTreasure> possibleDrops = Fishing.findPossibleDrops(target);

+ 10 - 10
src/main/java/com/gmail/nossr50/skills/herbalism/HerbalismManager.java

@@ -12,7 +12,7 @@ import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.runnables.skills.HerbalismBlockUpdaterTask;
 import com.gmail.nossr50.skills.SkillManager;
 import com.gmail.nossr50.util.*;
-import com.gmail.nossr50.util.skills.SubSkillActivationType;
+import com.gmail.nossr50.util.skills.SkillActivationType;
 import com.gmail.nossr50.util.skills.SkillUtils;
 import org.bukkit.Location;
 import org.bukkit.Material;
@@ -47,11 +47,11 @@ public class HerbalismManager extends SkillManager {
         PlayerInventory inventory = player.getInventory();
         Material itemType = inventory.getItemInMainHand().getType();
 
-        return (itemType == Material.BROWN_MUSHROOM || itemType == Material.RED_MUSHROOM) && inventory.contains(Material.BROWN_MUSHROOM, 1) && inventory.contains(Material.RED_MUSHROOM, 1) && BlockUtils.canMakeShroomy(blockState) && Permissions.isSubSkillEnabled(player, SubSkill.HERBALISM_SHROOM_THUMB);
+        return (itemType == Material.BROWN_MUSHROOM || itemType == Material.RED_MUSHROOM) && inventory.contains(Material.BROWN_MUSHROOM, 1) && inventory.contains(Material.RED_MUSHROOM, 1) && BlockUtils.canMakeShroomy(blockState) && Permissions.isSubSkillEnabled(player, SubSkillType.HERBALISM_SHROOM_THUMB);
     }
 
     public boolean canUseHylianLuck() {
-        return Permissions.isSubSkillEnabled(getPlayer(), SubSkill.HERBALISM_HYLIAN_LUCK);
+        return Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.HERBALISM_HYLIAN_LUCK);
     }
 
     public boolean canGreenTerraBlock(BlockState blockState) {
@@ -130,14 +130,14 @@ public class HerbalismManager extends SkillManager {
             CustomBlock customBlock = mcMMO.getModManager().getBlock(blockState);
             xp = customBlock.getXpGain();
 
-            if (Permissions.isSubSkillEnabled(player, SubSkill.HERBALISM_DOUBLE_DROPS) && customBlock.isDoubleDropEnabled()) {
+            if (Permissions.isSubSkillEnabled(player, SubSkillType.HERBALISM_DOUBLE_DROPS) && customBlock.isDoubleDropEnabled()) {
                 drops = blockState.getBlock().getDrops();
             }
         }
         else {
             xp = ExperienceConfig.getInstance().getXp(skill, blockState.getBlockData());
 
-            if (Config.getInstance().getDoubleDropsEnabled(skill, material) && Permissions.isSubSkillEnabled(player, SubSkill.HERBALISM_DOUBLE_DROPS)) {
+            if (Config.getInstance().getDoubleDropsEnabled(skill, material) && Permissions.isSubSkillEnabled(player, SubSkillType.HERBALISM_DOUBLE_DROPS)) {
                 drops = blockState.getBlock().getDrops();
             }
 
@@ -158,7 +158,7 @@ public class HerbalismManager extends SkillManager {
         }
 
         for (int i = greenTerra ? 2 : 1; i != 0; i--) {
-            if (SkillUtils.isActivationSuccessful(SubSkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkill.HERBALISM_DOUBLE_DROPS, player, this.skill, getSkillLevel(), activationChance)) {
+            if (SkillUtils.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.HERBALISM_DOUBLE_DROPS, player, this.skill, getSkillLevel(), activationChance)) {
                 for (ItemStack item : drops) {
                     Misc.dropItems(Misc.getBlockCenter(blockState), item, amount);
                 }
@@ -173,7 +173,7 @@ public class HerbalismManager extends SkillManager {
      * @return true if the ability was successful, false otherwise
      */
     public boolean processGreenThumbBlocks(BlockState blockState) {
-        if (!SkillUtils.isActivationSuccessful(SubSkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkill.HERBALISM_GREEN_THUMB, getPlayer(), this.skill, getSkillLevel(), activationChance)) {
+        if (!SkillUtils.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.HERBALISM_GREEN_THUMB, getPlayer(), this.skill, getSkillLevel(), activationChance)) {
             getPlayer().sendMessage(LocaleLoader.getString("Herbalism.Ability.GTh.Fail"));
             return false;
         }
@@ -188,7 +188,7 @@ public class HerbalismManager extends SkillManager {
      * @return true if the ability was successful, false otherwise
      */
     public boolean processHylianLuck(BlockState blockState) {
-        if (!SkillUtils.isActivationSuccessful(SubSkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkill.HERBALISM_HYLIAN_LUCK, getPlayer(), this.skill, getSkillLevel(), activationChance)) {
+        if (!SkillUtils.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.HERBALISM_HYLIAN_LUCK, getPlayer(), this.skill, getSkillLevel(), activationChance)) {
             return false;
         }
 
@@ -243,7 +243,7 @@ public class HerbalismManager extends SkillManager {
         playerInventory.removeItem(new ItemStack(Material.RED_MUSHROOM));
         player.updateInventory();
 
-        if (!SkillUtils.isActivationSuccessful(SubSkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkill.HERBALISM_SHROOM_THUMB, player, this.skill, getSkillLevel(), activationChance)) {
+        if (!SkillUtils.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.HERBALISM_SHROOM_THUMB, player, this.skill, getSkillLevel(), activationChance)) {
             player.sendMessage(LocaleLoader.getString("Herbalism.Ability.ShroomThumb.Fail"));
             return false;
         }
@@ -300,7 +300,7 @@ public class HerbalismManager extends SkillManager {
             return;
         }
 
-        if (!greenTerra && !SkillUtils.isActivationSuccessful(SubSkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkill.HERBALISM_GREEN_THUMB, player, this.skill, getSkillLevel(), activationChance)) {
+        if (!greenTerra && !SkillUtils.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.HERBALISM_GREEN_THUMB, player, this.skill, getSkillLevel(), activationChance)) {
             return;
         }
 

+ 4 - 4
src/main/java/com/gmail/nossr50/skills/mining/MiningManager.java

@@ -4,7 +4,7 @@ import com.gmail.nossr50.config.Config;
 import com.gmail.nossr50.datatypes.player.McMMOPlayer;
 import com.gmail.nossr50.datatypes.skills.SuperAbility;
 import com.gmail.nossr50.datatypes.skills.PrimarySkill;
-import com.gmail.nossr50.datatypes.skills.SubSkill;
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
 import com.gmail.nossr50.datatypes.skills.XPGainReason;
 import com.gmail.nossr50.locale.LocaleLoader;
 import com.gmail.nossr50.mcMMO;
@@ -15,7 +15,7 @@ import com.gmail.nossr50.util.BlockUtils;
 import com.gmail.nossr50.util.EventUtils;
 import com.gmail.nossr50.util.Misc;
 import com.gmail.nossr50.util.Permissions;
-import com.gmail.nossr50.util.skills.SubSkillActivationType;
+import com.gmail.nossr50.util.skills.SkillActivationType;
 import com.gmail.nossr50.util.skills.SkillUtils;
 import org.bukkit.inventory.ItemStack;
 import org.bukkit.Material;
@@ -61,7 +61,7 @@ public class MiningManager extends SkillManager {
 
         applyXpGain(Mining.getBlockXp(blockState), XPGainReason.PVE);
 
-        if (!Permissions.isSubSkillEnabled(player, SubSkill.MINING_DOUBLE_DROPS)) {
+        if (!Permissions.isSubSkillEnabled(player, SubSkillType.MINING_DOUBLE_DROPS)) {
             return;
         }
 
@@ -79,7 +79,7 @@ public class MiningManager extends SkillManager {
 
         //TODO: Make this readable
         for (int i = mcMMOPlayer.getAbilityMode(skill.getAbility()) ? 2 : 1; i != 0; i--) {
-            if (SkillUtils.isActivationSuccessful(SubSkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkill.MINING_DOUBLE_DROPS, player, this.skill, getSkillLevel(), activationChance)) {
+            if (SkillUtils.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.MINING_DOUBLE_DROPS, player, this.skill, getSkillLevel(), activationChance)) {
                 if (silkTouch) {
                     Mining.handleSilkTouchDrops(blockState);
                 }

+ 6 - 6
src/main/java/com/gmail/nossr50/skills/repair/RepairManager.java

@@ -3,7 +3,7 @@ package com.gmail.nossr50.skills.repair;
 import com.gmail.nossr50.config.Config;
 import com.gmail.nossr50.config.experience.ExperienceConfig;
 import com.gmail.nossr50.datatypes.player.McMMOPlayer;
-import com.gmail.nossr50.datatypes.skills.SubSkill;
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
 import com.gmail.nossr50.datatypes.skills.PrimarySkill;
 import com.gmail.nossr50.datatypes.skills.XPGainReason;
 import com.gmail.nossr50.locale.LocaleLoader;
@@ -15,7 +15,7 @@ import com.gmail.nossr50.util.EventUtils;
 import com.gmail.nossr50.util.Misc;
 import com.gmail.nossr50.util.Permissions;
 import com.gmail.nossr50.util.StringUtils;
-import com.gmail.nossr50.util.skills.SubSkillActivationType;
+import com.gmail.nossr50.util.skills.SkillActivationType;
 import com.gmail.nossr50.util.skills.SkillUtils;
 import org.bukkit.Material;
 import org.bukkit.Sound;
@@ -244,12 +244,12 @@ public class RepairManager extends SkillManager {
     private short repairCalculate(short durability, int repairAmount) {
         Player player = getPlayer();
 
-        if (Permissions.isSubSkillEnabled(player, SubSkill.REPAIR_REPAIR_MASTERY)) {
+        if (Permissions.isSubSkillEnabled(player, SubSkillType.REPAIR_REPAIR_MASTERY)) {
             double bonus = repairAmount * Math.min((((Repair.repairMasteryMaxBonus / Repair.repairMasteryMaxBonusLevel) * getSkillLevel()) / 100.0D), Repair.repairMasteryMaxBonus / 100.0D);
             repairAmount += bonus;
         }
 
-        if (Permissions.isSubSkillEnabled(player, SubSkill.REPAIR_SUPER_REPAIR) && checkPlayerProcRepair()) {
+        if (Permissions.isSubSkillEnabled(player, SubSkillType.REPAIR_SUPER_REPAIR) && checkPlayerProcRepair()) {
             repairAmount *= 2.0D;
         }
 
@@ -266,7 +266,7 @@ public class RepairManager extends SkillManager {
      * @return true if bonus granted, false otherwise
      */
     private boolean checkPlayerProcRepair() {
-        if (SkillUtils.isActivationSuccessful(SubSkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkill.REPAIR_SUPER_REPAIR, getPlayer(), this.skill, getSkillLevel(), activationChance)) {
+        if (SkillUtils.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.REPAIR_SUPER_REPAIR, getPlayer(), this.skill, getSkillLevel(), activationChance)) {
             getPlayer().sendMessage(LocaleLoader.getString("Repair.Skills.FeltEasy"));
             return true;
         }
@@ -293,7 +293,7 @@ public class RepairManager extends SkillManager {
             return;
         }
 
-        if (getArcaneForgingRank() == 0 || !Permissions.isSubSkillEnabled(player, SubSkill.REPAIR_ARCANE_FORGING)) {
+        if (getArcaneForgingRank() == 0 || !Permissions.isSubSkillEnabled(player, SubSkillType.REPAIR_ARCANE_FORGING)) {
             for (Enchantment enchant : enchants.keySet()) {
                 item.removeEnchantment(enchant);
             }

+ 5 - 5
src/main/java/com/gmail/nossr50/skills/smelting/SmeltingManager.java

@@ -2,7 +2,7 @@ package com.gmail.nossr50.skills.smelting;
 
 import com.gmail.nossr50.config.Config;
 import com.gmail.nossr50.datatypes.player.McMMOPlayer;
-import com.gmail.nossr50.datatypes.skills.SubSkill;
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
 import com.gmail.nossr50.datatypes.skills.PrimarySkill;
 import com.gmail.nossr50.datatypes.skills.XPGainReason;
 import com.gmail.nossr50.events.skills.secondaryabilities.SubSkillWeightedActivationCheckEvent;
@@ -16,7 +16,7 @@ import com.gmail.nossr50.util.EventUtils;
 import com.gmail.nossr50.util.Misc;
 import com.gmail.nossr50.util.Permissions;
 import com.gmail.nossr50.util.skills.ParticleEffectUtils;
-import com.gmail.nossr50.util.skills.SubSkillActivationType;
+import com.gmail.nossr50.util.skills.SkillActivationType;
 import com.gmail.nossr50.util.skills.SkillUtils;
 import org.bukkit.ChatColor;
 import org.bukkit.Material;
@@ -37,11 +37,11 @@ public class SmeltingManager extends SkillManager {
     }
 
     public boolean canUseFluxMining(BlockState blockState) {
-        return getSkillLevel() >= Smelting.fluxMiningUnlockLevel && BlockUtils.affectedByFluxMining(blockState) && Permissions.isSubSkillEnabled(getPlayer(), SubSkill.SMELTING_FLUX_MINING) && !mcMMO.getPlaceStore().isTrue(blockState);
+        return getSkillLevel() >= Smelting.fluxMiningUnlockLevel && BlockUtils.affectedByFluxMining(blockState) && Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.SMELTING_FLUX_MINING) && !mcMMO.getPlaceStore().isTrue(blockState);
     }
 
     public boolean isSecondSmeltSuccessful() {
-        return Permissions.isSubSkillEnabled(getPlayer(), SubSkill.SMELTING_SECOND_SMELT) && SkillUtils.isActivationSuccessful(SubSkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkill.SMELTING_SECOND_SMELT, getPlayer(), this.skill, getSkillLevel(), activationChance);
+        return Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.SMELTING_SECOND_SMELT) && SkillUtils.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.SMELTING_SECOND_SMELT, getPlayer(), this.skill, getSkillLevel(), activationChance);
     }
 
     /**
@@ -53,7 +53,7 @@ public class SmeltingManager extends SkillManager {
     public boolean processFluxMining(BlockState blockState) {
         Player player = getPlayer();
 
-        SubSkillWeightedActivationCheckEvent event = new SubSkillWeightedActivationCheckEvent(getPlayer(), SubSkill.SMELTING_FLUX_MINING, Smelting.fluxMiningChance / activationChance);
+        SubSkillWeightedActivationCheckEvent event = new SubSkillWeightedActivationCheckEvent(getPlayer(), SubSkillType.SMELTING_FLUX_MINING, Smelting.fluxMiningChance / activationChance);
         mcMMO.p.getServer().getPluginManager().callEvent(event);
         if ((event.getChance() * activationChance) > Misc.getRandom().nextInt(activationChance)) {
             ItemStack item = null;

+ 7 - 7
src/main/java/com/gmail/nossr50/skills/swords/SwordsManager.java

@@ -3,9 +3,9 @@ package com.gmail.nossr50.skills.swords;
 import java.util.Map;
 
 import com.gmail.nossr50.datatypes.skills.PrimarySkill;
-import com.gmail.nossr50.datatypes.skills.SubSkill;
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
 import com.gmail.nossr50.datatypes.skills.SuperAbility;
-import com.gmail.nossr50.util.skills.SubSkillActivationType;
+import com.gmail.nossr50.util.skills.SkillActivationType;
 import org.bukkit.entity.Entity;
 import org.bukkit.entity.LivingEntity;
 import org.bukkit.entity.Player;
@@ -32,11 +32,11 @@ public class SwordsManager extends SkillManager {
     }
 
     public boolean canUseBleed() {
-        return Permissions.isSubSkillEnabled(getPlayer(), SubSkill.SWORDS_BLEED);
+        return Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.SWORDS_BLEED);
     }
 
     public boolean canUseCounterAttack(Entity target) {
-        return target instanceof LivingEntity && Permissions.isSubSkillEnabled(getPlayer(), SubSkill.SWORDS_COUNTER_ATTACK);
+        return target instanceof LivingEntity && Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.SWORDS_COUNTER_ATTACK);
     }
 
     public boolean canUseSerratedStrike() {
@@ -49,9 +49,9 @@ public class SwordsManager extends SkillManager {
      * @param target The defending entity
      */
     public void bleedCheck(LivingEntity target) {
-        if (SkillUtils.isActivationSuccessful(SubSkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkill.SWORDS_BLEED, getPlayer(), this.skill, getSkillLevel(), activationChance)) {
+        if (SkillUtils.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.SWORDS_BLEED, getPlayer(), this.skill, getSkillLevel(), activationChance)) {
 
-            if (getSkillLevel() >= AdvancedConfig.getInstance().getMaxBonusLevel(SubSkill.SWORDS_BLEED)) {
+            if (getSkillLevel() >= AdvancedConfig.getInstance().getMaxBonusLevel(SubSkillType.SWORDS_BLEED)) {
                 BleedTimerTask.add(target, Swords.bleedMaxTicks);
             }
             else {
@@ -83,7 +83,7 @@ public class SwordsManager extends SkillManager {
             return;
         }
 
-        if (SkillUtils.isActivationSuccessful(SubSkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkill.SWORDS_COUNTER_ATTACK, getPlayer(), this.skill, getSkillLevel(), activationChance)) {
+        if (SkillUtils.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.SWORDS_COUNTER_ATTACK, getPlayer(), this.skill, getSkillLevel(), activationChance)) {
             CombatUtils.dealDamage(attacker, damage / Swords.counterAttackModifier, getPlayer());
 
             getPlayer().sendMessage(LocaleLoader.getString("Swords.Combat.Countered"));

+ 13 - 13
src/main/java/com/gmail/nossr50/skills/taming/TamingManager.java

@@ -5,7 +5,7 @@ import com.gmail.nossr50.config.Config;
 import com.gmail.nossr50.config.experience.ExperienceConfig;
 import com.gmail.nossr50.datatypes.player.McMMOPlayer;
 import com.gmail.nossr50.datatypes.skills.PrimarySkill;
-import com.gmail.nossr50.datatypes.skills.SubSkill;
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
 import com.gmail.nossr50.datatypes.skills.XPGainReason;
 import com.gmail.nossr50.events.fake.FakeEntityTameEvent;
 import com.gmail.nossr50.events.skills.secondaryabilities.SubSkillWeightedActivationCheckEvent;
@@ -18,7 +18,7 @@ import com.gmail.nossr50.util.Permissions;
 import com.gmail.nossr50.util.StringUtils;
 import com.gmail.nossr50.util.player.UserManager;
 import com.gmail.nossr50.util.skills.ParticleEffectUtils;
-import com.gmail.nossr50.util.skills.SubSkillActivationType;
+import com.gmail.nossr50.util.skills.SkillActivationType;
 import com.gmail.nossr50.util.skills.SkillUtils;
 import org.bukkit.Location;
 import org.bukkit.Sound;
@@ -37,35 +37,35 @@ public class TamingManager extends SkillManager {
     private static HashMap<EntityType, List<TrackedTamingEntity>> summonedEntities = new HashMap<EntityType, List<TrackedTamingEntity>>();
 
     public boolean canUseThickFur() {
-        return getSkillLevel() >= Taming.thickFurUnlockLevel && Permissions.isSubSkillEnabled(getPlayer(), SubSkill.TAMING_THICK_FUR);
+        return getSkillLevel() >= Taming.thickFurUnlockLevel && Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.TAMING_THICK_FUR);
     }
 
     public boolean canUseEnvironmentallyAware() {
-        return getSkillLevel() >= Taming.environmentallyAwareUnlockLevel && Permissions.isSubSkillEnabled(getPlayer(), SubSkill.TAMING_ENVIRONMENTALLY_AWARE);
+        return getSkillLevel() >= Taming.environmentallyAwareUnlockLevel && Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.TAMING_ENVIRONMENTALLY_AWARE);
     }
 
     public boolean canUseShockProof() {
-        return getSkillLevel() >= Taming.shockProofUnlockLevel && Permissions.isSubSkillEnabled(getPlayer(), SubSkill.TAMING_SHOCK_PROOF);
+        return getSkillLevel() >= Taming.shockProofUnlockLevel && Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.TAMING_SHOCK_PROOF);
     }
 
     public boolean canUseHolyHound() {
-        return getSkillLevel() >= Taming.holyHoundUnlockLevel && Permissions.isSubSkillEnabled(getPlayer(), SubSkill.TAMING_HOLY_HOUND);
+        return getSkillLevel() >= Taming.holyHoundUnlockLevel && Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.TAMING_HOLY_HOUND);
     }
 
     public boolean canUseFastFoodService() {
-        return getSkillLevel() >= Taming.fastFoodServiceUnlockLevel && Permissions.isSubSkillEnabled(getPlayer(), SubSkill.TAMING_FAST_FOOD_SERVICE);
+        return getSkillLevel() >= Taming.fastFoodServiceUnlockLevel && Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.TAMING_FAST_FOOD_SERVICE);
     }
 
     public boolean canUseSharpenedClaws() {
-        return getSkillLevel() >= Taming.sharpenedClawsUnlockLevel && Permissions.isSubSkillEnabled(getPlayer(), SubSkill.TAMING_SHARPENED_CLAWS);
+        return getSkillLevel() >= Taming.sharpenedClawsUnlockLevel && Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.TAMING_SHARPENED_CLAWS);
     }
 
     public boolean canUseGore() {
-        return Permissions.isSubSkillEnabled(getPlayer(), SubSkill.TAMING_GORE);
+        return Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.TAMING_GORE);
     }
 
     public boolean canUseBeastLore() {
-        return Permissions.isSubSkillEnabled(getPlayer(), SubSkill.TAMING_BEAST_LORE);
+        return Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.TAMING_BEAST_LORE);
     }
 
     /**
@@ -85,7 +85,7 @@ public class TamingManager extends SkillManager {
      */
     public void fastFoodService(Wolf wolf, double damage) {
         //static chance (3rd param)
-        if (!SkillUtils.isActivationSuccessful(SubSkillActivationType.RANDOM_STATIC_CHANCE, SubSkill.TAMING_FAST_FOOD_SERVICE, getPlayer(), this.skill, getSkillLevel(), activationChance)) {
+        if (!SkillUtils.isActivationSuccessful(SkillActivationType.RANDOM_STATIC_CHANCE, SubSkillType.TAMING_FAST_FOOD_SERVICE, getPlayer(), this.skill, getSkillLevel(), activationChance)) {
             return;
         }
 
@@ -105,7 +105,7 @@ public class TamingManager extends SkillManager {
      * @param damage The initial damage
      */
     public double gore(LivingEntity target, double damage) {
-        if (!SkillUtils.isActivationSuccessful(SubSkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkill.TAMING_GORE, getPlayer(), this.skill, getSkillLevel(), activationChance)) {
+        if (!SkillUtils.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.TAMING_GORE, getPlayer(), this.skill, getSkillLevel(), activationChance)) {
             return 0;
         }
 
@@ -190,7 +190,7 @@ public class TamingManager extends SkillManager {
 
     public void pummel(LivingEntity target, Wolf wolf) {
         double chance = 10 / activationChance;
-        SubSkillWeightedActivationCheckEvent event = new SubSkillWeightedActivationCheckEvent(getPlayer(), SubSkill.TAMING_PUMMEL, chance);
+        SubSkillWeightedActivationCheckEvent event = new SubSkillWeightedActivationCheckEvent(getPlayer(), SubSkillType.TAMING_PUMMEL, chance);
         mcMMO.p.getServer().getPluginManager().callEvent(event);
         if ((event.getChance() * activationChance) <= Misc.getRandom().nextInt(activationChance)) {
             return;

+ 11 - 11
src/main/java/com/gmail/nossr50/skills/unarmed/UnarmedManager.java

@@ -2,9 +2,9 @@ package com.gmail.nossr50.skills.unarmed;
 
 import com.gmail.nossr50.config.AdvancedConfig;
 import com.gmail.nossr50.datatypes.player.McMMOPlayer;
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
 import com.gmail.nossr50.datatypes.skills.SuperAbility;
 import com.gmail.nossr50.datatypes.skills.PrimarySkill;
-import com.gmail.nossr50.datatypes.skills.SubSkill;
 import com.gmail.nossr50.datatypes.skills.ToolType;
 import com.gmail.nossr50.locale.LocaleLoader;
 import com.gmail.nossr50.mcMMO;
@@ -14,7 +14,7 @@ import com.gmail.nossr50.util.ItemUtils;
 import com.gmail.nossr50.util.Misc;
 import com.gmail.nossr50.util.Permissions;
 import com.gmail.nossr50.util.player.UserManager;
-import com.gmail.nossr50.util.skills.SubSkillActivationType;
+import com.gmail.nossr50.util.skills.SkillActivationType;
 import com.gmail.nossr50.util.skills.SkillUtils;
 import org.bukkit.Material;
 import org.bukkit.block.BlockState;
@@ -34,7 +34,7 @@ public class UnarmedManager extends SkillManager {
     }
 
     public boolean canUseIronArm() {
-        return Permissions.isSubSkillEnabled(getPlayer(), SubSkill.UNARMED_IRON_ARM_STYLE);
+        return Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.UNARMED_IRON_ARM_STYLE);
     }
 
     public boolean canUseBerserk() {
@@ -42,21 +42,21 @@ public class UnarmedManager extends SkillManager {
     }
 
     public boolean canDisarm(LivingEntity target) {
-        return target instanceof Player && ((Player) target).getInventory().getItemInMainHand().getType() != Material.AIR && Permissions.isSubSkillEnabled(getPlayer(), SubSkill.UNARMED_DISARM);
+        return target instanceof Player && ((Player) target).getInventory().getItemInMainHand().getType() != Material.AIR && Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.UNARMED_DISARM);
     }
 
     public boolean canDeflect() {
         Player player = getPlayer();
 
-        return ItemUtils.isUnarmed(player.getInventory().getItemInMainHand()) && Permissions.isSubSkillEnabled(getPlayer(), SubSkill.UNARMED_ARROW_DEFLECT);
+        return ItemUtils.isUnarmed(player.getInventory().getItemInMainHand()) && Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.UNARMED_ARROW_DEFLECT);
     }
 
     public boolean canUseBlockCracker() {
-        return Permissions.isSubSkillEnabled(getPlayer(), SubSkill.UNARMED_BLOCK_CRACKER);
+        return Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.UNARMED_BLOCK_CRACKER);
     }
 
     public boolean blockCrackerCheck(BlockState blockState) {
-        if (!SkillUtils.isActivationSuccessful(SubSkillActivationType.ALWAYS_FIRES, SubSkill.UNARMED_BLOCK_CRACKER, getPlayer(), null, 0, 0)) {
+        if (!SkillUtils.isActivationSuccessful(SkillActivationType.ALWAYS_FIRES, SubSkillType.UNARMED_BLOCK_CRACKER, getPlayer(), null, 0, 0)) {
             return false;
         }
 
@@ -82,7 +82,7 @@ public class UnarmedManager extends SkillManager {
      * @param defender The defending player
      */
     public void disarmCheck(Player defender) {
-        if (SkillUtils.isActivationSuccessful(SubSkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkill.UNARMED_DISARM, getPlayer(), this.skill, getSkillLevel(), activationChance) && !hasIronGrip(defender)) {
+        if (SkillUtils.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.UNARMED_DISARM, getPlayer(), this.skill, getSkillLevel(), activationChance) && !hasIronGrip(defender)) {
             if (EventUtils.callDisarmEvent(defender).isCancelled()) {
                 return;
             }
@@ -102,7 +102,7 @@ public class UnarmedManager extends SkillManager {
      * Check for arrow deflection.
      */
     public boolean deflectCheck() {
-        if (SkillUtils.isActivationSuccessful(SubSkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkill.UNARMED_ARROW_DEFLECT, getPlayer(), this.skill, getSkillLevel(), activationChance)) {
+        if (SkillUtils.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.UNARMED_ARROW_DEFLECT, getPlayer(), this.skill, getSkillLevel(), activationChance)) {
             getPlayer().sendMessage(LocaleLoader.getString("Combat.ArrowDeflect"));
             return true;
         }
@@ -125,7 +125,7 @@ public class UnarmedManager extends SkillManager {
      * Handle the effects of the Iron Arm ability
      */
     public double ironArm() {
-        if (!SkillUtils.isActivationSuccessful(SubSkillActivationType.ALWAYS_FIRES, SubSkill.UNARMED_IRON_ARM_STYLE, getPlayer(), null, 0, 0)) {
+        if (!SkillUtils.isActivationSuccessful(SkillActivationType.ALWAYS_FIRES, SubSkillType.UNARMED_IRON_ARM_STYLE, getPlayer(), null, 0, 0)) {
             return 0;
         }
 
@@ -140,7 +140,7 @@ public class UnarmedManager extends SkillManager {
      * @return true if the defender was not disarmed, false otherwise
      */
     private boolean hasIronGrip(Player defender) {
-        if (!Misc.isNPCEntity(defender) && Permissions.isSubSkillEnabled(defender, SubSkill.UNARMED_IRON_GRIP) && SkillUtils.isActivationSuccessful(SubSkillActivationType.RANDOM_LINEAR_100_SCALE_NO_CAP, SubSkill.UNARMED_IRON_GRIP, getPlayer(), this.skill, getSkillLevel(), activationChance)) {
+        if (!Misc.isNPCEntity(defender) && Permissions.isSubSkillEnabled(defender, SubSkillType.UNARMED_IRON_GRIP) && SkillUtils.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_NO_CAP, SubSkillType.UNARMED_IRON_GRIP, getPlayer(), this.skill, getSkillLevel(), activationChance)) {
             defender.sendMessage(LocaleLoader.getString("Unarmed.Ability.IronGrip.Defender"));
             getPlayer().sendMessage(LocaleLoader.getString("Unarmed.Ability.IronGrip.Attacker"));
 

+ 4 - 4
src/main/java/com/gmail/nossr50/skills/woodcutting/WoodcuttingManager.java

@@ -2,9 +2,9 @@ package com.gmail.nossr50.skills.woodcutting;
 
 import com.gmail.nossr50.datatypes.mods.CustomBlock;
 import com.gmail.nossr50.datatypes.player.McMMOPlayer;
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
 import com.gmail.nossr50.datatypes.skills.SuperAbility;
 import com.gmail.nossr50.datatypes.skills.PrimarySkill;
-import com.gmail.nossr50.datatypes.skills.SubSkill;
 import com.gmail.nossr50.datatypes.skills.XPGainReason;
 import com.gmail.nossr50.locale.LocaleLoader;
 import com.gmail.nossr50.mcMMO;
@@ -12,7 +12,7 @@ import com.gmail.nossr50.skills.SkillManager;
 import com.gmail.nossr50.skills.woodcutting.Woodcutting.ExperienceGainMethod;
 import com.gmail.nossr50.util.*;
 import com.gmail.nossr50.util.skills.CombatUtils;
-import com.gmail.nossr50.util.skills.SubSkillActivationType;
+import com.gmail.nossr50.util.skills.SkillActivationType;
 import com.gmail.nossr50.util.skills.SkillUtils;
 import org.bukkit.Material;
 import org.bukkit.block.Block;
@@ -30,7 +30,7 @@ public class WoodcuttingManager extends SkillManager {
     }
 
     public boolean canUseLeafBlower(ItemStack heldItem) {
-        return Permissions.isSubSkillEnabled(getPlayer(), SubSkill.WOODCUTTING_LEAF_BLOWER) && getSkillLevel() >= Woodcutting.leafBlowerUnlockLevel && ItemUtils.isAxe(heldItem);
+        return Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.WOODCUTTING_LEAF_BLOWER) && getSkillLevel() >= Woodcutting.leafBlowerUnlockLevel && ItemUtils.isAxe(heldItem);
     }
 
     public boolean canUseTreeFeller(ItemStack heldItem) {
@@ -38,7 +38,7 @@ public class WoodcuttingManager extends SkillManager {
     }
 
     protected boolean canGetDoubleDrops() {
-        return Permissions.isSubSkillEnabled(getPlayer(), SubSkill.WOODCUTTING_HARVEST_LUMBER) && SkillUtils.isActivationSuccessful(SubSkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkill.WOODCUTTING_HARVEST_LUMBER, getPlayer(), this.skill, getSkillLevel(), activationChance);
+        return Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.WOODCUTTING_HARVEST_LUMBER) && SkillUtils.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.WOODCUTTING_HARVEST_LUMBER, getPlayer(), this.skill, getSkillLevel(), activationChance);
     }
 
     /**

+ 125 - 7
src/main/java/com/gmail/nossr50/util/EventUtils.java

@@ -3,18 +3,16 @@ package com.gmail.nossr50.util;
 import com.gmail.nossr50.datatypes.party.Party;
 import com.gmail.nossr50.datatypes.player.McMMOPlayer;
 import com.gmail.nossr50.datatypes.player.PlayerProfile;
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
 import com.gmail.nossr50.datatypes.skills.SuperAbility;
 import com.gmail.nossr50.datatypes.skills.PrimarySkill;
-import com.gmail.nossr50.datatypes.skills.SubSkill;
 import com.gmail.nossr50.datatypes.skills.XPGainReason;
+import com.gmail.nossr50.datatypes.skills.subskills.AbstractSubSkill;
 import com.gmail.nossr50.events.experience.McMMOPlayerLevelChangeEvent;
 import com.gmail.nossr50.events.experience.McMMOPlayerLevelDownEvent;
 import com.gmail.nossr50.events.experience.McMMOPlayerLevelUpEvent;
 import com.gmail.nossr50.events.experience.McMMOPlayerXpGainEvent;
-import com.gmail.nossr50.events.fake.FakeBlockBreakEvent;
-import com.gmail.nossr50.events.fake.FakeBlockDamageEvent;
-import com.gmail.nossr50.events.fake.FakePlayerAnimationEvent;
-import com.gmail.nossr50.events.fake.FakePlayerFishEvent;
+import com.gmail.nossr50.events.fake.*;
 import com.gmail.nossr50.events.hardcore.McMMOPlayerPreDeathPenaltyEvent;
 import com.gmail.nossr50.events.hardcore.McMMOPlayerStatLossEvent;
 import com.gmail.nossr50.events.hardcore.McMMOPlayerVampirismEvent;
@@ -32,10 +30,15 @@ import com.gmail.nossr50.events.skills.salvage.McMMOPlayerSalvageCheckEvent;
 import com.gmail.nossr50.locale.LocaleLoader;
 import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.util.player.UserManager;
+import com.gmail.nossr50.util.skills.CombatUtils;
 import org.bukkit.block.Block;
 import org.bukkit.enchantments.Enchantment;
+import org.bukkit.entity.Entity;
 import org.bukkit.entity.FishHook;
+import org.bukkit.entity.LivingEntity;
 import org.bukkit.entity.Player;
+import org.bukkit.event.Event;
+import org.bukkit.event.entity.EntityDamageEvent;
 import org.bukkit.event.player.PlayerFishEvent;
 import org.bukkit.inventory.ItemStack;
 import org.bukkit.plugin.PluginManager;
@@ -43,7 +46,100 @@ import org.bukkit.plugin.PluginManager;
 import java.util.HashMap;
 import java.util.Map;
 
+/**
+ * This class is meant to help make event related code less boilerplate
+ */
 public class EventUtils {
+    /*
+     * Quality of Life methods
+     */
+    /**
+     * Checks to see if damage is from natural sources
+     * This cannot be used to determine if damage is from vanilla MC, it just checks to see if the damage is from a complex behaviour in mcMMO such as bleed.
+     *
+     * @param event this event
+     * @return true if damage is NOT from an unnatural mcMMO skill (such as bleed DOTs)
+     */
+    public static boolean isDamageFromMcMMOComplexBehaviour(Event event) {
+        if (event instanceof FakeEntityDamageEvent) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * This little method is just to make the code more readable
+     * @param entity target entity
+     * @return the associated McMMOPlayer for this entity
+     */
+    public static McMMOPlayer getMcMMOPlayer(Entity entity)
+    {
+        return UserManager.getPlayer((Player)entity);
+    }
+
+    /**
+     * Checks to see if a Player was damaged in this EntityDamageEvent
+     *
+     * This method checks for the following things and if they are all true it returns true
+     *
+     * 1) The player is real and not an NPC
+     * 2) The player is not in god mode
+     * 3) The damage dealt is above 0
+     * 4) The player is loaded into our mcMMO user profiles
+     *
+     * @param entityDamageEvent
+     * @return
+     */
+    public static boolean isRealPlayerDamaged(EntityDamageEvent entityDamageEvent)
+    {
+        //Make sure the damage is above 0
+        double damage = entityDamageEvent.getFinalDamage();
+
+        if (damage <= 0) {
+            return false;
+        }
+
+        Entity entity = entityDamageEvent.getEntity();
+
+        //Check to make sure the entity is not an NPC
+        if(Misc.isNPCEntity(entity))
+            return false;
+
+        if (!entity.isValid() || !(entity instanceof LivingEntity)) {
+            return false;
+        }
+
+        LivingEntity livingEntity = (LivingEntity) entity;
+
+        if (CombatUtils.isInvincible(livingEntity, damage)) {
+            return false;
+        }
+
+        if (livingEntity instanceof Player) {
+            Player player = (Player) entity;
+
+            if (!UserManager.hasPlayerDataKey(player)) {
+                return false;
+            }
+
+            McMMOPlayer mcMMOPlayer = UserManager.getPlayer(player);
+
+            /* Check for invincibility */
+            if (mcMMOPlayer.getGodMode()) {
+                entityDamageEvent.setCancelled(true);
+                return false;
+            }
+
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /*
+     * Others
+     */
+
     public static McMMOPlayerAbilityActivateEvent callPlayerAbilityActivateEvent(Player player, PrimarySkill skill) {
         McMMOPlayerAbilityActivateEvent event = new McMMOPlayerAbilityActivateEvent(player, skill);
         mcMMO.p.getServer().getPluginManager().callEvent(event);
@@ -51,8 +147,28 @@ public class EventUtils {
         return event;
     }
 
-    public static SubSkillEvent callSubSkillEvent(Player player, SubSkill subSkill) {
-        SubSkillEvent event = new SubSkillEvent(player, subSkill);
+    /**
+     * Calls a new SubSkillEvent for this SubSkill and then returns it
+     * @param player target player
+     * @param subSkillType target subskill
+     * @return the event after it has been fired
+     */
+    @Deprecated
+    public static SubSkillEvent callSubSkillEvent(Player player, SubSkillType subSkillType) {
+        SubSkillEvent event = new SubSkillEvent(player, subSkillType);
+        mcMMO.p.getServer().getPluginManager().callEvent(event);
+
+        return event;
+    }
+
+    /**
+     * Calls a new SubSkillEvent for this SubSkill and then returns it
+     * @param player target player
+     * @param abstractSubSkill target subskill
+     * @return the event after it has been fired
+     */
+    public static SubSkillEvent callSubSkillEvent(Player player, AbstractSubSkill abstractSubSkill) {
+        SubSkillEvent event = new SubSkillEvent(player, abstractSubSkill);
         mcMMO.p.getServer().getPluginManager().callEvent(event);
 
         return event;
@@ -286,4 +402,6 @@ public class EventUtils {
 
         return event;
     }
+
+
 }

+ 4 - 3
src/main/java/com/gmail/nossr50/util/Permissions.java

@@ -1,6 +1,7 @@
 package com.gmail.nossr50.util;
 
 import com.gmail.nossr50.datatypes.skills.PrimarySkill;
+import com.gmail.nossr50.datatypes.skills.subskills.AbstractSubSkill;
 import org.bukkit.Material;
 import org.bukkit.Server;
 import org.bukkit.World;
@@ -14,7 +15,7 @@ import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.commands.party.PartySubcommandType;
 import com.gmail.nossr50.datatypes.skills.ItemType;
 import com.gmail.nossr50.datatypes.skills.MaterialType;
-import com.gmail.nossr50.datatypes.skills.SubSkill;
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
 
 public final class Permissions {
     private Permissions() {}
@@ -22,7 +23,6 @@ public final class Permissions {
     /*
      * GENERAL
      */
-
     public static boolean motd(Permissible permissible) { return permissible.hasPermission("mcmmo.motd"); }
     public static boolean mobHealthDisplay(Permissible permissible) { return permissible.hasPermission("mcmmo.mobhealthdisplay"); }
     public static boolean updateNotifications(Permissible permissible) {return permissible.hasPermission("mcmmo.tools.updatecheck"); }
@@ -133,7 +133,8 @@ public final class Permissions {
 
     public static boolean skillEnabled(Permissible permissible, PrimarySkill skill) {return permissible.hasPermission("mcmmo.skills." + skill.toString().toLowerCase()); }
     public static boolean vanillaXpBoost(Permissible permissible, PrimarySkill skill) { return permissible.hasPermission("mcmmo.ability." + skill.toString().toLowerCase() + ".vanillaxpboost"); }
-    public static boolean isSubSkillEnabled(Permissible permissible, SubSkill subSkill) { return permissible.hasPermission(subSkill.getPermissionNodeAddress()); }
+    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 bonusDamage(Permissible permissible, PrimarySkill skill) { return permissible.hasPermission("mcmmo.ability." + skill.toString().toLowerCase() + ".bonusdamage"); }
 
     /* ACROBATICS */

+ 0 - 200
src/main/java/com/gmail/nossr50/util/SkillTextComponentFactory.java

@@ -1,200 +0,0 @@
-package com.gmail.nossr50.util;
-
-import com.gmail.nossr50.config.AdvancedConfig;
-import com.gmail.nossr50.datatypes.player.PlayerProfile;
-import com.gmail.nossr50.datatypes.skills.PrimarySkill;
-import com.gmail.nossr50.datatypes.skills.SubSkill;
-import com.gmail.nossr50.datatypes.skills.SubSkillFlags;
-import com.gmail.nossr50.locale.LocaleLoader;
-import com.gmail.nossr50.util.player.UserManager;
-import com.gmail.nossr50.util.skills.RankUtils;
-import net.md_5.bungee.api.ChatColor;
-import net.md_5.bungee.api.chat.BaseComponent;
-import net.md_5.bungee.api.chat.ComponentBuilder;
-import net.md_5.bungee.api.chat.HoverEvent;
-import net.md_5.bungee.api.chat.TextComponent;
-import org.bukkit.entity.Player;
-
-import java.util.HashMap;
-import java.util.List;
-
-public class SkillTextComponentFactory {
-    public static HashMap<SubSkill, TextComponent> subSkillTextComponents;
-
-    //Yeah there's probably a better way to do this
-    public static HashMap<SubSkill, BaseComponent[]> lockedComponentMap;
-
-    //This is a nested map because each JSON component for a different rank is going to be a bit different.
-    public static HashMap<Integer, HashMap<SubSkill, BaseComponent[]>> hoverComponentOuterMap;
-
-    public static TextComponent getSubSkillTextComponent(Player player, SubSkill subSkill)
-    {
-        //Init our maps
-        if (subSkillTextComponents == null)
-        {
-            subSkillTextComponents = new HashMap<>();
-            lockedComponentMap = new HashMap<>();
-            hoverComponentOuterMap = new HashMap<>();
-        }
-
-        //The skill milestone holds relevant information about the ranks of a skill
-        PlayerProfile playerProfile = UserManager.getPlayer(player).getProfile();
-
-        //Get skill name & description from our locale file
-        String skillName = subSkill.getLocaleName();
-
-        if(subSkillTextComponents.get(subSkill) == null)
-        {
-            //Setup Text Component
-            TextComponent textComponent = new TextComponent(skillName);
-            textComponent.setColor(ChatColor.DARK_AQUA);
-
-            //Hover Event
-            textComponent.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, getBaseComponent(player, subSkill)));
-
-            //Insertion
-            textComponent.setInsertion(skillName);
-
-            subSkillTextComponents.put(subSkill, textComponent);
-            return subSkillTextComponents.get(subSkill);
-        } else {
-            return subSkillTextComponents.get(subSkill);
-        }
-    }
-
-    private static BaseComponent[] getBaseComponent(Player player, SubSkill subSkill)
-    {
-        //If the player hasn't unlocked this skill yet we use a different JSON template
-        if(subSkill.getNumRanks() > 0 && RankUtils.getRank(player, subSkill) == 0)
-        {
-            //If the JSON component already exists
-            if(lockedComponentMap.get(subSkill) != null)
-                return lockedComponentMap.get(subSkill);
-
-            BaseComponent[] newComponents = getSubSkillHoverEventJSON(subSkill, player);
-            lockedComponentMap.put(subSkill, newComponents);
-            return lockedComponentMap.get(subSkill);
-        }
-
-        int curRank = RankUtils.getRank(player, subSkill);
-
-        //If the inner hashmap for this rank isn't made yet
-        if(hoverComponentOuterMap.get(curRank) == null)
-            hoverComponentOuterMap.put(curRank, new HashMap<>());
-
-        //Inner Hashmap for current rank
-        HashMap<SubSkill, BaseComponent[]> innerMap = hoverComponentOuterMap.get(curRank);
-
-        if(innerMap.get(subSkill) == null)
-            innerMap.put(subSkill, getSubSkillHoverEventJSON(subSkill, player));
-
-        return innerMap.get(subSkill);
-    }
-
-    /**
-     * Checks to see if a bit is flagged in the subskill
-     * @param flag1 The flag to check for
-     * @param subSkill The target subskill
-     * @return returns true if the bit is flagged in the subskill
-     */
-    private static boolean checkFlags(int flag1, SubSkill subSkill)
-    {
-        return (flag1 & subSkill.getFlags()) == flag1;
-    }
-
-    private static BaseComponent[] getSubSkillHoverEventJSON(SubSkill subSkill, Player player)
-    {
-        String skillName = subSkill.getLocaleName();
-        String skillDescription = subSkill.getLocaleDescription();
-
-        /*
-         * Hover Event BaseComponent color table
-         */
-        ChatColor ccSubSkillHeader      = ChatColor.GOLD;
-        ChatColor ccRank                = ChatColor.BLUE;
-        ChatColor ccCurRank             = ChatColor.GREEN;
-        ChatColor ccPossessive          = ChatColor.WHITE;
-        ChatColor ccNumRanks            = ccCurRank;
-        ChatColor ccDescriptionHeader   = ChatColor.DARK_PURPLE;
-        ChatColor ccDescription         = ChatColor.WHITE;
-        ChatColor ccLocked              = ChatColor.DARK_GRAY;
-        ChatColor ccLevelRequirement    = ChatColor.BLUE;
-        ChatColor ccLevelRequired       = ChatColor.RED;
-
-        //SubSkill Name
-        ComponentBuilder componentBuilder = new ComponentBuilder(skillName);
-        componentBuilder.bold(true).color(ccSubSkillHeader);
-        componentBuilder.append("\n");
-
-        if(RankUtils.getRank(player, subSkill) == 0)
-        {
-            //Skill is not unlocked yet
-            componentBuilder.append(LocaleLoader.getString("JSON.Locked")).color(ccLocked).bold(true);
-            componentBuilder.append("\n").append("\n").bold(false);
-            componentBuilder.append(LocaleLoader.getString("JSON.LevelRequirement") +": ").color(ccLevelRequirement);
-            componentBuilder.append(String.valueOf(AdvancedConfig.getInstance().getSubSkillUnlockLevel(subSkill, 1))).color(ccLevelRequired);
-
-        } else {
-            addSubSkillTypeToHoverEventJSON(subSkill, componentBuilder);
-
-            //RANK
-            if(subSkill.getNumRanks() > 0)
-            {
-                //Rank
-                componentBuilder.append(LocaleLoader.getString("JSON.Rank") + ": ").bold(false).color(ccRank);
-
-                //x of y
-                componentBuilder.append(String.valueOf(RankUtils.getRank(player, subSkill))).color(ccCurRank);
-                componentBuilder.append(" "+LocaleLoader.getString("JSON.RankPossesive")+" ").color(ccPossessive);
-                componentBuilder.append(String.valueOf(subSkill.getNumRanks())).color(ccNumRanks);
-            }
-
-            //Empty line
-            componentBuilder.append("\n").bold(false);
-            componentBuilder.append("\n");
-
-            //Description Header
-            componentBuilder.append(LocaleLoader.getString("JSON.DescriptionHeader")).bold(false).color(ccDescriptionHeader);
-            componentBuilder.append("\n").bold(false);
-
-            //Description
-            componentBuilder.append(skillDescription).color(ccDescription);
-            //componentBuilder.append("\n");
-        }
-
-        return componentBuilder.create();
-    }
-
-    private static void addSubSkillTypeToHoverEventJSON(SubSkill subSkill, ComponentBuilder componentBuilder)
-    {
-        if(checkFlags(SubSkillFlags.SUPERABILITY, subSkill))
-        {
-            componentBuilder.append(LocaleLoader.getString("JSON.Type.SuperAbility")).color(ChatColor.LIGHT_PURPLE);
-            componentBuilder.bold(true);
-        } else if(checkFlags(SubSkillFlags.ACTIVE, subSkill))
-        {
-            componentBuilder.append(LocaleLoader.getString("JSON.Type.Active")).color(ChatColor.DARK_RED);
-            componentBuilder.bold(true);
-        } else {
-            componentBuilder.append(LocaleLoader.getString("JSON.Type.Passive")).color(ChatColor.GREEN);
-            componentBuilder.bold(true);
-        }
-
-        componentBuilder.append("\n");
-    }
-
-    public static void getSubSkillTextComponents(Player player, List<TextComponent> textComponents, PrimarySkill parentSkill) {
-        for(SubSkill subSkill : SubSkill.values())
-        {
-            if(subSkill.getParentSkill() == parentSkill)
-            {
-                if(Permissions.isSubSkillEnabled(player, subSkill))
-                {
-                    textComponents.add(SkillTextComponentFactory.getSubSkillTextComponent(player, subSkill));
-                }
-            }
-        }
-    }
-}
-
-

+ 0 - 1
src/main/java/com/gmail/nossr50/util/StringUtils.java

@@ -2,7 +2,6 @@ package com.gmail.nossr50.util;
 
 import com.gmail.nossr50.datatypes.party.PartyFeature;
 import com.gmail.nossr50.datatypes.skills.SuperAbility;
-import com.gmail.nossr50.datatypes.skills.SubSkill;
 import org.bukkit.Material;
 import org.bukkit.TreeSpecies;
 import org.bukkit.block.data.Ageable;

+ 531 - 0
src/main/java/com/gmail/nossr50/util/TextComponentFactory.java

@@ -0,0 +1,531 @@
+package com.gmail.nossr50.util;
+
+import com.gmail.nossr50.commands.skills.McMMOWebLinks;
+import com.gmail.nossr50.config.AdvancedConfig;
+import com.gmail.nossr50.datatypes.interactions.NotificationType;
+import com.gmail.nossr50.datatypes.json.McMMOUrl;
+import com.gmail.nossr50.datatypes.skills.PrimarySkill;
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
+import com.gmail.nossr50.datatypes.skills.subskills.AbstractSubSkill;
+import com.gmail.nossr50.listeners.InteractionManager;
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.util.skills.RankUtils;
+import net.md_5.bungee.api.ChatColor;
+import net.md_5.bungee.api.chat.*;
+import org.bukkit.entity.Player;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public class TextComponentFactory {
+    public static HashMap<String, TextComponent> subSkillTextComponents;
+
+    //Yeah there's probably a better way to do this
+    public static HashMap<String, BaseComponent[]> lockedComponentMap;
+
+    public static BaseComponent[] webComponents;
+
+    public static TextComponent getNotificationTextComponent(String localeKey, NotificationType notificationType)
+    {
+        TextComponent textComponent = new TextComponent(LocaleLoader.getString(localeKey));
+        return textComponent;
+    }
+
+    public static void sendPlayerUrlHeader(Player player) {
+        Player.Spigot spigotPlayer = player.spigot();
+
+        if(webComponents != null)
+        {
+            player.spigot().sendMessage(webComponents);
+            return;
+        }
+
+        TextComponent prefix = new TextComponent("[| ");
+        prefix.setColor(ChatColor.DARK_AQUA);
+        TextComponent suffix = new TextComponent(" |]");
+        suffix.setColor(ChatColor.DARK_AQUA);
+
+        TextComponent emptySpace = new TextComponent(" ");
+
+        BaseComponent[] baseComponents = {new TextComponent(prefix),
+                getWebLinkTextComponent(McMMOWebLinks.WEBSITE),
+                emptySpace,
+                getWebLinkTextComponent(McMMOWebLinks.DISCORD),
+                emptySpace,
+                getWebLinkTextComponent(McMMOWebLinks.PATREON),
+                emptySpace,
+                getWebLinkTextComponent(McMMOWebLinks.WIKI),
+                emptySpace,
+                getWebLinkTextComponent(McMMOWebLinks.SPIGOT),
+                emptySpace,
+                getWebLinkTextComponent(McMMOWebLinks.HELP_TRANSLATE),
+                new TextComponent(suffix)};
+
+        //Cache into memory since the links wont change
+        webComponents = baseComponents;
+        spigotPlayer.sendMessage(webComponents);
+    }
+
+    public static void sendPlayerSubSkillList(Player player, List<TextComponent> textComponents)
+    {
+        TextComponent emptySpace = new TextComponent(" ");
+        //TextComponent atSymbolText = new TextComponent(atSymbol);
+
+        ArrayList<BaseComponent> bulkMessage = new ArrayList<>();
+        int newLineCount = 0; //Hacky solution to wordwrap problems
+
+        for (TextComponent textComponent : textComponents) {
+            //Don't send more than 3 subskills per line to avoid MOST wordwrap problems
+            if(newLineCount > 2)
+            {
+                TextComponent[] bulkArray = new TextComponent[bulkMessage.size()];
+                bulkArray = bulkMessage.toArray(bulkArray);
+
+                player.spigot().sendMessage(bulkArray);
+                bulkMessage = new ArrayList<>();
+                newLineCount = 0;
+            }
+            //Style the skills into @links
+            final String originalTxt = textComponent.getText();
+
+            TextComponent stylizedText = new TextComponent("@");
+            stylizedText.setColor(ChatColor.YELLOW);
+            addChild(stylizedText, originalTxt);
+
+            if(textComponent.getHoverEvent() != null)
+                stylizedText.setHoverEvent(textComponent.getHoverEvent());
+
+            if(textComponent.getClickEvent() != null)
+                stylizedText.setClickEvent(textComponent.getClickEvent());
+
+            bulkMessage.add(stylizedText);
+            bulkMessage.add(emptySpace);
+
+            newLineCount++;
+        }
+
+        /*
+         * Convert our list into an array
+         */
+        TextComponent[] bulkArray = new TextComponent[bulkMessage.size()];
+        bulkArray = bulkMessage.toArray(bulkArray);
+
+        player.spigot().sendMessage(bulkArray);
+    }
+
+    public static TextComponent getWebLinkTextComponent(McMMOWebLinks webLinks)
+    {
+        TextComponent webTextComponent;
+
+        switch(webLinks)
+        {
+            case WEBSITE:
+                webTextComponent = new TextComponent("@");
+                webTextComponent.setColor(ChatColor.YELLOW);
+                addChild(webTextComponent, "Web");
+                webTextComponent.setClickEvent(getUrlClickEvent(McMMOUrl.urlWebsite));
+                break;
+            case SPIGOT:
+                webTextComponent = new TextComponent("@");
+                webTextComponent.setColor(ChatColor.YELLOW);
+                addChild(webTextComponent, "Spigot");
+                webTextComponent.setClickEvent(getUrlClickEvent(McMMOUrl.urlSpigot));
+                break;
+            case DISCORD:
+                webTextComponent = new TextComponent("@");
+                webTextComponent.setColor(ChatColor.YELLOW);
+                addChild(webTextComponent, "Discord");
+                webTextComponent.setClickEvent(getUrlClickEvent(McMMOUrl.urlDiscord));
+                break;
+            case PATREON:
+                webTextComponent = new TextComponent("@");
+                webTextComponent.setColor(ChatColor.YELLOW);
+                addChild(webTextComponent, "Patreon");
+                webTextComponent.setClickEvent(getUrlClickEvent(McMMOUrl.urlPatreon));
+                break;
+            case WIKI:
+                webTextComponent = new TextComponent("@");
+                webTextComponent.setColor(ChatColor.YELLOW);
+                addChild(webTextComponent, "Wiki");
+                webTextComponent.setClickEvent(getUrlClickEvent(McMMOUrl.urlWiki));
+                break;
+            case HELP_TRANSLATE:
+                webTextComponent = new TextComponent("@");
+                webTextComponent.setColor(ChatColor.YELLOW);
+                addChild(webTextComponent, "Lang");
+                webTextComponent.setClickEvent(getUrlClickEvent(McMMOUrl.urlTranslate));
+                break;
+            default:
+                webTextComponent = new TextComponent("NOT DEFINED");
+        }
+
+        webTextComponent.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, getUrlHoverEvent(webLinks)));
+        webTextComponent.setInsertion(webLinks.getUrl());
+
+        return webTextComponent;
+    }
+
+    private static void addChild(TextComponent webTextComponent, String childName) {
+        TextComponent childComponent = new TextComponent(childName);
+        childComponent.setColor(ChatColor.BLUE);
+        webTextComponent.addExtra(childComponent);
+    }
+
+    private static BaseComponent[] getUrlHoverEvent(McMMOWebLinks webLinks)
+    {
+        ComponentBuilder componentBuilder = new ComponentBuilder(webLinks.getNiceTitle());
+
+        switch(webLinks)
+        {
+            case WEBSITE:
+                addUrlHeaderHover(webLinks, componentBuilder);
+                componentBuilder.append("\n\n").italic(false);
+                componentBuilder.append("The official mcMMO Website!").color(ChatColor.GREEN);
+                break;
+            case SPIGOT:
+                addUrlHeaderHover(webLinks, componentBuilder);
+                componentBuilder.append("\n\n").italic(false);
+                componentBuilder.append("The official mcMMO Spigot Resource Page!").color(ChatColor.GREEN);
+                componentBuilder.append("\nI post regularly in the discussion thread here!").color(ChatColor.GRAY);
+                break;
+            case PATREON:
+                addUrlHeaderHover(webLinks, componentBuilder);
+                componentBuilder.append("\n\n").italic(false);
+                componentBuilder.append("Support nossr50 and development of mcMMO on Patreon!").color(ChatColor.GREEN);
+                break;
+            case WIKI:
+                addUrlHeaderHover(webLinks, componentBuilder);
+                componentBuilder.append("\n\n").italic(false);
+                componentBuilder.append("The official mcMMO wiki!").color(ChatColor.GREEN);
+                componentBuilder.append("\n");
+                componentBuilder.append("I'm looking for more wiki staff, contact me on our discord!").italic(false).color(ChatColor.DARK_GRAY);
+                break;
+            case DISCORD:
+                addUrlHeaderHover(webLinks, componentBuilder);
+                componentBuilder.append("\n\n").italic(false);
+                componentBuilder.append("The official mcMMO Discord server!").color(ChatColor.GREEN);
+                break;
+            case HELP_TRANSLATE:
+                addUrlHeaderHover(webLinks, componentBuilder);
+                componentBuilder.append("\n\n").italic(false);
+                componentBuilder.append("mcMMO's translation service!").color(ChatColor.GREEN);
+                componentBuilder.append("\n");
+                componentBuilder.append("You can use this website to help translate mcMMO into your language!" +
+                        "\nIf you want to know more contact me in discord.").italic(false).color(ChatColor.DARK_GRAY);
+        }
+
+        return componentBuilder.create();
+    }
+
+    private static void addUrlHeaderHover(McMMOWebLinks webLinks, ComponentBuilder componentBuilder) {
+        componentBuilder.append("\n");
+        componentBuilder.append(webLinks.getUrl()).color(ChatColor.GRAY).italic(true);
+    }
+
+    private static ClickEvent getUrlClickEvent(String url)
+    {
+        return new ClickEvent(ClickEvent.Action.OPEN_URL, url);
+    }
+
+    public static TextComponent getSubSkillTextComponent(Player player, SubSkillType subSkillType)
+    {
+        //Init our maps
+        if (subSkillTextComponents == null)
+        {
+            subSkillTextComponents = new HashMap<>();
+            lockedComponentMap = new HashMap<>();
+            //hoverComponentOuterMap = new HashMap<>();
+        }
+
+        //Get skill name & description from our locale file
+        String key = subSkillType.toString();
+        String skillName = subSkillType.getLocaleName();
+
+        if(subSkillTextComponents.get(key) == null)
+        {
+            //Setup Text Component
+            TextComponent textComponent = new TextComponent(skillName);
+            //textComponent.setColor(ChatColor.DARK_AQUA);
+
+            //Hover Event
+            textComponent.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, getBaseComponent(player, subSkillType)));
+
+            //Insertion
+            textComponent.setInsertion(skillName);
+
+            subSkillTextComponents.put(key, textComponent);
+            return subSkillTextComponents.get(key);
+        } else {
+            return subSkillTextComponents.get(key);
+        }
+    }
+
+    public static TextComponent getSubSkillTextComponent(Player player, AbstractSubSkill abstractSubSkill)
+    {
+        //Init our maps
+        if (subSkillTextComponents == null)
+        {
+            subSkillTextComponents = new HashMap<>();
+            lockedComponentMap = new HashMap<>();
+            //hoverComponentOuterMap = new HashMap<>();
+        }
+
+        //Get skill name & description from our locale file
+        String key = abstractSubSkill.getConfigKeyName();
+        String skillName = abstractSubSkill.getNiceName();
+
+        if(subSkillTextComponents.get(key) == null)
+        {
+            //Setup Text Component
+            TextComponent textComponent = new TextComponent(skillName);
+            textComponent.setColor(ChatColor.DARK_AQUA);
+
+            //Hover Event
+            textComponent.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, getBaseComponent(player, abstractSubSkill)));
+
+            //Insertion
+            textComponent.setInsertion(skillName);
+
+            subSkillTextComponents.put(key, textComponent);
+            return subSkillTextComponents.get(key);
+        } else {
+            return subSkillTextComponents.get(key);
+        }
+    }
+
+    private static BaseComponent[] getBaseComponent(Player player, AbstractSubSkill abstractSubSkill)
+    {
+
+        int curRank = RankUtils.getRank(player, abstractSubSkill);
+        String key = abstractSubSkill.getConfigKeyName();
+
+        //If the player hasn't unlocked this skill yet we use a different JSON template
+        if(abstractSubSkill.getNumRanks() > 0 && curRank == 0)
+        {
+            //If the JSON component already exists
+            if(lockedComponentMap.get(key) != null)
+                return lockedComponentMap.get(key);
+
+            BaseComponent[] newComponents = getSubSkillHoverEventJSON(abstractSubSkill, player, curRank);
+            lockedComponentMap.put(key, newComponents);
+            return lockedComponentMap.get(key);
+        }
+
+        //If the inner hashmap for this rank isn't made yet
+        /*if(hoverComponentOuterMap.get(curRank) == null)
+            hoverComponentOuterMap.put(curRank, new HashMap<>());*/
+
+        //Inner Hashmap for current rank
+        //HashMap<String, BaseComponent[]> innerMap = hoverComponentOuterMap.get(curRank);
+
+        /*if(innerMap.get(key) == null)
+            innerMap.put(key, getSubSkillHoverEventJSON(abstractSubSkill, player, curRank));*/
+
+        return getSubSkillHoverEventJSON(abstractSubSkill, player, curRank);
+    }
+
+    private static BaseComponent[] getBaseComponent(Player player, SubSkillType subSkillType)
+    {
+        int curRank = RankUtils.getRank(player, subSkillType);
+        String key = subSkillType.toString();
+
+        //If the player hasn't unlocked this skill yet we use a different JSON template
+        if(subSkillType.getNumRanks() > 0 && curRank == 0)
+        {
+            //If the JSON component already exists
+            if(lockedComponentMap.get(key) != null)
+                return lockedComponentMap.get(key);
+
+            BaseComponent[] newComponents = getSubSkillHoverEventJSON(subSkillType, player, curRank);
+            lockedComponentMap.put(key, newComponents);
+            return lockedComponentMap.get(key);
+        }
+
+        //If the inner hashmap for this rank isn't made yet
+        /*if(hoverComponentOuterMap.get(curRank) == null)
+            hoverComponentOuterMap.put(curRank, new HashMap<>());
+
+        //Inner Hashmap for current rank
+        HashMap<String, BaseComponent[]> innerMap = hoverComponentOuterMap.get(curRank);*/
+
+        /*if(innerMap.get(key) == null)
+            innerMap.put(key, getSubSkillHoverEventJSON(subSkillType, player, curRank));
+
+        return innerMap.get(key);*/
+
+        return getSubSkillHoverEventJSON(subSkillType, player, curRank);
+    }
+
+    /**
+     * Used for the skill in the new skill system (Deriving from AbstractSubSkill)
+     * @param abstractSubSkill this subskill
+     * @param player
+     * @param curRank
+     * @return
+     */
+    private static BaseComponent[] getSubSkillHoverEventJSON(AbstractSubSkill abstractSubSkill, Player player, int curRank)
+    {
+        String skillName = abstractSubSkill.getNiceName();
+
+        /*
+         * Hover Event BaseComponent color table
+         */
+        ChatColor ccSubSkillHeader      = ChatColor.GOLD;
+        ChatColor ccRank                = ChatColor.BLUE;
+        ChatColor ccCurRank             = ChatColor.GREEN;
+        ChatColor ccPossessive          = ChatColor.WHITE;
+        ChatColor ccNumRanks            = ccCurRank;
+        //ChatColor ccDescriptionHeader   = ChatColor.DARK_PURPLE;
+        //ChatColor ccDescription         = ChatColor.WHITE;
+        ChatColor ccLocked              = ChatColor.DARK_GRAY;
+        ChatColor ccLevelRequirement    = ChatColor.BLUE;
+        ChatColor ccLevelRequired       = ChatColor.RED;
+
+        //SubSkillType Name
+        ComponentBuilder componentBuilder = getNewComponentBuilder(skillName, ccSubSkillHeader);
+
+        if(RankUtils.getRank(player, abstractSubSkill) == 0)
+        {
+            //Skill is not unlocked yet
+            addLocked(abstractSubSkill, ccLocked, ccLevelRequirement, ccLevelRequired, componentBuilder);
+        } else {
+            addSubSkillTypeToHoverEventJSON(abstractSubSkill, componentBuilder);
+
+            //RANK
+            addRanked(ccRank, ccCurRank, ccPossessive, ccNumRanks, componentBuilder, abstractSubSkill.getNumRanks(), RankUtils.getRank(player, abstractSubSkill));
+
+            componentBuilder.append(LocaleLoader.getString("JSON.DescriptionHeader"));
+            componentBuilder.append("\n").append(abstractSubSkill.getDescription()).append("\n");
+
+            //Empty line
+            componentBuilder.append("\n").bold(false);
+            componentBuilder.append("\n");
+
+            //Finally, add details to the tooltip
+            abstractSubSkill.addStats(componentBuilder, player);
+        }
+
+        return componentBuilder.create();
+    }
+
+    private static ComponentBuilder getNewComponentBuilder(String skillName, ChatColor ccSubSkillHeader) {
+        ComponentBuilder componentBuilder = new ComponentBuilder(skillName);
+        componentBuilder.bold(true).color(ccSubSkillHeader);
+        componentBuilder.append("\n");
+        return componentBuilder;
+    }
+
+    private static void addRanked(ChatColor ccRank, ChatColor ccCurRank, ChatColor ccPossessive, ChatColor ccNumRanks, ComponentBuilder componentBuilder, int numRanks, int rank) {
+        if (numRanks > 0) {
+            //Rank
+            componentBuilder.append(LocaleLoader.getString("JSON.Rank") + ": ").bold(false).color(ccRank);
+
+            //x of y
+            componentBuilder.append(String.valueOf(rank)).color(ccCurRank);
+            componentBuilder.append(" " + LocaleLoader.getString("JSON.RankPossesive") + " ").color(ccPossessive);
+            componentBuilder.append(String.valueOf(numRanks)).color(ccNumRanks);
+        }
+    }
+
+    private static void addLocked(AbstractSubSkill abstractSubSkill, ChatColor ccLocked, ChatColor ccLevelRequirement, ChatColor ccLevelRequired, ComponentBuilder componentBuilder) {
+        componentBuilder.append(LocaleLoader.getString("JSON.Locked")).color(ccLocked).bold(true);
+        componentBuilder.append("\n").append("\n").bold(false);
+        componentBuilder.append(LocaleLoader.getString("JSON.LevelRequirement") +": ").color(ccLevelRequirement);
+        componentBuilder.append(String.valueOf(AdvancedConfig.getInstance().getSubSkillUnlockLevel(abstractSubSkill, 1))).color(ccLevelRequired);
+    }
+
+    @Deprecated
+    private static BaseComponent[] getSubSkillHoverEventJSON(SubSkillType subSkillType, Player player, int curRank)
+    {
+        String skillName = subSkillType.getLocaleName();
+
+        /*
+         * Hover Event BaseComponent color table
+         */
+        ChatColor ccSubSkillHeader      = ChatColor.GOLD;
+        ChatColor ccRank                = ChatColor.BLUE;
+        ChatColor ccCurRank             = ChatColor.GREEN;
+        ChatColor ccPossessive          = ChatColor.WHITE;
+        ChatColor ccNumRanks            = ccCurRank;
+        ChatColor ccDescriptionHeader   = ChatColor.DARK_PURPLE;
+        ChatColor ccDescription         = ChatColor.DARK_GRAY;
+        ChatColor ccLocked              = ChatColor.DARK_GRAY;
+        ChatColor ccLevelRequirement    = ChatColor.BLUE;
+        ChatColor ccLevelRequired       = ChatColor.RED;
+
+        //SubSkillType Name
+        ComponentBuilder componentBuilder = getNewComponentBuilder(skillName, ccSubSkillHeader);
+
+        if(RankUtils.getRank(player, subSkillType) == 0)
+        {
+            //Skill is not unlocked yet
+            componentBuilder.append(LocaleLoader.getString("JSON.Locked")).color(ccLocked).bold(true);
+            componentBuilder.append("\n").append("\n").bold(false);
+            componentBuilder.append(LocaleLoader.getString("JSON.LevelRequirement") +": ").color(ccLevelRequirement);
+            componentBuilder.append(String.valueOf(AdvancedConfig.getInstance().getSubSkillUnlockLevel(subSkillType, 1))).color(ccLevelRequired);
+
+        } else {
+            //addSubSkillTypeToHoverEventJSON(subSkillType, componentBuilder);
+
+            //RANK
+            if(subSkillType.getNumRanks() > 0)
+            {
+                addRanked(ccRank, ccCurRank, ccPossessive, ccNumRanks, componentBuilder, subSkillType.getNumRanks(), RankUtils.getRank(player, subSkillType));
+
+                //Empty line
+                componentBuilder.append("\n").bold(false);
+            }
+        }
+
+        componentBuilder.append(LocaleLoader.getString("JSON.DescriptionHeader"));
+        componentBuilder.color(ccDescriptionHeader);
+        componentBuilder.append("\n");
+        componentBuilder.append(subSkillType.getLocaleDescription());
+        componentBuilder.color(ccDescription);
+
+        return componentBuilder.create();
+    }
+
+    private static void addSubSkillTypeToHoverEventJSON(AbstractSubSkill abstractSubSkill, ComponentBuilder componentBuilder)
+    {
+        if(abstractSubSkill.isSuperAbility())
+        {
+            componentBuilder.append(LocaleLoader.getString("JSON.Type.SuperAbility")).color(ChatColor.LIGHT_PURPLE);
+            componentBuilder.bold(true);
+        } else if(abstractSubSkill.isActiveUse())
+        {
+            componentBuilder.append(LocaleLoader.getString("JSON.Type.Active")).color(ChatColor.DARK_RED);
+            componentBuilder.bold(true);
+        } else {
+            componentBuilder.append(LocaleLoader.getString("JSON.Type.Passive")).color(ChatColor.GREEN);
+            componentBuilder.bold(true);
+        }
+
+        componentBuilder.append("\n");
+    }
+
+    public static void getSubSkillTextComponents(Player player, List<TextComponent> textComponents, PrimarySkill parentSkill) {
+        for(SubSkillType subSkillType : SubSkillType.values())
+        {
+            if(subSkillType.getParentSkill() == parentSkill)
+            {
+                if(Permissions.isSubSkillEnabled(player, subSkillType))
+                {
+                    textComponents.add(TextComponentFactory.getSubSkillTextComponent(player, subSkillType));
+                }
+            }
+        }
+
+        /* NEW SKILL SYSTEM */
+        for(AbstractSubSkill abstractSubSkill : InteractionManager.getSubSkillList())
+        {
+            if(abstractSubSkill.getPrimarySkill() == parentSkill)
+            {
+                if(Permissions.isSubSkillEnabled(player, abstractSubSkill))
+                    textComponents.add(TextComponentFactory.getSubSkillTextComponent(player, abstractSubSkill));
+            }
+        }
+    }
+}
+
+

+ 0 - 30
src/main/java/com/gmail/nossr50/util/player/FeedbackManager.java

@@ -1,30 +0,0 @@
-package com.gmail.nossr50.util.player;
-
-import org.bukkit.entity.Player;
-
-/**
- * This class will manage all types of feedback for a player done through mcMMO
- * Including
- * 1) Audio Feedback
- * 2) Visual Feedback (Anything on the screen that we use to convey information)
- */
-public class FeedbackManager {
-
-    /**
-     * Used for sending specific sound cues to a player
-     * @param player The player who will be receiving the sound cues
-     */
-    public void sendAudioFeedback(Player player)
-    {
-
-    }
-
-    /**
-     * Used for sending specific visual aides to a player
-     * @param player The player who will be receiving the visual feedback
-     */
-    public void sendVisualFeedback(Player player)
-    {
-
-    }
-}

+ 0 - 7
src/main/java/com/gmail/nossr50/util/player/VisualFeedbackType.java

@@ -1,7 +0,0 @@
-package com.gmail.nossr50.util.player;
-
-public enum VisualFeedbackType {
-    ADVANCEMENT,
-    CHAT_MESSAGE,
-    CUSTOM_SCOREBOARD
-}

+ 111 - 37
src/main/java/com/gmail/nossr50/util/skills/RankUtils.java

@@ -1,66 +1,118 @@
 package com.gmail.nossr50.util.skills;
 
 import com.gmail.nossr50.config.AdvancedConfig;
-import com.gmail.nossr50.datatypes.skills.SubSkill;
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
+import com.gmail.nossr50.datatypes.skills.subskills.AbstractSubSkill;
 import com.gmail.nossr50.util.player.UserManager;
 import org.bukkit.entity.Player;
 
 import java.util.HashMap;
 
 public class RankUtils {
-    public static HashMap<SubSkill, HashMap<Integer, Integer>> subSkillRanks;
+    public static HashMap<String, HashMap<Integer, Integer>> subSkillRanks;
 
-    /**
-     * Adds ranks to subSkillRanks for target SubSkill
-     * @param subSkill Target SubSkill
-     */
-    private static void addRanks(SubSkill subSkill) {
-        int numRanks = subSkill.getNumRanks();
+    /* NEW SYSTEM */
+    private static void addRanks(AbstractSubSkill abstractSubSkill)
+    {
+        //Fill out the rank array
+        for(int i = 0; i < abstractSubSkill.getNumRanks(); i++)
+        {
+            //This adds the highest ranks first
+            addRank(abstractSubSkill, abstractSubSkill.getNumRanks()-i);
 
+            //TODO: Remove debug code
+            /*System.out.println("DEBUG: Adding rank "+(numRanks-i)+" to "+subSkillType.toString());*/
+        }
+    }
+
+    private static void addRanks(SubSkillType subSkillType)
+    {
         //Fill out the rank array
-        for(int i = 0; i < numRanks; i++)
+        for(int i = 0; i < subSkillType.getNumRanks(); i++)
         {
             //This adds the highest ranks first
-            addRank(subSkill, numRanks-i);
+            addRank(subSkillType, subSkillType.getNumRanks()-i);
 
             //TODO: Remove debug code
-            /*System.out.println("DEBUG: Adding rank "+(numRanks-i)+" to "+subSkill.toString());*/
+            /*System.out.println("DEBUG: Adding rank "+(numRanks-i)+" to "+subSkillType.toString());*/
         }
     }
 
     /**
      * Gets the current rank of the subskill for the player
      * @param player The player in question
-     * @param subSkill Target subskill
+     * @param subSkillType Target subskill
      * @return The rank the player currently has achieved in this skill. -1 for skills without ranks.
      */
-    public static int getRank(Player player, SubSkill subSkill)
+    public static int getRank(Player player, SubSkillType subSkillType)
     {
+        String skillName = subSkillType.toString();
+        int numRanks = subSkillType.getNumRanks();
+
         if(subSkillRanks == null)
             subSkillRanks = new HashMap<>();
 
-        if(subSkill.getNumRanks() == 0)
+        if(numRanks == 0)
             return -1; //-1 Means the skill doesn't have ranks
 
-        if(subSkillRanks.get(subSkill) == null && subSkill.getNumRanks() > 0)
-            addRanks(subSkill);
+        if(subSkillRanks.get(skillName) == null && numRanks > 0)
+            addRanks(subSkillType);
 
         //Get our rank map
-        HashMap<Integer, Integer> rankMap = subSkillRanks.get(subSkill);
+        HashMap<Integer, Integer> rankMap = subSkillRanks.get(skillName);
 
         //Skill level of parent skill
-        int currentSkillLevel = UserManager.getPlayer(player).getSkillLevel(subSkill.getParentSkill());
+        int currentSkillLevel = UserManager.getPlayer(player).getSkillLevel(subSkillType.getParentSkill());
 
-        for(int i = 0; i < subSkill.getNumRanks(); i++)
+        for(int i = 0; i < numRanks; i++)
         {
             //Compare against the highest to lowest rank in that order
-            int rank = subSkill.getNumRanks()-i;
-            int unlockLevel = getUnlockLevel(subSkill, rank);
+            int rank = numRanks-i;
+            int unlockLevel = getUnlockLevel(subSkillType, rank);
+
+            //If we check all ranks and still cannot unlock the skill, we return rank 0
+            if(rank == 0)
+                return 0;
+
+            //True if our skill level can unlock the current rank
+            if(currentSkillLevel >= unlockLevel)
+                return rank;
+        }
+
+        return 0; //We should never reach this
+    }
+
+    /**
+     * Gets the current rank of the subskill for the player
+     * @param player The player in question
+     * @param abstractSubSkill Target subskill
+     * @return The rank the player currently has achieved in this skill. -1 for skills without ranks.
+     */
+    public static int getRank(Player player, AbstractSubSkill abstractSubSkill)
+    {
+        String skillName = abstractSubSkill.getConfigKeyName();
+        int numRanks = abstractSubSkill.getNumRanks();
+
+        if(subSkillRanks == null)
+            subSkillRanks = new HashMap<>();
+
+        if(numRanks == 0)
+            return -1; //-1 Means the skill doesn't have ranks
 
-            //TODO: Remove this debug code
-            /*System.out.println("[DEBUG RANKCHECK] Checking rank "+rank+" of "+subSkill.getNumRanks());
-            System.out.println("[DEBUG RANKCHECK] Rank "+rank+" -- Unlock level: "+unlockLevel);
-            System.out.println("[DEBUG RANKCHECK] Rank" +rank+" -- Player Skill Level: "+currentSkillLevel);*/
+        if(subSkillRanks.get(skillName) == null && numRanks > 0)
+            addRanks(abstractSubSkill);
+
+        //Get our rank map
+        HashMap<Integer, Integer> rankMap = subSkillRanks.get(skillName);
+
+        //Skill level of parent skill
+        int currentSkillLevel = UserManager.getPlayer(player).getSkillLevel(abstractSubSkill.getPrimarySkill());
+
+        for(int i = 0; i < numRanks; i++)
+        {
+            //Compare against the highest to lowest rank in that order
+            int rank = numRanks-i;
+            int unlockLevel = getUnlockLevel(abstractSubSkill, rank);
 
             //If we check all ranks and still cannot unlock the skill, we return rank 0
             if(rank == 0)
@@ -76,32 +128,54 @@ public class RankUtils {
 
     /**
      * Adds ranks to our map
-     * @param subSkill The subskill to add ranks for
+     * @param abstractSubSkill The subskill to add ranks for
      * @param rank The rank to add
      */
-    private static void addRank(SubSkill subSkill, int rank)
+    private static void addRank(AbstractSubSkill abstractSubSkill, int rank)
     {
-        if(subSkillRanks == null)
-            subSkillRanks = new HashMap<>();
+        initMaps(abstractSubSkill.getConfigKeyName());
+
+        HashMap<Integer, Integer> rankMap = subSkillRanks.get(abstractSubSkill.getConfigKeyName());
 
-        if(subSkillRanks.get(subSkill) == null)
-            subSkillRanks.put(subSkill, new HashMap<>());
+        //TODO: Remove this debug code
+        //System.out.println("[DEBUG]: Rank "+rank+" for "+subSkillName.toString()+" requires skill level "+getUnlockLevel(subSkillType, rank));
+        rankMap.put(rank, getUnlockLevel(abstractSubSkill, rank));
+    }
+
+    @Deprecated
+    private static void addRank(SubSkillType subSkillType, int rank)
+    {
+        initMaps(subSkillType.toString());
 
-        HashMap<Integer, Integer> rankMap = subSkillRanks.get(subSkill);
+        HashMap<Integer, Integer> rankMap = subSkillRanks.get(subSkillType.toString());
 
         //TODO: Remove this debug code
-        //System.out.println("[DEBUG]: Rank "+rank+" for "+subSkill.toString()+" requires skill level "+getUnlockLevel(subSkill, rank));
-        rankMap.put(rank, getUnlockLevel(subSkill, rank));
+        //System.out.println("[DEBUG]: Rank "+rank+" for "+subSkillName.toString()+" requires skill level "+getUnlockLevel(subSkillType, rank));
+        rankMap.put(rank, getUnlockLevel(subSkillType, rank));
+    }
+
+    private static void initMaps(String s) {
+        if (subSkillRanks == null)
+            subSkillRanks = new HashMap<>();
+
+        if (subSkillRanks.get(s) == null)
+            subSkillRanks.put(s, new HashMap<>());
     }
 
     /**
      * Gets the unlock level for a specific rank in a subskill
-     * @param subSkill The target subskill
+     * @param subSkillType The target subskill
      * @param rank The target rank
      * @return The level at which this rank unlocks
      */
-    private static int getUnlockLevel(SubSkill subSkill, int rank)
+    @Deprecated
+    private static int getUnlockLevel(SubSkillType subSkillType, int rank)
+    {
+        return AdvancedConfig.getInstance().getSubSkillUnlockLevel(subSkillType, rank);
+    }
+
+    private static int getUnlockLevel(AbstractSubSkill abstractSubSkill, int rank)
     {
-        return AdvancedConfig.getInstance().getSubSkillUnlockLevel(subSkill, rank);
+        return AdvancedConfig.getInstance().getSubSkillUnlockLevel(abstractSubSkill, rank);
     }
 }

+ 1 - 1
src/main/java/com/gmail/nossr50/util/skills/SubSkillActivationType.java → src/main/java/com/gmail/nossr50/util/skills/SkillActivationType.java

@@ -3,7 +3,7 @@ package com.gmail.nossr50.util.skills;
 /**
  * Defines the type of random calculations to use with a given skill
  */
-public enum SubSkillActivationType {
+public enum SkillActivationType {
     RANDOM_LINEAR_100_SCALE_NO_CAP, //A skill level of 100 would guarantee the proc with this
     RANDOM_LINEAR_100_SCALE_WITH_CAP, //This one is based on a scale of 1-100 but with a specified cap for max bonus
     RANDOM_STATIC_CHANCE, //The skill always has a SPECIFIC chance to succeed

+ 139 - 20
src/main/java/com/gmail/nossr50/util/skills/SkillUtils.java

@@ -4,9 +4,12 @@ import com.gmail.nossr50.config.AdvancedConfig;
 import com.gmail.nossr50.config.Config;
 import com.gmail.nossr50.config.HiddenConfig;
 import com.gmail.nossr50.datatypes.player.McMMOPlayer;
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
 import com.gmail.nossr50.datatypes.skills.SuperAbility;
 import com.gmail.nossr50.datatypes.skills.PrimarySkill;
-import com.gmail.nossr50.datatypes.skills.SubSkill;
+import com.gmail.nossr50.datatypes.skills.XPGainReason;
+import com.gmail.nossr50.datatypes.skills.subskills.AbstractSubSkill;
+import com.gmail.nossr50.datatypes.skills.subskills.interfaces.RandomChance;
 import com.gmail.nossr50.events.skills.secondaryabilities.SubSkillEvent;
 import com.gmail.nossr50.events.skills.secondaryabilities.SubSkillWeightedActivationCheckEvent;
 import com.gmail.nossr50.locale.LocaleLoader;
@@ -28,10 +31,54 @@ import org.bukkit.inventory.meta.ItemMeta;
 import org.bukkit.potion.PotionEffect;
 import org.bukkit.potion.PotionEffectType;
 
+import java.text.DecimalFormat;
 import java.util.ArrayList;
 import java.util.List;
 
 public class SkillUtils {
+
+    public static final DecimalFormat percent = new DecimalFormat("##0.00%");
+    public static final DecimalFormat decimal = new DecimalFormat("##0.00");
+
+    public static void applyXpGain(McMMOPlayer mcMMOPlayer, PrimarySkill skill, float xp, XPGainReason xpGainReason) {
+        mcMMOPlayer.beginXpGain(skill, xp, xpGainReason);
+    }
+
+    /*
+     * Skill Stat Calculations
+     */
+
+    public static String[] calculateAbilityDisplayValues(double chance, boolean isLucky) {
+        String[] displayValues = new String[2];
+
+        displayValues[0] = percent.format(Math.min(chance, 100.0D) / 100.0D);
+        displayValues[1] = isLucky ? percent.format(Math.min(chance * 1.3333D, 100.0D) / 100.0D) : null;
+
+        return displayValues;
+    }
+
+    public static String[] calculateAbilityDisplayValues(float skillValue, SubSkillType subSkillType, boolean isLucky) {
+        int maxBonusLevel = AdvancedConfig.getInstance().getMaxBonusLevel(subSkillType);
+
+        return calculateAbilityDisplayValues((AdvancedConfig.getInstance().getMaxChance(subSkillType) / maxBonusLevel) * Math.min(skillValue, maxBonusLevel), isLucky);
+    }
+
+    public static String[] calculateLengthDisplayValues(Player player, float skillValue, PrimarySkill skill) {
+        int maxLength = skill.getAbility().getMaxLength();
+        int length = 2 + (int) (skillValue / AdvancedConfig.getInstance().getAbilityLength());
+        int enduranceLength = PerksUtils.handleActivationPerks(player, length, maxLength);
+
+        if (maxLength != 0) {
+            length = Math.min(length, maxLength);
+        }
+
+        return new String[] { String.valueOf(length), String.valueOf(enduranceLength) };
+    }
+
+    /*
+     * Others
+     */
+
     public static int handleFoodSkills(Player player, PrimarySkill skill, int eventFoodLevel, int baseLevel, int maxLevel, int rankChange) {
         int skillLevel = UserManager.getPlayer(player).getSkillLevel(skill);
 
@@ -199,7 +246,7 @@ public class SkillUtils {
 
     /**
      * Checks whether or not the given skill succeeds
-     * @param subSkill The ability corresponding to this check
+     * @param subSkillType The ability corresponding to this check
      * @param player The player whose skill levels we are checking against
      * @param skillLevel The skill level of the corresponding skill
      * @param activationChance used to determine activation chance
@@ -207,9 +254,15 @@ public class SkillUtils {
      * @param maxLevel maximum skill level bonus
      * @return true if random chance succeeds and the event isn't cancelled
      */
-    private static boolean performRandomSkillCheck(SubSkill subSkill, Player player, int skillLevel, int activationChance, double maxChance, int maxLevel) {
+    private static boolean performRandomSkillCheck(SubSkillType subSkillType, Player player, int skillLevel, int activationChance, double maxChance, int maxLevel) {
+        double chance = (maxChance / maxLevel) * Math.min(skillLevel, maxLevel) / activationChance;
+        return performRandomSkillCheckStatic(subSkillType, player, activationChance, chance);
+    }
+
+    /* NEW VERSION */
+    private static boolean performRandomSkillCheck(AbstractSubSkill abstractSubSkill, Player player, int skillLevel, int activationChance, double maxChance, int maxLevel) {
         double chance = (maxChance / maxLevel) * Math.min(skillLevel, maxLevel) / activationChance;
-        return performRandomSkillCheckStatic(subSkill, player, activationChance, chance);
+        return performRandomSkillCheckStatic(abstractSubSkill, player, activationChance, chance);
     }
 
     /**
@@ -224,48 +277,107 @@ public class SkillUtils {
      * Random skills check for success based on numbers and then fire a cancellable event, if that event is not cancelled they succeed
      * All other skills just fire the cancellable event and succeed if it is not cancelled
      *
-     * @param subSkill The identifier for this specific sub-skill
+     * @param subSkillType The identifier for this specific sub-skill
      * @param player The owner of this sub-skill
      * @param skill The identifier for the parent of our sub-skill
      * @param activationChance This is the value that we roll against, 100 is normal, and 75 is for lucky perk
      * @param subskillActivationType this value represents what kind of activation procedures this sub-skill uses
      * @return returns true if all conditions are met and they event is not cancelled
      */
-    public static boolean isActivationSuccessful(SubSkillActivationType subskillActivationType, SubSkill subSkill, Player player,
+    public static boolean isActivationSuccessful(SkillActivationType subskillActivationType, SubSkillType subSkillType, Player player,
                                                  PrimarySkill skill, int skillLevel, int activationChance)
     {
         //Maximum chance to succeed
-        double maxChance = AdvancedConfig.getInstance().getMaxChance(subSkill);
+        double maxChance = AdvancedConfig.getInstance().getMaxChance(subSkillType);
         //Maximum roll we can make
-        int maxBonusLevel = AdvancedConfig.getInstance().getMaxBonusLevel(subSkill);
+        int maxBonusLevel = AdvancedConfig.getInstance().getMaxBonusLevel(subSkillType);
 
         switch(subskillActivationType)
         {
             //100 Skill = Guaranteed
             case RANDOM_LINEAR_100_SCALE_NO_CAP:
-                return performRandomSkillCheck(subSkill, player, skillLevel, PerksUtils.handleLuckyPerks(player, skill), 100.0D, 100);
+                return performRandomSkillCheck(subSkillType, player, skillLevel, PerksUtils.handleLuckyPerks(player, skill), 100.0D, 100);
             case RANDOM_LINEAR_100_SCALE_WITH_CAP:
-                return performRandomSkillCheck(subSkill, player, skillLevel, PerksUtils.handleLuckyPerks(player, skill), AdvancedConfig.getInstance().getMaxChance(subSkill), AdvancedConfig.getInstance().getMaxBonusLevel(subSkill));
+                return performRandomSkillCheck(subSkillType, player, skillLevel, PerksUtils.handleLuckyPerks(player, skill), maxChance, maxBonusLevel);
             case RANDOM_STATIC_CHANCE:
                 //Grab the static activation chance of this skill
-                double staticRoll = getSecondaryAbilityStaticChance(subSkill) / activationChance;
-                return performRandomSkillCheckStatic(subSkill, player, activationChance, staticRoll);
+                double staticRoll = getSecondaryAbilityStaticChance(subSkillType) / activationChance;
+                return performRandomSkillCheckStatic(subSkillType, player, activationChance, staticRoll);
             case ALWAYS_FIRES:
-                SubSkillEvent event = EventUtils.callSubSkillEvent(player, subSkill);
+                SubSkillEvent event = EventUtils.callSubSkillEvent(player, subSkillType);
                 return !event.isCancelled();
                 default:
                     return false;
         }
     }
 
+    /* NEW VERSION */
+    public static boolean isActivationSuccessful(SkillActivationType skillActivationType, AbstractSubSkill abstractSubSkill, Player player, double maxChance, int maxBonusLevel)
+    {
+        int skillLevel = UserManager.getPlayer(player).getSkillLevel(abstractSubSkill.getPrimarySkill());
+        PrimarySkill skill = abstractSubSkill.getPrimarySkill();
+
+        switch(skillActivationType)
+        {
+            //100 Skill = Guaranteed
+            case RANDOM_LINEAR_100_SCALE_NO_CAP:
+                return performRandomSkillCheck(abstractSubSkill, player, skillLevel, PerksUtils.handleLuckyPerks(player, skill), 100.0D, 100);
+            case RANDOM_LINEAR_100_SCALE_WITH_CAP:
+                return performRandomSkillCheck(abstractSubSkill, player, skillLevel, PerksUtils.handleLuckyPerks(player, skill), maxChance, maxBonusLevel);
+            case RANDOM_STATIC_CHANCE:
+                //TODO: Add this in for the new system
+                //Grab the static activation chance of this skill
+                //double staticRoll = getSecondaryAbilityStaticChance(subSkillType) / activationChance;
+                //return performRandomSkillCheckStatic(subSkillType, player, activationChance, staticRoll);
+                return false;
+            case ALWAYS_FIRES:
+                SubSkillEvent event = EventUtils.callSubSkillEvent(player, abstractSubSkill);
+                return !event.isCancelled();
+            default:
+                return false;
+        }
+    }
+
+    public static boolean isActivationSuccessful(SkillActivationType skillActivationType, AbstractSubSkill abstractSubSkill, Player player)
+    {
+        //Maximum chance to succeed
+        RandomChance randomChance = (RandomChance) abstractSubSkill;
+        double maxChance = randomChance.getRandomChanceMaxChance();
+
+        //Maximum roll we can make
+        int maxBonusLevel = randomChance.getRandomChanceMaxBonus();
+        int skillLevel = UserManager.getPlayer(player).getSkillLevel(abstractSubSkill.getPrimarySkill());
+        PrimarySkill skill = abstractSubSkill.getPrimarySkill();
+
+        switch(skillActivationType)
+        {
+            //100 Skill = Guaranteed
+            case RANDOM_LINEAR_100_SCALE_NO_CAP:
+                return performRandomSkillCheck(abstractSubSkill, player, skillLevel, PerksUtils.handleLuckyPerks(player, skill), 100.0D, 100);
+            case RANDOM_LINEAR_100_SCALE_WITH_CAP:
+                return performRandomSkillCheck(abstractSubSkill, player, skillLevel, PerksUtils.handleLuckyPerks(player, skill), maxChance, maxBonusLevel);
+            case RANDOM_STATIC_CHANCE:
+                //TODO: Add this in for the new system
+                //Grab the static activation chance of this skill
+                //double staticRoll = getSecondaryAbilityStaticChance(subSkillType) / activationChance;
+                //return performRandomSkillCheckStatic(subSkillType, player, activationChance, staticRoll);
+                return false;
+            case ALWAYS_FIRES:
+                SubSkillEvent event = EventUtils.callSubSkillEvent(player, abstractSubSkill);
+                return !event.isCancelled();
+            default:
+                return false;
+        }
+    }
+
     /**
      * Grabs static activation rolls for Secondary Abilities
-     * @param subSkill The secondary ability to grab properties of
+     * @param subSkillType The secondary ability to grab properties of
      * @return The static activation roll involved in the RNG calculation
      */
-    public static double getSecondaryAbilityStaticChance(SubSkill subSkill)
+    public static double getSecondaryAbilityStaticChance(SubSkillType subSkillType)
     {
-        switch(subSkill)
+        switch(subSkillType)
         {
             case AXES_ARMOR_IMPACT:
                 return AdvancedConfig.getInstance().getImpactChance();
@@ -280,20 +392,27 @@ public class SkillUtils {
 
     /**
      * Used to determine whether or not a sub-skill activates from random chance (using static values)
-     * @param subSkill The identifier for this specific sub-skill
+     * @param subSkillType The identifier for this specific sub-skill
      * @param player The owner of this sub-skill
      * @param activationChance This is the value that we roll against, 100 is normal, and 75 is for lucky perk
      * @param chance This is the static modifier for our random calculations
      * @return true if random chance was successful and the event wasn't cancelled
      */
-    private static boolean performRandomSkillCheckStatic(SubSkill subSkill, Player player, int activationChance, double chance) {
-        SubSkillWeightedActivationCheckEvent event = new SubSkillWeightedActivationCheckEvent(player, subSkill, chance);
+    private static boolean performRandomSkillCheckStatic(SubSkillType subSkillType, Player player, int activationChance, double chance) {
+        SubSkillWeightedActivationCheckEvent event = new SubSkillWeightedActivationCheckEvent(player, subSkillType, chance);
+        mcMMO.p.getServer().getPluginManager().callEvent(event);
+        return (event.getChance() * activationChance) > Misc.getRandom().nextInt(activationChance) && !event.isCancelled();
+    }
+
+    /* NEW VERSION */
+    private static boolean performRandomSkillCheckStatic(AbstractSubSkill abstractSubSkill, Player player, int activationChance, double chance) {
+        SubSkillWeightedActivationCheckEvent event = new SubSkillWeightedActivationCheckEvent(player, abstractSubSkill, chance);
         mcMMO.p.getServer().getPluginManager().callEvent(event);
         return (event.getChance() * activationChance) > Misc.getRandom().nextInt(activationChance) && !event.isCancelled();
     }
 
     public static boolean treasureDropSuccessful(Player player, double dropChance, int activationChance) {
-        SubSkillWeightedActivationCheckEvent event = new SubSkillWeightedActivationCheckEvent(player, SubSkill.EXCAVATION_TREASURE_HUNTER, dropChance / activationChance);
+        SubSkillWeightedActivationCheckEvent event = new SubSkillWeightedActivationCheckEvent(player, SubSkillType.EXCAVATION_TREASURE_HUNTER, dropChance / activationChance);
         mcMMO.p.getServer().getPluginManager().callEvent(event);
         return (event.getChance() * activationChance) > (Misc.getRandom().nextDouble() * activationChance) && !event.isCancelled();
     }

+ 96 - 0
src/main/resources/advanced.yml

@@ -14,6 +14,15 @@
 #  Settings for the Skills
 ###
 Skills:
+    Feedback:
+        # Turning these to false will have them printed in chat instead
+        ActionBarNotifications:
+            LevelUps: true
+            ToolReady: true
+            SubSkillInteraction: true
+            SubSkillUnlocked: true
+            SuperAbilityInteraction: true
+            ExperienceGain: true
     General:
         Ability:
             # IncreaseLevel: This setting will determine when the length of every ability gets longer with 1 second
@@ -659,6 +668,93 @@ Skills:
                     LevelReq: 35
                 Rank_3:
                     LevelReq: 65
+Style:
+    JSON:
+        Notification:
+                LevelUps:
+                    Bold: true
+                    Italics: false
+                    Underline: false
+                ToolReady:
+                    Bold: false
+                    Italics: true
+                    Underline: false
+                SubSkillInteraction:
+                    Bold: false
+                    Italics: true
+                    Underline: false
+                SubSkillUnlocked:
+                    Bold: true
+                    Italics: false
+                    Underline: false
+                SuperAbilityInteraction:
+                    Bold: true
+                    Italics: false
+                    Underline: false
+                ExperienceGain:
+                    Bold: true
+                    Italics: false
+                    Underline: false
+        Hover:
+            Details:
+                Header:
+                    Bold: true
+                    Italics: false
+                    Underline: false
+                    Color: DARK_GRAY
+                Description:
+                    Bold: true
+                    Italics: false
+                    Underline: false
+                    Color: YELLOW
+                Target:
+                    Enable: true
+                    Prefix:
+                        Bold: false
+                        Italics: false
+                        Underline: false
+                        Color: YELLOW
+                    Value:
+                        Bold: false
+                        Italics: false
+                        Underline: false
+                        Color: YELLOW
+                Duration:
+                    Prefix:
+                        Bold: false
+                        Italics: false
+                        Underline: false
+                        Color: GOLD
+                    Value:
+                        Bold: false
+                        Italics: false
+                        Underline: false
+                        Color: GREEN
+                Reward:
+                    Prefix:
+                        Bold: false
+                        Italics: false
+                        Underline: false
+                        Color: GOLD
+                    Value:
+                        Bold: false
+                        Italics: false
+                        Underline: false
+                        Color: GREEN
+                Limit:
+                    Prefix:
+                        Bold: false
+                        Italics: false
+                        Underline: false
+                        Color: GOLD
+                    Value:
+                        Bold: false
+                        Italics: false
+                        Underline: false
+                        Color: GREEN
+
+
+
 
 #
 #  Customize the kraken!

+ 9 - 0
src/main/resources/coreskills.yml

@@ -0,0 +1,9 @@
+#
+# This file includes a few settings for each skill in mcMMO
+
+#Acrobatics
+Acrobatics:
+  # turn this to false to disable all subskills for this skill
+  Enabled: true
+  Roll:
+    Enabled: true

+ 3 - 3
src/main/resources/locale/locale_cs_CZ.properties

@@ -8,8 +8,8 @@ Acrobatics.SubSkill.GracefulRoll.Description=Dvojt\u00e1 effectivita ne\u017e no
 Acrobatics.SubSkill.Dodge.Name=\u00dahyb
 Acrobatics.SubSkill.Dodge.Description=Sn\u00ed\u017een\u00e9 zran\u011bn\u00ed o polovinu
 Acrobatics.Listener=Akrobacie:
-Acrobatics.Roll.Chance=[[RED]]\u0160ance na kotoul: [[YELLOW]]{0}
-Acrobatics.Roll.GraceChance=[[RED]]\u0160ance na \u0160\u0165astn\u00fd kotoul: [[YELLOW]]{0}
+Acrobatics.SubSkill.Roll.Chance=[[RED]]\u0160ance na kotoul: [[YELLOW]]{0}
+Acrobatics.SubSkill.Roll.GraceChance=[[RED]]\u0160ance na \u0160\u0165astn\u00fd kotoul: [[YELLOW]]{0}
 Acrobatics.Roll.Text=**Valis se**
 Acrobatics.SkillName=AKROBACIE
 Acrobatics.Skillup=[[YELLOW]]Dovednost akrobacie byla navysena o {0}. Celkem ({1})
@@ -544,7 +544,7 @@ Effects.Child=[[DARK_GRAY]]LVL: [[GREEN]]{0}
 Effects.Level=[[DARK_GRAY]]LVL: [[GREEN]]{0} [[DARK_AQUA]]XP[[YELLOW]]([[GOLD]]{1}[[YELLOW]]/[[GOLD]]{2}[[YELLOW]])
 Effects.Parent=[[GOLD]]{0} -
 Effects.Template=[[DARK_AQUA]]{0}: [[GREEN]]{1}
-Guides.Available=[[DARK_AQUA]]N\u00e1vod k {0} - napi\u0161te /{1} ? [page]
+Guides.Available=[[GRAY]]N\u00e1vod k {0} - napi\u0161te /{1} ? [page]
 Guides.Header=[[GOLD]]-=[[GREEN]]{0} N\u00e1vod[[GOLD]]=-
 Guides.Page.Invalid=Nespr\u00e1vn\u00e9 \u010d\u00edslo str\u00e1nky!
 Guides.Page.OutOfRange=Tato str\u00e1nka neexistuje, je tu pouze {0} str\u00e1nek.

+ 3 - 3
src/main/resources/locale/locale_cy.properties

@@ -8,8 +8,8 @@ Acrobatics.SubSkill.GracefulRoll.Description=Twice as effective as a normal Roll
 Acrobatics.SubSkill.Dodge.Name=Dodge
 Acrobatics.SubSkill.Dodge.Description=Reduce attack damage by half
 Acrobatics.Listener=Acrobateg
-Acrobatics.Roll.Chance=[[RED]]Roll Chance: [[YELLOW]]{0}
-Acrobatics.Roll.GraceChance=[[RED]]Graceful Roll Chance: [[YELLOW]]{0}
+Acrobatics.SubSkill.Roll.Chance=[[RED]]Roll Chance: [[YELLOW]]{0}
+Acrobatics.SubSkill.Roll.GraceChance=[[RED]]Graceful Roll Chance: [[YELLOW]]{0}
 Acrobatics.Roll.Text=**Rolled**
 Acrobatics.SkillName=ACROBATEG
 Acrobatics.Skillup=[[YELLOW]] Acrobateg sgil cynyddu {0}. Cyfanswm ({1})
@@ -426,7 +426,7 @@ XPRate.Event=[[GOLD]] mcMMO ar hyn o bryd mewn digwyddiad gyfradd XP! Gyfradd yn
 Effects.Effects=EFFEITHIAU
 Effects.Level=[[DARK_GRAY]]LVL: [[GREEN]]{0} [[DARK_AQUA]]XP[[YELLOW]]([[GOLD]]{1}[[YELLOW]]/[[GOLD]]{2}[[YELLOW]])
 Effects.Template=[[DARK_AQUA]]{0}: [[GREEN]]{1}
-Guides.Available=[[DARK_AQUA]]Guide for {0} available - type /{1} ? [page]
+Guides.Available=[[GRAY]]Guide for {0} available - type /{1} ? [page]
 Guides.Header=[[GOLD]]-=[[GREEN]]{0} Guide[[GOLD]]=-
 Guides.Page.Invalid=Not a valid page number!
 Guides.Usage=[[RED]] Usage is /{0} ? [page]

+ 3 - 3
src/main/resources/locale/locale_da.properties

@@ -8,8 +8,8 @@ Acrobatics.SubSkill.GracefulRoll.Description=Dobbelt s\u00e5 effektiv som en nor
 Acrobatics.SubSkill.Dodge.Name=Afv\u00e6rg
 Acrobatics.SubSkill.Dodge.Description=Reducer angrebs skade med halvdelen
 Acrobatics.Listener=Akrobatik:
-Acrobatics.Roll.Chance=[[RED]]Rulle Chance: [[YELLOW]]{0}
-Acrobatics.Roll.GraceChance=[[RED]]Yndefuldt Rul Chance: [[YELLOW]]{0}
+Acrobatics.SubSkill.Roll.Chance=[[RED]]Rulle Chance: [[YELLOW]]{0}
+Acrobatics.SubSkill.Roll.GraceChance=[[RED]]Yndefuldt Rul Chance: [[YELLOW]]{0}
 Acrobatics.Roll.Text=**Rullede**
 Acrobatics.SkillName=AKROBATIK
 Acrobatics.Skillup=[[YELLOW]]Akrobatik evne for\u00f8get med {0}. Total ({1})
@@ -427,7 +427,7 @@ XPRate.Event=[[GOLD]]mcMMO er lige nu i gang med et XP bed\u00f8mnings begivenhe
 Effects.Effects=Effekter
 Effects.Level=[[DARK_GRAY]]LVL: [[GREEN]]{0} [[DARK_AQUA]]XP[[YELLOW]]([[GOLD]]{1}[[YELLOW]]/[[GOLD]]{2}[[YELLOW]])
 Effects.Template=[[DARK_AQUA]]{0}: [[GREEN]]{1}
-Guides.Available=[[DARK_AQUA]]Guide for {0} tilg\u00e6ngelige - skriv /{1} ? [page]
+Guides.Available=[[GRAY]]Guide for {0} tilg\u00e6ngelige - skriv /{1} ? [page]
 Guides.Header=[[GOLD]]-=[[GREEN]]{0} Guide[[GOLD]]=-
 Guides.Page.Invalid=Ikke et gyldigt side nummer!
 Guides.Usage=[[RED]] Korrekt brug er /{0} ? [page]

+ 4 - 4
src/main/resources/locale/locale_de.properties

@@ -23,7 +23,7 @@
 #-Salvage aus Repair herausgenommen
 #-Added missing Swords.Effect skills
 #-Added horses and holy hound to taming
-#-Added Unarmed.SubSkill.IronGrip.Description
+#-Added Unarmed.SubSkillType.IronGrip.Description
 #-Added Smelting
 #Commands.addlevels
 #Everything except parties translated to German
@@ -42,8 +42,8 @@ Acrobatics.SubSkill.GracefulRoll.Description=Doppelte Effektivit\u00e4t der Roll
 Acrobatics.SubSkill.Dodge.Name=Ausweichen
 Acrobatics.SubSkill.Dodge.Description=Halbiert Angriffschaden
 Acrobatics.Listener=Akrobatik:
-Acrobatics.Roll.Chance=[[RED]]Rolle-Chance: [[YELLOW]]{0}
-Acrobatics.Roll.GraceChance=[[RED]]Grazi\u00f6se Rolle-Chance: [[YELLOW]]{0}
+Acrobatics.SubSkill.Roll.Chance=[[RED]]Rolle-Chance: [[YELLOW]]{0}
+Acrobatics.SubSkill.Roll.GraceChance=[[RED]]Grazi\u00f6se Rolle-Chance: [[YELLOW]]{0}
 Acrobatics.Roll.Text=**Abgerollt**
 Acrobatics.SkillName=AKROBATIK
 Acrobatics.Skillup=[[YELLOW]]Akrobatik Skill um {0} gestiegen. Gesamt ({1})
@@ -720,7 +720,7 @@ Effects.Parent = [[GOLD]]{0} -
 Effects.Template=[[DARK_AQUA]]{0}: [[GREEN]]{1}
 
 #GUIDES
-Guides.Available=[[DARK_AQUA]]Anleitung f\u00FCr {0} vorhanden - tippe /{1} ? [Seite]
+Guides.Available=[[GRAY]]Anleitung f\u00FCr {0} vorhanden - tippe /{1} ? [Seite]
 Guides.Header=[[GOLD]]-=[[GREEN]]{0} Anleitung[[GOLD]]=-
 Guides.Page.Invalid=Keine g\u00FCltige Seitenzahl!
 Guides.Page.OutOfRange=Es gibt nur insgesamt {0} Seiten.

+ 35 - 7
src/main/resources/locale/locale_en_US.properties

@@ -1,9 +1,13 @@
-#They are literally charging me by linecount so I'm reducing the # of lines
+#I'm going to try to normalize our locale file, forgive the mess for now.
 
+#DO NOT USE COLOR CODES IN THE JSON KEYS
+#COLORS ARE DEFINED IN advanced.yml IF YOU WISH TO CHANGE THEM
 JSON.Rank=Rank
 JSON.RankPossesive=of
 JSON.DescriptionHeader=Description
-JSON.Activation=How to use
+JSON.JWrapper.Header=Details
+JSON.JWrapper.Activation=Activation:
+JSON.JWrapper.Activation.Type.RightClick=Right Click
 JSON.Type.Passive=Passive
 JSON.Type.Active=Active
 JSON.Type.SuperAbility=Super Ability
@@ -11,19 +15,40 @@ JSON.SuperAbility.Charges=Charges
 JSON.SuperAbility.Duration=Duration
 JSON.Locked=-=[LOCKED]=-
 JSON.LevelRequirement=Level Requirement
+JSON.JWrapper.Random.ActivationChance=Activation Chance:
+JSON.JWrapper.Random.MaxChance=Max Bonus:
+JSON.JWrapper.Duration=Duration:
+JSON.JWrapper.Target.Type=Target Type:
+JSON.JWrapper.Target.Block=Block
+JSON.JWrapper.Target.Player=Player
+JSON.JWrapper.Target.Mobs=Mobs
+JSON.JWrapper.Perks.Header=[[GOLD]]Lucky Perks
+JSON.JWrapper.Perks.Lucky={0}% Better Odds
+JSON.Hover.Tips=Tips
+
+#This is the message sent to players when an ability is activated
+JSON.Notification.SuperAbility={0}
+
+#These are the JSON Strings used for SubSkills
+JSON.Acrobatics.Roll.Interaction.Activated=Test [[RED]]Rolled Test
+JSON.Acrobatics.SubSkill.Roll.Details.Tips=If you hold sneak while falling you can prevent up to twice the damage that you would normally take!
+
+#DO NOT USE COLOR CODES IN THE JSON KEYS
+#COLORS ARE DEFINED IN advanced.yml IF YOU WISH TO CHANGE THEM
+
 #ACROBATICS
 Acrobatics.Ability.Proc=[[GREEN]]**Graceful Landing**
 Acrobatics.Combat.Proc=[[GREEN]]**Dodged**
 Acrobatics.DodgeChance=[[RED]]Dodge Chance: [[YELLOW]]{0}
 Acrobatics.SubSkill.Roll.Name=Roll
 Acrobatics.SubSkill.Roll.Description=Reduces or Negates fall damage
+Acrobatics.SubSkill.Roll.Chance=[[RED]]Roll Chance: [[YELLOW]]{0}
+Acrobatics.SubSkill.Roll.GraceChance=[[RED]]Graceful Roll Chance: [[YELLOW]]{0}
 Acrobatics.SubSkill.GracefulRoll.Name=Graceful Roll
 Acrobatics.SubSkill.GracefulRoll.Description=Twice as effective as a normal Roll
 Acrobatics.SubSkill.Dodge.Name=Dodge
 Acrobatics.SubSkill.Dodge.Description=Reduce attack damage by half
 Acrobatics.Listener=Acrobatics:
-Acrobatics.Roll.Chance=[[RED]]Roll Chance: [[YELLOW]]{0}
-Acrobatics.Roll.GraceChance=[[RED]]Graceful Roll Chance: [[YELLOW]]{0}
 Acrobatics.Roll.Text=**Rolled**
 Acrobatics.SkillName=ACROBATICS
 Acrobatics.Skillup=[[YELLOW]]Acrobatics skill increased by {0}. Total ({1})
@@ -566,7 +591,7 @@ Commands.Skill.Invalid=[[RED]]That is not a valid skillname!
 Commands.Skill.ChildSkill=[[RED]]Child skills are not valid for this command!
 Commands.Skill.Leaderboard=[[YELLOW]]--mcMMO [[BLUE]]{0}[[YELLOW]] Leaderboard--
 Commands.SkillInfo=[[GREEN]]- View detailed information about a skill
-Commands.Stats.Self=YOUR STATS
+Commands.Stats.Self.Overhaul=Stats
 Commands.Stats=[[GREEN]]- View your mcMMO stats
 Commands.ToggleAbility=[[GREEN]]- Toggle ability activation with right click
 Commands.Usage.0=[[RED]]Proper usage is /{0}
@@ -678,6 +703,7 @@ Commands.XPGain.Taming=Animal Taming, or combat w/ your wolves
 Commands.XPGain.Unarmed=Attacking Monsters
 Commands.XPGain.Woodcutting=Chopping down trees
 Commands.XPGain=[[DARK_GRAY]]XP GAIN: [[WHITE]]{0}
+Commands.XPGain.Overhaul=[[YELLOW]]XP GAIN: [[WHITE]]{0}
 Commands.xplock.locked=[[GOLD]]Your XP BAR is now locked to {0}!
 Commands.xplock.unlocked=[[GOLD]]Your XP BAR is now [[GREEN]]UNLOCKED[[GOLD]]!
 Commands.xprate.modified=[[RED]]The XP RATE was modified to {0}
@@ -691,13 +717,14 @@ XPRate.Event= [[GOLD]]mcMMO is currently in an XP rate event! XP rate is {0}x!
 #EFFECTS
 ##generic
 Effects.Effects=EFFECTS
-Effects.SubSkills=SUB-SKILLS
+Effects.SubSkills.Overhaul=Sub-Skills
 Effects.Child=[[DARK_GRAY]]LVL: [[GREEN]]{0}
 Effects.Level=[[DARK_GRAY]]LVL: [[GREEN]]{0} [[DARK_AQUA]]XP[[YELLOW]]([[GOLD]]{1}[[YELLOW]]/[[GOLD]]{2}[[YELLOW]])
+Effects.Level.Overhaul=[[RED]]LVL: [[GREEN]]{0} [[DARK_AQUA]]XP[[YELLOW]]([[GOLD]]{1}[[YELLOW]]/[[GOLD]]{2}[[YELLOW]])
 Effects.Parent = [[GOLD]]{0} -
 Effects.Template=[[DARK_AQUA]]{0}: [[GREEN]]{1}
 #GUIDES
-Guides.Available=[[DARK_AQUA]]Guide for {0} available - type /{1} ? [page]
+Guides.Available=[[GRAY]]Guide for {0} available - type /{1} ? [page]
 Guides.Header=[[GOLD]]-=[[GREEN]]{0} Guide[[GOLD]]=-
 Guides.Page.Invalid=Not a valid page number!
 Guides.Page.OutOfRange=That page does not exist, there are only {0} total pages.
@@ -820,6 +847,7 @@ Teleport.Cancelled=[[DARK_RED]]Teleportation canceled!
 Skills.Child=[[GOLD]](CHILD SKILL)
 Skills.Disarmed=[[DARK_RED]]You have been disarmed!
 Skills.Header=[[RED]]-----[] [[GREEN]]{0}[[RED]] []-----
+Skills.Overhaul.Header=[[DARK_AQUA]][]=====[][[GOLD]] {0} [[DARK_AQUA]][]=====[]
 Skills.NeedMore=[[DARK_RED]]You need more [[GRAY]]{0}
 Skills.Parents = PARENTS
 Skills.Stats=[[YELLOW]]{0}[[GREEN]]{1}[[DARK_AQUA]] XP([[GRAY]]{2}[[DARK_AQUA]]/[[GRAY]]{3}[[DARK_AQUA]])

+ 3 - 3
src/main/resources/locale/locale_es.properties

@@ -8,8 +8,8 @@ Acrobatics.SubSkill.GracefulRoll.Description=El doble de efectivo que una rodada
 Acrobatics.SubSkill.Dodge.Name=Esquivar
 Acrobatics.SubSkill.Dodge.Description=Reduce el da\u00f1o de ataque a la mitad
 Acrobatics.Listener=Acrobacias:
-Acrobatics.Roll.Chance=[[RED]]Probabilidad de Rodar: [[YELLOW]]{0}%
-Acrobatics.Roll.GraceChance=[[RED]]Probabilidad de Rodada Majestuosa: [[YELLOW]]{0}%
+Acrobatics.SubSkill.Roll.Chance=[[RED]]Probabilidad de Rodar: [[YELLOW]]{0}%
+Acrobatics.SubSkill.Roll.GraceChance=[[RED]]Probabilidad de Rodada Majestuosa: [[YELLOW]]{0}%
 Acrobatics.Roll.Text=**Rodado**
 Acrobatics.SkillName=ACROBACIAS
 Acrobatics.Skillup=[[YELLOW]]Habilidad de Acrobacias incrementada en {0}. Total ({1})
@@ -557,7 +557,7 @@ Effects.Child=[[DARK_GRAY]]NIVEL: [[GREEN]]{0}
 Effects.Level=[[DARK_GRAY]]Nivel: [[GREEN]]{0} [[DARK_AQUA]]EXP[[YELLOW]]([[GOLD]]{1}[[YELLOW]]/[[GOLD]]{2}[[YELLOW]])
 Effects.Parent=[[GOLD]]{0} -
 Effects.Template=[[DARK_AQUA]]{0}: [[GREEN]]{1}
-Guides.Available=[[DARK_AQUA]]Guia para {0} disponible - tipo /{1} ? [page]
+Guides.Available=[[GRAY]]Guia para {0} disponible - tipo /{1} ? [page]
 Guides.Header=[[GOLD]]-=[[GREEN]]{0} Guia[[GOLD]]=-
 Guides.Page.Invalid=Numero de pagina no disponible
 Guides.Page.OutOfRange=La pagina no existe, solo hay {0} paginas en total

+ 3 - 3
src/main/resources/locale/locale_fr.properties

@@ -8,8 +8,8 @@ Acrobatics.SubSkill.GracefulRoll.Description=Deux fois plus efficace qu\'une rou
 Acrobatics.SubSkill.Dodge.Name=Esquive
 Acrobatics.SubSkill.Dodge.Description=R\u00e9duit de moiti\u00e9 les d\u00e9g\u00e2ts re\u00e7us
 Acrobatics.Listener=Acrobatie :
-Acrobatics.Roll.Chance=[[RED]]Probabilit\u00e9 de roulade : [[YELLOW]]{0}
-Acrobatics.Roll.GraceChance=[[RED]]Probabilit\u00e9 de roulade gracieuse : [[YELLOW]]{0}
+Acrobatics.SubSkill.Roll.Chance=[[RED]]Probabilit\u00e9 de roulade : [[YELLOW]]{0}
+Acrobatics.SubSkill.Roll.GraceChance=[[RED]]Probabilit\u00e9 de roulade gracieuse : [[YELLOW]]{0}
 Acrobatics.Roll.Text=**ROULADE**
 Acrobatics.SkillName=ACROBATIE
 Acrobatics.Skillup=[[YELLOW]]Le talent Acrobatie augmente de {0}. Total ({1})
@@ -656,7 +656,7 @@ Effects.Child=[[DARK_GRAY]]NIVEAU: [[GREEN]]{0}
 Effects.Level=[[DARK_GRAY]]NIV : [[GREEN]]{0} [[DARK_AQUA]]XP[[YELLOW]]([[GOLD]]{1}[[YELLOW]]/[[GOLD]]{2}[[YELLOW]])
 Effects.Parent=[[GOLD]]{0} -
 Effects.Template=[[DARK_AQUA]]{0} : [[GREEN]]{1}
-Guides.Available=[[DARK_AQUA]]Guide pour le/la {0} est disponible - \u00e9crivez /{1} ? [page]
+Guides.Available=[[GRAY]]Guide pour le/la {0} est disponible - \u00e9crivez /{1} ? [page]
 Guides.Header=[[GOLD]]-=[[GREEN]]{0} Guide[[GOLD]]=-
 Guides.Page.Invalid=Le num\u00e9ro de page est invalide
 Guides.Page.OutOfRange=Cette page n\'existe pas, il y a seulement {0} pages totales.

+ 3 - 3
src/main/resources/locale/locale_it.properties

@@ -9,8 +9,8 @@ Acrobatics.SubSkill.GracefulRoll.Description=Il doppio pi\u00F9 efficiente di un
 Acrobatics.SubSkill.Dodge.Name=Schivata
 Acrobatics.SubSkill.Dodge.Description=Riduce il danno degli attacchi della met\u00E0
 Acrobatics.Listener=Acrobatica:
-Acrobatics.Roll.Chance=[[RED]]Possibilit\u00E0 di Capriola: [[YELLOW]]{0}
-Acrobatics.Roll.GraceChance=[[RED]]Possibilit\u00E0 di Capriola Aggraziata: [[YELLOW]]{0}
+Acrobatics.SubSkill.Roll.Chance=[[RED]]Possibilit\u00E0 di Capriola: [[YELLOW]]{0}
+Acrobatics.SubSkill.Roll.GraceChance=[[RED]]Possibilit\u00E0 di Capriola Aggraziata: [[YELLOW]]{0}
 Acrobatics.Roll.Text=**Capriola Eseguita**
 Acrobatics.SkillName=ACROBATICA
 Acrobatics.Skillup=[[YELLOW]]L'abilit\u00E0 Acrobatica \u00E8 aumentata di {0}. Totale ({1})
@@ -688,7 +688,7 @@ Effects.Parent = [[GOLD]]{0} -
 Effects.Template=[[DARK_AQUA]]{0}: [[GREEN]]{1}
 
 #GUIDES
-Guides.Available=[[DARK_AQUA]]Guida per {0} disponibile - digita /{1} ? [pagina]
+Guides.Available=[[GRAY]]Guida per {0} disponibile - digita /{1} ? [pagina]
 Guides.Header=[[GOLD]]-=[[GREEN]]{0} Guida[[GOLD]]=-
 Guides.Page.Invalid=Non \u00E8 un numero di pagina valido!
 Guides.Page.OutOfRange=Quella pagina non esiste, ci sono solo {0} pagine in totale.

+ 3 - 3
src/main/resources/locale/locale_ko.properties

@@ -26,8 +26,8 @@ Acrobatics.SubSkill.GracefulRoll.Description=\uAD6C\uB974\uAE30 2\uBC30 \uD6A8\u
 Acrobatics.SubSkill.Dodge.Name=\uD68C\uD53C
 Acrobatics.SubSkill.Dodge.Description=\uB099\uD558 \uB370\uBBF8\uC9C0 \uC808\uBC18 \uAC10\uC18C
 Acrobatics.Listener=\uACE1\uC608(ACROBATICS):
-Acrobatics.Roll.Chance=[[RED]]\uAD6C\uB974\uAE30 \uD655\uB960: [[YELLOW]]{0}
-Acrobatics.Roll.GraceChance=[[RED]]\uC6B0\uC544\uD55C \uAD6C\uB974\uAE30 \uD655\uB960: [[YELLOW]]{0}
+Acrobatics.SubSkill.Roll.Chance=[[RED]]\uAD6C\uB974\uAE30 \uD655\uB960: [[YELLOW]]{0}
+Acrobatics.SubSkill.Roll.GraceChance=[[RED]]\uC6B0\uC544\uD55C \uAD6C\uB974\uAE30 \uD655\uB960: [[YELLOW]]{0}
 Acrobatics.Roll.Text=**\uAD6C\uB974\uAE30**
 Acrobatics.SkillName=\uACE1\uC608
 Acrobatics.Skillup=[[YELLOW]]\uB099\uBC95 \uAE30\uC220\uC774 {0} \uC62C\uB77C \uCD1D {1} \uB808\uBCA8\uC774 \uB418\uC5C8\uC2B5\uB2C8\uB2E4
@@ -705,7 +705,7 @@ Effects.Parent = [[GOLD]]{0} -
 Effects.Template=[[DARK_AQUA]]{0}: [[GREEN]]{1}
 
 #GUIDES
-Guides.Available=[[DARK_AQUA]]{0} \uAC00\uC774\uB4DC\uAC00 \uC788\uC2B5\uB2C8\uB2E4 - \uD0C0\uC785 /{1} ? [\uD398\uC774\uC9C0]
+Guides.Available=[[GRAY]]{0} \uAC00\uC774\uB4DC\uAC00 \uC788\uC2B5\uB2C8\uB2E4 - \uD0C0\uC785 /{1} ? [\uD398\uC774\uC9C0]
 Guides.Header=[[GOLD]]-=[[GREEN]]{0} \uAC00\uC774\uB4DC[[GOLD]]=-
 Guides.Page.Invalid=\uC62C\uBC14\uB978 \uD398\uC774\uC9C0 \uBC88\uD638\uAC00 \uC544\uB2D9\uB2C8\uB2E4!
 Guides.Page.OutOfRange=\uADF8 \uD398\uC774\uC9C0\uB294 \uC874\uC7AC\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4, \uC624\uC9C1 \uCD1D {0} \uD398\uC774\uC9C0\uAC00 \uC788\uC2B5\uB2C8\uB2E4.

+ 2 - 2
src/main/resources/locale/locale_nl.properties

@@ -5,8 +5,8 @@ Acrobatics.SubSkill.Roll.Name=Rollen
 Acrobatics.SubSkill.GracefulRoll.Name=Veilige Roll
 Acrobatics.SubSkill.Dodge.Name=Ontwijken
 Acrobatics.Listener=Acrobatiek
-Acrobatics.Roll.Chance=[[RED]]Rol Kans: [[YELLOW]]{0}
-Acrobatics.Roll.GraceChance=[[RED]]Elegante Rol Kans: [[YELLOW]]{0}
+Acrobatics.SubSkill.Roll.Chance=[[RED]]Rol Kans: [[YELLOW]]{0}
+Acrobatics.SubSkill.Roll.GraceChance=[[RED]]Elegante Rol Kans: [[YELLOW]]{0}
 Acrobatics.Roll.Text=**Gerold**
 Acrobatics.SkillName=ACROBATIEK
 Acrobatics.Skillup=[[YELLOW]]Acrobatiek toegenomen met {0}. Totaal ({1})

+ 3 - 3
src/main/resources/locale/locale_pl.properties

@@ -8,8 +8,8 @@ Acrobatics.SubSkill.GracefulRoll.Description=Dwukrotnie bardziej efektywne niz z
 Acrobatics.SubSkill.Dodge.Name=Unik
 Acrobatics.SubSkill.Dodge.Description=Redukuje obra\u017cenia o po\u0142owe
 Acrobatics.Listener=Akrobatyka:
-Acrobatics.Roll.Chance=[[RED]]Szansa na Przewr\u00f3t: [[YELLOW]]{0}
-Acrobatics.Roll.GraceChance=[[RED]]Szansa na \u0141agodny Przewr\u00f3t: [[YELLOW]]{0}
+Acrobatics.SubSkill.Roll.Chance=[[RED]]Szansa na Przewr\u00f3t: [[YELLOW]]{0}
+Acrobatics.SubSkill.Roll.GraceChance=[[RED]]Szansa na \u0141agodny Przewr\u00f3t: [[YELLOW]]{0}
 Acrobatics.Roll.Text=**Przewr\u00f3t**
 Acrobatics.SkillName=AKROBATYKA
 Acrobatics.Skillup=[[YELLOW]]Umiejetnosc akrobatyka wzrosla o {0}. Razem ({1})
@@ -508,7 +508,7 @@ Effects.Child=[[DARK_GRAY]]LVL: [[GREEN]]{0}
 Effects.Level=[[DARK_GRAY]]Poziom: [[GREEN]]{0} [[DARK_AQUA]]Doswiadczenie[[YELLOW]]([[GOLD]]{1}[[YELLOW]]/[[GOLD]]{2}[[YELLOW]])
 Effects.Parent=[[GOLD]]{0} -
 Effects.Template=[[DARK_AQUA]]{0}: [[GREEN]]{1}
-Guides.Available=[[DARK_AQUA]]Przewodnik dla {0} jest dost\u0119pny - wpisz /{1} ? [strona]
+Guides.Available=[[GRAY]]Przewodnik dla {0} jest dost\u0119pny - wpisz /{1} ? [strona]
 Guides.Header=[[GOLD]]-=[[GREEN]]{0} Przewodnik[[GOLD]]=-
 Guides.Page.Invalid=Niew\u0142a\u015bciwa strona!
 Guides.Page.OutOfRange=Ta strona nie istnieje, jest tylko {0} stron.

+ 3 - 3
src/main/resources/locale/locale_ru.properties

@@ -8,8 +8,8 @@ Acrobatics.SubSkill.GracefulRoll.Description=\u042d\u0444\u0444\u0435\u043a\u044
 Acrobatics.SubSkill.Dodge.Name=\u0423\u043a\u043b\u043e\u043d\u0435\u043d\u0438\u0435
 Acrobatics.SubSkill.Dodge.Description=\u0423\u043c\u0435\u043d\u044c\u0448\u0430\u0435\u0442 \u0423\u0440\u043e\u043d \u0432 \u0434\u0432\u0430 \u0440\u0430\u0437\u0430
 Acrobatics.Listener=\u0410\u043a\u0440\u043e\u0431\u0430\u0442\u0438\u043a\u0430:
-Acrobatics.Roll.Chance=[[RED]]\u0428\u0430\u043d\u0441 \u0421\u043a\u043e\u043b\u044c\u0436\u0435\u043d\u0438\u044f: [[YELLOW]]{0} %
-Acrobatics.Roll.GraceChance=[[RED]]\u0428\u0430\u043d\u0441 \u0418\u0437\u044f\u0449\u043d\u043e\u0433\u043e \u0421\u043a\u043e\u043b\u044c\u0436\u0435\u043d\u0438\u044f: [[YELLOW]]{0} %
+Acrobatics.SubSkill.Roll.Chance=[[RED]]\u0428\u0430\u043d\u0441 \u0421\u043a\u043e\u043b\u044c\u0436\u0435\u043d\u0438\u044f: [[YELLOW]]{0} %
+Acrobatics.SubSkill.Roll.GraceChance=[[RED]]\u0428\u0430\u043d\u0441 \u0418\u0437\u044f\u0449\u043d\u043e\u0433\u043e \u0421\u043a\u043e\u043b\u044c\u0436\u0435\u043d\u0438\u044f: [[YELLOW]]{0} %
 Acrobatics.Roll.Text=**\u0421\u043a\u043e\u043b\u044c\u0436\u0435\u043d\u0438\u0435**
 Acrobatics.SkillName=\u0410\u041a\u0420\u041e\u0411\u0410\u0422\u0418\u041a\u0410
 Acrobatics.Skillup=[[YELLOW]]\u0423\u0440\u043e\u0432\u0435\u043d\u044c \u043d\u0430\u0432\u044b\u043a\u0430 \"\u0410\u043a\u0440\u043e\u0431\u0430\u0442\u0438\u043a\u0430\" \u0443\u0432\u0435\u043b\u0438\u0447\u0435\u043d \u043d\u0430 {0}. \u0412\u0441\u0435\u0433\u043e ({1})
@@ -581,7 +581,7 @@ Effects.Child=[[DARK_GRAY]]\u0423\u0420\u041e\u0412\u0415\u041d\u042c: [[GREEN]]
 Effects.Level=[[DARK_GRAY]]\u0423\u0420\u041e\u0412\u0415\u041d\u042c: [[GREEN]]{0} [[DARK_AQUA]]\u041e\u041f\u042b\u0422[[YELLOW]]([[GOLD]]{1}[[YELLOW]]/[[GOLD]]{2}[[YELLOW]])
 Effects.Parent=[[GOLD]]{0} -
 Effects.Template=[[DARK_AQUA]]{0}: [[GREEN]]{1}
-Guides.Available=[[DARK_AQUA]]\u0420\u0443\u043a\u043e\u0432\u043e\u0434\u0441\u0442\u0432\u043e \u0434\u043b\u044f {0} \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e - \u043d\u0430\u043f\u0438\u0448\u0438\u0442\u0435 /{1} ? [\u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430]
+Guides.Available=[[GRAY]]\u0420\u0443\u043a\u043e\u0432\u043e\u0434\u0441\u0442\u0432\u043e \u0434\u043b\u044f {0} \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e - \u043d\u0430\u043f\u0438\u0448\u0438\u0442\u0435 /{1} ? [\u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430]
 Guides.Header=[[GOLD]]-=[[GREEN]]{0} \u0420\u0443\u043a\u043e\u0432\u043e\u0434\u0441\u0442\u0432\u043e[[GOLD]]=-
 Guides.Page.Invalid=\u041d\u0435\u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u044b\u0439 \u043d\u043e\u043c\u0435\u0440 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b!
 Guides.Page.OutOfRange=\u042d\u0442\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442, \u0432\u0441\u0435\u0433\u043e \u0435\u0441\u0442\u044c {0} \u0441\u0442\u0440\u0430\u043d\u0438\u0446.

+ 3 - 3
src/main/resources/locale/locale_th_TH.properties

@@ -8,8 +8,8 @@ Acrobatics.SubSkill.GracefulRoll.Description=\u0e40\u0e1e\u0e34\u0e48\u0e21\u0e1
 Acrobatics.SubSkill.Dodge.Name=Dodge
 Acrobatics.SubSkill.Dodge.Description=\u0e25\u0e14\u0e01\u0e32\u0e23\u0e44\u0e14\u0e49\u0e23\u0e31\u0e1a\u0e04\u0e27\u0e32\u0e21\u0e40\u0e2a\u0e35\u0e22\u0e2b\u0e32\u0e22\u0e04\u0e23\u0e36\u0e48\u0e07\u0e2b\u0e19\u0e36\u0e48\u0e07
 Acrobatics.Listener=\u0e17\u0e31\u0e01\u0e29\u0e30 Acrobatics:
-Acrobatics.Roll.Chance=[[RED]]Roll \u0e42\u0e2d\u0e01\u0e32\u0e2a: [[YELLOW]]{0}
-Acrobatics.Roll.GraceChance=[[RED]]Graceful Roll \u0e42\u0e2d\u0e01\u0e32\u0e2a: [[YELLOW]]{0}
+Acrobatics.SubSkill.Roll.Chance=[[RED]]Roll \u0e42\u0e2d\u0e01\u0e32\u0e2a: [[YELLOW]]{0}
+Acrobatics.SubSkill.Roll.GraceChance=[[RED]]Graceful Roll \u0e42\u0e2d\u0e01\u0e32\u0e2a: [[YELLOW]]{0}
 Acrobatics.Roll.Text=**\u0e43\u0e0a\u0e49\u0e17\u0e31\u0e01\u0e29\u0e30 Rolled**
 Acrobatics.SkillName=ACROBATICS
 Acrobatics.Skillup=[[YELLOW]]\u0e17\u0e31\u0e01\u0e29\u0e30 Acrobatics \u0e40\u0e1e\u0e34\u0e48\u0e21\u0e02\u0e36\u0e49\u0e19 {0}. \u0e21\u0e35\u0e17\u0e31\u0e49\u0e07\u0e2b\u0e21\u0e14 ({1})
@@ -541,7 +541,7 @@ Effects.Child=[[DARK_GRAY]]\u0e23\u0e30\u0e14\u0e31\u0e1a: [[GREEN]]{0}
 Effects.Level=[[DARK_GRAY]]\u0e23\u0e30\u0e14\u0e31\u0e1a: [[GREEN]]{0} [[DARK_AQUA]]EXP[[YELLOW]]([[GOLD]]{1}[[YELLOW]]/[[GOLD]]{2}[[YELLOW]])
 Effects.Parent=[[GOLD]]{0} -
 Effects.Template=[[DARK_AQUA]]{0}: [[GREEN]]{1}
-Guides.Available=[[DARK_AQUA]]\u0e41\u0e19\u0e30\u0e19\u0e33 {0} \u0e04\u0e27\u0e23\u0e43\u0e0a\u0e49 - \u0e0a\u0e19\u0e34\u0e14 /{1} ? [\u0e2b\u0e19\u0e49\u0e32]
+Guides.Available=[[GRAY]]\u0e41\u0e19\u0e30\u0e19\u0e33 {0} \u0e04\u0e27\u0e23\u0e43\u0e0a\u0e49 - \u0e0a\u0e19\u0e34\u0e14 /{1} ? [\u0e2b\u0e19\u0e49\u0e32]
 Guides.Header=[[GOLD]]-=[[GREEN]]{0} \u0e41\u0e19\u0e30\u0e19\u0e33[[GOLD]]=-
 Guides.Page.Invalid=\u0e44\u0e21\u0e48\u0e43\u0e0a\u0e48\u0e15\u0e31\u0e27\u0e40\u0e25\u0e02\u0e17\u0e35\u0e48\u0e16\u0e39\u0e01\u0e15\u0e49\u0e2d\u0e07!
 Guides.Page.OutOfRange=\u0e44\u0e21\u0e48\u0e21\u0e35\u0e2b\u0e19\u0e49\u0e32\u0e19\u0e35\u0e2d\u0e22\u0e39\u0e48 \u0e21\u0e35\u0e40\u0e1e\u0e35\u0e22\u0e07 {0} \u0e2b\u0e19\u0e49\u0e32.

+ 3 - 3
src/main/resources/locale/locale_zh_CN.properties

@@ -8,8 +8,8 @@ Acrobatics.SubSkill.GracefulRoll.Description=\u4e24\u500d\u7684\u7ffb\u6eda\u654
 Acrobatics.SubSkill.Dodge.Name=\u95ea\u907f
 Acrobatics.SubSkill.Dodge.Description=\u51cf\u5c11\u4e00\u534a\u7684\u4f24\u5bb3
 Acrobatics.Listener=\u6742\u6280(Acrobatics):
-Acrobatics.Roll.Chance=[[RED]]\u7ffb\u6eda\u51e0\u7387: [[YELLOW]]{0}
-Acrobatics.Roll.GraceChance=[[RED]]\u4f18\u96c5\u7684\u7ffb\u6eda\u51e0\u7387: [[YELLOW]]{0}
+Acrobatics.SubSkill.Roll.Chance=[[RED]]\u7ffb\u6eda\u51e0\u7387: [[YELLOW]]{0}
+Acrobatics.SubSkill.Roll.GraceChance=[[RED]]\u4f18\u96c5\u7684\u7ffb\u6eda\u51e0\u7387: [[YELLOW]]{0}
 Acrobatics.Roll.Text=**\u7ffb\u6eda**
 Acrobatics.SkillName=\u6742\u6280
 Acrobatics.Skillup=[[YELLOW]]\u6742\u6280\u6280\u80fd\u4e0a\u5347\u4e86 {0}. \u603b\u7b49\u7ea7 ({1})
@@ -642,7 +642,7 @@ Effects.Child=[[DARK_GRAY]]\u7b49\u7ea7: [[GREEN]]{0}
 Effects.Level=[[DARK_GRAY]]\u7b49\u7ea7: [[GREEN]]{0} [[DARK_AQUA]]XP[[YELLOW]]([[GOLD]]{1}[[YELLOW]]/[[GOLD]]{2}[[YELLOW]])
 Effects.Parent=[[GOLD]]{0} -
 Effects.Template=[[DARK_AQUA]]{0}: [[GREEN]]{1}
-Guides.Available=[[DARK_AQUA]]{0} \u7684\u5411\u5bfc - \u8f93\u5165/{1} ? [\u9875\u6570]
+Guides.Available=[[GRAY]]{0} \u7684\u5411\u5bfc - \u8f93\u5165/{1} ? [\u9875\u6570]
 Guides.Header=[[GOLD]]-=[[GREEN]]{0} \u5411\u5bfc[[GOLD]]=-
 Guides.Page.Invalid=\u4e0d\u662f\u4e00\u4e2a\u6709\u6548\u7684\u9875\u6570!
 Guides.Page.OutOfRange=\u90a3\u9875\u4e0d\u5b58\u5728, \u603b\u5171\u53ea\u6709 {0} \u9875

+ 3 - 3
src/main/resources/locale/locale_zh_TW.properties

@@ -8,8 +8,8 @@ Acrobatics.SubSkill.GracefulRoll.Description=\u5169\u500d\u7684\u7ffb\u6efe\u654
 Acrobatics.SubSkill.Dodge.Name=\u8ff4\u907f
 Acrobatics.SubSkill.Dodge.Description=\u6e1b\u5c11\u4e00\u534a\u7684\u50b7\u5bb3
 Acrobatics.Listener=\u96dc\u6280:
-Acrobatics.Roll.Chance=[[RED]]\u7ffb\u6efe\u6a5f\u7387: [[YELLOW]]{0}
-Acrobatics.Roll.GraceChance=[[RED]]\u6f02\u4eae\u7ffb\u6efe\u6a5f\u7387: [[YELLOW]]{0}
+Acrobatics.SubSkill.Roll.Chance=[[RED]]\u7ffb\u6efe\u6a5f\u7387: [[YELLOW]]{0}
+Acrobatics.SubSkill.Roll.GraceChance=[[RED]]\u6f02\u4eae\u7ffb\u6efe\u6a5f\u7387: [[YELLOW]]{0}
 Acrobatics.Roll.Text=**\u7ffb\u6efe**
 Acrobatics.SkillName=\u96dc\u6280
 Acrobatics.Skillup=[[YELLOW]]\u96dc\u6280\u7b49\u7d1a\u4e0a\u5347\u4e86{0}\u7b49. \u5171({1})\u7b49
@@ -581,7 +581,7 @@ Effects.Child=[[DARK_GRAY]]\u7b49\u7d1a: [[GREEN]]{0}
 Effects.Level=[[DARK_GRAY]]\u7b49\u7d1a: [[GREEN]]{0} [[DARK_AQUA]]\u7d93\u9a57\u503c[[YELLOW]]([[GOLD]]{1}[[YELLOW]]/[[GOLD]]{2}[[YELLOW]])
 Effects.Parent=[[GOLD]]{0} -
 Effects.Template=[[DARK_AQUA]]{0}: [[GREEN]]{1}
-Guides.Available=[[DARK_AQUA]] \u95dc\u65bc{0}\u7684\u8aaa\u660e - \u8f38\u5165 /{1} ? [\u9801\u6578]
+Guides.Available=[[GRAY]] \u95dc\u65bc{0}\u7684\u8aaa\u660e - \u8f38\u5165 /{1} ? [\u9801\u6578]
 Guides.Header=[[GOLD]]-=[[GREEN]]{0} \u6307\u5357[[GOLD]]=-
 Guides.Page.Invalid=\u4e0d\u5b58\u5728\u7684\u9801\u6578
 Guides.Page.OutOfRange=\u9019\u9801\u78bc\u4e0d\u5b58\u5728,\u7e3d\u5171\u53ea\u6709{0} \u9801.

+ 0 - 3
src/main/resources/plugin.yml

@@ -222,12 +222,9 @@ permissions:
         description: Allows access to all Acrobatics abilities
         children:
             mcmmo.ability.acrobatics.dodge: true
-            mcmmo.ability.acrobatics.gracefulroll: true
             mcmmo.ability.acrobatics.roll: true
     mcmmo.ability.acrobatics.dodge:
         description: Allows access to the Dodge ability
-    mcmmo.ability.acrobatics.gracefulroll:
-        description: Allows access to the Graceful Roll ability
     mcmmo.ability.acrobatics.roll:
         description: Allows access to the Roll ability
     mcmmo.ability.alchemy.*: