2
0
Эх сурвалжийг харах

Added new ExperienceFormula config and /mcconvert command

TfT_02 12 жил өмнө
parent
commit
68e433b3b7

+ 3 - 0
Changelog.txt

@@ -8,6 +8,8 @@ Key:
   - Removal
 
 Version 1.4.07-dev
+ + Added new experienceFormula config file! Has support for EXPONENTIAL formula curves.
+ + Added new /mcconvert command to convert players levels and experience from one formula curve to another.
  + Added snow to excavation
  + Added new experience curve option. Cumulative curve, calculates experience needed for next level using power level.
  + Added extra settings to config.yml for Call of the Wild (Taming)
@@ -26,6 +28,7 @@ Version 1.4.07-dev
  ! Changed Swords "Counter Attack" ability from passive to active. Blocking is required to activate.
  ! Admin and Party chat prefixes are now customizable
  ! Changed the color of party leader names in Party chat
+ ! Moved all experience formula related settings from config.yml to experienceFormula.yml (This includes skill modifiers and curve modifiers)
  ! Improved profile saving
  ! Improved partial name matcher
  ! Slightly improved update checker feedback

+ 69 - 0
src/main/java/com/gmail/nossr50/commands/database/McconvertCommand.java

@@ -0,0 +1,69 @@
+package com.gmail.nossr50.commands.database;
+
+import java.util.List;
+
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.bukkit.command.TabExecutor;
+import org.bukkit.entity.Player;
+
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.datatypes.experience.FormulaType;
+import com.gmail.nossr50.mcMMO;
+import com.gmail.nossr50.runnables.database.FormulaConversionTask;
+import com.gmail.nossr50.util.player.UserManager;
+import com.google.common.collect.ImmutableList;
+
+public class McconvertCommand implements TabExecutor {
+
+    /*
+    * Do this later; Use mcconvert instead of mmoupdate:
+    * OLD :
+    * /mmoupdate flatfile / mysql
+    *
+    * NEW :
+    * /mcconvert <database> <flatfile / sql>
+    * /mcconvert <experience> <linear / exponential>
+    * */
+    @Override
+    public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
+        switch (args.length) {
+            case 0:
+                sender.sendMessage("Usage is /mcconvert <linear | exponential>");
+                return true;
+
+            case 1:
+                FormulaType previousType = mcMMO.getFormulaManager().getPreviousFormulaType();
+                FormulaType newType = FormulaType.getFormulaType(args[0].toUpperCase());
+
+                if (newType == FormulaType.UNKNOWN) {
+                    sender.sendMessage("Unknown formula type! Valid types are: LINEAR and EXPONENTIAL");
+                    return true;
+                }
+
+                if (previousType == newType) {
+                    sender.sendMessage(LocaleLoader.getString("Commands.mcconvert.Same", newType));
+                    return true;
+                }
+
+                sender.sendMessage(LocaleLoader.getString("Commands.mcconvert.Start", previousType.toString(), newType.toString()));
+                UserManager.saveAll();
+                UserManager.clearAll();
+                new FormulaConversionTask(sender, newType).runTaskLater(mcMMO.p, 1);
+
+                for (Player player : mcMMO.p.getServer().getOnlinePlayers()) {
+                    UserManager.addUser(player);
+                }
+                return true;
+
+            default:
+                break;
+        }
+        return false;
+    }
+
+    @Override
+    public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
+        return ImmutableList.of();
+    }
+}

+ 0 - 9
src/main/java/com/gmail/nossr50/config/Config.java

@@ -192,10 +192,6 @@ public class Config extends AutoUpdateConfigLoader {
             reason.add("Experience.Gains.Mobspawners.Multiplier should be at least 0!");
         }
 
-        if (getFormulaMultiplierCurve() < 0) {
-            reason.add("Experience.Formula.Curve_Modifier should be at least 0!");
-        }
-
         return noErrorsInConfig(reason);
     }
 
