Browse Source

2.1.0 is probably playable now, but not unfinished

nossr50 6 years ago
parent
commit
4a30fcc2de

+ 2 - 2
Changelog.txt

@@ -59,7 +59,7 @@ Version 2.1.0
  ! (Skills) Some skill level rank requirements have changed
  ! (Skills) Some skill level rank requirements have changed
  ! (Skills) Fixed an edge case bug where Blast Mining wouldn't inform the player that it was on cooldown
  ! (Skills) Fixed an edge case bug where Blast Mining wouldn't inform the player that it was on cooldown
  ! (Skills) mcMMO skills will now be on a scale from 1-100 instead of 0-1000 (for existing mcMMO installs this is opt-in and off by default)
  ! (Skills) mcMMO skills will now be on a scale from 1-100 instead of 0-1000 (for existing mcMMO installs this is opt-in and off by default)
- ! (Skills) Skill Super Powers (Tree Feller, etc...) will now require level 10+ to use
+ ! (Skills) Skill Super Powers are now unlockabled skills, unlocking at level 10
  ! (Skills) Acrobatics' Roll exploit detection was tweaked to still allow for Roll to trigger even if it rewards no XP
  ! (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) 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) Woodcutting's Double Drop subskill is now named Harvest Lumber
@@ -92,7 +92,7 @@ Version 2.1.0
  ! (API) SecondaryAbilityEvent is now SubSkillEvent
  ! (API) SecondaryAbilityEvent is now SubSkillEvent
  ! (API) SubSkillType has had many helpful methods added to it
  ! (API) SubSkillType has had many helpful methods added to it
  ! (API) GREEN_THUMB_PLANT & GREEN_THUMB_BLOCK are replaced by GREEN_THUMB
  ! (API) GREEN_THUMB_PLANT & GREEN_THUMB_BLOCK are replaced by GREEN_THUMB
- ! (Code) Refactored some unreadable code relating to SecondaryAbility activation in SkillUtils
+ ! (Code) Refactored some unreadable code relating to SecondaryAbilityType activation in SkillUtils
 
 
 Version 2.0.0
 Version 2.0.0
  = Fixed an interaction between Tree Feller and Stripped Wood
  = Fixed an interaction between Tree Feller and Stripped Wood

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

@@ -1,6 +1,7 @@
 package com.gmail.nossr50.commands.skills;
 package com.gmail.nossr50.commands.skills;
 
 
 import com.gmail.nossr50.datatypes.json.McMMOUrl;
 import com.gmail.nossr50.datatypes.json.McMMOUrl;
