aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormat <github@matdoes.dev>2022-03-27 15:50:39 -0500
committermat <github@matdoes.dev>2022-03-27 15:50:39 -0500
commitc71f5cd982f96a726ff90f930f37108f18c6f352 (patch)
treecdda3ad9cde4592a41743981f0dc3c450e70187c
parent3531a091e052c1c8554fa974ad825dc0f4d6bf09 (diff)
downloadskyblock-api-c71f5cd982f96a726ff90f930f37108f18c6f352.tar.gz
skyblock-api-c71f5cd982f96a726ff90f930f37108f18c6f352.tar.bz2
skyblock-api-c71f5cd982f96a726ff90f930f37108f18c6f352.zip
Add pets
-rw-r--r--package-lock.json14
-rw-r--r--package.json2
-rw-r--r--src/cleaners/player.ts4
-rw-r--r--src/cleaners/skyblock/member.ts4
-rw-r--r--src/cleaners/skyblock/pets.ts87
-rw-r--r--src/cleaners/skyblock/profile.ts3
-rw-r--r--src/cleaners/skyblock/skills.ts4
-rw-r--r--src/constants.ts19
-rw-r--r--src/database.ts45
-rw-r--r--src/util.ts38
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
+}