diff options
author | mat <github@matdoes.dev> | 2022-03-27 15:50:39 -0500 |
---|---|---|
committer | mat <github@matdoes.dev> | 2022-03-27 15:50:39 -0500 |
commit | c71f5cd982f96a726ff90f930f37108f18c6f352 (patch) | |
tree | cdda3ad9cde4592a41743981f0dc3c450e70187c | |
parent | 3531a091e052c1c8554fa974ad825dc0f4d6bf09 (diff) | |
download | skyblock-api-c71f5cd982f96a726ff90f930f37108f18c6f352.tar.gz skyblock-api-c71f5cd982f96a726ff90f930f37108f18c6f352.tar.bz2 skyblock-api-c71f5cd982f96a726ff90f930f37108f18c6f352.zip |
Add pets
-rw-r--r-- | package-lock.json | 14 | ||||
-rw-r--r-- | package.json | 2 | ||||
-rw-r--r-- | src/cleaners/player.ts | 4 | ||||
-rw-r--r-- | src/cleaners/skyblock/member.ts | 4 | ||||
-rw-r--r-- | src/cleaners/skyblock/pets.ts | 87 | ||||
-rw-r--r-- | src/cleaners/skyblock/profile.ts | 3 | ||||
-rw-r--r-- | src/cleaners/skyblock/skills.ts | 4 | ||||
-rw-r--r-- | src/constants.ts | 19 | ||||
-rw-r--r-- | src/database.ts | 45 | ||||
-rw-r--r-- | src/util.ts | 38 |
10 files changed, 162 insertions, 58 deletions
diff --git a/package-lock.json b/package-lock.json index c672ac4..041b688 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "prismarine-nbt": "github:PrismarineJS/prismarine-nbt", "prom-client": "^14.0.1", "queue-promise": "^2.2.1", - "typed-hypixel-api": "^0.3.2", + "typed-hypixel-api": "^0.3.3", "uuid": "^8.3.2" }, "devDependencies": { @@ -1936,9 +1936,9 @@ } }, "node_modules/typed-hypixel-api": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/typed-hypixel-api/-/typed-hypixel-api-0.3.2.tgz", - "integrity": "sha512-x1aHU7MOJxmnZSuivAipwutjWwUL8ncu3HS8qwTnlflFr2EkzYnppKWdHXZndTC219fVQDVz9eplKWPYvHCfFg==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/typed-hypixel-api/-/typed-hypixel-api-0.3.3.tgz", + "integrity": "sha512-GxPRVL59mbvAAd2DUQ7LiesNXka30WRU1FBvb5vPzNn/FKP+cHaIYtuM/EBSLkbB87w0m+lchqKTfGI4ZJ8C1w==", "dependencies": { "typescript": "^4.6.3", "undici": "^4.16.0" @@ -3623,9 +3623,9 @@ } }, "typed-hypixel-api": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/typed-hypixel-api/-/typed-hypixel-api-0.3.2.tgz", - "integrity": "sha512-x1aHU7MOJxmnZSuivAipwutjWwUL8ncu3HS8qwTnlflFr2EkzYnppKWdHXZndTC219fVQDVz9eplKWPYvHCfFg==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/typed-hypixel-api/-/typed-hypixel-api-0.3.3.tgz", + "integrity": "sha512-GxPRVL59mbvAAd2DUQ7LiesNXka30WRU1FBvb5vPzNn/FKP+cHaIYtuM/EBSLkbB87w0m+lchqKTfGI4ZJ8C1w==", "requires": { "typescript": "^4.6.3", "undici": "^4.16.0" diff --git a/package.json b/package.json index 6d6690d..1061b31 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "prismarine-nbt": "github:PrismarineJS/prismarine-nbt", "prom-client": "^14.0.1", "queue-promise": "^2.2.1", - "typed-hypixel-api": "^0.3.2", + "typed-hypixel-api": "^0.3.3", "uuid": "^8.3.2" }, "devDependencies": { diff --git a/src/cleaners/player.ts b/src/cleaners/player.ts index 3981609..f2df07d 100644 --- a/src/cleaners/player.ts +++ b/src/cleaners/player.ts @@ -20,7 +20,7 @@ export interface CleanPlayer extends CleanBasicPlayer { rank: CleanRank socials: CleanSocialMedia profiles?: CleanBasicProfile[] - skyblockClaimed?: ClaimedSkyBlockItem[] + claimed?: ClaimedSkyBlockItem[] } export async function cleanPlayerResponse(data: typedHypixelApi.PlayerDataResponse['player']): Promise<CleanPlayer | null> { @@ -33,6 +33,6 @@ export async function cleanPlayerResponse(data: typedHypixelApi.PlayerDataRespon rank: cleanRank(data), socials: cleanSocialMedia(data), profiles: cleanPlayerSkyblockProfiles(data.stats?.SkyBlock?.profiles), - skyblockClaimed: cleanPlayerSkyblockClaimed(data) + claimed: cleanPlayerSkyblockClaimed(data) } } diff --git a/src/cleaners/skyblock/member.ts b/src/cleaners/skyblock/member.ts index 364e8b3..161f5cc 100644 --- a/src/cleaners/skyblock/member.ts +++ b/src/cleaners/skyblock/member.ts @@ -15,6 +15,7 @@ import * as constants from '../../constants.js' import { Included } from '../../hypixel.js' import { CleanPlayer } from '../player.js' import { CleanRank } from '../rank.js' +import { cleanPets, Pet, PetsData } from './pets.js' export interface CleanBasicMember { uuid: string @@ -37,6 +38,7 @@ export interface CleanMember extends CleanBasicMember { zones: Zone[] collections: Collection[] slayers: SlayerData + pets: PetsData /** Whether the user left the coop */ left: boolean } @@ -87,6 +89,7 @@ export async function cleanSkyBlockProfileMemberResponse(member: typedHypixelApi zones: await cleanVisitedZones(member), collections: cleanCollections(member), slayers: cleanSlayers(member), + pets: await cleanPets(member), left: (player.profiles?.find(profile => profile.uuid === profileId) === undefined) ?? false } @@ -109,6 +112,7 @@ export interface CleanMemberProfilePlayer extends CleanPlayer { zones: Zone[] collections: Collection[] slayers: SlayerData + pets: PetsData } export interface CleanMemberProfile { diff --git a/src/cleaners/skyblock/pets.ts b/src/cleaners/skyblock/pets.ts new file mode 100644 index 0000000..d6265e5 --- /dev/null +++ b/src/cleaners/skyblock/pets.ts @@ -0,0 +1,87 @@ +import typedHypixelApi from 'typed-hypixel-api' +import { fetchPets } from '../../constants.js' +import { levelFromXpTable } from '../../util.js' + +// https://hypixel-skyblock.fandom.com/wiki/Module:Pet/LevelingData?action=edit + +const PET_LEVELS = [ + 100, 110, 120, 130, 145, 160, 175, 190, 210, 230, 250, 275, 300, 330, 360, 400, 440, 490, 540, 600, 660, 730, 800, 880, 960, 1050, 1150, 1260, 1380, 1510, 1650, 1800, 1960, 2130, 2310, 2500, 2700, 2920, 3160, 3420, 3700, 4000, 4350, 4750, 5200, 5700, 6300, 7000, 7800, 8700, 9700, 10800, 12000, 13300, 14700, 16200, 17800, 19500, 21300, 23200, 25200, 27400, 29800, 32400, 35200, 38200, 41400, 44800, 48400, 52200, 56200, 60400, 64800, 69400, 74200, 79200, 84700, 90700, 97200, 104200, 111700, 119700, 128200, 137200, 146700, 156700, 167700, 179700, 192700, 206700, 221700, 237700, 254700, 272700, 291700, 311700, 333700, 357700, 383700, 411700, 441700, 476700, 516700, 561700, 611700, 666700, 726700, 791700, 861700, 936700, 1016700, 1101700, 1191700, 1286700, 1386700, 1496700, 1616700, 1746700, 1886700 +] + +const PET_RARITY_OFFSET: Record<typedHypixelApi.Pet['tier'], number> = { + COMMON: 0, + UNCOMMON: 6, + RARE: 11, + EPIC: 16, + LEGENDARY: 20, + MYTHIC: 20 +} + +function calculateXpTable(rarity: keyof typeof PET_RARITY_OFFSET) { + const data = [0] + let current = 0 + for (let i = 0; i < 100; i++) { + current += PET_LEVELS[i + PET_RARITY_OFFSET[rarity]] + data.push(current) + } + return data +} + +const RARITY_XP_TABLES = { + COMMON: calculateXpTable('COMMON'), + UNCOMMON: calculateXpTable('UNCOMMON'), + RARE: calculateXpTable('RARE'), + EPIC: calculateXpTable('EPIC'), + LEGENDARY: calculateXpTable('LEGENDARY'), + MYTHIC: calculateXpTable('MYTHIC') +} + + +export interface Pet { + id: string + xp: number + level: number + tier: typedHypixelApi.Pet['tier'] + skin: string | null + item: string | null +} + +export interface PetsData { + active: Pet | null + list: Pet[] + missingIds: string[] +} + +export async function cleanPets(data: typedHypixelApi.SkyBlockProfileMember): Promise<PetsData> { + const obtainedPets: Pet[] = [] + let activePet: Pet | null = null + + const allPetIds = await fetchPets() + const obtainedPetIds: string[] = [] + + for (const petData of data.pets ?? []) { + const xpTable = RARITY_XP_TABLES[petData.tier] + const level = levelFromXpTable(petData.exp, xpTable) + const pet: Pet = { + id: petData.type, + // we round down xp because there's no point in showing decimals + xp: Math.floor(petData.exp), + level, + tier: petData.tier, + skin: petData.skin ?? null, + item: petData.heldItem + } + obtainedPetIds.push(pet.id) + obtainedPets.push(pet) + if (petData.active) + activePet = pet + } + + const missingPetIds = allPetIds.filter(id => !obtainedPetIds.includes(id)) + + return { + active: activePet, + list: obtainedPets, + missingIds: missingPetIds + } +}
\ No newline at end of file diff --git a/src/cleaners/skyblock/profile.ts b/src/cleaners/skyblock/profile.ts index fe9b558..04d848a 100644 --- a/src/cleaners/skyblock/profile.ts +++ b/src/cleaners/skyblock/profile.ts @@ -108,8 +108,7 @@ export async function cleanSkyblockProfileResponse<O extends ApiOptions>( minionCount: uniqueMinions, maxUniqueMinions: maxUniqueMinions ?? 0, } - // we have to do this because of the basic checking typing - return cleanFullProfile as any + return cleanFullProfile } /** A basic profile that only includes the profile uuid and name */ diff --git a/src/cleaners/skyblock/skills.ts b/src/cleaners/skyblock/skills.ts index c69acd0..a186232 100644 --- a/src/cleaners/skyblock/skills.ts +++ b/src/cleaners/skyblock/skills.ts @@ -1,4 +1,5 @@ import typedHypixelApi from 'typed-hypixel-api' +import { levelFromXpTable } from '../../util' export interface Skill { name: string @@ -131,8 +132,7 @@ const skillsDefaultMaxLevel: number = 50 */ export function levelForSkillXp(xp: number, maxLevel: number) { const xpTable = (maxLevel <= 25 ? skillXpTableEasier : skillXpTable).slice(0, maxLevel) - const skillLevel = [...xpTable].reverse().findIndex(levelXp => xp >= levelXp) - return skillLevel === -1 ? 0 : xpTable.length - skillLevel + return levelFromXpTable(xp, xpTable) } export async function cleanSkills(data: typedHypixelApi.SkyBlockProfileMember): Promise<Skill[]> { diff --git a/src/constants.ts b/src/constants.ts index 27f413b..ddc9bc3 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -223,24 +223,27 @@ export async function addSlayers(addingSlayers: string[]): Promise<void> { await constants.addJSONConstants('slayers.json', addingSlayers, 'slayer') } -/** Fetch all the known SkyBlock slayer names as an array of strings */ +/** Fetch all the known SkyBlock minion names as an array of strings */ export async function fetchMinions(): Promise<string[]> { return await constants.fetchJSONConstant('minions.json') } -export async function fetchSkillXp(): Promise<number[]> { - return await constants.fetchJSONConstant('manual/skill_xp.json') +/** Add minions to skyblock-constants. This has caching so it's fine to call many times */ +export async function addMinions(addingMinions: string[]): Promise<void> { + await constants.addJSONConstants('minions.json', addingMinions, 'minion') } -export async function fetchSkillXpEasier(): Promise<number[]> { - return await constants.fetchJSONConstant('manual/skill_xp_easier.json') +/** Fetch all the known SkyBlock pet ids as an array of strings */ +export async function fetchPets(): Promise<string[]> { + return await constants.fetchJSONConstant('pets.json') } -/** Add skills to skyblock-constants. This has caching so it's fine to call many times */ -export async function addMinions(addingMinions: string[]): Promise<void> { - await constants.addJSONConstants('minions.json', addingMinions, 'minion') +/** Add pet ids to skyblock-constants. This has caching so it's fine to call many times */ +export async function addPets(addingPets: string[]): Promise<void> { + await constants.addJSONConstants('pets.json', addingPets, 'pet') } + interface constantValues { max_minions?: number max_fairy_souls?: number diff --git a/src/database.ts b/src/database.ts index f840d7b..8f5d9d7 100644 --- a/src/database.ts +++ b/src/database.ts @@ -69,7 +69,7 @@ interface ProfileLeaderboardItem { value: number } -export const cachedRawLeaderboards: Map<string, (memberRawLeaderboardItem|profileRawLeaderboardItem)[]> = new Map() +export const cachedRawLeaderboards: Map<string, (memberRawLeaderboardItem | profileRawLeaderboardItem)[]> = new Map() const leaderboardMax = 100 const reversedLeaderboards = [ @@ -110,7 +110,7 @@ let sessionsCollection: Collection<SessionSchema> let accountsCollection: Collection<AccountSchema> -const leaderboardInfos: { [ leaderboardName: string ]: string } = { +const leaderboardInfos: { [leaderboardName: string]: string } = { highest_crit_damage: 'This leaderboard is capped at the integer limit. Look at the <a href="/leaderboard/highest_critical_damage">highest critical damage leaderboard</a> instead.', highest_critical_damage: 'uhhhhh yeah idk either', leaderboards_count: 'This leaderboard counts how many leaderboards a player is in the top 100 spot for.', @@ -134,7 +134,7 @@ async function connect(): Promise<void> { } interface StringNumber { - [ name: string]: number + [name: string]: number } function getMemberCollectionAttributes(member: CleanMember): StringNumber { @@ -202,11 +202,11 @@ function getProfileLeaderboardAttributes(profile: CleanFullProfile): StringNumbe } -export async function fetchAllLeaderboardsCategorized(): Promise<{ [ category: string ]: string[] }> { +export async function fetchAllLeaderboardsCategorized(): Promise<{ [category: string]: string[] }> { const memberLeaderboardAttributes: string[] = await fetchAllMemberLeaderboardAttributes() const profileLeaderboardAttributes: string[] = await fetchAllProfileLeaderboardAttributes() - const categorizedLeaderboards: { [ category: string ]: string[] } = {} + const categorizedLeaderboards: { [category: string]: string[] } = {} for (const leaderboard of [...memberLeaderboardAttributes, ...profileLeaderboardAttributes]) { const { category } = categorizeStat(leaderboard) if (category) { @@ -236,7 +236,7 @@ export async function fetchSlayerLeaderboards(): Promise<string[]> { for (const slayerNameRaw of rawSlayerNames) { leaderboardNames.push(`slayer_${slayerNameRaw}_total_xp`) leaderboardNames.push(`slayer_${slayerNameRaw}_total_kills`) - for (let slayerTier = 1; slayerTier <= slayerLevels; slayerTier ++) { + for (let slayerTier = 1; slayerTier <= slayerLevels; slayerTier++) { leaderboardNames.push(`slayer_${slayerNameRaw}_${slayerTier}_kills`) } } @@ -297,7 +297,7 @@ async function fetchMemberLeaderboardRaw(name: string): Promise<memberRawLeaderb if (cachedRawLeaderboards.has(name)) return cachedRawLeaderboards.get(name) as memberRawLeaderboardItem[] - + // if it's currently being fetched, check every 100ms until it's in cachedRawLeaderboards if (fetchingRawLeaderboardNames.has(name) && !cachedRawLeaderboards.get(name)) { while (true) { @@ -403,7 +403,7 @@ interface ProfileLeaderboard { export async function fetchMemberLeaderboard(name: string): Promise<MemberLeaderboard> { const leaderboardRaw = await fetchMemberLeaderboardRaw(name) - const fetchLeaderboardPlayer = async(i: memberRawLeaderboardItem): Promise<MemberLeaderboardItem> => { + const fetchLeaderboardPlayer = async (i: memberRawLeaderboardItem): Promise<MemberLeaderboardItem> => { const player = await cached.fetchBasicPlayer(i.uuid) return { player, @@ -428,7 +428,7 @@ export async function fetchMemberLeaderboard(name: string): Promise<MemberLeader export async function fetchProfileLeaderboard(name: string): Promise<ProfileLeaderboard> { const leaderboardRaw = await fetchProfileLeaderboardRaw(name) - const fetchLeaderboardProfile = async(i: profileRawLeaderboardItem): Promise<ProfileLeaderboardItem> => { + const fetchLeaderboardProfile = async (i: profileRawLeaderboardItem): Promise<ProfileLeaderboardItem> => { const players: CleanPlayer[] = [] for (const playerUuid of i.players) { const player = await cached.fetchBasicPlayer(playerUuid) @@ -454,9 +454,9 @@ export async function fetchProfileLeaderboard(name: string): Promise<ProfileLead } /** Fetch a leaderboard */ -export async function fetchLeaderboard(name: string): Promise<MemberLeaderboard|ProfileLeaderboard> { +export async function fetchLeaderboard(name: string): Promise<MemberLeaderboard | ProfileLeaderboard> { const profileLeaderboards = await fetchAllProfileLeaderboardAttributes() - let leaderboard: MemberLeaderboard|ProfileLeaderboard + let leaderboard: MemberLeaderboard | ProfileLeaderboard if (profileLeaderboards.includes(name)) { leaderboard = await fetchProfileLeaderboard(name) } else { @@ -525,7 +525,7 @@ async function getApplicableMemberLeaderboardAttributes(member: CleanMember): Pr const applicableAttributes = {} const applicableTop1Attributes = {} - for (const [ leaderboard, attributeValue ] of Object.entries(leaderboardAttributes)) { + for (const [leaderboard, attributeValue] of Object.entries(leaderboardAttributes)) { const requirement = await getLeaderboardRequirement(leaderboard, 'member') const leaderboardReversed = isLeaderboardReversed(leaderboard) if ( @@ -555,7 +555,7 @@ async function getApplicableMemberLeaderboardAttributes(member: CleanMember): Pr ) ) applicableAttributes['leaderboards_count'] = leaderboardsCount - + // add the "first leaderboards count" attribute const top1LeaderboardsCount: number = Object.keys(applicableTop1Attributes).length const top1LeaderboardsCountRequirement = await getLeaderboardRequirement('top_1_leaderboards_count', 'member') @@ -577,14 +577,14 @@ async function getApplicableProfileLeaderboardAttributes(profile: CleanFullProfi const applicableAttributes = {} const applicableTop1Attributes = {} - for (const [ leaderboard, attributeValue ] of Object.entries(leaderboardAttributes)) { + for (const [leaderboard, attributeValue] of Object.entries(leaderboardAttributes)) { const requirement = await getLeaderboardRequirement(leaderboard, 'profile') const leaderboardReversed = isLeaderboardReversed(leaderboard) if ( (requirement.top_100 === null) || ( leaderboardReversed ? attributeValue < requirement.top_100 : attributeValue > requirement.top_100 - && attributeValue !== 0 + && attributeValue !== 0 ) ) { applicableAttributes[leaderboard] = attributeValue @@ -593,7 +593,7 @@ async function getApplicableProfileLeaderboardAttributes(profile: CleanFullProfi (requirement.top_1 === null) || ( leaderboardReversed ? attributeValue < requirement.top_1 : attributeValue > requirement.top_1 - && attributeValue !== 0 + && attributeValue !== 0 ) ) { applicableTop1Attributes[leaderboard] = attributeValue @@ -620,6 +620,7 @@ export async function updateDatabaseMember(member: CleanMember, profile: CleanFu await constants.addSkills(member.skills.map(skill => skill.name)) await constants.addZones(member.zones.map(zone => zone.name)) await constants.addSlayers(member.slayers.bosses.map(s => s.rawName)) + await constants.addPets(member.pets.list.map(s => s.id)) if (debug) console.debug('done constants..') @@ -641,7 +642,7 @@ export async function updateDatabaseMember(member: CleanMember, profile: CleanFu { upsert: true } ) - for (const [ attributeName, attributeValue ] of Object.entries(leaderboardAttributes)) { + for (const [attributeName, attributeValue] of Object.entries(leaderboardAttributes)) { const existingRawLeaderboard = await fetchMemberLeaderboardRaw(attributeName) const leaderboardReverse = isLeaderboardReversed(attributeName) @@ -696,7 +697,7 @@ export async function updateDatabaseProfile(profile: CleanFullProfile): Promise< ) // add the profile to the cached leaderboard without having to refetch it - for (const [ attributeName, attributeValue ] of Object.entries(leaderboardAttributes)) { + for (const [attributeName, attributeValue] of Object.entries(leaderboardAttributes)) { const existingRawLeaderboard = await fetchProfileLeaderboardRaw(attributeName) const leaderboardReverse = isLeaderboardReversed(attributeName) @@ -729,14 +730,14 @@ export const leaderboardUpdateProfileQueue = new Queue({ export function queueUpdateDatabaseMember(member: CleanMember, profile: CleanFullProfile): void { if (recentlyQueued.get(profile.uuid + member.uuid)) return else recentlyQueued.set(profile.uuid + member.uuid, true) - leaderboardUpdateMemberQueue.enqueue(async() => await updateDatabaseMember(member, profile)) + leaderboardUpdateMemberQueue.enqueue(async () => await updateDatabaseMember(member, profile)) } /** Queue an update for the profile's leaderboard data on the server if applicable */ export function queueUpdateDatabaseProfile(profile: CleanFullProfile): void { if (recentlyQueued.get(profile.uuid + 'profile')) return else recentlyQueued.set(profile.uuid + 'profile', true) - leaderboardUpdateProfileQueue.enqueue(async() => await updateDatabaseProfile(profile)) + leaderboardUpdateProfileQueue.enqueue(async () => await updateDatabaseProfile(profile)) } @@ -801,12 +802,12 @@ export async function createSession(refreshToken: string, userData: discord.Disc } export async function fetchSession(sessionId: string): Promise<WithId<SessionSchema> | null> { - return await sessionsCollection?.findOne({ _id: sessionId as any } ) + return await sessionsCollection?.findOne({ _id: sessionId as any }) } export async function deleteSession(sessionId: string) { - return await sessionsCollection?.deleteOne({ _id: sessionId as any } ) + return await sessionsCollection?.deleteOne({ _id: sessionId as any }) } export async function fetchAccount(minecraftUuid: string): Promise<WithId<AccountSchema> | null> { diff --git a/src/util.ts b/src/util.ts index 45839cc..5490049 100644 --- a/src/util.ts +++ b/src/util.ts @@ -8,19 +8,19 @@ export function undashUuid(uuid: string): string { export function jsonToQuery(data): string { - return Object.entries(data || {}).map(e => e.join('=')).join('&') + return Object.entries(data || {}).map(e => e.join('=')).join('&') } export function shuffle<T>(a: T[]): T[] { - for (let i = a.length - 1; i > 0; i--) { - const j = Math.floor(Math.random() * (i + 1)) - ;[a[i], a[j]] = [a[j], a[i]] - } - return a + for (let i = a.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)) + ;[a[i], a[j]] = [a[j], a[i]] + } + return a } -export const minecraftColorCodes: { [ key: string ]: string } = { +export const minecraftColorCodes: { [key: string]: string } = { '0': '#000000', '1': '#0000be', '2': '#00be00', @@ -62,12 +62,12 @@ export const minecraftColorCodes: { [ key: string ]: string } = { * @param colorName The name of the color (blue, red, aqua, etc) */ export function colorCodeFromName(colorName: string): string | undefined { - const hexColor = minecraftColorCodes[colorName.toLowerCase()] - for (const key in minecraftColorCodes) { - const value = minecraftColorCodes[key] - if (key.length === 1 && value === hexColor) - return key - } + const hexColor = minecraftColorCodes[colorName.toLowerCase()] + for (const key in minecraftColorCodes) { + const value = minecraftColorCodes[key] + if (key.length === 1 && value === hexColor) + return key + } } export async function sleep(ms: number): Promise<void> { @@ -77,4 +77,14 @@ export async function sleep(ms: number): Promise<void> { /** Returns whether a string is a UUID4 (Minecraft uuid) */ export function isUuid(string: string) { return undashUuid(string).length === 32 -}
\ No newline at end of file +} + +/** + * Get a level for an amount of total xp + * @param xp The xp we're finding the level for + * @param xpTable The list of required xp values for each level, starting at 1 + */ +export function levelFromXpTable(xp: number, xpTable: number[]) { + const skillLevel = [...xpTable].reverse().findIndex(levelXp => xp >= levelXp) + return skillLevel === -1 ? 0 : xpTable.length - skillLevel +} |