+import com.gmail.nossr50.locale.LocaleLoader;
 import com.gmail.nossr50.util.StringUtils;
 import com.gmail.nossr50.util.StringUtils;
 
 
 public enum McMMOWebLinks {
 public enum McMMOWebLinks {
@@ -20,4 +21,25 @@ public enum McMMOWebLinks {
     {
     {
         return StringUtils.getCapitalized(toString());
         return StringUtils.getCapitalized(toString());
     }
     }
+
+    public String getLocaleDescription()
+    {
+        switch (this)
+        {
+            case WEBSITE:
+                return LocaleLoader.getString( "JSON.URL.Website");
+            case DISCORD:
+                return LocaleLoader.getString( "JSON.URL.Discord");
+            case PATREON:
+                return LocaleLoader.getString( "JSON.URL.Patreon");
+            case HELP_TRANSLATE:
+                return LocaleLoader.getString( "JSON.URL.Translation");
+            case SPIGOT:
+                return LocaleLoader.getString("JSON.URL.Spigot");
+            case WIKI:
+                return LocaleLoader.getString("JSON.URL.Wiki");
+            default:
+                return "";
+        }
+    }
 }
 }

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

@@ -22,6 +22,9 @@ public class MmoInfoCommand implements TabExecutor {
 
 
     @Override
     @Override
     public boolean onCommand(CommandSender commandSender, Command command, String s, String[] args) {
     public boolean onCommand(CommandSender commandSender, Command command, String s, String[] args) {
+        /*
+         * Only allow players to use this command
+         */
         if(commandSender instanceof Player)
         if(commandSender instanceof Player)
         {
         {
             if(args.length < 1)
             if(args.length < 1)
@@ -33,8 +36,14 @@ public class MmoInfoCommand implements TabExecutor {
                 if(args == null || args[0] == null)
                 if(args == null || args[0] == null)
                     return false;
                     return false;
 
 
-                //Real skill
-                if(InteractionManager.getAbstractByName(args[0]) != null || PrimarySkillType.SUBSKILL_NAMES.contains(args[0]))
+                if(args[0].equalsIgnoreCase( "???"))
+                {
+                    player.sendMessage(LocaleLoader.getString("Commands.MmoInfo.Header"));
+                    player.sendMessage(LocaleLoader.getString("Commands.MmoInfo.SubSkillHeader", "???"));
+                    player.sendMessage(LocaleLoader.getString("Commands.MmoInfo.DetailsHeader"));
+                    player.sendMessage(LocaleLoader.getString("Commands.MmoInfo.Mystery"));
+                    return true;
+                } else if(InteractionManager.getAbstractByName(args[0]) != null || PrimarySkillType.SUBSKILL_NAMES.contains(args[0]))
                 {
                 {
                     displayInfo(player, args[0]);
                     displayInfo(player, args[0]);
                     return true;
                     return true;
@@ -42,8 +51,10 @@ public class MmoInfoCommand implements TabExecutor {
 
 
                 //Not a real skill
                 //Not a real skill
                 player.sendMessage(LocaleLoader.getString("Commands.MmoInfo.NoMatch"));
                 player.sendMessage(LocaleLoader.getString("Commands.MmoInfo.NoMatch"));
+                return true;
             }
             }
         }
         }
+
         return false;
         return false;
     }
     }
 
 
@@ -59,8 +70,6 @@ public class MmoInfoCommand implements TabExecutor {
 
 
     private void displayInfo(Player player, String subSkillName)
     private void displayInfo(Player player, String subSkillName)
     {
     {
-        System.out.println("[mcMMO] Debug: Grabbing info for skill "+subSkillName);
-
         //Check to see if the skill exists in the new system
         //Check to see if the skill exists in the new system
         AbstractSubSkill abstractSubSkill = InteractionManager.getAbstractByName(subSkillName);
         AbstractSubSkill abstractSubSkill = InteractionManager.getAbstractByName(subSkillName);
         if(abstractSubSkill != null)
         if(abstractSubSkill != null)

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

@@ -174,7 +174,7 @@ public class TamingCommand extends SkillCommand {
     protected List<TextComponent> getTextComponents(Player player) {
     protected List<TextComponent> getTextComponents(Player player) {
         List<TextComponent> textComponents = new ArrayList<>();
         List<TextComponent> textComponents = new ArrayList<>();
 
 
-        TextComponentFactory.getSubSkillTextComponents(player, textComponents, PrimarySkillType.TAMING);
+        TextComponentFactory.getSubSkillTextComponents(player, textComponents, this.skill);
 
 
         return textComponents;
         return textComponents;
     }
     }

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

@@ -6,6 +6,7 @@ import com.gmail.nossr50.datatypes.skills.SubSkillType;
 import com.gmail.nossr50.locale.LocaleLoader;
 import com.gmail.nossr50.locale.LocaleLoader;
 import com.gmail.nossr50.util.Permissions;
 import com.gmail.nossr50.util.Permissions;
 import com.gmail.nossr50.util.TextComponentFactory;
 import com.gmail.nossr50.util.TextComponentFactory;
+import com.gmail.nossr50.util.skills.RankUtils;
 import net.md_5.bungee.api.chat.TextComponent;
 import net.md_5.bungee.api.chat.TextComponent;
 import org.bukkit.entity.Player;
 import org.bukkit.entity.Player;
 
 
@@ -40,12 +41,7 @@ public class WoodcuttingCommand extends SkillCommand {
 
 
         // DOUBLE DROPS
         // DOUBLE DROPS
         if (canDoubleDrop) {
         if (canDoubleDrop) {
-            if(AdvancedConfig.getInstance().isSubSkillClassic(SubSkillType.WOODCUTTING_HARVEST_LUMBER))
-                setDoubleDropClassicChanceStrings(skillValue, isLucky);
-            else
-            {
-                //TODO: Set up datastrings for new harvest
-            }
+            setDoubleDropClassicChanceStrings(skillValue, isLucky);
         }
         }
     }
     }
 
 
@@ -58,7 +54,7 @@ public class WoodcuttingCommand extends SkillCommand {
     @Override
     @Override
     protected void permissionsCheck(Player player) {
     protected void permissionsCheck(Player player) {
         canTreeFell = Permissions.treeFeller(player);
         canTreeFell = Permissions.treeFeller(player);
-        canDoubleDrop = Permissions.isSubSkillEnabled(player, SubSkillType.WOODCUTTING_HARVEST_LUMBER) && !skill.getDoubleDropsDisabled();
+        canDoubleDrop = Permissions.isSubSkillEnabled(player, SubSkillType.WOODCUTTING_HARVEST_LUMBER) && !skill.getDoubleDropsDisabled() && RankUtils.getRank(player, SubSkillType.WOODCUTTING_HARVEST_LUMBER) >= 1;
         canLeafBlow = Permissions.isSubSkillEnabled(player, SubSkillType.WOODCUTTING_LEAF_BLOWER);
         canLeafBlow = Permissions.isSubSkillEnabled(player, SubSkillType.WOODCUTTING_LEAF_BLOWER);
         canSplinter = Permissions.isSubSkillEnabled(player, SubSkillType.WOODCUTTING_SPLINTER);
         canSplinter = Permissions.isSubSkillEnabled(player, SubSkillType.WOODCUTTING_SPLINTER);
         canBarkSurgeon = Permissions.isSubSkillEnabled(player, SubSkillType.WOODCUTTING_BARK_SURGEON);
         canBarkSurgeon = Permissions.isSubSkillEnabled(player, SubSkillType.WOODCUTTING_BARK_SURGEON);

+ 0 - 85
src/main/java/com/gmail/nossr50/config/AdvancedConfig.java

@@ -37,11 +37,6 @@ public class AdvancedConfig extends AutoUpdateConfigLoader {
         // Validate all the settings!
         // Validate all the settings!
         List<String> reason = new ArrayList<String>();
         List<String> reason = new ArrayList<String>();
 
 
-        /*
-         * In the future this method will check keys for all skills, but for now it only checks overhauled skills
-         */
-        checkKeys(reason);
-
         /* GENERAL */
         /* GENERAL */
         if (getAbilityLengthRetro() < 1) {
         if (getAbilityLengthRetro() < 1) {
             reason.add("Skills.General.Ability.Length.RetroMode.IncreaseLevel should be at least 1!");
             reason.add("Skills.General.Ability.Length.RetroMode.IncreaseLevel should be at least 1!");
@@ -814,51 +809,6 @@ public class AdvancedConfig extends AutoUpdateConfigLoader {
         return config.getBoolean(keyLocation);
         return config.getBoolean(keyLocation);
     }*/
     }*/
 
 
-
-    /**
-     * Gets the level required to unlock a subskill at a given rank
-     * @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(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
-         *
-         * 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
-         */
-
-        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(key + ".Rank_Levels.Rank_"+rank);
-    }
-
     /**
     /**
      * Some SubSkills have the ability to retain classic functionality
      * Some SubSkills have the ability to retain classic functionality
      * @param subSkillType SubSkillType with classic functionality
      * @param subSkillType SubSkillType with classic functionality
@@ -1034,39 +984,4 @@ public class AdvancedConfig extends AutoUpdateConfigLoader {
     public String getPlayerUnleashMessage() { return config.getString("Kraken.Unleashed_Message.Player", ""); }
     public String getPlayerUnleashMessage() { return config.getString("Kraken.Unleashed_Message.Player", ""); }
     public String getPlayerDefeatMessage() { return config.getString("Kraken.Defeated_Message.Killed", ""); }
     public String getPlayerDefeatMessage() { return config.getString("Kraken.Defeated_Message.Killed", ""); }
     public String getPlayerEscapeMessage() { return config.getString("Kraken.Defeated_Message.Escape", ""); }
     public String getPlayerEscapeMessage() { return config.getString("Kraken.Defeated_Message.Escape", ""); }
-
-    /**
-     * Checks for valid keys in the advanced.yml file for subskill ranks
-     */
-    private void checkKeys(List<String> reasons)
-    {
-        //For now we will only check ranks of stuff I've overhauled
-        for(SubSkillType subSkillType : SubSkillType.values())
-        {
-            if(subSkillType.getParentSkill() == PrimarySkillType.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 < subSkillType.getNumRanks(); x++)
-                {
-                    if(curRank > 0)
-                        prevRank = curRank;
-
-                    curRank = getSubSkillUnlockLevel(subSkillType, x);
-
-                    //Do we really care if its below 0? Probably not
-                    if(curRank < 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 "+ subSkillType.toString()+" set up poorly, sequential ranks should have ascending requirements");
-                    }
-                }
-            }
-        }
-    }
 }
 }

+ 3 - 2
src/main/java/com/gmail/nossr50/config/Config.java

@@ -557,9 +557,10 @@ public class Config extends AutoUpdateConfigLoader {
         return (cap <= 0) ? Integer.MAX_VALUE : cap;
         return (cap <= 0) ? Integer.MAX_VALUE : cap;
     }
     }
 
 
-    public int getSkillAbilityGate(PrimarySkillType skill) {
+
+    /*public int isSuperAbilityUnlocked(PrimarySkillType skill) {
         return config.getInt("Skills." + StringUtils.getCapitalized(skill.toString()) + ".Ability_Activation_Level_Gate");
         return config.getInt("Skills." + StringUtils.getCapitalized(skill.toString()) + ".Ability_Activation_Level_Gate");
-    }
+    }*/
 
 
     public boolean getTruncateSkills() { return config.getBoolean("General.TruncateSkills", false); }
     public boolean getTruncateSkills() { return config.getBoolean("General.TruncateSkills", false); }
 
 

+ 95 - 0
src/main/java/com/gmail/nossr50/config/RankConfig.java

@@ -1,5 +1,11 @@
 package com.gmail.nossr50.config;
 package com.gmail.nossr50.config;
 
 
+import com.gmail.nossr50.datatypes.skills.SubSkillType;
+import com.gmail.nossr50.datatypes.skills.subskills.AbstractSubSkill;
+
+import java.util.ArrayList;
+import java.util.List;
+
 public class RankConfig extends AutoUpdateConfigLoader {
 public class RankConfig extends AutoUpdateConfigLoader {
     private static RankConfig instance;
     private static RankConfig instance;
 
 
@@ -22,4 +28,93 @@ public class RankConfig extends AutoUpdateConfigLoader {
 
 
         return instance;
         return instance;
     }
     }
+
+    @Override
+    protected boolean validateKeys() {
+        List<String> reason = new ArrayList<String>();
+
+        /*
+         * In the future this method will check keys for all skills, but for now it only checks overhauled skills
+         */
+        checkKeys(reason);
+
+        return noErrorsInConfig(reason);
+    }
+
+    /**
+     * Returns the unlock level for a subskill depending on the gamemode
+     * @param subSkillType target subskill
+     * @param rank the rank we are checking
+     * @return the level requirement for a subskill at this particular rank
+     */
+    public int getSubSkillUnlockLevel(SubSkillType subSkillType, int rank)
+    {
+        String key = subSkillType.getRankConfigAddress();
+
+        return findRankByRootAddress(rank, key);
+    }
+
+    /**
+     * Returns the unlock level for a subskill depending on the gamemode
+     * @param abstractSubSkill target subskill
+     * @param rank the rank we are checking
+     * @return the level requirement for a subskill at this particular rank
+     */
+    public int getSubSkillUnlockLevel(AbstractSubSkill abstractSubSkill, int rank)
+    {
+        String key = abstractSubSkill.getPrimaryKeyName()+"."+abstractSubSkill.getConfigKeyName();
+
+        return findRankByRootAddress(rank, key);
+    }
+
+    /**
+     * Returns the unlock level for a subskill depending on the gamemode
+     * @param key root address of the subskill in the rankskills.yml file
+     * @param rank the rank we are checking
+     * @return the level requirement for a subskill at this particular rank
+     */
+    private int findRankByRootAddress(int rank, String key) {
+        String scalingKey = Config.getInstance().getIsRetroMode() ? ".RetroMode." : ".Standard.";
+
+        String targetRank = "Rank_" + rank;
+
+        key += scalingKey;
+        key += targetRank;
+
+        return config.getInt(key);
+    }
+
+    /**
+     * Checks for valid keys for subskill ranks
+     */
+    private void checkKeys(List<String> reasons)
+    {
+        //For now we will only check ranks of stuff I've overhauled
+        for(SubSkillType subSkillType : SubSkillType.values())
+        {
+            //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 < subSkillType.getNumRanks(); x++)
+            {
+                if(curRank > 0)
+                    prevRank = curRank;
+
+                curRank = getSubSkillUnlockLevel(subSkillType, x);
+
+                //Do we really care if its below 0? Probably not
+                if(curRank < 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 "+ subSkillType.toString()+" set up poorly, sequential ranks should have ascending requirements");
+                }
+            }
+        }
+    }
 }
 }

+ 9 - 11
src/main/java/com/gmail/nossr50/datatypes/player/McMMOPlayer.java

@@ -44,6 +44,7 @@ import com.gmail.nossr50.util.player.UserManager;
 import com.gmail.nossr50.util.scoreboards.ScoreboardManager;
 import com.gmail.nossr50.util.scoreboards.ScoreboardManager;
 import com.gmail.nossr50.util.skills.ParticleEffectUtils;
 import com.gmail.nossr50.util.skills.ParticleEffectUtils;
 import com.gmail.nossr50.util.skills.PerksUtils;
 import com.gmail.nossr50.util.skills.PerksUtils;
+import com.gmail.nossr50.util.skills.RankUtils;
 import com.gmail.nossr50.util.skills.SkillUtils;
 import com.gmail.nossr50.util.skills.SkillUtils;
 import com.gmail.nossr50.util.sounds.SoundManager;
 import com.gmail.nossr50.util.sounds.SoundManager;
 import com.gmail.nossr50.util.sounds.SoundType;
 import com.gmail.nossr50.util.sounds.SoundType;
@@ -739,19 +740,16 @@ public class McMMOPlayer {
             return;
             return;
         }
         }
 
 
-        /*
-         * Check if the player has passed the gate requirement
-         */
-        if(Config.getInstance().getAbilitiesGateEnabled())
+
+        //TODO: This is hacky and temporary solution until skills are move to the new system
+        //Potential problems with this include skills with two super abilities (ie mining)
+        if(!skill.isSuperAbilityUnlocked(getPlayer()))
         {
         {
-            if(getSkillLevel(skill) < skill.getSkillAbilityGate())
-            {
-                int diff = skill.getSkillAbilityGate() - getSkillLevel(skill);
+            int diff = RankUtils.getSuperAbilityUnlockRequirement(skill.getAbility()) - getSkillLevel(skill);
 
 
-                //Inform the player they are not yet skilled enough
-                NotificationManager.sendPlayerInformation(player, NotificationType.ABILITY_COOLDOWN, "Skills.AbilityGateRequirementFail", String.valueOf(diff), skill.getName());
-                return;
-            }
+            //Inform the player they are not yet skilled enough
+            NotificationManager.sendPlayerInformation(player, NotificationType.ABILITY_COOLDOWN, "Skills.AbilityGateRequirementFail", String.valueOf(diff), skill.getName());
+            return;
         }
         }
 
 
         int timeRemaining = calculateTimeRemaining(ability);
         int timeRemaining = calculateTimeRemaining(ability);

+ 3 - 2
src/main/java/com/gmail/nossr50/datatypes/skills/PrimarySkillType.java

@@ -23,6 +23,7 @@ import com.gmail.nossr50.skills.woodcutting.WoodcuttingManager;
 import com.gmail.nossr50.util.Permissions;
 import com.gmail.nossr50.util.Permissions;
 import com.gmail.nossr50.util.StringUtils;
 import com.gmail.nossr50.util.StringUtils;
 import com.gmail.nossr50.util.skills.ParticleEffectUtils;
 import com.gmail.nossr50.util.skills.ParticleEffectUtils;
+import com.gmail.nossr50.util.skills.RankUtils;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableList;
 import org.bukkit.Color;
 import org.bukkit.Color;
 import org.bukkit.entity.Entity;
 import org.bukkit.entity.Entity;
@@ -40,7 +41,7 @@ public enum PrimarySkillType {
     AXES(AxesManager.class, Color.AQUA, SuperAbilityType.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)),
     AXES(AxesManager.class, Color.AQUA, SuperAbilityType.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), SuperAbilityType.GIGA_DRILL_BREAKER, ToolType.SHOVEL, ImmutableList.of(SubSkillType.EXCAVATION_GIGA_DRILL_BREAKER, SubSkillType.EXCAVATION_TREASURE_HUNTER)),
     EXCAVATION(ExcavationManager.class, Color.fromRGB(139, 69, 19), SuperAbilityType.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)),
     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, SuperAbilityType.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)),
+    HERBALISM(HerbalismManager.class, Color.GREEN, SuperAbilityType.GREEN_TERRA, ToolType.HOE, ImmutableList.of(SubSkillType.HERBALISM_GREEN_TERRA, 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, SuperAbilityType.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)),
     MINING(MiningManager.class, Color.GRAY, SuperAbilityType.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)),
     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_UNDERSTANDING_THE_ART, SubSkillType.SALVAGE_ADVANCED_SALVAGE, SubSkillType.SALVAGE_ARCANE_SALVAGE)),
     SALVAGE(SalvageManager.class, Color.ORANGE, ImmutableList.of(SubSkillType.SALVAGE_UNDERSTANDING_THE_ART, SubSkillType.SALVAGE_ADVANCED_SALVAGE, SubSkillType.SALVAGE_ARCANE_SALVAGE)),