@@ -484,9 +480,4 @@ public class Config extends AutoUpdateConfigLoader {
     public double getWitherSkeletonXP() { return config.getDouble("Experience.Combat.Multiplier.Wither_Skeleton", 4.0); }
 
     public double getSpawnedMobXpMultiplier() { return config.getDouble("Experience.Gains.Mobspawners.Multiplier", 0.0); }
-
-    /* XP Formula Multiplier */
-    public int getFormulaMultiplierCurve() { return config.getInt("Experience.Formula.Curve_Modifier", 20); }
-    public boolean getCumulativeCurveEnabled() { return config.getBoolean("Experience.Formula.Cumulative_Curve", false); }
-    public double getFormulaSkillModifier(SkillType skill) { return config.getDouble("Experience.Formula.Modifier." + StringUtils.getCapitalized(skill.toString())); }
 }

+ 44 - 0
src/main/java/com/gmail/nossr50/config/experience/ExperienceConfig.java

@@ -0,0 +1,44 @@
+package com.gmail.nossr50.config.experience;
+
+import com.gmail.nossr50.config.AutoUpdateConfigLoader;
+import com.gmail.nossr50.datatypes.experience.FormulaType;
+import com.gmail.nossr50.datatypes.skills.SkillType;
+import com.gmail.nossr50.util.StringUtils;
+
+public class ExperienceConfig extends AutoUpdateConfigLoader {
+    private static ExperienceConfig instance;
+
+    private ExperienceConfig() {
+        super("experienceFormula.yml");
+    }
+
+    public static ExperienceConfig getInstance() {
+        if (instance == null) {
+            instance = new ExperienceConfig();
+        }
+
+        return instance;
+    }
+
+    @Override
+    protected void loadKeys() {}
+
+    /* XP Formula Multiplier */
+    public FormulaType getFormulaType() { return FormulaType.getFormulaType(config.getString("Experience_Formula.Curve")); }
+    public boolean getCumulativeCurveEnabled() { return config.getBoolean("Experience_Formula.Cumulative_Curve", false); }
+
+    /* Linear curve values */
+    public int getLinearBase() { return config.getInt("Experience_Formula.Linear_Values.base", 1020); }
+    public double getLinearMultiplier() { return config.getDouble("Experience_Formula.Linear_Values.multiplier", 20); }
+
+    /* Exponential curve values */
+    public double getExponentialMultiplier() { return config.getDouble("Experience_Formula.Exponential_Values.multiplier", 0.1); }
+    public double getExponentialExponent() { return config.getDouble("Experience_Formula.Exponential_Values.exponent", 1.80); }
+    public int getExponentialBase() { return config.getInt("Experience_Formula.Exponential_Values.base", 2000); }
+
+    /* Skill modifiers */
+    public double getFormulaSkillModifier(SkillType skill) { return config.getDouble("Experience_Formula.Modifier." + StringUtils.getCapitalized(skill.toString())); }
+
+    /* Conversion */
+    public double getExpModifier() { return config.getDouble("Conversion.Exp_Modifier", 1); }
+}

+ 16 - 0
src/main/java/com/gmail/nossr50/datatypes/experience/FormulaType.java

@@ -0,0 +1,16 @@
+package com.gmail.nossr50.datatypes.experience;
+
+public enum FormulaType {
+    LINEAR,
+    EXPONENTIAL,
+    UNKNOWN;
+
+    public static FormulaType getFormulaType(String string) {
+        try {
+            return valueOf(string);
+        }
+        catch (IllegalArgumentException ex) {
+            return UNKNOWN;
+        }
+    }
+};

+ 7 - 6
src/main/java/com/gmail/nossr50/datatypes/player/PlayerProfile.java

@@ -8,8 +8,10 @@ import org.bukkit.scoreboard.Scoreboard;
 
 import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.config.Config;
