Files
lilyissillyyy a69708be2c Fix lint
2025-08-17 01:12:08 -04:00

368 lines
14 KiB
JavaScript

const request = require('node-superfetch');
const { createCanvas } = require('@napi-rs/canvas');
const path = require('path');
const { firstUpperCase, delay } = require('../../util/Util');
const { cropToContent } = require('../../util/Canvas');
const missingno = require('../../assets/json/missingno');
const versions = require('../../assets/json/pokedex-location');
module.exports = class Pokemon {
constructor(store, data) {
this.store = store;
this.id = data.id;
const slugName = firstUpperCase(data.name).replaceAll('-', ' ');
this.name = data.names.length
? data.names.find(entry => entry.language.name === 'en').name
: slugName;
this.entries = data.flavor_text_entries
.filter(entry => entry.language.name === 'en')
.map(entry => {
const text = entry.flavor_text.replace(/\n|\f|\r/g, ' ');
return {
text,
version: versions[entry.version.name]
};
});
this.names = data.names.length
? data.names.map(entry => ({ name: entry.name, language: entry.language.name }))
: [{ name: slugName, language: 'en' }];
this.genus = data.genera.length
? `The ${data.genera.filter(entry => entry.language.name === 'en')[0].genus}`
: 'Undiscovered Pokémon';
this.genderRate = {
male: data.gender_rate === -1 ? 0 : 100 - ((data.gender_rate / 8) * 100),
female: data.gender_rate === -1 ? 0 : (data.gender_rate / 8) * 100,
genderless: data.gender_rate === -1
};
this.legendary = data.is_legendary;
this.mythical = data.is_mythical;
this.baby = data.is_baby;
this.heldItems = data.missingno ? data.held_items : [];
this.varieties = data.varieties.map(variety => {
const name = firstUpperCase(variety.pokemon.name
.replace(new RegExp(`${this.slug}-?`, 'i'), '')
.replaceAll('-', ' '));
return {
id: variety.pokemon.name,
name: name || null,
mega: data.missingno ? false : null,
stats: data.missingno ? variety.stats : {},
statsDiffer: data.missingno ? true : null,
default: variety.is_default,
types: data.missingno ? variety.types : [],
abilities: data.missingno ? variety.abilities : [],
gameDataCached: data.missingno || false
};
});
this.chain = {
url: data.evolution_chain ? data.evolution_chain.url : null,
data: data.missingno ? missingno.chain : data.evolution_chain ? [] : [data.id]
};
this.encountersURL = null;
this.encounters = data.missingno ? data.encounters : null;
this.height = data.missingno ? data.height : null;
this.weight = data.missingno ? data.weight : null;
this.moveSet = data.missingno ? data.moveSet : [];
this.moveSetVersion = data.missingno ? data.moveSetVersion : null;
this.gameDataCached = data.missingno || false;
this.gameDataFetching = false;
this.missingno = data.missingno || false;
this.cry = data.id > store.pokemonCountWithCry
? null
: path.join(__dirname, '..', '..', 'assets', 'sounds', 'pokedex', `${data.id}.wav`);
this.smogonTiers = data.missingno ? data.smogonTiers : {};
}
baseStatTotal(variety) {
const found = this.varieties.find(vrity => variety ? vrity.id === variety.toLowerCase() : vrity.default);
if (!found) return null;
return found.stats.hp + found.stats.atk + found.stats.def + found.stats.sAtk + found.stats.sDef + found.stats.spd;
}
get pseudo() {
if (!this.gameDataCached) return null;
if (this.legendary || this.mythical || this.baby || this.missingno) return false;
if (this.baseStatTotal() !== 600) return false;
if (this.chain.data.length !== 3) return false;
return true;
}
get generation() {
if (this.id > 1025) return null;
if (this.id >= 906) return 9;
if (this.id >= 810) return 8;
if (this.id >= 722) return 7;
if (this.id >= 650) return 6;
if (this.id >= 494) return 5;
if (this.id >= 387) return 4;
if (this.id >= 252) return 3;
if (this.id >= 152) return 2;
if (this.id >= 0) return 1;
return null;
}
get class() {
if (this.legendary) return 'legendary';
if (this.mythical) return 'mythical';
if (this.baby) return 'baby';
if (this.ultraBeast) return 'ultra beast';
if (this.missingno) return 'glitch';
if (this.pseudo) return 'pseudo';
return 'normal';
}
get ultraBeast() {
if (!this.gameDataCached) return null;
return this.varieties.some(variety => variety.abilities.some(ability => ability.slug === 'beast-boost'));
}
get mega() {
if (!this.gameDataCached) return null;
return this.varieties.some(variety => variety.mega);
}
get displayID() {
if (this.missingno) return '???';
return this.id.toString().padStart(3, '0');
}
get slug() {
return this.store.makeSlug(this.name);
}
get spriteImageURL() {
if (this.missingno) return missingno.sprites.default;
return `https://serebii.net/pokemon/art/${this.displayID}.png`;
}
formSpriteImageURL(variety) {
if (this.missingno) {
if (variety.toLowerCase() === 'missingno-yellow') return missingno.sprites.yellow;
return missingno.sprites.default;
}
const found = this.varieties.find(vrity => variety ? vrity.id === variety.toLowerCase() : vrity.default);
const name = found.default ? '' : found.mega
? found.name.toLowerCase().split(' ').map(n => n.charAt(0)).join('')
: found.name.toLowerCase().charAt(0);
return `https://serebii.net/pokemon/art/${this.displayID}${name ? `-${name}` : ''}.png`;
}
get serebiiURL() {
if (this.missingno) return missingno.url;
return `https://www.serebii.net/pokedex-sv/${this.displayID}.shtml`;
}
smogonURL(gen) {
if (this.missingno) return missingno.url;
return `https://www.smogon.com/dex/${gen.toLowerCase()}/pokemon/${this.slug}/`;
}
async generateBoxImage() {
if (!this.store.sprites) await this.store.loadSprites();
const canvas = createCanvas(40, 30);
const ctx = canvas.getContext('2d');
const x = 40 * (this.id % 12);
const y = Math.floor(this.id / 12) * 30;
ctx.drawImage(this.store.sprites, x, y, 40, 30, 0, 0, 40, 30);
cropToContent(ctx, canvas, canvas.width, canvas.height);
return canvas.toBuffer('image/png');
}
async fetchSmogonTiers(...gens) {
for (const gen of gens) {
if (!this.store.smogonData[gen.toLowerCase()]) await this.store.fetchSmogonData(gen.toLowerCase());
const pkmn = this.store.smogonData[gen.toLowerCase()].find(data => data.id === this.id);
this.smogonTiers[gen.toLowerCase()] = pkmn.formats;
}
return this.smogonTiers;
}
async fetchGameData() {
if (this.gameDataCached) return this;
if (this.gameDataFetching) {
await delay(1000);
return this.fetchGameData();
}
this.gameDataFetching = true;
await this.fetchDefaultVariety();
await this.fetchHeldItemNames();
await this.fetchOtherVarieties();
await this.fetchChain();
this.gameDataFetching = false;
this.gameDataCached = true;
return this;
}
async fetchDefaultVariety() {
const defaultVariety = this.varieties.find(variety => variety.default);
if (defaultVariety.gameDataCached) return this;
const { body: defaultBody } = await request.get(`https://pokeapi.co/api/v2/pokemon/${defaultVariety.id}`);
defaultVariety.types.push(...defaultBody.types.map(type => firstUpperCase(type.type.name)));
for (const ability of defaultBody.abilities) {
const defaultAbilityData = await this.store.abilities.fetch(ability.ability.name);
defaultVariety.abilities.push(defaultAbilityData);
}
defaultVariety.stats = {
hp: defaultBody.stats.find(stat => stat.stat.name === 'hp').base_stat,
atk: defaultBody.stats.find(stat => stat.stat.name === 'attack').base_stat,
def: defaultBody.stats.find(stat => stat.stat.name === 'defense').base_stat,
sAtk: defaultBody.stats.find(stat => stat.stat.name === 'special-attack').base_stat,
sDef: defaultBody.stats.find(stat => stat.stat.name === 'special-defense').base_stat,
spd: defaultBody.stats.find(stat => stat.stat.name === 'speed').base_stat
};
defaultVariety.statsDiffer = true;
defaultVariety.gameDataCached = true;
const inSwordShield = defaultBody.moves
.some(move => move.version_group_details.some(mve => mve.version_group.name === 'sword-shield'));
const inScarletViolet = defaultBody.moves
.some(move => move.version_group_details.some(mve => mve.version_group.name === 'scarlet-violet'));
this.moveSetVersion = inScarletViolet ? 'scarlet-violet' : inSwordShield ? 'sword-shield' : 'ultra-sun-ultra-moon';
this.height = defaultBody.height * 3.94;
this.weight = defaultBody.weight * 0.2205;
this.encountersURL = defaultBody.location_area_encounters;
await this.fetchMoves(defaultBody.moves);
await this.fetchHeldItems(defaultBody.held_items);
return this;
}
async fetchOtherVarieties() {
const defaultVariety = this.varieties.find(variety => variety.default);
for (const variety of this.varieties) {
if (variety.id === defaultVariety.id) continue;
if (variety.gameDataCached) continue;
const { body } = await request.get(`https://pokeapi.co/api/v2/pokemon/${variety.id}`);
const { body: formBody } = await request.get(`https://pokeapi.co/api/v2/pokemon-form/${variety.id}`);
variety.types.push(...body.types.map(type => firstUpperCase(type.type.name)));
variety.mega = formBody.is_mega || false;
variety.stats = {
hp: body.stats.find(stat => stat.stat.name === 'hp').base_stat,
atk: body.stats.find(stat => stat.stat.name === 'attack').base_stat,
def: body.stats.find(stat => stat.stat.name === 'defense').base_stat,
sAtk: body.stats.find(stat => stat.stat.name === 'special-attack').base_stat,
sDef: body.stats.find(stat => stat.stat.name === 'special-defense').base_stat,
spd: body.stats.find(stat => stat.stat.name === 'speed').base_stat
};
const baseStats = defaultVariety.stats;
variety.statsDiffer = !(baseStats.hp === variety.stats.hp
&& baseStats.atk === variety.stats.atk
&& baseStats.def === variety.stats.def
&& baseStats.sAtk === variety.stats.sAtk
&& baseStats.sDef === variety.stats.sDef
&& baseStats.spd === variety.stats.spd);
for (const ability of body.abilities) {
const abilityData = await this.store.abilities.fetch(ability.ability.name);
variety.abilities.push(abilityData);
}
variety.gameDataCached = true;
}
return this.varieties;
}
async fetchMoves(moves) {
for (const move of moves) {
const versionGroup = move.version_group_details.find(mve => mve.version_group.name === this.moveSetVersion);
if (!versionGroup || !versionGroup.level_learned_at) continue;
const moveData = await this.store.moves.fetch(move.move.name);
if (this.moveSet.some(mve => mve.move.id === moveData.id)) continue;
this.moveSet.push({
move: moveData,
level: versionGroup.level_learned_at
});
}
this.moveSet = this.moveSet.sort((a, b) => a.level - b.level);
return this.moveSet;
}
fetchHeldItems(heldItems) {
this.heldItems = heldItems
.filter(item => item.version_details.some(version => {
const inScarletViolet = version.version.name === 'scarlet' || version.version.name === 'violet';
const inSwordShield = version.version.name === 'sword' || version.version.name === 'shield';
const inUsUm = version.version.name === 'ultra-sun' || version.version.name === 'ultra-moon';
if (inScarletViolet) return true;
if (inSwordShield) return true;
if (!inSwordShield && !inScarletViolet && inUsUm) return true;
return false;
}))
.map(item => {
const inScarletViolet = item.version_details
.some(version => version.version.name === 'scarlet' || version.version.name === 'violet');
const inSwordShield = item.version_details
.some(version => version.version.name === 'sword' || version.version.name === 'shield');
const { rarity } = item.version_details
.find(version => {
if (inScarletViolet) return true;
if (inSwordShield) return true;
const sunMoon = version.version.name === 'ultra-sun' || version.version.name === 'ultra-moon';
if (!inSwordShield && !inScarletViolet && sunMoon) return true;
return false;
});
return {
data: null,
slug: item.item.name,
rarity
};
});
return this.heldItems;
}
async fetchChain() {
if (this.chain.data.length) return this.chain.data;
const { body } = await request.get(this.chain.url);
const basePkmn = await this.store.fetch(body.chain.species.name);
this.chain.data.push(basePkmn.id);
if (body.chain.evolves_to.length) {
const evolution1 = body.chain.evolves_to;
if (!evolution1) return this.chain.data;
const evos1 = [];
const evos2 = [];
for (const evolution of evolution1) {
const pkmn = await this.store.fetch(evolution.species.name);
evos1.push(pkmn.id);
if (evolution.evolves_to) {
for (const evolution2 of evolution.evolves_to) {
const pkmn2 = await this.store.fetch(evolution2.species.name);
evos2.push(pkmn2.id);
}
}
}
this.chain.data.push(evos1.length === 1 ? evos1[0] : evos1);
if (evos2.length) this.chain.data.push(evos2.length === 1 ? evos2[0] : evos2);
}
return this.chain.data;
}
async fetchHeldItemNames() {
for (const item of this.heldItems) {
const data = await this.store.items.fetch(item.slug);
item.data = data;
}
return this.heldItems;
}
async fetchEncounters() {
if (!this.encountersURL) return null;
if (this.encounters) return this.encounters;
const { body } = await request.get(this.encountersURL);
if (!body.length) {
this.encounters = body;
return body;
}
this.encounters = [];
for (const encounter of body) {
if (!encounter.version_details.some(version => versions[version.version.name])) continue;
const { body: encounterBody } = await request.get(encounter.location_area.url);
const { body: locationBody } = await request.get(encounterBody.location.url);
let name = locationBody.names.find(nm => nm.language?.name === 'en')?.name;
if (!name) name = firstUpperCase(locationBody.name.replace(/-/g, ' '));
this.encounters.push({
name,
versions: encounter.version_details
.filter(version => versions[version.version.name])
.map(version => version.version.name)
});
}
return this.encounters;
}
};