@@ -124,7 +125,7 @@ public enum PrimarySkillType {
         return Config.getInstance().getLevelCap(this);
         return Config.getInstance().getLevelCap(this);
     }
     }
 
 
-    public int getSkillAbilityGate() { return Config.getInstance().getSkillAbilityGate(this); }
+    public boolean isSuperAbilityUnlocked(Player player) { return RankUtils.getRank(player, getAbility().getSubSkillTypeDefinition()) >= 1; }
 
 
     public boolean getPVPEnabled() {
     public boolean getPVPEnabled() {
         return Config.getInstance().getPVPEnabled(this);
         return Config.getInstance().getPVPEnabled(this);

+ 28 - 19
src/main/java/com/gmail/nossr50/datatypes/skills/SubSkillType.java

@@ -11,20 +11,20 @@ public enum SubSkillType {
     ACROBATICS_ROLL,
     ACROBATICS_ROLL,
 
 
     /* ALCHEMY */
     /* ALCHEMY */
-    ALCHEMY_CATALYSIS,
+    ALCHEMY_CATALYSIS(1),
     ALCHEMY_CONCOCTIONS(8),
     ALCHEMY_CONCOCTIONS(8),
 
 
     /* ARCHERY */
     /* ARCHERY */
-    ARCHERY_DAZE(),
+    ARCHERY_DAZE,
     ARCHERY_ARROW_RETRIEVAL,
     ARCHERY_ARROW_RETRIEVAL,
     ARCHERY_SKILL_SHOT(20),
     ARCHERY_SKILL_SHOT(20),
 
 
     /* Axes */
     /* Axes */
-    AXES_ARMOR_IMPACT,
+    AXES_ARMOR_IMPACT(20),
     AXES_AXE_MASTERY(4),
     AXES_AXE_MASTERY(4),
     AXES_CRITICAL_STRIKES,
     AXES_CRITICAL_STRIKES,
     AXES_GREATER_IMPACT,
     AXES_GREATER_IMPACT,
-    AXES_SKULL_SPLITTER(0),
+    AXES_SKULL_SPLITTER(1),
 
 
     /* Excavation */
     /* Excavation */
     EXCAVATION_TREASURE_HUNTER,
     EXCAVATION_TREASURE_HUNTER,
@@ -44,11 +44,12 @@ public enum SubSkillType {
     HERBALISM_DOUBLE_DROPS,
     HERBALISM_DOUBLE_DROPS,
     HERBALISM_HYLIAN_LUCK,
     HERBALISM_HYLIAN_LUCK,
     HERBALISM_SHROOM_THUMB,
     HERBALISM_SHROOM_THUMB,
+    HERBALISM_GREEN_TERRA,
 
 
     /* Mining */
     /* Mining */
     MINING_DOUBLE_DROPS,
     MINING_DOUBLE_DROPS,
-    MINING_SUPER_BREAKER(0),
-    MINING_BLAST_MINING,
+    MINING_SUPER_BREAKER(1),
+    MINING_BLAST_MINING(8),
     MINING_BIGGER_BOMBS,
     MINING_BIGGER_BOMBS,
     MINING_DEMOLITIONS_EXPERTISE,
     MINING_DEMOLITIONS_EXPERTISE,
 
 
@@ -60,29 +61,29 @@ public enum SubSkillType {
     /* Salvage */
     /* Salvage */
     SALVAGE_ADVANCED_SALVAGE,
     SALVAGE_ADVANCED_SALVAGE,
     SALVAGE_ARCANE_SALVAGE,
     SALVAGE_ARCANE_SALVAGE,
-    SALVAGE_UNDERSTANDING_THE_ART,
+    SALVAGE_UNDERSTANDING_THE_ART(8),
 
 
     /* Smelting */
     /* Smelting */
     SMELTING_FLUX_MINING,
     SMELTING_FLUX_MINING,
     SMELTING_FUEL_EFFICIENCY,
     SMELTING_FUEL_EFFICIENCY,
     SMELTING_SECOND_SMELT,
     SMELTING_SECOND_SMELT,
-    SMELTING_UNDERSTANDING_THE_ART,
+    SMELTING_UNDERSTANDING_THE_ART(8),
 
 
     /* Swords */
     /* Swords */
     SWORDS_BLEED,
     SWORDS_BLEED,
     SWORDS_COUNTER_ATTACK,
     SWORDS_COUNTER_ATTACK,
-    SWORDS_SERRATED_STRIKES,
+    SWORDS_SERRATED_STRIKES(1),
 
 
     /* Taming */
     /* Taming */
     TAMING_BEAST_LORE,
     TAMING_BEAST_LORE,
     TAMING_CALL_OF_THE_WILD,
     TAMING_CALL_OF_THE_WILD,
-    TAMING_ENVIRONMENTALLY_AWARE,
-    TAMING_FAST_FOOD_SERVICE,
+    TAMING_ENVIRONMENTALLY_AWARE(1),
+    TAMING_FAST_FOOD_SERVICE(1),
     TAMING_GORE,
     TAMING_GORE,
-    TAMING_HOLY_HOUND,
-    TAMING_SHARPENED_CLAWS,
-    TAMING_SHOCK_PROOF,
-    TAMING_THICK_FUR,
+    TAMING_HOLY_HOUND(1),
+    TAMING_SHARPENED_CLAWS(1),
+    TAMING_SHOCK_PROOF(1),
+    TAMING_THICK_FUR(1),
     TAMING_PUMMEL,
     TAMING_PUMMEL,
 
 
     /* Unarmed */
     /* Unarmed */
@@ -91,7 +92,7 @@ public enum SubSkillType {
     UNARMED_DISARM,
     UNARMED_DISARM,
     UNARMED_IRON_ARM_STYLE,
     UNARMED_IRON_ARM_STYLE,
     UNARMED_IRON_GRIP,
     UNARMED_IRON_GRIP,
-    UNARMED_BERSERK(0),
+    UNARMED_BERSERK(1),
 
 
     /* Woodcutting */
     /* Woodcutting */
     WOODCUTTING_TREE_FELLER(5),
     WOODCUTTING_TREE_FELLER(5),
@@ -99,7 +100,7 @@ public enum SubSkillType {
     WOODCUTTING_BARK_SURGEON(3),
     WOODCUTTING_BARK_SURGEON(3),
     WOODCUTTING_NATURES_BOUNTY(3),
     WOODCUTTING_NATURES_BOUNTY(3),
     WOODCUTTING_SPLINTER(3),
     WOODCUTTING_SPLINTER(3),
-    WOODCUTTING_HARVEST_LUMBER(3);
+    WOODCUTTING_HARVEST_LUMBER(1);
 
 
     private final int numRanks;
     private final int numRanks;
     //TODO: SuperAbilityType should also contain flags for active by default? Not sure if it should work that way.
     //TODO: SuperAbilityType should also contain flags for active by default? Not sure if it should work that way.
@@ -131,13 +132,21 @@ public enum SubSkillType {
     public PrimarySkillType getParentSkill() { return PrimarySkillType.bySecondaryAbility(this); }
     public PrimarySkillType getParentSkill() { return PrimarySkillType.bySecondaryAbility(this); }
 
 
     /**
     /**
-     * Returns the permission root address for the advanced.yml for this subskill
-     * @return permission root address in advanced.yml for this subskill
+     * Returns the root address for this skill in the advanced.yml file
+     * @return the root address for this skill in advanced.yml
      */
      */
     public String getAdvConfigAddress() {
     public String getAdvConfigAddress() {
         return "Skills." + StringUtils.getCapitalized(getParentSkill().toString()) + "." + getConfigName(toString());
         return "Skills." + StringUtils.getCapitalized(getParentSkill().toString()) + "." + getConfigName(toString());
     }
     }
 
 
+    /**
+     * Returns the root address for this skill in the rankskills.yml file
+     * @return the root address for this skill in rankskills.yml
+     */
+    public String getRankConfigAddress() {
+        return StringUtils.getCapitalized(getParentSkill().toString()) + "." + getConfigName(toString());
+    }
+
     /**
     /**
      * Get the string representation of the permission node for this subskill
      * Get the string representation of the permission node for this subskill
      * @return the permission node for this subskill
      * @return the permission node for this subskill

+ 30 - 0
src/main/java/com/gmail/nossr50/datatypes/skills/SuperAbilityType.java

@@ -1,9 +1,15 @@
 package com.gmail.nossr50.datatypes.skills;
 package com.gmail.nossr50.datatypes.skills;
 
 
 import com.gmail.nossr50.config.Config;
 import com.gmail.nossr50.config.Config;
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
+import com.gmail.nossr50.datatypes.skills.subskills.interfaces.SubSkill;
 import com.gmail.nossr50.util.BlockUtils;
 import com.gmail.nossr50.util.BlockUtils;
 import com.gmail.nossr50.util.Permissions;
 import com.gmail.nossr50.util.Permissions;
 import com.gmail.nossr50.util.StringUtils;
 import com.gmail.nossr50.util.StringUtils;
+import com.gmail.nossr50.util.player.UserManager;
+import com.gmail.nossr50.util.skills.RankUtils;
+import com.google.common.collect.ImmutableList;
+import jdk.nashorn.internal.ir.annotations.Immutable;
 import org.bukkit.Material;
 import org.bukkit.Material;
 import org.bukkit.block.BlockState;
 import org.bukkit.block.BlockState;
 import org.bukkit.entity.Player;
 import org.bukkit.entity.Player;
@@ -69,11 +75,27 @@ public enum SuperAbilityType {
             null),
             null),
     ;
     ;
 
 
+    /*
+     * Defining their associated SubSkillType definitions
+     * This is a bit of a band-aid fix until the new skill system is in place
+     */
+    static {
+        BERSERK.subSkillTypeDefinition              = SubSkillType.UNARMED_BERSERK;
+        SUPER_BREAKER.subSkillTypeDefinition        = SubSkillType.MINING_SUPER_BREAKER;
+        GIGA_DRILL_BREAKER.subSkillTypeDefinition   = SubSkillType.EXCAVATION_GIGA_DRILL_BREAKER;
+        GREEN_TERRA.subSkillTypeDefinition          = SubSkillType.HERBALISM_GREEN_TERRA;
+        SKULL_SPLITTER.subSkillTypeDefinition       = SubSkillType.AXES_SKULL_SPLITTER;
+        TREE_FELLER.subSkillTypeDefinition          = SubSkillType.WOODCUTTING_TREE_FELLER;
+        SERRATED_STRIKES.subSkillTypeDefinition     = SubSkillType.SWORDS_SERRATED_STRIKES;
+        BLAST_MINING.subSkillTypeDefinition         = SubSkillType.MINING_BLAST_MINING;
+    }
+
     private String abilityOn;
     private String abilityOn;
     private String abilityOff;
     private String abilityOff;
     private String abilityPlayer;
     private String abilityPlayer;
     private String abilityRefresh;
     private String abilityRefresh;
     private String abilityPlayerOff;
     private String abilityPlayerOff;
+    private SubSkillType subSkillTypeDefinition;
 
 
     private SuperAbilityType(String abilityOn, String abilityOff, String abilityPlayer, String abilityRefresh, String abilityPlayerOff) {
     private SuperAbilityType(String abilityOn, String abilityOff, String abilityPlayer, String abilityRefresh, String abilityPlayerOff) {
         this.abilityOn = abilityOn;
         this.abilityOn = abilityOn;
@@ -200,4 +222,12 @@ public enum SuperAbilityType {
                 return false;
                 return false;
         }
         }
     }
     }
+
+    /**
+     * Grabs the associated SubSkillType definition for this SuperAbilityType
+     * @return the matching SubSkillType definition for this SuperAbilityType
+     */
+    public SubSkillType getSubSkillTypeDefinition() {
+        return subSkillTypeDefinition;
+    }
 }
 }

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

@@ -210,7 +210,7 @@ public class mcMMO extends JavaPlugin {
             placeStore.saveAll();       // Save our metadata
             placeStore.saveAll();       // Save our metadata
             placeStore.cleanUp();       // Cleanup empty metadata stores
             placeStore.cleanUp();       // Cleanup empty metadata stores
         }
         }
-        catch (NullPointerException e) {}
+        catch (NullPointerException e) { e.printStackTrace(); }
 
 
         debug("Canceling all tasks...");
         debug("Canceling all tasks...");
         getServer().getScheduler().cancelTasks(this); // This removes our tasks
         getServer().getScheduler().cancelTasks(this); // This removes our tasks

+ 1 - 3
src/main/java/com/gmail/nossr50/skills/alchemy/Alchemy.java

@@ -64,9 +64,7 @@ public final class Alchemy {
 
 
         List<AlchemyBrewTask> toFinish = new ArrayList<AlchemyBrewTask>();
         List<AlchemyBrewTask> toFinish = new ArrayList<AlchemyBrewTask>();
 
 
-        for (AlchemyBrewTask alchemyBrewTask : brewingStandMap.values()) {
-            toFinish.add(alchemyBrewTask);
-        }
+        toFinish.addAll(brewingStandMap.values());
 
 
         for (AlchemyBrewTask alchemyBrewTask : toFinish) {
         for (AlchemyBrewTask alchemyBrewTask : toFinish) {
             alchemyBrewTask.finishImmediately();
             alchemyBrewTask.finishImmediately();

+ 0 - 1
src/main/java/com/gmail/nossr50/skills/woodcutting/Woodcutting.java

@@ -18,7 +18,6 @@ import java.util.List;
 import java.util.Set;
 import java.util.Set;
 
 
 public final class Woodcutting {
 public final class Woodcutting {
-    public static int leafBlowerUnlockLevel = AdvancedConfig.getInstance().getLeafBlowUnlockLevel();
     public static int treeFellerThreshold = Config.getInstance().getTreeFellerThreshold();
     public static int treeFellerThreshold = Config.getInstance().getTreeFellerThreshold();
 
 
 
 

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

@@ -13,6 +13,7 @@ import com.gmail.nossr50.skills.woodcutting.Woodcutting.ExperienceGainMethod;
 import com.gmail.nossr50.util.*;
 import com.gmail.nossr50.util.*;
 import com.gmail.nossr50.util.player.NotificationManager;
 import com.gmail.nossr50.util.player.NotificationManager;
 import com.gmail.nossr50.util.skills.CombatUtils;
 import com.gmail.nossr50.util.skills.CombatUtils;
+import com.gmail.nossr50.util.skills.RankUtils;
 import com.gmail.nossr50.util.skills.SkillActivationType;
 import com.gmail.nossr50.util.skills.SkillActivationType;
 import com.gmail.nossr50.util.skills.SkillUtils;
 import com.gmail.nossr50.util.skills.SkillUtils;
 import org.bukkit.Material;
 import org.bukkit.Material;
@@ -31,7 +32,9 @@ public class WoodcuttingManager extends SkillManager {
     }
     }
 
 
     public boolean canUseLeafBlower(ItemStack heldItem) {
     public boolean canUseLeafBlower(ItemStack heldItem) {
-        return Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.WOODCUTTING_LEAF_BLOWER) && getSkillLevel() >= Woodcutting.leafBlowerUnlockLevel && ItemUtils.isAxe(heldItem);
+        return Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.WOODCUTTING_LEAF_BLOWER)
+                && RankUtils.hasUnlockedSubskill(getPlayer(), SubSkillType.WOODCUTTING_LEAF_BLOWER)
+                && ItemUtils.isAxe(heldItem);
     }
     }
 
 
     public boolean canUseTreeFeller(ItemStack heldItem) {
     public boolean canUseTreeFeller(ItemStack heldItem) {
@@ -39,7 +42,9 @@ public class WoodcuttingManager extends SkillManager {
     }
     }
 
 
     protected boolean canGetDoubleDrops() {
     protected boolean canGetDoubleDrops() {
-        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);
+        return Permissions.isSubSkillEnabled(getPlayer(), SubSkillType.WOODCUTTING_HARVEST_LUMBER)
+                && RankUtils.hasReachedRank(1, getPlayer(), SubSkillType.WOODCUTTING_HARVEST_LUMBER)
+                && SkillUtils.isActivationSuccessful(SkillActivationType.RANDOM_LINEAR_100_SCALE_WITH_CAP, SubSkillType.WOODCUTTING_HARVEST_LUMBER, getPlayer(), this.skill, getSkillLevel(), activationChance);
     }
     }
 
 
     /**
     /**

+ 93 - 114
src/main/java/com/gmail/nossr50/util/TextComponentFactory.java

@@ -3,6 +3,7 @@ package com.gmail.nossr50.util;
 import com.gmail.nossr50.commands.skills.McMMOWebLinks;
 import com.gmail.nossr50.commands.skills.McMMOWebLinks;
 import com.gmail.nossr50.config.AdvancedConfig;
 import com.gmail.nossr50.config.AdvancedConfig;
 import com.gmail.nossr50.config.Config;
 import com.gmail.nossr50.config.Config;
+import com.gmail.nossr50.config.RankConfig;
 import com.gmail.nossr50.datatypes.interactions.NotificationType;
 import com.gmail.nossr50.datatypes.interactions.NotificationType;
 import com.gmail.nossr50.datatypes.json.McMMOUrl;
 import com.gmail.nossr50.datatypes.json.McMMOUrl;
 import com.gmail.nossr50.datatypes.player.McMMOPlayer;
 import com.gmail.nossr50.datatypes.player.McMMOPlayer;
@@ -23,12 +24,6 @@ import java.util.List;
  * This class handles many of the JSON components that mcMMO makes and uses
  * This class handles many of the JSON components that mcMMO makes and uses
  */
  */
 public class TextComponentFactory {
 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;
 
 
     /**
     /**
      * Makes a text component using strings from a locale and supports passing an undefined number of variables to the LocaleLoader
      * Makes a text component using strings from a locale and supports passing an undefined number of variables to the LocaleLoader
@@ -39,20 +34,13 @@ public class TextComponentFactory {
      */
      */
     public static TextComponent getNotificationMultipleValues(String localeKey, NotificationType notificationType, String... values)
     public static TextComponent getNotificationMultipleValues(String localeKey, NotificationType notificationType, String... values)
     {
     {
-        //TODO: Make this colored
-        String preColoredString = LocaleLoader.getString(localeKey, values);
-
-        /*for(int x = 0; x < values.length; x++)
-        {
-
-        }*/
-
+        String preColoredString = LocaleLoader.getString(localeKey, (Object[]) values);
         return new TextComponent(preColoredString);
         return new TextComponent(preColoredString);
     }
     }
 
 
     public static TextComponent getNotificationTextComponentFromLocale(String localeKey, NotificationType notificationType)
     public static TextComponent getNotificationTextComponentFromLocale(String localeKey, NotificationType notificationType)
     {
     {
-        return getNotificationTextComponent(LocaleLoader.getString(localeKey), notificationType);
+        return getNotificationTextComponent(LocaleLoader.getString(localeKey));
     }
     }
 
 
     public static TextComponent getNotificationLevelUpTextComponent(McMMOPlayer player, PrimarySkillType skill, int currentLevel)
     public static TextComponent getNotificationLevelUpTextComponent(McMMOPlayer player, PrimarySkillType skill, int currentLevel)
@@ -69,47 +57,18 @@ public class TextComponentFactory {
         return textComponent;
         return textComponent;
     }
     }
 
 
-    public static TextComponent getNotificationTextComponent(String text, NotificationType notificationType)
+    private static TextComponent getNotificationTextComponent(String text)
     {
     {
-        TextComponent textComponent = new TextComponent(text);
         //textComponent.setColor(getNotificationColor(notificationType));
         //textComponent.setColor(getNotificationColor(notificationType));
-        return textComponent;
+        return new TextComponent(text);
     }
     }
 
 
-    /*public static ChatColor getNotificationColor(NotificationType notificationType)
-    {
-        ChatColor color = ChatColor.WHITE;
-        switch(notificationType)
-        {
-            case SUPER_ABILITY:
-                break;
-            case TOOL:
-                break;
-            case SUBSKILL_UNLOCKED:
-                break;
-            case SUBSKILL_MESSAGE:
-                break;
-            case LEVEL_UP_MESSAGE:
-                break;
-            case XP_GAIN:
-                break;
-        }
-
-        return color;
-    }*/
-
     public static void sendPlayerUrlHeader(Player player) {
     public static void sendPlayerUrlHeader(Player player) {
         if(!Config.getInstance().getUrlLinksEnabled())
         if(!Config.getInstance().getUrlLinksEnabled())
             return;
             return;
 
 
         Player.Spigot spigotPlayer = player.spigot();
         Player.Spigot spigotPlayer = player.spigot();
 
 
-        /*if(webComponents != null)
-        {
-            player.spigot().sendMessage(webComponents);
-            return;
-        }*/
-
         TextComponent prefix = new TextComponent("[| ");
         TextComponent prefix = new TextComponent("[| ");
         prefix.setColor(ChatColor.DARK_AQUA);
         prefix.setColor(ChatColor.DARK_AQUA);
         TextComponent suffix = new TextComponent(" |]");
         TextComponent suffix = new TextComponent(" |]");
@@ -137,7 +96,6 @@ public class TextComponentFactory {
     public static void sendPlayerSubSkillList(Player player, List<TextComponent> textComponents)
     public static void sendPlayerSubSkillList(Player player, List<TextComponent> textComponents)
     {
     {
         TextComponent emptySpace = new TextComponent(" ");
         TextComponent emptySpace = new TextComponent(" ");
-        //TextComponent atSymbolText = new TextComponent(atSymbol);
 
 
         ArrayList<BaseComponent> bulkMessage = new ArrayList<>();
         ArrayList<BaseComponent> bulkMessage = new ArrayList<>();
         int newLineCount = 0; //Hacky solution to wordwrap problems
         int newLineCount = 0; //Hacky solution to wordwrap problems
@@ -181,7 +139,7 @@ public class TextComponentFactory {
         player.spigot().sendMessage(bulkArray);
         player.spigot().sendMessage(bulkArray);
     }
     }
 
 
-    public static TextComponent getWebLinkTextComponent(McMMOWebLinks webLinks)
+    private static TextComponent getWebLinkTextComponent(McMMOWebLinks webLinks)
     {
     {
         TextComponent webTextComponent;
         TextComponent webTextComponent;
 
 
@@ -227,7 +185,7 @@ public class TextComponentFactory {
                 webTextComponent = new TextComponent("NOT DEFINED");
                 webTextComponent = new TextComponent("NOT DEFINED");
         }
         }
 
 
-        webTextComponent.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, getUrlHoverEvent(webLinks)));
+        addNewHoverComponentToTextComponent(webTextComponent, getUrlHoverEvent(webLinks));
         webTextComponent.setInsertion(webLinks.getUrl());
         webTextComponent.setInsertion(webLinks.getUrl());
 
 
         return webTextComponent;
         return webTextComponent;
@@ -248,35 +206,38 @@ public class TextComponentFactory {
             case WEBSITE:
             case WEBSITE:
                 addUrlHeaderHover(webLinks, componentBuilder);
                 addUrlHeaderHover(webLinks, componentBuilder);
                 componentBuilder.append("\n\n").italic(false);
                 componentBuilder.append("\n\n").italic(false);
-                componentBuilder.append("The official mcMMO Website!").color(ChatColor.GREEN);
+                componentBuilder.append(webLinks.getLocaleDescription()).color(ChatColor.GREEN);
+                componentBuilder.append("\nDev Blogs, and information related to mcMMO can be found here").color(ChatColor.GRAY);
                 break;
                 break;
             case SPIGOT:
             case SPIGOT:
                 addUrlHeaderHover(webLinks, componentBuilder);
                 addUrlHeaderHover(webLinks, componentBuilder);
                 componentBuilder.append("\n\n").italic(false);
                 componentBuilder.append("\n\n").italic(false);
-                componentBuilder.append("The official mcMMO Spigot Resource Page!").color(ChatColor.GREEN);
+                componentBuilder.append(webLinks.getLocaleDescription()).color(ChatColor.GREEN);
                 componentBuilder.append("\nI post regularly in the discussion thread here!").color(ChatColor.GRAY);
                 componentBuilder.append("\nI post regularly in the discussion thread here!").color(ChatColor.GRAY);
                 break;
                 break;
             case PATREON:
             case PATREON:
                 addUrlHeaderHover(webLinks, componentBuilder);
                 addUrlHeaderHover(webLinks, componentBuilder);
                 componentBuilder.append("\n\n").italic(false);
                 componentBuilder.append("\n\n").italic(false);
-                componentBuilder.append("Support nossr50 and development of mcMMO on Patreon!").color(ChatColor.GREEN);
+                componentBuilder.append(webLinks.getLocaleDescription()).color(ChatColor.GREEN);
+                componentBuilder.append("\n");
+                componentBuilder.append("Show support by buying me a coffee :)").italic(false).color(ChatColor.GRAY);
                 break;
                 break;
             case WIKI:
             case WIKI:
                 addUrlHeaderHover(webLinks, componentBuilder);
                 addUrlHeaderHover(webLinks, componentBuilder);
                 componentBuilder.append("\n\n").italic(false);
                 componentBuilder.append("\n\n").italic(false);
-                componentBuilder.append("The official mcMMO wiki!").color(ChatColor.GREEN);
+                componentBuilder.append(webLinks.getLocaleDescription()).color(ChatColor.GREEN);
                 componentBuilder.append("\n");
                 componentBuilder.append("\n");
                 componentBuilder.append("I'm looking for more wiki staff, contact me on our discord!").italic(false).color(ChatColor.DARK_GRAY);
                 componentBuilder.append("I'm looking for more wiki staff, contact me on our discord!").italic(false).color(ChatColor.DARK_GRAY);
                 break;
                 break;
             case DISCORD:
             case DISCORD:
                 addUrlHeaderHover(webLinks, componentBuilder);
                 addUrlHeaderHover(webLinks, componentBuilder);
                 componentBuilder.append("\n\n").italic(false);
                 componentBuilder.append("\n\n").italic(false);
-                componentBuilder.append("The official mcMMO Discord server!").color(ChatColor.GREEN);
+                componentBuilder.append(webLinks.getLocaleDescription()).color(ChatColor.GREEN);
                 break;
                 break;
             case HELP_TRANSLATE:
             case HELP_TRANSLATE:
                 addUrlHeaderHover(webLinks, componentBuilder);
                 addUrlHeaderHover(webLinks, componentBuilder);
                 componentBuilder.append("\n\n").italic(false);
                 componentBuilder.append("\n\n").italic(false);
-                componentBuilder.append("mcMMO's translation service!").color(ChatColor.GREEN);
+                componentBuilder.append(webLinks.getLocaleDescription()).color(ChatColor.GREEN);
                 componentBuilder.append("\n");
                 componentBuilder.append("\n");
                 componentBuilder.append("You can use this website to help translate mcMMO into your language!" +
                 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);
                         "\nIf you want to know more contact me in discord.").italic(false).color(ChatColor.DARK_GRAY);
@@ -295,84 +256,85 @@ public class TextComponentFactory {
         return new ClickEvent(ClickEvent.Action.OPEN_URL, url);
         return new ClickEvent(ClickEvent.Action.OPEN_URL, url);
     }
     }
 
 
-    public static TextComponent getSubSkillTextComponent(Player player, SubSkillType subSkillType)
+    private static TextComponent getSubSkillTextComponent(Player player, SubSkillType subSkillType)
     {
     {
-
-        //Get skill name & description from our locale file
-        //String key = subSkillType.toString();
+        //Get skill name
         String skillName = subSkillType.getLocaleName();
         String skillName = subSkillType.getLocaleName();
+        TextComponent textComponent;
 
 
         //Setup Text Component
         //Setup Text Component
-        TextComponent textComponent = new TextComponent(skillName);
-        //textComponent.setColor(ChatColor.DARK_AQUA);
+        if(RankUtils.hasUnlockedSubskill(player, subSkillType))
+        {
+            textComponent = new TextComponent(skillName);
+            textComponent.setColor(ChatColor.DARK_AQUA);
+            textComponent.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/mmoinfo "+subSkillType.getNiceNameNoSpaces(subSkillType)));
+        }
+        else {
+            textComponent = new TextComponent("???");
+            textComponent.setColor(ChatColor.DARK_GRAY);
+            textComponent.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/mmoinfo ???"));
+        }
 
 
         //Hover Event
         //Hover Event
-        textComponent.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, getBaseComponent(player, subSkillType)));
+        addNewHoverComponentToTextComponent(textComponent, getSubSkillHoverComponent(player, subSkillType));
 
 
         //Insertion
         //Insertion
         textComponent.setInsertion(skillName);
         textComponent.setInsertion(skillName);
-        textComponent.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/mmoinfo "+subSkillType.getNiceNameNoSpaces(subSkillType)));
 
 
         return textComponent;
         return textComponent;
     }
     }
 
 
-    public static TextComponent getSubSkillTextComponent(Player player, AbstractSubSkill abstractSubSkill)
+    private static void addNewHoverComponentToTextComponent(TextComponent textComponent, BaseComponent[] baseComponent) {
+        textComponent.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, baseComponent));
+    }
+
+    private static TextComponent getSubSkillTextComponent(Player player, AbstractSubSkill abstractSubSkill)
     {
     {
         //String key = abstractSubSkill.getConfigKeyName();
         //String key = abstractSubSkill.getConfigKeyName();
         String skillName = abstractSubSkill.getNiceName();
         String skillName = abstractSubSkill.getNiceName();
 
 
         //Setup Text Component
         //Setup Text Component
-        TextComponent textComponent = new TextComponent(skillName);
-        textComponent.setColor(ChatColor.DARK_AQUA);
+        TextComponent textComponent;
+
+        //Setup Text Component
+        if(RankUtils.hasUnlockedSubskill(player, abstractSubSkill))
+        {
+            textComponent = new TextComponent(skillName);
+            textComponent.setColor(ChatColor.DARK_AQUA);
+            textComponent.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/mmoinfo "+abstractSubSkill.getConfigKeyName()));
+        }
+        else {
+            textComponent = new TextComponent("???");
+            textComponent.setColor(ChatColor.DARK_GRAY);
+            textComponent.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/mmoinfo ???"));
+        }
 
 
         //Hover Event
         //Hover Event
-        textComponent.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, getBaseComponent(player, abstractSubSkill)));
+        addNewHoverComponentToTextComponent(textComponent, getSubSkillHoverComponent(player, abstractSubSkill));
 
 
         //Insertion
         //Insertion
         textComponent.setInsertion(skillName);
         textComponent.setInsertion(skillName);
-        textComponent.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/mmoinfo "+abstractSubSkill.getConfigKeyName()));
 
 
         return textComponent;
         return textComponent;
     }
     }
 
 
-    private static BaseComponent[] getBaseComponent(Player player, AbstractSubSkill abstractSubSkill)
+    private static BaseComponent[] getSubSkillHoverComponent(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)
-        {
-            BaseComponent[] newComponents = getSubSkillHoverEventJSON(abstractSubSkill, player, curRank);
-            return newComponents;
-        }
-
-        return getSubSkillHoverEventJSON(abstractSubSkill, player, curRank);
+        return getSubSkillHoverEventJSON(abstractSubSkill, player);
     }
     }
 
 
