[Solved] Setting and getting a player's current experience points (not levels)

Discussion in 'Spigot Plugin Development' started by Dev_Richard, Jun 19, 2015.

  1. Hi,
    Okay I am somewhat frustrated right now. I have to subtract experience points from a player when they buy something for a plugin I am developing and I can't figure it out. It seems pretty simple but I have searched Google, the Bukkit Forums, and here for the last hour to no avail. I looked at the Javadocs but they seem to be completely useless.

    For example getTotalExperience() says "Gets the players total experience points" but setTotalExperience() says "Sets the players current experience level". What the heck? So one sets the points and the other sets the level despite sharing the same name? Also setLevel() has the exact same description as setTotalExperience(). And interestingly enough this has been the case for years now. I don't get know how no one has noticed how completely inaccurate and ambiguous the javadocs are on this issue. o_O

    Also setTotalExperience has no effect on my actual experience bar, I don't know if this is because it is not updating or what. I did see another thread in which some people said that it doesn't affect your current xp at all and is actually affecting some statistic (which makes sense from the behavior but again completely contradicts the javadocs).

    So after all this ranting I still would like to know how to do this. I have seen servers that do it correctly so I know it can be done. Does anyone have any idea? Please help. :)

    Thanks in advance
     
  2. Have you tried using Player#giveExp and specifying a negative amount?
     
  3. Alright I didn't think to try that and when I saw it I figured it would work, but apparently there is a problem and if you set the negative value to a value that would subtract levels and not just XP it changes the xp bar but not the level. I saw a few ExperienceManagers on the web that people had created to do what I wanted but none of them seemed to work.

    I believe this is because the formula for experience must have changed sometime in the last year or two. So I decided to write my own, it's pretty basic but it gets the job done. All I have is a get and set total experience method. I used the formulas that were specified on the wiki.

    http://minecraft.gamepedia.com/Experience#Leveling_up

    Here is the code for future reference:
    Code (Text):
    import java.math.BigDecimal;
    import java.util.List;

    import org.bukkit.entity.Player;

    public class ExperienceManager {
       
       private Player player;
       
       public ExperienceManager(Player player) {
         this.player = player;
       }
       
       public int getTotalExperience() {
         int experience = 0;
         int level = player.getLevel();
         if(level >= 0 && level <= 15) {
           experience = (int) Math.ceil(Math.pow(level, 2) + (6 * level));
           int requiredExperience = 2 * level + 7;
           double currentExp = Double.parseDouble(Float.toString(player.getExp()));
           experience += Math.ceil(currentExp * requiredExperience);
           return experience;
         } else if(level > 15 && level <= 30) {
           experience = (int) Math.ceil((2.5 * Math.pow(level, 2) - (40.5 * level) + 360));
           int requiredExperience = 5 * level - 38;
           double currentExp = Double.parseDouble(Float.toString(player.getExp()));
           experience += Math.ceil(currentExp * requiredExperience);
           return experience;
         } else {
           experience = (int) Math.ceil(((4.5 * Math.pow(level, 2) - (162.5 * level) + 2220)));
           int requiredExperience = 9 * level - 158;
           double currentExp = Double.parseDouble(Float.toString(player.getExp()));
           experience += Math.ceil(currentExp * requiredExperience);
           return experience;      
         }
       }
       
       public void setTotalExperience(int xp) {
         //Levels 0 through 15
         if(xp >= 0 && xp < 351) {
           //Calculate Everything
           int a = 1; int b = 6; int c = -xp;
           int level = (int) (-b + Math.sqrt(Math.pow(b, 2) - (4 * a * c))) / (2 * a);
           int xpForLevel = (int) (Math.pow(level, 2) + (6 * level));
           int remainder = xp - xpForLevel;
           int experienceNeeded = (2 * level) + 7;
           float experience = (float) remainder / (float) experienceNeeded;
           experience = round(experience, 2);
           System.out.println("xpForLevel: " + xpForLevel);
           System.out.println(experience);
           
           //Set Everything
           player.setLevel(level);
           player.setExp(experience);
         //Levels 16 through 30
         } else if(xp >= 352 && xp < 1507) {
           //Calculate Everything
           double a = 2.5; double b = -40.5; int c = -xp + 360;
           double dLevel = (-b + Math.sqrt(Math.pow(b, 2) - (4 * a * c))) / (2 * a);
           int level = (int) Math.floor(dLevel);
           int xpForLevel = (int) (2.5 * Math.pow(level, 2) - (40.5 * level) + 360);
           int remainder = xp - xpForLevel;
           int experienceNeeded = (5 * level) - 38;
           float experience = (float) remainder / (float) experienceNeeded;
           experience = round(experience, 2);
           System.out.println("xpForLevel: " + xpForLevel);
           System.out.println(experience);
           
           //Set Everything
           player.setLevel(level);
           player.setExp(experience);    
         //Level 31 and greater
         } else {
           //Calculate Everything
           double a = 4.5; double b = -162.5; int c = -xp + 2220;
           double dLevel = (-b + Math.sqrt(Math.pow(b, 2) - (4 * a * c))) / (2 * a);
           int level = (int) Math.floor(dLevel);
           int xpForLevel = (int) (4.5 * Math.pow(level, 2) - (162.5 * level) + 2220);
           int remainder = xp - xpForLevel;
           int experienceNeeded = (9 * level) - 158;
           float experience = (float) remainder / (float) experienceNeeded;
           experience = round(experience, 2);
           System.out.println("xpForLevel: " + xpForLevel);
           System.out.println(experience);
           
           //Set Everything
           player.setLevel(level);
           player.setExp(experience);      
         }
       }
       
      private float round(float d, int decimalPlace) {
      BigDecimal bd = new BigDecimal(Float.toString(d));
      bd = bd.setScale(decimalPlace, BigDecimal.ROUND_HALF_DOWN);
      return bd.floatValue();
      }
    }
     
    • Winner Winner x 3
    • Like Like x 1
  4. That looks very elegant if it works- thank you for sharing? Do you mind if I were to use this?

    Have you tested it in various ways to make sure it matches up to vanilla?

    I've had all kinds of problems trying to do this and having it match up- like if I "/xp give 1000" to myself, and then use my code to subtract 1000 XP it doesn't come out quite right.

    I took a different approach of using the "getExpToLevel" method the Bukkit API to basically walk through all of the levels up to the player's current level and add up the exp in each.. but it doesn't always seem to work, and also feels really inefficient.
     
  5. Yeah it should work, I tested it with the /xp command primarily and it always worked. It does seem that the set method sets it one to many xp. (i.e if I set 1000 it sets 1001). This is probably just a rounding issue and I didn't consider it too significant as you could always just subtract one from the number you provide the set method. I would still love to fix it so if you anyone else sees what the problem please tell me.

    Anyway feel free to use this, it took me a long time to write and I would be happy if others could use this library so they didn't have to face the same struggles. :) Quite frankly I am surprised this is not a feature that is built into the API.
     
    • Agree Agree x 1
    • Like Like x 1
  6. Hello,
    i know this thread is a few days old but i have one question: What do i do with this pretty code if i have not an integer? I have to calculate with lower numbers as one, for example: 0,35; 0,1; 0,8 and so on.

    Thanks for your answers and excuse my bad english, it's not my native language. :)
     
    • Funny Funny x 1
  7. A code more functional and readable (the code above is not fully reliable)

    Code (Text):
    import org.bukkit.entity.Player;

    public class ExperienceManager {

        public static int getTotalExperience(int level) {
            int xp = 0;

            if (level >= 0 && level <= 15) {
                xp = (int) Math.round(Math.pow(level, 2) + 6 * level);
            } else if (level > 15 && level <= 30) {
                xp = (int) Math.round((2.5 * Math.pow(level, 2) - 40.5 * level + 360));
            } else if (level > 30) {
                xp = (int) Math.round(((4.5 * Math.pow(level, 2) - 162.5 * level + 2220)));
            }
            return xp;
        }

        public static int getTotalExperience(Player player) {
            return Math.round(player.getExp() * player.getExpToLevel()) + getTotalExperience(player.getLevel());
        }

        public static void setTotalExperience(Player player, int amount) {
            int level = 0;
            int xp = 0;
            float a = 0;
            float b = 0;
            float c = -amount;

            if (amount > getTotalExperience(0) && amount <= getTotalExperience(15)) {
                a = 1;
                b = 6;
            } else if (amount > getTotalExperience(15) && amount <= getTotalExperience(30)) {
                a = 2.5f;
                b = -40.5f;
                c += 360;
            } else if (amount > getTotalExperience(30)) {
                a = 4.5f;
                b = -162.5f;
                c += 2220;
            }
            level = (int) Math.floor((-b + Math.sqrt(Math.pow(b, 2) - (4 * a * c))) / (2 * a));
            xp = amount - getTotalExperience(level);
            player.setLevel(level);
            player.setExp(0);
            player.giveExp(xp);
        }
    }
     
    • Like Like x 1
    • Winner Winner x 1
    • Informative Informative x 1
    • Useful Useful x 1