Refactor combat level calculations to use closed-form formulas. (#8874)

* Refactor combat level calculations to use closed-form formulas.

Also move most calculations to the Experience utility class.
Fixes #7411.

* Add new test for magic levels that barely reach the next combat level.

* Add another test that breaks on master.
This commit is contained in:
emerald000
2019-05-20 10:31:16 -04:00
committed by Adam
parent 8890d5b634
commit 8026785f16
3 changed files with 396 additions and 286 deletions

View File

@@ -24,9 +24,6 @@
*/
package net.runelite.api;
import static java.lang.Math.floor;
import static java.lang.Math.max;
/**
* A utility class used for calculating experience related values.
* <p>
@@ -127,6 +124,15 @@ public class Experience
return high + 1;
}
private static double getMeleeRangeOrMagicCombatLevelContribution(int attackLevel, int strengthLevel, int magicLevel, int rangeLevel)
{
double melee = 0.325 * (attackLevel + strengthLevel);
double range = 0.325 * (Math.floor(rangeLevel / 2) + rangeLevel);
double magic = 0.325 * (Math.floor(magicLevel / 2) + magicLevel);
return Math.max(melee, Math.max(range, magic));
}
/**
* Calculates a non-virtual high-precision combat level without integer
* rounding.
@@ -146,13 +152,11 @@ public class Experience
int defenceLevel, int hitpointsLevel, int magicLevel,
int rangeLevel, int prayerLevel)
{
double base = 0.25 * (defenceLevel + hitpointsLevel + floor(prayerLevel / 2));
double base = 0.25 * (defenceLevel + hitpointsLevel + Math.floor(prayerLevel / 2));
double melee = 0.325 * (attackLevel + strengthLevel);
double range = 0.325 * (floor(rangeLevel / 2) + rangeLevel);
double magic = 0.325 * (floor(magicLevel / 2) + magicLevel);
double typeContribution = getMeleeRangeOrMagicCombatLevelContribution(attackLevel, strengthLevel, magicLevel, rangeLevel);
return base + max(melee, max(range, magic));
return base + typeContribution;
}
/**
@@ -173,4 +177,101 @@ public class Experience
{
return (int) getCombatLevelPrecise(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, magicLevel, rangeLevel, prayerLevel);
}
/**
* Calculate number of attack/strength levels required to increase combat level.
*
* @param attackLevel the attack level
* @param strengthLevel the strength level
* @param defenceLevel the defence level
* @param hitpointsLevel the hitpoints level
* @param magicLevel the magic level
* @param rangeLevel the range level
* @param prayerLevel the prayer level
* @return the number of levels required
*/
public static int getNextCombatLevelMelee(int attackLevel, int strengthLevel, int defenceLevel, int hitpointsLevel,
int magicLevel, int rangeLevel, int prayerLevel)
{
int nextCombatLevel = Experience.getCombatLevel(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, magicLevel, rangeLevel, prayerLevel) + 1;
return (int) Math.ceil(-10. / 13 * (defenceLevel + hitpointsLevel + Math.floor(prayerLevel / 2) - 4 * nextCombatLevel)) - strengthLevel - attackLevel;
}
/**
* Calculate number of hitpoints/defence levels required to increase combat level.
*
* @param attackLevel the attack level
* @param strengthLevel the strength level
* @param defenceLevel the defence level
* @param hitpointsLevel the hitpoints level
* @param magicLevel the magic level
* @param rangeLevel the range level
* @param prayerLevel the prayer level
* @return the number of levels required
*/
public static int getNextCombatLevelHpDef(int attackLevel, int strengthLevel, int defenceLevel, int hitpointsLevel,
int magicLevel, int rangeLevel, int prayerLevel)
{
int nextCombatLevel = Experience.getCombatLevel(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, magicLevel, rangeLevel, prayerLevel) + 1;
double typeContribution = Experience.getMeleeRangeOrMagicCombatLevelContribution(attackLevel, strengthLevel, magicLevel, rangeLevel);
return (int) Math.ceil(4 * nextCombatLevel - Math.floor(prayerLevel / 2) - 4 * typeContribution) - hitpointsLevel - defenceLevel;
}
/**
* Calculate number of magic levels required to increase combat level.
*
* @param attackLevel the attack level
* @param strengthLevel the strength level
* @param defenceLevel the defence level
* @param hitpointsLevel the hitpoints level
* @param magicLevel the magic level
* @param rangeLevel the range level
* @param prayerLevel the prayer level
* @return the number of levels required
*/
public static int getNextCombatLevelMagic(int attackLevel, int strengthLevel, int defenceLevel, int hitpointsLevel,
int magicLevel, int rangeLevel, int prayerLevel)
{
int nextCombatLevel = Experience.getCombatLevel(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, magicLevel, rangeLevel, prayerLevel) + 1;
return (int) Math.ceil(2. / 3 * Math.ceil(-10. / 13 * (hitpointsLevel + defenceLevel - 4 * nextCombatLevel + Math.floor(prayerLevel / 2)))) - magicLevel;
}
/**
* Calculate number of ranged levels required to increase combat level.
*
* @param attackLevel the attack level
* @param strengthLevel the strength level
* @param defenceLevel the defence level
* @param hitpointsLevel the hitpoints level
* @param magicLevel the magic level
* @param rangeLevel the range level
* @param prayerLevel the prayer level
* @return the number of levels required
*/
public static int getNextCombatLevelRange(int attackLevel, int strengthLevel, int defenceLevel, int hitpointsLevel,
int magicLevel, int rangeLevel, int prayerLevel)
{
int nextCombatLevel = Experience.getCombatLevel(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, magicLevel, rangeLevel, prayerLevel) + 1;
return (int) Math.ceil(2. / 3 * Math.ceil(-10. / 13 * (hitpointsLevel + defenceLevel - 4 * nextCombatLevel + Math.floor(prayerLevel / 2)))) - rangeLevel;
}
/**
* Calculate number of prayer levels required to increase combat level.
*
* @param attackLevel the attack level
* @param strengthLevel the strength level
* @param defenceLevel the defence level
* @param hitpointsLevel the hitpoints level
* @param magicLevel the magic level
* @param rangeLevel the range level
* @param prayerLevel the prayer level
* @return the number of levels required
*/
public static int getNextCombatLevelPrayer(int attackLevel, int strengthLevel, int defenceLevel, int hitpointsLevel,
int magicLevel, int rangeLevel, int prayerLevel)
{
int nextCombatLevel = Experience.getCombatLevel(attackLevel, strengthLevel, defenceLevel, hitpointsLevel, magicLevel, rangeLevel, prayerLevel) + 1;
double typeContribution = Experience.getMeleeRangeOrMagicCombatLevelContribution(attackLevel, strengthLevel, magicLevel, rangeLevel);
return 2 * (int) Math.ceil(-hitpointsLevel - defenceLevel + 4 * nextCombatLevel - 4 * typeContribution) - prayerLevel;
}
}