-    private static BaseComponent[] getBaseComponent(Player player, SubSkillType subSkillType)
+    private static BaseComponent[] getSubSkillHoverComponent(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)
-        {
-            BaseComponent[] newComponents = getSubSkillHoverEventJSON(subSkillType, player, curRank);
-            return newComponents;
-        }
-
-        return getSubSkillHoverEventJSON(subSkillType, player, curRank);
+        return getSubSkillHoverEventJSON(subSkillType, player);
     }
     }
 
 
     /**
     /**
      * Used for the skill in the new skill system (Deriving from AbstractSubSkill)
      * Used for the skill in the new skill system (Deriving from AbstractSubSkill)
      * @param abstractSubSkill this subskill
      * @param abstractSubSkill this subskill
-     * @param player
-     * @param curRank
-     * @return
+     * @param player the player who owns this subskill
+     * @return the hover basecomponent object for this subskill
      */
      */
-    private static BaseComponent[] getSubSkillHoverEventJSON(AbstractSubSkill abstractSubSkill, Player player, int curRank)
+    private static BaseComponent[] getSubSkillHoverEventJSON(AbstractSubSkill abstractSubSkill, Player player)
     {
     {
         String skillName = abstractSubSkill.getNiceName();
         String skillName = abstractSubSkill.getNiceName();
 
 
@@ -390,10 +352,15 @@ public class TextComponentFactory {
         ChatColor ccLevelRequirement    = ChatColor.BLUE;
         ChatColor ccLevelRequirement    = ChatColor.BLUE;
         ChatColor ccLevelRequired       = ChatColor.RED;
         ChatColor ccLevelRequired       = ChatColor.RED;
 
 
+        ComponentBuilder componentBuilder;
+
         //SubSkillType Name
         //SubSkillType Name
-        ComponentBuilder componentBuilder = getNewComponentBuilder(skillName, ccSubSkillHeader);
+        if(RankUtils.hasUnlockedSubskill(player, abstractSubSkill))
+            componentBuilder = getNewComponentBuilder(skillName, ccSubSkillHeader);
+        else
+            componentBuilder = getNewComponentBuilder("???", ccSubSkillHeader);
 
 
-        if(RankUtils.getRank(player, abstractSubSkill) == 0)
+        if(!RankUtils.hasUnlockedSubskill(player, abstractSubSkill))
         {
         {
             //Skill is not unlocked yet
             //Skill is not unlocked yet
             addLocked(abstractSubSkill, ccLocked, ccLevelRequirement, ccLevelRequired, componentBuilder);
             addLocked(abstractSubSkill, ccLocked, ccLevelRequirement, ccLevelRequired, componentBuilder);
@@ -436,15 +403,26 @@ public class TextComponentFactory {
         }
         }
     }
     }
 
 
+    private static void addLocked(SubSkillType subSkillType, ChatColor ccLocked, ChatColor ccLevelRequirement, ChatColor ccLevelRequired, ComponentBuilder componentBuilder) {
+        addLocked(ccLocked, ccLevelRequirement, componentBuilder);
+        componentBuilder.append(String.valueOf(RankConfig.getInstance().getSubSkillUnlockLevel(subSkillType, 1))).color(ccLevelRequired);
+        //componentBuilder.append("\n");
+    }
+
     private static void addLocked(AbstractSubSkill abstractSubSkill, ChatColor ccLocked, ChatColor ccLevelRequirement, ChatColor ccLevelRequired, ComponentBuilder componentBuilder) {
     private static void addLocked(AbstractSubSkill abstractSubSkill, ChatColor ccLocked, ChatColor ccLevelRequirement, ChatColor ccLevelRequired, ComponentBuilder componentBuilder) {
+        addLocked(ccLocked, ccLevelRequirement, componentBuilder);
+        componentBuilder.append(String.valueOf(RankConfig.getInstance().getSubSkillUnlockLevel(abstractSubSkill, 1))).color(ccLevelRequired);
+        //componentBuilder.append("\n");
+    }
+
+    private static void addLocked(ChatColor ccLocked, ChatColor ccLevelRequirement, ComponentBuilder componentBuilder) {
         componentBuilder.append(LocaleLoader.getString("JSON.Locked")).color(ccLocked).bold(true);
         componentBuilder.append(LocaleLoader.getString("JSON.Locked")).color(ccLocked).bold(true);
         componentBuilder.append("\n").append("\n").bold(false);
         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);
+        componentBuilder.append(LocaleLoader.getString("JSON.LevelRequirement") + ": ").color(ccLevelRequirement);
     }
     }
 
 
     @Deprecated
     @Deprecated
-    private static BaseComponent[] getSubSkillHoverEventJSON(SubSkillType subSkillType, Player player, int curRank)
+    private static BaseComponent[] getSubSkillHoverEventJSON(SubSkillType subSkillType, Player player)
     {
     {
         String skillName = subSkillType.getLocaleName();
         String skillName = subSkillType.getLocaleName();
 
 
@@ -462,17 +440,18 @@ public class TextComponentFactory {
         ChatColor ccLevelRequirement    = ChatColor.BLUE;
         ChatColor ccLevelRequirement    = ChatColor.BLUE;
         ChatColor ccLevelRequired       = ChatColor.RED;
         ChatColor ccLevelRequired       = ChatColor.RED;
 
 
+        ComponentBuilder componentBuilder;
+
         //SubSkillType Name
         //SubSkillType Name
-        ComponentBuilder componentBuilder = getNewComponentBuilder(skillName, ccSubSkillHeader);
+        if(RankUtils.hasUnlockedSubskill(player, subSkillType))
+            componentBuilder = getNewComponentBuilder(skillName, ccSubSkillHeader);
+        else
+            componentBuilder = getNewComponentBuilder("???", ccSubSkillHeader);
 
 
-        if(RankUtils.getRank(player, subSkillType) == 0)
+        if(!RankUtils.hasUnlockedSubskill(player, subSkillType))
         {
         {
             //Skill is not unlocked yet
             //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);
-
+            addLocked(subSkillType, ccLocked, ccLevelRequirement, ccLevelRequired, componentBuilder);
         } else {
         } else {
             //addSubSkillTypeToHoverEventJSON(subSkillType, componentBuilder);
             //addSubSkillTypeToHoverEventJSON(subSkillType, componentBuilder);
 
 
@@ -484,13 +463,13 @@ public class TextComponentFactory {
                 //Empty line
                 //Empty line
                 componentBuilder.append("\n").bold(false);
                 componentBuilder.append("\n").bold(false);
             }
             }
-        }
 
 
-        componentBuilder.append(LocaleLoader.getString("JSON.DescriptionHeader"));
-        componentBuilder.color(ccDescriptionHeader);
-        componentBuilder.append("\n");
-        componentBuilder.append(subSkillType.getLocaleDescription());
-        componentBuilder.color(ccDescription);
+            componentBuilder.append(LocaleLoader.getString("JSON.DescriptionHeader"));
+            componentBuilder.color(ccDescriptionHeader);
+            componentBuilder.append("\n");
+            componentBuilder.append(subSkillType.getLocaleDescription());
+            componentBuilder.color(ccDescription);
+        }
 
 
         return componentBuilder.create();
         return componentBuilder.create();
     }
     }

+ 80 - 5
src/main/java/com/gmail/nossr50/util/skills/RankUtils.java

@@ -1,7 +1,8 @@
 package com.gmail.nossr50.util.skills;
 package com.gmail.nossr50.util.skills;
 
 
-import com.gmail.nossr50.config.AdvancedConfig;
+import com.gmail.nossr50.config.RankConfig;
 import com.gmail.nossr50.datatypes.skills.SubSkillType;
 import com.gmail.nossr50.datatypes.skills.SubSkillType;
+import com.gmail.nossr50.datatypes.skills.SuperAbilityType;
 import com.gmail.nossr50.datatypes.skills.subskills.AbstractSubSkill;
 import com.gmail.nossr50.datatypes.skills.subskills.AbstractSubSkill;
 import com.gmail.nossr50.util.player.UserManager;
 import com.gmail.nossr50.util.player.UserManager;
 import org.bukkit.entity.Player;
 import org.bukkit.entity.Player;
@@ -38,6 +39,58 @@ public class RankUtils {
         }
         }
     }
     }
 
 
