|
1 | | -//CREDIT: https://github.com/SkyCrypt/SkyCryptWebsite (Modified) |
2 | | -const xp_tables = require("./xp_tables.js"); |
3 | | - |
4 | | -module.exports = function calcSkill(skill, experience, type) { |
5 | | - let table = "normal"; |
6 | | - if (skill === "runecrafting") table = "runecrafting"; |
7 | | - if (skill === "social") table = "social"; |
8 | | - if (skill === "dungeoneering") table = "catacombs"; |
9 | | - if (skill === "hotm") table = "hotm"; |
10 | | - |
11 | | - if (experience <= 0) { |
12 | | - return { |
13 | | - totalXp: 0, |
14 | | - xp: 0, |
15 | | - level: 0, |
16 | | - xpCurrent: 0, |
17 | | - xpForNext: xp_tables[table][0], |
18 | | - progress: 0 |
19 | | - }; |
| 1 | +const { cropTables, skillTables } = require("./leveling.js"); |
| 2 | + |
| 3 | +/** |
| 4 | + * Gets the xp table for the given type. |
| 5 | + * @param {string} [type='default'] The skill table type. Defaults to the "default" table for skills. |
| 6 | + * @returns {number[]} xp table |
| 7 | + */ |
| 8 | +function getXpTable(type = "default") { |
| 9 | + return cropTables[type] ?? skillTables[type] ?? skillTables.default; |
| 10 | +} |
| 11 | + |
| 12 | +/** |
| 13 | + * Gets the level and some other information from an xp amount. |
| 14 | + * @param {number} xp The experience points to calculate level information from. |
| 15 | + * @param {Object} [extra={}] Additional options for level calculation. |
| 16 | + * @param {string} [extra.type] The ID of the skill (used to determine xp table and default cap). |
| 17 | + * @param {number} [extra.cap] Override for the highest level the player can reach. |
| 18 | + */ |
| 19 | +function getLevelByXp(xp, extra = {}) { |
| 20 | + const xpTable = getXpTable(extra.type); |
| 21 | + |
| 22 | + if (typeof xp !== "number" || isNaN(xp)) { |
| 23 | + xp = 0; |
20 | 24 | } |
21 | | - let xp = 0; |
22 | | - let level = 0; |
23 | | - let xpForNext = 0; |
24 | | - let progress = 0; |
25 | | - let maxLevel = 0; |
26 | 25 |
|
27 | | - if (xp_tables.max_levels[skill]) maxLevel = xp_tables.max_levels[skill]; |
| 26 | + /** the level that this player is caped at */ |
| 27 | + const levelCap = |
| 28 | + extra.cap ?? skillTables.defaultSkillCaps[extra.type] ?? Math.max(...Object.keys(xpTable).map(Number)); |
| 29 | + |
| 30 | + /** the level ignoring the cap and using only the table */ |
| 31 | + let uncappedLevel = 0; |
28 | 32 |
|
29 | | - for (let i = 1; i <= maxLevel; i++) { |
30 | | - xp += xp_tables[table][i - 1]; |
| 33 | + /** the amount of xp over the amount required for the level (used for calculation progress to next level) */ |
| 34 | + let xpCurrent = xp; |
31 | 35 |
|
32 | | - if (xp > experience) { |
33 | | - xp -= xp_tables[table][i - 1]; |
34 | | - } else { |
35 | | - if (i <= maxLevel) level = i; |
| 36 | + /** like xpCurrent but ignores cap */ |
| 37 | + let xpRemaining = xp; |
| 38 | + |
| 39 | + while (xpTable[uncappedLevel + 1] <= xpRemaining) { |
| 40 | + uncappedLevel++; |
| 41 | + xpRemaining -= xpTable[uncappedLevel]; |
| 42 | + if (uncappedLevel <= levelCap) { |
| 43 | + xpCurrent = xpRemaining; |
36 | 44 | } |
37 | 45 | } |
38 | 46 |
|
39 | | - if (skill === "dungeoneering") { |
40 | | - level += Math.floor((experience - xp) / 200_000_000); |
41 | | - xp += Math.floor((experience - xp) / 200_000_000) * 200_000_000; |
| 47 | + /** Whether the skill has infinite leveling (dungeoneering and skyblock level) */ |
| 48 | + const isInfiniteLevelable = skillTables.infiniteLeveling.includes(extra.type); |
| 49 | + |
| 50 | + /** adds support for infinite leveling (dungeoneering and skyblock level) */ |
| 51 | + if (isInfiniteLevelable) { |
| 52 | + const maxExperience = Object.values(xpTable).at(-1); |
42 | 53 |
|
43 | | - xpForNext = 200000000; |
| 54 | + uncappedLevel += Math.floor(xpRemaining / maxExperience); |
| 55 | + xpRemaining %= maxExperience; |
| 56 | + xpCurrent = xpRemaining; |
44 | 57 | } |
45 | 58 |
|
46 | | - const xpCurrent = Math.floor(experience - xp); |
| 59 | + /** the maximum level that any player can achieve (used for gold progress bars) */ |
| 60 | + const maxLevel = isInfiniteLevelable |
| 61 | + ? Math.max(uncappedLevel, levelCap) |
| 62 | + : skillTables.maxedSkillCaps[extra.type] ?? levelCap; |
47 | 63 |
|
48 | | - const totalXp = experience; |
| 64 | + /** the level as displayed by in game UI */ |
| 65 | + const level = isInfiniteLevelable ? uncappedLevel : Math.min(levelCap, uncappedLevel); |
49 | 66 |
|
50 | | - if (level < maxLevel) { |
51 | | - xpForNext = Math.ceil(xp_tables[table][level] || 200000000); |
52 | | - } |
| 67 | + /** the amount amount of xp needed to reach the next level (used for calculation progress to next level) */ |
| 68 | + const xpForNext = |
| 69 | + level < maxLevel |
| 70 | + ? Math.ceil(xpTable[level + 1] ?? Object.values(xpTable).at(-1)) |
| 71 | + : isInfiniteLevelable |
| 72 | + ? Object.values(xpTable).at(-1) |
| 73 | + : Infinity; |
53 | 74 |
|
54 | | - progress = level >= maxLevel && skill !== "dungeoneering" ? 0 : Math.max(0, Math.min(xpCurrent / xpForNext, 1)); |
| 75 | + /** the fraction of the way toward the next level */ |
| 76 | + const progress = level >= maxLevel && !isInfiniteLevelable ? 0 : Math.max(0, Math.min(xpCurrent / xpForNext, 1)); |
| 77 | + |
| 78 | + /** a floating point value representing the current level for example if you are half way to level 5 it would be 4.5 */ |
| 79 | + const levelWithProgress = isInfiniteLevelable |
| 80 | + ? uncappedLevel + progress |
| 81 | + : Math.min(uncappedLevel + progress, levelCap); |
| 82 | + |
| 83 | + /** a floating point value representing the current level ignoring the in-game unlockable caps for example if you are half way to level 5 it would be 4.5 */ |
| 84 | + const unlockableLevelWithProgress = extra.cap ? Math.min(uncappedLevel + progress, maxLevel) : levelWithProgress; |
| 85 | + |
| 86 | + /** the amount of xp needed to max out the skill */ |
| 87 | + const maxExperience = getSkillExperience(extra.type, levelCap); |
55 | 88 |
|
56 | 89 | return { |
57 | | - totalXp, |
58 | 90 | xp, |
59 | 91 | level, |
| 92 | + maxLevel, |
60 | 93 | xpCurrent, |
61 | 94 | xpForNext, |
62 | 95 | progress, |
63 | | - levelWithProgress: level + progress || 0 |
| 96 | + levelCap, |
| 97 | + uncappedLevel, |
| 98 | + levelWithProgress, |
| 99 | + unlockableLevelWithProgress, |
| 100 | + maxExperience |
64 | 101 | }; |
| 102 | +} |
| 103 | + |
| 104 | +/** |
| 105 | + * Calculates the average skill level for a player's profile data. |
| 106 | + * |
| 107 | + * @param {Object} profileData The player's profile data. |
| 108 | + * @param {Object} hypixelPlayer The player's Hypixel data. |
| 109 | + * @param {Object} [options] Additional options for calculating the average. |
| 110 | + * @param {number} [options.decimals=2] The number of decimal places to round the average to. Default is 1. |
| 111 | + * @param {boolean} [options.progress=false] Whether to include progress towards the next level in the calculation. Default is false. |
| 112 | + * @param {boolean} [options.cosmetic=false] Whether to include cosmetic skills in the calculation. Default is false. |
| 113 | + * @returns {string} The average skill level rounded to the specified number of decimal places. |
| 114 | + */ |
| 115 | +function getSkillAverage(profileData, hypixelPlayer, options = { decimals: 2, progress: false, cosmetic: false }) { |
| 116 | + const skillLevelCaps = getSkillLevelCaps(profileData, hypixelPlayer); |
| 117 | + |
| 118 | + let totalLevel = 0; |
| 119 | + for (const skillId of skillTables.skills) { |
| 120 | + if (!options.cosmetic && skillTables.cosmeticSkills.includes(skillId)) { |
| 121 | + continue; |
| 122 | + } |
| 123 | + |
| 124 | + const skill = getLevelByXp(profileData.player_data?.experience?.[`SKILL_${skillId.toUpperCase()}`], { |
| 125 | + type: skillId, |
| 126 | + cap: skillLevelCaps[skillId] |
| 127 | + }); |
| 128 | + |
| 129 | + totalLevel += options.progress ? skill.levelWithProgress : skill.level; |
| 130 | + } |
| 131 | + |
| 132 | + const average = |
| 133 | + totalLevel / |
| 134 | + skillTables.skills.filter((skill) => !(!options.cosmetic && skillTables.cosmeticSkills.includes(skill))).length; |
| 135 | + |
| 136 | + return average.toFixed(options.decimals); |
| 137 | +} |
| 138 | + |
| 139 | +/** |
| 140 | + * Calculates the skill level caps for different skills based on the profile data and Hypixel player data. |
| 141 | + * @param {Object} profileData The profile data containing information about the player's skills. |
| 142 | + * @param {Object} hypixelPlayer The Hypixel player data containing information about the player's achievements. |
| 143 | + * @returns {Object} An object containing the skill level caps for farming, taming, and runecrafting. |
| 144 | + */ |
| 145 | +function getSkillLevelCaps(profileData, hypixelPlayer) { |
| 146 | + return { |
| 147 | + farming: 50 + (profileData.jacobs_contest?.perks?.farming_level_cap || 0), |
| 148 | + taming: 50 + (profileData.pets_data?.pet_care?.pet_types_sacrificed?.length || 0), |
| 149 | + runecrafting: 25 // hypixelPlayer?.newPackageRank ? 25 : 3 |
| 150 | + }; |
| 151 | +} |
| 152 | + |
| 153 | +/** |
| 154 | + * Calculates the total experience required to reach a certain level in a skill. |
| 155 | + * @param {string} skill The ID of the skill used to determine the xp table. |
| 156 | + * @param {number} level The target level. |
| 157 | + * @returns {number} The total experience required. |
| 158 | + */ |
| 159 | +function getSkillExperience(skill, level) { |
| 160 | + const skillTable = getXpTable(skill); |
| 161 | + |
| 162 | + return Object.entries(skillTable).reduce((acc, [key, value]) => (key <= level ? acc + value : acc), 0); |
| 163 | +} |
| 164 | + |
| 165 | +/** |
| 166 | + * Calculates the total social skill experience for a given profile. |
| 167 | + * @param {Object} profile The profile object containing skill data. |
| 168 | + * @returns {number} The total social skill experience. |
| 169 | + */ |
| 170 | +function getSocialSkillExperience(profile) { |
| 171 | + return Object.keys(profile.members).reduce((acc, member) => { |
| 172 | + return acc + (profile.members[member]?.player_data?.experience?.SKILL_SOCIAL || 0); |
| 173 | + }, 0); |
| 174 | +} |
| 175 | + |
| 176 | +module.exports = { |
| 177 | + getSkillAverage, |
| 178 | + getLevelByXp, |
| 179 | + getXpTable, |
| 180 | + getSkillLevelCaps, |
| 181 | + getSkillExperience, |
| 182 | + getSocialSkillExperience |
65 | 183 | }; |
0 commit comments