From 56b72b52cc6fc7a219ba2b0cf870dede8cd7f0a7 Mon Sep 17 00:00:00 2001 From: Kath Date: Mon, 29 Jul 2024 16:55:33 +0800 Subject: [PATCH] Rewrite Info --- API/getSkyblockAuctions.ts | 85 + src/API/getAchievements.ts | 18 + src/API/getActiveHouses.ts | 18 + src/API/getBoosters.ts | 18 + src/API/getChallenges.ts | 18 + src/API/getGameCounts.ts | 18 + src/API/getGuild.ts | 30 + src/API/getGuildAchievements.ts | 18 + src/API/getHouse.ts | 20 + src/API/getLeaderboards.ts | 27 + src/API/getPlayer.ts | 24 + src/API/getPlayerHouses.ts | 22 + src/API/getQuests.ts | 18 + src/API/getRecentGames.ts | 25 + src/API/getSkyblockAuction.ts | 32 + src/API/getSkyblockAuctionsByPlayer.ts | 22 + src/API/getSkyblockBazaar.ts | 19 + src/API/getSkyblockBingo.ts | 18 + src/API/getSkyblockBingoByPlayer.ts | 22 + src/API/getSkyblockEndedAuctions.ts | 22 + src/API/getSkyblockFireSales.ts | 18 + src/API/getSkyblockGarden.ts | 18 + src/API/getSkyblockGovernment.ts | 18 + src/API/getSkyblockMember.ts | 40 + src/API/getSkyblockMuseum.ts | 26 + src/API/getSkyblockNews.ts | 18 + src/API/getSkyblockProfiles.ts | 41 + src/API/getStatus.ts | 20 + src/API/getWatchdogStats.ts | 19 + src/Client.ts | 77 + src/Errors.ts | 66 + src/Private/CacheHandler.ts | 41 + src/Private/CheckUpdates.ts | 31 + src/Private/Endpoint.ts | 14 + src/Private/Requests.ts | 57 + src/Private/uuidCache.ts | 32 + src/index.ts | 1 + src/structures/APIIncident.ts | 34 + src/structures/APIStatus.ts | 24 + src/structures/Boosters/Booster.ts | 43 + src/structures/Color.ts | 81 + src/structures/Game.ts | 41 + src/structures/GameCounts.ts | 28 + src/structures/Guild/Guild.ts | 69 + src/structures/Guild/GuildMember.ts | 36 + src/structures/Guild/GuildRank.ts | 22 + src/structures/House.ts | 27 + src/structures/ItemBytes.ts | 20 + src/structures/Leaderboard.ts | 17 + src/structures/MiniGames/Arcade.ts | 511 +++ src/structures/MiniGames/ArenaBrawl.ts | 90 + src/structures/MiniGames/BedWars.ts | 322 ++ .../MiniGames/BlitzSurvivalGames.ts | 176 + src/structures/MiniGames/BuildBattle.ts | 32 + src/structures/MiniGames/CopsAndCrims.ts | 139 + src/structures/MiniGames/Duels.ts | 475 +++ src/structures/MiniGames/MegaWalls.ts | 199 ++ src/structures/MiniGames/MurderMystery.ts | 93 + src/structures/MiniGames/Paintball.ts | 39 + src/structures/MiniGames/Pit.ts | 138 + src/structures/MiniGames/PitInventoryItem.ts | 21 + src/structures/MiniGames/Quakecraft.ts | 73 + src/structures/MiniGames/SkyWars.ts | 298 ++ src/structures/MiniGames/SmashHeroes.ts | 117 + src/structures/MiniGames/SpeedUHC.ts | 74 + src/structures/MiniGames/TNTGames.ts | 126 + src/structures/MiniGames/TurboKartRacers.ts | 62 + src/structures/MiniGames/UHC.ts | 186 + src/structures/MiniGames/VampireZ.ts | 44 + src/structures/MiniGames/Walls.ts | 26 + src/structures/MiniGames/Warlords.ts | 82 + src/structures/MiniGames/WoolWars.ts | 151 + src/structures/Pet.ts | 41 + src/structures/Pets.ts | 17 + src/structures/Player.ts | 151 + src/structures/PlayerCosmetics.ts | 130 + src/structures/RecentGame.ts | 27 + src/structures/ServerInfo.ts | 35 + src/structures/SkyBlock/Auctions/Auction.ts | 46 + .../SkyBlock/Auctions/AuctionInfo.ts | 32 + .../SkyBlock/Auctions/BaseAuction.ts | 25 + src/structures/SkyBlock/Auctions/Bid.ts | 25 + .../SkyBlock/Auctions/PartialAuction.ts | 13 + src/structures/SkyBlock/Bazzar/Order.ts | 21 + src/structures/SkyBlock/Bazzar/Product.ts | 33 + src/structures/SkyBlock/News/SkyblockNews.ts | 37 + src/structures/SkyBlock/PlayerBingo.ts | 22 + src/structures/SkyBlock/SkyblockGarden.ts | 74 + .../SkyBlock/SkyblockInventoryItem.ts | 84 + src/structures/SkyBlock/SkyblockMember.ts | 248 ++ src/structures/SkyBlock/SkyblockMuseum.ts | 49 + src/structures/SkyBlock/SkyblockMuseumItem.ts | 31 + src/structures/SkyBlock/SkyblockPet.ts | 29 + src/structures/SkyBlock/SkyblockProfile.ts | 46 + src/structures/SkyBlock/Static/Bingo.ts | 56 + src/structures/SkyBlock/Static/BingoData.ts | 24 + src/structures/SkyBlock/Static/Candidate.ts | 21 + src/structures/SkyBlock/Static/FireSale.ts | 27 + src/structures/SkyBlock/Static/Government.ts | 45 + src/structures/SkyBlock/Static/Perk.ts | 13 + src/structures/Static/Achievement.ts | 52 + src/structures/Static/AchievementTier.ts | 24 + src/structures/Static/Achievements.ts | 23 + src/structures/Static/Challenges.ts | 23 + src/structures/Static/GameAchievements.ts | 22 + src/structures/Static/GameChallenges.ts | 25 + src/structures/Static/GameQuests.ts | 16 + src/structures/Static/GuildAchievements.ts | 22 + src/structures/Static/Quest.ts | 30 + src/structures/Static/Quests.ts | 23 + src/structures/Status.ts | 19 + src/structures/Watchdog/Stats.ts | 19 + src/typings/index.d.ts | 751 ++++ src/utils/Constants.ts | 3131 +++++++++++++++++ src/utils/Guild.ts | 89 + src/utils/Player.ts | 100 + src/utils/SkyblockUtils.ts | 474 +++ src/utils/arrayTools.ts | 2 + src/utils/divide.ts | 5 + src/utils/isGuildID.ts | 3 + src/utils/isUUID.ts | 5 + src/utils/oscillation.ts | 12 + src/utils/removeSnakeCase.ts | 18 + src/utils/rgbToHexColor.ts | 8 + src/utils/romanize.ts | 41 + src/utils/toUuid.ts | 23 + src/utils/varInt.ts | 15 + tsconfig.json | 9 +- 128 files changed, 11331 insertions(+), 2 deletions(-) create mode 100644 API/getSkyblockAuctions.ts create mode 100644 src/API/getAchievements.ts create mode 100644 src/API/getActiveHouses.ts create mode 100644 src/API/getBoosters.ts create mode 100644 src/API/getChallenges.ts create mode 100644 src/API/getGameCounts.ts create mode 100644 src/API/getGuild.ts create mode 100644 src/API/getGuildAchievements.ts create mode 100644 src/API/getHouse.ts create mode 100644 src/API/getLeaderboards.ts create mode 100644 src/API/getPlayer.ts create mode 100644 src/API/getPlayerHouses.ts create mode 100644 src/API/getQuests.ts create mode 100644 src/API/getRecentGames.ts create mode 100644 src/API/getSkyblockAuction.ts create mode 100644 src/API/getSkyblockAuctionsByPlayer.ts create mode 100644 src/API/getSkyblockBazaar.ts create mode 100644 src/API/getSkyblockBingo.ts create mode 100644 src/API/getSkyblockBingoByPlayer.ts create mode 100644 src/API/getSkyblockEndedAuctions.ts create mode 100644 src/API/getSkyblockFireSales.ts create mode 100644 src/API/getSkyblockGarden.ts create mode 100644 src/API/getSkyblockGovernment.ts create mode 100644 src/API/getSkyblockMember.ts create mode 100644 src/API/getSkyblockMuseum.ts create mode 100644 src/API/getSkyblockNews.ts create mode 100644 src/API/getSkyblockProfiles.ts create mode 100644 src/API/getStatus.ts create mode 100644 src/API/getWatchdogStats.ts create mode 100644 src/Client.ts create mode 100644 src/Errors.ts create mode 100644 src/Private/CacheHandler.ts create mode 100644 src/Private/CheckUpdates.ts create mode 100644 src/Private/Endpoint.ts create mode 100644 src/Private/Requests.ts create mode 100644 src/Private/uuidCache.ts create mode 100644 src/index.ts create mode 100644 src/structures/APIIncident.ts create mode 100644 src/structures/APIStatus.ts create mode 100644 src/structures/Boosters/Booster.ts create mode 100644 src/structures/Color.ts create mode 100644 src/structures/Game.ts create mode 100644 src/structures/GameCounts.ts create mode 100644 src/structures/Guild/Guild.ts create mode 100644 src/structures/Guild/GuildMember.ts create mode 100644 src/structures/Guild/GuildRank.ts create mode 100644 src/structures/House.ts create mode 100644 src/structures/ItemBytes.ts create mode 100644 src/structures/Leaderboard.ts create mode 100644 src/structures/MiniGames/Arcade.ts create mode 100644 src/structures/MiniGames/ArenaBrawl.ts create mode 100644 src/structures/MiniGames/BedWars.ts create mode 100644 src/structures/MiniGames/BlitzSurvivalGames.ts create mode 100644 src/structures/MiniGames/BuildBattle.ts create mode 100644 src/structures/MiniGames/CopsAndCrims.ts create mode 100644 src/structures/MiniGames/Duels.ts create mode 100644 src/structures/MiniGames/MegaWalls.ts create mode 100644 src/structures/MiniGames/MurderMystery.ts create mode 100644 src/structures/MiniGames/Paintball.ts create mode 100644 src/structures/MiniGames/Pit.ts create mode 100644 src/structures/MiniGames/PitInventoryItem.ts create mode 100644 src/structures/MiniGames/Quakecraft.ts create mode 100644 src/structures/MiniGames/SkyWars.ts create mode 100644 src/structures/MiniGames/SmashHeroes.ts create mode 100644 src/structures/MiniGames/SpeedUHC.ts create mode 100644 src/structures/MiniGames/TNTGames.ts create mode 100644 src/structures/MiniGames/TurboKartRacers.ts create mode 100644 src/structures/MiniGames/UHC.ts create mode 100644 src/structures/MiniGames/VampireZ.ts create mode 100644 src/structures/MiniGames/Walls.ts create mode 100644 src/structures/MiniGames/Warlords.ts create mode 100644 src/structures/MiniGames/WoolWars.ts create mode 100644 src/structures/Pet.ts create mode 100644 src/structures/Pets.ts create mode 100644 src/structures/Player.ts create mode 100644 src/structures/PlayerCosmetics.ts create mode 100644 src/structures/RecentGame.ts create mode 100644 src/structures/ServerInfo.ts create mode 100644 src/structures/SkyBlock/Auctions/Auction.ts create mode 100644 src/structures/SkyBlock/Auctions/AuctionInfo.ts create mode 100644 src/structures/SkyBlock/Auctions/BaseAuction.ts create mode 100644 src/structures/SkyBlock/Auctions/Bid.ts create mode 100644 src/structures/SkyBlock/Auctions/PartialAuction.ts create mode 100644 src/structures/SkyBlock/Bazzar/Order.ts create mode 100644 src/structures/SkyBlock/Bazzar/Product.ts create mode 100644 src/structures/SkyBlock/News/SkyblockNews.ts create mode 100644 src/structures/SkyBlock/PlayerBingo.ts create mode 100644 src/structures/SkyBlock/SkyblockGarden.ts create mode 100644 src/structures/SkyBlock/SkyblockInventoryItem.ts create mode 100644 src/structures/SkyBlock/SkyblockMember.ts create mode 100644 src/structures/SkyBlock/SkyblockMuseum.ts create mode 100644 src/structures/SkyBlock/SkyblockMuseumItem.ts create mode 100644 src/structures/SkyBlock/SkyblockPet.ts create mode 100644 src/structures/SkyBlock/SkyblockProfile.ts create mode 100644 src/structures/SkyBlock/Static/Bingo.ts create mode 100644 src/structures/SkyBlock/Static/BingoData.ts create mode 100644 src/structures/SkyBlock/Static/Candidate.ts create mode 100644 src/structures/SkyBlock/Static/FireSale.ts create mode 100644 src/structures/SkyBlock/Static/Government.ts create mode 100644 src/structures/SkyBlock/Static/Perk.ts create mode 100644 src/structures/Static/Achievement.ts create mode 100644 src/structures/Static/AchievementTier.ts create mode 100644 src/structures/Static/Achievements.ts create mode 100644 src/structures/Static/Challenges.ts create mode 100644 src/structures/Static/GameAchievements.ts create mode 100644 src/structures/Static/GameChallenges.ts create mode 100644 src/structures/Static/GameQuests.ts create mode 100644 src/structures/Static/GuildAchievements.ts create mode 100644 src/structures/Static/Quest.ts create mode 100644 src/structures/Static/Quests.ts create mode 100644 src/structures/Status.ts create mode 100644 src/structures/Watchdog/Stats.ts create mode 100644 src/typings/index.d.ts create mode 100644 src/utils/Constants.ts create mode 100644 src/utils/Guild.ts create mode 100644 src/utils/Player.ts create mode 100644 src/utils/SkyblockUtils.ts create mode 100644 src/utils/arrayTools.ts create mode 100644 src/utils/divide.ts create mode 100644 src/utils/isGuildID.ts create mode 100644 src/utils/isUUID.ts create mode 100644 src/utils/oscillation.ts create mode 100644 src/utils/removeSnakeCase.ts create mode 100644 src/utils/rgbToHexColor.ts create mode 100644 src/utils/romanize.ts create mode 100644 src/utils/toUuid.ts create mode 100644 src/utils/varInt.ts diff --git a/API/getSkyblockAuctions.ts b/API/getSkyblockAuctions.ts new file mode 100644 index 00000000..e115a5b0 --- /dev/null +++ b/API/getSkyblockAuctions.ts @@ -0,0 +1,85 @@ +import AuctionInfo from '../src/structures/SkyBlock/Auctions/AuctionInfo'; +import Auction from '../src/structures/SkyBlock/Auctions/Auction'; +import Errors from '../src/Errors'; +async function getPage(this: any, page: any = 0, options: any = {}) { + // eslint-disable-next-line no-underscore-dangle + const content = await this._makeRequest(`/skyblock/auctions?page=${page}`, false); + const result: any = {}; + if (!options.noInfo) result.info = new AuctionInfo(content); + if (options.raw) result.auctions = content.auctions; + else if (options.noAuctions) result.auctions = []; + else result.auctions = content.auctions.map((x: any) => new Auction(x, options.includeItemBytes)); + return result; +} +async function noReject(promise: any, args: any = [], retries: any = 3, cooldown: any = 100) { + try { + const result = await promise.call(null, ...args); + return result; + } catch { + if (retries) { + await new Promise((resolve) => setTimeout(resolve, cooldown)); + return await noReject(promise, args, retries - 1, cooldown); + } + return null; + } +} + +import Endpoint from '../src/Private/Endpoint'; +import Client from '../src/Client'; +export default class getSkyblockAuctions extends Endpoint { + readonly client: Client; + readonly name: string; + constructor(client: Client) { + super(client); + this.client = client; + this.name = 'getSkyblockAuctions'; + } + + async execute(range: any, options: any) { + options.retries ||= 3; + options.cooldown ||= 100; + if (null === range || '*' === range) range = [0, (await getPage(0, { noAuctions: true })).info.totalPages]; + if (!Array.isArray(range)) range = [parseInt(range), parseInt(range)]; + if (isNaN(range[0])) throw new Error(Errors.PAGE_INDEX_ERROR); + if (parseInt(options.retries) !== options.retries || 10 < options.retries || 0 > options.retries) { + throw new Error(Errors.INVALID_OPTION_VALUE); + } + if (parseInt(options.cooldown) !== options.cooldown || 3000 < options.cooldown || 0 > options.cooldown) { + throw new Error(Errors.INVALID_OPTION_VALUE); + } + range = range.sort(); + const result: any = { auctions: [] }; + const fetches = []; + const failedPages = []; + if (options.noAuctions) { + return { info: options.noInfo ? null : (await getPage(range[1], { noAuctions: true })).info }; + } + for (let i = range[0]; i <= range[1]; i++) { + if (options.race) { + fetches.push(noReject(getPage, [i, options], options.retries, options.cooldown)); + } else { + const resp = await noReject(getPage, [i, options], options.retries, options.cooldown); + if (resp) { + result.auctions = result.auctions.concat(resp.auctions); + if (resp.info) result.info = resp.info; + } else { + failedPages.push(i); + } + } + } + if (fetches.length) { + result.auctions = (await Promise.all(fetches)).reduce((pV, cV, index) => { + if (!cV) { + failedPages.push(index + range[0]); + return pV; + } + if (cV.info) result.info = cV.info; + if (cV.auctions.length) return pV.concat(cV.auctions); + return pV; + }, []); + } + // eslint-disable-next-line no-underscore-dangle + result.info = result.info ? result.info._extend('failedPages', failedPages) : { failedPages }; + return result; + } +} diff --git a/src/API/getAchievements.ts b/src/API/getAchievements.ts new file mode 100644 index 00000000..6e6d42bc --- /dev/null +++ b/src/API/getAchievements.ts @@ -0,0 +1,18 @@ +import Achievements from '../structures/Static/Achievements'; +import Endpoint from '../Private/Endpoint'; +import Client from '../Client'; +export default class getAchievements extends Endpoint { + readonly client: Client; + readonly name: string; + constructor(client: Client) { + super(client); + this.client = client; + this.name = 'getAchievements'; + } + + async execute() { + const res = await this.client.requests.request('/resources/achievements'); + if (res.raw) return res; + return new Achievements(res); + } +} diff --git a/src/API/getActiveHouses.ts b/src/API/getActiveHouses.ts new file mode 100644 index 00000000..ba67d7ae --- /dev/null +++ b/src/API/getActiveHouses.ts @@ -0,0 +1,18 @@ +import Endpoint from '../Private/Endpoint'; +import House from '../structures/House'; +import Client from '../Client'; +export default class getActiveHouses extends Endpoint { + readonly client: Client; + readonly name: string; + constructor(client: Client) { + super(client); + this.client = client; + this.name = 'getActiveHouses'; + } + + async execute() { + const res = await this.client.requests.request('/housing/active'); + if (res.raw) return res; + return res.length ? res.map((b: any) => new House(b)) : []; + } +} diff --git a/src/API/getBoosters.ts b/src/API/getBoosters.ts new file mode 100644 index 00000000..df119b14 --- /dev/null +++ b/src/API/getBoosters.ts @@ -0,0 +1,18 @@ +import Booster from '../structures/Boosters/Booster'; +import Endpoint from '../Private/Endpoint'; +import Client from '../Client'; +export default class getBoosters extends Endpoint { + readonly client: Client; + readonly name: string; + constructor(client: Client) { + super(client); + this.client = client; + this.name = 'getBoosters'; + } + + async execute() { + const res = await this.client.requests.request('/boosters'); + if (res.raw) return res; + return res.boosters.length ? res.boosters.map((b: any) => new Booster(b)).reverse() : []; + } +} diff --git a/src/API/getChallenges.ts b/src/API/getChallenges.ts new file mode 100644 index 00000000..e12b288f --- /dev/null +++ b/src/API/getChallenges.ts @@ -0,0 +1,18 @@ +import Challenges from '../structures/Static/Challenges'; +import Endpoint from '../Private/Endpoint'; +import Client from '../Client'; +export default class getChallenges extends Endpoint { + readonly client: Client; + readonly name: string; + constructor(client: Client) { + super(client); + this.client = client; + this.name = 'getChallenges'; + } + + async execute() { + const res = await this.client.requests.request('/resources/challenges'); + if (res.raw) return res; + return new Challenges(res); + } +} diff --git a/src/API/getGameCounts.ts b/src/API/getGameCounts.ts new file mode 100644 index 00000000..e7c7a505 --- /dev/null +++ b/src/API/getGameCounts.ts @@ -0,0 +1,18 @@ +import GameCounts from '../structures/GameCounts'; +import Endpoint from '../Private/Endpoint'; +import Client from '../Client'; +export default class getGameCounts extends Endpoint { + readonly client: Client; + readonly name: string; + constructor(client: Client) { + super(client); + this.client = client; + this.name = 'getGameCounts'; + } + + async execute() { + const res = await this.client.requests.request('/counts'); + if (res.raw) return res; + return new GameCounts(res); + } +} diff --git a/src/API/getGuild.ts b/src/API/getGuild.ts new file mode 100644 index 00000000..aa5a0c94 --- /dev/null +++ b/src/API/getGuild.ts @@ -0,0 +1,30 @@ +import Guild from '../structures/Guild/Guild'; +import isGuildID from '../utils/isGuildID'; +import Endpoint from '../Private/Endpoint'; +import toUuid from '../utils/toUuid'; +import Errors from '../Errors'; +import Client from '../Client'; +export default class getGuild extends Endpoint { + readonly client: Client; + readonly name: string; + constructor(client: Client) { + super(client); + this.client = client; + this.name = 'getGuild'; + } + + async execute(searchParameter: 'id' | 'name' | 'player', query: string) { + if (!query) throw new Error(Errors.NO_GUILD_QUERY); + if ('id' === searchParameter && !isGuildID(query)) throw new Error(Errors.INVALID_GUILD_ID); + const isPlayerQuery = 'player' === searchParameter; + if (isPlayerQuery) query = await toUuid(query); + if (!['id', 'name', 'player'].includes(searchParameter)) throw new Error(Errors.INVALID_GUILD_SEARCH_PARAMETER); + const res = await this.client.requests.request(`/guild?${searchParameter}=${encodeURI(query)}`); + if (res.raw) return res; + if (!res.guild && 'player' !== searchParameter) { + throw new Error(Errors.GUILD_DOES_NOT_EXIST); + } + + return res.guild ? new Guild(res.guild, isPlayerQuery ? query : undefined) : null; + } +} diff --git a/src/API/getGuildAchievements.ts b/src/API/getGuildAchievements.ts new file mode 100644 index 00000000..7e81c80d --- /dev/null +++ b/src/API/getGuildAchievements.ts @@ -0,0 +1,18 @@ +import GuildAchievements from '../structures/Static/GuildAchievements'; +import Endpoint from '../Private/Endpoint'; +import Client from '../Client'; +export default class getGuildAchievements extends Endpoint { + readonly client: Client; + readonly name: string; + constructor(client: Client) { + super(client); + this.client = client; + this.name = 'getGuildAchievements'; + } + + async execute() { + const res = await this.client.requests.request('/resources/guilds/achievements'); + if (res.raw) return res; + return new GuildAchievements(res); + } +} diff --git a/src/API/getHouse.ts b/src/API/getHouse.ts new file mode 100644 index 00000000..ee3c8a2e --- /dev/null +++ b/src/API/getHouse.ts @@ -0,0 +1,20 @@ +import Endpoint from '../Private/Endpoint'; +import House from '../structures/House'; +import Errors from '../Errors'; +import Client from '../Client'; +export default class getHouse extends Endpoint { + readonly client: Client; + readonly name: string; + constructor(client: Client) { + super(client); + this.client = client; + this.name = 'getHouse'; + } + + async execute(query: string) { + if (!query) throw new Error(Errors.NO_UUID); + const res = await this.client.requests.request(`/housing/house?house=${query}`); + if (res.raw) return res; + return new House(res); + } +} diff --git a/src/API/getLeaderboards.ts b/src/API/getLeaderboards.ts new file mode 100644 index 00000000..6d64d428 --- /dev/null +++ b/src/API/getLeaderboards.ts @@ -0,0 +1,27 @@ +import Leaderboard from '../structures/Leaderboard'; +import Constants from '../utils/Constants'; +import Errors from '../Errors'; +import Endpoint from '../Private/Endpoint'; +import Client from '../Client'; +export default class getLeaderboards extends Endpoint { + readonly client: Client; + readonly name: string; + constructor(client: Client) { + super(client); + this.client = client; + this.name = 'getLeaderboards'; + } + + async execute() { + const res = await this.client.requests.request('/leaderboards'); + if (res.raw) return res; + if (!res.leaderboards) throw new Error(Errors.SOMETHING_WENT_WRONG.replace(/{cause}/, 'Try again.')); + const lbnames = Object.create(Constants.leaderboardNames); + for (const name in lbnames) { + lbnames[name] = res.leaderboards[lbnames[name]].length + ? res.leaderboards[lbnames[name]].map((lb: any) => new Leaderboard(lb)) + : []; + } + return lbnames; + } +} diff --git a/src/API/getPlayer.ts b/src/API/getPlayer.ts new file mode 100644 index 00000000..a409f7d0 --- /dev/null +++ b/src/API/getPlayer.ts @@ -0,0 +1,24 @@ +import Player from '../structures/Player'; +import toUuid from '../utils/toUuid'; +import Errors from '../Errors'; + +import Endpoint from '../Private/Endpoint'; +import Client from '../Client'; +export default class getPlayer extends Endpoint { + readonly client: Client; + readonly name: string; + constructor(client: Client) { + super(client); + this.client = client; + this.name = 'getPlayer'; + } + + async execute(query: string) { + if (!query) throw new Error(Errors.NO_NICKNAME_UUID); + query = await toUuid(query); + const res = await this.client.requests.request(`/player?uuid=${query}`); + if (res.raw) return res; + if (query && !res.player) throw new Error(Errors.PLAYER_HAS_NEVER_LOGGED); + return new Player(res.player); + } +} diff --git a/src/API/getPlayerHouses.ts b/src/API/getPlayerHouses.ts new file mode 100644 index 00000000..22fe9c4d --- /dev/null +++ b/src/API/getPlayerHouses.ts @@ -0,0 +1,22 @@ +import Endpoint from '../Private/Endpoint'; +import House from '../structures/House'; +import toUuid from '../utils/toUuid'; +import Errors from '../Errors'; +import Client from '../Client'; +export default class getPlayerHouses extends Endpoint { + readonly client: Client; + readonly name: string; + constructor(client: Client) { + super(client); + this.client = client; + this.name = 'getPlayerHouses'; + } + + async execute(query: string) { + if (!query) throw new Error(Errors.NO_NICKNAME_UUID); + query = await toUuid(query); + const res = await this.client.requests.request(`/housing/houses?player=${query}`); + if (res.raw) return res; + return res.length ? res.map((h: any) => new House(h)) : []; + } +} diff --git a/src/API/getQuests.ts b/src/API/getQuests.ts new file mode 100644 index 00000000..b8d836f8 --- /dev/null +++ b/src/API/getQuests.ts @@ -0,0 +1,18 @@ +import Quests from '../structures/Static/Quests'; +import Endpoint from '../Private/Endpoint'; +import Client from '../Client'; +export default class getQuests extends Endpoint { + readonly client: Client; + readonly name: string; + constructor(client: Client) { + super(client); + this.client = client; + this.name = 'getQuests'; + } + + async execute() { + const res = await this.client.requests.request('/resources/quests'); + if (res.raw) return res; + return new Quests(res); + } +} diff --git a/src/API/getRecentGames.ts b/src/API/getRecentGames.ts new file mode 100644 index 00000000..0c6e529d --- /dev/null +++ b/src/API/getRecentGames.ts @@ -0,0 +1,25 @@ +import RecentGame from '../structures/RecentGame'; +import Endpoint from '../Private/Endpoint'; +import toUuid from '../utils/toUuid'; +import Errors from '../Errors'; +import Client from '../Client'; +export default class getRecentGames extends Endpoint { + readonly client: Client; + readonly name: string; + constructor(client: Client) { + super(client); + this.client = client; + this.name = 'getRecentGames'; + } + + async execute(query: string) { + if (!query) throw new Error(Errors.NO_NICKNAME_UUID); + query = await toUuid(query); + const res = await this.client.requests.request(`/recentgames?uuid=${query}`); + if (res.raw) return res; + if (0 === res.games.length) { + return []; + } + return res.games.map((x: any) => new RecentGame(x)); + } +} diff --git a/src/API/getSkyblockAuction.ts b/src/API/getSkyblockAuction.ts new file mode 100644 index 00000000..52a6bcbb --- /dev/null +++ b/src/API/getSkyblockAuction.ts @@ -0,0 +1,32 @@ +import Auction from '../structures/SkyBlock/Auctions/Auction'; +import Endpoint from '../Private/Endpoint'; +import toUuid from '../utils/toUuid'; +import Errors from '../Errors'; +import Client from '../Client'; +export default class getSkyblockAction extends Endpoint { + readonly client: Client; + readonly name: string; + constructor(client: Client) { + super(client); + this.client = client; + this.name = 'getSkyblockAction'; + } + + async execute(query: string, type: 'PROFILE' | 'PLAYER' | 'AUCTION', includeItemBytes: boolean = false) { + if (!query) throw new Error(Errors.NO_NICKNAME_UUID); + let filter; + if ('PROFILE' === type) { + filter = 'profile'; + } else if ('PLAYER' === type) { + query = await toUuid(query); + filter = 'player'; + } else if ('AUCTION' === type) { + filter = 'uuid'; + } else { + throw new Error(Errors.BAD_AUCTION_FILTER); + } + const res = await this.client.requests.request(`/skyblock/auction?${filter}=${query}`); + if (res.raw) return res; + return res.auctions.length ? res.auctions.map((a: any) => new Auction(a, includeItemBytes)) : []; + } +} diff --git a/src/API/getSkyblockAuctionsByPlayer.ts b/src/API/getSkyblockAuctionsByPlayer.ts new file mode 100644 index 00000000..3075032d --- /dev/null +++ b/src/API/getSkyblockAuctionsByPlayer.ts @@ -0,0 +1,22 @@ +import Auction from '../structures/SkyBlock/Auctions/Auction'; +import Endpoint from '../Private/Endpoint'; +import toUuid from '../utils/toUuid'; +import Errors from '../Errors'; +import Client from '../Client'; +export default class getSkyblockActionsByPlayer extends Endpoint { + readonly client: Client; + readonly name: string; + constructor(client: Client) { + super(client); + this.client = client; + this.name = 'getSkyblockActionsByPlayer'; + } + + async execute(query: string, includeItemBytes: boolean) { + if (!query) throw new Error(Errors.NO_NICKNAME_UUID); + query = await toUuid(query); + const res = await this.client.requests.request(`/skyblock/auction?player=${query}`); + if (res.raw) return res; + return res.auctions.length ? res.auctions.map((a: any) => new Auction(a, includeItemBytes)) : []; + } +} diff --git a/src/API/getSkyblockBazaar.ts b/src/API/getSkyblockBazaar.ts new file mode 100644 index 00000000..e2de073e --- /dev/null +++ b/src/API/getSkyblockBazaar.ts @@ -0,0 +1,19 @@ +import Product from '../structures/SkyBlock/Bazzar/Product'; +import Endpoint from '../Private/Endpoint'; +import Client from '../Client'; +export default class getSkyblockBazaar extends Endpoint { + readonly client: Client; + readonly name: string; + constructor(client: Client) { + super(client); + this.client = client; + this.name = 'getSkyblockBazaar'; + } + + async execute() { + const res = await this.client.requests.request('/skyblock/bazaar'); + if (res.raw) return res; + const productsKeys = Object.keys(res.products); + return productsKeys.map((x) => new Product(res.products[x])); + } +} diff --git a/src/API/getSkyblockBingo.ts b/src/API/getSkyblockBingo.ts new file mode 100644 index 00000000..5cfcf2f2 --- /dev/null +++ b/src/API/getSkyblockBingo.ts @@ -0,0 +1,18 @@ +import BingoData from '../structures/SkyBlock/Static/BingoData'; +import Endpoint from '../Private/Endpoint'; +import Client from '../Client'; +export default class getSkyblockBingo extends Endpoint { + readonly client: Client; + readonly name: string; + constructor(client: Client) { + super(client); + this.client = client; + this.name = 'getSkyblockBingo'; + } + + async execute() { + const res = await this.client.requests.request('/resources/skyblock/bingo'); + if (res.raw) return res; + return new BingoData(res); + } +} diff --git a/src/API/getSkyblockBingoByPlayer.ts b/src/API/getSkyblockBingoByPlayer.ts new file mode 100644 index 00000000..cec180d1 --- /dev/null +++ b/src/API/getSkyblockBingoByPlayer.ts @@ -0,0 +1,22 @@ +import PlayerBingo from '../structures/SkyBlock/PlayerBingo'; +import Endpoint from '../Private/Endpoint'; +import toUuid from '../utils/toUuid'; +import Errors from '../Errors'; +import Client from '../Client'; +export default class getBingoByPlayer extends Endpoint { + readonly client: Client; + readonly name: string; + constructor(client: Client) { + super(client); + this.client = client; + this.name = 'getBingoByPlayer'; + } + + async execute(query: string) { + if (!query) throw new Error(Errors.NO_NICKNAME_UUID); + query = await toUuid(query); + const res = await this.client.requests.request(`/skyblock/uuid?player=${query}`); + if (res.raw) return res; + return new PlayerBingo(res); + } +} diff --git a/src/API/getSkyblockEndedAuctions.ts b/src/API/getSkyblockEndedAuctions.ts new file mode 100644 index 00000000..263d734a --- /dev/null +++ b/src/API/getSkyblockEndedAuctions.ts @@ -0,0 +1,22 @@ +import PartialAuction from '../structures/SkyBlock/Auctions/PartialAuction'; +import AuctionInfo from '../structures/SkyBlock/Auctions/AuctionInfo'; +import Endpoint from '../Private/Endpoint'; +import Client from '../Client'; +export default class getSkyblockEndedAuctions extends Endpoint { + readonly client: Client; + readonly name: string; + constructor(client: Client) { + super(client); + this.client = client; + this.name = 'getSkyblockEndedAuctions'; + } + + async execute(includeItemBytes: any): Promise { + const res = await this.client.requests.request('/skyblock/auctions_ended'); + if (res.raw) return res; + return { + info: new AuctionInfo({ ...res, totalAuctions: res.auctions.length, totalPages: 1 }), + auctions: res.auctions.length ? res.auctions.map((a: any) => new PartialAuction(a, includeItemBytes)) : [] + }; + } +} diff --git a/src/API/getSkyblockFireSales.ts b/src/API/getSkyblockFireSales.ts new file mode 100644 index 00000000..f553dd3d --- /dev/null +++ b/src/API/getSkyblockFireSales.ts @@ -0,0 +1,18 @@ +import FireSale from '../structures/SkyBlock/Static/FireSale'; +import Endpoint from '../Private/Endpoint'; +import Client from '../Client'; +export default class getSkyblockFireSales extends Endpoint { + readonly client: Client; + readonly name: string; + constructor(client: Client) { + super(client); + this.client = client; + this.name = 'getSkyblockFireSales'; + } + + async execute() { + const res = await this.client.requests.request('/skyblock/firesales'); + if (res.raw) return res; + return res.sales.length ? res.sales.map((a: any) => new FireSale(a)) : []; + } +} diff --git a/src/API/getSkyblockGarden.ts b/src/API/getSkyblockGarden.ts new file mode 100644 index 00000000..9a16ce10 --- /dev/null +++ b/src/API/getSkyblockGarden.ts @@ -0,0 +1,18 @@ +import SkyblockGarden from '../structures/SkyBlock/SkyblockGarden'; +import Endpoint from '../Private/Endpoint'; +import Client from '../Client'; +export default class getSkyblockGarden extends Endpoint { + readonly client: Client; + readonly name: string; + constructor(client: Client) { + super(client); + this.client = client; + this.name = 'getSkyblockGarden'; + } + + async execute(profileId: string) { + const res = await this.client.requests.request(`/skyblock/garden?profile=${profileId}`); + if (res.raw) return res; + return new SkyblockGarden(res); + } +} diff --git a/src/API/getSkyblockGovernment.ts b/src/API/getSkyblockGovernment.ts new file mode 100644 index 00000000..63bae098 --- /dev/null +++ b/src/API/getSkyblockGovernment.ts @@ -0,0 +1,18 @@ +import GovernmentData from '../structures/SkyBlock/Static/Government.js'; +import Endpoint from '../Private/Endpoint.js'; +import Client from '../Client.js'; +export default class getSkyblockGovernment extends Endpoint { + readonly client: Client; + readonly name: string; + constructor(client: Client) { + super(client); + this.client = client; + this.name = 'getSkyblockGovernment'; + } + + async execute() { + const res = await this.client.requests.request('/resources/skyblock/election'); + if (res.raw) return res; + return new GovernmentData(res); + } +} diff --git a/src/API/getSkyblockMember.ts b/src/API/getSkyblockMember.ts new file mode 100644 index 00000000..02db9346 --- /dev/null +++ b/src/API/getSkyblockMember.ts @@ -0,0 +1,40 @@ +import SkyblockMember from '../structures/SkyBlock/SkyblockMember'; +import Endpoint from '../Private/Endpoint'; +import toUuid from '../utils/toUuid'; +import Errors from '../Errors'; +import Client from '../Client'; + +export default class getSkyblockMember extends Endpoint { + readonly client: Client; + readonly name: string; + constructor(client: Client) { + super(client); + this.client = client; + this.name = 'getSkyblockMember'; + } + + async execute(query: string) { + if (!query) throw new Error(Errors.NO_NICKNAME_UUID); + query = await toUuid(query); + const res = await this.client.requests.request(`/skyblock/profiles?uuid=${query}`); + if (res.raw) return res; + if (!res.profiles || !res.profiles.length) throw new Error(Errors.NO_SKYBLOCK_PROFILES); + const memberByProfileName = new Map(); + for (const profile of res.profiles) { + memberByProfileName.set( + profile.cute_name, + new SkyblockMember({ + uuid: query, + profileId: profile.profile_id, + profileName: profile.cute_name, + gameMode: profile.game_mode || null, + m: profile.members[query], + banking: profile.banking, + communityUpgrades: profile.community_upgrades, + selected: profile.selected + }) + ); + } + return memberByProfileName; + } +} diff --git a/src/API/getSkyblockMuseum.ts b/src/API/getSkyblockMuseum.ts new file mode 100644 index 00000000..93d968b5 --- /dev/null +++ b/src/API/getSkyblockMuseum.ts @@ -0,0 +1,26 @@ +import SkyblockMuseum from '../structures/SkyBlock/SkyblockMuseum'; +import Endpoint from '../Private/Endpoint'; +import toUuid from '../utils/toUuid'; +import Errors from '../Errors'; +import Client from '../Client'; +export default class getSkyblockMuseum extends Endpoint { + readonly client: Client; + readonly name: string; + constructor(client: Client) { + super(client); + this.client = client; + this.name = 'getSkyblockMuseum'; + } + + async execute(query: string, profileId: string) { + if (!query) throw new Error(Errors.NO_NICKNAME_UUID); + query = await toUuid(query); + const res = await this.client.requests.request(`/skyblock/museum?uuid=${query}&profile=${profileId}`); + if (res.raw) return res; + return new SkyblockMuseum({ + uuid: query, + m: res, + profileId: profileId + }); + } +} diff --git a/src/API/getSkyblockNews.ts b/src/API/getSkyblockNews.ts new file mode 100644 index 00000000..3716de9f --- /dev/null +++ b/src/API/getSkyblockNews.ts @@ -0,0 +1,18 @@ +import SkyblockNews from '../structures/SkyBlock/News/SkyblockNews'; +import Endpoint from '../Private/Endpoint'; +import Client from '../Client'; +export default class getSkyblockNews extends Endpoint { + readonly client: Client; + readonly name: string; + constructor(client: Client) { + super(client); + this.client = client; + this.name = 'getSkyblockNews'; + } + + async execute() { + const res = await this.client.requests.request('/skyblock/news'); + if (res.raw) return res; + return res.items.map((i: any) => new SkyblockNews(i)); + } +} diff --git a/src/API/getSkyblockProfiles.ts b/src/API/getSkyblockProfiles.ts new file mode 100644 index 00000000..538b6c2e --- /dev/null +++ b/src/API/getSkyblockProfiles.ts @@ -0,0 +1,41 @@ +import SkyblockProfile from '../structures/SkyBlock/SkyblockProfile'; +import Endpoint from '../Private/Endpoint'; +import toUuid from '../utils/toUuid'; +import Errors from '../Errors'; +import Client from '../Client'; +export default class getSkyblockProfiles extends Endpoint { + readonly client: Client; + readonly name: string; + constructor(client: Client) { + super(client); + this.client = client; + this.name = 'getSkyblockProfiles'; + } + + async execute(query: string) { + if (!query) throw new Error(Errors.NO_NICKNAME_UUID); + query = await toUuid(query); + const res = await this.client.requests.request(`/skyblock/profiles?uuid=${query}`); + if (res.raw) return res; + if (!res.profiles || !res.profiles.length) throw new Error(Errors.NO_SKYBLOCK_PROFILES); + + const profiles = []; + for (let i = 0; i < res.profiles.length; i++) { + profiles.push({ + uuid: query, + profileId: res.profiles[i].profile_id, + profileName: res.profiles[i].cute_name, + gameMode: res.profiles[i].game_mode || null, + m: res.profiles[i].members[query], + banking: res.profiles[i].banking, + communityUpgrades: res.profiles[i].community_upgrades, + museum: null, + garden: null, + selected: res.profiles[i].selected, + members: res.profiles[i].members + }); + } + + return profiles.map((p) => new SkyblockProfile(p)); + } +} diff --git a/src/API/getStatus.ts b/src/API/getStatus.ts new file mode 100644 index 00000000..a187c1de --- /dev/null +++ b/src/API/getStatus.ts @@ -0,0 +1,20 @@ +import Endpoint from '../Private/Endpoint'; +import Status from '../structures/Status'; +import toUuid from '../utils/toUuid'; +import Client from '../Client'; +export default class getStatus extends Endpoint { + readonly client: Client; + readonly name: string; + constructor(client: Client) { + super(client); + this.client = client; + this.name = 'getStatus'; + } + + async execute(query: string) { + query = await toUuid(query); + const res = await this.client.requests.request(`/status?uuid=${query}`); + if (res.raw) return res; + return new Status(res.session); + } +} diff --git a/src/API/getWatchdogStats.ts b/src/API/getWatchdogStats.ts new file mode 100644 index 00000000..e83247bb --- /dev/null +++ b/src/API/getWatchdogStats.ts @@ -0,0 +1,19 @@ +import WatchdogStats from '../structures/Watchdog/Stats'; +import Endpoint from '../Private/Endpoint'; +import Client from '../Client'; + +export default class getWatchdogStatsEndpoint extends Endpoint { + readonly client: Client; + readonly name: string; + constructor(client: Client) { + super(client); + this.client = client; + this.name = 'getWatchdogStats'; + } + + async execute() { + const res = await this.client.requests.request('/punishmentstats'); + if (res.raw) return res; + return new WatchdogStats(res); + } +} diff --git a/src/Client.ts b/src/Client.ts new file mode 100644 index 00000000..01329284 --- /dev/null +++ b/src/Client.ts @@ -0,0 +1,77 @@ +import CheckUpdates from './Private/CheckUpdates'; +import CacheHandler from './Private/CacheHandler'; +import Requests from './Private/Requests'; +import Errors from './Errors'; +import fs from 'fs'; + +const clients: any[] = []; + +export interface ClientOptions { + cache?: boolean; + cacheTime?: number; + cacheMaxKeys?: number; + cacheCheckPeriod?: number; + rateLimit?: 'AUTO' | 'HARD' | 'NONE'; + silent?: boolean; + checkForUpdates?: boolean; +} + +export class Client { + readonly key?: string; + + declare requests: Requests; + declare cacheHandler: CacheHandler; + + declare cache: boolean; + declare cacheTime: number; + declare cacheMaxKeys: number; + declare cacheCheckPeriod: number; + declare rateLimit?: 'AUTO' | 'HARD' | 'NONE'; + declare silent: boolean; + declare checkForUpdates: boolean; + declare endpoints: any; + + constructor(key: string, options?: ClientOptions) { + this.key = key; + + this.cache = options?.cache ?? true; + this.cacheTime = options?.cacheTime ?? 300; + this.cacheMaxKeys = options?.cacheMaxKeys ?? -1; + this.cacheCheckPeriod = options?.cacheCheckPeriod ?? 180; + this.rateLimit = options?.rateLimit ?? 'AUTO'; + this.silent = options?.silent ?? false; + this.checkForUpdates = options?.checkForUpdates ?? true; + this.endpoints = {}; + + this.requests = new Requests(this); + this.cacheHandler = new CacheHandler(this); + + const endpoints = fs.readdirSync('./src/api').filter((file) => file.endsWith('.ts')); + + for (const file of endpoints) { + import(`./api/${file}`).then((endpoint) => { + const data = new endpoint.default(this); + this.endpoints[data.name] = data.execute; + }); + } + setTimeout(() => { + // console.log(this.endpoints); + }, 1000); + if (clients.find((x) => x.key === key)) { + // eslint-disable-next-line no-console + console.warn(Errors.MULTIPLE_INSTANCES); + return clients.find((x) => x.key === key); + } + + if (this.checkForUpdates) { + CheckUpdates().catch(() => { + // eslint-disable-next-line no-console + if (!this.silent) console.warn(Errors.UPDATER_REQUEST_NOT_OK); + }); + } + + clients.push(this); + } +} + +export default Client; diff --git a/src/Errors.ts b/src/Errors.ts new file mode 100644 index 00000000..fbe9cf2d --- /dev/null +++ b/src/Errors.ts @@ -0,0 +1,66 @@ +/* eslint-disable max-len */ +export default { + INVALID_API_KEY: '[Hypixel-API-Reborn] Invalid API Key! For help join our Discord Server https://discord.gg/NSEBNMM', + NO_API_KEY: '[Hypixel-API-Reborn] No API Key specified! For help join our Discord Server https://discord.gg/NSEBNMM', + ERROR_CODE_CAUSE: + '[Hypixel-API-Reborn] Code: {code} - {cause}! For help join our Discord Server https://discord.gg/NSEBNMM', + ERROR_STATUSTEXT: '[Hypixel-API-Reborn] {statustext}! For help join our Discord Server https://discord.gg/NSEBNMM', + KEY_MUST_BE_A_STRING: + '[Hypixel-API-Reborn] Specified API Key must be a string! For help join our Discord Server https://discord.gg/NSEBNMM', + OPTIONS_MUST_BE_AN_OBJECT: + '[Hypixel-API-Reborn] Options must be an object! For help join our Discord Server https://discord.gg/NSEBNMM', + CACHE_TIME_MUST_BE_A_NUMBER: + '[Hypixel-API-Reborn] Cache Time must be a number! For help join our Discord Server https://discord.gg/NSEBNMM', + CACHE_LIMIT_MUST_BE_A_NUMBER: + '[Hypixel-API-Reborn] Cache Limit must be a number! For help join our Discord Server https://discord.gg/NSEBNMM', + CACHE_FILTER_INVALID: + '[Hypixel-API-Reborn] Cache Filter must be a function returning a boolean, an object, an array, or a string!', + NO_NICKNAME_UUID: '[Hypixel-API-Reborn] No nickname or uuid specified.', + NO_UUID: '[Hypixel-API-Reborn] No uuid specified.', + UUID_NICKNAME_MUST_BE_A_STRING: '[Hypixel-API-Reborn] Nickname or uuid must be a string.', + MALFORMED_UUID: '[Hypixel-API-Reborn] Malformed UUID!', + PLAYER_DOES_NOT_EXIST: '[Hypixel-API-Reborn] Player does not exist.', + PLAYER_HAS_NEVER_LOGGED: '[Hypixel-API-Reborn] Player has never logged into Hypixel.', + PLAYER_IS_INACTIVE: + "[Hypixel-API-Reborn] No records of recent games because player hasn't been online for more than 3 days or is lacking data to determine the cause of an empty response", + PLAYER_DISABLED_ENDPOINT: '[Hypixel-API-Reborn] Player has disabled this endpoint.', + NO_GUILD_QUERY: '[Hypixel-API-Reborn] No guild search query specified.', + INVALID_GUILD_ID: '[Hypixel-API-Reborn] Specified Guild ID is invalid.', + INVALID_GUILD_SEARCH_PARAMETER: "[Hypixel-API-Reborn] getGuild() searchParameter must be 'id', 'guild' or 'player'.", + SOMETHING_WENT_WRONG: '[Hypixel-API-Reborn] Something went wrong. {cause}', + GUILD_DOES_NOT_EXIST: '[Hypixel-API-Reborn] Guild does not exist.', + PAGE_INDEX_ERROR: + '[Hypixel-API-Reborn] Invalid page index. Must be an integer, an array of 2 integers, or a keyword. For help join our Discord Server https://discord.gg/NSEBNMM', + INVALID_OPTION_VALUE: + '[Hypixel-API-Reborn] Invalid option value! For help join our Discord Server https://discord.gg/NSEBNMM', + INVALID_RESPONSE_BODY: + '[Hypixel-API-Reborn] An error occurred while converting to JSON. Perhaps this is due to an update or maintenance. For help join our Discord Server https://discord.gg/NSEBNMM', + INVALID_RATE_LIMIT_OPTION: + "[Hypixel-API-Reborn] Rate Limit provided in Client options must be 'HARD', 'AUTO', or 'NONE'. For help join our Discord Server https://discord.gg/NSEBNMM", + INVALID_KEY_LIMIT_OPTION: + '[Hypixel-API-Reborn] Key Limit provided in Client options must be an integer representing the amount of requests possible in one minute with your key.', + INVALID_HEADER_SYNC_OPTION: '[Hypixel-API-Reborn] Invalid Value for maxSyncHeaders : must be a boolean', + INVALID_BURST_OPTION: '[Hypixel-API-Reborn] maxBurstRequests provided in Client options must be a number', + NODE_VERSION_ERR: + "[Hypixel-API-Reborn] You are using a version of Nodejs that doesn't support certain features we use. Please upgrade to version 14 or above.", + UPDATER_REQUEST_NOT_OK: '[Hypixel-API-Reborn] Something went wrong while checking for updates.', + CONNECTION_ERROR: '[Hypixel-API-Reborn] Failed to connect.', + RATE_LIMIT_INIT_ERROR: + '[Hypixel-API-Reborn] An error happened whilst initializing rate limit. We strongly recommend restarting the code as this can lead to desynchronization.', + MULTIPLE_INSTANCES: + '[Hypixel-API-Reborn] Multiple instances of hypixel-api-reborn are found so we merged them for you. Please refrain from spawning multiple instances in the future. For more information, join our Discord Server https://discord.gg/NSEBNMM.', + INVALID_HEADERS: + '[Hypixel-API-Reborn] Invalid Headers are provided in ClientOptions. For help join our Discord Server https://discord.gg/NSEBNMM', + INVALID_CACHE_HANDLER: + '[Hypixel-API-Reborn] An invalid cache handler is provideed. For help join our Discord Server https://discord.gg/NSEBNMM', + UNEXPECTED_ERROR: + "[Hypixel-API-Reborn] The data provided to hypixel API is malformed and thus not recognized by hypixel, but this shouldn't be your fault. Please report this error in our Discord Server https://discord.gg/NSEBNMM or GitHub. ", + RATE_LIMIT_EXCEEDED: + "[Hypixel-API-Reborn] The rate limitations on your API Key has been exceeded. There might be an outage (Check Hypixel's status page), or you simply did too many requests in a short time. Hint: Enable rate limit options! They can help you avoid this error! For help join our Discord Server https://discord.gg/NSEBNMM", + NO_SKYBLOCK_PROFILES: '[Hypixel-API-Reborn] The player has no skyblock profiles.', + INVALID_SILENT_OPTION: '[Hypixel-API-Reborn] Invalid Value for silent : must be a boolean', + INVALID_UPDATE_OPTION: '[Hypixel-API-Reborn] Invalid Value for update : must be a boolean', + INVALID_THIRD_PARTY_API_OPTION: '[Hypixel-API-Reborn] Invalid Third Party API option : must be a boolean or string', + BAD_AUCTION_FILTER: + '[Hypixel-API-Reborn] Unexpected filter for Client#getSkyblockAuction. Expected one of "PLAYER", "AUCTION", "PROFILE", but got something else.' +}; diff --git a/src/Private/CacheHandler.ts b/src/Private/CacheHandler.ts new file mode 100644 index 00000000..49d02245 --- /dev/null +++ b/src/Private/CacheHandler.ts @@ -0,0 +1,41 @@ +import NodeCache from 'node-cache'; +import Client from '../Client'; + +class CacheHandler { + readonly client: Client; + declare cache: NodeCache; + constructor(client: Client) { + this.client = client; + this.cache = new NodeCache({ + stdTTL: this.client.cacheTime, + maxKeys: this.client.cacheMaxKeys, + checkperiod: this.client.cacheCheckPeriod + }); + } + + set(key: string, value: any): any { + return this.cache.set(key, value); + } + + has(key: string): boolean { + return this.cache.has(key); + } + + get(key: string): any { + return this.cache.get(key); + } + + keys(): string[] { + return this.cache.keys(); + } + + size(): number { + return this.cache.keys().length; + } + + clear(): void { + this.cache.flushAll(); + } +} + +export default CacheHandler; diff --git a/src/Private/CheckUpdates.ts b/src/Private/CheckUpdates.ts new file mode 100644 index 00000000..76306663 --- /dev/null +++ b/src/Private/CheckUpdates.ts @@ -0,0 +1,31 @@ +/* eslint-disable no-console */ +import { version } from '../../package.json'; +import Errors from '../Errors'; +import axios from 'axios'; + +function compareVersions(a: string, b: string): boolean { + const pa = a.split('.'); + const pb = b.split('.'); + for (let i = 0; 3 > i; i++) { + const na = Number(pa[i]); + const nb = Number(pb[i]); + if (na > nb) return false; + if (nb > na) return true; + if (!isNaN(na) && isNaN(nb)) return false; + if (isNaN(na) && !isNaN(nb)) return true; + } + return false; +} + +export default async function (): Promise { + const request = await axios.get('https://registry.npmjs.org/hypixel-api-reborn'); + if (200 !== request.status) return console.log(Errors.UPDATER_REQUEST_NOT_OK); + const metadata = await request.data; + const latest = metadata['dist-tags'].latest; + const compare = compareVersions(version, latest); + if (compare) { + console.log( + `New version of hypixel-api-reborn is available! Current version: ${version}, Latest version: ${latest}` + ); + } +} diff --git a/src/Private/Endpoint.ts b/src/Private/Endpoint.ts new file mode 100644 index 00000000..5963a9e0 --- /dev/null +++ b/src/Private/Endpoint.ts @@ -0,0 +1,14 @@ +import Client from '../Client'; + +class Endpoint { + readonly client: Client; + constructor(client: Client) { + this.client = client; + } + + execute(query?: string, range?: any, options?: any, includeItemBytes?: any): Promise | any { + throw new Error('Command execute method is not implemented yet!'); + } +} + +export default Endpoint; diff --git a/src/Private/Requests.ts b/src/Private/Requests.ts new file mode 100644 index 00000000..f44d0a62 --- /dev/null +++ b/src/Private/Requests.ts @@ -0,0 +1,57 @@ +const BASE_URL = 'https://api.hypixel.net/v2'; +import Client from '../Client'; +import Errors from '../Errors'; +import axios from 'axios'; + +export interface RequestOptions { + raw?: boolean; + noCache?: boolean; +} + +class Requests { + readonly client: Client; + constructor(client: Client) { + this.client = client; + } + + async request(endpoint: string, options?: RequestOptions): Promise { + const info = { + raw: options?.raw ?? false, + noCache: options?.noCache ?? false + }; + if (this.client.cacheHandler.has(endpoint)) { + return this.client.cacheHandler.get(endpoint); + } + const res = await axios.get(BASE_URL + endpoint, { headers: { 'API-Key': this.client.key } }); + if (500 <= res.status && 528 > res.status) { + throw new Error( + Errors.ERROR_STATUSTEXT.replace(/{statustext}/, `Server Error : ${res.status} ${res.statusText}`) + ); + } + const parsedRes = await res.data; + if (400 === res.status) { + throw new Error( + Errors.ERROR_CODE_CAUSE.replace(/{code}/, '400 Bad Request').replace(/{cause}/, parsedRes.cause || '') + ); + } + if (403 === res.status) throw new Error(Errors.INVALID_API_KEY); + if (422 === res.status) throw new Error(Errors.UNEXPECTED_ERROR); + if (429 === res.status) throw new Error(Errors.RATE_LIMIT_EXCEEDED); + if (200 !== res.status) throw new Error(Errors.ERROR_STATUSTEXT.replace(/{statustext}/, res.statusText)); + if (!parsedRes.success && !endpoint.startsWith('/housing')) { + throw new Error(Errors.SOMETHING_WENT_WRONG.replace(/{cause}/, res.statusText)); + } + + // eslint-disable-next-line no-underscore-dangle + parsedRes._headers = res.headers; + parsedRes.raw = Boolean(info.raw); + if (info.noCache) return parsedRes; + if (this.client.cache && !info.raw) { + this.client.cacheHandler.set(endpoint, parsedRes); + } + + return parsedRes; + } +} + +export default Requests; diff --git a/src/Private/uuidCache.ts b/src/Private/uuidCache.ts new file mode 100644 index 00000000..40222e90 --- /dev/null +++ b/src/Private/uuidCache.ts @@ -0,0 +1,32 @@ +import NodeCache from 'node-cache'; +const cache = new NodeCache(); +import axios from 'axios'; + +export interface CacheData { + status: number; + id: string | null; + name: string | null; +} + +export default async function (url: string, query: string, cacheTime: number): Promise { + if (cache.has(query.toLowerCase())) return cache.get(query.toLowerCase()) as CacheData; + const res = await axios.get(url); + const data = await res.data; + // Don't cache 4xx + if (400 <= res.status) { + return { + status: res.status, + id: null, + name: null + }; + } + + cache.set(query.toLowerCase(), { status: res.status, id: data.id, name: data.name }, cacheTime); + cache.set(data.id.toLowerCase(), { status: res.status, id: data.id, name: data.name }, cacheTime); + + return { + status: res.status, + id: data.id, + name: data.name + }; +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 00000000..633d1455 --- /dev/null +++ b/src/index.ts @@ -0,0 +1 @@ +export * from './Client'; diff --git a/src/structures/APIIncident.ts b/src/structures/APIIncident.ts new file mode 100644 index 00000000..fe7a8f77 --- /dev/null +++ b/src/structures/APIIncident.ts @@ -0,0 +1,34 @@ +const regex = /https:\/\/status.hypixel.net\/incidents\/([a-z0-9]+)/; +/** + * API incident class + */ +class APIIncident { + link: string | null; + start: Date | null; + startFormatted: string | null; + startTimestamp: number | null; + author: string | null; + HTMLContent: string; + snippet: string | null; + guid: string | null; + categories: string[]; + isResolved: boolean; + constructor(data: Record) { + this.link = data.link || null; + this.start = data.pubDate ? new Date(data.pubDate) : null; + this.startFormatted = data.pubDate || null; + this.startTimestamp = new Date(data.pubDate).getTime() || null; + this.author = data.creator || null; + this.HTMLContent = data.content; + this.snippet = data.contentSnippet || null; + const match = data.guid ? regex.exec(data.guid) : null; + this.guid = match ? match[1] : null; + this.categories = data.categories || []; + this.isResolved = this.HTMLContent.includes('Resolved -') || this.HTMLContent.includes('Completed -'); + } + toString(): string { + return this.HTMLContent; + } +} + +export default APIIncident; diff --git a/src/structures/APIStatus.ts b/src/structures/APIStatus.ts new file mode 100644 index 00000000..780bca6a --- /dev/null +++ b/src/structures/APIStatus.ts @@ -0,0 +1,24 @@ +import APIIncident from './APIIncident'; +/** + * API status class + */ +class APIStatus { + sourceUrl: string | null; + title: string | null; + description: string | null; + incidents: APIIncident[]; + currentIncidents: APIIncident[]; + constructor(data: Record) { + this.sourceUrl = data.feedUrl || null; + this.title = data.title || null; + this.description = data.description || null; + this.incidents = data.items.map((x: any) => new APIIncident(x)); + this.currentIncidents = this.incidents.filter((i) => !i.isResolved); + } + + toString(): string | null { + return this.title; + } +} + +export default APIStatus; diff --git a/src/structures/Boosters/Booster.ts b/src/structures/Boosters/Booster.ts new file mode 100644 index 00000000..1daeb53c --- /dev/null +++ b/src/structures/Boosters/Booster.ts @@ -0,0 +1,43 @@ +import Game from '../Game'; + +function parseType(data: Record): 'STACKED' | 'QUEUED' | 'ACTIVE' { + if (true === data.stacked) return 'STACKED'; + if (!data.stacked) return 'QUEUED'; + return 'ACTIVE'; +} + +/** + * Booster class + */ +class Booster { + purchaser: string; + amount: number; + originalLength: number; + remaining: number; + activatedTimestamp: number; + activated: Date; + game: Game | null; + isActive: boolean; + type: 'STACKED' | 'QUEUED' | 'ACTIVE'; + stackers: string[]; + expired: boolean; + constructor(data: Record) { + this.purchaser = data.purchaserUuid; + this.amount = data.amount; + this.originalLength = data.originalLength; + this.remaining = data.length; + this.activatedTimestamp = data.dateActivated; + this.activated = new Date(data.dateActivated); + this.game = data.gameType ? new Game(data.gameType) : null; + this.isActive = Array.isArray(data.stacked); + this.type = parseType(data); + this.stackers = Array.isArray(data.stacked) ? Array.from(data.stacked) : []; + this.expired = 0 > data.length; + } + + toString(): string { + return `${this.purchaser}'s booster in ${this.game}`; + } +} + +export default Booster; diff --git a/src/structures/Color.ts b/src/structures/Color.ts new file mode 100644 index 00000000..b1483066 --- /dev/null +++ b/src/structures/Color.ts @@ -0,0 +1,81 @@ +const ColorStrings: { [key: string]: string } = { + BLACK: 'Black', + DARK_BLUE: 'Dark Blue', + DARK_GREEN: 'Dark Green', + DARK_AQUA: 'Dark Aqua', + DARK_RED: 'Dark Red', + DARK_PURPLE: 'Dark Purple', + GOLD: 'Gold', + GRAY: 'Gray', + DARK_GRAY: 'Dark Gray', + BLUE: 'Blue', + GREEN: 'Green', + AQUA: 'Aqua', + RED: 'Red', + LIGHT_PURPLE: 'Light Purple', + YELLOW: 'Yellow', + WHITE: 'White' +}; + +const ColorHex: { [key: string]: string } = { + BLACK: '#000000', + DARK_BLUE: '#0000AA', + DARK_GREEN: '#008000', + DARK_AQUA: '#00AAAA', + DARK_RED: '#AA0000', + DARK_PURPLE: '#AA00AA', + GOLD: '#FFAA00', + GRAY: '#AAAAAA', + DARK_GRAY: '#555555', + BLUE: '#5555FF', + GREEN: '#3CE63C', + AQUA: '#3CE6E6', + RED: '#FF5555', + LIGHT_PURPLE: '#FF55FF', + YELLOW: '#FFFF55', + WHITE: '#FFFFFF' +}; + +const InGameCodes: { [key: string]: string } = { + BLACK: '§0', + DARK_BLUE: '§1', + DARK_GREEN: '§2', + DARK_AQUA: '§3', + DARK_RED: '§4', + DARK_PURPLE: '§5', + GOLD: '§6', + GRAY: '§7', + DARK_GRAY: '§8', + BLUE: '§9', + GREEN: '§a', + AQUA: '§b', + RED: '§c', + LIGHT_PURPLE: '§d', + YELLOW: '§e', + WHITE: '§f' +}; + +class Color { + color: string; + constructor(color: string) { + this.color = color; + } + + toString(): string { + return ColorStrings[this.color]; + } + + toHex(): string { + return ColorHex[this.color]; + } + + toCode(): string { + return this.color; + } + + toInGameCode() { + return InGameCodes[this.color]; + } +} + +export default Color; diff --git a/src/structures/Game.ts b/src/structures/Game.ts new file mode 100644 index 00000000..52f9f198 --- /dev/null +++ b/src/structures/Game.ts @@ -0,0 +1,41 @@ +import { GameCode, GameID, GameString } from '../typings'; +import Constants from '../utils/Constants'; + +/** + * Game class + */ +class Game { + game: GameID | GameCode; + id: GameID | null; + code: GameCode | null; + name: GameString | null; + found: boolean; + + constructor(game: GameID | GameCode) { + this.game = game; + const result = Constants.games.find( + (g) => g.code.toLowerCase() === this.game || g.id.toString() === this.game || g.name.toLowerCase() === this.game + ) as any; + this.id = result ? result.id : null; + this.code = result ? result.code : null; + this.name = result ? result.name : null; + this.found = Boolean(result); + } + + toString(): GameString | null { + return this.name; + } + static get IDS(): GameID[] { + return Constants.games.map((x) => x.id as GameID); + } + + static get CODES(): GameCode[] { + return Constants.games.map((x) => x.code) as GameCode[]; + } + + static get NAMES(): GameString[] { + return Constants.games.map((x) => x.name) as GameString[]; + } +} + +export default Game; diff --git a/src/structures/GameCounts.ts b/src/structures/GameCounts.ts new file mode 100644 index 00000000..92b9ba77 --- /dev/null +++ b/src/structures/GameCounts.ts @@ -0,0 +1,28 @@ +import { recursive, removeSnakeCaseString } from '../utils/removeSnakeCase'; +import Constants from '../utils/Constants'; + +/** + * GameCounts class + */ +class GameCounts { + [x: string]: object | number; + constructor(data: Record) { + this.playerCount = data.playerCount; + for (const game in data.games) { + if (Object.prototype.hasOwnProperty.call(Constants.MiniGamesString, game)) { + const objectName = (Constants.MiniGamesString as { [key: string]: string })[game] + .toUpperCase() + .replace(/ +/g, '_'); + this[removeSnakeCaseString(objectName)] = recursive(data.games[game], true); + } else { + this[removeSnakeCaseString(game)] = recursive(data.games[game], true); + } + } + } + + toString(): number { + return this.playerCount as number; + } +} + +export default GameCounts; diff --git a/src/structures/Guild/Guild.ts b/src/structures/Guild/Guild.ts new file mode 100644 index 00000000..ecbb7190 --- /dev/null +++ b/src/structures/Guild/Guild.ts @@ -0,0 +1,69 @@ +import { calculateExpHistory, getGuildLevel, members, ranks, totalWeeklyGexp } from '../../utils/Guild'; +import { ExpHistory } from '../../typings'; +import GuildMember from './GuildMember'; +import GuildRank from './GuildRank'; +import Color from '../Color'; +import Game from '../Game'; + +class Guild { + id: string; + name: string; + description: string; + experience: number; + level: number; + members: GuildMember[]; + me: GuildMember | any; + ranks: GuildRank[]; + totalWeeklyGexp: number; + createdAtTimestamp: string; + createdAt: Date; + joinable: boolean; + publiclyListed: boolean; + chatMuteUntilTimestamp: number | null; + chatMuteUntil: Date | null; + banner: { Pattern: string; Color: string }[]; + tag: string; + tagColor: Color | null; + expHistory: ExpHistory[]; + achievements: { winners: number; experienceKings: number; onlinePlayers: number }; + preferredGames: Game[]; + + constructor(data: Record, uuid?: string) { + // eslint-disable-next-line no-underscore-dangle + this.id = data._id; + this.name = data.name; + this.description = data.description ?? ''; + this.experience = data.exp || 0; + this.level = getGuildLevel(this.experience); + this.members = members(data); + this.me = uuid ? this.members.find((member) => member.uuid === uuid) : null; + this.ranks = ranks(data); + this.totalWeeklyGexp = totalWeeklyGexp(data); + this.createdAtTimestamp = data.created; + this.createdAt = new Date(data.created); + this.joinable = data.joinable ?? false; + this.publiclyListed = Boolean(data.publiclyListed); + this.chatMuteUntilTimestamp = data.chatMute ?? null; + this.chatMuteUntil = data.chatMute ? new Date(data.chatMute) : null; + this.banner = data.banner ?? null; + this.tag = data.tag ?? null; + this.tagColor = data.tagColor ? new Color(data.tagColor) : null; + this.expHistory = calculateExpHistory(data); + this.achievements = { + winners: data.achievements.WINNERS ?? 0, + experienceKings: data.achievements.EXPERIENCE_KINGS ?? 0, + onlinePlayers: data.achievements.ONLINE_PLAYERS ?? 0 + }; + this.preferredGames = data.preferredGames ? data.preferredGames.map((g: any) => new Game(g)) : []; + } + toString(): string { + return this.name; + } + guildMaster(): GuildMember { + return this.members.find( + (member) => 'Guild Master' === member.rank || 'GUILDMASTER' === member.rank + ) as GuildMember; + } +} + +export default Guild; diff --git a/src/structures/Guild/GuildMember.ts b/src/structures/Guild/GuildMember.ts new file mode 100644 index 00000000..6fa25438 --- /dev/null +++ b/src/structures/Guild/GuildMember.ts @@ -0,0 +1,36 @@ +import { parseHistory } from '../../utils/Guild'; +import { ExpHistory } from '../../typings'; + +class GuildMember { + uuid: string; + joinedAtTimestamp: number; + joinedAt: Date; + questParticipation: number; + rank: string; + mutedUntilTimestamp: number | null; + mutedUntil: Date | null; + expHistory: ExpHistory[]; + weeklyExperience: any | null; + + constructor(data: Record) { + this.uuid = data.uuid; + this.joinedAtTimestamp = data.joined; + this.joinedAt = new Date(data.joined); + this.questParticipation = data.questParticipation || 0; + this.rank = data.rank; + this.mutedUntilTimestamp = data.mutedTill ?? null; + this.mutedUntil = data.mutedTill ? new Date(data.mutedTill) : null; + const xpCheck = data.expHistory && 'number' === typeof Object.values(data.expHistory)[0]; + this.expHistory = parseHistory(data.expHistory); + this.weeklyExperience = xpCheck ? Object.values(data.expHistory).reduce((pV: any, cV: any) => pV + cV, 0) : null; + } + /** + * UUID + * @return {string} + */ + toString() { + return this.uuid; + } +} + +export default GuildMember; diff --git a/src/structures/Guild/GuildRank.ts b/src/structures/Guild/GuildRank.ts new file mode 100644 index 00000000..eb2dfd9f --- /dev/null +++ b/src/structures/Guild/GuildRank.ts @@ -0,0 +1,22 @@ +class GuildRank { + name: string; + default: boolean; + tag: string | null; + createdAtTimestamp: number; + createdAt: Date; + priority: number; + + constructor(data: Record) { + this.name = data.name; + this.default = data.default; + this.tag = data.tag ?? null; + this.createdAtTimestamp = data.created ? data.created : data.createdAtTimestamp; + this.createdAt = new Date(data.created ? data.created : data.createdAtTimestamp); + this.priority = data.priority; + } + toString() { + return this.name; + } +} + +export default GuildRank; diff --git a/src/structures/House.ts b/src/structures/House.ts new file mode 100644 index 00000000..a48bfa10 --- /dev/null +++ b/src/structures/House.ts @@ -0,0 +1,27 @@ +/** + * House class + */ +class House { + name: string; + uuid: string; + owner: string; + createdAtTimestamp: number; + createdAt: Date; + players: number; + cookies: number; + constructor(data: Record) { + this.name = data.name || ''; + this.uuid = data.uuid || ''; + this.owner = data.owner || ''; + this.createdAtTimestamp = data.createdAt || 0; + this.createdAt = new Date(this.createdAtTimestamp); + this.players = data.players || 0; + this.cookies = data.cookies?.current || 0; + } + + toString(): string { + return this.name; + } +} + +export default House; diff --git a/src/structures/ItemBytes.ts b/src/structures/ItemBytes.ts new file mode 100644 index 00000000..997b4f47 --- /dev/null +++ b/src/structures/ItemBytes.ts @@ -0,0 +1,20 @@ +import { decode } from '../utils/SkyblockUtils'; + +/** + * Item Bytes class + */ +class ItemBytes { + bytesBuffer: Buffer; + constructor(data: Record) { + this.bytesBuffer = Buffer.from(JSON.stringify(data), 'base64'); + } + + base64(): string { + return this.bytesBuffer.toString('base64'); + } + + async readNBT(): Promise { + return await decode(this.bytesBuffer, true); + } +} +export default ItemBytes; diff --git a/src/structures/Leaderboard.ts b/src/structures/Leaderboard.ts new file mode 100644 index 00000000..e9744b7c --- /dev/null +++ b/src/structures/Leaderboard.ts @@ -0,0 +1,17 @@ +/** + * Leaderboard class + */ +class Leaderboard { + name: string | null; + title: string; + playerCount: string; + leaders: string[]; + constructor(data: Record) { + this.name = data.prefix || null; + this.title = data.title || null; + this.playerCount = data.count || 0; + this.leaders = data.leaders || []; + } +} + +export default Leaderboard; diff --git a/src/structures/MiniGames/Arcade.ts b/src/structures/MiniGames/Arcade.ts new file mode 100644 index 00000000..30a9774e --- /dev/null +++ b/src/structures/MiniGames/Arcade.ts @@ -0,0 +1,511 @@ +// IMPORTANT : a lot of the properties from the API seem to be nonsense + +import { removeSnakeCaseString } from '../../utils/removeSnakeCase'; +import { weekAB, monthAB } from '../../utils/oscillation'; +import divide from '../../utils/divide'; + +function parseZombiesKills(data: Record): Record { + const matches = Array.from(Object.keys(data)) + .map((x) => x.match(/^([A-Za-z]+)_zombie_kills_zombies$/)) + .filter((x) => x); + // From entries might be broken + return Object.fromEntries(matches.map((x: any) => [removeSnakeCaseString(x[1]), data[x[0]] || 0])); +} +/** + * Zombies - Stats by Map + Difficulty + */ +class ZombiesStats { + bestRound: number; + deaths: number; + doorsOpened: number; + fastestRound10: number; + fastestRound20: number; + fastestRound30: number; + playersRevived: number; + timesKnockedDown: number; + roundsSurvived: number; + windowsRepaired: number; + wins: number; + zombieKills: number; + constructor(data: Record, type: string = '') { + if (type) type = `_${type}`; + this.bestRound = data[`best_round_zombies${type}`] || 0; + this.deaths = data[`deaths_zombies${type}`] || 0; + this.doorsOpened = data[`doors_opened_zombies${type}`] || 0; + this.fastestRound10 = data[`fastest_time_10_zombies${type}_normal`] || 0; + this.fastestRound20 = data[`fastest_time_20_zombies${type}_normal`] || 0; + this.fastestRound30 = data[`fastest_time_30_zombies${type}_normal`] || 0; + this.playersRevived = data[`players_revived_zombies${type}`] || 0; + this.timesKnockedDown = data[`times_knocked_down_zombies${type}`] || 0; + this.roundsSurvived = data[`total_rounds_survived_zombies${type}`] || 0; + this.windowsRepaired = data[`windows_repaired_zombies${type}`] || 0; + this.wins = data[`wins_zombies${type}`] || 0; + this.zombieKills = data[`zombie_kills_zombies${type}`] || 0; + } +} + +/** + * Zombies - Overall stats + */ +class Zombies { + overall: ZombiesStats; + deadEnd: ZombiesStats; + badBlood: ZombiesStats; + alienArcadium: ZombiesStats; + prison: ZombiesStats; + killsByZombie: Record; + bulletsHit: number; + bulletsShot: number; + gunAccuracy: number; + headshots: number; + headshotAccuracy: number; + constructor(data: Record) { + this.overall = new ZombiesStats(data); + this.deadEnd = new ZombiesStats(data, 'deadend'); + this.badBlood = new ZombiesStats(data, 'badblood'); + this.alienArcadium = new ZombiesStats(data, 'alienarcadium'); + this.prison = new ZombiesStats(data, 'prison'); + this.killsByZombie = parseZombiesKills(data); + this.bulletsHit = data.bullets_hit_zombies || 0; + this.bulletsShot = data.bullets_shot_zombies || 0; + this.gunAccuracy = divide(this.bulletsHit, this.bulletsShot); + this.headshots = data.headshots_zombies || 0; + this.headshotAccuracy = divide(this.headshots, this.bulletsShot); + } +} + +/** + * Dropper stats by map + */ +class DropperMap { + bestTime: number; + completions: number; + constructor(data: Record, mapName: string) { + this.bestTime = data?.[mapName]?.best_time ?? 0; + this.completions = data?.[mapName]?.completions ?? 0; + } +} + +/** + * Blocking Dead class + */ +class BlockingDead { + wins: number; + kills: number; + headshots: number; + constructor(data: Record) { + this.wins = data.wins_dayone || 0; + this.kills = data.kills_dayone || 0; + this.headshots = data.headshots_dayone || 0; + } +} +/** + * Bounty Hunters class + */ +class BountyHunters { + wins: number; + kills: number; + deaths: number; + KDRatio: number; + bountyKills: number; + bowKills: number; + swordKills: number; + constructor(data: Record) { + this.wins = data.wins_oneinthequiver || 0; + this.kills = data.kills_oneinthequiver || 0; + this.deaths = data.deaths_oneinthequiver || 0; + this.KDRatio = divide(this.kills, this.deaths); + this.bountyKills = data.bounty_kills_oneinthequiver || 0; + this.bowKills = data.bow_kills_oneinthequiver || 0; + this.swordKills = data.sword_kills_oneinthequiver || 0; + } +} +/** + * Capture The Wool class + */ +class CaptureTheWool { + wins: number; + losses: number; + WLRatio: number; + draws: number; + kills: number; + deaths: number; + KDRatio: number; + assists: number; + woolPickedUp: number; + woolCaptured: number; + fastestWin: number; + longestGame: number; + constructor(data: Record) { + this.wins = data.woolhunt_participated_wins || 0; + this.losses = data.woolhunt_participated_losses || 0; + this.WLRatio = divide(this.wins, this.losses); + this.draws = data.woolhunt_participated_draws || 0; + this.kills = data.woolhunt_kills || 0; + this.deaths = data.woolhunt_deaths || 0; + this.KDRatio = divide(this.kills, this.deaths); + this.assists = data.woolhunt_assists || 0; + this.woolPickedUp = data.woolhunt_wools_stolen || 0; + this.woolCaptured = data.woolhunt_wools_captured || 0; + this.fastestWin = data.woolhunt_fastest_win || 0; + this.longestGame = data.woolhunt_longest_game || 0; + } +} +/** + * Dragon Wars class + */ +class DragonWars { + wins: number; + kills: number; + constructor(data: Record) { + this.wins = data.wins_dragonwars2 || 0; + this.kills = data.kills_dragonwars2 || 0; + } +} +/** + * Dropper class + */ +class Dropper { + wins: number; + fails: number; + fastestGame: number; + flawlessGames: number; + gamesPlayed: number; + mapsCompleted: number; + gamesFinished: number; + maps: Record; + constructor(data: Record) { + this.wins = data?.wins ?? 0; + this.fails = data?.fails ?? 0; + this.fastestGame = data?.fastest_game ?? 0; + this.flawlessGames = data?.flawless_games ?? 0; + this.gamesPlayed = data?.games_played ?? 0; + this.mapsCompleted = data?.maps_completed ?? 0; + this.gamesFinished = data?.games_finished ?? 0; + this.maps = {}; + Object.keys(data?.map_stats ?? {}).forEach((map) => { + this.maps[map] = new DropperMap(data?.map_stats, map); + }); + } +} +/** + * Ender Spleef class + */ +class EnderSpleef { + wins: number; + kills: number; + trail: string; + blocksDestroyed: number; + bigShotActivations: number; + tripleShotActivations: number; + totalPowerUpActivations: number; + constructor(data: Record) { + this.wins = data.wins_ender || 0; + this.kills = data.kills_dragonwars2 || 0; + this.trail = data.enderspleef_trail || ''; + this.blocksDestroyed = data.blocks_destroyed_ender || 0; + this.bigShotActivations = data.bigshot_powerup_activations_ender || 0; + this.tripleShotActivations = data.tripleshot_powerup_activations_ender || 0; + this.totalPowerUpActivations = this.bigShotActivations + this.tripleShotActivations; + } +} +/** + * Farm Hunt class + */ +class FarmHunt { + wins: number; + winsAsAnimal: number; + winsAsHunter: number; + kills: number; + killsAsAnimal: number; + killsAsHunter: number; + tauntsUsed: number; + riskyTauntsUsed: number; + safeTauntsUsed: number; + dangerousTauntsUsed: number; + fireworkTauntsUsed: number; + poop: number; + constructor(data: Record) { + this.wins = data.wins_farm_hunt || 0; + this.winsAsAnimal = data.animal_wins_farm_hunt || 0; + this.winsAsHunter = data.hunter_wins_farm_hunt || 0; + this.kills = data.kills_farm_hunt || 0; + this.killsAsAnimal = data.animal_kills_farm_hunt || 0; + this.killsAsHunter = data.hunter_kills_farm_hunt || 0; + this.tauntsUsed = data.taunts_used_farm_hunt || 0; + this.riskyTauntsUsed = data.risky_taunts_used_farm_hunt || 0; + this.safeTauntsUsed = data.safe_taunts_used_farm_hunt || 0; + this.dangerousTauntsUsed = data.dangerous_taunts_used_farm_hunt || 0; + this.fireworkTauntsUsed = data.firework_taunts_used_farm_hunt || 0; + this.poop = (data.poop_collected_farm_hunt || 0) + (data.poop_collected || 0); + } +} +/** + * Football class + */ +class Football { + wins: number; + goals: number; + kicks: number; + powerKicks: number; + constructor(data: Record) { + this.wins = data.wins_soccer || 0; + this.goals = data.goals_soccer || 0; + this.kicks = data.kicks_soccer || 0; + this.powerKicks = data.powerkicks_soccer || 0; + } +} +/** + * Galxy Wars + */ +class GalaxyWars { + wins: number; + kills: number; + deaths: number; + shotsFired: number; + weeklyKills: number; + monthlyKills: number; + attackerKills: number; + defenderKills: number; + constructor(data: Record) { + this.wins = data.sw_game_wins || 0; + this.kills = data.sw_kills || 0; + this.deaths = data.sw_deaths || 0; + this.shotsFired = data.sw_shots_fired || 0; + this.weeklyKills = parseInt(data[`weekly_kills_${weekAB()}`] || 0, 10); + this.monthlyKills = parseInt(data[`monthly_kills_${monthAB()}`] || 0, 10); + this.attackerKills = data.sw_rebel_kills || 0; + this.defenderKills = data.sw_empire_kills || 0; + } +} +/** + * Party Popper Stats (Sub gamemode of Hide and Seek) + */ +class PartyPopper { + winsAsSeeker: number; + winsAsHider: number; + wins: number; + constructor(data: Record) { + this.winsAsSeeker = data.party_pooper_seeker_wins_hide_and_seek || 0; + this.winsAsHider = data.party_pooper_hider_wins_hide_and_seek || 0; + this.wins = this.winsAsSeeker + this.winsAsHider; + } +} +/** + * Prop Hunt Stats (Sub gamemode of Hide and Seek) + */ +class PropHunt { + winsAsSeeker: number; + winsAsHider: number; + wins: number; + constructor(data: Record) { + this.winsAsSeeker = data.prop_hunt_seeker_wins_hide_and_seek || 0; + this.winsAsHider = data.prop_hunt_hider_wins_hide_and_seek || 0; + this.wins = this.winsAsSeeker + this.winsAsHider; + } +} +/** + * Hide And Seek Stats + */ +class HideAndSeek { + partyPopper: PartyPopper; + propHunt: PropHunt; + winsAsSeeker: number; + winsAsHider: number; + constructor(data: Record) { + this.partyPopper = new PartyPopper(data); + this.propHunt = new PropHunt(data); + this.winsAsSeeker = data.seeker_wins_hide_and_seek || 0; + this.winsAsHider = data.hider_wins_hide_and_seek || 0; + } +} +/** + * Hide And Seek Stats + */ +class HoleInTheWall { + wins: number; + rounds: number; + scoreRecordFinals: number; + scoreRecordNormal: number; + scoreRecordOverall: number; + constructor(data: Record) { + this.wins = data.wins_hole_in_the_wall || 0; + this.rounds = data.rounds_hole_in_the_wall || 0; + this.scoreRecordFinals = data.hitw_record_f || 0; + this.scoreRecordNormal = data.hitw_record_q || 0; + this.scoreRecordOverall = this.scoreRecordFinals + this.scoreRecordNormal; + } +} +/** + * Hypixel Says Stats + */ +class HypixelSays { + wins: number; + rounds: number; + roundWins: number; + topScore: number; + constructor(data: Record) { + this.wins = data.wins_simon_says || 0; + this.rounds = data.rounds_simon_says || 0; + this.roundWins = data.round_wins_simon_says || 0; + this.topScore = data.top_score_simon_says || 0; + } +} +/** + * Mini Walls class + */ +class MiniWalls { + kit: string; + wins: number; + kills: number; + deaths: number; + KDRatio: number; + finalKills: number; + witherKills: number; + witherDamage: number; + arrowsShot: number; + arrowsHit: number; + bowAccuracy: number; + constructor(data: Record) { + this.kit = data.miniWalls_activeKit || ''; + this.wins = data.wins_mini_walls || 0; + this.kills = data.kills_mini_walls || 0; + this.deaths = data.deaths_mini_walls || 0; + this.KDRatio = divide(this.kills, this.deaths); + this.finalKills = data.final_kills_mini_walls || 0; + this.witherKills = data.wither_kills_mini_walls || 0; + this.witherDamage = data.wither_damage_mini_walls || 0; + this.arrowsShot = data.arrows_shot_mini_walls || 0; + this.arrowsHit = data.arrows_hit_mini_walls || 0; + this.bowAccuracy = divide(this.arrowsHit, this.arrowsShot); + } +} +/** + * Party Games class + */ +class PartyGames { + wins: number; + roundWins: number; + stars: number; + constructor(data: Record) { + this.wins = data.wins_party || 0; + this.roundWins = data.round_wins_party || 0; + this.stars = data.total_stars_party || 0; + } +} +/** + * Pixel Party Game Mode + */ +class PixelPartyGameMode { + wins: number; + gamesPlayed: number; + losses: number; + WLRatio: number; + roundsPlayed: number; + powerUpsCollected: number; + constructor(data: Record, modeName: string) { + this.wins = data?.[`wins_${modeName}`] || 0; + this.gamesPlayed = data?.[`games_played_${modeName}`] || 0; + this.losses = this.gamesPlayed - this.wins; + this.WLRatio = divide(this.wins, this.losses); + this.roundsPlayed = data?.[`rounds_completed_${modeName}`] || 0; + this.powerUpsCollected = data?.[`power_ups_collected_${modeName}`] || 0; + } +} +/** + * Party Games class + */ +class PixelParty { + wins: number; + gamesPlayed: number; + losses: number; + WLRatio: number; + roundsPlayed: number; + powerUpsCollected: number; + normal: PixelPartyGameMode; + hyper: PixelPartyGameMode; + highestRound: number; + musicVolume: number; + colorBlind: object; + constructor(data: Record) { + this.wins = data?.pixel_party?.wins || 0; + this.gamesPlayed = data?.pixel_party?.games_played || 0; + this.losses = this.gamesPlayed - this.wins; + this.WLRatio = divide(this.wins, this.losses); + this.roundsPlayed = data?.pixel_party?.rounds_completed || 0; + this.powerUpsCollected = data?.pixel_party?.power_ups_collected || 0; + this.normal = new PixelPartyGameMode(data.pixel_party, 'normal'); + this.hyper = new PixelPartyGameMode(data.pixel_party, 'hyper'); + this.highestRound = data?.pixel_party?.highest_round || 0; + this.musicVolume = data.pixel_party_music_volume || 0; + this.colorBlind = data.pixelparty || {}; + } +} +/** + * Throw Out class + */ +class ThrowOut { + wins: number; + kills: number; + deaths: number; + KDRatio: number; + constructor(data: Record) { + this.wins = data.wins_throw_out || 0; + this.kills = data.kills_throw_out || 0; + this.deaths = data.deaths_throw_out || 0; + this.KDRatio = divide(this.kills, this.deaths); + } +} +/** + * Arcade class + */ +class Arcade { + coins: number; + weeklyCoins: number; + monthlyCoins: number; + hintsDisabled: boolean; + flashDisabled: boolean; + blockingDead: BlockingDead; + bountyHunters: BountyHunters; + captureTheWool: CaptureTheWool; + dragonWars: DragonWars; + dropper: Dropper; + enderSpleef: EnderSpleef; + farmHunt: FarmHunt; + football: Football; + galaxyWars: GalaxyWars; + hideAndSeek: HideAndSeek; + holeInTheWall: HoleInTheWall; + hypixelSays: HypixelSays; + miniWalls: MiniWalls; + partyGames: PartyGames; + pixelParty: PixelParty; + throwOut: ThrowOut; + zombies: Zombies; + constructor(data: Record = {}) { + this.coins = data.coins || 0; + this.weeklyCoins = parseInt(data[`weekly_coins_${weekAB()}`] || 0, 10); + this.monthlyCoins = parseInt(data[`monthly_coins_${monthAB()}`] || 0, 10); + this.hintsDisabled = !data.hints; + this.flashDisabled = !data.flash; + this.blockingDead = new BlockingDead(data); + this.bountyHunters = new BountyHunters(data); + this.captureTheWool = new CaptureTheWool(data); + this.dragonWars = new DragonWars(data); + this.dropper = new Dropper(data.dropper); + this.enderSpleef = new EnderSpleef(data); + this.farmHunt = new FarmHunt(data); + this.football = new Football(data); + this.galaxyWars = new GalaxyWars(data); + this.hideAndSeek = new HideAndSeek(data); + this.holeInTheWall = new HoleInTheWall(data); + this.hypixelSays = new HypixelSays(data); + this.miniWalls = new MiniWalls(data); + this.partyGames = new PartyGames(data); + this.pixelParty = new PixelParty(data); + this.throwOut = new ThrowOut(data); + this.zombies = new Zombies(data); + } +} + +export default Arcade; diff --git a/src/structures/MiniGames/ArenaBrawl.ts b/src/structures/MiniGames/ArenaBrawl.ts new file mode 100644 index 00000000..6032be30 --- /dev/null +++ b/src/structures/MiniGames/ArenaBrawl.ts @@ -0,0 +1,90 @@ +import divide from '../../utils/divide'; + +class ArenaBrawlMode { + damage: number; + kills: number; + deaths: number; + KDRatio: number; + healed: number; + wins: number; + losses: number; + WLRatio: number; + games: number; + winstreak: number; + constructor(data: Record, mode: string) { + this.damage = data[`damage_${mode}`]; + this.kills = data[`kills_${mode}`]; + this.deaths = data[`deaths_${mode}`]; + this.KDRatio = divide(this.kills, this.deaths); + this.healed = data[`healed_${mode}`]; + this.wins = data[`wins_${mode}`]; + this.losses = data[`losses_${mode}`]; + this.WLRatio = divide(this.wins, this.losses); + this.games = data[`games_${mode}`]; + this.winstreak = data[`win_streaks_${mode}`]; + } +} + +/** + * ArenaBrawl class + */ +class ArenaBrawl { + coins: number; + coinsSpent: number; + wins: number; + keys: number; + chests: number; + rune: string; + '1v1': ArenaBrawlMode; + '2v2': ArenaBrawlMode; + '4v4': ArenaBrawlMode; + constructor(data: Record) { + /** + * Coins + * @type {number} + */ + this.coins = data.coins || 0; + /** + * Coins Spent + * @type {number} + */ + this.coinsSpent = data.coins_spent || 0; + /** + * Wins + * @type {number} + */ + this.wins = data.wins || 0; + /** + * Keys + * @type {number} + */ + this.keys = data.keys || 0; + /** + * Chests + * @type {number} + */ + this.chests = data.magical_chest || 0; + /** + * Rune + * @type {string} + */ + this.rune = data.active_rune || ''; + /** + * ArenaBrawl mode stats + * @type {ArenaBrawlMode} + */ + this['1v1'] = new ArenaBrawlMode(data, '1v1'); + /** + * ArenaBrawl mode stats + * @type {ArenaBrawlMode} + */ + this['2v2'] = new ArenaBrawlMode(data, '2v2'); + /** + * ArenaBrawl mode stats + * @type {ArenaBrawlMode} + */ + this['4v4'] = new ArenaBrawlMode(data, '4v4'); + } +} + +export default ArenaBrawl; diff --git a/src/structures/MiniGames/BedWars.ts b/src/structures/MiniGames/BedWars.ts new file mode 100644 index 00000000..a2a44ec9 --- /dev/null +++ b/src/structures/MiniGames/BedWars.ts @@ -0,0 +1,322 @@ +import { + BedWarsCollectedItems, + BedWarsPracticeStats, + BedwarsDreamStats, + BedWarsModeStats, + BedWarsPrestige, + BedWarsBeds, + BedWarsAvg +} from '../../typings'; +import divide from '../../utils/divide'; + +function generateStatsForMode(data: Record, mode: string): BedWarsModeStats { + return { + winstreak: data[`${mode}_winstreak`] || 0, + playedGames: data[`${mode}_games_played_bedwars`] || 0, + + kills: data[`${mode}_kills_bedwars`] || 0, + deaths: data[`${mode}_deaths_bedwars`] || 0, + + wins: data[`${mode}_wins_bedwars`] || 0, + losses: data[`${mode}_losses_bedwars`] || 0, + + finalKills: data[`${mode}_final_kills_bedwars`] || 0, + finalDeaths: data[`${mode}_final_deaths_bedwars`] || 0, + + beds: { + broken: data[`${mode}_beds_broken_bedwars`] || 0, + lost: data[`${mode}_beds_lost_bedwars`] || 0, + BLRatio: divide(data[`${mode}_beds_broken_bedwars`], data[`${mode}_beds_lost_bedwars`]) + }, + + avg: { + kills: divide(data[`${mode}_kills_bedwars`], data[`${mode}_games_played_bedwars`]), + finalKills: divide(data[`${mode}_final_kills_bedwars`], data[`${mode}_games_played_bedwars`]), + bedsBroken: divide(data[`${mode}_beds_broken_bedwars`], data[`${mode}_games_played_bedwars`]) + }, + + KDRatio: divide(data[`${mode}_kills_bedwars`], data[`${mode}_deaths_bedwars`]), + WLRatio: divide(data[`${mode}_wins_bedwars`], data[`${mode}_losses_bedwars`]), + finalKDRatio: divide(data[`${mode}_final_kills_bedwars`], data[`${mode}_final_deaths_bedwars`]) + }; +} + +function getBedWarsPrestige(level: number): BedWarsPrestige | string { + if (5000 <= level) return 'Eternal'; + return ( + [ + 'Stone', + 'Iron', + 'Gold', + 'Diamond', + 'Emerald', + 'Sapphire', + 'Ruby', + 'Crystal', + 'Opal', + 'Amethyst', + 'Rainbow', + 'Iron Prime', + 'Gold Prime', + 'Diamond Prime', + 'Emerald Prime', + 'Sapphire Prime', + 'Ruby Prime', + 'Crystal Prime', + 'Opal Prime', + 'Amethyst Prime', + 'Mirror', + 'Light', + 'Dawn', + 'Dusk', + 'Air', + 'Wind', + 'Nebula', + 'Thunder', + 'Earth', + 'Water', + 'Fire', + 'Sunrise', + 'Eclipse', + 'Gamma', + 'Majestic', + 'Andesine', + 'Marine', + 'Element', + 'Galaxy', + 'Atomic', + 'Sunset', + 'Time', + 'Winter', + 'Obsidian', + 'Spring', + 'Ice', + 'Summer', + 'Spinel', + 'Autumn', + 'Mystic', + 'Eternal' + ][Math.floor(level / 100)] || 'Eternal' + ); +} +const EASY_LEVELS = 4; +const EASY_LEVELS_XP = 7000; +const XP_PER_PRESTIGE = 96 * 5000 + EASY_LEVELS_XP; +const LEVELS_PER_PRESTIGE = 100; +const HIGHEST_PRESTIGE = 10; + +function getLevelRespectingPrestige(level: number) { + if (level > HIGHEST_PRESTIGE * LEVELS_PER_PRESTIGE) { + return level - HIGHEST_PRESTIGE * LEVELS_PER_PRESTIGE; + } + return level % LEVELS_PER_PRESTIGE; +} + +function getExpForLevel(level: number) { + if (0 === level) return 0; + const respectedLevel = getLevelRespectingPrestige(level); + if (respectedLevel > EASY_LEVELS) return 5000; + switch (respectedLevel) { + case 1: + return 500; + case 2: + return 1000; + case 3: + return 2000; + case 4: + return 3500; + default: { + return 5000; + } + } +} + +function getLevelForExp(exp: number) { + const prestiges = Math.floor(exp / XP_PER_PRESTIGE); + let level = prestiges * LEVELS_PER_PRESTIGE; + let expWithoutPrestiges = exp - prestiges * XP_PER_PRESTIGE; + + for (let i = 1; i <= EASY_LEVELS; ++i) { + const expForEasyLevel = getExpForLevel(i); + if (expWithoutPrestiges < expForEasyLevel) { + break; + } + level++; + expWithoutPrestiges -= expForEasyLevel; + } + return level + Math.floor(expWithoutPrestiges / 5000); +} + +function generateStatsForPractice(data: Record): BedWarsPracticeStats { + return { + selected: data?.practice?.selected || 'NONE', + bridging: { + blocksPlaced: data?.practice?.bridging?.blocks_placed ?? 0, + attempts: { + failed: data?.practice?.bridging?.failed_attempts ?? 0, + successful: data?.practice?.bridging?.successful_attempts ?? 0, + total: data?.practice?.bridging?.failed_attempts + data?.practice?.bridging?.successful_attempts + }, + records: { + blocks30: { + elevation: { + none: { + straight: data?.practice?.records?.['bridging_distance_30:elevation_NONE:angle_STRAIGHT:'] ?? 0, + diagonal: data?.practice?.records?.['bridging_distance_30:elevation_NONE:angle_DIAGONAL:'] ?? 0 + }, + slight: { + straight: data?.practice?.records?.['bridging_distance_30:elevation_SLIGHT:angle_STRAIGHT:'] ?? 0, + diagonal: data?.practice?.records?.['bridging_distance_30:elevation_SLIGHT:angle_DIAGONAL:'] ?? 0 + }, + staircase: { + straight: data?.practice?.records?.['bridging_distance_30:elevation_STAIRCASE:angle_STRAIGHT:'] ?? 0, + diagonal: data?.practice?.records?.['bridging_distance_30:elevation_STAIRCASE:angle_DIAGONAL:'] ?? 0 + } + } + }, + blocks50: { + elevation: { + none: { + straight: data?.practice?.records?.['bridging_distance_50:elevation_NONE:angle_STRAIGHT:'] ?? 0, + diagonal: data?.practice?.records?.['bridging_distance_50:elevation_NONE:angle_DIAGONAL:'] ?? 0 + }, + slight: { + straight: data?.practice?.records?.['bridging_distance_50:elevation_SLIGHT:angle_STRAIGHT:'] ?? 0, + diagonal: data?.practice?.records?.['bridging_distance_50:elevation_SLIGHT:angle_DIAGONAL:'] ?? 0 + }, + staircase: { + straight: data?.practice?.records?.['bridging_distance_50:elevation_STAIRCASE:angle_STRAIGHT:'] ?? 0, + diagonal: data?.practice?.records?.['bridging_distance_50:elevation_STAIRCASE:angle_DIAGONAL:'] ?? 0 + } + } + }, + blocks100: { + elevation: { + none: { + straight: data?.practice?.records?.['bridging_distance_100:elevation_NONE:angle_STRAIGHT:'] ?? 0, + diagonal: data?.practice?.records?.['bridging_distance_100:elevation_NONE:angle_DIAGONAL:'] ?? 0 + }, + slight: { + straight: data?.practice?.records?.['bridging_distance_100:elevation_SLIGHT:angle_STRAIGHT:'] ?? 0, + diagonal: data?.practice?.records?.['bridging_distance_100:elevation_SLIGHT:angle_DIAGONAL:'] ?? 0 + }, + staircase: { + straight: data?.practice?.records?.['bridging_distance_100:elevation_STAIRCASE:angle_STRAIGHT:'] ?? 0, + diagonal: data?.practice?.records?.['bridging_distance_100:elevation_STAIRCASE:angle_DIAGONAL:'] ?? 0 + } + } + } + } + }, + fireballJumping: { + blocksPlaced: data?.practice?.fireball_jumping?.blocks_placed ?? 0, + attempts: { + failed: data?.practice?.fireball_jumping?.failed_attempts ?? 0, + successful: data?.practice?.fireball_jumping?.successful_attempts ?? 0, + total: data?.practice?.fireball_jumping?.failed_attempts + data?.practice?.fireball_jumping?.successful_attempts + } + }, + pearlClutching: { + attempts: { + failed: data?.practice?.pearl_clutching?.failed_attempts ?? 0, + successful: data?.practice?.pearl_clutching?.successful_attempts ?? 0, + total: data?.practice?.pearl_clutching?.failed_attempts + data?.practice?.pearl_clutching?.successful_attempts + } + }, + mlg: { + blocksPlaced: data?.practice?.mlg?.blocks_placed ?? 0, + attempts: { + failed: data?.practice?.mlg?.failed_attempts ?? 0, + successful: data?.practice?.mlg?.successful_attempts ?? 0, + total: data?.practice?.mlg?.failed_attempts + data?.practice?.mlg?.successful_attempts + } + } + }; +} +/** + * BedWars class + */ +class BedWars { + tokens: number; + level: number; + experience: number; + prestige: BedWarsPrestige | string; + playedGames: number; + wins: number; + winstreak: number; + kills: number; + finalKills: number; + losses: number; + deaths: number; + finalDeaths: number; + collectedItemsTotal: BedWarsCollectedItems; + beds: BedWarsBeds; + avg: BedWarsAvg; + KDRatio: number; + finalKDRatio: number; + WLRatio: number; + solo: BedWarsModeStats; + doubles: BedWarsModeStats; + threes: BedWarsModeStats; + fours: BedWarsModeStats; + '4v4': BedWarsModeStats; + dream: BedwarsDreamStats | object; + castle: BedWarsModeStats; + practice: BedWarsPracticeStats; + slumberTickets: number; + totalSlumberTicket: number; + constructor(data: Record) { + this.tokens = data.coins || 0; + this.level = data.Experience ? getLevelForExp(data.Experience) : 0; + this.experience = data.Experience || 0; + this.prestige = getBedWarsPrestige(getLevelForExp(data.Experience)); + this.playedGames = data.games_played_bedwars || 0; + this.wins = data.wins_bedwars || 0; + this.winstreak = data.winstreak || 0; + this.kills = data.kills_bedwars || 0; + this.finalKills = data.final_kills_bedwars || 0; + this.losses = data.losses_bedwars || 0; + this.deaths = data.deaths_bedwars || 0; + this.finalDeaths = data.final_deaths_bedwars || 0; + this.collectedItemsTotal = { + iron: data.iron_resources_collected_bedwars || 0, + gold: data.gold_resources_collected_bedwars || 0, + diamond: data.diamond_resources_collected_bedwars || 0, + emerald: data.emerald_resources_collected_bedwars || 0 + }; + this.beds = { + lost: data.beds_lost_bedwars || 0, + broken: data.beds_broken_bedwars || 0, + BLRatio: divide(data.beds_broken_bedwars, data.beds_lost_bedwars) + }; + this.avg = { + kills: divide(this.kills, this.playedGames), + finalKills: divide(this.finalKills, this.playedGames), + bedsBroken: divide(this.beds.broken, this.playedGames) + }; + this.KDRatio = divide(this.kills, this.deaths); + this.finalKDRatio = divide(this.finalKills, this.finalDeaths); + this.WLRatio = divide(this.wins, this.losses); + this.solo = generateStatsForMode(data, 'eight_one'); + this.doubles = generateStatsForMode(data, 'eight_two'); + this.threes = generateStatsForMode(data, 'four_three'); + this.fours = generateStatsForMode(data, 'four_four'); + this['4v4'] = generateStatsForMode(data, 'two_four'); + this.dream = ['ultimate', 'rush', 'armed', 'lucky', 'voidless'].reduce( + (ac, mode) => ({ + [mode]: { + doubles: generateStatsForMode(data, `eight_two_${mode}`), + fours: generateStatsForMode(data, `four_four_${mode}`) + }, + ...ac + }), + {} + ); + this.castle = generateStatsForMode(data, 'castle'); + this.practice = generateStatsForPractice(data); + this.slumberTickets = data.slumber?.tickets ?? 0; + this.totalSlumberTicket = data.slumber?.total_tickets ?? 0; + } +} + +export default BedWars; diff --git a/src/structures/MiniGames/BlitzSurvivalGames.ts b/src/structures/MiniGames/BlitzSurvivalGames.ts new file mode 100644 index 00000000..96da098a --- /dev/null +++ b/src/structures/MiniGames/BlitzSurvivalGames.ts @@ -0,0 +1,176 @@ +import divide from '../../utils/divide'; + +class BlitzSGKit { + level: number; + exp: number; + kills: number; + deaths: number; + KDRatio: number; + wins: number; + gamesPlayed: number; + losses: number; + WLRatio: number; + arrowsShot: number; + arrowsHit: number; + bowAccuracy: number; + damage: number; + damageTaken: number; + potionsDrunk: number; + potionsThrown: number; + playTime: number; + mobsSpawned: number; + chestsOpened: number; + constructor(data: Record, kitName: string) { + this.level = data[kitName] || 0; + this.exp = data[`exp_${kitName}`] || 0; + this.kills = data[`kills_${kitName}`] || 0; + this.deaths = data[`deaths_${kitName}`] || 0; + this.KDRatio = divide(this.kills, this.deaths); + this.wins = data[`wins_${kitName}`] || 0; + this.gamesPlayed = data[`games_played_${kitName}`] || 0; + this.losses = this.gamesPlayed - this.wins; + this.WLRatio = divide(this.wins, this.losses); + this.arrowsShot = data[`arrows_fired_${kitName}`] || 0; + this.arrowsHit = data[`arrows_hit_${kitName}`] || 0; + this.bowAccuracy = divide(this.arrowsHit, this.arrowsShot); + this.damage = data[`damage_${kitName}`] || 0; + this.damageTaken = data[`damage_taken_${kitName}`] || 0; + this.potionsDrunk = data[`potions_drunk_${kitName}`] || 0; + this.potionsThrown = data[`potions_thrown_${kitName}`] || 0; + this.playTime = data[`time_played_${kitName}`] || 0; + this.mobsSpawned = data[`mobs_spawned_${kitName}`] || 0; + this.chestsOpened = data[`chests_opened_${kitName}`] || 0; + } +} + +/** + * Blitz SG class + */ +class BlitzSurvivalGames { + coins: number; + kills: number; + kit: string; + killsSolo: number; + killsTeams: number; + deaths: number; + KDRatio: number; + wins: number; + winsSolo: number; + winsTeam: number; + gamesPlayed: number; + losses: number; + WLRatio: number; + arrowsShot: number; + arrowsHit: number; + bowAccuracy: number; + damage: number; + damageTaken: number; + potionsDrunk: number; + potionsThrown: number; + mobsSpawned: number; + playTime: number; + blitzUses: number; + chestsOpened: number; + archer: BlitzSGKit; + meatmaster: BlitzSGKit; + speleologist: BlitzSGKit; + baker: BlitzSGKit; + knight: BlitzSGKit; + guardian: BlitzSGKit; + scout: BlitzSGKit; + hunter: BlitzSGKit; + hypeTrain: BlitzSGKit; + fisherman: BlitzSGKit; + armorer: BlitzSGKit; + horsetamer: BlitzSGKit; + astronaut: BlitzSGKit; + troll: BlitzSGKit; + reaper: BlitzSGKit; + shark: BlitzSGKit; + reddragon: BlitzSGKit; + toxicologist: BlitzSGKit; + rogue: BlitzSGKit; + warlock: BlitzSGKit; + slimeyslime: BlitzSGKit; + jockey: BlitzSGKit; + golem: BlitzSGKit; + viking: BlitzSGKit; + shadowKnight: BlitzSGKit; + pigman: BlitzSGKit; + paladin: BlitzSGKit; + necromancer: BlitzSGKit; + florist: BlitzSGKit; + diver: BlitzSGKit; + arachnologist: BlitzSGKit; + blaze: BlitzSGKit; + wolftamer: BlitzSGKit; + tim: BlitzSGKit; + farmer: BlitzSGKit; + creepertamer: BlitzSGKit; + snowman: BlitzSGKit; + constructor(data: Record) { + this.coins = data.coins || 0; + this.kills = data.kills || 0; + this.kit = data.defaultkit || ''; + this.killsSolo = data.kills_solo_normal || 0; + this.killsTeams = data.kills_teams_normal || 0; + this.deaths = data.deaths || 0; + this.KDRatio = divide(this.kills, this.deaths); + this.wins = data.wins || 0; + this.winsSolo = data.wins_solo_normal || 0; + this.winsTeam = data.wins_teams || 0; + this.gamesPlayed = data.games_played || 0; + this.losses = this.gamesPlayed - this.wins; + this.WLRatio = divide(this.wins, this.losses); + this.arrowsShot = data.arrows_fired || 0; + this.arrowsHit = data.arrows_hit || 0; + this.bowAccuracy = divide(this.arrowsHit, this.arrowsShot); + this.damage = data.damage || 0; + this.damageTaken = data.damage_taken || 0; + this.potionsDrunk = data.potions_drunk || 0; + this.potionsThrown = data.potions_thrown || 0; + this.mobsSpawned = data.mobs_spawned || 0; + this.playTime = data.time_played || 0; + this.blitzUses = data.blitz_uses || 0; + this.chestsOpened = data.chests_opened || 0; + this.archer = new BlitzSGKit(data, 'archer'); + this.meatmaster = new BlitzSGKit(data, 'meatmaster'); + this.speleologist = new BlitzSGKit(data, 'speleologist'); + this.baker = new BlitzSGKit(data, 'baker'); + this.knight = new BlitzSGKit(data, 'knight'); + this.guardian = new BlitzSGKit(data, 'guardian'); + this.scout = new BlitzSGKit(data, 'scout'); + this.hunter = new BlitzSGKit(data, 'hunter'); + this.hypeTrain = new BlitzSGKit(data, 'hype train'); + this.fisherman = new BlitzSGKit(data, 'fisherman'); + this.armorer = new BlitzSGKit(data, 'armorer'); + this.horsetamer = new BlitzSGKit(data, 'horsetamer'); + this.astronaut = new BlitzSGKit(data, 'astronaut'); + this.troll = new BlitzSGKit(data, 'troll'); + this.reaper = new BlitzSGKit(data, 'reaper'); + this.shark = new BlitzSGKit(data, 'shark'); + this.reddragon = new BlitzSGKit(data, 'reddragon'); + this.toxicologist = new BlitzSGKit(data, 'toxicologist'); + this.rogue = new BlitzSGKit(data, 'rogue'); + this.warlock = new BlitzSGKit(data, 'warlock'); + this.slimeyslime = new BlitzSGKit(data, 'slimeyslime'); + this.jockey = new BlitzSGKit(data, 'jockey'); + this.golem = new BlitzSGKit(data, 'golem'); + this.viking = new BlitzSGKit(data, 'viking'); + this.shadowKnight = new BlitzSGKit(data, 'shadow knight'); + this.pigman = new BlitzSGKit(data, 'pigman'); + this.paladin = new BlitzSGKit(data, 'paladin'); + this.necromancer = new BlitzSGKit(data, 'necromancer'); + this.florist = new BlitzSGKit(data, 'florist'); + this.diver = new BlitzSGKit(data, 'diver'); + this.arachnologist = new BlitzSGKit(data, 'arachnologist'); + this.blaze = new BlitzSGKit(data, 'blaze'); + this.wolftamer = new BlitzSGKit(data, 'wolftamer'); + this.tim = new BlitzSGKit(data, 'tim'); + this.farmer = new BlitzSGKit(data, 'farmer'); + this.creepertamer = new BlitzSGKit(data, 'creepertamer'); + this.snowman = new BlitzSGKit(data, 'snowman'); + } +} + +export default BlitzSurvivalGames; diff --git a/src/structures/MiniGames/BuildBattle.ts b/src/structures/MiniGames/BuildBattle.ts new file mode 100644 index 00000000..4c6ef231 --- /dev/null +++ b/src/structures/MiniGames/BuildBattle.ts @@ -0,0 +1,32 @@ +import { BuildBattleWins } from '../../typings'; +import divide from '../../utils/divide'; +/** + * BuildBattle class + */ +class BuildBattle { + score: number; + totalWins: number; + games: number; + WLRatio: number; + superVotes: number; + coins: number; + totalVotes: number; + wins: BuildBattleWins; + constructor(data: Record) { + this.score = data.score || 0; + this.totalWins = data.wins || 0; + this.games = data.games_played || 0; + this.WLRatio = divide(this.totalWins, this.games); + this.superVotes = data.super_votes || 0; + this.coins = data.coins || 0; + this.totalVotes = data.total_votes || 0; + this.wins = { + solo: data.wins_solo_normal || 0, + teams: data.wins_teams_normal || 0, + pro: data.wins_solo_pro || 0, + gtb: data.wins_guess_the_build || 0 + }; + } +} + +export default BuildBattle; diff --git a/src/structures/MiniGames/CopsAndCrims.ts b/src/structures/MiniGames/CopsAndCrims.ts new file mode 100644 index 00000000..106199e4 --- /dev/null +++ b/src/structures/MiniGames/CopsAndCrims.ts @@ -0,0 +1,139 @@ +import divide from '../../utils/divide'; +/** + * Cops and crims Defusal class + */ +class CopsAndCrimsDefusal { + kills: number; + headshotKills: number; + assists: number; + deaths: number; + KDRatio: number; + wins: number; + gamesPlayed: number; + losses: number; + WLRatio: number; + roundWins: number; + shotsFired: number; + bombsDefused: number; + bombsPlanted: number; + killsAsCrim: number; + killsAsCop: number; + constructor(data: Record) { + this.kills = data.kills || 0; + this.headshotKills = data.headshot_kills || 0; + this.assists = data.assists || 0; + this.deaths = data.deaths || 0; + this.KDRatio = divide(this.kills, this.deaths); + this.wins = data.game_wins || 0; + this.gamesPlayed = data.game_plays || 0; + this.losses = this.gamesPlayed - this.wins; + this.WLRatio = divide(this.wins, this.losses); + this.roundWins = data.round_wins || 0; + this.shotsFired = data.shots_fired || 0; + this.bombsDefused = data.bombs_defused || 0; + this.bombsPlanted = data.bombs_planted || 0; + this.killsAsCrim = data.criminal_kills || 0; + this.killsAsCop = data.cop_kills || 0; + } +} +/** + * Cops and crims Deathmatch class + */ +class CopsAndCrimsDeathmatch { + kills: number; + assists: number; + deaths: number; + KDRatio: number; + wins: number; + gamesPlayed: number; + losses: number; + WLRatio: number; + killsAsCrim: number; + killsAsCop: number; + constructor(data: Record) { + this.kills = data.kills_deathmatch || 0; + this.assists = data.assists_deathmatch || 0; + this.deaths = data.deaths_deathmatch || 0; + this.KDRatio = divide(this.kills, this.deaths); + this.wins = data.game_wins_deathmatch || 0; + this.gamesPlayed = data.game_plays_deathmatch || 0; + this.losses = this.gamesPlayed - this.wins; + this.WLRatio = divide(this.wins, this.losses); + this.killsAsCrim = data.criminal_kills_deathmatch || 0; + this.killsAsCop = data.cop_kills_deathmatch || 0; + } +} +/** + * Cops and crims Gun Game class + */ +class CopsAndCrimsGunGame { + kills: number; + assists: number; + deaths: number; + KDRatio: number; + wins: number; + gamesPlayed: number; + losses: number; + WLRatio: number; + killsAsCrim: number; + killsAsCop: number; + fastestWin: number; + constructor(data: Record) { + this.kills = data.kills_gungame || 0; + this.assists = data.assists_gungame || 0; + this.deaths = data.deaths_gungame || 0; + this.KDRatio = divide(this.kills, this.deaths); + this.wins = data.game_wins_gungame || 0; + this.gamesPlayed = data.game_plays_gungame || 0; + this.losses = this.gamesPlayed - this.wins; + this.WLRatio = divide(this.wins, this.losses); + this.killsAsCrim = data.criminal_kills_gungame || 0; + this.killsAsCop = data.cop_kills_gungame || 0; + this.fastestWin = data.fastest_win_gungame || 0; + } +} +/** + * Cops and crims class + */ +class CopsAndCrims { + defusal: CopsAndCrimsDefusal; + deathmath: CopsAndCrimsDeathmatch; + gunGame: CopsAndCrimsGunGame; + coins: number; + kills: number; + assists: number; + deaths: number; + KDRatio: number; + wins: number; + gamesPlayed: number; + losses: number; + WLRatio: number; + killsAsCrim: number; + killsAsCop: number; + prefixColor: string; + showPrefix: boolean; + selectedPrefix: string; + killsInPrefix: boolean; + constructor(data: Record) { + this.defusal = new CopsAndCrimsDefusal(data); + this.deathmath = new CopsAndCrimsDeathmatch(data); + this.gunGame = new CopsAndCrimsGunGame(data); + this.coins = data.coins || 0; + this.kills = this.defusal.kills + this.deathmath.kills + this.gunGame.kills; + this.assists = this.defusal.assists + this.deathmath.assists + this.gunGame.assists; + this.deaths = this.defusal.deaths + this.deathmath.deaths + this.gunGame.deaths; + this.KDRatio = divide(this.kills, this.deaths); + this.wins = this.defusal.wins + this.deathmath.wins + this.gunGame.wins; + this.gamesPlayed = this.defusal.gamesPlayed + this.deathmath.gamesPlayed + this.gunGame.gamesPlayed; + this.losses = this.gamesPlayed - this.wins; + this.WLRatio = divide(this.wins, this.losses); + this.killsAsCrim = this.defusal.killsAsCrim + this.deathmath.killsAsCrim + this.gunGame.killsAsCrim; + this.killsAsCop = this.defusal.killsAsCop + this.deathmath.killsAsCop + this.gunGame.killsAsCop; + this.prefixColor = data.lobbyPrefixColor || ''; + this.showPrefix = data.show_lobby_prefix || false; + this.selectedPrefix = data.selected_lobby_prefix || ''; + this.killsInPrefix = data.show_kills_in_prefix || false; + } +} + +export default CopsAndCrims; diff --git a/src/structures/MiniGames/Duels.ts b/src/structures/MiniGames/Duels.ts new file mode 100644 index 00000000..17f01a2f --- /dev/null +++ b/src/structures/MiniGames/Duels.ts @@ -0,0 +1,475 @@ +import Constants from '../../utils/Constants'; +import romanize from '../../utils/romanize'; +import divide from '../../utils/divide'; + +function getTitle(data: Record, mode: string | null = null): string { + for (const div of Constants.duelsDivisions.slice().reverse()) { + const prestige = data[`${mode ? mode : 'all_modes'}_${div.key}_title_prestige`]; + if (prestige) { + return `${div.name} ${romanize(prestige)}`; + } + } + return ''; +} + +class DuelsGamemode { + title: string; + winstreak: number; + bestWinstreak: number; + kills: number; + deaths: number; + KDRatio: number; + wins: number; + losses: number; + WLRatio: number; + playedGames: number; + swings: number; + hits: number; + meleeAccuracy: number; + bowShots: number; + bowHits: number; + bowAccuracy: number; + blocksPlaced: number; + healthRegenerated: number; + goldenApplesEatan: number; + constructor(data: Record, mode: string, title: string = '') { + this.title = title; + this.winstreak = data[`current_winstreak_mode_${mode}`] || 0; + this.bestWinstreak = data[`best_winstreak_mode_${mode}`] || 0; + this.kills = data[`${mode}_kills`] || 0; + this.deaths = data[`${mode}_deaths`] || 0; + this.KDRatio = divide(this.kills, this.deaths); + this.wins = data[`${mode}_wins`] || 0; + this.losses = data[`${mode}_losses`] || 0; + this.WLRatio = divide(this.wins, this.losses); + this.playedGames = data[`${mode}_rounds_played`] || 0; + this.swings = data[`${mode}_melee_swings`] || 0; + this.hits = data[`${mode}_melee_hits`] || 0; + this.meleeAccuracy = divide(this.swings, this.hits); + this.bowShots = data[`${mode}_bow_shots`] || 0; + this.bowHits = data[`${mode}_bow_hits`] || 0; + this.bowAccuracy = divide(this.bowShots, this.bowHits); + this.blocksPlaced = data[`${mode}_blocks_placed`] || 0; + this.healthRegenerated = data[`${mode}_health_regenerated`] || 0; + this.goldenApplesEatan = data[`${mode}_golden_apples_eaten`] || 0; + } +} + +class DuelsUHC { + title: string; + winstreak: number; + bestWinstreak: number; + solo: DuelsGamemode; + doubles: DuelsGamemode; + fours: DuelsGamemode; + deathmatch: DuelsGamemode; + kills: number; + deaths: number; + KDRatio: number; + wins: number; + losses: number; + WLRatio: number; + playedGames: number; + swings: number; + hits: number; + meleeAccuracy: number; + bowShots: number; + bowHits: number; + bowAccuracy: number; + blocksPlaced: number; + healthRegenerated: number; + goldenApplesEatan: number; + constructor(data: Record) { + this.title = getTitle(data, 'uhc'); + this.winstreak = data.current_uhc_winstreak || 0; + this.bestWinstreak = data.best_uhc_winstreak || 0; + this.solo = new DuelsGamemode(data, 'uhc_duel', this.title); + this.doubles = new DuelsGamemode(data, 'uhc_doubles', this.title); + this.fours = new DuelsGamemode(data, 'uhc_four', this.title); + this.deathmatch = new DuelsGamemode(data, 'uhc_meetup', this.title); + this.kills = this.solo.kills + this.doubles.kills + this.fours.kills + this.deathmatch.kills; + this.deaths = this.solo.deaths + this.doubles.deaths + this.fours.deaths + this.deathmatch.deaths; + this.KDRatio = divide(this.kills, this.deaths); + this.wins = this.solo.wins + this.doubles.wins + this.fours.wins + this.deathmatch.wins; + this.losses = this.solo.losses + this.doubles.losses + this.fours.losses + this.deathmatch.losses; + this.WLRatio = divide(this.wins, this.losses); + this.playedGames = + this.solo.playedGames + this.doubles.playedGames + this.fours.playedGames + this.deathmatch.playedGames; + this.swings = this.solo.swings + this.doubles.swings + this.fours.swings + this.deathmatch.swings; + this.hits = this.solo.hits + this.doubles.hits + this.fours.hits + this.deathmatch.hits; + this.meleeAccuracy = divide(this.hits, this.swings); + this.bowShots = this.solo.bowShots + this.doubles.bowShots + this.fours.bowShots + this.deathmatch.bowShots; + this.bowHits = this.solo.bowHits + this.doubles.bowHits + this.fours.bowHits + this.deathmatch.bowHits; + this.bowAccuracy = divide(this.bowHits, this.bowShots); + this.blocksPlaced = + this.solo.blocksPlaced + this.doubles.blocksPlaced + this.fours.blocksPlaced + this.deathmatch.blocksPlaced; + this.healthRegenerated = + this.solo.healthRegenerated + + this.doubles.healthRegenerated + + this.fours.healthRegenerated + + this.deathmatch.healthRegenerated; + this.goldenApplesEatan = + this.solo.goldenApplesEatan + + this.doubles.goldenApplesEatan + + this.fours.goldenApplesEatan + + this.deathmatch.goldenApplesEatan; + } +} +class DuelsSkyWars { + title: string; + winstreak: number; + bestWinstreak: number; + solo: DuelsGamemode; + doubles: DuelsGamemode; + kills: number; + deaths: number; + KDRatio: number; + wins: number; + losses: number; + WLRatio: number; + playedGames: number; + swings: number; + hits: number; + meleeAccuracy: number; + bowShots: number; + bowHits: number; + bowAccuracy: number; + blocksPlaced: number; + healthRegenerated: number; + goldenApplesEatan: number; + constructor(data: Record) { + this.title = getTitle(data, 'sw'); + this.winstreak = data.current_sw_winstreak || 0; + this.bestWinstreak = data.best_sw_winstreak || 0; + this.solo = new DuelsGamemode(data, 'sw_duel', this.title); + this.doubles = new DuelsGamemode(data, 'sw_doubles', this.title); + this.kills = this.solo.kills + this.doubles.kills; + this.deaths = this.solo.deaths + this.doubles.deaths; + this.KDRatio = divide(this.kills, this.deaths); + this.wins = this.solo.wins + this.doubles.wins; + this.losses = this.solo.losses + this.doubles.losses; + this.WLRatio = divide(this.wins, this.losses); + this.playedGames = this.solo.playedGames + this.doubles.playedGames; + this.swings = this.solo.swings + this.doubles.swings; + this.hits = this.solo.hits + this.doubles.hits; + this.meleeAccuracy = divide(this.hits, this.swings); + this.bowShots = this.solo.bowShots + this.doubles.bowShots; + this.bowHits = this.solo.bowHits + this.doubles.bowHits; + this.bowAccuracy = divide(this.bowHits, this.bowShots); + this.blocksPlaced = this.solo.blocksPlaced + this.doubles.blocksPlaced; + this.healthRegenerated = this.solo.healthRegenerated + this.doubles.healthRegenerated; + this.goldenApplesEatan = this.solo.goldenApplesEatan + this.doubles.goldenApplesEatan; + } +} +class DuelsMegaWalls { + title: string; + winstreak: number; + bestWinstreak: number; + solo: DuelsGamemode; + doubles: DuelsGamemode; + kills: number; + deaths: number; + KDRatio: number; + wins: number; + losses: number; + WLRatio: number; + playedGames: number; + swings: number; + hits: number; + meleeAccuracy: number; + bowShots: number; + bowHits: number; + bowAccuracy: number; + blocksPlaced: number; + healthRegenerated: number; + goldenApplesEatan: number; + constructor(data: Record) { + this.title = getTitle(data, 'mega_walls'); + this.winstreak = data.current_mega_walls_winstreak || 0; + this.bestWinstreak = data.best_mega_walls_winstreak || 0; + this.solo = new DuelsGamemode(data, 'mw_duel', this.title); + this.doubles = new DuelsGamemode(data, 'mw_doubles', this.title); + this.kills = this.solo.kills + this.doubles.kills; + this.deaths = this.solo.deaths + this.doubles.deaths; + this.KDRatio = divide(this.kills, this.deaths); + this.wins = this.solo.wins + this.doubles.wins; + this.losses = this.solo.losses + this.doubles.losses; + this.WLRatio = divide(this.wins, this.losses); + this.playedGames = this.solo.playedGames + this.doubles.playedGames; + this.swings = this.solo.swings + this.doubles.swings; + this.hits = this.solo.hits + this.doubles.hits; + this.meleeAccuracy = divide(this.hits, this.swings); + this.bowShots = this.solo.bowShots + this.doubles.bowShots; + this.bowHits = this.solo.bowHits + this.doubles.bowHits; + this.bowAccuracy = divide(this.bowHits, this.bowShots); + this.blocksPlaced = this.solo.blocksPlaced + this.doubles.blocksPlaced; + this.healthRegenerated = this.solo.healthRegenerated + this.doubles.healthRegenerated; + this.goldenApplesEatan = this.solo.goldenApplesEatan + this.doubles.goldenApplesEatan; + } +} +class DuelsOP { + title: string; + winstreak: number; + bestWinstreak: number; + solo: DuelsGamemode; + doubles: DuelsGamemode; + kills: number; + deaths: number; + KDRatio: number; + wins: number; + losses: number; + WLRatio: number; + playedGames: number; + swings: number; + hits: number; + meleeAccuracy: number; + bowShots: number; + bowHits: number; + bowAccuracy: number; + blocksPlaced: number; + healthRegenerated: number; + goldenApplesEatan: number; + constructor(data: Record) { + this.title = getTitle(data, 'op'); + this.winstreak = data.current_op_winstreak || 0; + this.bestWinstreak = data.best_op_winstreak || 0; + this.solo = new DuelsGamemode(data, 'op_duel', this.title); + this.doubles = new DuelsGamemode(data, 'op_doubles', this.title); + this.kills = this.solo.kills + this.doubles.kills; + this.deaths = this.solo.deaths + this.doubles.deaths; + this.KDRatio = divide(this.kills, this.deaths); + this.wins = this.solo.wins + this.doubles.wins; + this.losses = this.solo.losses + this.doubles.losses; + this.WLRatio = divide(this.wins, this.losses); + this.playedGames = this.solo.playedGames + this.doubles.playedGames; + this.swings = this.solo.swings + this.doubles.swings; + this.hits = this.solo.hits + this.doubles.hits; + this.meleeAccuracy = divide(this.hits, this.swings); + this.bowShots = this.solo.bowShots + this.doubles.bowShots; + this.bowHits = this.solo.bowHits + this.doubles.bowHits; + this.bowAccuracy = divide(this.bowHits, this.bowShots); + this.blocksPlaced = this.solo.blocksPlaced + this.doubles.blocksPlaced; + this.healthRegenerated = this.solo.healthRegenerated + this.doubles.healthRegenerated; + this.goldenApplesEatan = this.solo.goldenApplesEatan + this.doubles.goldenApplesEatan; + } +} +class DuelsBridge { + title: string; + winstreak: number; + bestWinstreak: number; + solo: DuelsGamemode; + doubles: DuelsGamemode; + threes: DuelsGamemode; + fours: DuelsGamemode; + '2v2v2v2': DuelsGamemode; + '3v3v3v3': DuelsGamemode; + ctf: DuelsGamemode; + kills: number; + deaths: number; + KDRatio: number; + wins: number; + losses: number; + WLRatio: number; + playedGames: number; + swings: number; + hits: number; + meleeAccuracy: number; + bowShots: number; + bowHits: number; + bowAccuracy: number; + blocksPlaced: number; + healthRegenerated: number; + goldenApplesEatan: number; + constructor(data: Record) { + this.title = getTitle(data, 'bridge'); + this.winstreak = data.current_bridge_winstreak || 0; + this.bestWinstreak = data.best_bridge_winstreak || 0; + this.solo = new DuelsGamemode(data, 'bridge_duel', this.title); + this.doubles = new DuelsGamemode(data, 'bridge_doubles', this.title); + this.threes = new DuelsGamemode(data, 'bridge_threes', this.title); + this.fours = new DuelsGamemode(data, 'bridge_fours', this.title); + this['2v2v2v2'] = new DuelsGamemode(data, '2v2v2v2', this.title); + this['3v3v3v3'] = new DuelsGamemode(data, '3v3v3v3', this.title); + this.ctf = new DuelsGamemode(data, 'capture_threes', this.title); + this.kills = + this.solo.kills + + this.doubles.kills + + this.threes.kills + + this.fours.kills + + this['2v2v2v2'].kills + + this['3v3v3v3'].kills + + this.ctf.kills; + this.deaths = + this.solo.deaths + + this.doubles.deaths + + this.threes.deaths + + this.fours.deaths + + this['2v2v2v2'].deaths + + this['3v3v3v3'].deaths + + this.ctf.deaths; + this.KDRatio = divide(this.kills, this.deaths); + this.wins = + this.solo.wins + + this.doubles.wins + + this.threes.wins + + this.fours.wins + + this['2v2v2v2'].wins + + this['3v3v3v3'].wins + + this.ctf.wins; + this.losses = + this.solo.losses + + this.doubles.losses + + this.threes.losses + + this.fours.losses + + this['2v2v2v2'].losses + + this['3v3v3v3'].losses + + this.ctf.losses; + this.WLRatio = divide(this.wins, this.losses); + this.playedGames = + this.solo.playedGames + + this.doubles.playedGames + + this.threes.playedGames + + this.fours.playedGames + + this['2v2v2v2'].playedGames + + this['3v3v3v3'].playedGames + + this.ctf.playedGames; + this.swings = + this.solo.swings + + this.doubles.swings + + this.threes.swings + + this.fours.swings + + this['2v2v2v2'].swings + + this['3v3v3v3'].swings + + this.ctf.swings; + this.hits = + this.solo.hits + + this.doubles.hits + + this.threes.hits + + this.fours.hits + + this['2v2v2v2'].hits + + this['3v3v3v3'].hits + + this.ctf.hits; + this.meleeAccuracy = divide(this.hits, this.swings); + this.bowShots = + this.solo.bowShots + + this.doubles.bowShots + + this.threes.bowShots + + this.fours.bowShots + + this['2v2v2v2'].bowShots + + this['3v3v3v3'].bowShots + + this.ctf.bowShots; + this.bowHits = + this.solo.bowHits + + this.doubles.bowHits + + this.threes.bowHits + + this.fours.bowHits + + this['2v2v2v2'].bowHits + + this['3v3v3v3'].bowHits + + this.ctf.bowHits; + this.bowAccuracy = divide(this.bowHits, this.bowShots); + this.blocksPlaced = + this.solo.blocksPlaced + + this.doubles.blocksPlaced + + this.threes.blocksPlaced + + this.fours.blocksPlaced + + this['2v2v2v2'].blocksPlaced + + this['3v3v3v3'].blocksPlaced + + this.ctf.blocksPlaced; + this.healthRegenerated = + this.solo.healthRegenerated + + this.doubles.healthRegenerated + + this.threes.healthRegenerated + + this.fours.healthRegenerated + + this['2v2v2v2'].healthRegenerated + + this['3v3v3v3'].healthRegenerated + + this.ctf.healthRegenerated; + this.goldenApplesEatan = + this.solo.goldenApplesEatan + + this.doubles.goldenApplesEatan + + this.threes.goldenApplesEatan + + this.fours.goldenApplesEatan + + this['2v2v2v2'].goldenApplesEatan + + this['3v3v3v3'].goldenApplesEatan + + this.ctf.goldenApplesEatan; + } +} + +/** + * Duels class + */ +class Duels { + tokens: number; + title: string | null; + kills: number; + deaths: number; + KDRatio: number; + wins: number; + losses: number; + WLRatio: number; + playedGames: number; + winstreak: number; + bestWinstreak: number; + ping: number; + blocksPlaced: number; + swings: number; + hits: number; + meleeAccuracy: number; + bowShots: number; + bowHits: number; + bowAccuracy: number; + healthRegenerated: number; + goldenApplesEatan: number; + uhc: DuelsUHC; + skywars: DuelsSkyWars; + megawalls: DuelsMegaWalls; + blitz: DuelsGamemode; + op: DuelsOP; + classic: DuelsGamemode; + bow: DuelsGamemode; + noDebuff: DuelsGamemode; + combo: DuelsGamemode; + bowSpleef: DuelsGamemode; + sumo: DuelsGamemode; + bridge: DuelsBridge; + parkour: DuelsGamemode; + arena: DuelsGamemode; + constructor(data: Record) { + this.tokens = data.coins || 0; + this.title = getTitle(data); + this.kills = data.kills || 0; + this.deaths = data.deaths || 0; + this.KDRatio = divide(this.kills, this.deaths); + this.wins = data.wins || 0; + this.losses = data.losses || 0; + this.WLRatio = divide(this.wins, this.losses); + this.playedGames = data.games_played_duels || 0; + this.winstreak = data.current_winstreak || 0; + this.bestWinstreak = data.best_overall_winstreak || 0; + this.ping = data.pingPreference || 0; + this.blocksPlaced = data.blocks_placed || 0; + this.swings = data.melee_swings || 0; + this.hits = data.melee_hits || 0; + this.meleeAccuracy = divide(this.hits, this.swings); + this.bowShots = data.bow_shots || 0; + this.bowHits = data.bow_hits || 0; + this.bowAccuracy = divide(this.bowHits, this.bowShots); + this.healthRegenerated = data.health_regenerated || 0; + this.goldenApplesEatan = data.golden_apples_eaten || 0; + this.uhc = new DuelsUHC(data); + this.skywars = new DuelsSkyWars(data); + this.megawalls = new DuelsMegaWalls(data); + this.blitz = new DuelsGamemode(data, 'blitz_duel', getTitle(data, 'blitz')); + this.op = new DuelsOP(data); + this.classic = new DuelsGamemode(data, 'classic_duel', getTitle(data, 'classic')); + this.bow = new DuelsGamemode(data, 'bow_duel', getTitle(data, 'bow')); + this.noDebuff = new DuelsGamemode(data, 'potion_duel', getTitle(data, 'no_debuff')); + this.combo = new DuelsGamemode(data, 'combo_duel', getTitle(data, 'combo')); + this.bowSpleef = new DuelsGamemode(data, 'bowspleef_duel', getTitle(data, 'tnt_games')); + this.sumo = new DuelsGamemode(data, 'sumo_duel', getTitle(data, 'sumo')); + this.bridge = new DuelsBridge(data); + this.parkour = new DuelsGamemode(data, 'parkour_eight', getTitle(data, 'parkour')); + this.arena = new DuelsGamemode(data, 'duel_arena'); + } +} + +export default Duels; diff --git a/src/structures/MiniGames/MegaWalls.ts b/src/structures/MiniGames/MegaWalls.ts new file mode 100644 index 00000000..ed2c13c9 --- /dev/null +++ b/src/structures/MiniGames/MegaWalls.ts @@ -0,0 +1,199 @@ +import divide from '../../utils/divide'; + +class MegaWallsModeStats { + kills: number; + assists: number; + deaths: number; + KDRatio: number; + wins: number; + losses: number; + WLRatio: number; + finalKills: number; + finalAssists: number; + finalDeaths: number; + finalKDRatio: number; + playedGames: number; + witherDamage: number; + defenderKills: number; + walked: number; + blocksPlaced: number; + blocksBroken: number; + meleeKills: number; + damageDealt: number; + constructor(data: Record, mode: string, kit?: string) { + if (kit) kit = `${kit}_`; + this.kills = data[`${kit}kills_${mode}`] || 0; + this.assists = data[`${kit}assists_${mode}`] || 0; + this.deaths = data[`${kit}deaths_${mode}`] || 0; + this.KDRatio = divide(this.kills, this.deaths); + this.wins = data[`${kit}wins_${mode}`] || 0; + this.losses = data[`${kit}losses_${mode}`] || 0; + this.WLRatio = divide(this.wins, this.losses); + this.finalKills = data[`${kit}final_kills_${mode}`] || 0; + this.finalAssists = data[`${kit}final_assists_${mode}`] || 0; + this.finalDeaths = data[`${kit}final_deaths_${mode}`] || 0; + this.finalKDRatio = divide(this.finalKills, this.finalDeaths); + this.playedGames = data[`${kit}games_played_${mode}`] || 0; + this.witherDamage = data[`${kit}wither_damage_${mode}`] || 0; + this.defenderKills = data[`${kit}defender_kills_${mode}`] || 0; + this.walked = data[`${kit}meters_walked_${mode}`] || 0; + this.blocksPlaced = data[`${kit}blocks_placed_${mode}`] || 0; + this.blocksBroken = data[`${kit}blocks_broken_${mode}`] || 0; + this.meleeKills = data[`${kit}kills_melee_${mode}`] || 0; + this.damageDealt = data[`${kit}damage_dealt_${mode}`] || 0; + } +} +class MegaWallsKitStats { + kills: number; + assists: number; + deaths: number; + KDRatio: number; + wins: number; + losses: number; + WLRatio: number; + finalKills: number; + finalAssists: number; + finalDeaths: number; + finalKDRatio: number; + playedGames: number; + witherDamage: number; + defenderKills: number; + walked: number; + blocksPlaced: number; + blocksBroken: number; + meleeKills: number; + damageDealt: number; + faceOff: MegaWallsModeStats; + casualBrawl: MegaWallsModeStats; + constructor(data: Record, kit: string) { + this.kills = data[`${kit}_kills`] || 0; + this.assists = data[`${kit}_assists`] || 0; + this.deaths = data[`${kit}_deaths`] || 0; + this.KDRatio = divide(this.kills, this.deaths); + this.wins = data[`${kit}_wins`] || 0; + this.losses = data[`${kit}_losses`] || 0; + this.WLRatio = divide(this.wins, this.losses); + this.finalKills = data[`${kit}_final_kills`] || 0; + this.finalAssists = data[`${kit}_final_assists`] || 0; + this.finalDeaths = data[`${kit}_final_deaths`] || 0; + this.finalKDRatio = divide(this.finalKills, this.finalDeaths); + this.playedGames = data[`${kit}_games_played`] || 0; + this.witherDamage = data[`${kit}_wither_damage`] || 0; + this.defenderKills = data[`${kit}_defender_kills`] || 0; + this.walked = data[`${kit}_meters_walked`] || 0; + this.blocksPlaced = data[`${kit}_blocks_placed`] || 0; + this.blocksBroken = data[`${kit}_blocks_broken`] || 0; + this.meleeKills = data[`${kit}_kills_melee`] || 0; + this.damageDealt = data[`${kit}_damage_dealt`] || 0; + this.faceOff = new MegaWallsModeStats(data, 'face_off', kit); + this.casualBrawl = new MegaWallsModeStats(data, 'gvg', kit); + } +} + +/** + * MegaWalls class + */ +class MegaWalls { + selectedClass: string | null; + coins: number; + kills: number; + assists: number; + deaths: number; + KDRatio: number; + wins: number; + losses: number; + WLRatio: number; + finalKills: number; + finalAssists: number; + finalDeaths: number; + finalKDRatio: number; + playedGames: number; + witherDamage: number; + defenderKills: number; + walked: number; + blocksPlaced: number; + blocksBroken: number; + meleeKills: number; + damageDealt: number; + faceOff: MegaWallsModeStats; + casualBrawl: MegaWallsModeStats; + cow: MegaWallsKitStats; + hunter: MegaWallsKitStats; + shark: MegaWallsKitStats; + arcanist: MegaWallsKitStats; + deadlord: MegaWallsKitStats; + golem: MegaWallsKitStats; + herobrine: MegaWallsKitStats; + pigman: MegaWallsKitStats; + zombie: MegaWallsKitStats; + blaze: MegaWallsKitStats; + enderman: MegaWallsKitStats; + shaman: MegaWallsKitStats; + squid: MegaWallsKitStats; + creeper: MegaWallsKitStats; + pirate: MegaWallsKitStats; + sheep: MegaWallsKitStats; + skeleton: MegaWallsKitStats; + spider: MegaWallsKitStats; + werewolf: MegaWallsKitStats; + angel: MegaWallsKitStats; + assassin: MegaWallsKitStats; + automaton: MegaWallsKitStats; + moleman: MegaWallsKitStats; + phoenix: MegaWallsKitStats; + renegade: MegaWallsKitStats; + snowman: MegaWallsKitStats; + constructor(data: Record) { + this.selectedClass = data.chosen_class || null; + this.coins = data.coins || 0; + this.kills = data.kills || 0; + this.assists = data.assists || 0; + this.deaths = data.deaths || 0; + this.KDRatio = divide(this.kills, this.deaths); + this.wins = data.wins || 0; + this.losses = data.losses || 0; + this.WLRatio = divide(this.wins, this.losses); + this.finalKills = (data.final_kills || 0) + (data.finalkills || 0); + this.finalAssists = (data.final_assists || 0) + (data.finalassists || 0); + this.finalDeaths = (data.final_deaths || 0) + (data.finalDeaths || 0); + this.finalKDRatio = divide(this.finalKills, this.finalDeaths); + this.playedGames = data.games_played || 0; + this.witherDamage = (data.wither_damage || 0) + (data.witherDamager || 0); + this.defenderKills = data.defender_kills || 0; + this.walked = data.meters_walked || 0; + this.blocksPlaced = data.blocks_placed || 0; + this.blocksBroken = data.blocks_broken || 0; + this.meleeKills = data.kills_melee || 0; + this.damageDealt = data.damage_dealt || 0; + this.faceOff = new MegaWallsModeStats(data, 'face_off'); + this.casualBrawl = new MegaWallsModeStats(data, 'gvg'); + this.cow = new MegaWallsKitStats(data, 'cow'); + this.hunter = new MegaWallsKitStats(data, 'hunter'); + this.shark = new MegaWallsKitStats(data, 'shark'); + this.arcanist = new MegaWallsKitStats(data, 'arcanist'); + this.deadlord = new MegaWallsKitStats(data, 'deadlord'); + this.golem = new MegaWallsKitStats(data, 'golem'); + this.herobrine = new MegaWallsKitStats(data, 'herobrine'); + this.pigman = new MegaWallsKitStats(data, 'pigman'); + this.zombie = new MegaWallsKitStats(data, 'zombie'); + this.blaze = new MegaWallsKitStats(data, 'blaze'); + this.enderman = new MegaWallsKitStats(data, 'enderman'); + this.shaman = new MegaWallsKitStats(data, 'shaman'); + this.squid = new MegaWallsKitStats(data, 'squid'); + this.creeper = new MegaWallsKitStats(data, 'creeper'); + this.pirate = new MegaWallsKitStats(data, 'pirate'); + this.sheep = new MegaWallsKitStats(data, 'sheep'); + this.skeleton = new MegaWallsKitStats(data, 'skeleton'); + this.spider = new MegaWallsKitStats(data, 'spider'); + this.werewolf = new MegaWallsKitStats(data, 'werewolf'); + this.angel = new MegaWallsKitStats(data, 'angel'); + this.assassin = new MegaWallsKitStats(data, 'assassin'); + this.automaton = new MegaWallsKitStats(data, 'automaton'); + this.moleman = new MegaWallsKitStats(data, 'moleman'); + this.phoenix = new MegaWallsKitStats(data, 'phoenix'); + this.renegade = new MegaWallsKitStats(data, 'renegade'); + this.snowman = new MegaWallsKitStats(data, 'snowman'); + } +} + +export default MegaWalls; diff --git a/src/structures/MiniGames/MurderMystery.ts b/src/structures/MiniGames/MurderMystery.ts new file mode 100644 index 00000000..5de106ef --- /dev/null +++ b/src/structures/MiniGames/MurderMystery.ts @@ -0,0 +1,93 @@ +import divide from '../../utils/divide'; + +/** + * MurderMystery stats by gamemode + */ +class MurderMysteryModeStats { + goldPickedUp: number; + kills: number; + thrownKnifeKills: number; + knifeKills: number; + bowKills: number; + trapKills: number; + deaths: number; + suicides: number; + KDRatio: number; + wins: number; + winsAsDetective: number; + winsAsMurderer: number; + winsAsHero: number; + playedGames: number; + constructor(data: Record, gamemode: string) { + this.goldPickedUp = data[`coins_pickedup_${gamemode}`] || 0; + this.kills = data[`kills_${gamemode}`] || 0; + this.thrownKnifeKills = data[`thrown_knife_kills_${gamemode}`] || 0; + this.knifeKills = data[`knife_kills_${gamemode}`] || 0; + this.bowKills = data[`bow_kills_${gamemode}`] || 0; + this.trapKills = data[`trap_kills_${gamemode}`] || 0; + this.deaths = data[`deaths_${gamemode}`] || 0; + this.suicides = data[`suicides_${gamemode}`] || 0; + this.KDRatio = divide(this.kills, this.deaths); + this.wins = data[`wins_${gamemode}`] || 0; + this.winsAsDetective = data[`detective_wins_${gamemode}`] || 0; + this.winsAsMurderer = data[`murderer_wins_${gamemode}`] || 0; + this.winsAsHero = data[`was_hero_${gamemode}`] || 0; + this.playedGames = data[`games_${gamemode}`] || 0; + } +} + +/** + * MurderMystery class + */ +class MurderMystery { + tokens: number; + goldPickedUp: number; + playedGames: number; + kills: number; + thrownKnifeKills: number; + knifeKills: number; + trapKills: number; + bowKills: number; + killsAsMurderer: number; + deaths: number; + KDRatio: number; + winsAsMurderer: number; + winsAsDetective: number; + winsAsHero: number; + fastestWinAsMurderer: number; + fastestWinAsDetective: number; + totalTimeSurvived: number; + wins: number; + suicides: number; + classic: MurderMysteryModeStats; + assassins: MurderMysteryModeStats; + doubleUp: MurderMysteryModeStats; + infection: MurderMysteryModeStats; + constructor(data: Record) { + this.tokens = data.coins || 0; + this.goldPickedUp = data.coins_pickedup || 0; + this.playedGames = data.games || 0; + this.kills = data.kills || 0; + this.thrownKnifeKills = data.thrown_knife_kills || 0; + this.knifeKills = data.knife_kills || 0; + this.trapKills = data.trap_kills || 0; + this.bowKills = data.bow_kills || 0; + this.killsAsMurderer = data.kills_as_murderer || 0; + this.deaths = data.deaths || 0; + this.KDRatio = divide(this.kills, this.deaths); + this.winsAsMurderer = data.murderer_wins || 0; + this.winsAsDetective = data.detective_wins || 0; + this.winsAsHero = data.was_hero || 0; + this.fastestWinAsMurderer = data.quickest_murderer_win_time_seconds || 0; + this.fastestWinAsDetective = data.quickest_detective_win_time_seconds || 0; + this.totalTimeSurvived = data.total_time_survived_seconds || 0; + this.wins = data.wins || 0; + this.suicides = data.suicides || 0; + this.classic = new MurderMysteryModeStats(data, 'MURDER_CLASSIC'); + this.assassins = new MurderMysteryModeStats(data, 'MURDER_ASSASSINS'); + this.doubleUp = new MurderMysteryModeStats(data, 'MURDER_DOUBLE_UP'); + this.infection = new MurderMysteryModeStats(data, 'MURDER_INFECTION'); + } +} + +export default MurderMystery; diff --git a/src/structures/MiniGames/Paintball.ts b/src/structures/MiniGames/Paintball.ts new file mode 100644 index 00000000..c174c198 --- /dev/null +++ b/src/structures/MiniGames/Paintball.ts @@ -0,0 +1,39 @@ +import divide from '../../utils/divide'; +/** + * Paintball class + */ +class Paintball { + coins: number; + kills: number; + deaths: number; + KDRatio: number; + wins: number; + shotsFired: number; + killstreaks: number; + forceFieldTime: number; + hat: string; + adrenaline: number; + endurance: number; + fortune: number; + godfather: number; + superluck: number; + transfusion: number; + constructor(data: Record) { + this.coins = data.coins || 0; + this.kills = data.kills || 0; + this.deaths = data.deaths || 0; + this.KDRatio = divide(this.kills, this.deaths); + this.wins = data.wins || 0; + this.shotsFired = data.shots_fired || 0; + this.killstreaks = data.killstreaks || 0; + this.forceFieldTime = data.forcefieldTime || 0; + this.hat = data.hat || 'None'; + this.adrenaline = data.adrenaline || 0; + this.endurance = data.endurance || 0; + this.fortune = data.fortune || 0; + this.godfather = data.godfather || 0; + this.superluck = data.superluck || 0; + this.transfusion = data.transfusion || 0; + } +} +export default Paintball; diff --git a/src/structures/MiniGames/Pit.ts b/src/structures/MiniGames/Pit.ts new file mode 100644 index 00000000..9f409850 --- /dev/null +++ b/src/structures/MiniGames/Pit.ts @@ -0,0 +1,138 @@ +import { decode } from '../../utils/SkyblockUtils'; +import PitInventoryItem from './PitInventoryItem'; +import Constants from '../../utils/Constants'; +import { PitArmor } from '../../typings'; +import divide from '../../utils/divide'; + +/** + * Pit Class + */ +class Pit { + prestige: number; + xp: number; + level: number; + kills: number; + deaths: number; + KDRatio: number; + assists: number; + maxKillStreak: number; + playtime: number; + joins: number; + damageReceived: number; + damageDealt: number; + damageRatio: number; + meleeDamageReceived: number; + meleeDamageDealt: number; + swordHits: number; + leftClicks: number; + meleeAccuracy: number; + meleeDamageRatio: number; + bowDamageReceived: number; + bowDamageDealt: number; + arrowsHit: number; + arrowsFired: number; + bowAccuracy: number; + bowDamageRatio: number; + goldenHeadsEaten: number; + getInventory: () => Promise; + getEnterChest: () => Promise; + getArmor: () => Promise; + constructor(data: Record) { + this.prestige = data.profile?.prestiges?.[data.profile?.prestiges?.length - 1].index || 0; + this.xp = data.profile?.xp || 0; + this.level = + Pit.calcLevel( + this.prestige, + 0 < this.prestige ? this.xp - Constants.pit.Prestiges[this.prestige - 1].SumXp : this.xp + ) ?? 0; + this.kills = data.pit_stats_ptl?.kills || 0; + this.deaths = data.pit_stats_ptl?.deaths || 0; + this.KDRatio = divide(this.kills, this.deaths); + this.assists = data.pit_stats_ptl?.assists || 0; + this.maxKillStreak = data.pit_stats_ptl?.max_streak || 0; + this.playtime = (data.pit_stats_ptl?.playtime_minutes || 0) * 60; + this.joins = data.pit_stats_ptl?.joins || 0; + this.damageReceived = data.pit_stats_ptl?.damage_received || 0; + this.damageDealt = data.pit_stats_ptl?.damage_dealt || 0; + this.damageRatio = divide(this.damageDealt, this.damageReceived); + this.meleeDamageReceived = data.pit_stats_ptl?.melee_damage_received || 0; + this.meleeDamageDealt = data.pit_stats_ptl?.melee_damage_dealt || 0; + this.swordHits = data.pit_stats_ptl?.sword_hits || 0; + this.leftClicks = data.pit_stats_ptl?.left_clicks || 0; + this.meleeAccuracy = divide(this.swordHits, this.leftClicks); + this.meleeDamageRatio = divide(this.meleeDamageDealt, this.meleeDamageReceived); + this.bowDamageReceived = data.pit_stats_ptl?.bow_damage_received || 0; + this.bowDamageDealt = data.pit_stats_ptl?.bow_damage_dealt || 0; + this.arrowsHit = data.pit_stats_ptl?.arrow_hits || 0; + this.arrowsFired = data.pit_stats_ptl?.arrows_fired || 0; + this.bowAccuracy = divide(this.arrowsHit, this.arrowsFired); + this.bowDamageRatio = divide(this.bowDamageDealt, this.bowDamageReceived); + this.goldenHeadsEaten = data.pit_stats_ptl?.ghead_eaten || 0; + this.getInventory = async (): Promise => { + let inventory = data.profile.inv_contents; + if (!inventory) return []; + + try { + inventory = await decode(inventory.data); + const edited = []; + for (let i = 1; i < inventory.length; i++) { + if (!inventory[i].id) { + continue; + } + edited.push(new PitInventoryItem(inventory[i])); + } + return edited; + } catch { + return []; + } + }; + this.getEnterChest = async () => { + let chest = data.profile.inv_enderchest; + if (!chest) return []; + + try { + chest = await decode(chest.data); + const edited = []; + for (let i = 1; i < chest.length; i++) { + if (!chest[i].id) { + continue; + } + edited.push(new PitInventoryItem(chest[i])); + } + return edited; + } catch { + return []; + } + }; + this.getArmor = async () => { + const base64 = data.profile.inv_armor; + const decoded = await decode(base64.data); + const armor = { + helmet: decoded[3].id ? new PitInventoryItem(decoded[3]) : null, + chestplate: decoded[2].id ? new PitInventoryItem(decoded[2]) : null, + leggings: decoded[1].id ? new PitInventoryItem(decoded[1]) : null, + boots: decoded[0].id ? new PitInventoryItem(decoded[0]) : null + }; + return armor; + }; + } + // Credit https://github.com/PitPanda/PitPandaProduction/blob/b1971f56ea1aa8c829b722cbb33247c96591c0cb/structures/Pit.js + static calcLevel(prestige: number, xp: number): number { + const multiplier = Constants.pit.Prestiges[prestige].Multiplier; + let level = 0; + while (0 < xp && 120 > level) { + const levelXp = Constants.pit.Levels[Math.floor(level / 10)].Xp * multiplier; + if (xp >= levelXp * 10) { + xp -= levelXp * 10; + level += 10; + } else { + const gain = Math.floor(xp / levelXp); + level += gain; + xp = 0; + } + } + return level; + } +} + +export default Pit; diff --git a/src/structures/MiniGames/PitInventoryItem.ts b/src/structures/MiniGames/PitInventoryItem.ts new file mode 100644 index 00000000..dba00b76 --- /dev/null +++ b/src/structures/MiniGames/PitInventoryItem.ts @@ -0,0 +1,21 @@ +/** + * PitItem class + */ +class PitInventoryItem { + itemId: number; + count: number; + name: string | null; + lore: string | null; + loreArray: string[]; + extraAttributes: object | null; + constructor(data: Record) { + this.itemId = data.id || 0; + this.count = data.Count || 0; + this.name = data?.tag?.display?.Name ? data.tag.display.Name.toString().replace(/§([1-9]|[a-f])|§/gm, '') : null; + this.lore = data?.tag?.display?.Lore ? data.tag.display.Lore.join('\n') : null; + this.loreArray = data?.tag?.display?.Lore ?? []; + this.extraAttributes = data?.tag?.ExtraAttributes ?? null; + } +} + +export default PitInventoryItem; diff --git a/src/structures/MiniGames/Quakecraft.ts b/src/structures/MiniGames/Quakecraft.ts new file mode 100644 index 00000000..4ec8ddf4 --- /dev/null +++ b/src/structures/MiniGames/Quakecraft.ts @@ -0,0 +1,73 @@ +import divide from '../../utils/divide'; + +class QuakecraftMode { + wins: number; + kills: number; + deaths: number; + KDRatio: number; + killstreaks: number; + distanceTravelled: number; + shotsFired: number; + headshots: number; + constructor(data: Record, gamemode?: string) { + if (gamemode) gamemode = `_${gamemode}`; + this.wins = data[`wins${gamemode}`] || 0; + this.kills = data[`kills${gamemode}`] || 0; + this.deaths = data[`deaths${gamemode}`] || 0; + this.KDRatio = divide(this.kills, this.deaths); + this.killstreaks = data[`killstreaks${gamemode}`] || 0; + this.distanceTravelled = data[`distance_travelled${gamemode}`] || 0; + this.shotsFired = data[`shots_fired${gamemode}`] || 0; + this.headshots = data[`headshots${gamemode}`] || 0; + } +} + +/** + * Quakecraft class + */ +class Quakecraft { + coins: number; + solo: QuakecraftMode; + teams: QuakecraftMode; + wins: number; + kills: number; + deaths: number; + KDRatio: number; + killstreaks: number; + distanceTravelled: number; + shotsFired: number; + headshots: number; + instantRespawn: boolean; + killPrefixColor: string; + showPrefix: boolean; + killSound: string; + barrel: string; + case: string; + muzzle: string; + sight: string; + trigger: string; + constructor(data: Record) { + this.coins = data.coins || 0; + this.solo = new QuakecraftMode(data); + this.teams = new QuakecraftMode(data, 'teams'); + this.wins = this.solo.wins + this.teams.wins; + this.kills = this.solo.kills + this.teams.kills; + this.deaths = this.solo.deaths + this.teams.deaths; + this.KDRatio = divide(this.kills, this.deaths); + this.killstreaks = this.solo.killstreaks + this.teams.killstreaks; + this.distanceTravelled = this.solo.distanceTravelled + this.teams.distanceTravelled; + this.shotsFired = this.solo.shotsFired + this.teams.shotsFired; + this.headshots = this.solo.headshots + this.teams.headshots; + this.instantRespawn = data.instantRespawn || false; + this.killPrefixColor = data.selectedKillPrefix || ''; + this.showPrefix = data.showKillPrefix || false; + this.killSound = data.killsound || ''; + this.barrel = data.barrel || ''; + this.case = data.case || ''; + this.muzzle = data.muzzle || ''; + this.sight = data.sight || ''; + this.trigger = data.trigger || ''; + } +} + +export default Quakecraft; diff --git a/src/structures/MiniGames/SkyWars.ts b/src/structures/MiniGames/SkyWars.ts new file mode 100644 index 00000000..44b6af65 --- /dev/null +++ b/src/structures/MiniGames/SkyWars.ts @@ -0,0 +1,298 @@ +import { removeSnakeCaseString } from '../../utils/removeSnakeCase'; +import { SkyWarsPrestige } from '../../typings'; +import divide from '../../utils/divide'; + +function getSkyWarsPrestige(level: number): SkyWarsPrestige { + if (60 <= level) return 'Mythic'; + return (['Iron', 'Iron', 'Gold', 'Diamond', 'Emerald', 'Sapphire', 'Ruby', 'Crystal', 'Opal', 'Amethyst', 'Rainbow'][ + Math.floor(level / 5) + ] || 'Iron') as SkyWarsPrestige; +} + +function getSkyWarsLevel(xp: number): number { + const totalXp = [0, 2, 7, 15, 25, 50, 100, 200, 350, 600, 1000, 1500]; + if (15000 <= xp) return Math.floor((xp - 15000) / 10000 + 12); + const level = totalXp.findIndex((x) => 0 < x * 10 - xp); + return level; +} + +function getSkyWarsLevelProgress(xp: number) { + const totalXp = [0, 2, 7, 15, 25, 50, 100, 200, 350, 600, 1000, 1500]; + const xpToNextLvl = [0, 2, 5, 8, 10, 25, 50, 100, 150, 250, 400, 500]; + let percent; + let xpToNextLevel; + let currentLevelXp = xp; + if (15000 <= xp) { + currentLevelXp -= 15000; + if (0 === currentLevelXp) return { currentLevelXp: 0, xpToNextLevel: 10000, percent: 0, xpNextLevel: 10000 }; + if (10000 < currentLevelXp) { + do { + currentLevelXp -= 10000; + } while (10000 <= currentLevelXp); + } + xpToNextLevel = 10000 - currentLevelXp; + percent = Math.round(currentLevelXp) / 100; + const percentRemaining = Math.round((100 - percent) * 100) / 100; + return { + currentLevelXp, + xpToNextLevel, + percent, + xpNextLevel: 10000, + percentRemaining + }; + } + const totalXptoNextLevel = xpToNextLvl[totalXp.findIndex((x) => 0 < x * 10 - xp)] * 10; + for (let i = 0; i < xpToNextLvl.length; i++) { + if (0 > currentLevelXp - xpToNextLvl[i] * 10) break; + currentLevelXp -= xpToNextLvl[i] * 10; + } + xpToNextLevel = totalXptoNextLevel - currentLevelXp; + percent = Math.round((currentLevelXp / totalXptoNextLevel) * 10000) / 100; + return { + currentLevelXp, + xpToNextLevel, + percent, + xpNextLevel: totalXptoNextLevel + }; +} + +class SkywarsMode { + kills: number; + deaths: number; + KDRatio: number; + wins: number; + losses: number; + WLRatio: number; + constructor(data: Record, gamemode: string) { + this.kills = data[`kills_${gamemode}`] || 0; + this.deaths = data[`deaths_${gamemode}`] || 0; + this.KDRatio = divide(data.kills, data.deaths); + this.wins = data[`wins_${gamemode}`] || 0; + this.losses = data[`losses_${gamemode}`] || 0; + this.WLRatio = divide(data.wins, data.losses); + } +} +class SkywarsModeStats { + activeKit: string; + killstreak: number; + kills: number; + voidKills: number; + meleeKills: number; + bowKills: number; + mobKills: number; + assists: number; + deaths: number; + KDRatio: number; + wins: number; + losses: number; + WLRatio: number; + gamesPlayed: number; + survivedPlayers: number; + chestsOpened: number; + timePlayed: number; + shard: number; + longestBowShot: number; + arrowsShot: number; + arrowsHit: number; + bowAccuracy: number; + fastestWin: number; + heads: number; + constructor(data: Record, gamemode: string) { + this.activeKit = data[`activeKit_${gamemode.toUpperCase()}`] || ''; + this.killstreak = data[`killstreak_${gamemode}`] || 0; + this.kills = data[`kills_${gamemode}`] || 0; + this.voidKills = data[`void_kills_${gamemode}`] || 0; + this.meleeKills = data[`melee_kills_${gamemode}`] || 0; + this.bowKills = data[`bow_kills_${gamemode}`] || 0; + this.mobKills = data[`mob_kills_${gamemode}`] || 0; + this.assists = data[`assists_${gamemode}`] || 0; + this.deaths = data[`deaths_${gamemode}`] || 0; + this.KDRatio = divide(data.kills, data.deaths); + this.wins = data[`wins_${gamemode}`] || 0; + this.losses = data[`losses_${gamemode}`] || 0; + this.WLRatio = divide(data.wins, data.losses); + this.gamesPlayed = data[`games_${gamemode}`] || 0; + this.survivedPlayers = data[`survived_players_${gamemode}`] || 0; + this.chestsOpened = data[`chests_opened_${gamemode}`] || 0; + this.timePlayed = data[`time_played_${gamemode}`] || 0; + this.shard = data[`shard_${gamemode}`] || 0; + this.longestBowShot = data[`longest_bow_shot_${gamemode}`] || 0; + this.arrowsShot = data[`arrows_shot_${gamemode}`] || 0; + this.arrowsHit = data[`arrows_hit_${gamemode}`] || 0; + this.bowAccuracy = divide(this.arrowsHit, this.arrowsShot); + this.fastestWin = data[`fastest_win_${gamemode}`] || 0; + this.heads = data[`heads_${gamemode}`] || 0; + } +} + +/** + * Parses SkyWars Kit + */ +class SkywarsKit { + kitData: string[] | null; + isKit: boolean; + gameMode: string; + kitType: string; + kitName: string; + constructor(kit: Record) { + this.kitData = kit.match(/^kit_([a-z]+)_([a-z]+)_([a-z]+)$/); + this.isKit = Boolean(this.kitData); + this.gameMode = this.kitData ? this.kitData[2] : ''; + this.kitType = this.kitData ? this.kitData[1] : ''; + this.kitName = removeSnakeCaseString(this.kitData ? this.kitData[3] : ''); + } +} + +/** + * Parses SkyWars Kits + */ +class SkywarsKits { + kits: SkywarsKit[]; + constructor(kits: Record) { + this.kits = kits.map((kit: any) => new SkywarsKit(kit)).filter((kit: SkywarsKit) => kit.isKit); + } + + get(gameMode: string = '', type: string = ''): SkywarsKit[] { + return this.kits.filter((kit) => kit.gameMode.startsWith(gameMode) && kit.kitType.startsWith(type)); + } +} +/** + * Skywars Packages - parses every package player has + */ +class SkywarsPackages { + rawPackages: Record; + cages: any; + kits: SkywarsKits; + achievements: any; + constructor(data: Record) { + // TODO : a lot more + this.rawPackages = data; + this.cages = this.parseCages(); + this.kits = new SkywarsKits(data); + this.achievements = this.rawPackages + .map((pkg: string) => pkg.match(/^([A-Za-z]+)_?achievement([0-9]?)$/)) + .filter((x: any) => x) + .map((x: any[]) => x.slice(1).join('')); + } + + parseCages(): string[] { + return this.rawPackages + .map((pkg: string) => pkg.match(/^cage_([A-Za-z]+)-cage$/)) + .filter((x: any) => x) + .map((x: string[]) => x[1].replace(/-[a-z]/g, (x) => x[1].toUpperCase())); + } +} +/** + * SkyWars class + */ +class SkyWars { + coins: number; + souls: number; + tokens: number; + experience: number; + level: number; + levelProgress: any; + levelFormatted: string; + prestige: SkyWarsPrestige; + opals: number; + avarice: number; + tenacity: number; + shards: number; + angelOfDeathLevel: number; + killstreak: number; + kills: number; + voidKills: number; + meleeKills: number; + bowKills: number; + mobKills: number; + assists: number; + deaths: number; + KDRatio: number; + wins: number; + losses: number; + WLRatio: number; + gamesPlayed: number; + survivedPlayers: number; + chestsOpened: number; + timePlayed: number; + shard: number; + longestBowShot: number; + arrowsShot: number; + arrowsHit: number; + bowAccuracy: number; + fastestWin: number; + heads: number; + blocksPlaced: number; + blocksBroken: number; + eggThrown: number; + enderpearlsThrown: number; + solo: SkywarsModeStats; + soloNormal: SkywarsMode; + soloInsane: SkywarsMode; + team: SkywarsModeStats; + teamNormal: SkywarsMode; + teamInsane: SkywarsMode; + mega: SkywarsMode; + megaDoubles: SkywarsMode; + lab: SkywarsMode; + packages: SkywarsPackages; + constructor(data: Record) { + this.coins = data.coins || 0; + this.souls = data.souls || 0; + this.tokens = data.cosmetic_tokens || 0; + this.experience = data.skywars_experience || 0; + this.level = getSkyWarsLevel(data.skywars_experience); + this.levelProgress = getSkyWarsLevelProgress(data.skywars_experience); + this.levelFormatted = data.levelFormatted + ? data.levelFormatted + .replace(/§l/gm, '**') + .replace(/§([a-f]|[1-9])/gm, '') + .replace(/§r/gm, '') + : null; + this.prestige = getSkyWarsPrestige(this.level); + this.opals = data.opals || 0; + this.avarice = data.avarice || 0; + this.tenacity = data.tenacity || 0; + this.shards = data.shard || 0; + this.angelOfDeathLevel = data.angel_of_death_level || 0; + this.killstreak = data.killstreak || 0; + this.kills = data.kills || 0; + this.voidKills = data.void_kills || 0; + this.meleeKills = data.melee_kills || 0; + this.bowKills = data.bow_kills || 0; + this.mobKills = data.mob_kills || 0; + this.assists = data.assists || 0; + this.deaths = data.deaths || 0; + this.KDRatio = divide(data.kills, data.deaths); + this.wins = data.wins || 0; + this.losses = data.losses || 0; + this.WLRatio = divide(data.wins, data.losses); + this.gamesPlayed = data.games || 0; + this.survivedPlayers = data.survived_players || 0; + this.chestsOpened = data.chests_opened || 0; + this.timePlayed = data.time_played || 0; + this.shard = data.shard || 0; + this.longestBowShot = data.longest_bow_shot || 0; + this.arrowsShot = data.arrows_shot || 0; + this.arrowsHit = data.arrows_hit || 0; + this.bowAccuracy = divide(this.arrowsHit, this.arrowsShot); + this.fastestWin = data.fastest_win || 0; + this.heads = data.heads || 0; + this.blocksPlaced = data.blocks_placed || 0; + this.blocksBroken = data.blocks_broken || 0; + this.eggThrown = data.egg_thrown || 0; + this.enderpearlsThrown = data.enderpearls_thrown || 0; + this.solo = new SkywarsModeStats(data, 'solo'); + this.soloNormal = new SkywarsMode(data, 'solo_normal'); + this.soloInsane = new SkywarsMode(data, 'solo_insane'); + this.team = new SkywarsModeStats(data, 'team'); + this.teamNormal = new SkywarsMode(data, 'team_normal'); + this.teamInsane = new SkywarsMode(data, 'team_insane'); + this.mega = new SkywarsMode(data, 'mega'); + this.megaDoubles = new SkywarsMode(data, 'mega_doubles'); + this.lab = new SkywarsMode(data, 'lab'); + this.packages = new SkywarsPackages(data.packages || []); + } +} + +export default SkyWars; diff --git a/src/structures/MiniGames/SmashHeroes.ts b/src/structures/MiniGames/SmashHeroes.ts new file mode 100644 index 00000000..3d0d9bba --- /dev/null +++ b/src/structures/MiniGames/SmashHeroes.ts @@ -0,0 +1,117 @@ +import divide from '../../utils/divide'; + +class SmashHeroesMode { + kills: number; + deaths: number; + KDRatio: number; + wins: number; + losses: number; + WLRatio: number; + constructor(data: Record, mode: string) { + this.kills = data[`kills_${mode}`] || 0; + this.deaths = data[`deaths_${mode}`] || 0; + this.KDRatio = divide(this.kills, this.deaths); + this.wins = data[`wins_${mode}`] || 0; + this.losses = data[`losses_${mode}`] || 0; + this.WLRatio = divide(this.wins, this.losses); + } +} + +class SmashHeoresHero { + name: string; + level: number; + xp: number; + prestige: number; + playedGames: number; + kills: number; + deaths: number; + KDRatio: number; + wins: number; + losses: number; + WLRatio: number; + constructor(data: Record, hero: string) { + this.name = hero; + this.level = data[`lastLevel_${hero}`] || 0; + this.xp = data[`xp_${hero}`] || 0; + this.prestige = data[`pg_${hero}`] || 0; + this.playedGames = data.class_stats?.[hero]?.games || 0; + this.kills = data.class_stats?.[hero]?.kills || 0; + this.deaths = data.class_stats?.[hero]?.deaths || 0; + this.KDRatio = divide(this.kills, this.deaths); + this.wins = data.class_stats?.[hero]?.wins || 0; + this.losses = data.class_stats?.[hero]?.losses || 0; + this.WLRatio = divide(this.wins, this.losses); + } +} + +/** + * SmashHeroes class + */ +class SmashHeroes { + coins: number; + level: number; + winstreak: number; + playedGames: number; + kills: number; + deaths: number; + KDRatio: number; + wins: number; + losses: number; + WLRatio: number; + smashed: number; + '1v1v1v1': SmashHeroesMode; + '2v2': SmashHeroesMode; + '2v2v2': SmashHeroesMode; + activeClass: string; + theBulk: SmashHeoresHero; + cakeMonster: SmashHeoresHero; + generalCluck: SmashHeoresHero; + botmun: SmashHeoresHero; + marauder: SmashHeoresHero; + pug: SmashHeoresHero; + tinman: SmashHeoresHero; + spoderman: SmashHeoresHero; + frosty: SmashHeoresHero; + sergeantShield: SmashHeoresHero; + skullfire: SmashHeoresHero; + goku: SmashHeoresHero; + sanic: SmashHeoresHero; + duskCrawler: SmashHeoresHero; + shoopDaWhoop: SmashHeoresHero; + greenHood: SmashHeoresHero; + constructor(data: Record) { + this.coins = data.coins || 0; + this.level = data.smash_level_total || 0; + this.winstreak = data.win_streak || 0; + this.playedGames = data.games || 0; + this.kills = data.kills || 0; + this.deaths = data.deaths || 0; + this.KDRatio = divide(this.kills, this.deaths); + this.wins = data.wins || 0; + this.losses = data.losses || 0; + this.WLRatio = divide(this.wins, this.losses); + this.smashed = data.smashed || 0; + this['1v1v1v1'] = new SmashHeroesMode(data, 'normal'); + this['2v2'] = new SmashHeroesMode(data, '2v2'); + this['2v2v2'] = new SmashHeroesMode(data, 'teams'); + this.activeClass = data.active_class || null; + this.theBulk = new SmashHeoresHero(data, 'THE_BULK'); + this.cakeMonster = new SmashHeoresHero(data, 'CAKE_MONSTER'); + this.generalCluck = new SmashHeoresHero(data, 'GENERAL_CLUCK'); + this.botmun = new SmashHeoresHero(data, 'BOTMUN'); + this.marauder = new SmashHeoresHero(data, 'MARAUDER'); + this.pug = new SmashHeoresHero(data, 'PUG'); + this.tinman = new SmashHeoresHero(data, 'TINMAN'); + this.spoderman = new SmashHeoresHero(data, 'SPODERMAN'); + this.frosty = new SmashHeoresHero(data, 'FROSTY'); + this.sergeantShield = new SmashHeoresHero(data, 'SERGEANT_SHIELD'); + this.skullfire = new SmashHeoresHero(data, 'SKULLFIRE'); + this.goku = new SmashHeoresHero(data, 'GOKU'); + this.sanic = new SmashHeoresHero(data, 'SANIC'); + this.duskCrawler = new SmashHeoresHero(data, 'DUSK_CRAWLER'); + this.shoopDaWhoop = new SmashHeoresHero(data, 'SHOOP_DA_WHOOP'); + this.greenHood = new SmashHeoresHero(data, 'GREEN_HOOD'); + } +} + +export default SmashHeroes; diff --git a/src/structures/MiniGames/SpeedUHC.ts b/src/structures/MiniGames/SpeedUHC.ts new file mode 100644 index 00000000..fee40f2d --- /dev/null +++ b/src/structures/MiniGames/SpeedUHC.ts @@ -0,0 +1,74 @@ +import divide from '../../utils/divide'; + +class SpeedUHCMode { + kills: number; + deaths: number; + wins: number; + losses: number; + playedGames: number; + winstreak: number; + killStreak: number; + assists: number; + constructor(data: Record, mode: string) { + this.kills = data[`kills_${mode}`] || 0; + this.deaths = data[`deaths_${mode}`] || 0; + this.wins = data[`wins_${mode}`] || 0; + this.losses = data[`losses_${mode}`] || 0; + this.playedGames = data[`games_${mode}`] || 0; + this.winstreak = data[`win_streak_${mode}`] || 0; + this.killStreak = data[`killstreak_${mode}`] || 0; + this.assists = data[`assists_${mode}`] || 0; + } +} + +/** + * Speed UHC class + */ +class SpeedUHC { + coins: number; + kills: number; + deaths: number; + KDRatio: number; + wins: number; + losses: number; + WLRatio: number; + playedGames: number; + winstreak: number; + killstreak: number; + blocksBroken: number; + blocksPlaced: number; + quits: number; + itemsEnchanted: number; + assists: number; + solo: SpeedUHCMode; + soloNormal: SpeedUHCMode; + soloInsane: SpeedUHCMode; + team: SpeedUHCMode; + teamNormal: SpeedUHCMode; + teamInsane: SpeedUHCMode; + constructor(data: Record) { + this.coins = data.coins || 0; + this.kills = data.kills || 0; + this.deaths = data.deaths || 0; + this.KDRatio = divide(this.kills, this.deaths); + this.wins = data.wins || 0; + this.losses = data.losses || 0; + this.WLRatio = divide(this.wins, this.losses); + this.playedGames = data.games || 0; + this.winstreak = data.win_streak || 0; + this.killstreak = data.killstreak || 0; + this.blocksBroken = data.blocks_broken || 0; + this.blocksPlaced = data.blocks_placed || 0; + this.quits = data.quits || 0; + this.itemsEnchanted = data.items_enchanted || 0; + this.assists = data.assists || 0; + this.solo = new SpeedUHCMode(data, 'solo'); + this.soloNormal = new SpeedUHCMode(data, 'solo_normal'); + this.soloInsane = new SpeedUHCMode(data, 'solo_insane'); + this.team = new SpeedUHCMode(data, 'team'); + this.teamNormal = new SpeedUHCMode(data, 'team_normal'); + this.teamInsane = new SpeedUHCMode(data, 'team_insane'); + } +} + +export default SpeedUHC; diff --git a/src/structures/MiniGames/TNTGames.ts b/src/structures/MiniGames/TNTGames.ts new file mode 100644 index 00000000..226c94f6 --- /dev/null +++ b/src/structures/MiniGames/TNTGames.ts @@ -0,0 +1,126 @@ +import divide from '../../utils/divide'; + +class TNTRun { + wins: number; + bestTime: number; + deaths: number; + slownessPotions: number; + speedPotions: number; + doubleJumps: number; + prefix: string; + constructor(data: Record) { + this.wins = data.wins_tntrun || 0; + this.bestTime = data.record_tntrun || 0; + this.deaths = data.deaths_tntrun || 0; + this.slownessPotions = data.new_tntrun_slowness_potions || 0; + this.speedPotions = data.new_tntrun_speed_potions || 0; + this.doubleJumps = data.new_tntrun_double_jumps || 0; + this.prefix = data.prefix_tntrun || ''; + } +} +class PVPRun { + wins: number; + bestTime: number; + kills: number; + deaths: number; + KDRatio: number; + regeneration: number; + notoriety: number; + fortitude: number; + doubleJumps: number; + prefix: string; + constructor(data: Record) { + this.wins = data.wins_pvprun || 0; + this.bestTime = data.record_pvprun || 0; + this.kills = data.kills_pvprun || 0; + this.deaths = data.deaths_pvprun || 0; + this.KDRatio = divide(this.kills, this.deaths); + this.regeneration = data.new_pvprun_regeneration || 0; + this.notoriety = data.new_pvprun_notoriety || 0; + this.fortitude = data.new_pvprun_fortitude || 0; + this.doubleJumps = data.new_pvprun_double_jumps || 0; + this.prefix = data.prefix_pvprun || ''; + } +} +class BowSpleef { + wins: number; + tags: number; + deaths: number; + prefix: string; + constructor(data: Record) { + this.wins = data.wins_bowspleef || 0; + this.tags = data.tags_bowspleef || 0; + this.deaths = data.deaths_bowspleef || 0; + this.prefix = data.prefix_bowspleef || ''; + } +} +class TNTTag { + wins: number; + kills: number; + deaths: number; + KDRatio: number; + speed: number; + blastProtection: number; + speedItUp: number; + slowItDown: number; + prefix: string; + constructor(data: Record) { + this.wins = data.wins_tntag || 0; + this.kills = data.kills_tntag || 0; + this.deaths = data.deaths_tntag || 0; + this.KDRatio = divide(this.kills, this.deaths); + this.speed = data.new_tntag_speedy || 0; + this.blastProtection = data.tag_blastprotection || 0; + this.speedItUp = data.tag_speeditup || 0; + this.slowItDown = data.tag_slowitdown || 0; + this.prefix = data.prefix_tntag || ''; + } +} +class TNTWizards { + wins: number; + kills: number; + assists: number; + deaths: number; + KDRatio: number; + points: number; + kineticHealing: number; + airTime: number; + prefix: string; + constructor(data: Record) { + this.wins = data.wins_capture || 0; + this.kills = data.kills_capture || 0; + this.assists = data.assists_capture || 0; + this.deaths = data.deaths_capture || 0; + this.KDRatio = divide(this.kills, this.deaths); + this.points = data.points_capture || 0; + this.kineticHealing = data.kinetic_healing_capture || 0; + this.airTime = data.air_time_capture || 0; + this.prefix = data.prefix_capture || ''; + } +} + +/** + * The TNT Games class + */ +class TNTGames { + coins: number; + winstreak: number; + wins: number; + tntrun: TNTRun; + pvpRun: PVPRun; + bowSpleef: BowSpleef; + tnttag: TNTTag; + wizards: TNTWizards; + constructor(data: Record) { + this.coins = data.coins || 0; + this.winstreak = data.winstreak || 0; + this.wins = data.wins || 0; + this.tntrun = new TNTRun(data); + this.pvpRun = new PVPRun(data); + this.bowSpleef = new BowSpleef(data); + this.tnttag = new TNTTag(data); + this.wizards = new TNTWizards(data); + } +} + +export default TNTGames; diff --git a/src/structures/MiniGames/TurboKartRacers.ts b/src/structures/MiniGames/TurboKartRacers.ts new file mode 100644 index 00000000..cc099e6d --- /dev/null +++ b/src/structures/MiniGames/TurboKartRacers.ts @@ -0,0 +1,62 @@ +class TurboKartRacersMap { + map: string; + plays: number; + boxPickups: number; + bronzeTrophies: number; + silverTrophies: number; + goldTrophies: number; + constructor(data: Record, mapName: string) { + this.map = mapName; + this.plays = data[`${mapName}_plays`] || 0; + this.boxPickups = data[`box_pickups_${mapName}`] || 0; + this.bronzeTrophies = data[`bronze_trophy_${mapName}`] || 0; + this.silverTrophies = data[`silver_trophy_${mapName}`] || 0; + this.goldTrophies = data[`gold_trophy_${mapName}`] || 0; + } +} + +/** + * TurboKartRacers class + */ +class TurboKartRacers { + coins: number; + wins: number; + completedLaps: number; + bronzeTrophies: number; + silverTrophies: number; + goldTrophies: number; + boxPickups: number; + horn: 'DEFAULT' | 'SHY' | 'ALIEN' | 'TAXI' | 'KLAXON' | 'TRICYCLE' | 'ALARM' | 'KLOON' | 'TEDDY' | 'TRUCK' | 'JERRY'; + retro: TurboKartRacersMap; + hypixelgp: TurboKartRacersMap; + olympus: TurboKartRacersMap; + junglerush: TurboKartRacersMap; + canyon: TurboKartRacersMap; + bananaHitsReceived: number; + bananaHitsSent: number; + blueTorpedoHit: number; + grandPrix: boolean; + grandPrixTokens: number; + constructor(data: Record) { + this.coins = data.coins || 0; + this.wins = data.wins || 0; + this.completedLaps = data.laps_completed || 0; + this.bronzeTrophies = data.bronze_trophy || 0; + this.silverTrophies = data.silver_trophy || 0; + this.goldTrophies = data.gold_trophy || 0; + this.boxPickups = data.box_pickups || 0; + this.horn = data.horn || 'DEFAULT'; + this.retro = new TurboKartRacersMap(data, 'retro'); + this.hypixelgp = new TurboKartRacersMap(data, 'hypixelgp'); + this.olympus = new TurboKartRacersMap(data, 'olympus'); + this.junglerush = new TurboKartRacersMap(data, 'junglerush'); + this.canyon = new TurboKartRacersMap(data, 'canyon'); + this.bananaHitsReceived = data.banana_hits_received || 0; + this.bananaHitsSent = data.banana_hits_sent || 0; + this.blueTorpedoHit = data.blue_torpedo_hit || 0; + this.grandPrix = data.grand_prix || 'false'; + this.grandPrixTokens = data.grand_prix_tokens || 0; + } +} + +export default TurboKartRacers; diff --git a/src/structures/MiniGames/UHC.ts b/src/structures/MiniGames/UHC.ts new file mode 100644 index 00000000..cf6e49ec --- /dev/null +++ b/src/structures/MiniGames/UHC.ts @@ -0,0 +1,186 @@ +import divide from '../../utils/divide'; + +function getStarLevel(kills: number, wins: number) { + const sum = Number(kills) + wins * 10; + let starLevel = 1; + const sums = [0, 1, 6, 21, 46, 96, 171, 271, 521, 1021, 1321, 1621, 1921, 2221, 2521, Infinity]; + starLevel += sums.map((x) => x * 10 - sum).findIndex((x) => 0 < x) - 1; + return starLevel; +} + +class UHCGamemode { + kills: number; + deaths: number; + wins: number; + headsEaten: number; + ultimatesCrafted: number; + extraUltimatesCrafted: number; + constructor(data: Record, mode?: string) { + if (mode) mode = `_${mode}`; + /** + * @type {kills:number} + */ + this.kills = data[`kills${mode}`] || 0; + /** + * @type {deaths:number} + */ + this.deaths = data[`deaths${mode}`] || 0; + /** + * @type {wins:number} + */ + this.wins = data[`wins${mode}`] || 0; + /** + * @type {headsEaten:number} + */ + this.headsEaten = data[`heads_eaten${mode}`] || 0; + /** + * @type {ultimatesCrafted:number} + */ + this.ultimatesCrafted = data[`ultimates_crafted${mode}`] || 0; + /** + * @type {extraUltimatesCrafted:number} + */ + this.extraUltimatesCrafted = data[`extra_ultimates_crafted${mode}`] || 0; + } +} + +/** + * UHC class + */ +class UHC { + coins: number; + score: number; + kit: string; + solo: UHCGamemode; + team: UHCGamemode; + redVsBlue: UHCGamemode; + noDiamond: UHCGamemode; + brawl: UHCGamemode; + soloBrawl: UHCGamemode; + duoBrawl: UHCGamemode; + wins: number; + kills: number; + deaths: number; + KDRatio: number; + headsEaten: number; + ultimatesCrafted: number; + extraUltimatesCrafted: number; + starLevel: number; + constructor(data: Record) { + /** + * @type {coins:number} + */ + this.coins = data.coins || 0; + /** + * @type {score:number} + */ + this.score = data.score || 0; + /** + * @type {kit:string} + */ + this.kit = data.equippedKit || ''; + /** + * @type {solo:UHCGamemode} + */ + this.solo = new UHCGamemode(data, 'solo'); + /** + * @type {team:UHCGamemode} + */ + this.team = new UHCGamemode(data); + /** + * @type {redVsBlue:UHCGamemode} + */ + this.redVsBlue = new UHCGamemode(data, 'red_vs_blue'); + /** + * @type {noDiamond:UHCGamemode} + */ + this.noDiamond = new UHCGamemode(data, 'no_diamonds'); + /** + * @type {brawl:UHCGamemode} + */ + this.brawl = new UHCGamemode(data, 'brawl'); + /** + * @type {soloBrawl:UHCGamemode} + */ + this.soloBrawl = new UHCGamemode(data, 'solo_brawl'); + /** + * @type {duoBrawl:UHCGamemode} + */ + this.duoBrawl = new UHCGamemode(data, 'duo_brawl'); + /** + * @type {wins:number} + */ + this.wins = + this.solo.wins + + this.team.wins + + this.redVsBlue.wins + + this.noDiamond.wins + + this.brawl.wins + + this.soloBrawl.wins + + this.duoBrawl.wins; + /** + * @type {kills:number} + */ + this.kills = + this.solo.kills + + this.team.kills + + this.redVsBlue.kills + + this.noDiamond.kills + + this.brawl.kills + + this.soloBrawl.kills + + this.duoBrawl.kills; + /** + * @type {deaths:number} + */ + this.deaths = + this.solo.deaths + + this.team.deaths + + this.redVsBlue.deaths + + this.noDiamond.deaths + + this.brawl.deaths + + this.soloBrawl.deaths + + this.duoBrawl.deaths; + /** + * @type {KDRatio:number} + */ + this.KDRatio = divide(this.kills, this.deaths); + /** + * @type {headsEaten:number} + */ + this.headsEaten = + this.solo.headsEaten + + this.team.headsEaten + + this.redVsBlue.headsEaten + + this.noDiamond.headsEaten + + this.brawl.headsEaten + + this.soloBrawl.headsEaten + + this.duoBrawl.headsEaten; + /** + * @type {ultimatesCrafted:number} + */ + this.ultimatesCrafted = + this.solo.ultimatesCrafted + + this.team.ultimatesCrafted + + this.redVsBlue.ultimatesCrafted + + this.noDiamond.ultimatesCrafted + + this.brawl.ultimatesCrafted + + this.soloBrawl.ultimatesCrafted + + this.duoBrawl.ultimatesCrafted; + /** + * @type {extraUltimatesCrafted:number} + */ + this.extraUltimatesCrafted = + this.solo.extraUltimatesCrafted + + this.team.extraUltimatesCrafted + + this.redVsBlue.extraUltimatesCrafted + + this.noDiamond.extraUltimatesCrafted + + this.brawl.extraUltimatesCrafted + + this.soloBrawl.extraUltimatesCrafted + + this.duoBrawl.extraUltimatesCrafted; + /** + * @type {starLevel:number} + */ + this.starLevel = getStarLevel(this.kills, this.wins); + } +} +export default UHC; diff --git a/src/structures/MiniGames/VampireZ.ts b/src/structures/MiniGames/VampireZ.ts new file mode 100644 index 00000000..afc6473e --- /dev/null +++ b/src/structures/MiniGames/VampireZ.ts @@ -0,0 +1,44 @@ +import divide from '../../utils/divide'; + +class VampireZRole { + kills: number; + deaths: number; + KDRatio: number; + wins: number; + constructor(data: Record, role: string) { + this.kills = data[`${role}_kills`]; + this.deaths = data[`${role}_deaths`]; + this.KDRatio = divide(this.kills, this.deaths); + this.wins = data[`${role}_wins`]; + } +} + +/** + * VampireZ class + */ +class VampireZ { + coins: number; + goldBought: number; + blood: boolean; + zombieKills: number; + human: VampireZRole; + vampire: VampireZRole; + kills: number; + deaths: number; + KDRatio: number; + wins: number; + constructor(data: Record) { + this.coins = data.coins || 0; + this.goldBought = data.gold_bought || 0; + this.blood = data.blood || false; + this.zombieKills = data.zombie_kills || 0; + this.human = new VampireZRole(data, 'human'); + this.vampire = new VampireZRole(data, 'vampire'); + this.kills = this.human.kills + this.vampire.kills; + this.deaths = this.human.deaths + this.vampire.deaths; + this.KDRatio = divide(this.kills, this.deaths); + this.wins = this.human.wins + this.vampire.wins; + } +} + +export default VampireZ; diff --git a/src/structures/MiniGames/Walls.ts b/src/structures/MiniGames/Walls.ts new file mode 100644 index 00000000..99a6dbe4 --- /dev/null +++ b/src/structures/MiniGames/Walls.ts @@ -0,0 +1,26 @@ +import divide from '../../utils/divide'; +/** + * Walls class + */ +class Walls { + coins: number; + kills: number; + deaths: number; + KDRatio: number; + wins: number; + losses: number; + WLRatio: number; + assists: number; + constructor(data: Record) { + this.coins = data.coins || 0; + this.kills = data.kills || 0; + this.deaths = data.deaths || 0; + this.KDRatio = divide(this.kills, this.deaths); + this.wins = data.wins || 0; + this.losses = data.losses || 0; + this.WLRatio = divide(this.wins, this.losses); + this.assists = data.assists || 0; + } +} + +export default Walls; diff --git a/src/structures/MiniGames/Warlords.ts b/src/structures/MiniGames/Warlords.ts new file mode 100644 index 00000000..6366b9b6 --- /dev/null +++ b/src/structures/MiniGames/Warlords.ts @@ -0,0 +1,82 @@ +import divide from '../../utils/divide'; + +class WarlordsClass { + wins: number; + losses: number; + WLRatio: number; + gamesPlayed: number; + damage: number; + heal: number; + damagePrevented: number; + constructor(data: Record, className: string) { + this.wins = data[`wins_${className}`] || 0; + this.losses = data[`losses_${className}`] || 0; + this.WLRatio = divide(this.wins, this.losses); + this.gamesPlayed = data[`${className}_plays`]; + this.damage = data[`damage_${className}`] || 0; + this.heal = data[`heal_${className}`] || 0; + this.damagePrevented = data[`damage_prevented_${className}`] || 0; + } +} + +/** + * Warlords class + */ +class Warlords { + coins: number; + kills: number; + deaths: number; + KDRatio: number; + wins: number; + losses: number; + WLRatio: number; + winstreak: number; + assists: number; + class: string; + pyromancer: WarlordsClass; + mage: WarlordsClass; + thunderlord: WarlordsClass; + shaman: WarlordsClass; + earthwarden: WarlordsClass; + aquamancer: WarlordsClass; + paladin: WarlordsClass; + avenger: WarlordsClass; + warrior: WarlordsClass; + defender: WarlordsClass; + cryomancer: WarlordsClass; + crusader: WarlordsClass; + berserker: WarlordsClass; + protector: WarlordsClass; + revenant: WarlordsClass; + spiritguard: WarlordsClass; + constructor(data: Record) { + this.coins = data.coins || 0; + this.kills = data.kills || 0; + this.deaths = data.deaths || 0; + this.KDRatio = divide(this.kills, this.deaths); + this.wins = data.wins || 0; + this.losses = data.losses || 0; + this.WLRatio = divide(this.wins, this.losses); + this.winstreak = data.win_streak || 0; + this.assists = data.assists || 0; + this.class = data.chosen_class || ''; + this.pyromancer = new WarlordsClass(data, 'pyromancer'); + this.mage = new WarlordsClass(data, 'mage'); + this.thunderlord = new WarlordsClass(data, 'thunderlord'); + this.shaman = new WarlordsClass(data, 'shaman'); + this.earthwarden = new WarlordsClass(data, 'earthwarden'); + this.aquamancer = new WarlordsClass(data, 'aquamancer'); + this.paladin = new WarlordsClass(data, 'paladin'); + this.avenger = new WarlordsClass(data, 'avenger'); + this.warrior = new WarlordsClass(data, 'warrior'); + this.defender = new WarlordsClass(data, 'defender'); + this.cryomancer = new WarlordsClass(data, 'cryomancer'); + this.crusader = new WarlordsClass(data, 'crusader'); + this.berserker = new WarlordsClass(data, 'berserker'); + this.protector = new WarlordsClass(data, 'protector'); + this.revenant = new WarlordsClass(data, 'revenant'); + this.spiritguard = new WarlordsClass(data, 'spiritguard'); + } +} + +export default Warlords; diff --git a/src/structures/MiniGames/WoolWars.ts b/src/structures/MiniGames/WoolWars.ts new file mode 100644 index 00000000..c72cc6c9 --- /dev/null +++ b/src/structures/MiniGames/WoolWars.ts @@ -0,0 +1,151 @@ +import { WoolWarsPrivateGamesConfig, WoolWarsStats } from '../../typings'; +import divide from '../../utils/divide'; + +function convertXPToLevel(exp: number): number { + const minimalExp = [0, 1e3, 3e3, 6e3, 1e4, 15e3]; + const baseLevel = minimalExp.length; + const baseExp = minimalExp[minimalExp.length - 1]; + const expToLevel100 = 49e4; + if (exp < baseExp) return minimalExp.findIndex((x) => exp < x); + const theoreticalLevel = (exp - baseExp) / 5e3 + baseLevel; + if (100 < theoreticalLevel) return 100 + convertXPToLevel(exp - expToLevel100); + return theoreticalLevel; +} + +function generateStatsFor(data: Record, className: string): WoolWarsStats { + const workingData = (className ? data?.classes?.[className] : data) || {}; + return { + wins: workingData.wins || 0, + gamesPlayed: workingData.games_played || 0, + woolsPlaced: workingData.wool_placed || 0, + blocksBroken: workingData.blocks_broken || 0, + placeBreakRatio: divide(workingData.wool_placed || 0, workingData.blocks_broken || 0), + kills: workingData.kills || 0, + deaths: workingData.deaths || 0, + KDRatio: divide(workingData.kills, workingData.deaths), + assists: workingData.assists || 0, + powerups: workingData.powerups_gotten || 0 + }; +} + +/** + * Wool Wars Class + */ +class WoolWars { + layers: number; + xp: number; + exactLevel: number; + level: number; + coins: number; + wins: number; + gamesPlayed: number; + woolsPlaced: number; + blocksBroken: number; + placeBreakRatio: number; + kills: number; + deaths: number; + KDRatio: number; + assists: number; + powerups: number; + selectedClass: 'ASSAULT' | 'TANK' | 'GOLEM' | 'SWORDSMAN' | 'ENGINEER' | 'ARCHER' | 'NONE'; + assault: WoolWarsStats; + tank: WoolWarsStats; + golem: WoolWarsStats; + swordsman: WoolWarsStats; + engineer: WoolWarsStats; + archer: WoolWarsStats; + ownedCosmetics: string[]; + privateGamesConfig: WoolWarsPrivateGamesConfig; + constructor(data: Record) { + /** + * Sheep layers, similar to prestige + * @type {layers:number} + */ + this.layers = data.progression?.available_layers || 0; + /** + * Wool Wars XP + * @type {xp:number} + */ + this.xp = data.progression?.experience || 0; + /** + * Wool Wars Decimal Level + * @type {exactLevel:number} + */ + this.exactLevel = convertXPToLevel(this.xp); + /** + * Wool wars level (as shown in game) + * @type {level:number} + */ + this.level = Math.floor(this.exactLevel); + /** + * Coins + * @type {coins:number} + */ + this.coins = data.coins || 0; + /** + * Wins + * @type {wins:number} + */ + this.wins = data.wins || 0; + /** + * gamesPlayed + * @type {gamesPlayed:number} + */ + this.gamesPlayed = data.games_played || 0; + /** + * woolsPlaced + * @type {woolsPlaced:number} + */ + this.woolsPlaced = data.wool_placed || 0; + /** + * blocksBroken + * @type {blocksBroken:number} + */ + this.blocksBroken = data.blocks_broken || 0; + /** + * placeBreakRatio + * @type {placeBreakRatio:number} + */ + this.placeBreakRatio = divide(this.woolsPlaced, this.blocksBroken); + /** + * kills + * @type {kills:number} + */ + this.kills = data.kills || 0; + /** + * deaths + * @type {deaths:number} + */ + this.deaths = data.deaths || 0; + /** + * KDRatio + * @type {KDRatio:number} + */ + this.KDRatio = divide(this.kills, this.deaths); + /** + * assists + * @type {assists:number} + */ + this.assists = data.assists || 0; + /** + * powerups + * @type {powerups:number} + */ + this.powerups = data.powerups_gotten || 0; + /** + * Selected class, or NONE if field isn't present in API for some reason + * @type {selectedClass:'ASSAULT'|'TANK'|'GOLEM'|'SWORDSMAN'|'ENGINEER'|'ARCHER'|'NONE'} + */ + this.selectedClass = data.wool_wars?.selected_class || 'NONE'; + this.assault = generateStatsFor(data.wool_wars?.stats, 'assault'); + this.tank = generateStatsFor(data.wool_wars?.stats, 'tank'); + this.golem = generateStatsFor(data.wool_wars?.stats, 'golem'); + this.swordsman = generateStatsFor(data.wool_wars?.stats, 'swordsman'); + this.engineer = generateStatsFor(data.wool_wars?.stats, 'engineer'); + this.archer = generateStatsFor(data.wool_wars?.stats, 'archer'); + this.ownedCosmetics = data.packages || []; + this.privateGamesConfig = data.privategames || {}; + } +} + +export default WoolWars; diff --git a/src/structures/Pet.ts b/src/structures/Pet.ts new file mode 100644 index 00000000..32abf6e2 --- /dev/null +++ b/src/structures/Pet.ts @@ -0,0 +1,41 @@ +import { recursive } from '../utils/removeSnakeCase'; + +class Pet { + isFavorite: boolean; + name: string | null; + active: boolean; + hunger: number | null; + lastFed: number | null; + lastFedAt: Date | null; + thirst: number | null; + lastDrank: number | null; + lastDrankAt: Date | null; + exercise: number | null; + lastExercised: number | null; + lastExercisedAt: Date | null; + rawNickname: string | null; + nickname: string | null; + experience: number; + + constructor(name: string, data: Record) { + this.isFavorite = data.vanityFavorites ? Boolean(data.vanityFavorites.includes(name.toUpperCase())) : false; + name = name.replace('pet_', ''); + this.name = recursive(name) || null; + this.active = data.currentPet === name.toUpperCase(); + const stats = data.petStats && data.petStats[name.toUpperCase()]; + this.hunger = stats.HUNGER ? stats.HUNGER.value : null; + this.lastFed = stats.HUNGER ? stats.HUNGER.timestamp : null; + this.lastFedAt = this.lastFed ? new Date(this.lastFed) : null; + this.thirst = stats.THIRST ? stats.THIRST.value : null; + this.lastDrank = stats.THIRST ? stats.THIRST.timestamp : null; + this.lastDrankAt = this.lastDrank ? new Date(this.lastDrank) : null; + this.exercise = stats.EXERCISE ? stats.EXERCISE.value : null; + this.lastExercised = stats.EXERCISE ? stats.EXERCISE.timestamp : null; + this.lastExercisedAt = this.lastExercised ? new Date(this.lastExercised) : null; + this.rawNickname = stats.name || null; + this.nickname = stats.name ? stats.name.replace(/§([0-9]|[a-f])|§/gm, '') : null; + this.experience = stats.experience || 0; + } +} + +export default Pet; diff --git a/src/structures/Pets.ts b/src/structures/Pets.ts new file mode 100644 index 00000000..c39c54e2 --- /dev/null +++ b/src/structures/Pets.ts @@ -0,0 +1,17 @@ +import { PetConsumables } from '../typings'; +import Pet from './Pet'; + +class Pets { + pets: Pet[]; + lastJourneyTimestamp: number | null; + lastJourneyAt: Date | null; + petConsumables: PetConsumables; + constructor(pets: string[], data: Record) { + this.pets = pets.map((x) => new Pet(x, data)); + this.lastJourneyTimestamp = data.petJourneyTimestamp || null; + this.lastJourneyAt = this.lastJourneyTimestamp ? new Date(this.lastJourneyTimestamp) : null; + this.petConsumables = data.petConsumables; + } +} + +export default Pets; diff --git a/src/structures/Player.ts b/src/structures/Player.ts new file mode 100644 index 00000000..33d7c159 --- /dev/null +++ b/src/structures/Player.ts @@ -0,0 +1,151 @@ +import { getPlayerLevel, getRank, getSocialMedia, parseClaimedRewards, playerLevelProgress } from '../utils/Player'; +import { LevelProgress, PlayerRank, PlayerSocialMedia, RanksPurchaseTime } from '../typings'; +import { recursive } from '../utils/removeSnakeCase'; +import Color from './Color'; +import Game from './Game'; +import Arcade from './MiniGames/Arcade'; +import ArenaBrawl from './MiniGames/ArenaBrawl'; +import BedWars from './MiniGames/BedWars'; +import BlitzSurvivalGames from './MiniGames/BlitzSurvivalGames'; +import BuildBattle from './MiniGames/BuildBattle'; +import CopsAndCrims from './MiniGames/CopsAndCrims'; +import Duels from './MiniGames/Duels'; +import MegaWalls from './MiniGames/MegaWalls'; +import MurderMystery from './MiniGames/MurderMystery'; +import Paintball from './MiniGames/Paintball'; +import Pit from './MiniGames/Pit'; +import Quakecraft from './MiniGames/Quakecraft'; +import SkyWars from './MiniGames/SkyWars'; +import SmashHeroes from './MiniGames/SmashHeroes'; +import SpeedUHC from './MiniGames/SpeedUHC'; +import TNTGames from './MiniGames/TNTGames'; +import TurboKartRacers from './MiniGames/TurboKartRacers'; +import UHC from './MiniGames/UHC'; +import VampireZ from './MiniGames/VampireZ'; +import Walls from './MiniGames/Walls'; +import Warlords from './MiniGames/Warlords'; +import WoolWars from './MiniGames/WoolWars'; +import PlayerCosmetics from './PlayerCosmetics'; + +class Player { + nickname: string; + uuid: string; + rank: PlayerRank; + channel: string | null; + firstLoginTimestamp: number | null; + firstLogin: Date | null; + lastLoginTimestamp: number | null; + lastLogin: Date | null; + lastLogoutTimestamp: number | null; + lastLogout: Date | null; + recentlyPlayedGame: Game | null; + plusColor: Color | null; + prefixColor: Color | null; + karma: number; + achievements: object; + achievementPoints: number; + totalExperience: number; + level: number; + socialMedia: PlayerSocialMedia[]; + giftBundlesSent: number; + giftBundlesReceived: number; + giftsSent: number; + isOnline: boolean; + lastDailyReward: Date | null; + lastDailyRewardTimestamp: number | null; + totalRewards: number | null; + totalDailyRewards: number | null; + rewardStreak: number | null; + rewardScore: number | null; + rewardHighScore: number | null; + levelProgress: LevelProgress; + stats: any | null; + userLanguage: string; + claimedLevelingRewards: number[]; + globalCosmetics: PlayerCosmetics | null; + ranksPurchaseTime: RanksPurchaseTime; + + constructor(data: Record) { + this.nickname = data.displayname; + this.uuid = data.uuid; + this.rank = getRank(data); + this.channel = data.channel || null; + this.firstLoginTimestamp = data.firstLogin || null; + this.firstLogin = data.firstLogin ? new Date(data.firstLogin) : null; + this.lastLoginTimestamp = data.lastLogin || null; + this.lastLogin = data.lastLogin ? new Date(data.lastLogin) : null; + this.lastLogoutTimestamp = data.lastLogout || null; + this.lastLogout = data.lastLogout ? new Date(data.lastLogout) : null; + this.recentlyPlayedGame = data.mostRecentGameType ? new Game(data.mostRecentGameType) : null; + this.plusColor = + 'MVP+' === this.rank || 'MVP++' === this.rank + ? data.rankPlusColor + ? new Color(data.rankPlusColor) + : new Color('RED') + : null; + this.prefixColor = + 'MVP++' === this.rank ? (data.monthlyRankColor ? new Color(data.monthlyRankColor) : new Color('GOLD')) : null; + this.karma = data.karma || 0; + this.achievements = recursive(data.achievements); + this.achievementPoints = data.achievementPoints || 0; + this.totalExperience = data.networkExp || 0; + this.level = getPlayerLevel(this.totalExperience) || 0; + this.socialMedia = getSocialMedia(data.socialMedia) || []; + this.giftBundlesSent = data.giftingMeta ? data.giftingMeta.realBundlesGiven || 0 : null; + this.giftBundlesReceived = data.giftingMeta ? data.giftingMeta.realBundlesReceived || 0 : null; + this.giftsSent = data.giftingMeta ? data.giftingMeta.giftsGiven || 0 : null; + this.isOnline = + null !== this.lastLoginTimestamp && + null !== this.lastLogoutTimestamp && + this.lastLoginTimestamp > this.lastLogoutTimestamp; + this.lastDailyReward = data.lastAdsenseGenerateTime ? new Date(data.lastAdsenseGenerateTime) : null; + this.lastDailyRewardTimestamp = data.lastAdsenseGenerateTime || null; + this.totalRewards = data.totalRewards || null; + this.totalDailyRewards = data.totalDailyRewards || null; + this.rewardStreak = data.rewardStreak || null; + this.rewardScore = data.rewardScore || null; + this.rewardHighScore = data.rewardHighScore || null; + this.levelProgress = playerLevelProgress(data.networkExp); + this.stats = data.stats + ? { + arcade: data.stats.Arcade ? new Arcade({ ...data.stats.Arcade, ...data.achievements }) : null, + arena: data.stats.Arena ? new ArenaBrawl(data.stats.Arena) : null, + bedwars: data.stats.Bedwars ? new BedWars(data.stats.Bedwars) : null, + blitzsg: data.stats.HungerGames ? new BlitzSurvivalGames(data.stats.HungerGames) : null, + buildbattle: data.stats.BuildBattle ? new BuildBattle(data.stats.BuildBattle) : null, + copsandcrims: data.stats.MCGO ? new CopsAndCrims(data.stats.MCGO) : null, + duels: data.stats.Duels ? new Duels(data.stats.Duels) : null, + megawalls: data.stats.Walls3 ? new MegaWalls(data.stats.Walls3) : null, + murdermystery: data.stats.MurderMystery ? new MurderMystery(data.stats.MurderMystery) : null, + paintball: data.stats.Paintball ? new Paintball(data.stats.Paintball) : null, + pit: data.stats.Pit ? new Pit(data.stats.Pit) : null, + quakecraft: data.stats.Quake ? new Quakecraft(data.stats.Quake) : null, + skywars: data.stats.SkyWars ? new SkyWars(data.stats.SkyWars) : null, + smashheroes: data.stats.SuperSmash ? new SmashHeroes(data.stats.SuperSmash) : null, + speeduhc: data.stats.SpeedUHC ? new SpeedUHC(data.stats.SpeedUHC) : null, + tntgames: data.stats.TNTGames ? new TNTGames(data.stats.TNTGames) : null, + turbokartracers: data.stats.GingerBread ? new TurboKartRacers(data.stats.GingerBread) : null, + uhc: data.stats.UHC ? new UHC(data.stats.UHC) : null, + vampirez: data.stats.VampireZ ? new VampireZ(data.stats.VampireZ) : null, + walls: data.stats.Walls ? new Walls(data.stats.Walls) : null, + warlords: data.stats.Battleground ? new Warlords(data.stats.Battleground) : null, + woolwars: data.stats.WoolGames ? new WoolWars(data.stats.WoolGames) : null + } + : null; + this.userLanguage = data.userLanguage || 'ENGLISH'; + this.claimedLevelingRewards = parseClaimedRewards(data) || []; + this.globalCosmetics = data ? new PlayerCosmetics(data) : null; + this.ranksPurchaseTime = { + VIP: data.levelUp_VIP ? new Date(data.levelUp_VIP) : null, + VIP_PLUS: data.levelUp_VIP_PLUS ? new Date(data.levelUp_VIP_PLUS) : null, + MVP: data.levelUp_MVP ? new Date(data.levelUp_MVP) : null, + MVP_PLUS: data.levelUp_MVP_PLUS ? new Date(data.levelUp_MVP_PLUS) : null + }; + } + + toString(): string { + return this.nickname; + } +} + +export default Player; diff --git a/src/structures/PlayerCosmetics.ts b/src/structures/PlayerCosmetics.ts new file mode 100644 index 00000000..135bee99 --- /dev/null +++ b/src/structures/PlayerCosmetics.ts @@ -0,0 +1,130 @@ +/* eslint-disable no-underscore-dangle */ +import { removeSnakeCaseString } from '../utils/removeSnakeCase'; +import Pets from './Pets'; + +/** + * Player Cosmetics class + */ +class PlayerCosmetics { + allCosmetics: string[]; + petManager: Pets | null; + _suits: any; + _hats: any; + _gadgets: any; + _morphs: any; + _cloaks: any; + _taunts: any; + _rankColors: any; + _particle: any; + _particlepacks: any; + _clickfx: any; + constructor(data: Record) { + this.allCosmetics = data?.vanityMeta?.packages || undefined; + this.petManager = this.allCosmetics + ? new Pets( + this.allCosmetics.filter((x) => x.startsWith('pet_')), + data + ) + : null; + } + + suits(): string[] { + if (!this._suits) { + this._suits = this.allCosmetics + ? this.allCosmetics + .filter((x) => x.startsWith('suit_')) + .map((x) => removeSnakeCaseString(x.replace('suit_', ''))) || [] + : []; + } + return this._suits; + } + + hats(): string[] { + if (!this._hats) { + this._hats = this.allCosmetics + ? this.allCosmetics + .filter((x) => x.startsWith('hat_')) + .map((x) => removeSnakeCaseString(x.replace('hat_', ''))) || [] + : []; + } + return this._hats; + } + + gadgets(): string[] { + if (!this._gadgets) { + this._gadgets = this.allCosmetics + ? this.allCosmetics + .filter((x) => x.startsWith('gadget_')) + .map((x) => removeSnakeCaseString(x.replace('gadget_', ''))) || [] + : []; + } + return this._gadgets; + } + + morphs(): string[] { + if (!this._morphs) { + this._morphs = this.allCosmetics + ? this.allCosmetics + .filter((x) => x.startsWith('morph_')) + .map((x) => removeSnakeCaseString(x.replace('morph_', ''))) || [] + : []; + } + return this._morphs; + } + + cloaks(): string[] { + if (!this._cloaks) { + this._cloaks = this.allCosmetics + ? this.allCosmetics + .filter((x) => x.startsWith('cloak_')) + .map((x) => removeSnakeCaseString(x.replace('cloak_', ''))) || [] + : []; + } + return this._cloaks; + } + + taunts(): string[] { + if (!this._taunts) { + this._taunts = this.allCosmetics + ? this.allCosmetics + .filter((x) => x.startsWith('taunt_')) + .map((x) => removeSnakeCaseString(x.replace('taunt_', ''))) || [] + : []; + } + return this._taunts; + } + + rankColors(): string[] { + if (!this._rankColors) { + this._rankColors = this.allCosmetics + ? this.allCosmetics + .filter((x) => x.startsWith('rankcolor_')) + .map((x) => removeSnakeCaseString(x.replace('rankcolor_', ''))) || [] + : []; + } + return this._rankColors; + } + + particlePacks(): string[] { + if (!this._particle) { + this._particle = this.allCosmetics + ? this.allCosmetics + .filter((x) => x.startsWith('particlepack_')) + .map((x) => removeSnakeCaseString(x.replace('particlepack_', ''))) || [] + : []; + } + return this._particlepacks; + } + + clickEffects(): string[] { + if (!this._clickfx) { + this._clickfx = this.allCosmetics + ? this.allCosmetics + .filter((x) => x.startsWith('clickeffects_')) + .map((x) => removeSnakeCaseString(x.replace('clickeffects_', ''))) || [] + : []; + } + return this._clickfx; + } +} +export default PlayerCosmetics; diff --git a/src/structures/RecentGame.ts b/src/structures/RecentGame.ts new file mode 100644 index 00000000..9305aadb --- /dev/null +++ b/src/structures/RecentGame.ts @@ -0,0 +1,27 @@ +import Game from './Game'; + +class RecentGame extends Game { + dateTimestamp: number | null; + date: Date | null; + mode: string | null; + map: string | null; + ongoing: boolean; + endedAt: Date | null; + endedTimestamp: number | null; + + constructor(data: Record) { + super(data.gameType); + this.dateTimestamp = data.date || null; + this.date = data.date ? new Date(data.date) : null; + this.mode = data.mode || null; + this.map = data.map || null; + this.ongoing = Boolean(!data.ended); + this.endedAt = data.ended ? new Date(data.ended) : null; + this.endedTimestamp = data.ended ? data.ended : null; + } + toString(): any { + return this.mode; + } +} + +export default RecentGame; diff --git a/src/structures/ServerInfo.ts b/src/structures/ServerInfo.ts new file mode 100644 index 00000000..fc6ea541 --- /dev/null +++ b/src/structures/ServerInfo.ts @@ -0,0 +1,35 @@ +import { PlayerInfo } from '../typings'; + +class ServerInfo { + protocolUsed: number; + versionInfo: string; + players: PlayerInfo; + rawMOTD: string; + cleanMOTD: string; + textMOTD: string; + faviconB64: string; + favicon: Buffer; + ping: number; + + constructor(data: Record, ping: string | number) { + this.protocolUsed = data.version.protocol || 736; + this.versionInfo = data.version.name || 'Unknown'; + this.players = { + max: data.players.max || 0, + online: data.players.online || 0, + players: data.players.sample || [], + toString: () => `${this.players.online}/${this.players.max}` + }; + this.rawMOTD = data.description || ''; + this.cleanMOTD = this.rawMOTD.replace(/§[a-z0-9]/gi, ''); + this.textMOTD = this.cleanMOTD.replace(/^\s+/gm, ''); + this.faviconB64 = data.favicon; + this.favicon = Buffer.from(this.faviconB64, 'base64'); + this.ping = parseInt(String(ping), 10); + } + toString(): string { + return `${this.textMOTD} - ${this.players} Players (${this.ping} ms) - ${this.versionInfo}`; + } +} + +export default ServerInfo; diff --git a/src/structures/SkyBlock/Auctions/Auction.ts b/src/structures/SkyBlock/Auctions/Auction.ts new file mode 100644 index 00000000..0905acdc --- /dev/null +++ b/src/structures/SkyBlock/Auctions/Auction.ts @@ -0,0 +1,46 @@ +import { SkyblockRarity } from '../../../typings'; +import BaseAuction from './BaseAuction'; +import Bid from './Bid'; + +/** + * Auction class + */ +class Auction extends BaseAuction { + coop: string[]; + auctionStartTimestamp: number | null; + auctionStart: Date | null; + auctionEnd: Date | null; + auctionEndTimestamp: number; + item: string; + itemLore: string; + itemLoreRaw: string; + rarity: SkyblockRarity; + startingBid: number; + highestBid: number; + bids: Bid[]; + claimed: boolean; + claimedBidders: string[]; + constructor(data: Record, includeItemBytes: boolean) { + super(data, includeItemBytes); + this.coop = data.coop || []; + this.auctionStartTimestamp = data.start || null; + this.auctionStart = data.start ? new Date(data.start) : null; + this.auctionEnd = data.end ? new Date(data.end) : null; + this.auctionEndTimestamp = data.end || null; + this.item = data.item_name || null; + this.itemLore = data.item_lore ? data.item_lore.replace(/§([1-9]|[a-l])|§/gm, '') : null; + this.itemLoreRaw = data.item_lore || null; + this.rarity = data.tier || null; + this.startingBid = data.starting_bid || 0; + this.highestBid = this.bin ? data.starting_bid : data.highest_bid_amount || 0; + this.bids = data.bids.length ? data.bids.map((b: any) => new Bid(b)) : []; + this.claimed = data.claimed || false; + this.claimedBidders = this.claimed ? data.claimed_bidders : []; + } + + toString(): string { + return this.item; + } +} + +export default Auction; diff --git a/src/structures/SkyBlock/Auctions/AuctionInfo.ts b/src/structures/SkyBlock/Auctions/AuctionInfo.ts new file mode 100644 index 00000000..c6ae7135 --- /dev/null +++ b/src/structures/SkyBlock/Auctions/AuctionInfo.ts @@ -0,0 +1,32 @@ +/** + * Auction info class + */ +class AuctionInfo { + page: number; + totalPages: number; + totalAuctions: number; + lastUpdatedTimestamp: number; + lastUpdatedAt: Date; + age: number; + [key: string]: any; + constructor(data: Record) { + this.page = parseInt(data.page, 10) || 0; + this.totalPages = parseInt(data.totalPages, 10) || 1; + this.totalAuctions = parseInt(data.totalAuctions, 10) || 0; + this.lastUpdatedTimestamp = data.lastUpdated; + this.lastUpdatedAt = new Date(data.lastUpdated); + // eslint-disable-next-line no-underscore-dangle + this.age = parseInt(data._headers.get('age'), 10) || 0; + } + + _extend(name: any, value: any) { + this[name] = value; + return this; + } + + toString(): string { + return `${this.page} / ${this.totalPages}`; + } +} + +export default AuctionInfo; diff --git a/src/structures/SkyBlock/Auctions/BaseAuction.ts b/src/structures/SkyBlock/Auctions/BaseAuction.ts new file mode 100644 index 00000000..4940865b --- /dev/null +++ b/src/structures/SkyBlock/Auctions/BaseAuction.ts @@ -0,0 +1,25 @@ +import ItemBytes from '../../ItemBytes'; + +/** + * Base auction class + */ +class BaseAuction { + auctionId: string | null; + auctioneerUuid: string | null; + auctioneerProfile: string | null; + bin: boolean; + itemBytes: ItemBytes | null; + constructor(data: Record, includeItemBytes: boolean) { + this.auctionId = data.uuid || data.auction_id || null; + this.auctioneerUuid = data.auctioneer || data.seller || null; + this.auctioneerProfile = data.profile_id || data.seller_profile || null; + this.bin = data.bin || false; + this.itemBytes = includeItemBytes ? new ItemBytes(data.item_bytes) : null; + } + + toString(): string | null { + return this.auctionId; + } +} + +export default BaseAuction; diff --git a/src/structures/SkyBlock/Auctions/Bid.ts b/src/structures/SkyBlock/Auctions/Bid.ts new file mode 100644 index 00000000..9135a6f6 --- /dev/null +++ b/src/structures/SkyBlock/Auctions/Bid.ts @@ -0,0 +1,25 @@ +/** + * Bid class + */ +class Bid { + auctionId: string | null; + profileId: string | null; + amount: number; + timestamp: number; + at: Date | null; + bidder: string; + constructor(data: Record) { + this.auctionId = data.auction_id || null; + this.profileId = data.profile_id || null; + this.amount = data.amount || 0; + this.timestamp = data.timestamp || null; + this.at = data.timestamp ? new Date(data.timestamp) : null; + this.bidder = data.bidder || null; + } + + toString(): string { + return `${this.bidder} bid ${this.amount} coins`; + } +} + +export default Bid; diff --git a/src/structures/SkyBlock/Auctions/PartialAuction.ts b/src/structures/SkyBlock/Auctions/PartialAuction.ts new file mode 100644 index 00000000..c9eae648 --- /dev/null +++ b/src/structures/SkyBlock/Auctions/PartialAuction.ts @@ -0,0 +1,13 @@ +import BaseAuction from './BaseAuction'; + +class PartialAuction extends BaseAuction { + buyer: string | null; + price: number; + constructor(data: Record, includeItemBytes: boolean) { + super(data, includeItemBytes); + this.buyer = data.buyer || null; + this.price = parseInt(data.price, 10) || 0; + } +} + +export default PartialAuction; diff --git a/src/structures/SkyBlock/Bazzar/Order.ts b/src/structures/SkyBlock/Bazzar/Order.ts new file mode 100644 index 00000000..0e204cea --- /dev/null +++ b/src/structures/SkyBlock/Bazzar/Order.ts @@ -0,0 +1,21 @@ +/** + * Order class + */ +class Order { + amount: number; + pricePerUnit: number; + totalPrice: number; + orders: number; + constructor(data: Record) { + this.amount = data.amount || 0; + this.pricePerUnit = data.pricePerUnit || 0; + this.totalPrice = Math.round(this.amount * this.pricePerUnit * 10) / 10; + this.orders = data.orders || 0; + } + + toString(): number { + return this.totalPrice; + } +} + +export default Order; diff --git a/src/structures/SkyBlock/Bazzar/Product.ts b/src/structures/SkyBlock/Bazzar/Product.ts new file mode 100644 index 00000000..82e4ccd6 --- /dev/null +++ b/src/structures/SkyBlock/Bazzar/Product.ts @@ -0,0 +1,33 @@ +import { ProductStatus } from '../../../typings'; +import Order from './Order'; + +/** + * Product class + */ +class Product { + productId: string; + sellSummary: Order[]; + buySummary: Order[]; + status: ProductStatus; + constructor(data: Record) { + this.productId = data.product_id; + this.sellSummary = data.sell_summary.length + ? data.sell_summary.map((sellOrder: Record) => new Order(sellOrder)) + : []; + this.buySummary = data.buy_summary.length + ? data.buy_summary.map((buyOrder: Record) => new Order(buyOrder)) + : []; + this.status = { + sellPrice: isNaN(data.quick_status.sellPrice) ? 0 : Math.round(data.quick_status.sellPrice * 100) / 100, + buyPrice: isNaN(data.quick_status.buyPrice) ? 0 : Math.round(data.quick_status.buyPrice * 100) / 100, + sellVolume: isNaN(data.quick_status.sellVolume) ? 0 : data.quick_status.sellVolume, + buyVolume: isNaN(data.quick_status.buyVolume) ? 0 : data.quick_status.buyVolume, + sellMovingWeek: isNaN(data.quick_status.sellMovingWeek) ? 0 : data.quick_status.sellMovingWeek, + buyMovingWeek: isNaN(data.quick_status.buyMovingWeek) ? 0 : data.quick_status.buyMovingWeek, + sellOrders: isNaN(data.quick_status.sellOrders) ? 0 : data.quick_status.sellOrders, + buyOrders: isNaN(data.quick_status.buyOrders) ? 0 : data.quick_status.buyOrders + }; + } +} + +export default Product; diff --git a/src/structures/SkyBlock/News/SkyblockNews.ts b/src/structures/SkyBlock/News/SkyblockNews.ts new file mode 100644 index 00000000..320ba0f5 --- /dev/null +++ b/src/structures/SkyBlock/News/SkyblockNews.ts @@ -0,0 +1,37 @@ +const dateRegExp = /(\d{1,2})(?:st|nd|rd|th|) ([A-Za-z]+) (\d+)/; +const versionRegExp = /v\d+(\.\d+){1,}/; + +function parseDate(stringDate: string): Date | null { + const matched = stringDate.match(dateRegExp); + if (!matched) return null; + return new Date(matched.slice(1).join(' ')); +} + +function parseVer(stringVer: string): string | null { + const matches = versionRegExp.exec(stringVer); + if (!matches?.length) return null; + return matches[0]; +} +/** + * SkyblockNews + */ +class SkyblockNews { + title: string; + link: string; + rawDate: string; + date: Date | null; + version: string | null; + constructor(data: Record) { + this.title = data.title; + this.link = data.link; + this.rawDate = data.text; + this.date = parseDate(data.text); + this.version = parseVer(this.title); + } + + toString(): string { + return this.title; + } +} + +export default SkyblockNews; diff --git a/src/structures/SkyBlock/PlayerBingo.ts b/src/structures/SkyBlock/PlayerBingo.ts new file mode 100644 index 00000000..f49b8b01 --- /dev/null +++ b/src/structures/SkyBlock/PlayerBingo.ts @@ -0,0 +1,22 @@ +import { PlayerBingoDataPerEvent } from '../../typings'; + +/** + * Player Bingo Class + */ +class PlayerBingo { + dataPerEvent: PlayerBingoDataPerEvent[]; + constructor(data: Record) { + const events = data.success && Array.isArray(data.events) ? data.events : []; + this.dataPerEvent = events.map((eventData) => { + let doneGoals = eventData.completed_goals; + if (!Array.isArray(doneGoals)) doneGoals = []; + return { + eventId: parseInt(eventData.key, 10) || 0, + points: parseInt(eventData.points, 10) || 0, + goalsCompleted: doneGoals + }; + }); + } +} + +export default PlayerBingo; diff --git a/src/structures/SkyBlock/SkyblockGarden.ts b/src/structures/SkyBlock/SkyblockGarden.ts new file mode 100644 index 00000000..b4279b9c --- /dev/null +++ b/src/structures/SkyBlock/SkyblockGarden.ts @@ -0,0 +1,74 @@ +import { + SkyblockGardenCropMilestones, + SkyblockGardenComposter, + SkyblockGardenVisitor, + SkyblockGarenCrops, + SkyblockSkillLevel +} from '../../typings'; +import { getLevelByXp } from '../../utils/SkyblockUtils'; + +/** + * Skyblock Garden class + */ +class SkyblockGarden { + level: SkyblockSkillLevel; + barnSkin: string; + unlockedPlots: string[]; + visitors: SkyblockGardenVisitor; + cropMilestones: SkyblockGardenCropMilestones; + composter: SkyblockGardenComposter; + cropUpgrades: SkyblockGarenCrops; + constructor(data: Record) { + this.level = getLevelByXp(data?.garden?.garden_experience || 0, 'garden'); + this.barnSkin = data.garden?.selected_barn_skin || ''; + this.unlockedPlots = data.garden?.unlocked_plots_ids || []; + this.visitors = { + visited: data.garden?.commission_data?.visits || {}, + completed: data.garden?.commission_data?.completed || {}, + served: { + total: data.garden?.commission_data?.total_completed || 0, + unique: data.garden?.commission_data?.unique_npcs_served || 0 + } + }; + this.cropMilestones = { + wheat: getLevelByXp(data.garden?.resources_collected?.WHEAT || 0, 'wheat'), + carrot: getLevelByXp(data.garden?.resources_collected?.CARROT_ITEM || 0, 'carrot'), + sugarCane: getLevelByXp(data.garden?.resources_collected?.SUGAR_CANE || 0, 'sugarCane'), + potato: getLevelByXp(data.garden?.resources_collected?.POTATO_ITEM || 0, 'potato'), + pumpkin: getLevelByXp(data.garden?.resources_collected?.PUMPKIN || 0, 'pumpkin'), + melon: getLevelByXp(data.garden?.resources_collected?.MELON || 0, 'melon'), + cactus: getLevelByXp(data.garden?.resources_collected?.CACTUS || 0, 'cactus'), + cocoBeans: getLevelByXp(data.garden?.resources_collected?.['INK_SACK:3'] || 0, 'cocoBeans'), + mushroom: getLevelByXp(data.garden?.resources_collected?.MUSHROOM_COLLECTION || 0, 'mushroom'), + netherWart: getLevelByXp(data.garden?.resources_collected?.NETHER_STALK || 0, 'netherWart') + }; + this.composter = { + organicMatter: data.garden?.composter_data?.organic_matter || 0, + fuelUnits: data.garden?.composter_data?.fuel_units || 0, + compostUnits: data.garden?.composter_data?.compost_units || 0, + compostItems: data.garden?.composter_data?.compost_items || 0, + conversionTicks: data.garden?.composter_data?.conversion_ticks || 0, + upgrades: { + speed: data.garden?.composter_data?.upgrades?.speed || 0, + multiDrop: data.garden?.composter_data?.upgrades?.multi_drop || 0, + fuelCap: data.garden?.composter_data?.upgrades?.fuel_cap || 0, + organicMatterCap: data.garden?.composter_data?.upgrades?.organic_matter_cap || 0, + costReduction: data.garden?.composter_data?.upgrades?.cost_reduction || 0 + } + }; + this.cropUpgrades = { + wheat: data.garden?.crop_upgrade_levels?.WHEAT || 0, + carrot: data.garden?.crop_upgrade_levels?.CARROT_ITEM || 0, + sugarCane: data.garden?.crop_upgrade_levels?.SUGAR_CANE || 0, + potato: data.garden?.crop_upgrade_levels?.POTATO_ITEM || 0, + pumpkin: data.garden?.crop_upgrade_levels?.PUMPKIN || 0, + melon: data.garden?.crop_upgrade_levels?.MELON || 0, + cactus: data.garden?.crop_upgrade_levels?.CACTUS || 0, + cocoBeans: data.garden?.crop_upgrade_levels?.['INK_SACK:3'] || 0, + mushroom: data.garden?.crop_upgrade_levels?.MUSHROOM_COLLECTION || 0, + netherWart: data.garden?.crop_upgrade_levels?.NETHER_STALK || 0 + }; + } +} + +export default SkyblockGarden; diff --git a/src/structures/SkyBlock/SkyblockInventoryItem.ts b/src/structures/SkyBlock/SkyblockInventoryItem.ts new file mode 100644 index 00000000..ee99c0ca --- /dev/null +++ b/src/structures/SkyBlock/SkyblockInventoryItem.ts @@ -0,0 +1,84 @@ +import { parseGearScore, parseRarity } from '../../utils/SkyblockUtils'; +import { SkyblockGemstoneQuality } from '../../typings'; + +class SkyblockGemstone { + type: string; + quality: SkyblockGemstoneQuality; + constructor(data: Record) { + this.type = data.type; + this.quality = data.quality; + } +} +/** + * Item class + */ +class SkyblockInventoryItem { + itemId: number; + count: number; + name: string; + lore: string; + loreArray: string[]; + loreForEmbed: string; + color: string | null; + enchantments: Record; + reforge: string; + gemstones: SkyblockGemstone[]; + damage: number; + rarity: string; + dungeonStars: number; + gearScore: number; + uuid: string; + soulbound: boolean; + artOfWar: number; + rune: object; + hotPotatoBooks: number; + recombobulated: boolean; + attributes: object; + hecatomb: number; + champion: number; + cultivating: number; + expertise: number; + compact: number; + blocksWalked: number; + constructor(data: Record) { + this.itemId = data.id || 0; + this.count = data.Count || 0; + this.name = + null !== data.tag.display.Name ? data.tag.display.Name.toString().replace(/§([1-9]|[a-f])|§/gm, '') : null; + this.lore = data.tag.display.Lore.join('\n'); + this.loreArray = data.tag.display.Lore; + this.loreForEmbed = this.lore.replace(/§([0-9]|[a-f])|§/gm, '').replace(/
/gm, '\n'); + this.color = data.tag.ExtraAttributes.color ?? data.tag.display.color ?? null; + this.enchantments = data.tag.ExtraAttributes.enchantments ?? null; + this.reforge = data.tag.ExtraAttributes.modifier ?? null; + this.gemstones = Object.entries(data.tag.ExtraAttributes.gems).map((gem) => { + return { + type: gem[0].split('_')[0], + quality: gem[1] as SkyblockGemstoneQuality + }; + }); + this.damage = data.Damage || 0; + this.rarity = parseRarity(this.loreArray[this.loreArray.length - 1]); + this.dungeonStars = data.tag.ExtraAttributes.upgrade_level ?? 0; + this.gearScore = parseGearScore(this.loreArray); + this.uuid = data.tag.ExtraAttributes.uuid ?? ''; + this.soulbound = 1 === data.tag.ExtraAttributes.donated_museum; + this.artOfWar = data.tag.ExtraAttributes.art_of_war_count ?? 0; + this.rune = data.tag.ExtraAttributes.runes ?? null; + this.hotPotatoBooks = data.tag.ExtraAttributes.hot_potato_count ?? 0; + this.recombobulated = 1 === data.tag.ExtraAttributes.rarity_upgrades; + this.attributes = data.tag.ExtraAttributes.attributes ?? {}; + this.hecatomb = data.tag.ExtraAttributes.hecatomb_s_runs ?? 0; + this.champion = data.tag.ExtraAttributes.champion_combat_xp ?? 0; + this.cultivating = data.tag.ExtraAttributes.farmed_cultivating ?? 0; + this.expertise = data.tag.ExtraAttributes.expertise_kills ?? 0; + this.compact = data.tag.ExtraAttributes.compact_blocks ?? 0; + this.blocksWalked = data.tag.ExtraAttributes.blocks_walked ?? 0; + } + + toString(): string { + return this.name; + } +} + +export default SkyblockInventoryItem; diff --git a/src/structures/SkyBlock/SkyblockMember.ts b/src/structures/SkyBlock/SkyblockMember.ts new file mode 100644 index 00000000..0bc9fd08 --- /dev/null +++ b/src/structures/SkyBlock/SkyblockMember.ts @@ -0,0 +1,248 @@ +import { getNetworth, NetworthResult } from 'skyhelper-networth'; +import { + SkyblockMemberArmor, + SkyblockMemberChocolateFactoryData, + SkyblockMemberDungeons, + SkyblockMemberEquipment, + SkyblockMemberJacobData, + SkyblockMemberSkills, + SkyblockMemberSlayer, + SkyblockMemberStats, + SkyblockMemberTrophyFishRank, + SkyblockSkillLevel +} from '../../typings'; +import Constants from '../../utils/Constants'; +import { + decode, + getBestiaryLevel, + getChocolateFactory, + getDungeons, + getJacobData, + getLevelByXp, + getMemberStats, + getPetLevel, + getSkills, + getSlayer, + getTrophyFishRank +} from '../../utils/SkyblockUtils'; +import Player from '../Player'; +import SkyblockInventoryItem from './SkyblockInventoryItem'; +import SkyblockPet from './SkyblockPet'; + +/** + * Skyblock member class + */ +class SkyblockMember { + uuid: string; + player: Player | null; + gameMode: string | null; + selected: boolean; + profileName: string; + profileId: string; + firstJoinTimestamp: number; + firstJoinAt: Date; + experience: number; + level: number; + hotm: SkyblockSkillLevel; + trophyFish: SkyblockMemberTrophyFishRank; + highestMagicalPower: number; + fairySouls: number; + fairyExchanges: number; + skills: SkyblockMemberSkills; + bestiary: number; + slayer: SkyblockMemberSlayer | null; + dungeons: SkyblockMemberDungeons | null; + collections: object; + purse: number; + stats: SkyblockMemberStats | null; + pets: SkyblockPet[]; + jacob: SkyblockMemberJacobData; + chocolate: SkyblockMemberChocolateFactoryData; + + getArmor: () => Promise; + getWardrobe: () => Promise; + getEnderChest: () => Promise; + getInventory: () => Promise; + getPetScore: () => number; + getEquipment: () => Promise; + getPersonalVault: () => Promise; + getNetworth: () => Promise; + constructor(data: Record) { + this.uuid = data.uuid; + this.player = data.m.player || null; + this.gameMode = data.gameMode; + this.selected = data.selected; + this.profileName = data.profileName; + this.profileId = data.profileId; + this.firstJoinTimestamp = data.m.profile?.first_join; + this.firstJoinAt = new Date(data.m.profile?.first_join); + this.experience = data.m.leveling?.experience ?? 0; + this.level = this.experience ? this.experience / 100 : 0; + this.hotm = getLevelByXp(data.m.mining_core?.experience, 'hotm'); + this.trophyFish = getTrophyFishRank(data.m.trophy_fish?.rewards?.length ?? 0); + this.highestMagicalPower = data.m.accessory_bag_storage?.highest_magical_power ?? 0; + this.fairySouls = data.m?.fairy_soul?.total_collected ?? 0; + this.fairyExchanges = data.m?.fairy_soul?.fairy_exchanges ?? 0; + this.skills = getSkills(data.m); + this.bestiary = getBestiaryLevel(data.m); + this.slayer = getSlayer(data.m); + this.dungeons = getDungeons(data.m); + this.collections = data.m.collection ? data.m.collection : null; + this.purse = data.m?.currencies?.coin_purse ?? 0; + this.stats = data.m.player_stats ? getMemberStats(data.m.player_stats) : null; + this.pets = data.m?.pets_data?.pets ? data.m.pets_data.pets.map((pet: any) => new SkyblockPet(pet)) : []; + this.jacob = getJacobData(data.m); + this.chocolate = getChocolateFactory(data.m); + this.getArmor = async () => { + const base64 = data.m.inventory.inv_armor; + const decoded = await decode(base64.data); + const armor = { + helmet: decoded[3].id ? new SkyblockInventoryItem(decoded[3]) : null, + chestplate: decoded[2].id ? new SkyblockInventoryItem(decoded[2]) : null, + leggings: decoded[1].id ? new SkyblockInventoryItem(decoded[1]) : null, + boots: decoded[0].id ? new SkyblockInventoryItem(decoded[0]) : null + }; + return armor; + }; + this.getWardrobe = async () => { + const base64 = data.m?.inventory?.wardrobe_contents?.data; + if (!base64) return []; + const decoded = await decode(base64); + const armor = decoded + .filter((item) => 0 !== Object.keys(item).length) + .map((item) => new SkyblockInventoryItem(item)); + return armor; + }; + this.getEnderChest = async () => { + let chest = data.m.inventory.ender_chest_contents; + if (!chest) return []; + + try { + chest = await decode(chest.data); + const edited = []; + for (let i = 0; i < chest.length; i++) { + if (!chest[i].id) { + continue; + } + edited.push(new SkyblockInventoryItem(chest[i])); + } + return edited; + } catch { + return []; + } + }; + this.getInventory = async () => { + let inventory = data.m.inventory.inv_contents; + if (!inventory) return []; + + try { + inventory = await decode(inventory.data); + const edited = []; + for (let i = 0; i < inventory.length; i++) { + if (!inventory[i].id) { + continue; + } + edited.push(new SkyblockInventoryItem(inventory[i])); + } + return edited; + } catch { + return []; + } + }; + this.getPetScore = () => { + const highestRarity: { [key: string]: any } = {}; + for (const pet of data.m.pets_data.pets) { + if ( + !(pet.type in highestRarity) || + (Constants.petScore as { [key: number]: number })[pet.tier] > highestRarity[pet.type] + ) { + highestRarity[pet.type] = (Constants.petScore as { [key: number]: number })[pet.tier]; + } + } + + const highestLevel: { [key: string]: any } = {}; + for (const pet of data.m.pets_data.pets) { + const maxLevel = 'GOLDEN_DRAGON' === pet.type ? 200 : 100; + const petLevel = getPetLevel(pet.exp, pet.tier, maxLevel); + + if (!(pet.type in highestLevel) || petLevel.level > highestLevel[pet.type]) { + if (petLevel.level < maxLevel) { + continue; + } + + highestLevel[pet.type] = 1; + } + } + + return ( + Object.values(highestRarity).reduce((a, b) => a + b, 0) + Object.values(highestLevel).reduce((a, b) => a + b, 0) + ); + }; + this.getEquipment = async () => { + let equipment = data.m.inventory.equipment_contents; + if (!equipment) { + return { + gauntlet: null, + belt: null, + cloak: null, + necklace: null + }; + } + + try { + equipment = await decode(equipment.data); + const playerEquipment = { + gauntlet: equipment[3].id ? new SkyblockInventoryItem(equipment[3]) : null, + belt: equipment[2].id ? new SkyblockInventoryItem(equipment[2]) : null, + cloak: equipment[1].id ? new SkyblockInventoryItem(equipment[1]) : null, + necklace: equipment[0].id ? new SkyblockInventoryItem(equipment[0]) : null + }; + return playerEquipment; + } catch { + return { + gauntlet: null, + belt: null, + cloak: null, + necklace: null + }; + } + }; + this.getPersonalVault = async () => { + let vault = data.m.inventory.personal_vault_contents; + if (!vault) return []; + + try { + vault = await decode(vault.data); + const edited = []; + for (let i = 0; i < vault.length; i++) { + if (!vault[i].id) { + continue; + } + edited.push(new SkyblockInventoryItem(vault[i])); + } + return edited; + } catch { + return []; + } + }; + this.getNetworth = async () => { + try { + const nw = await getNetworth(data.m, data.banking?.balance ?? 0, { + onlyNetworth: true, + v2Endpoint: true, + cache: true, + museumData: data.museum?.raw ?? {} + }); + return nw; + } catch { + return null; + } + }; + } + + toString(): string { + return this.uuid; + } +} + +export default SkyblockMember; diff --git a/src/structures/SkyBlock/SkyblockMuseum.ts b/src/structures/SkyBlock/SkyblockMuseum.ts new file mode 100644 index 00000000..94fa3cef --- /dev/null +++ b/src/structures/SkyBlock/SkyblockMuseum.ts @@ -0,0 +1,49 @@ +import SkyblockMuseumItem from './SkyblockMuseumItem'; +import { decode } from '../../utils/SkyblockUtils'; + +/** + * Skyblock Museum class + */ +class SkyblockMuseum { + raw: Record; + getItems: () => Promise; + getSpecial: () => Promise; + constructor(data: Record) { + this.raw = data.m.members?.[data.uuid] ?? {}; + this.getItems = async (): Promise => { + const keys = Object.keys(data.m.members[data.uuid].items); + const items = []; + for (const key of keys) { + const decoded = await decode(data.m.members[data.uuid].items[key].items.data); + items.push( + new SkyblockMuseumItem({ + decoded: decoded, + borrowing: data.m.members[data.uuid].items[key].borrowing ?? false, + featuredSlot: data.m.members[data.uuid].items[key].featured_slot ?? null, + donatedTime: data.m.members[data.uuid].items[key].donated_time, + name: key.toLowerCase().replace(/_/g, ' ') + }) + ); + } + return items; + }; + this.getSpecial = async (): Promise => { + const items = []; + for (const item of data.m.members[data.uuid].special) { + const decoded = await decode(item.items.data); + items.push( + new SkyblockMuseumItem({ + decoded: decoded, + borrowing: item.borrowing ?? false, + featuredSlot: item.featured_slot ?? null, + donatedTime: item.donated_time, + name: null + }) + ); + } + return items; + }; + } +} + +export default SkyblockMuseum; diff --git a/src/structures/SkyBlock/SkyblockMuseumItem.ts b/src/structures/SkyBlock/SkyblockMuseumItem.ts new file mode 100644 index 00000000..532fd53b --- /dev/null +++ b/src/structures/SkyBlock/SkyblockMuseumItem.ts @@ -0,0 +1,31 @@ +import SkyblockInventoryItem from './SkyblockInventoryItem'; + +/** + * Item class + */ +class SkyblockMuseumItem { + name: string | null; + items: SkyblockInventoryItem[]; + donatedTime: number; + donatedTimeAt: Date; + borrowing: boolean; + featuredSlot: string | null; + constructor(data: Record) { + this.name = data.name; + this.items = []; + data.decoded.forEach((item: any) => { + if (!item.tag) return; + this.items.push(new SkyblockInventoryItem(item)); + }); + this.donatedTime = data.donatedTime; + this.donatedTimeAt = new Date(data.donatedTime); + this.borrowing = data.borrowing; + this.featuredSlot = data.featuredSlot; + } + + toString(): string | null { + return this.name; + } +} + +export default SkyblockMuseumItem; diff --git a/src/structures/SkyBlock/SkyblockPet.ts b/src/structures/SkyBlock/SkyblockPet.ts new file mode 100644 index 00000000..b5a51d72 --- /dev/null +++ b/src/structures/SkyBlock/SkyblockPet.ts @@ -0,0 +1,29 @@ +import { SkyblockRarity } from '../../typings'; +import Constants from '../../utils/Constants'; + +/** + * Skyblock Pet class + */ +class SkyblockPet { + uuid: string; + type: string; + xp: number; + active: boolean; + rarity: SkyblockRarity; + petScore: number; + heldItem: string | null; + candyUsed: number; + skin: string | null; + constructor(data: Record) { + this.uuid = data.uuid; + this.type = data.type; + this.xp = data.exp || 0; + this.active = Boolean(data.active); + this.rarity = data.tier; + this.petScore = (Constants.petScore as { [key: number]: number })[data.tier] || 0; + this.heldItem = data.heldItem ? data.heldItem.replace(/^PET_ITEM_/, '') : null; + this.candyUsed = data.candyUsed || 0; + this.skin = data.skin; + } +} +export default SkyblockPet; diff --git a/src/structures/SkyBlock/SkyblockProfile.ts b/src/structures/SkyBlock/SkyblockProfile.ts new file mode 100644 index 00000000..9826d86e --- /dev/null +++ b/src/structures/SkyBlock/SkyblockProfile.ts @@ -0,0 +1,46 @@ +import SkyblockMember from './SkyblockMember'; + +/** + * Skyblock Profile class + */ +class SkyblockProfile { + profileId: string; + profileName: string; + gameMode: string | null; + banking: object; + communityUpgrades: object; + selected: boolean; + members: SkyblockMember[]; + me: SkyblockMember | undefined; + constructor(data: Record) { + this.profileId = data.profileId; + this.profileName = data.profileName; + this.gameMode = data.gameMode; + this.banking = data.banking; + this.communityUpgrades = data.communityUpgrades; + this.selected = data.selected; + this.members = Object.keys(data.members).map( + (uuid) => + new SkyblockMember({ + uuid: uuid, + profileId: this.profileId, + profileName: this.profileName, + gameMode: this.gameMode, + m: data.members[uuid], + banking: this.banking, + communityUpgrades: this.communityUpgrades, + selected: this.selected + }) + ); + this.me = this.members.find((x) => x.uuid === data.uuid); + } + /** + * Profile Name + * @return {string} + */ + toString() { + return this.profileName; + } +} + +export default SkyblockProfile; diff --git a/src/structures/SkyBlock/Static/Bingo.ts b/src/structures/SkyBlock/Static/Bingo.ts new file mode 100644 index 00000000..c9b93d6b --- /dev/null +++ b/src/structures/SkyBlock/Static/Bingo.ts @@ -0,0 +1,56 @@ +function parsePosition(position: any): [number, number] { + const x = (position % 5) + 1; + const y = Math.floor(position / 5) + 1; + return [x, y]; +} +/** + * Bingo class + */ +class Bingo { + name: string; + id: string; + row: number | null; + column: number | null; + rawLore: string; + lore: string; + tiers: number[]; + tierStep: number | null; + requiredAmount: number | null; + type: 'ONE_TIME' | 'ONE_TIER' | 'TIERED'; + constructor(data: Record, position: number = 0) { + this.name = data.name; + this.id = data.id; + const [row, column] = parsePosition(position); + this.row = row; + this.column = column; + this.rawLore = data.lore; + this.lore = data.lore?.replace?.(/§([1-9]|[a-l])|§/gm, '') || []; + this.tiers = Array.isArray(data.tiers) ? data.tiers.map((x) => parseInt(x, 10) || 0) : []; + this.tierStep = this.getTierStep(); + this.requiredAmount = parseInt(data.requiredAmount, 10) ?? null; + this.type = this.tiers ? 'TIERED' : this.requiredAmount ? 'ONE_TIER' : 'ONE_TIME'; + } + + toString(): string { + return this.id; + } + /** + * Gets tier step, if constant + * @private + * @returns {number|null} + */ + getTierStep() { + if ('TIERED' !== this.type) return null; + // No step possible + if (2 > this.tiers.length) return null; + const hypotheticStep = this.tiers[1] - this.tiers[0]; + // Check if every 2 elements have the same step + const isConstant = this.tiers.slice(1).every((el, index) => { + return hypotheticStep === this.tiers[index - 1] - el; + }); + if (!isConstant) return null; + return hypotheticStep; + } +} + +export default Bingo; diff --git a/src/structures/SkyBlock/Static/BingoData.ts b/src/structures/SkyBlock/Static/BingoData.ts new file mode 100644 index 00000000..9a59061e --- /dev/null +++ b/src/structures/SkyBlock/Static/BingoData.ts @@ -0,0 +1,24 @@ +import Bingo from './Bingo'; + +/** + * SB Bingo Class + */ +class BingoData { + lastUpdatedTimestamp: number; + lastUpdatedAt: Date | null; + id: number | null; + goals: Bingo[] | null; + constructor(data: Record) { + this.lastUpdatedTimestamp = parseInt(data.lastUpdated, 10); + this.lastUpdatedAt = new Date(this.lastUpdatedTimestamp); + this.id = parseInt(data.id, 10) || null; + this.goals = Array.isArray(data.goals) ? data.goals.map((goal, index) => new Bingo(goal, index)) : null; + } + + getGoal(column: number, row: number): Bingo | undefined { + if (!this.goals || 1 > this.goals.length) return; + return this.goals.find((goal) => goal.row === row && goal.column === column); + } +} + +export default BingoData; diff --git a/src/structures/SkyBlock/Static/Candidate.ts b/src/structures/SkyBlock/Static/Candidate.ts new file mode 100644 index 00000000..a35cb69f --- /dev/null +++ b/src/structures/SkyBlock/Static/Candidate.ts @@ -0,0 +1,21 @@ +import Perk from './Perk'; + +/** + * Candidate class + */ +class Candidate { + name: string; + keyBenefit: string; + perks: Perk[]; + isMayor: boolean; + votesReceived: number; + constructor(data: Record, isMayor: boolean = false) { + this.name = data.name; + this.keyBenefit = data.key; + this.perks = data.perks.map((x: any) => new Perk(x)); + this.isMayor = isMayor || false; + this.votesReceived = parseInt(data.votes, 10) || 0; + } +} + +export default Candidate; diff --git a/src/structures/SkyBlock/Static/FireSale.ts b/src/structures/SkyBlock/Static/FireSale.ts new file mode 100644 index 00000000..3f12e93f --- /dev/null +++ b/src/structures/SkyBlock/Static/FireSale.ts @@ -0,0 +1,27 @@ +/** + * SB Fire Sale + */ +class FireSale { + itemId: string | null; + startTimestamp: number; + startAt: Date; + endTimestamp: number; + endAt: Date; + amount: number; + price: number; + constructor(data: Record) { + this.itemId = data.item_id || null; + this.startTimestamp = parseInt(data.start, 10); + this.startAt = new Date(this.startTimestamp); + this.endTimestamp = parseInt(data.end, 10); + this.endAt = new Date(this.endTimestamp); + this.amount = data.amount || 0; + this.price = data.price || 0; + } + + toString(): string | null { + return this.itemId; + } +} + +export default FireSale; diff --git a/src/structures/SkyBlock/Static/Government.ts b/src/structures/SkyBlock/Static/Government.ts new file mode 100644 index 00000000..46245662 --- /dev/null +++ b/src/structures/SkyBlock/Static/Government.ts @@ -0,0 +1,45 @@ +import Candidate from './Candidate'; + +/** + * SB Government Class + */ +class GovernmentData { + lastUpdatedTimestamp: number; + lastUpdatedAt: Date | null; + lastElectionResults: Map; + mayor: Candidate | undefined; + runningYear: number; + currentElectionResults: Map | null; + currentElectionFor: number | null; + constructor(data: Record) { + this.lastUpdatedTimestamp = parseInt(data.lastUpdated, 10); + this.lastUpdatedAt = new Date(this.lastUpdatedTimestamp); + const lastElectionResults = data.mayor.election.candidates.map( + (x: any) => new Candidate(x, x.name === data.mayor.name) + ); + this.lastElectionResults = new Map( + lastElectionResults + .sort((a: any, b: any) => a.votesReceived - b.votesReceived) + .reverse() + .map((x: any) => [x.name, x]) + ); + this.mayor = this.lastElectionResults.get(data.mayor.name); + this.runningYear = parseInt(data.mayor.election.year, 10) || 0; + const thisElection = data.current?.candidates.map((x: any) => new Candidate(x, x.name === data.mayor.name)) || null; + this.currentElectionResults = thisElection + ? new Map( + thisElection + .sort((a: any, b: any) => a.votesReceived - b.votesReceived) + .reverse() + .map((x: any) => [x.name, x]) + ) + : null; + this.currentElectionFor = parseInt(data.current?.year, 10) || null; + } + + toString(): string { + return this.mayor?.name || ''; + } +} + +export default GovernmentData; diff --git a/src/structures/SkyBlock/Static/Perk.ts b/src/structures/SkyBlock/Static/Perk.ts new file mode 100644 index 00000000..159a3e85 --- /dev/null +++ b/src/structures/SkyBlock/Static/Perk.ts @@ -0,0 +1,13 @@ +/** + * Candidate class + */ +class Perk { + name: string; + description: string; + constructor(data: Record) { + this.name = data.name; + this.description = data.description; + } +} + +export default Perk; diff --git a/src/structures/Static/Achievement.ts b/src/structures/Static/Achievement.ts new file mode 100644 index 00000000..793f36ec --- /dev/null +++ b/src/structures/Static/Achievement.ts @@ -0,0 +1,52 @@ +import AchievementTier from './AchievementTier'; + +function collectAll(data: AchievementTier | null) { + if (null === data) { + return { totalPoints: 0, totalAmount: 0 }; + } + const mTier = data.maxTier; + let totalPoints = 0; + let totalAmount = 0; + for (let i = 1; i <= mTier; i++) { + totalPoints += data.getTier(i).pointsRewarded; + totalAmount += data.getTier(i).amountRequired; + } + return { totalPoints, totalAmount }; +} + +/** + * Achievement Class + */ +class Achievement { + name: string; + codeName: string; + description: string; + type: 'ONE_TIME' | 'TIERED'; + rarity: Record<'local' | 'localPercentage' | 'global' | 'globalPercentage', number> | null; + tierInformation: AchievementTier | null; + points: number; + totalAmountRequired: number | null; + constructor(achievementName: string, data: Record) { + this.name = data.name.trim(); + this.codeName = achievementName; + this.description = data.description.trim(); + this.type = data.tiers ? 'TIERED' : 'ONE_TIME'; + this.rarity = { + local: parseFloat(data.gamePercentUnlocked) || 0, + localPercentage: parseFloat(data.gamePercentUnlocked) * 100 || 0, + global: data.globalPercentUnlocked, + globalPercentage: parseFloat(data.globalPercentUnlocked) * 100 || 0 + }; + this.tierInformation = 'TIERED' === this.type ? new AchievementTier(data.tiers) : null; + + const { totalPoints = 0, totalAmount = 0 }: { totalPoints: number; totalAmount: number } = + 'TIERED' === this.type ? collectAll(this.tierInformation) : { totalPoints: 0, totalAmount: 0 }; + this.points = 'ONE_TIME' === this.type ? parseInt(data.points, 10) : totalPoints; + this.totalAmountRequired = 'TIERED' === this.type ? totalAmount : null; + } + toString(): string { + return this.codeName; + } +} + +export default Achievement; diff --git a/src/structures/Static/AchievementTier.ts b/src/structures/Static/AchievementTier.ts new file mode 100644 index 00000000..e29d5d2f --- /dev/null +++ b/src/structures/Static/AchievementTier.ts @@ -0,0 +1,24 @@ +/** + * AchievementTier class + */ +class AchievementTier { + maxTier: number; + tierInfo: any; + constructor(data: Record) { + this.maxTier = data.length; + this.tierInfo = data.sort( + ({ tier: tierA }: { tier: number }, { tier: tierB }: { tier: number }) => Number(tierA) - Number(tierB) + ); + } + + getTier(tier: number): Record<'pointsRewarded' | 'amountRequired', number> { + const index = tier - 1; + const info = this.tierInfo[index]; + return { + pointsRewarded: parseInt(info.points, 10) || 0, + amountRequired: parseInt(info.amount, 10) || 0 + }; + } +} + +export default AchievementTier; diff --git a/src/structures/Static/Achievements.ts b/src/structures/Static/Achievements.ts new file mode 100644 index 00000000..e4b63563 --- /dev/null +++ b/src/structures/Static/Achievements.ts @@ -0,0 +1,23 @@ +import { StaticGameNames } from '../../typings'; +import GameAchievements from './GameAchievements'; + +/** + * Achievement class + */ +class Achievements { + lastUpdatedTimestamp: number; + lastUpdatedAt: Date | null; + achievementsPerGame: Record; + constructor(data: Record) { + this.lastUpdatedTimestamp = parseInt(data.lastUpdated, 10); + this.lastUpdatedAt = new Date(this.lastUpdatedTimestamp); + this.achievementsPerGame = Object.fromEntries( + Object.entries(data.achievements).map(([game, data]) => [ + game, + new GameAchievements(game as StaticGameNames, data as Record) + ]) + ) as Record; + } +} + +export default Achievements; diff --git a/src/structures/Static/Challenges.ts b/src/structures/Static/Challenges.ts new file mode 100644 index 00000000..34710588 --- /dev/null +++ b/src/structures/Static/Challenges.ts @@ -0,0 +1,23 @@ +import { StaticGameNames } from '../../typings'; +import GameChallenges from './GameChallenges'; + +/** + * Challenges class + */ +class Challenges { + lastUpdatedTimestamp: number; + lastUpdatedAt: Date | null; + challengesPerGame: Record; + constructor(data: Record) { + this.lastUpdatedTimestamp = parseInt(data.lastUpdated, 10); + this.lastUpdatedAt = new Date(this.lastUpdatedTimestamp); + this.challengesPerGame = Object.fromEntries( + Object.entries(data.challenges).map(([game, data]) => [ + game, + new GameChallenges(game as StaticGameNames, data as Record) + ]) + ) as Record; + } +} + +export default Challenges; diff --git a/src/structures/Static/GameAchievements.ts b/src/structures/Static/GameAchievements.ts new file mode 100644 index 00000000..fc6ec95c --- /dev/null +++ b/src/structures/Static/GameAchievements.ts @@ -0,0 +1,22 @@ +import { StaticGameNames } from '../../typings'; +import Achievement from './Achievement'; + +/** + * Game achievements class + */ +class GameAchievements { + category: StaticGameNames; + totalPoints: number; + totalLegacyPoints: number; + achievements: Achievement[]; + constructor(name: StaticGameNames, data: Record) { + this.category = name; + this.totalPoints = parseInt(data.total_points, 10) || 0; + this.totalLegacyPoints = parseInt(data.total_legacy_points, 10) || 0; + this.achievements = Object.entries({ ...(data.one_time || {}), ...(data.tiered || {}) }).map( + ([name, data]) => new Achievement(name, data as Record) + ); + } +} + +export default GameAchievements; diff --git a/src/structures/Static/GameChallenges.ts b/src/structures/Static/GameChallenges.ts new file mode 100644 index 00000000..4ebdfd09 --- /dev/null +++ b/src/structures/Static/GameChallenges.ts @@ -0,0 +1,25 @@ +import { ChallengeData, StaticGameNames } from '../../typings'; + +/** + * Game challenges class + */ +class GameChallenges { + category: StaticGameNames; + challenges: Map; + constructor(name: StaticGameNames, data: Record) { + this.category = name; + this.challenges = new Map(); + + data.forEach((challenge: any) => { + const content = { + id: challenge.id, + name: challenge.name, + reward: parseInt(challenge.rewards[0].amount, 10) || 0, + rewardType: challenge.rewards[0].type + }; + this.challenges.set(challenge.id, content); + }); + } +} + +export default GameChallenges; diff --git a/src/structures/Static/GameQuests.ts b/src/structures/Static/GameQuests.ts new file mode 100644 index 00000000..ad2a8613 --- /dev/null +++ b/src/structures/Static/GameQuests.ts @@ -0,0 +1,16 @@ +import { StaticGameNames } from '../../typings'; +import Quest from './Quest'; + +/** + * Game quests class + */ +class GameQuests { + game: StaticGameNames; + quests: Quest[]; + constructor(name: StaticGameNames, data: Record) { + this.game = name; + this.quests = (data || []).map((x: any) => new Quest(x)); + } +} + +export default GameQuests; diff --git a/src/structures/Static/GuildAchievements.ts b/src/structures/Static/GuildAchievements.ts new file mode 100644 index 00000000..c0a53018 --- /dev/null +++ b/src/structures/Static/GuildAchievements.ts @@ -0,0 +1,22 @@ +import Achievement from './Achievement'; + +/** + * Achievement class + */ +class GuildAchievements { + lastUpdatedTimestamp: number; + lastUpdatedAt: Date | null; + achievements: Record; + constructor(data: Record) { + this.lastUpdatedTimestamp = parseInt(data.lastUpdated, 10); + this.lastUpdatedAt = new Date(this.lastUpdatedTimestamp); + this.achievements = Object.fromEntries( + Object.entries({ ...(data.tiered || {}), ...(data.one_time || {}) }).map(([name, value]) => [ + name, + new Achievement(name, value as Record) + ]) + ); + } +} + +export default GuildAchievements; diff --git a/src/structures/Static/Quest.ts b/src/structures/Static/Quest.ts new file mode 100644 index 00000000..6cf8a876 --- /dev/null +++ b/src/structures/Static/Quest.ts @@ -0,0 +1,30 @@ +import { QuestObjective, QuestReward } from '../../typings'; + +/** + * Quest Class + */ +class Quest { + questName: string; + questID: string; + description: string; + type: 'DAILY' | 'WEEKLY'; + objectives: QuestObjective[]; + rewards: QuestReward[]; + constructor(data: Record) { + this.questName = data.name.trim(); + this.questID = data.id; + this.description = data.description.trim(); + this.type = 'DailyResetQuestRequirement' === data.requirements?.[0].type ? 'DAILY' : 'WEEKLY'; + this.objectives = data.objectives.map((objective: any) => ({ + id: objective.id, + type: 'IntegerObjective' === objective.type ? 'Integer' : 'Boolean', + amountNeeded: parseInt(objective.integer || '1', 10) + })); + this.rewards = data.rewards || []; + } + toString(): string { + return this.questName; + } +} + +export default Quest; diff --git a/src/structures/Static/Quests.ts b/src/structures/Static/Quests.ts new file mode 100644 index 00000000..a57cf33f --- /dev/null +++ b/src/structures/Static/Quests.ts @@ -0,0 +1,23 @@ +import { StaticGameNames } from '../../typings'; +import GameQuests from './GameQuests'; + +/** + * Quest class + */ +class Quests { + lastUpdatedTimestamp: number; + lastUpdatedAt: Date | null; + questsPerGame: Record; + constructor(data: Record) { + this.lastUpdatedTimestamp = parseInt(data.lastUpdated, 10); + this.lastUpdatedAt = new Date(this.lastUpdatedTimestamp); + this.questsPerGame = Object.fromEntries( + Object.entries(data.quests).map(([game, data]) => [ + game, + new GameQuests(game as StaticGameNames, data as Record) + ]) + ) as Record; + } +} + +export default Quests; diff --git a/src/structures/Status.ts b/src/structures/Status.ts new file mode 100644 index 00000000..5f7b494b --- /dev/null +++ b/src/structures/Status.ts @@ -0,0 +1,19 @@ +import Game from './Game'; + +class Status { + online: boolean; + game: Game | null; + mode: string | null; + map: string | null; + constructor(data: Record) { + this.online = data.online; + this.game = data.gameType ? new Game(data.gameType) : null; + this.mode = data.mode ?? null; + this.map = data.map ?? null; + } + toString(): string { + return this.online ? 'Online' : 'Offline'; + } +} + +export default Status; diff --git a/src/structures/Watchdog/Stats.ts b/src/structures/Watchdog/Stats.ts new file mode 100644 index 00000000..b52cc32e --- /dev/null +++ b/src/structures/Watchdog/Stats.ts @@ -0,0 +1,19 @@ +/** + * WatchdogStats class + */ +class WatchdogStats { + byWatchdogTotal: number; + byWatchdogLastMinute: number; + byWatchdogRollingDay: number; + byStaffTotal: number; + byStaffRollingDay: number; + constructor(data: Record) { + this.byWatchdogTotal = data.watchdog_total || 0; + this.byWatchdogLastMinute = data.watchdog_lastMinute || 0; + this.byWatchdogRollingDay = data.watchdog_rollingDaily || 0; + this.byStaffTotal = data.staff_total || 0; + this.byStaffRollingDay = data.staff_rollingDaily || 0; + } +} + +export default WatchdogStats; diff --git a/src/typings/index.d.ts b/src/typings/index.d.ts new file mode 100644 index 00000000..5de4b09a --- /dev/null +++ b/src/typings/index.d.ts @@ -0,0 +1,751 @@ +import SkyblockInventoryItem from '../structures/SkyBlock/SkyblockInventoryItem'; +import PitInventoryItem from '../structures/MiniGames/PitInventoryItem'; +import Cache from '../Private/defaultCache'; +import Arcade from '../structures/MiniGames/Arcade'; +import ArenaBrawl from '../structures/MiniGames/ArenaBrawl'; +import BedWars from '../structures/MiniGames/BedWars'; +import BlitzSurvivalGames from '../structures/MiniGames/BlitzSurvivalGames'; +import BuildBattle from '../structures/MiniGames/BuildBattle'; +import CopsAndCrims from '../structures/MiniGames/CopsAndCrims'; +import Duels from '../structures/MiniGames/Duels'; +import MegaWalls from '../structures/MiniGames/MegaWalls'; +import MurderMystery from '../structures/MiniGames/MurderMystery'; +import Paintball from '../structures/MiniGames/Paintball'; +import Pit from '../structures/MiniGames/Pit'; +import Quakecraft from '../structures/MiniGames/Quakecraft'; +import SkyWars from '../structures/MiniGames/SkyWars'; +import SmashHeroes from '../structures/MiniGames/SmashHeroes'; +import SpeedUHC from '../structures/MiniGames/SpeedUHC'; +import TNTGames from '../structures/MiniGames/TNTGames'; +import TurboKartRacers from '../structures/MiniGames/TurboKartRacers'; +import UHC from '../structures/MiniGames/UHC'; +import VampireZ from '../structures/MiniGames/VampireZ'; +import Walls from '../structures/MiniGames/Walls'; +import Warlords from '../structures/MiniGames/Warlords'; +import WoolWars from '../structures/MiniGames/WoolWars'; + +export type ActionFilterType = 'PROFILE' | 'PLAYER' | 'AUCTION'; +export type GuildSearchParameter = 'id' | 'name' | 'player'; +export type RatelimitOptions = 'AUTO' | 'HARD' | 'NONE'; +export interface ClientOptions { + cache?: boolean; + hypixelCacheTime?: number; + mojangCacheTime?: number; + cacheHandler?: Cache; + rateLimit?: RatelimitOptions; + syncWithHeaders?: boolean; + keyLimit?: number; + cacheSize?: number; + silent?: boolean; + headers?: object; + checkForUpdates?: boolean; + useThirdPartyAPI?: boolean | string; +} + +export interface UpdateHandler { + checkForUpdates(): void; + compare(a: string, b: string): number; +} + +export interface ExpHistory { + day: string; + date: Date | undefined; + exp: number; + totalExp: number; +} + +export type GameString = + | 'Quake Craft' + | 'Walls' + | 'Paintball' + | 'Blitz Survival Games' + | 'The TNT Games' + | 'VampireZ' + | 'Mega Walls' + | 'Arcade' + | 'Arena Brawl' + | 'UHC Champions' + | 'Cops and Crims' + | 'Warlords' + | 'Smash Heroes' + | 'Turbo Kart Racers' + | 'Housing' + | 'SkyWars' + | 'Crazy Walls' + | 'Speed UHC' + | 'SkyClash' + | 'Classic Games' + | 'Prototype' + | 'Bed Wars' + | 'Murder Mystery' + | 'Build Battle' + | 'Duels' + | 'SkyBlock' + | 'The Pit' + | 'Replay' + | 'SMP' + | 'Wool Wars' + | 'Limbo' + | 'Queue' + | 'Main Lobby' + | 'Tournament Lobby' + | 'Idle'; + +export type GameCode = + | 'QUAKECRAFT' + | 'WALLS' + | 'PAINTBALL' + | 'SURVIVAL_GAMES' + | 'TNTGAMES' + | 'VAMPIREZ' + | 'WALLS3' + | 'ARCADE' + | 'ARENA' + | 'UHC' + | 'MCGO' + | 'BATTLEGROUND' + | 'SUPER_SMASH' + | 'GINGERBREAD' + | 'HOUSING' + | 'SKYWARS' + | 'TRUE_COMBAT' + | 'SPEED_UHC' + | 'SKYCLASH' + | 'LEGACY' + | 'PROTOTYPE' + | 'BEDWARS' + | 'MURDER_MYSTERY' + | 'BUILD_BATTLE' + | 'DUELS' + | 'SKYBLOCK' + | 'PIT' + | 'REPLAY' + | 'SMP' + | 'WOOL_GAMES' + | 'LIMBO' + | 'QUEUE' + | 'MAIN_LOBBY' + | 'TOURNAMENT_LOBBY' + | 'IDLE'; + +export type GameID = + | 2 + | 3 + | 4 + | 5 + | 6 + | 7 + | 13 + | 14 + | 17 + | 20 + | 21 + | 23 + | 24 + | 25 + | 26 + | 51 + | 52 + | 54 + | 55 + | 56 + | 57 + | 58 + | 59 + | 60 + | 61 + | 63 + | 64 + | 65 + | 67 + | 68 + | -2 + | -3 + | -4 + | -5 + | -6; + +export interface LevelProgress { + xpToNext: number; + remainingXP: number; + currentXP: number; + percent: number; + percentRemaining: number; +} + +export interface PlayerSocialMedia { + name: string; + link: string; + id: string; +} + +export interface SkyblockSkillLevel { + xp: number; + level: number; + maxLevel: number; + xpCurrent: number; + xpForNext: number; + progress: number; + cosmetic: boolean; +} +export interface SkyblockMemberSkills { + combat: SkyblockSkillLevel; + farming: SkyblockSkillLevel; + fishing: SkyblockSkillLevel; + mining: SkyblockSkillLevel; + foraging: SkyblockSkillLevel; + enchanting: SkyblockSkillLevel; + alchemy: SkyblockSkillLevel; + carpentry: SkyblockSkillLevel; + runecrafting: SkyblockSkillLevel; + taming: SkyblockSkillLevel; + social: SkyblockSkillLevel; + average: number; +} + +export interface SkyblockMemberSlayerLevel { + xp: number; + tier1: number; + tier2: number; + tier3: number; + tier4: number; + tier5: number; + level: number; +} + +export interface SkyblockMemberSlayer { + zombie: SkyblockMemberSlayerLevel; + spider: SkyblockMemberSlayerLevel; + wolf: SkyblockMemberSlayerLevel; + enderman: SkyblockMemberSlayerLevel; + blaze: SkyblockMemberSlayerLevel; + vampire: SkyblockMemberSlayerLevel; +} + +export interface SkyblockMemberDungeonsTypes { + catacombs: SkyblockSkillLevel; +} + +export interface SkyblockMemberDungeonsClasses { + healer: SkyblockSkillLevel; + mage: SkyblockSkillLevel; + berserk: SkyblockSkillLevel; + archer: SkyblockSkillLevel; + tank: SkyblockSkillLevel; +} + +export interface SkyblockMemberDungeons { + types: SkyblockMemberDungeonsTypes; + classes: SkyblockMemberDungeonsClasses; +} + +export interface SkyblockMemberJacobDataMedals { + gold: number; + silver: number; + bronze: number; +} + +export interface SkyblockMemberJacobDataPerks { + doubleDrops: number; + farmingLevelCap: number; + personalBests: boolean; +} + +export interface SkyblockMemberJacobData { + medals: SkyblockMemberJacobDataMedals; + perks: SkyblockMemberJacobDataPerks; + contests: Record; +} + +export interface SkyblockMemberChocolateFactoryDataEmployees { + bro: number; + cousin: number; + sis: number; + father: number; + grandma: number; + dog: number; + uncle: number; +} + +export interface SkyblockMemberChocolateFactoryDataChocolate { + current: number; + total: number; + sincePrestige: number; +} + +export interface SkyblockMemberChocolateFactoryDataTimeTower { + charges: number; + level: number; +} + +export interface SkyblockMemberChocolateFactoryDataUpgrades { + click: number; + multiplier: number; + rabbitRarity: number; +} + +export interface SkyblockMemberChocolateFactoryDataGoldenClick { + amount: number; + year: number; +} + +export interface SkyblockMemberChocolateFactoryData { + employees: SkyblockMemberChocolateFactoryDataEmployees; + chocolate: SkyblockMemberChocolateFactoryDataChocolate; + timeTower: SkyblockMemberChocolateFactoryDataTimeTower; + upgrades: SkyblockMemberChocolateFactoryDataUpgrades; + goldenClick: SkyblockMemberChocolateFactoryDataGoldenClick; + barnCapacity: number; + prestige: number; +} + +export interface PlayerInfo { + max: number; + online: number; + players: any[]; + toString(): string; +} + +export type PlayerRank = 'VIP' | 'VIP+' | 'MVP' | 'MVP+' | 'MVP++' | 'Game Master' | 'Admin' | 'YouTube'; + +export interface RanksPurchaseTime { + VIP: Date | null; + VIP_PLUS: Date | null; + MVP: Date | null; + MVP_PLUS: Date | null; +} + +export interface PetConsumables { + BAKED_POTATO: number; + COOKIE: number; + FEATHER: number; + HAY_BLOCK: number; + SLIME_BALL: number; + COOKED_BEEF: number; + RED_ROSE: number; + WATER_BUCKET: number; + MELON: number; + STICK: number; + WOOD_SWORD: number; + MILK_BUCKET: number; + GOLD_RECORD: number; + LEASH: number; + LAVA_BUCKET: number; + BONE: number; + MAGMA_CREAM: number; + WHEAT: number; + MUSHROOM_SOUP: number; + BREAD: number; + PUMPKIN_PIE: number; + APPLE: number; + CARROT_ITEM: number; + RAW_FISH: number; + PORK: number; + CAKE: number; + ROTTEN_FLESH: number; +} + +export type BedWarsPrestige = + | 'Stone' + | 'Iron' + | 'Gold' + | 'Diamond' + | 'Emerald' + | 'Sapphire' + | 'Ruby' + | 'Crystal' + | 'Opal' + | 'Amethyst' + | 'Rainbow' + | 'Iron Prime' + | 'Gold Prime' + | 'Diamond Prime' + | 'Emerald Prime' + | 'Sapphire Prime' + | 'Ruby Prime' + | 'Crystal Prime' + | 'Opal Prime' + | 'Amethyst Prime' + | 'Mirror' + | 'Light' + | 'Dawn' + | 'Dusk' + | 'Air' + | 'Wind' + | 'Nebula' + | 'Thunder' + | 'Earth' + | 'Water' + | 'Fire' + | 'Sunrise' + | 'Eclipse' + | 'Gamma' + | 'Majestic' + | 'Andesine' + | 'Marine' + | 'Element' + | 'Galaxy' + | 'Atomic' + | 'Sunset' + | 'Time' + | 'Winter' + | 'Obsidian' + | 'Spring' + | 'Ice' + | 'Summer' + | 'Spinel' + | 'Autumn' + | 'Mystic' + | 'Eternal'; + +export interface BedWarsCollectedItems { + iron: number; + gold: number; + diamond: number; + emerald: number; +} + +export interface BedWarsAvg { + kills: number; + finalKills: number; + bedsBroken: number; +} + +export interface BedWarsBeds { + lost: number; + broken: number; + BLRatio: number; +} + +export interface BedWarsModeStats { + winstreak: number; + playedGames: number; + kills: number; + deaths: number; + wins: number; + losses: number; + finalKills: number; + finalDeaths: number; + beds: BedWarsBeds; + avg: BedWarsAvg; + KDRatio: number; + WLRatio: number; + finalKDRatio: number; +} +export interface BedwarsDreamModeStats { + doubles: BedWarsModeStats; + fours: BedWarsModeStats; +} + +export interface BedwarsDreamStats { + ultimate: BedwarsDreamModeStats; + rush: BedwarsDreamModeStats; + armed: BedwarsDreamModeStats; + lucky: BedwarsDreamModeStats; + voidless: BedwarsDreamModeStats; +} + +export interface BedWarsPracticeAttempts { + failed: number; + successful: number; + total: number; +} + +export interface BedWarsPracticeElevation { + straight: number; + diagonal: number; +} + +export interface BedWarsPracticeElevations { + none: BedWarsPracticeElevation; + slight: BedWarsPracticeElevation; + staircase: BedWarsPracticeElevation; +} + +export interface BedWarsPracticeRecord { + elevation: BedWarsPracticeElevations; +} + +export interface BedWarsPracticeRecords { + blocks30: BedWarsPracticeRecord; + blocks50: BedWarsPracticeRecord; + blocks100: BedWarsPracticeRecord; +} + +export interface BedWarsPracticeBridging { + blocksPlaced: number; + attempts: BedWarsPracticeAttempts; + records: BedWarsPracticeRecords; +} + +export interface BedWarsPracticePearlClutching { + attempts: BedWarsPracticeAttempts; +} + +export interface BedWarsPracticeBase { + blocksPlaced: number; + attempts: BedWarsPracticeAttempts; +} + +export interface BedWarsPracticeStats { + selected: string; + bridging: BedWarsPracticeBridging; + fireballJumping: BedWarsPracticeBase; + pearlClutching: BedWarsPracticePearlClutching; + mlg: BedWarsPracticeBase; +} + +export interface BuildBattleWins { + solo: number; + teams: number; + pro: number; + gtb: number; +} + +export interface PitArmor { + helmet: PitInventoryItem | null; + chestplate: PitInventoryItem | null; + leggings: PitInventoryItem | null; + boots: PitInventoryItem | null; +} + +export type SkyWarsPrestige = + | 'Iron' + | 'Gold' + | 'Diamond' + | 'Emerald' + | 'Sapphire' + | 'Ruby' + | 'Crystal' + | 'Opal' + | 'Amethyst' + | 'Rainbow' + | 'Mythic'; + +export type SkyWarsPrestigeIcons = + | '⋆' + | '★' + | '☆' + | '⁕' + | '✶' + | '✳' + | '✴' + | '✷' + | '❋' + | '✼' + | '❂' + | '❁' + | '☬' + | '✙' + | '❤️' + | '☠' + | '✦' + | '✌' + | '❦' + | '✵' + | '❣' + | '☯' + | '✺' + | 'ಠ_ಠ' + | '⚔'; + +export interface WoolWarsStats { + wins: number; + gamesPlayed: number; + woolsPlaced: number; + blocksBroken: number; + placeBreakRatio: number; + kills: number; + deaths: number; + KDRatio: number; + assists: number; + powerups: number; +} + +export interface WoolWarsPrivateGamesConfig { + one_hit_one_kil: boolean; + rainbow_wool: 'Enabled' | 'Disabled'; + health_buff: string; + game_speed: string; + speed: string; + no_class: 'Enabled' | 'Disabled'; + respawn_enable: boolean; +} + +export interface QuestObjective { + id: string; + type: 'Integer' | 'Boolean'; + amountNeeded: number; +} + +export interface QuestReward { + type: string; + amount: number; +} + +export interface ChallengeData { + id: string; + name: string; + reward: number; + rewardType: string; +} + +export type StaticGameNames = + | 'arcade' + | 'arena' + | 'bedwars' + | 'hungergames' + | 'buildbattle' + | 'truecombat' + | 'duels' + | 'mcgo' + | 'murdermystery' + | 'paintball' + | 'quake' + | 'skyclash' + | 'skywars' + | 'supersmash' + | 'speeduhc' + | 'gingerbread' + | 'tntgames' + | 'uhc' + | 'vampirez' + | 'walls3' + | 'walls' + | 'battleground' + | 'woolgames'; + +export type SkyblockGemstoneQuality = 'Rough' | 'Flawed' | 'Fine' | 'Flawless' | 'Perfect'; + +export type SkyblockRarity = + | 'COMMON' + | 'UNCOMMON' + | 'RARE' + | 'EPIC' + | 'LEGENDARY' + | 'MYTHIC' + | 'DIVINE' + | 'SPECIAL' + | 'VERY_SPECIAL'; + +export interface SkyblockGardenVisitorServed { + total: number; + unique: number; +} +export interface SkyblockGardenVisitor { + visited: Record; + completed: Record; + served: SkyblockGardenVisitorServed; +} + +export interface SkyblockGardenComposterUpgrades { + speed: number; + multiDrop: number; + fuelCap: number; + organicMatterCap: number; + costReduction: number; +} + +export interface SkyblockGardenComposter { + organicMatter: number; + fuelUnits: number; + compostUnits: number; + compostItems: number; + conversionTicks: number; + upgrades: SkyblockGardenComposterUpgrades; +} + +export interface SkyblockGarenCrops { + wheat: number; + carrot: number; + sugarCane: number; + potato: number; + pumpkin: number; + melon: number; + cactus: number; + cocoBeans: number; + mushroom: number; + netherWart: number; +} + +export interface SkyblockGardenCropMilestones { + wheat: SkyblockSkillLevel; + carrot: SkyblockSkillLevel; + sugarCane: SkyblockSkillLevel; + potato: SkyblockSkillLevel; + pumpkin: SkyblockSkillLevel; + melon: SkyblockSkillLevel; + cactus: SkyblockSkillLevel; + cocoBeans: SkyblockSkillLevel; + mushroom: SkyblockSkillLevel; + netherWart: SkyblockSkillLevel; +} + +export type SkyblockMemberTrophyFishRank = 'Bronze' | 'Silver' | 'Gold' | 'Diamond'; + +export interface SkyblockMemberEquipment { + gauntlet: SkyblockInventoryItem | null; + belt: SkyblockInventoryItem | null; + cloak: SkyblockInventoryItem | null; + necklace: SkyblockInventoryItem | null; +} + +export interface SkyblockMemberArmor { + helmet: SkyblockInventoryItem | null; + chestplate: SkyblockInventoryItem | null; + leggings: SkyblockInventoryItem | null; + boots: SkyblockInventoryItem | null; +} + +export interface SkyblockMemberStats { + kills: Record; + deaths: Record; +} + +export interface PlayerBingoDataPerEvent { + eventId: number; + points: number; + goalsCompleted: Bingo[] | string[]; +} + +export interface ProductStatus { + sellPrice: number; + buyPrice: number; + sellVolume: number; + buyVolume: number; + sellMovingWeek: number; + buyMovingWeek: number; + sellOrders: number; + buyOrders: number; +} + +export interface PlayerStats { + arcade: Arcade; + arena: ArenaBrawl; + bedwars: BedWars; + blitzsg: BlitzSurvivalGames; + buildbattle: BuildBattle; + copsandcrims: CopsAndCrims; + duels: Duels; + megawalls: MegaWalls; + murdermystery: MurderMystery; + paintball: Paintball; + pit: Pit; + quakecraft: Quakecraft; + skywars: SkyWars; + smashheroes: SmashHeroes; + speeduhc: SpeedUHC; + tntgames: TNTGames; + turbokartracers: TurboKartRacers; + uhc: UHC; + vampirez: VampireZ; + walls: Walls; + warlords: Warlords; + woolwars: WoolWars; +} + +export interface RLOptions { + keyLimit: number; + ratelimit: RatelimitOptions; + syncWithHeaders: boolean; +} diff --git a/src/utils/Constants.ts b/src/utils/Constants.ts new file mode 100644 index 00000000..aa658a40 --- /dev/null +++ b/src/utils/Constants.ts @@ -0,0 +1,3131 @@ +export default { + duelsDivisions: [ + { name: 'Rookie', key: 'rookie' }, + { name: 'Iron', key: 'iron' }, + { name: 'Gold', key: 'gold' }, + { name: 'Diamond', key: 'diamond' }, + { name: 'Master', key: 'master' }, + { name: 'Legend', key: 'legend' }, + { name: 'Grandmaster', key: 'grandmaster' }, + { name: 'Godlike', key: 'godlike' }, + { name: 'Celestial', key: 'celestial' }, + { name: 'Divine', key: 'divine' }, + { name: 'Ascended', key: 'ascended' } + ], + levelingXp: { + 1: 50, + 2: 125, + 3: 200, + 4: 300, + 5: 500, + 6: 750, + 7: 1000, + 8: 1500, + 9: 2000, + 10: 3500, + 11: 5000, + 12: 7500, + 13: 10000, + 14: 15000, + 15: 20000, + 16: 30000, + 17: 50000, + 18: 75000, + 19: 100000, + 20: 200000, + 21: 300000, + 22: 400000, + 23: 500000, + 24: 600000, + 25: 700000, + 26: 800000, + 27: 900000, + 28: 1000000, + 29: 1100000, + 30: 1200000, + 31: 1300000, + 32: 1400000, + 33: 1500000, + 34: 1600000, + 35: 1700000, + 36: 1800000, + 37: 1900000, + 38: 2000000, + 39: 2100000, + 40: 2200000, + 41: 2300000, + 42: 2400000, + 43: 2500000, + 44: 2600000, + 45: 2750000, + 46: 2900000, + 47: 3100000, + 48: 3400000, + 49: 3700000, + 50: 4000000, + 51: 4300000, + 52: 4600000, + 53: 4900000, + 54: 5200000, + 55: 5500000, + 56: 5800000, + 57: 6100000, + 58: 6400000, + 59: 6700000, + 60: 7000000 + }, + runecraftingXp: { + 1: 50, + 2: 100, + 3: 125, + 4: 160, + 5: 200, + 6: 250, + 7: 315, + 8: 400, + 9: 500, + 10: 625, + 11: 785, + 12: 1000, + 13: 1250, + 14: 1600, + 15: 2000, + 16: 2465, + 17: 3125, + 18: 4000, + 19: 5000, + 20: 6200, + 21: 7800, + 22: 9800, + 23: 12200, + 24: 15300, + 25: 19050 + }, + skillsCap: { + taming: 50, + farming: 60, + mining: 60, + combat: 60, + foraging: 50, + fishing: 50, + enchanting: 60, + alchemy: 50, + carpentry: 50, + runecrafting: 25, + dungeons: 50, + social: 25, + hotm: 10, + garden: 15 + }, + dungeonXp: { + 1: 50, + 2: 75, + 3: 110, + 4: 160, + 5: 230, + 6: 330, + 7: 470, + 8: 670, + 9: 950, + 10: 1340, + 11: 1890, + 12: 2665, + 13: 3760, + 14: 5260, + 15: 7380, + 16: 10300, + 17: 14400, + 18: 20000, + 19: 27600, + 20: 38000, + 21: 52500, + 22: 71500, + 23: 97000, + 24: 132000, + 25: 180000, + 26: 243000, + 27: 328000, + 28: 445000, + 29: 600000, + 30: 800000, + 31: 1065000, + 32: 1410000, + 33: 1900000, + 34: 2500000, + 35: 3300000, + 36: 4300000, + 37: 5600000, + 38: 7200000, + 39: 9200000, + 40: 1.2e7, + 41: 1.5e7, + 42: 1.9e7, + 43: 2.4e7, + 44: 3e7, + 45: 3.8e7, + 46: 4.8e7, + 47: 6e7, + 48: 7.5e7, + 49: 9.3e7, + 50: 1.1625e8, + 55: 1e9 + }, + hotmXp: { + 1: 0, + 2: 3000, + 3: 9000, + 4: 25000, + 5: 60000, + 6: 100000, + 7: 150000, + 8: 210000, + 9: 290000, + 10: 400000 + }, + socialXp: { + 1: 50, + 2: 100, + 3: 150, + 4: 250, + 5: 500, + 6: 750, + 7: 1000, + 8: 1250, + 9: 1500, + 10: 2000, + 11: 2500, + 12: 3000, + 13: 3750, + 14: 4500, + 15: 6000, + 16: 8000, + 17: 10000, + 18: 12500, + 19: 15000, + 20: 20000, + 21: 25000, + 22: 30000, + 23: 35000, + 24: 40000, + 25: 50000 + }, + garden: { + 1: 0, + 2: 70, + 3: 70, + 4: 140, + 5: 240, + 6: 600, + 7: 1500, + 8: 2000, + 9: 2500, + 10: 3000, + 11: 10000, + 12: 10000, + 13: 10000, + 14: 10000, + 15: 10000 + }, + wheat: { + 1: 30, + 2: 50, + 3: 80, + 4: 170, + 5: 330, + 6: 670, + 7: 1330, + 8: 2500, + 9: 3500, + 10: 5000, + 11: 6500, + 12: 8000, + 13: 10000, + 14: 20000, + 15: 35000, + 16: 50000, + 17: 75000, + 18: 100000, + 19: 175000, + 20: 250000, + 21: 350000, + 22: 500000, + 23: 750000, + 24: 1000000, + 25: 1300000, + 26: 1600000, + 27: 2000000, + 28: 2300000, + 29: 2600000, + 30: 3000000, + 31: 3000000, + 32: 3000000, + 33: 3000000, + 34: 3000000, + 35: 3000000, + 36: 3000000, + 37: 3000000, + 38: 3000000, + 39: 3000000, + 40: 3000000, + 41: 3000000, + 42: 3000000, + 43: 3000000, + 44: 3000000, + 45: 3000000, + 46: 3000000 + }, + carrot: { + 1: 100, + 2: 150, + 3: 250, + 4: 500, + 5: 1500, + 6: 2500, + 7: 5000, + 8: 7500, + 9: 10000, + 10: 15000, + 11: 20000, + 12: 25000, + 13: 40000, + 14: 70000, + 15: 100000, + 16: 200000, + 17: 250000, + 18: 250000, + 19: 500000, + 20: 750000, + 21: 1000000, + 22: 1500000, + 23: 2000000, + 24: 3000000, + 25: 4000000, + 26: 5000000, + 27: 6000000, + 28: 7000000, + 29: 8000000, + 30: 9000000, + 31: 10000000, + 32: 10000000, + 33: 10000000, + 34: 10000000, + 35: 10000000, + 36: 10000000, + 37: 10000000, + 38: 10000000, + 39: 10000000, + 40: 10000000, + 41: 10000000, + 42: 10000000, + 43: 10000000, + 44: 10000000, + 45: 10000000, + 46: 10000000 + }, + potato: { + 1: 100, + 2: 150, + 3: 250, + 4: 500, + 5: 1500, + 6: 2500, + 7: 5000, + 8: 7500, + 9: 10000, + 10: 15000, + 11: 20000, + 12: 25000, + 13: 40000, + 14: 70000, + 15: 100000, + 16: 200000, + 17: 250000, + 18: 250000, + 19: 500000, + 20: 750000, + 21: 1000000, + 22: 1500000, + 23: 2000000, + 24: 3000000, + 25: 4000000, + 26: 5000000, + 27: 6000000, + 28: 7000000, + 29: 8000000, + 30: 9000000, + 31: 10000000, + 32: 10000000, + 33: 10000000, + 34: 10000000, + 35: 10000000, + 36: 10000000, + 37: 10000000, + 38: 10000000, + 39: 10000000, + 40: 10000000, + 41: 10000000, + 42: 10000000, + 43: 10000000, + 44: 10000000, + 45: 10000000, + 46: 10000000 + }, + melon: { + 1: 150, + 2: 250, + 3: 400, + 4: 850, + 5: 1650, + 6: 3350, + 7: 6650, + 8: 12500, + 9: 17500, + 10: 25000, + 11: 32500, + 12: 40000, + 13: 50000, + 14: 100000, + 15: 175000, + 16: 250000, + 17: 375000, + 18: 500000, + 19: 875000, + 20: 1250000, + 21: 1750000, + 22: 2500000, + 23: 3750000, + 24: 5000000, + 25: 6500000, + 26: 8000000, + 27: 10000000, + 28: 11500000, + 29: 13000000, + 30: 15000000, + 31: 15000000, + 32: 15000000, + 33: 15000000, + 34: 15000000, + 35: 15000000, + 36: 15000000, + 37: 15000000, + 38: 15000000, + 39: 15000000, + 40: 15000000, + 41: 15000000, + 42: 15000000, + 43: 15000000, + 44: 15000000, + 45: 15000000, + 46: 15000000 + }, + pumpkin: { + 1: 30, + 2: 50, + 3: 80, + 4: 170, + 5: 330, + 6: 670, + 7: 1330, + 8: 2500, + 9: 3500, + 10: 5000, + 11: 6500, + 12: 8000, + 13: 10000, + 14: 20000, + 15: 35000, + 16: 50000, + 17: 75000, + 18: 100000, + 19: 175000, + 20: 250000, + 21: 350000, + 22: 500000, + 23: 750000, + 24: 1000000, + 25: 1300000, + 26: 1600000, + 27: 2000000, + 28: 2300000, + 29: 2600000, + 30: 3000000, + 31: 3000000, + 32: 3000000, + 33: 3000000, + 34: 3000000, + 35: 3000000, + 36: 3000000, + 37: 3000000, + 38: 3000000, + 39: 3000000, + 40: 3000000, + 41: 3000000, + 42: 3000000, + 43: 3000000, + 44: 3000000, + 45: 3000000, + 46: 3000000 + }, + sugarCane: { + 1: 60, + 2: 100, + 3: 160, + 4: 340, + 5: 660, + 6: 1340, + 7: 2660, + 8: 5000, + 9: 7000, + 10: 10000, + 11: 13000, + 12: 16000, + 13: 20000, + 14: 40000, + 15: 70000, + 16: 100000, + 17: 150000, + 18: 200000, + 19: 350000, + 20: 500000, + 21: 700000, + 22: 1000000, + 23: 1500000, + 24: 2000000, + 25: 2600000, + 26: 3200000, + 27: 4000000, + 28: 4600000, + 29: 5200000, + 30: 6000000, + 31: 6000000, + 32: 6000000, + 33: 6000000, + 34: 6000000, + 35: 6000000, + 36: 6000000, + 37: 6000000, + 38: 6000000, + 39: 6000000, + 40: 6000000, + 41: 6000000, + 42: 6000000, + 43: 6000000, + 44: 6000000, + 45: 6000000, + 46: 6000000 + }, + cocoaBeans: { + 1: 90, + 2: 150, + 3: 250, + 4: 500, + 5: 1000, + 6: 2000, + 7: 4000, + 8: 7500, + 9: 10000, + 10: 15000, + 11: 20000, + 12: 25000, + 13: 30000, + 14: 50000, + 15: 100000, + 16: 150000, + 17: 200000, + 18: 300000, + 19: 500000, + 20: 750000, + 21: 1000000, + 22: 1500000, + 23: 2000000, + 24: 3000000, + 25: 4000000, + 26: 5000000, + 27: 6000000, + 28: 7000000, + 29: 8000000, + 30: 9000000, + 31: 9000000, + 32: 9000000, + 33: 9000000, + 34: 9000000, + 35: 9000000, + 36: 9000000, + 37: 9000000, + 38: 9000000, + 39: 9000000, + 40: 9000000, + 41: 9000000, + 42: 9000000, + 43: 9000000, + 44: 9000000, + 45: 9000000, + 46: 9000000 + }, + cactus: { + 1: 60, + 2: 100, + 3: 160, + 4: 340, + 5: 660, + 6: 1340, + 7: 2660, + 8: 5000, + 9: 7000, + 10: 10000, + 11: 13000, + 12: 16000, + 13: 20000, + 14: 40000, + 15: 70000, + 16: 100000, + 17: 150000, + 18: 200000, + 19: 350000, + 20: 500000, + 21: 700000, + 22: 1000000, + 23: 1500000, + 24: 2000000, + 25: 2600000, + 26: 3200000, + 27: 4000000, + 28: 4600000, + 29: 5200000, + 30: 6000000, + 31: 6000000, + 32: 6000000, + 33: 6000000, + 34: 6000000, + 35: 6000000, + 36: 6000000, + 37: 6000000, + 38: 6000000, + 39: 6000000, + 40: 6000000, + 41: 6000000, + 42: 6000000, + 43: 6000000, + 44: 6000000, + 45: 6000000, + 46: 6000000 + }, + mushroom: { + 1: 30, + 2: 50, + 3: 80, + 4: 170, + 5: 330, + 6: 670, + 7: 1330, + 8: 2500, + 9: 3500, + 10: 5000, + 11: 6500, + 12: 8000, + 13: 10000, + 14: 20000, + 15: 35000, + 16: 50000, + 17: 75000, + 18: 100000, + 19: 175000, + 20: 250000, + 21: 350000, + 22: 500000, + 23: 750000, + 24: 1000000, + 25: 1300000, + 26: 1600000, + 27: 2000000, + 28: 2300000, + 29: 2600000, + 30: 3000000, + 31: 3000000, + 32: 3000000, + 33: 3000000, + 34: 3000000, + 35: 3000000, + 36: 3000000, + 37: 3000000, + 38: 3000000, + 39: 3000000, + 40: 3000000, + 41: 3000000, + 42: 3000000, + 43: 3000000, + 44: 3000000, + 45: 3000000, + 46: 3000000 + }, + netherWart: { + 1: 90, + 2: 150, + 3: 250, + 4: 500, + 5: 1000, + 6: 2000, + 7: 4000, + 8: 7500, + 9: 10000, + 10: 15000, + 11: 20000, + 12: 25000, + 13: 30000, + 14: 50000, + 15: 100000, + 16: 150000, + 17: 200000, + 18: 300000, + 19: 500000, + 20: 750000, + 21: 1000000, + 22: 1500000, + 23: 2000000, + 24: 3000000, + 25: 4000000, + 26: 5000000, + 27: 6000000, + 28: 7000000, + 29: 8000000, + 30: 9000000, + 31: 9000000, + 32: 9000000, + 33: 9000000, + 34: 9000000, + 35: 9000000, + 36: 9000000, + 37: 9000000, + 38: 9000000, + 39: 9000000, + 40: 9000000, + 41: 9000000, + 42: 9000000, + 43: 9000000, + 44: 9000000, + 45: 9000000, + 46: 9000000 + }, + petScore: { + COMMON: 1, + UNCOMMON: 2, + RARE: 3, + EPIC: 4, + LEGENDARY: 5, + MYTHIC: 6, + VERY_SPECIAL: 6 + }, + petRarityOffset: { + COMMON: 0, + UNCOMMON: 6, + RARE: 11, + EPIC: 16, + LEGENDARY: 20, + MYTHIC: 20 + }, + petLevels: [ + 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, 0, 5555, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, + 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, + 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, + 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, + 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, + 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, + 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, + 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700, 1886700 + ], + leaderboardNames: { + ARENA: 'ARENA', + COPS_AND_CRIMS: 'MCGO', + WARLORDS: 'BATTLEGROUND', + BLITZ_SURVIVAL_GAMES: 'SURVIVAL_GAMES', + UHC: 'UHC', + WALLS: 'WALLS', + PROTOTYPE: 'PROTOTYPE', + PAINTBALL: 'PAINTBALL', + SKYWARS: 'SKYWARS', + MURDER_MYSTERY: 'MURDER_MYSTERY', + SMASH_HEROES: 'SUPER_SMASH', + DUELS: 'DUELS', + SPEED_UHC: 'SPEED_UHC', + TNTGAMES: 'TNTGAMES', + BEDWARS: 'BEDWARS', + TURBO_KART_RACERS: 'GINGERBREAD', + BUILD_BATTLE: 'BUILD_BATTLE', + ARCADE: 'ARCADE', + SKYCLASH: 'SKYCLASH', + QUAKECRAFT: 'QUAKECRAFT', + CRAZY_WALLS: 'TRUE_COMBAT', + MEGA_WALLS: 'WALLS3', + VAMPIREZ: 'VAMPIREZ' + }, + MiniGamesString: { + QUAKECRAFT: 'Quakecraft', + WALLS: 'Walls', + PAINTBALL: 'Paintball', + SURVIVAL_GAMES: 'Blitz Survival Games', + TNTGAMES: 'The TNT Games', + VAMPIREZ: 'VampireZ', + WALLS3: 'Mega Walls', + ARCADE: 'Arcade', + ARENA: 'Arena Brawl', + MCGO: 'Cops and Crims', + UHC: 'UHC Champions', + BATTLEGROUND: 'Warlords', + SUPER_SMASH: 'Smash Heroes', + GINGERBREAD: 'Turbo Kart Racers', + HOUSING: 'Housing', + SKYWARS: 'SkyWars', + TRUE_COMBAT: 'Crazy Walls', + SPEED_UHC: 'Speed UHC', + SKYCLASH: 'SkyClash', + LEGACY: 'Classic Games', + PROTOTYPE: 'Prototype', + BEDWARS: 'BedWars', + MURDER_MYSTERY: 'Murder Mystery', + BUILD_BATTLE: 'Build Battle', + DUELS: 'Duels', + PIT: 'The Pit', + SKYBLOCK: 'SkyBlock', + REPLAY: 'Replay', + LIMBO: 'Limbo', + IDLE: 'Idle', + QUEUE: 'Queue', + MAIN_LOBBY: 'Main Lobby', + TOURNAMENT_LOBBY: 'Tournament Lobby', + WOOL_GAMES: 'Wool Wars' + }, + SkyWarsPrestigeIcons: { + /* eslint-disable camelcase */ + default: '⋆', + angel_1: '★', + angel_2: '☆', + angel_3: '⁕', + angel_4: '✶', + angel_5: '✳', + angel_6: '✴', + angel_7: '✷', + angel_8: '❋', + angel_9: '✼', + angel_10: '❂', + angel_11: '❁', + angel_12: '☬', + iron_prestige: '✙', + gold_prestige: '❤️', + diamond_prestige: '☠', + emerald_prestige: '✦', + sapphire_prestige: '✌', + ruby_prestige: '❦', + crystal_prestige: '✵', + opal_prestige: '❣', + amethyst_prestige: '☯', + rainbow_prestige: '✺', + mythic_prestige: 'ಠ_ಠ', + favor_of_the_angel_prestige: '⚔' + }, + games: [ + { name: 'Quake Craft', code: 'QUAKECRAFT', id: 2 }, + { name: 'Walls', code: 'WALLS', id: 3 }, + { name: 'Paintball', code: 'PAINTBALL', id: 4 }, + { name: 'Blitz Survival Games', code: 'SURVIVAL_GAMES', id: 5 }, + { name: 'The TNT Games', code: 'TNTGAMES', id: 6 }, + { name: 'VampireZ', code: 'VAMPIREZ', id: 7 }, + { name: 'Mega Walls', code: 'WALLS3', id: 13 }, + { name: 'Arcade', code: 'ARCADE', id: 14 }, + { name: 'Arena Brawl', code: 'ARENA', id: 17 }, + { name: 'UHC Champions', code: 'UHC', id: 20 }, + { name: 'Cops and Crims', code: 'MCGO', id: 21 }, + { name: 'Warlords', code: 'BATTLEGROUND', id: 23 }, + { name: 'Smash Heroes', code: 'SUPER_SMASH', id: 24 }, + { name: 'Turbo Kart Racers', code: 'GINGERBREAD', id: 25 }, + { name: 'Housing', code: 'HOUSING', id: 26 }, + { name: 'SkyWars', code: 'SKYWARS', id: 51 }, + { name: 'Crazy Walls', code: 'TRUE_COMBAT', id: 52 }, + { name: 'Speed UHC', code: 'SPEED_UHC', id: 54 }, + { name: 'SkyClash', code: 'SKYCLASH', id: 55 }, + { name: 'Classic Games', code: 'LEGACY', id: 56 }, + { name: 'Prototype', code: 'PROTOTYPE', id: 57 }, + { name: 'Bed Wars', code: 'BEDWARS', id: 58 }, + { name: 'Murder Mystery', code: 'MURDER_MYSTERY', id: 59 }, + { name: 'Build Battle', code: 'BUILD_BATTLE', id: 60 }, + { name: 'Duels', code: 'DUELS', id: 61 }, + { name: 'SkyBlock', code: 'SKYBLOCK', id: 63 }, + { name: 'The Pit', code: 'PIT', id: 64 }, + { name: 'Replay', code: 'REPLAY', id: 65 }, + { name: 'SMP', code: 'SMP', id: 67 }, + { name: 'Wool Wars', code: 'WOOL_GAMES', id: 68 }, + { name: 'Limbo', code: 'LIMBO', id: -2 }, + { name: 'Queue', code: 'QUEUE', id: -3 }, + { name: 'Main Lobby', code: 'MAIN_LOBBY', id: -4 }, + { name: 'Tournament Lobby', code: 'TOURNAMENT_LOBBY', id: -5 }, + { name: 'Idle', code: 'IDLE', id: -6 } + ], + + /* eslint-disable camelcase */ + bestiary: { + dynamic: { + name: 'Private Island', + + mobs: [ + { + name: 'Creeper', + cap: 200, + + mobs: ['creeper_1'], + bracket: 1 + }, + { + name: 'Enderman', + cap: 200, + + mobs: [ + 'enderman_1', + 'enderman_2', + 'enderman_3', + 'enderman_4', + 'enderman_5', + 'enderman_6', + 'enderman_7', + 'enderman_8', + 'enderman_9', + 'enderman_10', + 'enderman_11', + 'enderman_12', + 'enderman_13', + 'enderman_14', + 'enderman_15' + ], + bracket: 1 + }, + { + name: 'Skeleton', + cap: 200, + + mobs: [ + 'skeleton_1', + 'skeleton_2', + 'skeleton_3', + 'skeleton_4', + 'skeleton_5', + 'skeleton_6', + 'skeleton_7', + 'skeleton_8', + 'skeleton_9', + 'skeleton_10', + 'skeleton_11', + 'skeleton_12', + 'skeleton_13', + 'skeleton_14', + 'skeleton_15' + ], + bracket: 1 + }, + { + name: 'Slime', + cap: 200, + + mobs: [ + 'slime_1', + 'slime_2', + 'slime_3', + 'slime_4', + 'slime_5', + 'slime_6', + 'slime_7', + 'slime_8', + 'slime_9', + 'slime_10', + 'slime_11', + 'slime_12', + 'slime_13', + 'slime_14', + 'slime_15' + ], + bracket: 1 + }, + { + name: 'Spider', + cap: 200, + + mobs: [ + 'spider_1', + 'spider_2', + 'spider_3', + 'spider_4', + 'spider_5', + 'spider_6', + 'spider_7', + 'spider_8', + 'spider_9', + 'spider_10', + 'spider_11', + 'spider_12', + 'spider_13', + 'spider_14', + 'spider_15' + ], + bracket: 1 + }, + { + name: 'Witch', + cap: 200, + + mobs: [ + 'witch_1', + 'witch_2', + 'witch_3', + 'witch_4', + 'witch_5', + 'witch_6', + 'witch_7', + 'witch_8', + 'witch_9', + 'witch_10', + 'witch_11', + 'witch_12', + 'witch_13', + 'witch_14', + 'witch_15' + ], + bracket: 1 + }, + { + name: 'Zombie', + cap: 200, + + mobs: [ + 'zombie_1', + 'zombie_2', + 'zombie_3', + 'zombie_4', + 'zombie_5', + 'zombie_6', + 'zombie_7', + 'zombie_8', + 'zombie_9', + 'zombie_10', + 'zombie_11', + 'zombie_12', + 'zombie_13', + 'zombie_14', + 'zombie_15' + ], + bracket: 1 + } + ] + }, + hub: { + name: 'Hub', + + mobs: [ + { + name: 'Crypt Ghoul', + cap: 40000, + + mobs: ['unburried_zombie_30'], + bracket: 1 + }, + { + name: 'Golden Ghoul', + cap: 4000, + + mobs: ['unburried_zombie_60'], + bracket: 3 + }, + { + name: 'Graveyard Zombie', + cap: 200, + + mobs: ['graveyard_zombie_1'], + bracket: 1 + }, + { + name: 'Old Wolf', + cap: 4000, + + mobs: ['old_wolf_50'], + bracket: 3 + }, + { + name: 'Wolf', + cap: 40000, + + mobs: ['ruin_wolf_15'], + bracket: 1 + }, + { + name: 'Zombie Villager', + cap: 1000, + + mobs: ['zombie_villager_1'], + bracket: 4 + } + ] + }, + farming_1: { + name: 'The Farming Islands', + + mobs: [ + { + name: 'Chicken', + cap: 200, + + mobs: ['farming_chicken_1'], + bracket: 1 + }, + { + name: 'Cow', + cap: 200, + + mobs: ['farming_cow_1'], + bracket: 1 + }, + { + name: 'Mushroom Cow', + cap: 200, + + mobs: ['mushroom_cow_1'], + bracket: 1 + }, + { + name: 'Pig', + cap: 200, + + mobs: ['farming_pig_1'], + bracket: 1 + }, + { + name: 'Rabbit', + cap: 200, + + mobs: ['farming_rabbit_1'], + bracket: 1 + }, + { + name: 'Sheep', + cap: 200, + + mobs: ['farming_sheep_1'], + bracket: 1 + } + ] + }, + combat_1: { + name: 'Spiders Den', + + mobs: [ + { + name: 'Arachne', + cap: 500, + + mobs: ['arachne_500', 'arachne_300'], + bracket: 7 + }, + { + name: "Arachne's Brood", + cap: 1000, + + mobs: ['arachne_brood_200', 'arachne_brood_100'], + bracket: 4 + }, + { + name: "Arachne's Keeper", + cap: 400, + + mobs: ['arachne_keeper_100'], + bracket: 5 + }, + { + name: 'Brood Mother', + cap: 400, + + mobs: ['brood_mother_spider_12'], + bracket: 5 + }, + { + name: 'Dasher Spider', + cap: 10000, + + mobs: ['dasher_spider_50', 'dasher_spider_45', 'dasher_spider_42', 'dasher_spider_4', 'dasher_spider_6'], + bracket: 2 + }, + { + name: 'Gravel Skeleton', + cap: 4000, + + mobs: ['respawning_skeleton_2'], + bracket: 3 + }, + { + name: 'Rain Slime', + cap: 1000, + + mobs: ['random_slime_8', 'random_slime_20'], + bracket: 4 + }, + { + name: 'Silverfish', + cap: 40000, + + mobs: [ + 'jockey_shot_silverfish_3', + 'splitter_spider_silverfish_2', + 'splitter_spider_silverfish_45', + 'splitter_spider_silverfish_42', + 'splitter_spider_silverfish_50', + 'jockey_shot_silverfish_42' + ], + bracket: 1 + }, + { + name: 'Spider Jockey', + cap: 10000, + + mobs: ['spider_jockey_3', 'spider_jockey_42', 'spider_jockey_5'], + bracket: 2 + }, + { + name: 'Splitter Spider', + cap: 10000, + + mobs: [ + 'splitter_spider_2', + 'splitter_spider_45', + 'splitter_spider_42', + 'splitter_spider_50', + 'splitter_spider_4', + 'splitter_spider_6' + ], + bracket: 2 + }, + { + name: 'Voracious Spider', + cap: 40000, + + mobs: ['voracious_spider_50', 'voracious_spider_42', 'voracious_spider_45', 'voracious_spider_10'], + bracket: 1 + }, + { + name: 'Weaver Spider', + cap: 10000, + + mobs: [ + 'weaver_spider_3', + 'weaver_spider_4', + 'weaver_spider_5', + 'weaver_spider_6', + 'weaver_spider_42', + 'weaver_spider_45', + 'weaver_spider_50' + ], + bracket: 2 + } + ] + }, + combat_3: { + name: 'The End', + + mobs: [ + { + name: 'Dragon', + cap: 1000, + + mobs: [ + 'protector_dragon_100', + 'old_dragon_100', + 'young_dragon_100', + 'wise_dragon_100', + 'superior_dragon_100', + 'strong_dragon_100', + 'unstable_dragon_100' + ], + bracket: 5 + }, + { + name: 'Enderman', + cap: 25000, + + mobs: ['enderman_50', 'enderman_45', 'enderman_42'], + bracket: 4 + }, + { + name: 'Endermite', + cap: 10000, + + mobs: ['nest_endermite_50', 'endermite_37', 'endermite_40'], + bracket: 5 + }, + { + name: 'Endstone Protector', + cap: 500, + + mobs: ['corrupted_protector_100'], + bracket: 7 + }, + { + name: 'Obsidian Defender', + cap: 25000, + + mobs: ['obsidian_wither_55'], + bracket: 4 + }, + { + name: 'Voidling Extremist', + cap: 4000, + + mobs: ['voidling_extremist_100'], + bracket: 3 + }, + { + name: 'Voidling Fanatic', + cap: 25000, + + mobs: ['voidling_fanatic_85'], + bracket: 4 + }, + { + name: 'Watcher', + cap: 25000, + + mobs: ['watcher_55'], + bracket: 4 + }, + { + name: 'Zealot', + cap: 100000, + + mobs: ['zealot_bruiser_100', 'zealot_enderman_55'], + bracket: 3 + } + ] + }, + crimson_isle: { + name: 'Crimson Isle', + + mobs: [ + { + name: 'Ashfang', + cap: 1000, + + mobs: ['ashfang_200'], + bracket: 5 + }, + { + name: 'Barbarian Duke X', + cap: 1000, + + mobs: ['barbarian_duke_x_200'], + bracket: 5 + }, + { + name: 'Bladesoul', + cap: 1000, + + mobs: ['bladesoul_200'], + bracket: 5 + }, + { + name: 'Blaze', + cap: 3000, + + mobs: ['blaze_25', 'blaze_70', 'bezal_80', 'mutated_blaze_70'], + bracket: 4 + }, + { + name: 'Smoldering Blaze', + cap: 25000, + + mobs: ['smoldering_blaze_95'], + bracket: 2 + }, + { + name: 'Millenia-Aged Blaze', + cap: 4000, + + mobs: ['old_blaze_110'], + bracket: 3 + }, + { + name: 'Flaming Spider', + cap: 10000, + + mobs: ['flaming_spider_80'], + bracket: 3 + }, + { + name: 'Flare', + cap: 100000, + + mobs: ['flare_90'], + bracket: 1 + }, + { + name: 'Ghast', + cap: 1000, + + mobs: ['ghast_85', 'dive_ghast_90'], + bracket: 4 + }, + { + name: 'Mage Outlaw', + cap: 1000, + + mobs: ['mage_outlaw_200'], + bracket: 5 + }, + { + name: 'Magma Cube', + cap: 10000, + + mobs: ['pack_magma_cube_90', 'magma_cube_75', 'fireball_magma_cube_75'], + bracket: 3 + }, + { + name: 'Magma Boss', + cap: 1000, + + mobs: ['magma_boss_500'], + bracket: 5 + }, + { + name: 'Matcho', + cap: 400, + + mobs: ['matcho_100'], + bracket: 5 + }, + { + name: 'Mushroom Bull', + cap: 10000, + + mobs: ['charging_mushroom_cow_80'], + bracket: 3 + }, + { + name: 'Pigman', + cap: 10000, + + mobs: ['kada_knight_90', 'magma_cube_rider_90', 'pigman_12'], + bracket: 3 + }, + { + name: 'Wither Skeleton', + cap: 10000, + + mobs: ['wither_skeleton_70'], + bracket: 3 + }, + { + name: 'Wither Spectre', + cap: 10000, + + mobs: ['wither_spectre_70'], + bracket: 3 + }, + { + name: 'Tentacle', + cap: 1000, + + mobs: ['hellwisp_100'], + bracket: 5 + }, + { + name: 'Vanquisher', + cap: 1000, + + mobs: ['vanquisher_100'], + bracket: 5 + } + ] + }, + mining_2: { + name: 'Deep Caverns', + + mobs: [ + { + name: 'Emerald Slime', + cap: 3000, + + mobs: ['emerald_slime_5', 'emerald_slime_10', 'emerald_slime_15'], + bracket: 1 + }, + { + name: 'Miner Skeleton', + cap: 3000, + + mobs: ['diamond_skeleton_15', 'diamond_skeleton_20'], + bracket: 1 + }, + { + name: 'Miner Zombie', + cap: 3000, + + mobs: ['diamond_zombie_15', 'diamond_zombie_20'], + bracket: 1 + }, + { + name: 'Redstone Pigman', + cap: 3000, + + mobs: ['redstone_pigman_10'], + bracket: 1 + }, + { + name: 'Sneaky Creeper', + cap: 300, + + mobs: ['invisible_creeper_3'], + bracket: 3 + }, + { + name: 'Lapis Zombie', + cap: 3000, + + mobs: ['lapis_zombie_7'], + bracket: 1 + } + ] + }, + mining_3: { + name: 'Dwarven Mines', + + mobs: [ + { + name: 'Ghost', + cap: 250000, + + mobs: ['caverns_ghost_250'], + bracket: 2 + }, + { + name: 'Goblin', + cap: 25000, + + mobs: [ + 'goblin_weakling_melee_25', + 'goblin_weakling_melee_40', + 'goblin_weakling_bow_25', + 'goblin_weakling_bow_40', + 'goblin_creepertamer_100', + 'goblin_pitfighter_70', + 'goblin_knife_thrower_25', + 'goblin_knife_thrower_40', + 'goblin_flamethrower_100', + 'goblin_murderlover_200' + ], + bracket: 2 + }, + { + name: 'Goblin Raiders', + cap: 1000, + + mobs: [ + 'goblin_weakling_melee_5', + 'goblin_weakling_bow_5', + 'goblin_creepertamer_90', + 'goblin_creeper_20', + 'goblin_battler_60', + 'goblin_murderlover_150', + 'goblin_golem_150' + ], + bracket: 4 + }, + { + name: 'Golden Goblin', + cap: 400, + + mobs: ['goblin_50'], + bracket: 5 + }, + { + name: 'Ice Walker', + cap: 10000, + + mobs: ['ice_walker_45'], + bracket: 2 + }, + { + name: 'Powder Ghast', + cap: 200, + + mobs: ['powder_ghast_1'], + bracket: 1 + }, + { + name: 'Star Sentry', + cap: 1000, + + mobs: ['crystal_sentry_50'], + bracket: 4 + }, + { + name: 'Treasure Hoarder', + cap: 3000, + + mobs: ['treasure_hoarder_70'], + bracket: 3 + } + ] + }, + crystal_hollows: { + name: 'Crystal Hollows', + + mobs: [ + { + name: 'Thyst', + cap: 4000, + + mobs: ['thyst_20'], + bracket: 3 + }, + { + name: 'Worm', + cap: 400, + + mobs: ['worm_5', 'scatha_10'], + bracket: 5 + }, + { + name: 'Yog', + cap: 4000, + + mobs: ['yog_100'], + bracket: 3 + }, + { + name: 'Sludge', + cap: 10000, + + mobs: ['sludge_5', 'sludge_10', 'sludge_100'], + bracket: 2 + }, + { + name: 'Automaton', + cap: 10000, + + mobs: ['automaton_100', 'automaton_150'], + bracket: 2 + }, + { + name: 'Butterfly', + cap: 1000, + + mobs: ['butterfly_100'], + bracket: 4 + }, + { + name: 'Grunt', + cap: 4000, + + mobs: [ + 'team_treasurite_grunt_50', + 'team_treasurite_viper_100', + 'team_treasurite_wendy_100', + 'team_treasurite_sebastian_100', + 'team_treasurite_corleone_200' + ], + bracket: 3 + }, + { + name: 'Bal', + cap: 250, + + mobs: ['bal_boss_100'], + bracket: 6 + }, + { + name: 'Key Guardian', + cap: 250, + + mobs: ['key_guardian_100'], + bracket: 6 + } + ] + }, + foraging_1: { + name: 'The Park', + + mobs: [ + { + name: 'Howling Spirit', + cap: 10000, + + mobs: ['howling_spirit_35'], + bracket: 2 + }, + { + name: 'Pack Spirit', + cap: 10000, + + mobs: ['pack_spirit_30'], + bracket: 2 + }, + { + name: 'Soul of the Alpha', + cap: 1000, + + mobs: ['soul_of_the_alpha_55'], + bracket: 4 + } + ] + }, + spooky_festival: { + name: 'Spooky Festival', + + mobs: [ + { + name: 'Crazy Witch', + cap: 750, + + mobs: ['batty_witch_60'], + bracket: 2 + }, + { + name: 'Headless Horseman', + cap: 500, + + mobs: ['horseman_horse_100'], + bracket: 7 + }, + { + name: 'Phantom Spirit', + cap: 750, + + mobs: ['phantom_spirit_35'], + bracket: 2 + }, + { + name: 'Scary Jerry', + cap: 750, + + mobs: ['scary_jerry_30'], + bracket: 2 + }, + { + name: 'Trick or Treater', + cap: 750, + + mobs: ['trick_or_treater_30'], + bracket: 2 + }, + { + name: 'Wither Gourd', + cap: 750, + + mobs: ['wither_gourd_40'], + bracket: 2 + }, + { + name: 'Wraith', + cap: 750, + + mobs: ['wraith_50'], + bracket: 2 + } + ] + }, + mythological_creatures: { + name: 'Mythological Creatures', + + mobs: [ + { + name: 'Gaia Construct', + cap: 3000, + + mobs: ['gaia_construct_140', 'gaia_construct_260'], + bracket: 4 + }, + { + name: 'Minos Champion', + cap: 1000, + + mobs: ['minos_champion_175', 'minos_champion_310'], + bracket: 5 + }, + { + name: 'Minos Hunter', + cap: 1000, + + mobs: ['minos_hunter_125', 'minos_hunter_15', 'minos_hunter_60'], + bracket: 5 + }, + { + name: 'Minos Inquisitor', + cap: 500, + + mobs: ['minos_inquisitor_750'], + bracket: 7 + }, + { + name: 'Minotaur', + cap: 3000, + + mobs: ['minotaur_45', 'minotaur_120', 'minotaur_210'], + bracket: 4 + }, + { + name: 'Siamese Lynx', + cap: 3000, + + mobs: ['siamese_lynx_25', 'siamese_lynx_85', 'siamese_lynx_155'], + bracket: 4 + } + ] + }, + jerry: { + name: 'Jerry', + + mobs: [ + { + name: 'Green Jerry', + cap: 75, + + mobs: ['mayor_jerry_green_1'], + bracket: 4 + }, + { + name: 'Blue Jerry', + cap: 30, + + mobs: ['mayor_jerry_blue_2'], + bracket: 5 + }, + { + name: 'Purple Jerry', + cap: 25, + + mobs: ['mayor_jerry_purple_3'], + bracket: 6 + }, + { + name: 'Golden Jerry', + cap: 20, + + mobs: ['mayor_jerry_golden_5'], + bracket: 7 + } + ] + }, + kuudra: { + name: 'Kuudra', + + mobs: [ + { + name: 'Blazing Golem', + cap: 300, + + mobs: [ + 'blazing_golem_100', + 'blazing_golem_200', + 'blazing_golem_300', + 'blazing_golem_400', + 'blazing_golem_500' + ], + bracket: 3 + }, + { + name: 'Blight', + cap: 10000, + + mobs: ['blight_100', 'blight_200', 'blight_300', 'blight_400', 'blight_500'], + bracket: 3 + }, + { + name: 'Dropship', + cap: 300, + + mobs: ['dropship_100', 'dropship_200', 'dropship_300', 'dropship_400', 'dropship_500'], + bracket: 3 + }, + { + name: 'Explosive Imp', + cap: 10000, + + mobs: [ + 'explosive_imp_100', + 'explosive_imp_200', + 'explosive_imp_300', + 'explosive_imp_400', + 'explosive_imp_500' + ], + bracket: 3 + }, + { + name: 'Inferno Magma Cube', + cap: 10000, + + mobs: [ + 'inferno_magma_cube_100', + 'inferno_magma_cube_200', + 'inferno_magma_cube_300', + 'inferno_magma_cube_400', + 'inferno_magma_cube_500' + ], + bracket: 3 + }, + { + name: 'Kuudra Berserker', + cap: 10000, + + mobs: [ + 'kuudra_berserker_100', + 'kuudra_berserker_200', + 'kuudra_berserker_300', + 'kuudra_berserker_400', + 'kuudra_berserker_500' + ], + bracket: 3 + }, + { + name: 'Kuudra Follower', + cap: 25000, + + mobs: [ + 'kuudra_follower_100', + 'kuudra_follower_200', + 'kuudra_follower_300', + 'kuudra_follower_400', + 'kuudra_follower_500' + ], + bracket: 2 + }, + { + name: 'Kuudra Knocker', + cap: 10000, + + mobs: [ + 'kuudra_knocker_100', + 'kuudra_knocker_200', + 'kuudra_knocker_300', + 'kuudra_knocker_400', + 'kuudra_knocker_500' + ], + bracket: 3 + }, + { + name: 'Kuudra Landmine', + cap: 10000, + + mobs: [ + 'kuudra_landmine_100', + 'kuudra_landmine_200', + 'kuudra_landmine_300', + 'kuudra_landmine_400', + 'kuudra_landmine_500' + ], + bracket: 3 + }, + { + name: 'Kuudra Slasher', + cap: 30, + + mobs: [ + 'kuudra_slasher_100', + 'kuudra_slasher_200', + 'kuudra_slasher_300', + 'kuudra_slasher_400', + 'kuudra_slasher_500' + ], + bracket: 5 + }, + { + name: 'Magma Follower', + cap: 30, + + mobs: [ + 'magma_follower_100', + 'magma_follower_200', + 'magma_follower_300', + 'magma_follower_400', + 'magma_follower_500' + ], + bracket: 5 + }, + { + name: 'Wandering Blaze', + cap: 3000, + + mobs: [ + 'wandering_blaze_100', + 'wandering_blaze_200', + 'wandering_blaze_300', + 'wandering_blaze_400', + 'wandering_blaze_500' + ], + bracket: 4 + }, + { + name: 'Wither Sentry', + cap: 75, + + mobs: [ + 'wither_sentry_100', + 'wither_sentry_200', + 'wither_sentry_300', + 'wither_sentry_400', + 'wither_sentry_500' + ], + bracket: 4 + } + ] + }, + fishing: { + fishing: { + name: 'Fishing', + + mobs: [ + { + name: 'Agarimoo', + cap: 4000, + + mobs: ['agarimoo_35'], + bracket: 3 + }, + { + name: 'Carrot King', + cap: 400, + + mobs: ['carrot_king_25'], + bracket: 5 + }, + { + name: 'Catfish', + cap: 1000, + + mobs: ['catfish_23'], + bracket: 4 + }, + { + name: 'Deep Sea Protector', + cap: 1000, + + mobs: ['deep_sea_protector_60'], + bracket: 4 + }, + { + name: 'Guardian Defender', + cap: 1000, + + mobs: ['guardian_defender_45'], + bracket: 4 + }, + { + name: 'Night Squid', + cap: 1000, + + mobs: ['night_squid_6'], + bracket: 4 + }, + { + name: 'Oasis Rabbit', + cap: 300, + + mobs: ['oasis_rabbit_10'], + bracket: 3 + }, + { + name: 'Oasis Sheep', + cap: 300, + + mobs: ['oasis_sheep_10'], + bracket: 3 + }, + { + name: 'Poisoned Water Worm', + cap: 1000, + + mobs: ['poisoned_water_worm_25'], + bracket: 4 + }, + { + name: 'Rider of the Deep', + cap: 4000, + + mobs: ['zombie_deep_20', 'chicken_deep_20'], + bracket: 3 + }, + { + name: 'Sea Archer', + cap: 4000, + + mobs: ['sea_archer_15'], + bracket: 3 + }, + { + name: 'Sea Guardian', + cap: 4000, + + mobs: ['sea_guardian_10'], + bracket: 3 + }, + { + name: 'Sea Leech', + cap: 1000, + + mobs: ['sea_leech_30'], + bracket: 4 + }, + { + name: 'Sea Walker', + cap: 4000, + + mobs: ['sea_walker_4'], + bracket: 3 + }, + { + name: 'Sea Witch', + cap: 4000, + + mobs: ['sea_witch_15'], + bracket: 3 + }, + { + name: 'Squid', + cap: 10000, + + mobs: ['pond_squid_1'], + bracket: 2 + }, + { + name: 'The Sea Emperor', + cap: 100, + + mobs: ['skeleton_emperor_150', 'guardian_emperor_150'], + bracket: 7 + }, + { + name: 'Water Hydra', + cap: 400, + + mobs: ['water_hydra_100'], + bracket: 5 + }, + { + name: 'Water Worm', + cap: 1000, + + mobs: ['water_worm_20'], + bracket: 4 + }, + { + name: 'Zombie Miner', + cap: 250, + + mobs: ['zombie_miner_150'], + bracket: 6 + } + ] + }, + lava: { + name: 'Lava Fishing', + + mobs: [ + { + name: 'Fire Eel', + cap: 1000, + + mobs: ['fire_eel_240'], + bracket: 4 + }, + { + name: 'Flaming Worm', + cap: 4000, + + mobs: ['flaming_worm_50'], + bracket: 3 + }, + { + name: 'Lava Blaze', + cap: 1000, + + mobs: ['lava_blaze_100'], + bracket: 4 + }, + { + name: 'Lava Flame', + cap: 1000, + + mobs: ['lava_flame_230'], + bracket: 4 + }, + { + name: 'Lava Leech', + cap: 4000, + + mobs: ['lava_leech_220'], + bracket: 3 + }, + { + name: 'Lava Pigman', + cap: 1000, + + mobs: ['lava_pigman_100'], + bracket: 4 + }, + { + name: 'Lord Jawbus', + cap: 250, + + mobs: ['lord_jawbus_600'], + bracket: 6 + }, + { + name: 'Magma Slug', + cap: 10000, + + mobs: ['magma_slug_200'], + bracket: 2 + }, + { + name: 'Moogma', + cap: 4000, + + mobs: ['moogma_210'], + bracket: 3 + }, + { + name: 'Plhlegblast', + cap: 7, + + mobs: ['pond_squid_300'], + bracket: 7 + }, + { + name: 'Pyroclastic Worm', + cap: 4000, + + mobs: ['pyroclastic_worm_240'], + bracket: 3 + }, + { + name: 'Taurus', + cap: 1000, + + mobs: ['pig_rider_250'], + bracket: 4 + }, + { + name: 'Thunder', + cap: 400, + + mobs: ['thunder_400'], + bracket: 5 + } + ] + }, + spooky_festival: { + name: 'Spooky Festival Fishing', + + mobs: [ + { + name: 'Grim Reaper', + cap: 100, + + mobs: ['grim_reaper_190'], + bracket: 7 + }, + { + name: 'Nightmare', + cap: 1000, + + mobs: ['nightmare_24'], + bracket: 4 + }, + { + name: 'Phantom Fisher', + cap: 250, + + mobs: ['phantom_fisherman_160'], + bracket: 6 + }, + { + name: 'Scarecrow', + cap: 4000, + + mobs: ['scarecrow_9'], + bracket: 3 + }, + { + name: 'Werewolf', + cap: 1000, + + mobs: ['werewolf_50'], + bracket: 4 + } + ] + }, + fishing_festival: { + name: 'Fishing Festival', + + mobs: [ + { + name: 'Blue Shark', + cap: 1000, + + mobs: ['blue_shark_20'], + bracket: 4 + }, + { + name: 'Great White Shark', + cap: 400, + + mobs: ['great_white_shark_180'], + bracket: 5 + }, + { + name: 'Nurse Shark', + cap: 4000, + + mobs: ['nurse_shark_6'], + bracket: 3 + }, + { + name: 'Tiger Shark', + cap: 1000, + + mobs: ['tiger_shark_50'], + bracket: 4 + } + ] + }, + winter: { + name: 'Winter Fishing', + + mobs: [ + { + name: 'Frosty', + cap: 4000, + + mobs: ['frosty_the_snowman_13'], + bracket: 3 + }, + { + name: 'Frozen Steve', + cap: 4000, + + mobs: ['frozen_steve_7'], + bracket: 3 + }, + { + name: 'Grinch', + cap: 250, + + mobs: ['grinch_21'], + bracket: 6 + }, + { + name: 'Nutcracker', + cap: 400, + + mobs: ['nutcracker_50'], + bracket: 5 + }, + { + name: 'Reindrake', + cap: 100, + + mobs: ['reindrake_100'], + bracket: 7 + }, + { + name: 'Yeti', + cap: 250, + + mobs: ['yeti_175'], + bracket: 6 + } + ] + } + }, + catacombs: { + name: 'Catacombs', + + mobs: [ + { + name: 'Angry Archeologist', + cap: 10000, + + mobs: [ + 'diamond_guy_80', + 'diamond_guy_90', + 'diamond_guy_100', + 'diamond_guy_110', + 'diamond_guy_120', + 'diamond_guy_130', + 'diamond_guy_140', + 'diamond_guy_150', + 'diamond_guy_160', + 'diamond_guy_170', + 'master_diamond_guy_80', + 'master_diamond_guy_90', + 'master_diamond_guy_100', + 'master_diamond_guy_110', + 'master_diamond_guy_120', + 'master_diamond_guy_130', + 'master_diamond_guy_140', + 'master_diamond_guy_150', + 'master_diamond_guy_160', + 'master_diamond_guy_170' + ], + bracket: 5 + }, + { + name: 'Lonely Spider', + + cap: 25000, + mobs: [ + 'lonely_spider_35', + 'lonely_spider_55', + 'lonely_spider_65', + 'lonely_spider_75', + 'lonely_spider_85', + 'lonely_spider_95', + 'lonely_spider_105', + 'lonely_spider_115', + 'master_lonely_spider_35', + 'master_lonely_spider_55', + 'master_lonely_spider_65', + 'master_lonely_spider_75', + 'master_lonely_spider_85', + 'master_lonely_spider_95', + 'master_lonely_spider_105', + 'master_lonely_spider_115' + ], + bracket: 4 + }, + { + name: 'Bat', + cap: 1000, + + mobs: ['dungeon_secret_bat_1'], + bracket: 4 + }, + { + name: 'Cellar Spider', + cap: 1000, + + mobs: [ + 'cellar_spider_45', + 'cellar_spider_65', + 'cellar_spider_75', + 'cellar_spider_85', + 'cellar_spider_95', + 'cellar_spider_105', + 'cellar_spider_115', + 'cellar_spider_125', + 'master_cellar_spider_45', + 'master_cellar_spider_65', + 'master_cellar_spider_75', + 'master_cellar_spider_85', + 'master_cellar_spider_95', + 'master_cellar_spider_105', + 'master_cellar_spider_115', + 'master_cellar_spider_125' + ], + bracket: 4 + }, + { + name: 'Crypt Dreadlord', + cap: 25000, + + mobs: [ + 'crypt_dreadlord_47', + 'crypt_dreadlord_67', + 'crypt_dreadlord_77', + 'crypt_dreadlord_87', + 'crypt_dreadlord_97', + 'crypt_dreadlord_107', + 'crypt_dreadlord_117', + 'crypt_dreadlord_127', + 'master_crypt_dreadlord_47', + 'master_crypt_dreadlord_67', + 'master_crypt_dreadlord_77', + 'master_crypt_dreadlord_87', + 'master_crypt_dreadlord_97', + 'master_crypt_dreadlord_107', + 'master_crypt_dreadlord_117', + 'master_crypt_dreadlord_127' + ], + bracket: 4 + }, + { + name: 'Crypt Lurker', + cap: 25000, + + mobs: [ + 'crypt_lurker_41', + 'crypt_lurker_61', + 'crypt_lurker_71', + 'crypt_lurker_81', + 'crypt_lurker_91', + 'crypt_lurker_101', + 'crypt_lurker_111', + 'crypt_lurker_121', + 'master_crypt_lurker_41', + 'master_crypt_lurker_61', + 'master_crypt_lurker_71', + 'master_crypt_lurker_81', + 'master_crypt_lurker_91', + 'master_crypt_lurker_101', + 'master_crypt_lurker_111', + 'master_crypt_lurker_121' + ], + bracket: 4 + }, + { + name: 'Crypt Souleater', + cap: 25000, + + mobs: [ + 'crypt_souleater_45', + 'crypt_souleater_65', + 'crypt_souleater_75', + 'crypt_souleater_85', + 'crypt_souleater_95', + 'crypt_souleater_105', + 'crypt_souleater_115', + 'crypt_souleater_125', + 'master_crypt_souleater_45', + 'master_crypt_souleater_65', + 'master_crypt_souleater_75', + 'master_crypt_souleater_85', + 'master_crypt_souleater_95', + 'master_crypt_souleater_105', + 'master_crypt_souleater_115', + 'master_crypt_souleater_125' + ], + bracket: 4 + }, + { + name: 'Fels', + cap: 25000, + + mobs: [ + 'tentaclees_90', + 'tentaclees_100', + 'tentaclees_110', + 'master_tentaclees_90', + 'master_tentaclees_100', + 'master_tentaclees_110' + ], + bracket: 4 + }, + { + name: 'Golem', + cap: 1000, + + mobs: ['sadan_golem_1', 'master_sadan_golem_1'], + bracket: 4 + }, + { + name: 'King Midas', + cap: 1000, + + mobs: [ + 'king_midas_130', + 'king_midas_140', + 'king_midas_150', + 'king_midas_160', + 'king_midas_170', + 'master_king_midas_130', + 'master_king_midas_140', + 'master_king_midas_150', + 'master_king_midas_160', + 'master_king_midas_170' + ], + bracket: 5 + }, + { + name: 'Lost Adventurer', + cap: 10000, + + mobs: [ + 'lost_adventurer_80', + 'lost_adventurer_81', + 'lost_adventurer_82', + 'lost_adventurer_83', + 'lost_adventurer_85', + 'lost_adventurer_86', + 'lost_adventurer_87', + 'lost_adventurer_88', + 'lost_adventurer_90', + 'lost_adventurer_91', + 'lost_adventurer_92', + 'lost_adventurer_93', + 'lost_adventurer_100', + 'lost_adventurer_101', + 'lost_adventurer_102', + 'lost_adventurer_103', + 'lost_adventurer_110', + 'lost_adventurer_111', + 'lost_adventurer_112', + 'lost_adventurer_113', + 'lost_adventurer_120', + 'lost_adventurer_121', + 'lost_adventurer_122', + 'lost_adventurer_123', + 'lost_adventurer_130', + 'lost_adventurer_131', + 'lost_adventurer_132', + 'lost_adventurer_133', + 'lost_adventurer_134', + 'lost_adventurer_135', + 'lost_adventurer_140', + 'lost_adventurer_141', + 'lost_adventurer_142', + 'lost_adventurer_143', + 'lost_adventurer_144', + 'lost_adventurer_150', + 'lost_adventurer_151', + 'lost_adventurer_152', + 'lost_adventurer_153', + 'lost_adventurer_154', + 'lost_adventurer_160', + 'lost_adventurer_161', + 'lost_adventurer_162', + 'lost_adventurer_163', + 'lost_adventurer_164', + 'master_lost_adventurer_80', + 'master_lost_adventurer_81', + 'master_lost_adventurer_82', + 'master_lost_adventurer_83', + 'master_lost_adventurer_85', + 'master_lost_adventurer_86', + 'master_lost_adventurer_87', + 'master_lost_adventurer_88', + 'master_lost_adventurer_90', + 'master_lost_adventurer_91', + 'master_lost_adventurer_92', + 'master_lost_adventurer_93', + 'master_lost_adventurer_100', + 'master_lost_adventurer_101', + 'master_lost_adventurer_102', + 'master_lost_adventurer_103', + 'master_lost_adventurer_110', + 'master_lost_adventurer_111', + 'master_lost_adventurer_112', + 'master_lost_adventurer_113', + 'master_lost_adventurer_120', + 'master_lost_adventurer_121', + 'master_lost_adventurer_122', + 'master_lost_adventurer_123', + 'master_lost_adventurer_130', + 'master_lost_adventurer_131', + 'master_lost_adventurer_132', + 'master_lost_adventurer_133', + 'master_lost_adventurer_134', + 'master_lost_adventurer_135', + 'master_lost_adventurer_140', + 'master_lost_adventurer_141', + 'master_lost_adventurer_142', + 'master_lost_adventurer_143', + 'master_lost_adventurer_144', + 'master_lost_adventurer_150', + 'master_lost_adventurer_151', + 'master_lost_adventurer_152', + 'master_lost_adventurer_153', + 'master_lost_adventurer_154', + 'master_lost_adventurer_160', + 'master_lost_adventurer_161', + 'master_lost_adventurer_162', + 'master_lost_adventurer_163', + 'master_lost_adventurer_164' + ], + bracket: 5 + }, + { + name: 'Mimic', + cap: 1000, + + mobs: ['mimic_115', 'mimic_125', 'master_mimic_115', 'master_mimic_125'], + bracket: 4 + }, + { + name: 'Scared Skeleton', + cap: 4000, + + mobs: [ + 'scared_skeleton_42', + 'scared_skeleton_62', + 'scared_skeleton_72', + 'master_scared_skeleton_42', + 'master_scared_skeleton_62', + 'master_scared_skeleton_72' + ], + bracket: 3 + }, + { + name: 'Shadow Assassin', + cap: 10000, + + mobs: [ + 'shadow_assassin_120', + 'shadow_assassin_130', + 'shadow_assassin_140', + 'shadow_assassin_150', + 'shadow_assassin_160', + 'shadow_assassin_170', + 'shadow_assassin_171', + 'master_shadow_assassin_120', + 'master_shadow_assassin_130', + 'master_shadow_assassin_140', + 'master_shadow_assassin_150', + 'master_shadow_assassin_160', + 'master_shadow_assassin_170', + 'master_shadow_assassin_171' + ], + bracket: 5 + }, + { + name: 'Skeleton Grunt', + cap: 4000, + + mobs: [ + 'skeleton_grunt_40', + 'skeleton_grunt_60', + 'skeleton_grunt_70', + 'skeleton_grunt_80', + 'master_skeleton_grunt_40', + 'master_skeleton_grunt_60', + 'master_skeleton_grunt_70', + 'master_skeleton_grunt_80' + ], + bracket: 3 + }, + { + name: 'Skeleton Lord', + cap: 1000, + + mobs: ['skeleton_lord_150', 'master_skeleton_lord_150'], + bracket: 5 + }, + { + name: 'Skeleton Master', + cap: 25000, + + mobs: [ + 'skeleton_master_48', + 'skeleton_master_68', + 'skeleton_master_78', + 'skeleton_master_88', + 'skeleton_master_98', + 'skeleton_master_108', + 'skeleton_master_118', + 'skeleton_master_128', + 'master_skeleton_master_48', + 'master_skeleton_master_68', + 'master_skeleton_master_78', + 'master_skeleton_master_88', + 'master_skeleton_master_98', + 'master_skeleton_master_108', + 'master_skeleton_master_118', + 'master_skeleton_master_128' + ], + bracket: 4 + }, + { + name: 'Skeleton Soldier', + cap: 40000, + + mobs: [ + 'skeleton_soldier_46', + 'skeleton_soldier_66', + 'skeleton_soldier_76', + 'skeleton_soldier_86', + 'skeleton_soldier_96', + 'skeleton_soldier_106', + 'skeleton_soldier_116', + 'skeleton_soldier_126', + 'master_skeleton_soldier_46', + 'master_skeleton_soldier_66', + 'master_skeleton_soldier_76', + 'master_skeleton_soldier_86', + 'master_skeleton_soldier_96', + 'master_skeleton_soldier_106', + 'master_skeleton_soldier_116', + 'master_skeleton_soldier_126' + ], + bracket: 1 + }, + { + name: 'Skeletor', + cap: 10000, + + mobs: [ + 'skeletor_80', + 'skeletor_90', + 'skeletor_100', + 'skeletor_101', + 'skeletor_110', + 'skeletor_120', + 'skeletor_prime_100', + 'skeletor_prime_110', + 'skeletor_prime_120', + 'master_skeletor_80', + 'master_skeletor_90', + 'master_skeletor_100', + 'master_skeletor_101', + 'master_skeletor_110', + 'master_skeletor_120', + 'master_skeletor_prime_100', + 'master_skeletor_prime_110', + 'master_skeletor_prime_120' + ], + bracket: 5 + }, + { + name: 'Sniper', + cap: 4000, + + mobs: [ + 'sniper_skeleton_43', + 'sniper_skeleton_63', + 'sniper_skeleton_73', + 'sniper_skeleton_83', + 'sniper_skeleton_93', + 'sniper_skeleton_103', + 'sniper_skeleton_113', + 'sniper_skeleton_123', + 'master_sniper_skeleton_43', + 'master_sniper_skeleton_63', + 'master_sniper_skeleton_73', + 'master_sniper_skeleton_83', + 'master_sniper_skeleton_93', + 'master_sniper_skeleton_103', + 'master_sniper_skeleton_113', + 'master_sniper_skeleton_123' + ], + bracket: 3 + }, + { + name: 'Super Archer', + cap: 10000, + + mobs: [ + 'super_archer_90', + 'super_archer_100', + 'super_archer_110', + 'super_archer_120', + 'master_super_archer_90', + 'master_super_archer_100', + 'master_super_archer_110', + 'master_super_archer_120' + ], + bracket: 5 + }, + { + name: 'Super Tank Zombie', + cap: 25000, + + mobs: [ + 'super_tank_zombie_90', + 'super_tank_zombie_100', + 'super_tank_zombie_110', + 'super_tank_zombie_120', + 'master_super_tank_zombie_90', + 'master_super_tank_zombie_100', + 'master_super_tank_zombie_110', + 'master_super_tank_zombie_120' + ], + bracket: 4 + }, + { + name: 'Tank Zombie', + cap: 4000, + + mobs: [ + 'crypt_tank_zombie_40', + 'crypt_tank_zombie_60', + 'crypt_tank_zombie_70', + 'crypt_tank_zombie_80', + 'crypt_tank_zombie_90', + 'master_crypt_tank_zombie_40', + 'master_crypt_tank_zombie_60', + 'master_crypt_tank_zombie_70', + 'master_crypt_tank_zombie_80', + 'master_crypt_tank_zombie_90' + ], + bracket: 3 + }, + { + name: 'Withermancer', + + cap: 25000, + mobs: [ + 'crypt_witherskeleton_90', + 'crypt_witherskeleton_100', + 'crypt_witherskeleton_110', + 'crypt_witherskeleton_120', + 'master_crypt_witherskeleton_90', + 'master_crypt_witherskeleton_100', + 'master_crypt_witherskeleton_110', + 'master_crypt_witherskeleton_120' + ], + bracket: 4 + }, + { + name: 'Terracotta', + cap: 40000, + + mobs: ['sadan_statue_1', 'master_sadan_statue_1'], + bracket: 1 + }, + { + name: 'Undead', + cap: 10000, + + mobs: [ + 'watcher_summon_undead_1', + 'watcher_summon_undead_2', + 'watcher_summon_undead_3', + 'watcher_summon_undead_4', + 'watcher_summon_undead_5', + 'watcher_summon_undead_6', + 'watcher_summon_undead_7', + 'watcher_summon_undead_8', + 'master_watcher_summon_undead_1', + 'master_watcher_summon_undead_2', + 'master_watcher_summon_undead_3', + 'master_watcher_summon_undead_4', + 'master_watcher_summon_undead_5', + 'master_watcher_summon_undead_6', + 'master_watcher_summon_undead_7', + 'master_watcher_summon_undead_8' + ], + bracket: 2 + }, + { + name: 'Undead Skeleton', + cap: 25000, + + mobs: [ + 'dungeon_respawning_skeleton_40', + 'dungeon_respawning_skeleton_40', + 'dungeon_respawning_skeleton_60', + 'dungeon_respawning_skeleton_60', + 'dungeon_respawning_skeleton_70', + 'dungeon_respawning_skeleton_70', + 'dungeon_respawning_skeleton_80', + 'dungeon_respawning_skeleton_80', + 'dungeon_respawning_skeleton_90', + 'dungeon_respawning_skeleton_90', + 'dungeon_respawning_skeleton_100', + 'dungeon_respawning_skeleton_100', + 'dungeon_respawning_skeleton_110', + 'dungeon_respawning_skeleton_110', + 'dungeon_respawning_skeleton_120', + 'dungeon_respawning_skeleton_120', + 'master_dungeon_respawning_skeleton_40', + 'master_dungeon_respawning_skeleton_40', + 'master_dungeon_respawning_skeleton_60', + 'master_dungeon_respawning_skeleton_60', + 'master_dungeon_respawning_skeleton_70', + 'master_dungeon_respawning_skeleton_70', + 'master_dungeon_respawning_skeleton_80', + 'master_dungeon_respawning_skeleton_80', + 'master_dungeon_respawning_skeleton_90', + 'master_dungeon_respawning_skeleton_90', + 'master_dungeon_respawning_skeleton_100', + 'master_dungeon_respawning_skeleton_100', + 'master_dungeon_respawning_skeleton_110', + 'master_dungeon_respawning_skeleton_110', + 'master_dungeon_respawning_skeleton_120' + ], + bracket: 4 + }, + { + name: 'Wither Guard', + cap: 10000, + + mobs: ['wither_guard_100', 'master_wither_guard_100'], + bracket: 5 + }, + { + name: 'Wither Husk', + cap: 10000, + + mobs: ['master_wither_husk_100'], + bracket: 5 + }, + { + name: 'Wither Miner', + cap: 25000, + + mobs: ['wither_miner_100', 'master_wither_miner_100'], + bracket: 4 + }, + { + name: 'Zombie Commander', + cap: 3000, + + mobs: [ + 'zombie_commander_110', + 'zombie_commander_120', + 'master_zombie_commander_110', + 'master_zombie_commander_120' + ], + bracket: 4 + }, + { + name: 'Zombie Grunt', + cap: 4000, + + mobs: [ + 'zombie_grunt_40', + 'zombie_grunt_60', + 'zombie_grunt_70', + 'zombie_grunt_80', + 'master_zombie_grunt_40', + 'master_zombie_grunt_60', + 'master_zombie_grunt_70', + 'master_zombie_grunt_80' + ], + bracket: 3 + }, + { + name: 'Zombie Knight', + cap: 25000, + + mobs: [ + 'zombie_knight_86', + 'zombie_knight_96', + 'zombie_knight_106', + 'zombie_knight_116', + 'zombie_knight_126', + 'master_zombie_knight_86', + 'master_zombie_knight_96', + 'master_zombie_knight_106', + 'master_zombie_knight_116', + 'master_zombie_knight_126' + ], + bracket: 4 + }, + { + name: 'Zombie Lord', + cap: 1000, + + mobs: ['zombie_lord_150', 'master_zombie_lord_150'], + bracket: 5 + }, + { + name: 'Zombie Soldier', + cap: 40000, + + mobs: [ + 'zombie_soldier_83', + 'zombie_soldier_93', + 'zombie_soldier_103', + 'zombie_soldier_113', + 'zombie_soldier_123', + 'master_zombie_soldier_83', + 'master_zombie_soldier_93', + 'master_zombie_soldier_103', + 'master_zombie_soldier_113', + 'master_zombie_soldier_123' + ], + bracket: 1 + } + ] + } + }, + bestiaryBrackets: { + 1: [ + 20, 40, 60, 100, 200, 400, 800, 1400, 2000, 3000, 6000, 12000, 20000, 30000, 40000, 50000, 60000, 72000, 86000, + 100000, 200000, 400000, 600000, 800000, 1000000 + ], + 2: [ + 5, 10, 15, 25, 50, 100, 200, 350, 500, 750, 1500, 3000, 5000, 7500, 10000, 12500, 15000, 18000, 21500, 25000, + 50000, 100000, 150000, 200000, 250000 + ], + 3: [ + 4, 8, 12, 16, 20, 40, 80, 140, 200, 300, 600, 1200, 2000, 3000, 4000, 5000, 6000, 7200, 8600, 10000, 20000, 40000, + 60000, 80000, 100000 + ], + 4: [ + 2, 4, 6, 10, 15, 20, 25, 35, 50, 75, 150, 300, 500, 750, 1000, 1350, 1650, 2000, 2500, 3000, 5000, 10000, 15000, + 20000, 25000 + ], + 5: [ + 1, 2, 3, 5, 7, 10, 15, 20, 25, 30, 60, 120, 200, 300, 400, 500, 600, 720, 860, 1000, 2000, 4000, 6000, 8000, 10000 + ], + 6: [1, 2, 3, 5, 7, 9, 14, 17, 21, 25, 50, 80, 125, 175, 250, 325, 425, 525, 625, 750, 1500], + 7: [1, 2, 3, 5, 7, 9, 11, 14, 17, 20, 30, 40, 55, 75, 100, 150, 200, 275, 375, 500, 1000] + }, + // Credits (pit) https://github.com/PitPanda/PitPandaProduction/blob/b1971f56ea1aa8c829b722cbb33247c96591c0cb/structures/Pit.js + pit: { + Prestiges: [ + { Multiplier: 1, TotalXp: 65950, SumXp: 65950, GoldReq: 10000, Renown: 0 }, + { Multiplier: 1.1, TotalXp: 72560, SumXp: 138510, GoldReq: 20000, Renown: 10 }, + { Multiplier: 1.2, TotalXp: 79170, SumXp: 217680, GoldReq: 20000, Renown: 10 }, + { Multiplier: 1.3, TotalXp: 85750, SumXp: 303430, GoldReq: 20000, Renown: 10 }, + { Multiplier: 1.4, TotalXp: 92330, SumXp: 395760, GoldReq: 30000, Renown: 10 }, + { Multiplier: 1.5, TotalXp: 98940, SumXp: 494700, GoldReq: 35000, Renown: 10 }, + { Multiplier: 1.75, TotalXp: 115440, SumXp: 610140, GoldReq: 40000, Renown: 20 }, + { Multiplier: 2, TotalXp: 131900, SumXp: 742040, GoldReq: 45000, Renown: 20 }, + { Multiplier: 2.5, TotalXp: 164890, SumXp: 906930, GoldReq: 50000, Renown: 20 }, + { Multiplier: 3, TotalXp: 197850, SumXp: 1104780, GoldReq: 60000, Renown: 20 }, + { Multiplier: 4, TotalXp: 263800, SumXp: 1368580, GoldReq: 70000, Renown: 20 }, + { Multiplier: 5, TotalXp: 329750, SumXp: 1698330, GoldReq: 80000, Renown: 30 }, + { Multiplier: 6, TotalXp: 395700, SumXp: 2094030, GoldReq: 90000, Renown: 30 }, + { Multiplier: 7, TotalXp: 461650, SumXp: 2555680, GoldReq: 100000, Renown: 30 }, + { Multiplier: 8, TotalXp: 527600, SumXp: 3083280, GoldReq: 125000, Renown: 30 }, + { Multiplier: 9, TotalXp: 593550, SumXp: 3676830, GoldReq: 150000, Renown: 30 }, + { Multiplier: 10, TotalXp: 659500, SumXp: 4336330, GoldReq: 175000, Renown: 40 }, + { Multiplier: 12, TotalXp: 791400, SumXp: 5127730, GoldReq: 200000, Renown: 40 }, + { Multiplier: 14, TotalXp: 923300, SumXp: 6051030, GoldReq: 250000, Renown: 40 }, + { Multiplier: 16, TotalXp: 1055200, SumXp: 7106230, GoldReq: 300000, Renown: 40 }, + { Multiplier: 18, TotalXp: 1187100, SumXp: 8293330, GoldReq: 350000, Renown: 40 }, + { Multiplier: 20, TotalXp: 1319000, SumXp: 9612330, GoldReq: 400000, Renown: 50 }, + { Multiplier: 24, TotalXp: 1582800, SumXp: 11195130, GoldReq: 500000, Renown: 50 }, + { Multiplier: 28, TotalXp: 1846600, SumXp: 13041730, GoldReq: 600000, Renown: 50 }, + { Multiplier: 32, TotalXp: 2110400, SumXp: 15152130, GoldReq: 700000, Renown: 50 }, + { Multiplier: 36, TotalXp: 2374200, SumXp: 17526330, GoldReq: 800000, Renown: 50 }, + { Multiplier: 40, TotalXp: 2638000, SumXp: 20164330, GoldReq: 900000, Renown: 75 }, + { Multiplier: 45, TotalXp: 2967750, SumXp: 23132080, GoldReq: 1000000, Renown: 75 }, + { Multiplier: 50, TotalXp: 3297500, SumXp: 26429580, GoldReq: 1000000, Renown: 75 }, + { Multiplier: 75, TotalXp: 4946250, SumXp: 31375830, GoldReq: 1000000, Renown: 75 }, + { Multiplier: 100, TotalXp: 6595000, SumXp: 37970830, GoldReq: 1000000, Renown: 250 }, + { Multiplier: 101, TotalXp: 6660950, SumXp: 44631780, GoldReq: 1000000, Renown: 100 }, + { Multiplier: 101, TotalXp: 6660950, SumXp: 51292730, GoldReq: 1000000, Renown: 100 }, + { Multiplier: 101, TotalXp: 6660950, SumXp: 57953680, GoldReq: 1000000, Renown: 100 }, + { Multiplier: 101, TotalXp: 6660950, SumXp: 64614630, GoldReq: 1000000, Renown: 100 }, + { Multiplier: 101, TotalXp: 6660950, SumXp: 71275580, GoldReq: 2000000, Renown: 100 }, + { Multiplier: 200, TotalXp: 13190000, SumXp: 84465580, GoldReq: 2000000, Renown: 100 }, + { Multiplier: 300, TotalXp: 19785000, SumXp: 104250580, GoldReq: 2000000, Renown: 100 }, + { Multiplier: 400, TotalXp: 26380000, SumXp: 130630580, GoldReq: 2000000, Renown: 100 }, + { Multiplier: 500, TotalXp: 32975000, SumXp: 163605580, GoldReq: 2000000, Renown: 100 }, + { Multiplier: 750, TotalXp: 49462500, SumXp: 213068080, GoldReq: 2000000, Renown: 100 }, + { Multiplier: 1000, TotalXp: 65950000, SumXp: 279018080, GoldReq: 2000000, Renown: 100 }, + { Multiplier: 1250, TotalXp: 82437500, SumXp: 361455580, GoldReq: 2000000, Renown: 100 }, + { Multiplier: 1500, TotalXp: 98925000, SumXp: 460380580, GoldReq: 2000000, Renown: 100 }, + { Multiplier: 1750, TotalXp: 115412500, SumXp: 575793080, GoldReq: 2000000, Renown: 100 }, + { Multiplier: 2000, TotalXp: 131900000, SumXp: 707693080, GoldReq: 2000000, Renown: 100 }, + { Multiplier: 3000, TotalXp: 197850000, SumXp: 905543080, GoldReq: 2000000, Renown: 100 }, + { Multiplier: 5000, TotalXp: 329750000, SumXp: 1235293080, GoldReq: 2000000, Renown: 100 }, + { Multiplier: 10000, TotalXp: 659500000, SumXp: 1894793080, GoldReq: 2000000, Renown: 100 }, + { Multiplier: 50000, TotalXp: 3297500000, SumXp: 5192293080, GoldReq: 2000000, Renown: 100 }, + { Multiplier: 100000, TotalXp: 6595000000, SumXp: 11787293080, GoldReq: 0, Renown: 100 } + ], + Levels: [ + { Xp: 15 }, + { Xp: 30 }, + { Xp: 50 }, + { Xp: 75 }, + { Xp: 125 }, + { Xp: 300 }, + { Xp: 600 }, + { Xp: 800 }, + { Xp: 900 }, + { Xp: 1000 }, + { Xp: 1200 }, + { Xp: 1500 }, + { Xp: 0 } + ] + } +}; diff --git a/src/utils/Guild.ts b/src/utils/Guild.ts new file mode 100644 index 00000000..a90d60f1 --- /dev/null +++ b/src/utils/Guild.ts @@ -0,0 +1,89 @@ +import GuildMember from '../structures/Guild/GuildMember'; +import GuildRank from '../structures/Guild/GuildRank'; +import { ExpHistory } from '../typings'; + +const dateRegExp = /(\d{4})-(\d{2})-(\d{2})/; + +export function parseDate(date: Record) { + date[1] -= 1; + return new Date( + Math.round(new Date(new Date().setUTCFullYear(...(date as [any]))).setUTCHours(5, 0, 0) / 1000) * 1000 + ); +} + +export function parseHistory(historyData: Record): ExpHistory[] { + return Object.entries(historyData).map((x, index) => ({ + day: x[0], + date: x[0].match(dateRegExp) + ? parseDate( + x[0] + .match(dateRegExp)! + .slice(1) + .map((x) => parseInt(x, 10)) + ) + : undefined, + exp: x[1] || 0, + totalExp: + Object.values(historyData) + .slice(0, index + 1) + .reduce((pV: any, cV: any) => pV + cV, 0) || 0 + })); +} + +export function getGuildLevel(exp: number) { + const EXP_NEEDED = [ + 100000, 150000, 250000, 500000, 750000, 1000000, 1250000, 1500000, 2000000, 2500000, 2500000, 2500000, 2500000, + 2500000, 3000000 + ]; + + let level = 0; + + for (let i = 0; 1000 >= i; i += 1) { + let need; + if (i >= EXP_NEEDED.length) { + need = EXP_NEEDED[EXP_NEEDED.length - 1]; + } else { + need = EXP_NEEDED[i]; + } + + if (0 > exp - need) { + return Math.round((level + exp / need) * 100) / 100; + } + level += 1; + exp -= need; + } + + return 1000; +} + +export function ranks(data: Record) { + return data.ranks && data.ranks.length + ? data.ranks.map((r: any) => new GuildRank(r)).sort((a: any, b: any) => a.priority - b.priority) + : []; +} + +export function expLimit(exp: number) { + return 2e5 < exp ? (7e5 < exp ? 2.5e5 + Math.round(exp * 0.03) : 2e5 + Math.round((exp - 2e5) / 10)) : exp; +} + +export function calculateExpHistory(data: Record): ExpHistory[] { + const finalObj: any = {}; + for (const day of Object.keys(data.members[0].expHistory)) { + let gexp = 0; + for (const member of data.members) { + gexp += member.expHistory[day] || 0; + } + finalObj[day] = expLimit(gexp); + } + return parseHistory(finalObj); +} + +export function members(data: Record): GuildMember[] { + return data.members.length ? data.members.map((m: Record) => new GuildMember(m)) : []; +} + +export function totalWeeklyGexp(data: Record) { + return members(data) + .map((m) => m.weeklyExperience) + .reduce((acc: number, cur: number) => acc + cur); +} diff --git a/src/utils/Player.ts b/src/utils/Player.ts new file mode 100644 index 00000000..2d7f5e1e --- /dev/null +++ b/src/utils/Player.ts @@ -0,0 +1,100 @@ +import { LevelProgress, PlayerRank, PlayerSocialMedia } from '../typings'; + +export function getRank(player: Record): PlayerRank { + let rank; + if (player.prefix) { + rank = player.prefix.replace(/§[0-9|a-z]|\[|\]/g, ''); + } else if (player.rank && 'NORMAL' !== player.rank) { + switch (player.rank) { + case 'YOUTUBER': + rank = 'YouTube'; + break; + case 'GAME_MASTER': + rank = 'Game Master'; + break; + case 'ADMIN': + rank = 'Admin'; + break; + default: + rank = ''; + break; + } + } else { + switch (player.newPackageRank) { + case 'MVP_PLUS': + rank = player.monthlyPackageRank && 'SUPERSTAR' === player.monthlyPackageRank ? 'MVP++' : 'MVP+'; + break; + case 'MVP': + rank = 'MVP'; + break; + case 'VIP_PLUS': + rank = 'VIP+'; + break; + case 'VIP': + rank = 'VIP'; + break; + default: + rank = player.monthlyPackageRank && 'SUPERSTAR' === player.monthlyPackageRank ? 'MVP++' : ''; + } + } + return rank; +} + +export function getPlayerLevel(exp: number): number { + const base = 10000; + const growth = 2500; + const reversePqPrefix = -(base - 0.5 * growth) / growth; + const reverseConst = reversePqPrefix * reversePqPrefix; + const growthDivides2 = 2 / growth; + const num = 1 + reversePqPrefix + Math.sqrt(reverseConst + growthDivides2 * exp); + const level = Math.round(num * 100) / 100; + return level; +} + +export function xpToNextLevel(xp: number): number { + const lvl = getPlayerLevel(xp); + const xpToNext = 2500 * Math.floor(lvl) + 5000; + if (10000 > xp) return 10000; + return xpToNext; +} + +export function levelToXP(xp: number): number { + let level = Number(Math.floor(getPlayerLevel(xp))); + level = level - 1; + return 1250 * level ** 2 + 8750 * level; +} + +export function playerLevelProgress(xp: number): LevelProgress { + const xpFromLevel = levelToXP(xp); + let currentXP = xp - xpFromLevel; + const xpToNext = xpToNextLevel(xp); + const remainingXP = xpToNext - currentXP + 2500; + currentXP = currentXP - 2500; + const percent = Math.round((currentXP / xpToNext) * 100 * 100) / 100; + const percentRemaining = Math.round((100 - percent) * 100) / 100; + return { + xpToNext, + remainingXP, + currentXP, + percent, + percentRemaining + }; +} + +export function getSocialMedia(data: Record): PlayerSocialMedia[] { + const links = data.links; + const formattedNames = ['Twitter', 'YouTube', 'Instagram', 'Twitch', 'Hypixel', 'Discord']; + const upperNames = ['TWITTER', 'YOUTUBE', 'INSTAGRAM', 'TWITCH', 'HYPIXEL', 'DISCORD']; + return Object.keys(links) + .map((x) => upperNames.indexOf(x)) + .filter((x) => -1 !== x) + .map((x) => ({ name: formattedNames[x], link: links[upperNames[x]], id: upperNames[x] })); +} + +export function parseClaimedRewards(data: Record): number[] { + return Object.keys(data) + .map((x) => x.match(/levelingReward_(\d+)/)) + .filter((x) => x) + .map((x) => (x ? parseInt(x[1], 10) : null)) + .filter((x) => null !== x); +} diff --git a/src/utils/SkyblockUtils.ts b/src/utils/SkyblockUtils.ts new file mode 100644 index 00000000..deecb958 --- /dev/null +++ b/src/utils/SkyblockUtils.ts @@ -0,0 +1,474 @@ +import { promisify } from 'util'; +import { + SkyblockMemberChocolateFactoryData, + SkyblockMemberTrophyFishRank, + SkyblockMemberSlayerLevel, + SkyblockMemberJacobData, + SkyblockMemberDungeons, + SkyblockMemberSkills, + SkyblockMemberSlayer, + SkyblockSkillLevel, + SkyblockRarity, + SkyblockMemberStats +} from '../typings'; +import Constants from './Constants'; +import nbt from 'prismarine-nbt'; + +export async function decode(base64: string | Buffer, isBuffer = false): Promise { + const parseNbt = promisify(nbt.parse); + const buffer = isBuffer ? base64 : Buffer.from(String(base64), 'base64'); + const NBTData = await parseNbt(buffer as Buffer); + const data = nbt.simplify(NBTData) as any; + const newdata = []; + for (let i = 0; i < data.i.length; i++) { + newdata.push(data.i[i]); + } + return newdata; +} + +export function getLevelByXp(xp: number, type: string): SkyblockSkillLevel { + let xpTable: Record; + switch (type) { + case 'runecrafting': + xpTable = Constants.runecraftingXp; + break; + case 'dungeons': + xpTable = Constants.dungeonXp; + break; + case 'hotm': + xpTable = Constants.hotmXp; + break; + case 'social': + xpTable = Constants.socialXp; + break; + case 'garden': + xpTable = Constants.garden; + break; + case 'wheat': + xpTable = Constants.wheat; + break; + case 'carrot': + xpTable = Constants.carrot; + break; + case 'potato': + xpTable = Constants.potato; + break; + case 'melon': + xpTable = Constants.melon; + break; + case 'pumpkin': + xpTable = Constants.pumpkin; + break; + case 'sugarCane': + xpTable = Constants.sugarCane; + break; + case 'cocoaBeans': + xpTable = Constants.cocoaBeans; + break; + case 'cactus': + xpTable = Constants.cactus; + break; + case 'mushroom': + xpTable = Constants.mushroom; + break; + case 'netherWart': + xpTable = Constants.netherWart; + break; + default: + xpTable = Constants.levelingXp; + } + const maxLevel = Math.max(...Object.keys(xpTable).map(Number)); + if (isNaN(xp)) { + return { + xp: 0, + level: 0, + maxLevel, + xpCurrent: 0, + xpForNext: xpTable[1], + progress: 0, + cosmetic: Boolean('runecrafting' === type || 'social' === type) + }; + } + let xpTotal = 0; + let level = 0; + let xpForNext = 0; + for (let x = 1; x <= maxLevel; x++) { + if (!xpTable[x]) continue; + xpTotal += xpTable[x]; + if (xpTotal > xp) { + xpTotal -= xpTable[x]; + break; + } else { + level = x; + } + } + const xpCurrent = Math.floor(xp - xpTotal); + if (level < maxLevel) xpForNext = Math.ceil(xpTable[level + 1]); + const progress = Math.floor(Math.max(0, Math.min(xpCurrent / xpForNext, 1)) * 100 * 10) / 10; + + return { + xp: xp, + level: level, + maxLevel: maxLevel, + xpCurrent: xpCurrent, + xpForNext: xpForNext, + progress: progress, + cosmetic: Boolean('runecrafting' === type || 'social' === type) + }; +} + +export function getSlayerLevel(slayer: Record): SkyblockMemberSlayerLevel { + if (!slayer) { + return { + xp: 0, + tier1: 0, + tier2: 0, + tier3: 0, + tier4: 0, + tier5: 0, + level: 0 + }; + } + + // eslint-disable-next-line camelcase + const { claimed_levels } = slayer; + let level = 0; + + // eslint-disable-next-line camelcase + for (const levelName in claimed_levels) { + if (Object.prototype.hasOwnProperty.call(claimed_levels, levelName)) { + const newLevel = parseInt(levelName.replace('_special', '').split('_').pop() ?? '', 10); + if (newLevel > level) { + level = newLevel; + } + } + } + return { + xp: slayer.xp || 0, + tier1: slayer.boss_kills_tier_0 || 0, + tier2: slayer.boss_kills_tier_1 || 0, + tier3: slayer.boss_kills_tier_2 || 0, + tier4: slayer.boss_kills_tier_3 || 0, + tier5: slayer.boss_kills_tier_4 || 0, + level + }; +} + +export function getMemberStats(obj: Record): SkyblockMemberStats { + return Object.keys(obj).reduce( + (result, currentKey) => { + const key = currentKey.replace(/_[a-z]/gi, (match) => match[1].toUpperCase()); + + if (currentKey.startsWith('kills') || currentKey.startsWith('deaths')) { + const category = currentKey.startsWith('kills') ? 'kills' : 'deaths'; + const subKey = key === category ? 'total' : key; + + result[category as keyof typeof result][ + subKey.replace(category, (sub, _, key) => { + return key[sub.length].toLowerCase() + key.slice(sub.length + 1); + }) + ] = obj[currentKey]; + } else { + result[key as keyof typeof result] = obj[currentKey]; + } + + return result; + }, + { kills: {}, deaths: {} } as { kills: Record; deaths: Record } + ); +} + +export function getTrophyFishRank(level: number): SkyblockMemberTrophyFishRank { + if (1 === level) { + return 'Bronze'; + } else if (2 === level) { + return 'Silver'; + } else if (3 === level) { + return 'Gold'; + } else if (4 === level) { + return 'Diamond'; + } + return 'Bronze'; +} + +export function getSkills(data: Record): SkyblockMemberSkills { + const skillsObject: SkyblockMemberSkills = { + combat: getLevelByXp(data?.player_data?.experience?.SKILL_COMBAT ?? 0, 'combat'), + farming: getLevelByXp(data?.player_data?.experience?.SKILL_FARMING ?? 0, 'farming'), + fishing: getLevelByXp(data?.player_data?.experience?.SKILL_FISHING ?? 0, 'fishing'), + mining: getLevelByXp(data?.player_data?.experience?.SKILL_MINING ?? 0, 'mining'), + foraging: getLevelByXp(data?.player_data?.experience?.SKILL_FORAGING ?? 0, 'foraging'), + enchanting: getLevelByXp(data?.player_data?.experience?.SKILL_ENCHANTING ?? 0, 'enchanting'), + alchemy: getLevelByXp(data?.player_data?.experience?.SKILL_ALCHEMY ?? 0, 'alchemy'), + carpentry: getLevelByXp(data?.player_data?.experience?.SKILL_CARPENTRY ?? 0, 'carpentry'), + runecrafting: getLevelByXp(data?.player_data?.experience?.SKILL_RUNECRAFTING ?? 0, 'runecrafting'), + taming: getLevelByXp(data?.player_data?.experience?.SKILL_TAMING ?? 0, 'taming'), + social: getLevelByXp(data?.player_data?.experience?.SKILL_SOCIAL ?? 0, 'social'), + average: 0 + }; + const levels = Object.values(skillsObject) + .filter((skill) => true !== skill.cosmetic) + .map((skill) => skill.level); + skillsObject.average = levels.reduce((a, b) => a + b, 0) / levels.length; + return skillsObject; +} + +function formatBestiaryMobs(userProfile: Record, mobs: any) { + const output = []; + for (const mob of mobs) { + const mobBracket = (Constants.bestiaryBrackets as { [key: number]: number[] })[mob.bracket]; + + const totalKills = mob.mobs.reduce((acc: any, cur: any) => { + return acc + (userProfile.bestiary.kills[cur] ?? 0); + }, 0); + + const maxKills = mob.cap; + const nextTierKills = mobBracket.find((tier: any) => totalKills < tier && tier <= maxKills); + const tier = nextTierKills ? mobBracket.indexOf(nextTierKills) : mobBracket.indexOf(maxKills) + 1; + + output.push({ + tier: tier + }); + } + + return output; +} + +export function getBestiaryLevel(userProfile: Record): number { + try { + if (userProfile.bestiary?.kills === undefined) { + return 0; + } + + const output: { [key: string]: any } = {}; + let tiersUnlocked = 0; + for (const [category, data] of Object.entries(Constants.bestiary)) { + const { mobs } = data as { mobs: any }; + output[category] = {}; + + if ('fishing' === category) { + for (const [key, value] of Object.entries(data)) { + output[category][key] = { + mobs: formatBestiaryMobs(userProfile, value.mobs) + }; + tiersUnlocked += output[category][key].mobs.reduce((acc: any, cur: any) => acc + cur.tier, 0); + } + } else { + output[category].mobs = formatBestiaryMobs(userProfile, mobs); + tiersUnlocked += output[category].mobs.reduce((acc: any, cur: any) => acc + cur.tier, 0); + } + } + + return tiersUnlocked / 10; + } catch (error) { + return 0; + } +} + +export function getSlayer(data: Record): SkyblockMemberSlayer | null { + if (!data?.slayer?.slayer_bosses) return null; + return { + zombie: getSlayerLevel(data?.slayer?.slayer_bosses?.zombie), + spider: getSlayerLevel(data?.slayer?.slayer_bosses?.spider), + wolf: getSlayerLevel(data?.slayer?.slayer_bosses?.wolf), + enderman: getSlayerLevel(data?.slayer?.slayer_bosses?.enderman), + blaze: getSlayerLevel(data?.slayer?.slayer_bosses?.blaze), + vampire: getSlayerLevel(data?.slayer?.slayer_bosses?.vampire) + }; +} + +export function getDungeons(data: Record): SkyblockMemberDungeons | null { + return { + types: { + catacombs: getLevelByXp(data.dungeons?.dungeon_types?.catacombs ?? 0, 'dungeons') + }, + classes: { + healer: getLevelByXp(data.dungeons?.player_classes?.healer ?? 0, 'dungeons'), + mage: getLevelByXp(data.dungeons?.player_classes?.mage ?? 0, 'dungeons'), + berserk: getLevelByXp(data.dungeons?.player_classes?.berserk ?? 0, 'dungeons'), + archer: getLevelByXp(data.dungeons?.player_classes?.archer ?? 0, 'dungeons'), + tank: getLevelByXp(data.dungeons?.player_classes?.tank ?? 0, 'dungeons') + } + }; +} + +export function getJacobData(data: Record): SkyblockMemberJacobData { + if (!data.jacobs_contest) { + return { + medals: { + bronze: 0, + silver: 0, + gold: 0 + }, + perks: { + doubleDrops: 0, + farmingLevelCap: 0, + personalBests: false + }, + contests: {} + }; + } + return { + medals: data.jacobs_contest.medals_inv + ? { + bronze: data.jacobs_contest.medals_inv.bronze || 0, + silver: data.jacobs_contest.medals_inv.silver || 0, + gold: data.jacobs_contest.medals_inv.gold || 0 + } + : { bronze: 0, silver: 0, gold: 0 }, + perks: data.jacobs_contest.perks + ? { + doubleDrops: data.jacobs_contest.perks.double_drops || 0, + farmingLevelCap: data.jacobs_contest.perks.farming_level_cap || 0, + personalBests: data.jacobs_contest.perks.personal_bests || false + } + : { doubleDrops: 0, farmingLevelCap: 0, personalBests: false }, + contests: data.jacobs_contest.contests || {} + }; +} + +export function getChocolateFactory(data: Record): SkyblockMemberChocolateFactoryData { + if (!data?.events?.easter) { + return { + employees: { + bro: 0, + cousin: 0, + sis: 0, + father: 0, + grandma: 0, + dog: 0, + uncle: 0 + }, + chocolate: { + current: 0, + total: 0, + sincePrestige: 0 + }, + timeTower: { + charges: 0, + level: 0 + }, + upgrades: { + click: 0, + multiplier: 0, + rabbitRarity: 0 + }, + goldenClick: { + amount: 0, + year: 0 + }, + barnCapacity: 0, + prestige: 0 + }; + } + return { + employees: { + bro: data?.events?.easter?.employees?.rabbit_bro || 0, + cousin: data?.events?.easter?.employees?.rabbit_cousin || 0, + sis: data?.events?.easter?.employees?.rabbit_sis || 0, + father: data?.events?.easter?.employees?.rabbit_father || 0, + grandma: data?.events?.easter?.employees?.rabbit_grandma || 0, + dog: data?.events?.easter?.employees?.rabbit_dog || 0, + uncle: data?.events?.easter?.employees?.rabbit_uncle || 0 + }, + chocolate: { + current: data?.events?.easter?.chocolate || 0, + total: data?.events?.easter?.total_chocolate || 0, + sincePrestige: data?.events?.easter?.chocolate_since_prestige || 0 + }, + timeTower: { + charges: data?.events?.easter?.time_tower?.charges || 0, + level: data?.events?.easter?.time_tower?.level || 0 + }, + upgrades: { + click: data?.events?.easter?.click_upgrades || 0, + multiplier: data?.events?.easter?.chocolate_multiplier_upgrades || 0, + rabbitRarity: data?.events?.easter?.rabbit_rarity_upgrades || 0 + }, + goldenClick: { + amount: data?.events?.easter?.golden_click_amount || 0, + year: data?.events?.easter?.golden_click_year || 0 + }, + barnCapacity: data?.events?.easter?.rabbit_barn_capacity_level || 0, + prestige: data?.events?.easter?.chocolate_level || 0 + }; +} + +export function getPetLevel(petExp: number, offsetRarity: number, maxLevel: number) { + const rarityOffset = Constants.petRarityOffset[offsetRarity as unknown as keyof typeof Constants.petRarityOffset]; + const levels = Constants.petLevels.slice(rarityOffset, rarityOffset + maxLevel - 1); + + const xpMaxLevel = levels.reduce((a, b) => a + b, 0); + let xpTotal = 0; + let level = 1; + + let xpForNext; + + for (let i = 0; i < maxLevel; i++) { + xpTotal += levels[i]; + + if (xpTotal > petExp) { + xpTotal -= levels[i]; + break; + } else { + level++; + } + } + + let xpCurrent = Math.floor(petExp - xpTotal); + let progress; + + if (level < maxLevel) { + xpForNext = Math.ceil(levels[level - 1]); + progress = Math.max(0, Math.min(xpCurrent / xpForNext, 1)); + } else { + level = maxLevel; + xpCurrent = petExp - levels[maxLevel - 1]; + xpForNext = 0; + progress = 1; + } + + return { + level, + xpCurrent, + xpForNext, + progress, + xpMaxLevel + }; +} + +export function parseRarity(str: string): SkyblockRarity { + const rarityArray = [ + 'COMMON', + 'UNCOMMON', + 'RARE', + 'EPIC', + 'LEGENDARY', + 'MYTHIC', + 'DIVINE', + 'SPECIAL', + 'VERY SPECIAL' + ]; + for (const rarity of rarityArray) { + if (str.includes(rarity)) return rarity as SkyblockRarity; + } + return 'COMMON'; +} + +export function parseGearScore(lore: any): number { + for (const line of lore) { + if (line.match(/Gear Score: §[0-9a-f](\d+)/)) return Number(line.match(/Gear Score: §d(\d+)/)[1]); + } + return 0; +} + +export function populateGoals(achieved: any[], all: any) { + const populatedAchieved: any = []; + const unachieved = []; + for (const goal of all) { + if (achieved.find((str: any) => str === goal.name)) populatedAchieved.push(goal); + else unachieved.push(goal); + } + populatedAchieved.unachievedGoals = unachieved; + return populatedAchieved; +} diff --git a/src/utils/arrayTools.ts b/src/utils/arrayTools.ts new file mode 100644 index 00000000..06b0f0da --- /dev/null +++ b/src/utils/arrayTools.ts @@ -0,0 +1,2 @@ +export const isStrArray = (input: string) => Array.isArray(input) || 'string' === typeof input; +export const strToArray = (input: string) => [input].flat(); diff --git a/src/utils/divide.ts b/src/utils/divide.ts new file mode 100644 index 00000000..323819d0 --- /dev/null +++ b/src/utils/divide.ts @@ -0,0 +1,5 @@ +export default function divide(a: number = 0, b: number = 1): number { + const out: number = Number(((a || 0) / (b || 0)).toFixed(2)) || 0; + if (isFinite(out)) return out; + return a; +} diff --git a/src/utils/isGuildID.ts b/src/utils/isGuildID.ts new file mode 100644 index 00000000..d9d523ec --- /dev/null +++ b/src/utils/isGuildID.ts @@ -0,0 +1,3 @@ +export default function isGuildID(id: string): boolean { + return 24 === id.length; +} diff --git a/src/utils/isUUID.ts b/src/utils/isUUID.ts new file mode 100644 index 00000000..fe15a196 --- /dev/null +++ b/src/utils/isUUID.ts @@ -0,0 +1,5 @@ +export default function (uuid: string) { + const regexp = /^[0-9a-f]{32}$/i; + uuid = uuid.replace(/-/g, ''); + return regexp.test(uuid); +} diff --git a/src/utils/oscillation.ts b/src/utils/oscillation.ts new file mode 100644 index 00000000..93a63d3c --- /dev/null +++ b/src/utils/oscillation.ts @@ -0,0 +1,12 @@ +// See https://github.com/HypixelDev/PublicAPI/blob/db26b5fd3b7bb29da14e40e6d211143ec44a4519/Documentation/misc/Oscillation.md +// Month oscillation started in December 2014, so every month that is pair ( odd in js!! ) is month A +// Weekly oscillation started... just refer to the code in the docs +const weeklyOscillationStart = 1417237200000; + +export function monthAB() { + return new Date().getMonth() % 2 ? 'a' : 'b'; +} + +export function weekAB() { + return (Math.abs(new Date().getTime() - weeklyOscillationStart) / 604800000) % 2 ? 'a' : 'b'; +} diff --git a/src/utils/removeSnakeCase.ts b/src/utils/removeSnakeCase.ts new file mode 100644 index 00000000..8c5aabe0 --- /dev/null +++ b/src/utils/removeSnakeCase.ts @@ -0,0 +1,18 @@ +export function validateJSON(obj: any) { + return 'object' === typeof obj && '{' === JSON.stringify(obj)[0]; +} + +export function recursive(obj: any, lowerCase: boolean = false): any { + if (!validateJSON(obj)) return obj; + return Object.keys(obj).reduce( + (pV, cV) => ({ + ...pV, + [(lowerCase ? cV : cV.toLowerCase()).replace(/_[a-z]/gi, (x) => x[1].toUpperCase())]: recursive(obj[cV]) + }), + {} + ); +} + +export function removeSnakeCaseString(str: string): string { + return str.toLowerCase().replace(/_[a-z]/gi, (x) => x[1].toUpperCase()); +} diff --git a/src/utils/rgbToHexColor.ts b/src/utils/rgbToHexColor.ts new file mode 100644 index 00000000..bf127423 --- /dev/null +++ b/src/utils/rgbToHexColor.ts @@ -0,0 +1,8 @@ +export default function (rgb: string): string { + let hexCode = '#'; + for (const num of rgb) { + const hex = Number(num).toString(16); + hexCode += 1 === hex.length ? '0' + hex : hex; + } + return hexCode; +} diff --git a/src/utils/romanize.ts b/src/utils/romanize.ts new file mode 100644 index 00000000..27a0c567 --- /dev/null +++ b/src/utils/romanize.ts @@ -0,0 +1,41 @@ +export default function (num: number): string { + const digits = String(Number(num)).split(''); + const key = [ + '', + 'C', + 'CC', + 'CCC', + 'CD', + 'D', + 'DC', + 'DCC', + 'DCCC', + 'CM', + '', + 'X', + 'XX', + 'XXX', + 'XL', + 'L', + 'LX', + 'LXX', + 'LXXX', + 'XC', + '', + 'I', + 'II', + 'III', + 'IV', + 'V', + 'VI', + 'VII', + 'VIII', + 'IX' + ]; + let roman = ''; + let i = 3; + while (i--) { + roman = (key[Number(digits.pop()) + i * 10] || '') + roman; + } + return Array(Number(digits.join('')) + 1).join('M') + roman; +} diff --git a/src/utils/toUuid.ts b/src/utils/toUuid.ts new file mode 100644 index 00000000..6c479496 --- /dev/null +++ b/src/utils/toUuid.ts @@ -0,0 +1,23 @@ +import fetch from '../Private/uuidCache'; +import Errors from '../Errors'; +import isUUID from './isUUID'; + +export default async function (input: string, cacheTime: number = 600, useThirdPartyAPI: string = ''): Promise { + if (!input) throw new Error(Errors.NO_NICKNAME_UUID); + if ('string' !== typeof input) throw new Error(Errors.UUID_NICKNAME_MUST_BE_A_STRING); + if (isUUID(input)) return input.replace(/-/g, ''); + try { + const customUrl = useThirdPartyAPI ? 'https://api.minetools.eu/uuid/' : useThirdPartyAPI; + const url = useThirdPartyAPI ? `${customUrl}${input}` : `https://mowojang.matdoes.dev/${input}`; + const res = await fetch(url, input, cacheTime); + if (404 === res.status) { + return Promise.reject(new Error(Errors.PLAYER_DOES_NOT_EXIST)); + } else if (200 !== res.status) { + throw new Error('Unknown Error whilst retrieving player information'); + } + if (!res.id) throw new Error(Errors.PLAYER_DOES_NOT_EXIST); + return res.id; + } catch (e) { + throw new Error(Errors.PLAYER_DOES_NOT_EXIST); + } +} diff --git a/src/utils/varInt.ts b/src/utils/varInt.ts new file mode 100644 index 00000000..31f38aa8 --- /dev/null +++ b/src/utils/varInt.ts @@ -0,0 +1,15 @@ +export default function (bytes: any) { + let numRead = 0; + let result = 0; + do { + const read = bytes[numRead]; + const value = read & 0b01111111; + result |= value << (7 * numRead); + numRead++; + if (5 < numRead) { + return NaN; + } + } while (numRead < bytes.length); + + return result; +} diff --git a/tsconfig.json b/tsconfig.json index 3c0e09ad..50c8036c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,7 +6,12 @@ "esModuleInterop": true, "skipLibCheck": true, "outDir": "./build", + "module": "ES2022", "target": "ES2022", "strict": true - } -} + }, + "include": [ + "src", + "API" + ], +} \ No newline at end of file