+    /**
+     * Returns whether or not the player has unlocked the first rank in target subskill
+     * @param player the player
+     * @param subSkillType the target subskill
+     * @return true if the player has at least one rank in the skill
+     */
+    public static boolean hasUnlockedSubskill(Player player, SubSkillType subSkillType)
+    {
+        int curRank = getRank(player, subSkillType);
+
+        //-1 means the skill has no unlockable levels and is therefor unlocked
+        return curRank == -1 || curRank >= 1;
+    }
+
+    /**
+     * Returns whether or not the player has unlocked the first rank in target subskill
+     * @param player the player
+     * @param abstractSubSkill the target subskill
+     * @return true if the player has at least one rank in the skill
+     */
+    public static boolean hasUnlockedSubskill(Player player, AbstractSubSkill abstractSubSkill)
+    {
+        int curRank = getRank(player, abstractSubSkill);
+
+        //-1 means the skill has no unlockable levels and is therefor unlocked
+        return curRank == -1 || curRank >= 1;
+    }
+
+    /**
+     * Returns whether or not the player has reached the specified rank in target subskill
+     * @param rank the target rank
+     * @param player the player
+     * @param subSkillType the target subskill
+     * @return true if the player is at least that rank in this subskill
+     */
+    public static boolean hasReachedRank(int rank, Player player, SubSkillType subSkillType)
+    {
+        return getRank(player, subSkillType) >= rank;
+    }
+
+    /**
+     * Returns whether or not the player has reached the specified rank in target subskill
+     * @param rank the target rank
+     * @param player the player
+     * @param abstractSubSkill the target subskill
+     * @return true if the player is at least that rank in this subskill
+     */
+    public static boolean hasReachedRank(int rank, Player player, AbstractSubSkill abstractSubSkill)
+    {
+        return getRank(player, abstractSubSkill) >= rank;
+    }
+
     /**
     /**
      * Gets the current rank of the subskill for the player
      * Gets the current rank of the subskill for the player
      * @param player The player in question
      * @param player The player in question
@@ -162,6 +215,23 @@ public class RankUtils {
             subSkillRanks.put(s, new HashMap<>());
             subSkillRanks.put(s, new HashMap<>());
     }
     }
 
 
+/*    public static int getSubSkillUnlockRequirement(SubSkillType subSkillType)
+    {
+        String skillName = subSkillType.toString();
+        int numRanks = subSkillType.getNumRanks();
+
+        if(subSkillRanks == null)
+            subSkillRanks = new HashMap<>();
+
+        if(numRanks == 0)
+            return -1; //-1 Means the skill doesn't have ranks
+
+        if(subSkillRanks.get(skillName) == null && numRanks > 0)
+            addRanks(subSkillType);
+
+        return subSkillRanks.get(subSkillType.toString()).get(1);
+    }*/
+
     /**
     /**
      * Gets the unlock level for a specific rank in a subskill
      * Gets the unlock level for a specific rank in a subskill
      * @param subSkillType The target subskill
      * @param subSkillType The target subskill
@@ -169,13 +239,18 @@ public class RankUtils {
      * @return The level at which this rank unlocks
      * @return The level at which this rank unlocks
      */
      */
     @Deprecated
     @Deprecated