+import com.gmail.nossr50.config.experience.ExperienceConfig;
 import com.gmail.nossr50.config.spout.SpoutConfig;
 import com.gmail.nossr50.datatypes.MobHealthbarType;
+import com.gmail.nossr50.datatypes.experience.FormulaType;
 import com.gmail.nossr50.datatypes.skills.AbilityType;
 import com.gmail.nossr50.datatypes.skills.SkillType;
 import com.gmail.nossr50.datatypes.spout.huds.HudType;
@@ -271,17 +273,16 @@ public class PlayerProfile {
     }
 
     /**
-     * Get the amount of Xp remaining before the next level.
+     * Get the total amount of Xp before the next level.
      *
      * @param skillType Type of skill to check
-     * @return the Xp remaining until next level
+     * @return the total amount of Xp until next level
      */
     public int getXpToLevel(SkillType skillType) {
-        if (Config.getInstance().getCumulativeCurveEnabled()) {
-            return 1020 + (UserManager.getPlayer(playerName).getPowerLevel() * Config.getInstance().getFormulaMultiplierCurve());
-        }
+        int level = (ExperienceConfig.getInstance().getCumulativeCurveEnabled()) ? UserManager.getPlayer(playerName).getPowerLevel() : skills.get(skillType);
+        FormulaType formulaType = ExperienceConfig.getInstance().getFormulaType();
 
-        return 1020 + (skills.get(skillType) * Config.getInstance().getFormulaMultiplierCurve());
+        return mcMMO.getFormulaManager().getCachedXpToLevel(level, formulaType);
     }
 
     private int getChildSkillLevel(SkillType skillType) {

+ 2 - 1
src/main/java/com/gmail/nossr50/datatypes/skills/SkillType.java

@@ -8,6 +8,7 @@ import org.bukkit.Color;
 
 import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.config.Config;
+import com.gmail.nossr50.config.experience.ExperienceConfig;
 import com.gmail.nossr50.locale.LocaleLoader;
 import com.gmail.nossr50.skills.SkillManager;
 import com.gmail.nossr50.skills.acrobatics.AcrobaticsManager;
@@ -108,7 +109,7 @@ public enum SkillType {
     }
 
     public double getXpModifier() {
-        return Config.getInstance().getFormulaSkillModifier(this);
+        return ExperienceConfig.getInstance().getFormulaSkillModifier(this);
     }
 
     public static SkillType getSkill(String skillName) {

+ 9 - 0
src/main/java/com/gmail/nossr50/mcMMO.java

@@ -5,6 +5,7 @@ import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 
+import com.gmail.nossr50.util.experience.FormulaManager;
 import net.shatteredlands.shatt.backup.ZipLibrary;
 
 import org.bukkit.entity.Player;
@@ -59,6 +60,7 @@ public class mcMMO extends JavaPlugin {
     private static ChunkManager      placeStore;
     private static RepairableManager repairableManager;
     private static DatabaseManager   databaseManager;
+    private static FormulaManager    formulaManager;
 
     /* File Paths */
     private static String mainDirectory;
@@ -125,6 +127,8 @@ public class mcMMO extends JavaPlugin {
 
             PartyManager.loadParties();
 
+            formulaManager = new FormulaManager(this);
+
             for (Player player : getServer().getOnlinePlayers()) {
                 UserManager.addUser(player); // In case of reload add all users back into UserManager
             }
@@ -166,6 +170,7 @@ public class mcMMO extends JavaPlugin {
         try {
             UserManager.saveAll();      // Make sure to save player information if the server shuts down
             PartyManager.saveParties(); // Save our parties
+            formulaManager.saveFormula();
             placeStore.saveAll();       // Save our metadata
             placeStore.cleanUp();       // Cleanup empty metadata stores
         }
@@ -232,6 +237,10 @@ public class mcMMO extends JavaPlugin {
         getLogger().info("[Debug] " + message);
     }
 
+    public static FormulaManager getFormulaManager() {
+        return formulaManager;
+    }
+
     public static ChunkManager getPlaceStore() {
         return placeStore;
     }

+ 14 - 3
src/main/java/com/gmail/nossr50/metrics/MetricsManager.java

@@ -8,6 +8,7 @@ import java.util.Locale;
 import com.gmail.nossr50.mcMMO;
 import com.gmail.nossr50.config.Config;
 import com.gmail.nossr50.config.HiddenConfig;
+import com.gmail.nossr50.config.experience.ExperienceConfig;
 import com.gmail.nossr50.locale.LocaleLoader;
 import com.turt2live.metrics.EMetrics;
 import com.turt2live.metrics.Metrics;
@@ -176,6 +177,16 @@ public class MetricsManager {
                     }
                 });
 
+                // ExperienceFormulaShape Graph
+                Graph experienceFormulaShapeGraph = metrics.createGraph("Experience Formula Shape Graph");
+
+                experienceFormulaShapeGraph.addPlotter(new Metrics.Plotter(ExperienceConfig.getInstance().getFormulaType().toString()) {
+                    @Override
+                    public int getValue() {
+                        return 1;
+                    }
+                });
+
                 // GlobalMultiplier Graph
                 Graph globalMultiplierGraph = metrics.createGraph("Global Multiplier Graph");
 
@@ -189,7 +200,7 @@ public class MetricsManager {
                 // GlobalCurveModifier Graph
                 Graph globalCurveModifierGraph = metrics.createGraph("Global Curve Modifier Graph");
 
-                globalCurveModifierGraph.addPlotter(new Metrics.Plotter(Config.getInstance().getFormulaMultiplierCurve() + "") {
+                globalCurveModifierGraph.addPlotter(new Metrics.Plotter(ExperienceConfig.getInstance().getLinearMultiplier() + "") {
                     @Override
                     public int getValue() {
                         return 1;
@@ -227,7 +238,7 @@ public class MetricsManager {
                 // GlobalCurveModifier Fuzzy Logic Numbers
                 Graph globalCurveMultiplierGraphFuzzy = metrics.createGraph("Global Curve Multiplier Fuzz");
 
-                if (Config.getInstance().getFormulaMultiplierCurve() > 20.0) {
+                if (ExperienceConfig.getInstance().getLinearMultiplier() > 20.0) {
                     globalCurveMultiplierGraphFuzzy.addPlotter(new Metrics.Plotter("Higher") {
                         @Override
                         public int getValue() {
@@ -235,7 +246,7 @@ public class MetricsManager {
                         }
                     });
                 }
-                else if (Config.getInstance().getFormulaMultiplierCurve() < 20.0) {
+                else if (ExperienceConfig.getInstance().getLinearMultiplier() < 20.0) {
                     globalCurveMultiplierGraphFuzzy.addPlotter(new Metrics.Plotter("Lower") {
                         @Override
                         public int getValue() {

+ 96 - 0
src/main/java/com/gmail/nossr50/runnables/database/FormulaConversionTask.java

@@ -0,0 +1,96 @@
+package com.gmail.nossr50.runnables.database;
+
+import com.gmail.nossr50.datatypes.experience.FormulaType;
+
+import org.bukkit.command.CommandSender;
+import org.bukkit.scheduler.BukkitRunnable;
+
+import com.gmail.nossr50.locale.LocaleLoader;
+import com.gmail.nossr50.mcMMO;
+import com.gmail.nossr50.config.experience.ExperienceConfig;
+import com.gmail.nossr50.datatypes.player.McMMOPlayer;
+import com.gmail.nossr50.datatypes.player.PlayerProfile;
+import com.gmail.nossr50.datatypes.skills.SkillType;
+import com.gmail.nossr50.util.player.UserManager;
+
+public class FormulaConversionTask extends BukkitRunnable {
+    private CommandSender sender;
+    private FormulaType formulaType;
+
+    public FormulaConversionTask(CommandSender sender, FormulaType formulaType) {
+        this.sender = sender;
+        this.formulaType = formulaType;
+    }
+
+    @Override
+    public void run() {
+        for (String playerName : mcMMO.getDatabaseManager().getStoredUsers()) {
+            McMMOPlayer mcMMOPlayer = UserManager.getPlayer(playerName);
+            PlayerProfile profile;
+
+            // If the mcMMOPlayer doesn't exist, create a temporary profile and check if it's present in the database. If it's not, abort the process.
+            if (mcMMOPlayer == null) {
+                profile = mcMMO.getDatabaseManager().loadPlayerProfile(playerName, false);
+
+                if (!profile.isLoaded()) {
+                    mcMMO.p.debug("Profile not loaded");
+                    continue;
+                }
+
+                editValues(profile);
+                profile.save(); // Since this is a temporary profile, we save it here.
+            }
+            else {
+                profile = mcMMOPlayer.getProfile();
+                editValues(profile);
+            }
+        }
+        mcMMO.getFormulaManager().setPreviousFormulaType(formulaType);
+
+        sender.sendMessage(LocaleLoader.getString("Commands.mcconvert.Finish", formulaType.toString()));
+    }
+
+    private void editValues(PlayerProfile profile) {
+        mcMMO.p.debug("========================================================================");
+        mcMMO.p.debug("Conversion report for " + profile.getPlayerName() + ":");
+        for (SkillType skillType : SkillType.values()) {
+            if (skillType.isChildSkill()) {
+                continue;
+            }
+
+            int[] oldExperienceValues = new int[2];
+            oldExperienceValues[0] = profile.getSkillLevel(skillType);
+            oldExperienceValues[1] = profile.getSkillXpLevel(skillType);
+
+            int totalOldXP = mcMMO.getFormulaManager().calculateTotalExperience(oldExperienceValues);
+            if (totalOldXP == 0) {
+                continue;
+            }
+
+            double modifier = ExperienceConfig.getInstance().getExpModifier();
+            if (modifier <= 0) {
+                modifier = 1;
+                mcMMO.p.getLogger().warning("Invalid value found for Conversion.Exp_Modifier! Skipping using the modifier...");
+            }
+
+            int[] newExperienceValues = mcMMO.getFormulaManager().calculateNewLevel(skillType, (int) Math.floor(totalOldXP / modifier), formulaType);
+            int newLevel = newExperienceValues[0];
+            int newXPlevel = newExperienceValues[1];
+
+            mcMMO.p.debug("  Skill: " + skillType.toString());
+
+            mcMMO.p.debug("    OLD:");
+            mcMMO.p.debug("      Level: " + oldExperienceValues[0]);
+            mcMMO.p.debug("      XP " + oldExperienceValues[1]);
+            mcMMO.p.debug("      Total XP " + totalOldXP);
+
+            mcMMO.p.debug("    NEW:");
+            mcMMO.p.debug("      Level " + newLevel);
+            mcMMO.p.debug("      XP " + newXPlevel);
+            mcMMO.p.debug("------------------------------------------------------------------------");
+
+            profile.modifySkill(skillType, newLevel);
+            profile.setSkillXpLevel(skillType, newXPlevel);
+        }
+    }
+}

+ 11 - 0
src/main/java/com/gmail/nossr50/util/commands/CommandRegistrationManager.java

@@ -21,6 +21,7 @@ import com.gmail.nossr50.commands.database.McpurgeCommand;
 import com.gmail.nossr50.commands.database.McremoveCommand;
 import com.gmail.nossr50.commands.database.MmoshowdbCommand;
 import com.gmail.nossr50.commands.database.MmoupdateCommand;
+import com.gmail.nossr50.commands.database.McconvertCommand;
 import com.gmail.nossr50.commands.experience.AddlevelsCommand;
 import com.gmail.nossr50.commands.experience.AddxpCommand;
 import com.gmail.nossr50.commands.experience.MmoeditCommand;
@@ -291,6 +292,15 @@ public final class CommandRegistrationManager {
         command.setExecutor(new MmoshowdbCommand());
     }
 
+    private static void registerMcconvertCommand() {
+        PluginCommand command = mcMMO.p.getCommand("mcconvert");
+        command.setDescription(LocaleLoader.getString("Commands.Description.mcconvert"));
+        command.setPermission("mcmmo.commands.mcconvert");
+        command.setPermissionMessage(permissionsMessage);
+        command.setUsage(LocaleLoader.getString("Commands.Usage.0", "mcconvert"));
+        command.setExecutor(new McconvertCommand());
+    }
+
     private static void registerAdminChatCommand() {
         PluginCommand command = mcMMO.p.getCommand("adminchat");
         command.setDescription(LocaleLoader.getString("Commands.Description.adminchat"));
@@ -432,6 +442,7 @@ public final class CommandRegistrationManager {
         registerMcremoveCommand();
         registerMmoupdateCommand();
         registerMmoshowdbCommand();
+        registerMcconvertCommand();
 
         // Experience Commands
         registerAddlevelsCommand();

+ 192 - 0
src/main/java/com/gmail/nossr50/util/experience/FormulaManager.java

@@ -0,0 +1,192 @@
+package com.gmail.nossr50.util.experience;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.bukkit.configuration.file.YamlConfiguration;
+
+import com.gmail.nossr50.config.Config;
+import com.gmail.nossr50.config.experience.ExperienceConfig;
+import com.gmail.nossr50.datatypes.experience.FormulaType;
+import com.gmail.nossr50.datatypes.skills.SkillType;
+import com.gmail.nossr50.mcMMO;
+
+public class FormulaManager {
+    private final mcMMO plugin;
+    private static String formulaFilePath = mcMMO.getFlatFileDirectory() + "formula.yml";
+    private static File formulaFile = new File(formulaFilePath);
+
+    // Experience needed to reach a level, cached values to improve conversion speed
+    private final Map<Integer, Integer> experienceNeededLinear = new HashMap<Integer, Integer>();
+    private final Map<Integer, Integer> experienceNeededExponential = new HashMap<Integer, Integer>();
+
+    private FormulaType previousFormula;
+
+    public FormulaManager(final mcMMO plugin) {
+        this.plugin = plugin;
+
+        loadFormula();
+    }
+
+    /**
+     * Get the formula type that was used before converting
+     *
+     * @return previously used formula type
+     */
+    public FormulaType getPreviousFormulaType() {
+        return previousFormula;
+    }
+
+    /**
+     * Set the formula type that was used before converting
+     *
+     * @param previousFormulaType The {@link FormulaType} previously used
+     */
+    public void setPreviousFormulaType(FormulaType previousFormulaType) {
+        this.previousFormula = previousFormulaType;
+    }
+
+    /**
+     * Calculate the total amount of experience earned based on
+     * the amount of levels and experience, using the previously
+     * used formula type.
+     *
+     * @param oldExperienceValues level and experience amount
+     * @return The total amount of experience
+     */
+    public int calculateTotalExperience(int[] oldExperienceValues) {
+        int totalXP = 0;
+
+        int skillLevel = oldExperienceValues[0];
+        int skillXPLevel = oldExperienceValues[1];
+
+        for (int level = 0; level < skillLevel; level++) {
+            totalXP += getCachedXpToLevel(level, previousFormula);
+        }
+        totalXP += skillXPLevel;
+
+        return totalXP;
+    }
+
+    /**
+     * Calculate how many levels a player should have using
+     * the new formula type.
+     *
+     * @param skillType skill where new levels and experience are calculated for
+     * @param experience total amount of experience
+     * @param formulaType The new {@link FormulaType}
+     * @return the amount of levels and experience
+     */
+    public int[] calculateNewLevel(SkillType skillType, int experience, FormulaType formulaType) {
+        int[] newExperienceValues = new int[2];
+        int newLevel = 0;
+        int remainder = 0;
+        int maxLevel = Config.getInstance().getLevelCap(skillType);
+
+        while (experience > 0 && newLevel < maxLevel) {
+            int experienceToNextLevel = getCachedXpToLevel(newLevel, formulaType);
+            if (experience - experienceToNextLevel >= 0) {
+                newLevel++;
+                experience -= experienceToNextLevel;
+            }
+            else {
+                remainder = experience;
+                break;
+            }
+        }
+        newExperienceValues[0] = newLevel;
+        newExperienceValues[1] = remainder;
+        return newExperienceValues;
+    }
+
+    /**
+     * Get the cached amount of experience needed to reach the next level,
+     * if cache doesn't contain the given value it is calculated and added
+     * to the cached data.
+     *
+     * @param level level to check
+     * @param formulaType The {@link FormulaType} used
+     * @return amount of experience needed to reach next level
+     */
+    public int getCachedXpToLevel(int level, FormulaType formulaType) {
+        int experience;
+
+        switch (formulaType) {
+            case UNKNOWN:
+            case LINEAR:
+                if (experienceNeededLinear.containsKey(level)) {
+                    experience = experienceNeededLinear.get(level);
+                    return experience;
+                }
+
+                double multiplier = ExperienceConfig.getInstance().getLinearMultiplier();
+                if (multiplier <= 0) {
+                    multiplier = 20;
+                }
+
+                experience = (int) Math.floor(ExperienceConfig.getInstance().getLinearBase() + level * multiplier);
+                experienceNeededLinear.put(level, experience);
+                return experience;
+
+            case EXPONENTIAL:
+                if (experienceNeededExponential.containsKey(level)) {
+                    experience = experienceNeededExponential.get(level);
+                    return experience;
+                }
+
+                multiplier = ExperienceConfig.getInstance().getExponentialMultiplier();
+                double exponent = ExperienceConfig.getInstance().getExponentialExponent();
+                int base = ExperienceConfig.getInstance().getExponentialBase();
+
+                if (multiplier <= 0) {
+                    multiplier = 0.1;
+                }
+
+                if (exponent <= 0) {
+                    exponent = 1.80;
+                }
+
+                experience = (int) Math.floor(multiplier * Math.pow(level, exponent) + base);
+                experienceNeededExponential.put(level, experience);
+                return experience;
+
+            default:
+                return 0;
+        }
+    }
+
+    /**
+     * Load formula file.
+     */
+    public void loadFormula() {
+        if (!formulaFile.exists()) {
+            previousFormula = FormulaType.UNKNOWN;
+            return;
+        }
+
+        YamlConfiguration formulasFile = YamlConfiguration.loadConfiguration(formulaFile);
+
+        previousFormula = FormulaType.getFormulaType(formulasFile.getString("Previous_Formula", "UNKNOWN"));
+    }
+
+    /**
+     * Save formula file.
+     */
+    public void saveFormula() {
+        if (formulaFile.exists()) {
+            formulaFile.delete();
+        }
+
+        YamlConfiguration formulasFile = new YamlConfiguration();
+
+        formulasFile.set("Previous_Formula", previousFormula.toString());
+
+        try {
+            formulasFile.save(formulaFile);
+        }
+        catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+}

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

@@ -294,27 +294,6 @@ Experience:
             Global: 1.0
     PVP:
         Rewards: true
-    Formula:
-        Curve_Modifier: 20
-
-        # Cumulative experience curves will use a players power level instead of their skill level,
-        # players with high power levels will have to gain a lot more experience to reach the next level in every skill.
-        Cumulative_Curve: false
-
-        # Experience gained will get divided by these values. 1.0 by default, 2.0 means two times lower
-        Modifier:
-            Swords: 1.0
-            Taming: 1.0
-            Acrobatics: 1.0
-            Excavation: 1.0
-            Herbalism: 1.0
-            Unarmed: 1.0
-            Woodcutting: 1.0
-            Mining: 1.0
-            Archery: 1.0
-            Axes: 1.0
-            Repair: 1.0
-            Fishing: 1.0
     Fishing:
         Base: 800
     Excavation:

+ 38 - 0
src/main/resources/experienceFormula.yml

@@ -0,0 +1,38 @@
+Experience_Formula:
+    # Valid values are: LINEAR and EXPONENTIAL
+    # If an invalid value is entered, this will reset to the default setting, which is LINEAR
+    # LINEAR:      base + (level * multiplier)
+    # EXPONENTIAL: multiplier * level ^ exponent + base
+    Curve: LINEAR
+
+    # If invalid values are entered mcMMO will use the default values instead and print an error in the console
+    Linear_Values:
+        base: 1020
+        multiplier: 20
+    Exponential_Values:
+        multiplier: 0.1
+        exponent: 1.80
+        base: 2000
+
+    # Cumulative experience curves will use a players power level instead of their skill level,
+    # players with high power levels will have to gain a lot more experience to reach the next level in every skill.
+    Cumulative_Curve: false
+
+    # Experience gained will get divided by these values. 1.0 by default, 2.0 means two times lower
+    Modifier:
+        Swords: 1.0
+        Taming: 1.0
+        Acrobatics: 1.0
+        Excavation: 1.0
+        Herbalism: 1.0
+        Unarmed: 1.0
+        Woodcutting: 1.0
+        Mining: 1.0
+        Archery: 1.0
+        Axes: 1.0
+        Repair: 1.0
+        Fishing: 1.0
+
+Conversion:
+    # Old experience will get divided by this modifier
+    Exp_Modifier: 1

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

@@ -447,6 +447,9 @@ Commands.mmoupdate.InvalidType=[[RED]]{0} is not a valid database type.
 Commands.mmoupdate.Start=[[GRAY]]Starting conversion from {0} to {1}...
 Commands.mmoupdate.Finish=[[GRAY]]Database migration complete; the {1} database now has all data from the {0} database.
 Commands.mmoshowdb=[[YELLOW]]The currently used database is [[GREEN]]{0}
+Commands.mcconvert.Same=[[RED]]Already using formula type {0}
+Commands.mcconvert.Start=[[GRAY]]Starting conversion from {0} to {1} curve
+Commands.mcconvert.Finish=[[GRAY]]Formula conversion complete; now using an {0} XP curve.
 Commands.ModDescription=[[RED]]- Read brief mod description
 Commands.NoConsole=This command does not support console usage.
 Commands.Notifications.Off=Ability notifications toggled [[RED]]off
@@ -755,6 +758,7 @@ Commands.Description.mcstats=Show your mcMMO levels and XP
 Commands.Description.mctop=Show mcMMO leader boards
 Commands.Description.mmoedit=Edit mcMMO levels for a user
 Commands.Description.mmoupdate=Migrate mcMMO database from an old database into the current one
+Commands.Description.mcconvert=Converts formula types
 Commands.Description.mmoshowdb=Show the name of the current database type (for later use with /mmoupdate)
 Commands.Description.party=Control various mcMMO party settings
 Commands.Description.partychat=Toggle mcMMO party chat on/off or send party chat messages

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

@@ -61,6 +61,8 @@ commands:
         description: Migrate mcMMO database from an old database type to the current
     mmoshowdb:
         description: Show the name of the current database type (for later use with /mmoupdate)
+    mcconvert:
+        description: Convert from linear to exponential formula
     partychat:
         aliases: [pc, p]
         description: Toggle Party chat or send party chat messages