<commit_before><commit_msg>add Exp utility<commit_after>/*
 * This file is part of NucleusFramework for Bukkit, licensed under the MIT License (MIT).
 *
 * Copyright (c) JCThePants (www.jcwhatever.com)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package com.jcwhatever.nucleus.utils.player;

import com.jcwhatever.nucleus.utils.ArrayUtils;
import com.jcwhatever.nucleus.utils.PreCon;
import org.bukkit.entity.Player;

import java.util.Arrays;

/**
 * Static player exp utilities.
 */
public final class Exp {

    private Exp() {}

    private static final int MAX_LEVELS = 21863;
    private static final int[] EXP_LEVEL_LOOKUP = new int[MAX_LEVELS / 4];

    static {

        for (int i=0; i < EXP_LEVEL_LOOKUP.length; i++) {
            EXP_LEVEL_LOOKUP[i] = calculateExpInLevel(i);
        }
    }

    /**
     * Get a players total exp.
     *
     * @param player  The player.
     */
    public static double get(Player player) {
        PreCon.notNull(player);

        int level = player.getLevel();
        float exp = player.getExp();

        return get(level) + exp;
    }

    /**
     * Set a players total experience.
     *
     * @param player  The player.
     * @param exp     The experience.
     */
    public static void set(Player player, double exp) {
        PreCon.notNull(player);
        PreCon.positiveNumber(exp);

        double current = get(player);

        int level = getLevel(exp);
        double lvlExp = get(level);

        player.setLevel(level);

        if (exp > current) {
            player.setTotalExperience((int) (player.getTotalExperience() + (exp - current)));
        }
        else {
            // causes client update
            player.setTotalExperience(player.getTotalExperience());
        }

        double capacity = getBarCapacity(level);
        double expPercent = (exp - lvlExp) / capacity;
        player.setExp((float) expPercent);
    }

    /**
     * Increment a players exp by the specified amount.
     *
     * <p>Negative numbers are allowed.</p>
     *
     * @param player  The player.
     * @param exp     The amount of experience to add or subtract.
     */
    public static void increment(Player player, double exp) {
        PreCon.notNull(player);

        if (Double.compare(exp, 0.0D) == 0)
            return;

        double current = get(player);
        current += exp;

        set(player, Math.max(0.0D, current));
    }

    /**
     * Get the total Exp required to reach the given level.
     *
     * @param level  The exp level.
     */
    public static double get(int level) {
        PreCon.positiveNumber(level);

        if (level >= EXP_LEVEL_LOOKUP.length)
            return calculateExpInLevel(level);

        return EXP_LEVEL_LOOKUP[level];
    }

    /**
     * Get the exp level of the specified amount of experience.
     *
     * @param exp  The experience.
     */
    public static int getLevel(double exp) {
        PreCon.positiveNumber(exp);

        if (Double.compare(exp, 0.0D) == 0)
            return 0;

        if (exp > ArrayUtils.last(EXP_LEVEL_LOOKUP)) {
            return calculateLevel((int)exp);
        }

        int insertion = Arrays.binarySearch(EXP_LEVEL_LOOKUP, (int) exp);
        return insertion < 0 ? -(insertion) - 2 : insertion;
    }

    /**
     * Set a players experience to the amount required for the
     * specified level.
     *
     * @param player  The player.
     * @param level   The level.
     */
    public static void setLevel(Player player, int level) {
        PreCon.notNull(player);
        PreCon.positiveNumber(level);

        double exp = get(level);
        set(player, exp);
    }

    /**
     * Increment a players level by the specified amount.
     *
     * <p>Negative numbers are allowed.</p>
     *
     * @param player  The player.
     * @param amount  The amount to add or subtract.
     */
    public static void incrementLevel(Player player, int amount) {
        PreCon.notNull(player);

        if (amount == 0)
            return;

        int level = player.getLevel();
        double currLevelExp = get(level);

        level += amount;
        level = Math.max(0, level);

        double newLevelExp = get(level);

        double deltaExp = newLevelExp - currLevelExp;

        increment(player, deltaExp);
    }

    /**
     * Get the amount of Exp that can be held in the players experience
     * bar at the given level.
     *
     * @param level  The exp level.
     */
    public static double getBarCapacity(int level) {
        PreCon.positiveNumber(level);

        // algorithm found at NMS: EntityHuman.getExpToLevel()
        if (level >= 30) {
            return 112 + (level - 30) * 9;
        }
        else if (level >= 15) {
            return 37 + (level - 15) * 5;
        }
        else {
            return 7 + level * 2;
        }
    }

    private static int calculateExpInLevel(int level) {
        int current = 0;
        int exp = 0;

        while (current < level) {
            exp += getBarCapacity(current);
            current++;
        }

        return exp;
    }

    private static int calculateLevel(int exp) {
        int level = 0;
        int current = getLevel(1);

        while (current <= exp) {
            level++;
            current = getLevel(level);
        }

        return level;
    }
}
{'fraction_non_alphanumeric': 0.09527546695966096, 'fraction_numerical': 0.0064354104536179565, 'mean_word_length': 2.1420118343195265, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 12, 'https://': 0, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5556396', 'n_tokens_mistral': 1869, 'n_tokens_neox': 1748, 'n_words': 605}