-    private static int getUnlockLevel(SubSkillType subSkillType, int rank)
+    public static int getUnlockLevel(SubSkillType subSkillType, int rank)
+    {
+        return RankConfig.getInstance().getSubSkillUnlockLevel(subSkillType, rank);
+    }
+
+    public static int getUnlockLevel(AbstractSubSkill abstractSubSkill, int rank)
     {
     {
-        return AdvancedConfig.getInstance().getSubSkillUnlockLevel(subSkillType, rank);
+        return RankConfig.getInstance().getSubSkillUnlockLevel(abstractSubSkill, rank);
     }
     }
 
 
-    private static int getUnlockLevel(AbstractSubSkill abstractSubSkill, int rank)
+    public static int getSuperAbilityUnlockRequirement(SuperAbilityType superAbilityType)
     {
     {
-        return AdvancedConfig.getInstance().getSubSkillUnlockLevel(abstractSubSkill, rank);
+        return getUnlockLevel(superAbilityType.getSubSkillTypeDefinition(), 1);
     }
     }
 }
 }

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

@@ -122,43 +122,11 @@ Skills:
             MaxBonusLevel: 100
             MaxBonusLevel: 100
             MinSpeed: 1.0
             MinSpeed: 1.0
             MaxSpeed: 4.0
             MaxSpeed: 4.0
-            
-        # Rank_Levels: Alchemy level where rank gets unlocked
-        Rank_Levels:
-            Rank_1: 0
-            Rank_2: 12
-            Rank_3: 25
-            Rank_4: 37
-            Rank_5: 50
-            Rank_6: 62
-            Rank_7: 75
-            Rank_8: 87
     #
     #
     #  Settings for Archery
     #  Settings for Archery
     ###
     ###
     Archery:
     Archery:
         SkillShot:
         SkillShot:
-            Rank_Levels:
-                Rank_1: 5
-                Rank_2: 10
-                Rank_3: 15
-                Rank_4: 20
-                Rank_5: 25
-                Rank_6: 30
-                Rank_7: 35
-                Rank_8: 40
-                Rank_9: 45
-                Rank_10: 50
-                Rank_11: 55
-                Rank_12: 60
-                Rank_13: 65
-                Rank_14: 70
-                Rank_15: 75
-                Rank_16: 80
-                Rank_17: 85
-                Rank_18: 90
-                Rank_19: 95
-                Rank_20: 100
             # RankDamageMultiplier: The current rank of this subskill is multiplied by this value to determine the bonus damage, rank 20 would result in 200% damage increase with a value of 10.0 for RankDamageMultiplier
             # RankDamageMultiplier: The current rank of this subskill is multiplied by this value to determine the bonus damage, rank 20 would result in 200% damage increase with a value of 10.0 for RankDamageMultiplier
             # RankDamageMultiplier is a percentage
             # RankDamageMultiplier is a percentage
             RankDamageMultiplier: 10.0
             RankDamageMultiplier: 10.0
@@ -189,11 +157,6 @@ Skills:
             # This number is multiplied by the current rank the player has in this subskill to find the amount of additional damage to apply
             # This number is multiplied by the current rank the player has in this subskill to find the amount of additional damage to apply
             # With the default config value of 1.0, at rank 4 a player will deal 4.0 extra damage with Axes (1.0 * 4)
             # With the default config value of 1.0, at rank 4 a player will deal 4.0 extra damage with Axes (1.0 * 4)
             RankDamageMultiplier: 1.0
             RankDamageMultiplier: 1.0
-            Rank_Levels:
-                Rank_1: 5
-                Rank_2: 10
-                Rank_3: 15
-                Rank_4: 20
         CriticalStrikes:
         CriticalStrikes:
             # ChanceMax: Maximum chance of causing a critical hit when on <MaxBonusLevel> or higher
             # ChanceMax: Maximum chance of causing a critical hit when on <MaxBonusLevel> or higher
             # MaxBonusLevel: Level where <ChanceMax> of causing critical hits is reached
             # MaxBonusLevel: Level where <ChanceMax> of causing critical hits is reached
@@ -226,16 +189,6 @@ Skills:
     #  Settings for Fishing
     #  Settings for Fishing
     ###
     ###
     Fishing:
     Fishing:
-        # Rank_Levels: Fishing level where rank gets unlocked
-        Rank_Levels:
-            Rank_1: 0
-            Rank_2: 12
-            Rank_3: 25
-            Rank_4: 37
-            Rank_5: 50
-            Rank_6: 62
-            Rank_7: 70
-            Rank_8: 87
 
 
         ShakeChance:
         ShakeChance:
             Rank_1: 15.0
             Rank_1: 15.0
@@ -277,15 +230,11 @@ Skills:
     #  Settings for Herbalism
     #  Settings for Herbalism
     ###
     ###
     Herbalism:
     Herbalism:
-        FarmersDiet:
-            # This determines when Farmers Diet adds extra hunger recovery to food
-            RankChange: 20
 
 
         GreenThumb:
         GreenThumb:
             # StageChange: Level value when the GreenThumb stage rank goes up
             # StageChange: Level value when the GreenThumb stage rank goes up
             # ChanceMax: Maximum chance of GreenThumb when on <MaxBonusLevel> or higher
             # ChanceMax: Maximum chance of GreenThumb when on <MaxBonusLevel> or higher
             # MaxBonusLevel: On this level, GreenThumb chance will be <ChanceMax>
             # MaxBonusLevel: On this level, GreenThumb chance will be <ChanceMax>
-            StageChange: 20
             ChanceMax: 100.0
             ChanceMax: 100.0
             MaxBonusLevel: 100
             MaxBonusLevel: 100
 
 
@@ -318,15 +267,6 @@ Skills:
 
 
         # BlastMining_Rank: BlastMining rank unlocks
         # BlastMining_Rank: BlastMining rank unlocks
         BlastMining:
         BlastMining:
-            Rank_Levels:
-                Rank_1: 12
-                Rank_2: 25
-                Rank_3: 37
-                Rank_4: 50
-                Rank_5: 62
-                Rank_6: 75
-                Rank_7: 87
-                Rank_8: 100
 
 
             # BlastDamageDecrease Ranks: % of damage reduced from TNT explosions
             # BlastDamageDecrease Ranks: % of damage reduced from TNT explosions
             BlastDamageDecrease:
             BlastDamageDecrease:
@@ -400,15 +340,6 @@ Skills:
 
 
         ArcaneForging:
         ArcaneForging:
             May_Lose_Enchants: true
             May_Lose_Enchants: true
-            Rank_Levels:
-                Rank_1: 12
-                Rank_2: 25
-                Rank_3: 37
-                Rank_4: 50
-                Rank_5: 62
-                Rank_6: 75
-                Rank_7: 87
-                Rank_8: 100
             Keep_Enchants_Chance:
             Keep_Enchants_Chance:
                 Rank_1: 10.0
                 Rank_1: 10.0
                 Rank_2: 20.0
                 Rank_2: 20.0
@@ -490,17 +421,6 @@ Skills:
             UnlockLevel: 25
             UnlockLevel: 25
             Chance: 33.0
             Chance: 33.0
 
 
-        # Rank_Levels: Smelting level where rank gets unlocked
-        Rank_Levels:
-            Rank_1: 12
-            Rank_2: 25
-            Rank_3: 37
-            Rank_4: 50
-            Rank_5: 62
-            Rank_6: 75
-            Rank_7: 87
-            Rank_8: 100
-
         # VanillaXPMultiplier: Vanilla XP gained from smelting ores is multiplied by these values.
         # VanillaXPMultiplier: Vanilla XP gained from smelting ores is multiplied by these values.
         VanillaXPMultiplier:
         VanillaXPMultiplier:
             Rank_1: 1
             Rank_1: 1
@@ -630,14 +550,6 @@ Skills:
     #  Settings for Woodcutting
     #  Settings for Woodcutting
     ###
     ###
     Woodcutting:
     Woodcutting:
-        Splinter:
-            Rank_Levels:
-                Rank_1:
-                    LevelReq: 5
-                Rank_2:
-                    LevelReq: 30
-                Rank_3:
-                    LevelReq: 55
         TreeFeller:
         TreeFeller:
             # If set to true then tree feller will not use the new system and will use its old behaviour
             # If set to true then tree feller will not use the new system and will use its old behaviour
             Classic: false
             Classic: false
@@ -645,64 +557,27 @@ Skills:
             ChargeRate: 600
             ChargeRate: 600
             Rank_Levels:
             Rank_Levels:
                 Rank_1:
                 Rank_1:
-                    LevelReq: 10
                     TreeSizeMax: 100
                     TreeSizeMax: 100
                     Charges: 1
                     Charges: 1
                 Rank_2:
                 Rank_2:
-                    LevelReq: 25
                     TreeSizeMax: 200
                     TreeSizeMax: 200
                     Charges: 1
                     Charges: 1
                 Rank_3:
                 Rank_3:
-                    LevelReq: 50
                     TreeSizeMax: 200
                     TreeSizeMax: 200
                     Charges: 2
                     Charges: 2
                 Rank_4:
                 Rank_4:
-                    LevelReq: 75
                     TreeSizeMax: 200
                     TreeSizeMax: 200
                     Charges: 3
                     Charges: 3
                 Rank_5:
                 Rank_5:
-                    LevelReq: 100
                     TreeSizeMax: 500
                     TreeSizeMax: 500
                     Charges: 3
                     Charges: 3
-        BarkSurgeon:
-            Rank_Levels:
-                Rank_1:
-                    LevelReq: 70
-                Rank_2:
-                    LevelReq: 80
-                Rank_3:
-                    LevelReq: 95
-        NaturesBounty:
-            Rank_Levels:
-                Rank_1:
-                    LevelReq: 40
-                Rank_2:
-                    LevelReq: 60
-                Rank_3:
-                    LevelReq: 90
         # Double Drops
         # Double Drops
         HarvestLumber:
         HarvestLumber:
-            Classic: false
             # ChanceMax & MaxBonusLevel are only used for Classic, I'll make that more clear in the future.
             # ChanceMax & MaxBonusLevel are only used for Classic, I'll make that more clear in the future.
             # ChanceMax: Maximum chance of receiving double drops (100 = 100%)
             # ChanceMax: Maximum chance of receiving double drops (100 = 100%)
             # MaxBonusLevel: Level when the maximum chance of receiving double drops is reached
             # MaxBonusLevel: Level when the maximum chance of receiving double drops is reached
             ChanceMax: 100.0
             ChanceMax: 100.0
             MaxBonusLevel: 100
             MaxBonusLevel: 100
-            Rank_Levels:
-                Rank_1:
-                    LevelReq: 20
-                Rank_2:
-                    LevelReq: 45
-                Rank_3:
-                    LevelReq: 85
-        LeafBlower:
-            Rank_Levels:
-                Rank_1:
-                    LevelReq: 15
-                Rank_2:
-                    LevelReq: 35
-                Rank_3:
-                    LevelReq: 65
 Style:
 Style:
     JSON:
     JSON:
         Notification:
         Notification:

+ 0 - 2
src/main/resources/config.yml

@@ -277,8 +277,6 @@ Abilities:
     Enabled: true
     Enabled: true
     Messages: true
     Messages: true
     Activation:
     Activation:
-        # If set to true, abilities will not be available until they meet specific level requirements to use
-        Level_Gate_Abilities: true
         Only_Activate_When_Sneaking: false
         Only_Activate_When_Sneaking: false
     Cooldowns:
     Cooldowns:
         Berserk: 240
         Berserk: 240

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

@@ -40,6 +40,12 @@ JSON.Taming=Taming
 JSON.Unarmed=Unarmed
 JSON.Unarmed=Unarmed
 JSON.Woodcutting=Woodcutting
 JSON.Woodcutting=Woodcutting
 JSON.LevelUp=increased to
 JSON.LevelUp=increased to
+JSON.URL.Website=The official mcMMO Website!
+JSON.URL.Discord=The official mcMMO Discord server!
+JSON.URL.Patreon=Support nossr50 and his work for mcMMO on Patreon!
+JSON.URL.Spigot=The official mcMMO Spigot Resource Page!
+JSON.URL.Translation=Translate mcMMO into other languages!
+JSON.URL.Wiki=The official mcMMO wiki!
 
 
 #This is the message sent to players when an ability is activated
 #This is the message sent to players when an ability is activated
 JSON.Notification.SuperAbility={0}
 JSON.Notification.SuperAbility={0}
@@ -634,6 +640,7 @@ Commands.Usage.Skill=skill
 Commands.Usage.SubSkill=subskill
 Commands.Usage.SubSkill=subskill
 Commands.Usage.XP=xp
 Commands.Usage.XP=xp
 Commands.Description.mmoinfo=Read details about a skill or mechanic.
 Commands.Description.mmoinfo=Read details about a skill or mechanic.
+Commands.MmoInfo.Mystery=[[GRAY]]You haven't unlocked this skill yet, but when you do you will be able to read details about it here!
 Commands.MmoInfo.NoMatch=That subskill doesn't exist!
 Commands.MmoInfo.NoMatch=That subskill doesn't exist!
 Commands.MmoInfo.Header=[[DARK_AQUA]]-=[]=====[][[GOLD]] MMO Info [[DARK_AQUA]][]=====[]=-
 Commands.MmoInfo.Header=[[DARK_AQUA]]-=[]=====[][[GOLD]] MMO Info [[DARK_AQUA]][]=====[]=-
 Commands.MmoInfo.SubSkillHeader=[[GOLD]]Name:[[YELLOW]] {0} 
 Commands.MmoInfo.SubSkillHeader=[[GOLD]]Name:[[YELLOW]] {0} 

+ 88 - 7
src/main/resources/skillranks.yml

@@ -3,13 +3,12 @@
 # You can however, change when they unlock, if you make all ranks of a skill level 0 for example every player will have the most powerful version of that skill right away.
 # You can however, change when they unlock, if you make all ranks of a skill level 0 for example every player will have the most powerful version of that skill right away.
 # Retro Mode and Standard have separate config settings to make it easier for server owners to understand the difference between the two
 # Retro Mode and Standard have separate config settings to make it easier for server owners to understand the difference between the two
 # Retro Mode is setup to be an entirely cosmetic change, keeping the old 0-1000 feeling of mcMMO
 # Retro Mode is setup to be an entirely cosmetic change, keeping the old 0-1000 feeling of mcMMO
-# Retro Mode has 10x faster leveling and 10x higher skill requirements, if you do the math you can see its the same as Standard other than cosmetics!
+# Retro Mode has 10x faster leveling and 10x higher skill requirements, if you do the math you can see its the same as Standrd and only cosmetic!
 ###
 ###
 Alchemy:
 Alchemy:
-    Alchemy:
-        Catalysis:
-            Standard:
-                Rank_1: 10
+    Catalysis:
+        Standard:
+            Rank_1: 10
         RetroMode:
         RetroMode:
             Rank_1: 100
             Rank_1: 100
     Concoctions:
     Concoctions:
@@ -55,7 +54,7 @@ Archery:
             Rank_19: 95
             Rank_19: 95
             Rank_20: 100
             Rank_20: 100
         Retro:
         Retro:
-            Rank_1: 5
+            Rank_1: 50
             Rank_2: 100
             Rank_2: 100
             Rank_3: 150
             Rank_3: 150
             Rank_4: 200
             Rank_4: 200
@@ -76,6 +75,11 @@ Archery:
             Rank_19: 950
             Rank_19: 950
             Rank_20: 1000
             Rank_20: 1000
 Axes:
 Axes:
+    SkullSplitter:
+        Standard:
+            Rank_1: 10
+        RetroMode:
+            Rank_1: 100
     ArmorImpact:
     ArmorImpact:
         Standard:
         Standard:
             Rank_1: 5
             Rank_1: 5
@@ -99,7 +103,7 @@ Axes:
             Rank_19: 95
             Rank_19: 95
             Rank_20: 100
             Rank_20: 100
         Retro:
         Retro:
-            Rank_1: 5
+            Rank_1: 50
             Rank_2: 100
             Rank_2: 100
             Rank_3: 150
             Rank_3: 150
             Rank_4: 200
             Rank_4: 200
@@ -202,6 +206,11 @@ Salvage:
             Rank_7: 850
             Rank_7: 850
             Rank_8: 1000
             Rank_8: 1000
 Mining:
 Mining:
+    SuperBreaker:
+        Standard:
+            Rank_1: 10
+        RetroMode:
+            Rank_1: 100
     BlastMining:
     BlastMining:
         Standard:
         Standard:
             Rank_1: 10
             Rank_1: 10
@@ -222,6 +231,11 @@ Mining:
             Rank_7: 850
             Rank_7: 850
             Rank_8: 1000
             Rank_8: 1000
 Herbalism:
 Herbalism:
+    GreenTerra:
+        Standard:
+            Rank_1: 10
+        RetroMode:
+            Rank_1: 100
     GreenThumb:
     GreenThumb:
         Standard:
         Standard:
             Rank_1: 20
             Rank_1: 20
@@ -291,3 +305,70 @@ Fishing:
             Rank_6: 750
             Rank_6: 750
             Rank_7: 850
             Rank_7: 850
             Rank_8: 1000
             Rank_8: 1000
+Swords:
+    SerratedStrikes:
+        Standard:
+            Rank_1: 10
+        RetroMode:
+            Rank_1: 100
+Unarmed:
+    Berserk:
+        Standard:
+            Rank_1: 10
+        RetroMode:
+            Rank_1: 100
+Woodcutting:
+    Splinter:
+        Standard:
+            Rank_1: 5
+            Rank_2: 30
+            Rank_3: 55
+        RetroMode:
+            Rank_1: 50
+            Rank_2: 300
+            Rank_3: 550
+    TreeFeller:
+        Standard:
+            Rank_1: 10
+            Rank_2: 25
+            Rank_3: 50
+            Rank_4: 75
+            Rank_5: 100
+        RetroMode:
+            Rank_1: 100
+            Rank_2: 250
+            Rank_3: 500
+            Rank_4: 750
+            Rank_5: 1000
+    BarkSurgeon:
+        Standard:
+            Rank_1: 70
+            Rank_2: 80
+            Rank_3: 95
+        RetroMode:
+            Rank_1: 700
+            Rank_2: 800
+            Rank_3: 950
+    NaturesBounty:
+        Standard:
+            Rank_1: 40
+            Rank_2: 60
+            Rank_3: 90
+        RetroMode:
+            Rank_1: 400
+            Rank_2: 600
+            Rank_3: 900
+    HarvestLumber:
+        Standard:
+            Rank_1: 1
+        RetroMode:
+            Rank_1: 10
+    LeafBlower:
+        Standard:
+            Rank_1: 15
+            Rank_2: 35
+            Rank_3: 65
+        RetroMode:
+            Rank_1: 150
+            Rank_2: 350
+            Rank_3: 650