From d20f1fbb6429ae8de5d3b34db1ba2fd1cd352e54 Mon Sep 17 00:00:00 2001 From: Kris Johnson <11083252+KrisXV@users.noreply.github.com> Date: Mon, 11 May 2020 04:08:33 -0600 Subject: [PATCH] Typescript info (#6670) --- server/chat-commands/{info.js => info.ts} | 666 ++++++++++++---------- sim/dex-data.ts | 5 + sim/dex.ts | 4 +- sim/global-types.ts | 6 +- 4 files changed, 385 insertions(+), 296 deletions(-) rename server/chat-commands/{info.js => info.ts} (81%) diff --git a/server/chat-commands/info.js b/server/chat-commands/info.ts similarity index 81% rename from server/chat-commands/info.js rename to server/chat-commands/info.ts index ba53799948..31139ff993 100644 --- a/server/chat-commands/info.js +++ b/server/chat-commands/info.ts @@ -8,14 +8,9 @@ * * @license MIT */ +import * as net from 'net'; -'use strict'; - -const net = require('net'); - -/** @type {ChatCommands} */ -const commands = { - +export const commands: ChatCommands = { '!whois': true, ip: 'whois', rooms: 'whois', @@ -23,8 +18,9 @@ const commands = { alts: 'whois', whoare: 'whois', whois(target, room, user, connection, cmd) { - if (room && room.roomid === 'staff' && !this.runBroadcast()) return; - if (!room) room = Rooms.global; + let usedRoom: ChatRoom | GameRoom | GlobalRoom = room; + if (usedRoom?.roomid === 'staff' && !this.runBroadcast()) return; + if (!usedRoom) usedRoom = Rooms.global; const targetUser = this.targetUserOrSelf(target, user.group === ' '); const showAll = (cmd === 'ip' || cmd === 'whoare' || cmd === 'alt' || cmd === 'alts'); if (!targetUser) { @@ -37,14 +33,16 @@ const commands = { let buf = Chat.html`${targetUser.group}${targetUser.name} `; const ac = targetUser.autoconfirmed; - if (ac && showAll) buf += ` (ac${targetUser.id === ac ? `` : `: ${ac}`})`; + if (ac && showAll) { + buf += ` (ac${targetUser.id === ac ? `` : `: ${ac}`})`; + } const trusted = targetUser.trusted; if (trusted && showAll) { buf += ` (trusted${targetUser.id === trusted ? `` : `: ${trusted}`})`; } if (!targetUser.connected) buf += ` (offline)`; let roomauth = ''; - if (room.auth && targetUser.id in room.auth) roomauth = room.auth[targetUser.id]; + if (usedRoom.auth && targetUser.id in usedRoom.auth) roomauth = usedRoom.auth[targetUser.id]; if (Config.groups[roomauth] && Config.groups[roomauth].name) { buf += `
${Config.groups[roomauth].name} (${roomauth})`; } @@ -57,42 +55,43 @@ const commands = { if (!targetUser.registered) { buf += `
(Unregistered)`; } - let publicrooms = ""; - let hiddenrooms = ""; - let privaterooms = ""; + let publicrooms = ``; + let hiddenrooms = ``; + let privaterooms = ``; for (const roomid of targetUser.inRooms) { if (roomid === 'global') continue; - const targetRoom = Rooms.get(roomid); + const targetRoom = Rooms.get(roomid)!; const authSymbol = (targetRoom.auth && targetRoom.auth[targetUser.id] ? targetRoom.auth[targetUser.id] : ''); - const battleTitle = (roomid.battle ? ` title="${roomid.title}"` : ''); + const battleTitle = (targetRoom.battle ? ` title="${targetRoom.title}"` : ''); const output = `${authSymbol}${roomid}`; if (targetRoom.isPrivate === true) { if (targetRoom.modjoin === '~') continue; - if (privaterooms) privaterooms += " | "; + if (privaterooms) privaterooms += ` | `; privaterooms += output; } else if (targetRoom.isPrivate) { - if (hiddenrooms) hiddenrooms += " | "; + if (hiddenrooms) hiddenrooms += ` | `; hiddenrooms += output; } else { - if (publicrooms) publicrooms += " | "; + if (publicrooms) publicrooms += ` | `; publicrooms += output; } } - buf += '
Rooms: ' + (publicrooms || '(no public rooms)'); + buf += `
Rooms: ${publicrooms || `(no public rooms)`}`; if (!showAll) { return this.sendReplyBox(buf); } const canViewAlts = (user === targetUser || user.can('alts', targetUser)); - const canViewPunishments = canViewAlts || (room.isPrivate !== true && user.can('mute', targetUser, room) && targetUser.id in room.users); + const canViewPunishments = canViewAlts || + (usedRoom.isPrivate !== true && user.can('mute', targetUser, usedRoom) && targetUser.id in usedRoom.users); const canViewSecretRooms = user === targetUser || (canViewAlts && targetUser.locked) || user.can('makeroom'); - buf += '
'; + buf += `
`; if (canViewAlts) { let prevNames = Object.keys(targetUser.prevNames).map(userid => { const punishment = Punishments.userids.get(userid); - return userid + (punishment ? ` (${Punishments.punishmentTypes.get(punishment[0]) || 'punished'}${punishment[1] !== targetUser.id ? ` as ${punishment[1]}` : ''})` : ''); + return `${userid}${punishment ? ` (${Punishments.punishmentTypes.get(punishment[0]) || `punished`}${punishment[1] !== targetUser.id ? ` as ${punishment[1]}` : ``})` : ``}`; }).join(", "); if (prevNames) buf += Chat.html`
Previous names: ${prevNames}`; @@ -101,12 +100,13 @@ const commands = { if (targetAlt.group === '~' && user.group !== '~') continue; const punishment = Punishments.userids.get(targetAlt.id); - const punishMsg = punishment ? ` (${Punishments.punishmentTypes.get(punishment[0]) || 'punished'}${punishment[1] !== targetAlt.id ? ` as ${punishment[1]}` : ''})` : ''; + const punishMsg = punishment ? ` (${Punishments.punishmentTypes.get(punishment[0]) || 'punished'}` + + `${punishment[1] !== targetAlt.id ? ` as ${punishment[1]}` : ''})` : ''; buf += Chat.html`
Alt: ${targetAlt.name}${punishMsg}`; if (!targetAlt.connected) buf += ` (offline)`; prevNames = Object.keys(targetAlt.prevNames).map(userid => { - const punishment = Punishments.userids.get(userid); - return userid + (punishment ? ` (${Punishments.punishmentTypes.get(punishment[0]) || 'punished'}${punishment[1] !== targetAlt.id ? ` as ${punishment[1]}` : ''})` : ''); + const p = Punishments.userids.get(userid); + return `${userid}${p ? ` (${Punishments.punishmentTypes.get(p[0]) || 'punished'}${p[1] !== targetAlt.id ? ` as ${p[1]}` : ``})` : ``}`; }).join(", "); if (prevNames) buf += `
Previous names: ${prevNames}`; } @@ -114,7 +114,7 @@ const commands = { if (canViewPunishments) { if (targetUser.namelocked) { buf += `
NAMELOCKED: ${targetUser.namelocked}`; - const punishment = Punishments.userids.get(targetUser.locked); + const punishment = Punishments.userids.get(targetUser.locked!); if (punishment) { const expiresIn = Punishments.checkLockExpiration(targetUser.locked); if (expiresIn) buf += expiresIn; @@ -142,7 +142,7 @@ const commands = { buf += `
BATTLEBANNED: ${battlebanned[1]}`; const expiresIn = new Date(battlebanned[2]).getTime() - Date.now(); const expiresDays = Math.round(expiresIn / 1000 / 60 / 60 / 24); - let expiresText = ''; + let expiresText = ``; if (expiresDays >= 1) { expiresText = `in around ${Chat.count(expiresDays, "days")}`; } else { @@ -190,21 +190,21 @@ const commands = { } const gameRooms = []; - for (const room of Rooms.rooms.values()) { - if (!room.game) continue; - if ((targetUser.id in room.game.playerTable && !targetUser.inRooms.has(room.roomid)) || - (room.auth && room.auth[targetUser.id] === Users.PLAYER_SYMBOL)) { - if (room.isPrivate && !canViewAlts) { + for (const curRoom of Rooms.rooms.values()) { + if (!curRoom.game) continue; + if ((targetUser.id in curRoom.game.playerTable && !targetUser.inRooms.has(curRoom.roomid)) || + (curRoom.auth && curRoom.auth[targetUser.id] === Users.PLAYER_SYMBOL)) { + if (curRoom.isPrivate && !canViewAlts) { continue; } - gameRooms.push(room.roomid); + gameRooms.push(curRoom.roomid); } } if (gameRooms.length) { - buf += '
Recent games: ' + gameRooms.map(id => { + buf += `
Recent games: ${gameRooms.map(id => { const shortId = id.startsWith('battle-') ? id.slice(7) : id; return Chat.html`${shortId}`; - }).join(' | '); + }).join(' | ')}`; } if (canViewPunishments) { @@ -213,8 +213,8 @@ const commands = { if (punishments.length) { buf += `
Room punishments: `; - buf += punishments.map(([room, punishment]) => { - const [punishType, punishUserid, expireTime, reason] = punishment; + buf += punishments.map(([curRoom, curPunishment]) => { + const [punishType, punishUserid, expireTime, reason] = curPunishment; let punishDesc = Punishments.roomPunishmentTypes.get(punishType); if (!punishDesc) punishDesc = `punished`; if (punishUserid !== targetUser.id) punishDesc += ` as ${punishUserid}`; @@ -223,7 +223,7 @@ const commands = { punishDesc += ` for ${expireString}`; if (reason) punishDesc += `: ${reason}`; - return `${room} (${punishDesc})`; + return `${curRoom} (${punishDesc})`; }).join(', '); } } @@ -248,7 +248,7 @@ const commands = { if (!targetUser || !targetUser.connected) buf += ` (offline)`; let roomauth = ''; - if (room && room.auth && userid in room.auth) roomauth = room.auth[userid]; + if (room?.auth && userid in room.auth) roomauth = room.auth[userid]; if (Config.groups[roomauth] && Config.groups[roomauth].name) { buf += `
${Config.groups[roomauth].name} (${roomauth})`; } @@ -279,13 +279,13 @@ const commands = { } } - const punishments = Punishments.getRoomPunishments(targetUser || {id: userid}); + const punishments = Punishments.getRoomPunishments(targetUser || {id: userid} as User); - if (punishments && punishments.length) { + if (punishments?.length) { buf += `
Room punishments: `; - buf += punishments.map(([room, punishment]) => { - const [punishType, punishUserid, expireTime, reason] = punishment; + buf += punishments.map(([curRoom, curPunishment]) => { + const [punishType, punishUserid, expireTime, reason] = curPunishment; let punishDesc = Punishments.roomPunishmentTypes.get(punishType); if (!punishDesc) punishDesc = `punished`; if (punishUserid !== userid) punishDesc += ` as ${punishUserid}`; @@ -294,7 +294,7 @@ const commands = { punishDesc += ` for ${expireString}`; if (reason) punishDesc += `: ${reason}`; - return `${room} (${punishDesc})`; + return `${curRoom} (${punishDesc})`; }).join(', '); atLeastOne = true; } @@ -315,11 +315,11 @@ const commands = { const userID2 = toID(targetUsername2); const battles = []; - for (const room of Rooms.rooms.values()) { - if (!room.battle) continue; - if ((user1 && user1.inRooms.has(room.roomid) || (room.auth && room.auth[userID1])) && - (user2 && user2.inRooms.has(room.roomid) || (room.auth && room.auth[userID2]))) { - battles.push(room.roomid); + for (const curRoom of Rooms.rooms.values()) { + if (!curRoom.battle) continue; + if ((user1?.inRooms.has(curRoom.roomid) || (curRoom.auth && curRoom.auth[userID1])) && + (user2?.inRooms.has(curRoom.roomid) || (curRoom.auth && curRoom.auth[userID2]))) { + battles.push(curRoom.roomid); } } @@ -334,7 +334,9 @@ const commands = { sp: 'showpunishments', showpunishments(target, room, user) { - if (!room.chatRoomData || room.roomid.includes('-')) return this.errorReply("This command is unavailable in temporary rooms."); + if (!room.chatRoomData || room.roomid.includes('-')) { + return this.errorReply("This command is unavailable in temporary rooms."); + } return this.parse(`/join view-punishments-${room}`); }, showpunishmentshelp: [`/showpunishments - Shows the current punishments in the room. Requires: % @ # & ~`], @@ -352,7 +354,7 @@ const commands = { if (!this.can('ip')) return; target = target.trim(); if (!net.isIPv4(target)) return this.errorReply('You must pass a valid IPv4 IP to /host.'); - IPTools.lookup(target).then(({dnsbl, host, hostType}) => { + void IPTools.lookup(target).then(({dnsbl, host, hostType}) => { const dnsblMessage = dnsbl ? ` [${dnsbl}]` : ``; this.sendReply(`IP ${target}: ${host || "ERROR"} [${hostType}]${dnsblMessage}`); }); @@ -369,8 +371,10 @@ const commands = { let [ip, roomid] = this.splitOne(target); const targetRoom = roomid ? Rooms.get(roomid) : null; - if (!targetRoom && targetRoom !== null) return this.errorReply(`The room "${roomid}" does not exist.`); - const results = /** @type {string[]} */ ([]); + if (typeof targetRoom === 'undefined') { + return this.errorReply(`The room "${roomid}" does not exist.`); + } + const results: string[] = []; const isAll = (cmd === 'ipsearchall'); if (/[a-z]/.test(ip)) { @@ -380,7 +384,7 @@ const commands = { if (results.length > 100 && !isAll) continue; if (!curUser.latestHost || !curUser.latestHost.endsWith(ip)) continue; if (targetRoom && !curUser.inRooms.has(targetRoom.roomid)) continue; - results.push((curUser.connected ? " \u25C9 " : " \u25CC ") + " " + curUser.name); + results.push(`${curUser.connected ? ` \u25C9 ` : ` \u25CC `} ${curUser.name}`); } if (results.length > 100 && !isAll) { return this.sendReply(`More than 100 users match the specified IP range. Use /ipsearchall to retrieve the full list.`); @@ -393,7 +397,7 @@ const commands = { if (results.length > 100 && !isAll) continue; if (!curUser.latestIp.startsWith(ip)) continue; if (targetRoom && !curUser.inRooms.has(targetRoom.roomid)) continue; - results.push((curUser.connected ? " \u25C9 " : " \u25CC ") + " " + curUser.name); + results.push(`${curUser.connected ? ` \u25C9 ` : ` \u25CC `} ${curUser.name}`); } if (results.length > 100 && !isAll) { return this.sendReply(`More than 100 users match the specified IP range. Use /ipsearchall to retrieve the full list.`); @@ -403,7 +407,7 @@ const commands = { for (const curUser of Users.users.values()) { if (curUser.latestIp !== ip) continue; if (targetRoom && !curUser.inRooms.has(targetRoom.roomid)) continue; - results.push((curUser.connected ? " \u25C9 " : " \u25CC ") + " " + curUser.name); + results.push(`${curUser.connected ? ` \u25C9 ` : ` \u25CC `} ${curUser.name}`); } } if (!results.length) { @@ -425,7 +429,7 @@ const commands = { const user1 = this.targetUser; const user2 = Users.get(target); if (!user1 || !user2 || user1 === user2) return this.parse(`/help checkchallenges`); - if (!(user1 in room.users) || !(user2 in room.users)) { + if (!(user1.id in room.users) || !(user2.id in room.users)) { return this.errorReply(`Both users must be in this room.`); } const challenges = []; @@ -460,7 +464,9 @@ const commands = { unignore: 'ignore', ignore(target, room, user) { - if (!room) this.errorReply(`In PMs, this command can only be used by itself to ignore the person you're talking to: "/${this.cmd}", not "/${this.cmd} ${target}"`); + if (!room) { + this.errorReply(`In PMs, this command can only be used by itself to ignore the person you're talking to: "/${this.cmd}", not "/${this.cmd} ${target}"`); + } this.errorReply(`You're using a custom client that doesn't support the ignore command.`); }, @@ -483,7 +489,7 @@ const commands = { const targetId = toID(target); if (!targetId) return this.parse('/help data'); const targetNum = parseInt(target); - if (!isNaN(targetNum) && '' + targetNum === target) { + if (!isNaN(targetNum) && `${targetNum}` === target) { for (const p in Dex.data.Pokedex) { const pokemon = Dex.getSpecies(p); if (pokemon.num === targetNum) { @@ -493,8 +499,7 @@ const commands = { } } let dex = Dex; - /** @type {Format?} */ - let format = null; + let format: Format | null = null; if (sep[1] && toID(sep[1]) in Dex.dexes) { dex = Dex.mod(toID(sep[1])); } else if (sep[1]) { @@ -503,7 +508,7 @@ const commands = { return this.errorReply(`Unrecognized format or mod "${format.name}"`); } dex = Dex.mod(format.mod); - } else if (room && room.battle) { + } else if (room?.battle) { format = Dex.getFormat(room.battle.format); dex = Dex.mod(format.mod); } @@ -517,30 +522,34 @@ const commands = { if (newTarget.isInexact && !i) { buffer = `No Pok\u00e9mon, item, move, ability or nature named '${target}' was found${Dex.gen > dex.gen ? ` in Gen ${dex.gen}` : ""}. Showing the data of '${newTargets[0].name}' instead.\n`; } - /** @type {AnyObject} */ - let details = null; + let details: {[k: string]: string} = {}; switch (newTarget.searchType) { case 'nature': const nature = Dex.getNature(newTarget.name); - buffer += "" + nature.name + " nature: "; + buffer += `${nature.name} nature: `; if (nature.plus) { - const statNames = {'atk': "Attack", 'def': "Defense", 'spa': "Special Attack", 'spd': "Special Defense", 'spe': "Speed"}; - buffer += "+10% " + statNames[nature.plus] + ", -10% " + statNames[nature.minus] + "."; + const statNames = { + atk: "Attack", def: "Defense", spa: "Special Attack", spd: "Special Defense", spe: "Speed", + }; + buffer += `+10% ${statNames[nature.plus]}, -10% ${statNames[nature.minus!]}.`; } else { - buffer += "No effect."; + buffer += `No effect.`; } return this.sendReply(buffer); case 'pokemon': let pokemon = dex.getSpecies(newTarget.name); - if (format && format.onModifySpecies) { - pokemon = format.onModifySpecies.call({dex}, pokemon) || pokemon; + if (format?.onModifySpecies) { + pokemon = format.onModifySpecies.call({dex} as Battle, pokemon) || pokemon; } - let tier = pokemon.tier; - if (room && (room.roomid === 'smogondoubles' || - ['gen7doublesou', 'gen7doublesubers', 'gen7doublesuu'].includes(room.battle && room.battle.format))) { - tier = pokemon.doublesTier; + let displayedTier = pokemon.tier; + if (room?.battle) { + if (room.battle.format.includes('doubles') || room.battle.format.includes('vgc')) { + displayedTier = pokemon.doublesTier; + } else if (room.battle.format.includes('nationaldex')) { + displayedTier = pokemon.num >= 0 ? String(pokemon.num) : pokemon.tier; + } } - buffer += `|raw|${Chat.getDataPokemonHTML(pokemon, dex.gen, tier)}\n`; + buffer += `|raw|${Chat.getDataPokemonHTML(pokemon, dex.gen, displayedTier)}\n`; if (showDetails) { let weighthit = 20; if (pokemon.weighthg >= 2000) { @@ -555,16 +564,19 @@ const commands = { weighthit = 40; } details = { - "Dex#": pokemon.num, - "Gen": pokemon.gen || 'CAP', - "Height": pokemon.heightm + " m", + "Dex#": String(pokemon.num), + Gen: String(pokemon.gen) || 'CAP', + Height: `${pokemon.heightm} m`, }; - if (!pokemon.forme || pokemon.forme !== "Gmax") details["Weight"] = pokemon.weighthg / 10 + " kg (" + weighthit + " BP)"; - else details["Weight"] = "0 kg (GK/LK fail)"; + if (!pokemon.forme || pokemon.forme !== "Gmax") { + details["Weight"] = `${pokemon.weighthg / 10} kg (${weighthit} BP)`; + } else { + details["Weight"] = "0 kg (GK/LK fail)"; + } if (pokemon.isGigantamax) details["G-Max Move"] = pokemon.isGigantamax; if (pokemon.color && dex.gen >= 5) details["Dex Colour"] = pokemon.color; if (pokemon.eggGroups && dex.gen >= 2) details["Egg Group(s)"] = pokemon.eggGroups.join(", "); - const evos = /** @type {string[]} */ ([]); + const evos: string[] = []; for (const evoName of pokemon.evos) { const evo = dex.getSpecies(evoName); if (evo.gen <= dex.gen) { @@ -597,7 +609,7 @@ const commands = { } } if (!evos.length) { - details['Does Not Evolve'] = ""; + details[`Does Not Evolve`] = ""; } else { details["Evolution"] = evos.join(", "); } @@ -608,27 +620,30 @@ const commands = { buffer += `|raw|${Chat.getDataItemHTML(item)}\n`; if (showDetails) { details = { - "Gen": item.gen, + Gen: String(item.gen), }; if (dex.gen >= 4) { if (item.fling) { - details["Fling Base Power"] = item.fling.basePower; + details["Fling Base Power"] = String(item.fling.basePower); if (item.fling.status) details["Fling Effect"] = item.fling.status; if (item.fling.volatileStatus) details["Fling Effect"] = item.fling.volatileStatus; if (item.isBerry) details["Fling Effect"] = "Activates the Berry's effect on the target."; if (item.id === 'whiteherb') details["Fling Effect"] = "Restores the target's negative stat stages to 0."; - if (item.id === 'mentalherb') details["Fling Effect"] = "Removes the effects of Attract, Disable, Encore, Heal Block, Taunt, and Torment from the target."; + if (item.id === 'mentalherb') { + const flingEffect = "Removes the effects of Attract, Disable, Encore, Heal Block, Taunt, and Torment from the target."; + details["Fling Effect"] = flingEffect; + } } else { details["Fling"] = "This item cannot be used with Fling."; } } if (item.naturalGift && dex.gen >= 3) { details["Natural Gift Type"] = item.naturalGift.type; - details["Natural Gift Base Power"] = item.naturalGift.basePower; + details["Natural Gift Base Power"] = String(item.naturalGift.basePower); } if (item.isNonstandard) { - details["Unobtainable in Gen " + dex.gen] = ""; + details[`Unobtainable in Gen ${dex.gen}`] = ""; } } break; @@ -637,8 +652,8 @@ const commands = { buffer += `|raw|${Chat.getDataMoveHTML(move)}\n`; if (showDetails) { details = { - "Priority": move.priority, - "Gen": move.gen || 'CAP', + Priority: String(move.priority), + Gen: String(move.gen) || 'CAP', }; if (move.isNonstandard === "Past" && dex.gen >= 8) details["✗ Past Gens Only"] = ""; @@ -662,30 +677,35 @@ const commands = { if (dex.gen >= 7) { if (move.gen >= 8 && move.isMax) { // Don't display Z-Power for Max/G-Max moves - } else if (move.zMove && move.zMove.basePower) { - details["Z-Power"] = move.zMove.basePower; - } else if (move.zMove && move.zMove.effect) { - details["Z-Effect"] = { - 'clearnegativeboost': "Restores negative stat stages to 0", - 'crit2': "Crit ratio +2", - 'heal': "Restores HP 100%", - 'curse': "Restores HP 100% if user is Ghost type, otherwise Attack +1", - 'redirect': "Redirects opposing attacks to user", - 'healreplacement': "Restores replacement's HP 100%", - }[move.zMove.effect]; - } else if (move.zMove && move.zMove.boost) { + } else if (move.zMove?.basePower) { + details["Z-Power"] = String(move.zMove.basePower); + } else if (move.zMove?.effect) { + const zEffects: {[k: string]: string} = { + clearnegativeboost: "Restores negative stat stages to 0", + crit2: "Crit ratio +2", + heal: "Restores HP 100%", + curse: "Restores HP 100% if user is Ghost type, otherwise Attack +1", + redirect: "Redirects opposing attacks to user", + healreplacement: "Restores replacement's HP 100%", + }; + details["Z-Effect"] = zEffects[move.zMove.effect]; + } else if (move.zMove?.boost) { details["Z-Effect"] = ""; const boost = move.zMove.boost; - const stats = {atk: 'Attack', def: 'Defense', spa: 'Sp. Atk', spd: 'Sp. Def', spe: 'Speed', accuracy: 'Accuracy', evasion: 'Evasiveness'}; - for (const i in boost) { - details["Z-Effect"] += " " + stats[i] + " +" + boost[i]; + const stats: {[k in BoostName]: string} = { + atk: 'Attack', def: 'Defense', spa: 'Sp. Atk', spd: 'Sp. Def', spe: 'Speed', accuracy: 'Accuracy', evasion: 'Evasiveness', + }; + let h: BoostName; + for (h in boost) { + details["Z-Effect"] += ` ${stats[h]} +${boost[h]}`; } - } else if (move.isZ) { + } else if (move.isZ && typeof move.isZ === 'string') { details["✓ Z-Move"] = ""; - details["Z-Crystal"] = dex.getItem(move.isZ).name; - if (move.basePower !== 1) { - details["User"] = dex.getItem(move.isZ).itemUser.join(", "); - details["Required Move"] = dex.getItem(move.isZ).zMoveFrom; + const zCrystal = dex.getItem(move.isZ); + details["Z-Crystal"] = zCrystal.name; + if (zCrystal.itemUser) { + details["User"] = zCrystal.itemUser.join(", "); + details["Required Move"] = dex.getItem(move.isZ).zMoveFrom!; } } else { details["Z-Effect"] = "None"; @@ -695,26 +715,27 @@ const commands = { if (dex.gen >= 8) { if (move.isMax) { details["✓ Max Move"] = ""; - if (typeof move.isMax === "string") details["User"] = move.isMax + "-Gmax"; - } else if (move.maxMove && move.maxMove.basePower) { - details["Dynamax Power"] = move.maxMove.basePower; + if (typeof move.isMax === "string") details["User"] = `${move.isMax}-Gmax`; + } else if (move.maxMove?.basePower) { + details["Dynamax Power"] = String(move.maxMove.basePower); } } - details["Target"] = { - 'normal': "One Adjacent Pok\u00e9mon", - 'self': "User", - 'adjacentAlly': "One Ally", - 'adjacentAllyOrSelf': "User or Ally", - 'adjacentFoe': "One Adjacent Opposing Pok\u00e9mon", - 'allAdjacentFoes': "All Adjacent Opponents", - 'foeSide': "Opposing Side", - 'allySide': "User's Side", - 'allyTeam': "User's Side", - 'allAdjacent': "All Adjacent Pok\u00e9mon", - 'any': "Any Pok\u00e9mon", - 'all': "All Pok\u00e9mon", - }[move.target] || "Unknown"; + const targetTypes: {[k: string]: string} = { + normal: "One Adjacent Pok\u00e9mon", + self: "User", + adjacentAlly: "One Ally", + adjacentAllyOrSelf: "User or Ally", + adjacentFoe: "One Adjacent Opposing Pok\u00e9mon", + allAdjacentFoes: "All Adjacent Opponents", + foeSide: "Opposing Side", + allySide: "User's Side", + allyTeam: "User's Side", + allAdjacent: "All Adjacent Pok\u00e9mon", + any: "Any Pok\u00e9mon", + all: "All Pok\u00e9mon", + }; + details["Target"] = targetTypes[move.target] || "Unknown"; if (move.id === 'snatch' && dex.gen >= 3) { details[`Non-Snatchable Moves`] = ''; @@ -723,23 +744,28 @@ const commands = { details[`Non-Mirrorable Moves`] = ''; } if (move.isNonstandard === 'Unobtainable') { - details["Unobtainable in Gen " + dex.gen] = ""; + details[`Unobtainable in Gen ${dex.gen}`] = ""; } } break; case 'ability': const ability = dex.getAbility(newTarget.name); buffer += `|raw|${Chat.getDataAbilityHTML(ability)}\n`; + if (showDetails) { + details = { + Gen: String(ability.gen) || 'CAP', + }; + } break; default: throw new Error(`Unrecognized searchType`); } if (details) { - buffer += '|raw|' + Object.keys(details).map(detail => { + buffer += `|raw|${Object.keys(details).map(detail => { if (details[detail] === '') return detail; - return '' + detail + ': ' + details[detail]; - }).join(" |  ") + '\n'; + return `${detail}: ${details[detail]}`; + }).join(" |  ")}\n`; } } this.sendReply(buffer); @@ -772,23 +798,21 @@ const commands = { target = target.trim(); const modName = target.split(','); let mod = Dex; - /** @type {Format?} */ - let format = null; + let format: Format | null = null; if (modName[modName.length - 1] && toID(modName[modName.length - 1]) in Dex.dexes) { mod = Dex.mod(toID(modName[modName.length - 1])); - } else if (room && room.battle) { + } else if (room?.battle) { format = Dex.getFormat(room.battle.format); mod = Dex.mod(format.mod); } const targets = target.split(/ ?[,/] ?/); - /** @type {{types: string[], [k: string]: any}} */ - let species = mod.getSpecies(targets[0]); + let species: {types: string[], [k: string]: any} = mod.getSpecies(targets[0]); const type1 = mod.getType(targets[0]); const type2 = mod.getType(targets[1]); const type3 = mod.getType(targets[2]); if (species.exists) { - target = species.species; + target = species.name; } else { const types = []; if (type1.exists) { @@ -820,19 +844,19 @@ const commands = { weaknesses.push(type); break; case 2: - weaknesses.push("" + type + ""); + weaknesses.push(`${type}`); break; case 3: - weaknesses.push("" + type + ""); + weaknesses.push(`${type}`); break; case -1: resistances.push(type); break; case -2: - resistances.push("" + type + ""); + resistances.push(`${type}`); break; case -3: - resistances.push("" + type + ""); + resistances.push(`${type}`); break; } } else { @@ -841,10 +865,10 @@ const commands = { } const buffer = []; - buffer.push(species.exists ? "" + species.name + ' (ignoring abilities):' : '' + target + ':'); - buffer.push('Weaknesses: ' + (weaknesses.join(', ') || 'None')); - buffer.push('Resistances: ' + (resistances.join(', ') || 'None')); - buffer.push('Immunities: ' + (immunities.join(', ') || 'None')); + buffer.push(`${species.exists ? `${species.name} (ignoring abilities):` : `${target}:`}`); + buffer.push(`Weaknesses: ${weaknesses.join(', ') || 'None'}`); + buffer.push(`Resistances: ${resistances.join(', ') || 'None'}`); + buffer.push(`Immunities: ${immunities.join(', ') || 'None'}`); this.sendReplyBox(buffer.join('
')); }, weaknesshelp: [ @@ -865,13 +889,21 @@ const commands = { let searchMethods = ['getType', 'getMove', 'getSpecies']; const sourceMethods = ['getType', 'getMove']; const targetMethods = ['getType', 'getSpecies']; - let source, defender, foundData, atkName, defName; + let source; + let defender; + let foundData; + let atkName; + let defName; + const dex: any = Dex; for (let i = 0; i < 2; ++i) { - let method; - for (method of searchMethods) { - foundData = Dex[method](targets[i]); - if (foundData.exists) break; + let method!: string; + for (const m of searchMethods) { + foundData = dex[m](targets[i]); + if (foundData.exists) { + method = m; + break; + } } if (!foundData.exists) return this.parse('/help effectiveness'); if (!source && sourceMethods.includes(method)) { @@ -886,7 +918,7 @@ const commands = { } else if (!defender && targetMethods.includes(method)) { if (foundData.types) { defender = foundData; - defName = foundData.species + " (not counting abilities)"; + defName = `${foundData.species} (not counting abilities)`; } else { defender = {types: [foundData.name]}; defName = foundData.name; @@ -898,12 +930,13 @@ const commands = { if (!this.runBroadcast()) return; let factor = 0; - if (Dex.getImmunity(source, defender) || source.ignoreImmunity && (source.ignoreImmunity === true || source.ignoreImmunity[source.type])) { + if (Dex.getImmunity(source, defender) || + source.ignoreImmunity && (source.ignoreImmunity === true || source.ignoreImmunity[source.type])) { let totalTypeMod = 0; if (source.effectType !== 'Move' || source.category !== 'Status' && (source.basePower || source.basePowerCallback)) { for (const type of defender.types) { const baseMod = Dex.getEffectiveness(source, type); - const moveMod = source.onEffectiveness && source.onEffectiveness.call({dex: Dex}, baseMod, null, type, source); + const moveMod = source.onEffectiveness?.call({dex: Dex} as Battle, baseMod, null, type, source); totalTypeMod += typeof moveMod === 'number' ? moveMod : baseMod; } } @@ -913,7 +946,7 @@ const commands = { const hasThousandArrows = source.id === 'thousandarrows' && defender.types.includes('Flying'); const additionalInfo = hasThousandArrows ? "
However, Thousand Arrows will be 1x effective on the first hit." : ""; - this.sendReplyBox("" + atkName + " is " + factor + "x effective against " + defName + "." + additionalInfo); + this.sendReplyBox(`${atkName} is ${factor}x effective against ${defName}.${additionalInfo}`); }, effectivenesshelp: [ `/effectiveness [attack], [defender] - Provides the effectiveness of a move or type on another type or a Pok\u00e9mon.`, @@ -927,9 +960,9 @@ const commands = { if (!target) return this.parse("/help coverage"); const targets = target.split(/[,+]/); - const sources = []; + const sources: (string | Move)[] = []; let dex = Dex; - if (room && room.battle) { + if (room?.battle) { const format = Dex.getFormat(room.battle.format); dex = Dex.mod(format.mod); } @@ -937,7 +970,7 @@ const commands = { dex = Dex.mod(toID(targets[targets.length - 1])); } let dispTable = false; - const bestCoverage = {}; + const bestCoverage: {[k: string]: number} = {}; let hasThousandArrows = false; for (const type in dex.data.TypeChart) { @@ -988,7 +1021,7 @@ const commands = { } else { if (!dex.getImmunity(move.type, type) && !move.ignoreImmunity) continue; const baseMod = dex.getEffectiveness(move, type); - const moveMod = move.onEffectiveness && move.onEffectiveness.call({dex}, baseMod, null, type, move); + const moveMod = move.onEffectiveness?.call({dex} as Battle, baseMod, null, type, move as ActiveMove); eff = typeof moveMod === 'number' ? moveMod : baseMod; } if (eff > bestCoverage[type]) bestCoverage[type] = eff; @@ -1007,11 +1040,11 @@ const commands = { } if (!dispTable) { - const buffer = []; - const superEff = []; - const neutral = []; - const resists = []; - const immune = []; + const buffer: string[] = []; + const superEff: string[] = []; + const neutral: string[] = []; + const resists: string[] = []; + const immune: string[] = []; for (const type in bestCoverage) { switch (bestCoverage[type]) { @@ -1030,29 +1063,29 @@ const commands = { superEff.push(type); break; default: - throw new Error("/coverage effectiveness of " + bestCoverage[type] + " from parameters: " + target); + throw new Error(`/coverage effectiveness of ${bestCoverage[type]} from parameters: ${target}`); } } - buffer.push('Coverage for ' + sources.join(' + ') + ':'); - buffer.push('Super Effective: ' + (superEff.join(', ') || 'None')); - buffer.push('Neutral: ' + (neutral.join(', ') || 'None')); - buffer.push('Resists: ' + (resists.join(', ') || 'None')); - buffer.push('Immunities: ' + (immune.join(', ') || 'None')); + buffer.push(`Coverage for ${sources.join(' + ')}:`); + buffer.push(`Super Effective: ${superEff.join(', ') || 'None'}`); + buffer.push(`Neutral: ${neutral.join(', ') || 'None'}`); + buffer.push(`Resists: ${resists.join(', ') || 'None'}`); + buffer.push(`Immunities: ${immune.join(', ') || 'None'}`); return this.sendReplyBox(buffer.join('
')); } else { let buffer = '
'; - const icon = {}; + const icon: {[k: string]: string} = {}; for (const type in dex.data.TypeChart) { icon[type] = ``; // row of icons at top - buffer += ''; + buffer += ``; } buffer += ''; for (const type1 in dex.data.TypeChart) { // assembles the rest of the rows - buffer += ''; + buffer += ``; for (const type2 in dex.data.TypeChart) { - let typing; + let typing: string; let cell = ''; buffer += cell; @@ -1107,7 +1152,7 @@ const commands = { buffer += "
Thousand Arrows has neutral type effectiveness on Flying-type Pok\u00e9mon if not already smacked down."; } - this.sendReplyBox('Coverage for ' + sources.join(' + ') + ':
' + buffer); + this.sendReplyBox(`Coverage for ${sources.join(' + ')}:
${buffer}`); } }, coveragehelp: [ @@ -1123,10 +1168,23 @@ const commands = { const targets = target.split(' '); - let lvlSet, natureSet, ivSet, evSet, baseSet, modSet, realSet = false; + let lvlSet = false; + let natureSet = false; + let ivSet = false; + let evSet = false; + let baseSet = false; + let modSet = false; + let realSet = false; - let pokemon; - let useStat = ''; + let pokemon: StatsTable = { + hp: 0, + atk: 0, + def: 0, + spa: 0, + spd: 0, + spe: 0, + }; + let useStat: StatName | '' = ''; let level = 100; let calcHP = false; @@ -1136,7 +1194,7 @@ const commands = { let baseStat = -1; let modifier = 0; let positiveMod = true; - let realStat; + let realStat = 0; for (const arg of targets) { const lowercase = arg.toLowerCase(); @@ -1226,7 +1284,8 @@ const commands = { } else if (lowercase === 'uninvested') { ev = 0; evSet = true; - } else if (lowercase.endsWith('ev') || lowercase.endsWith('evs') || lowercase.endsWith('+') || lowercase.endsWith('-')) { + } else if (lowercase.endsWith('ev') || lowercase.endsWith('evs') || + lowercase.endsWith('+') || lowercase.endsWith('-')) { ev = parseInt(arg); evSet = true; @@ -1264,7 +1323,7 @@ const commands = { modSet = true; } if (isNaN(modifier)) { - return this.sendReplyBox('Invalid value for modifier: ' + Chat.escapeHTML(modifier)); + return this.sendReplyBox('Invalid value for modifier: ' + Chat.escapeHTML(String(modifier))); } if (modifier > 6) { return this.sendReplyBox('Modifier should be a number between -6 and +6'); @@ -1353,7 +1412,7 @@ const commands = { return this.sendReplyBox('No valid value for base stat found.'); } - let output; + let output: number; if (calcHP) { output = (((iv + (2 * baseStat) + (ev / 4) + 100) * level) / 100) + 10; @@ -1365,7 +1424,7 @@ const commands = { output *= 2 / (2 + modifier); } } - return this.sendReplyBox('Base ' + baseStat + (calcHP ? ' HP ' : ' ') + 'at level ' + level + ' with ' + iv + ' IVs, ' + ev + (nature === 1.1 ? '+' : (nature === 0.9 ? '-' : '')) + ' EVs' + (modifier > 0 && !calcHP ? ' at ' + (positiveMod ? '+' : '-') + modifier : '') + ': ' + Math.floor(output) + '.'); + return this.sendReplyBox(`Base ${baseStat} ${calcHP ? ' HP ' : ' '}at level ${level} with ${iv} IVs, ${ev}${nature === 1.1 ? '+' : nature === 0.9 ? '-' : ''} EVs${modifier > 0 && !calcHP ? ` at ${positiveMod ? '+' : '-'}${modifier}` : ''}: ${Math.floor(output)}.`); }, statcalchelp: [ `/statcalc [level] [base stat] [IVs] [nature] [EVs] [modifier] (only base stat is required) - Calculates what the actual stat of a Pokémon is with the given parameters. For example, '/statcalc lv50 100 30iv positive 252ev scarf' calculates the speed of a base 100 scarfer with HP Ice in Battle Spot, and '/statcalc uninvested 90 neutral' calculates the attack of an uninvested Crobat.`, @@ -1526,7 +1585,7 @@ const commands = { bugreports: 'bugs', bugs(target, room, user) { if (!this.runBroadcast()) return; - if (room && room.battle) { + if (room?.battle) { this.sendReplyBox(`
QuestionsBug Reports
`); } else { this.sendReplyBox( @@ -1608,11 +1667,17 @@ const commands = { const DEFAULT_CALC_COMMANDS = ['honkalculator', 'honkocalc']; const RANDOMS_CALC_COMMANDS = ['randomscalc', 'randbatscalc', 'rcalc']; const BATTLESPOT_CALC_COMMANDS = ['bsscalc', 'cantsaycalc']; - const SUPPORTED_RANDOM_FORMATS = ['gen8randombattle', 'gen8unratedrandombattle', 'gen7randombattle', 'gen6randombattle', 'gen5randombattle', 'gen4randombattle', 'gen3randombattle', 'gen2randombattle', 'gen1randombattle']; - const SUPPORTED_BATTLESPOT_FORMATS = ['gen5gbusingles', 'gen5gbudoubles', 'gen6battlespotsingles', 'gen6battlespotdoubles', 'gen6battlespottriples', 'gen7battlespotsingles', 'gen7battlespotdoubles', 'gen7bssfactory']; - const isRandomBattle = (room && room.battle && SUPPORTED_RANDOM_FORMATS.includes(room.battle.format)); - const isBattleSpotBattle = (room && room.battle && (SUPPORTED_BATTLESPOT_FORMATS.includes(room.battle.format) || room.battle.format.includes("battlespotspecial"))); - if (RANDOMS_CALC_COMMANDS.includes(cmd) || (isRandomBattle && !DEFAULT_CALC_COMMANDS.includes(cmd) && !BATTLESPOT_CALC_COMMANDS.includes(cmd))) { + const SUPPORTED_RANDOM_FORMATS = [ + 'gen8randombattle', 'gen8unratedrandombattle', 'gen7randombattle', 'gen6randombattle', 'gen5randombattle', 'gen4randombattle', 'gen3randombattle', 'gen2randombattle', 'gen1randombattle', + ]; + const SUPPORTED_BATTLESPOT_FORMATS = [ + 'gen5gbusingles', 'gen5gbudoubles', 'gen6battlespotsingles', 'gen6battlespotdoubles', 'gen6battlespottriples', 'gen7battlespotsingles', 'gen7battlespotdoubles', 'gen7bssfactory', + ]; + const isRandomBattle = (room?.battle && SUPPORTED_RANDOM_FORMATS.includes(room.battle.format)); + const isBattleSpotBattle = (room?.battle && (SUPPORTED_BATTLESPOT_FORMATS.includes(room.battle.format) || + room.battle.format.includes("battlespotspecial"))); + if (RANDOMS_CALC_COMMANDS.includes(cmd) || + (isRandomBattle && !DEFAULT_CALC_COMMANDS.includes(cmd) && !BATTLESPOT_CALC_COMMANDS.includes(cmd))) { return this.sendReplyBox( `Random Battles damage calculator. (Courtesy of Austin & pre)
` + `- Random Battles Damage Calculator` @@ -1686,32 +1751,42 @@ const commands = { const isOMSearch = (cmd === 'om' || cmd === 'othermetas'); let targetId = toID(target); - if (targetId === 'ladder') targetId = 'search'; + if (targetId === 'ladder') targetId = 'search' as ID; if (targetId === 'all') targetId = ''; - let formatList; + let formatList: string[] = []; const format = Dex.getFormat(targetId); - if (format.effectType === 'Format' || format.effectType === 'ValidatorRule' || format.effectType === 'Rule') formatList = [targetId]; - if (!formatList) { + if (['Format', 'ValidatorRule', 'Rule'].includes(format.effectType)) formatList = [targetId]; + if (!formatList.length) { formatList = Object.keys(Dex.formats); } // Filter formats and group by section let exactMatch = ''; - const sections = {}; + const sections: {[k: string]: {name: string, formats: string[]}} = {}; let totalMatches = 0; for (const mode of formatList) { - const format = Dex.getFormat(mode); - const sectionId = toID(format.section); - let formatId = format.id; - if (!/^gen\d+/.test(targetId)) formatId = formatId.replace(/^gen\d+/, ''); // skip generation prefix if it wasn't provided - if (targetId && !format[targetId + 'Show'] && sectionId !== targetId && format.id === mode && !formatId.startsWith(targetId)) continue; - if (isOMSearch && format.id.startsWith('gen') && ['ou', 'uu', 'ru', 'ubers', 'lc', 'customgame', 'doublescustomgame', 'gbusingles', 'gbudoubles'].includes(format.id.slice(4))) continue; - if (isOMSearch && (format.id === 'gen5nu')) continue; + const subformat = Dex.getFormat(mode); + const sectionId = toID(subformat.section); + let formatId = subformat.id; + if (!/^gen\d+/.test(targetId)) { + // Skip generation prefix if it wasn't provided + formatId = formatId.replace(/^gen\d+/, '') as ID; + } + if (targetId && !(subformat as any)[targetId + 'Show'] && sectionId !== targetId && + subformat.id === mode && !formatId.startsWith(targetId)) continue; + if (isOMSearch) { + const officialFormats = [ + 'ou', 'uu', 'ru', 'nu', 'pu', 'ubers', 'lc', 'monotype', 'customgame', 'doublescustomgame', 'gbusingles', 'gbudoubles', + ]; + if (subformat.id.startsWith('gen') && officialFormats.includes(subformat.id.slice(4))) { + continue; + } + } totalMatches++; - if (!sections[sectionId]) sections[sectionId] = {name: format.section, formats: []}; - sections[sectionId].formats.push(format.id); - if (format.id !== targetId) continue; + if (!sections[sectionId]) sections[sectionId] = {name: subformat.section!, formats: []}; + sections[sectionId].formats.push(subformat.id); + if (subformat.id !== targetId) continue; exactMatch = sectionId; break; } @@ -1719,33 +1794,39 @@ const commands = { if (!totalMatches) return this.errorReply("No matched formats found."); if (!this.runBroadcast()) return; if (totalMatches === 1) { - const rules = []; + const rules: string[] = []; let rulesetHtml = ''; - const format = Dex.getFormat(Object.values(sections)[0].formats[0]); - if (format.effectType === 'ValidatorRule' || format.effectType === 'Rule' || format.effectType === 'Format') { - if (format.ruleset && format.ruleset.length) rules.push("Ruleset - " + Chat.escapeHTML(format.ruleset.join(", "))); - if (format.removedRules && format.removedRules.length) rules.push("Removed rules - " + Chat.escapeHTML(format.removedRules.join(", "))); - if (format.banlist && format.banlist.length) rules.push("Bans - " + Chat.escapeHTML(format.banlist.join(", "))); - if (format.unbanlist && format.unbanlist.length) rules.push("Unbans - " + Chat.escapeHTML(format.unbanlist.join(", "))); - if (format.restricted && format.restricted.length) rules.push("Restricted - " + Chat.escapeHTML(format.restricted.join(", "))); + const subformat = Dex.getFormat(Object.values(sections)[0].formats[0]); + if (['Format', 'Rule', 'ValidatorRule'].includes(subformat.effectType)) { + if (subformat.ruleset?.length) { + rules.push(`Ruleset - ${Chat.escapeHTML(subformat.ruleset.join(", "))}`); + } + if (subformat.banlist?.length) { + rules.push(`Bans - ${Chat.escapeHTML(subformat.banlist.join(", "))}`); + } + if (subformat.unbanlist?.length) { + rules.push(`Unbans - ${Chat.escapeHTML(subformat.unbanlist.join(", "))}`); + } + if (subformat.restricted?.length) { + rules.push(`Restricted - ${Chat.escapeHTML(subformat.restricted.join(", "))}`); + } if (rules.length > 0) { rulesetHtml = `
Banlist/Ruleset${rules.join("
")}
`; } else { - rulesetHtml = "No ruleset found for " + format.name; + rulesetHtml = `No ruleset found for ${format.name}`; } } - let formatType = (format.gameType || "singles"); + let formatType: string = (format.gameType || "singles"); formatType = formatType.charAt(0).toUpperCase() + formatType.slice(1).toLowerCase(); if (!format.desc && !format.threads) { if (format.effectType === 'Format') { - return this.sendReplyBox("No description found for this " + formatType + " " + format.section + " format." + "
" + rulesetHtml); + return this.sendReplyBox(`No description found for this ${formatType} ${format.section} format.
${rulesetHtml}`); } else { - return this.sendReplyBox("No description found for this rule." + "
" + rulesetHtml); + return this.sendReplyBox(`No description found for this rule.
${rulesetHtml}`); } } - let descHtml = format.desc ? [format.desc] : []; - if (format.threads) descHtml = descHtml.concat(format.threads); - return this.sendReplyBox(descHtml.join("
") + "
" + rulesetHtml); + const descHtml = [...(format.desc ? [format.desc] : []), ...(format.threads || [])]; + return this.sendReplyBox(`${descHtml.join("
")}
${rulesetHtml}`); } let tableStyle = `border:1px solid gray; border-collapse:collapse`; @@ -1760,16 +1841,15 @@ const commands = { if (exactMatch && sectionId !== exactMatch) continue; buf.push(Chat.html`
`); for (const section of sections[sectionId].formats) { - const format = Dex.getFormat(section); - const nameHTML = Chat.escapeHTML(format.name); - let desc = format.desc ? [format.desc] : []; - if (format.threads) desc = desc.concat(format.threads); + const subformat = Dex.getFormat(section); + const nameHTML = Chat.escapeHTML(subformat.name); + const desc = [...(subformat.desc ? [subformat.desc] : []), ...(subformat.threads || [])]; const descHTML = desc.length ? desc.join("
") : "—"; buf.push(`
`); } } buf.push(`
' + icon[type] + '${icon[type]}
' + icon[type1] + '
${icon[type1]} bestEff) bestEff = curEff; } @@ -1081,21 +1126,21 @@ const commands = { } switch (bestEff) { case 0: - cell += 'bgcolor=#666666 title="' + typing + '">' + bestEff + ''; + cell += `bgcolor=#666666 title="${typing}">${bestEff}`; break; case 0.25: case 0.5: - cell += 'bgcolor=#AA5544 title="' + typing + '">' + bestEff + ''; + cell += `bgcolor=#AA5544 title="${typing}">${bestEff}`; break; case 1: - cell += 'bgcolor=#6688AA title="' + typing + '">' + bestEff + ''; + cell += `bgcolor=#6688AA title="${typing}">${bestEff}`; break; case 2: case 4: - cell += 'bgcolor=#559955 title="' + typing + '">' + bestEff + ''; + cell += `bgcolor=#559955 title="${typing}">${bestEff}`; break; default: - throw new Error("/coverage effectiveness of " + bestEff + " from parameters: " + target); + throw new Error(`/coverage effectiveness of ${bestEff} from parameters: ${target}`); } cell += '${sections[sectionId].name}
${nameHTML}${descHTML}
`); - return this.sendReply("|raw|" + buf.join("") + ""); + return this.sendReply(`|raw|${buf.join("")}`); }, '!roomhelp': true, @@ -1853,7 +1933,7 @@ const commands = { if (!this.runBroadcast()) return; this.sendReplyBox( `${room ? this.tr("Please follow the rules:") + '
' : ``}` + - (room && room.rulesLink ? Chat.html`- ${this.tr `${room.title} room rules`}
` : ``) + + `${room?.rulesLink ? Chat.html`- ${this.tr `${room.title} room rules`}
` : ``}` + `- ${this.tr("Global Rules")}` ); return; @@ -1987,12 +2067,13 @@ const commands = { return this.sendReplyBox(`${pokemon.name} did not exist in ${generation.toUpperCase()}!`); } - if ((pokemon.battleOnly && pokemon.baseSpecies !== 'Greninja') || ['Keldeo', 'Genesect'].includes(pokemon.baseSpecies)) { + if ((pokemon.battleOnly && pokemon.baseSpecies !== 'Greninja') || + ['Keldeo', 'Genesect'].includes(pokemon.baseSpecies)) { pokemon = Dex.getSpecies(pokemon.changesFrom || pokemon.baseSpecies); } let formatName = extraFormat.name; - let formatId = extraFormat.id; + let formatId: string = extraFormat.id; if (formatName.startsWith('[Gen ')) { formatName = formatName.replace('[Gen ' + formatName[formatName.indexOf('[') + 5] + '] ', ''); formatId = toID(formatName); @@ -2018,7 +2099,7 @@ const commands = { } else if (extraFormat.effectType !== 'Format') { formatName = formatId = ''; } - const supportedLanguages = { + const supportedLanguages: {[k: string]: string} = { spanish: 'es', french: 'fr', italian: 'it', @@ -2027,8 +2108,9 @@ const commands = { }; let id = pokemon.id; // Special case for Meowstic-M - if (id === 'meowstic') id = 'meowsticm'; - if (['ou', 'uu'].includes(formatId) && generation === 'sm' && room && room.language in supportedLanguages) { + if (id === 'meowstic') id = 'meowsticm' as ID; + if (['ou', 'uu'].includes(formatId) && generation === 'sm' && + room?.language && room.language in supportedLanguages) { // Limited support for translated analysis // Translated analysis do not support automatic redirects from a id to the proper page this.sendReplyBox(Chat.html`${generation.toUpperCase()} ${formatName} ${pokemon.name} analysis, brought to you by Smogon University`); @@ -2064,7 +2146,7 @@ const commands = { // Format if (format.id) { let formatName = format.name; - let formatId = format.id; + let formatId: string = format.id; if (formatId === 'battlespotdoubles') { formatId = 'battle_spot_doubles'; } else if (formatId === 'battlespottriples') { @@ -2081,8 +2163,8 @@ const commands = { } else if (formatId === 'ubers') { formatId = 'uber'; } else if (formatId.includes('vgc')) { - formatId = 'vgc' + formatId.slice(-2); - formatName = 'VGC20' + formatId.slice(-2); + formatId = `vgc${formatId.slice(-2)}`; + formatName = `VGC20${formatId.slice(-2)}`; } else if (format.effectType !== 'Format') { formatName = formatId = ''; } @@ -2118,9 +2200,11 @@ const commands = { // Pokemon if (pokemon.exists) { atLeastOne = true; - if (pokemon.isNonstandard && pokemon.isNonstandard !== 'Past') return this.errorReply(`${pokemon.species} is not a real Pok\u00e9mon.`); + if (pokemon.isNonstandard && pokemon.isNonstandard !== 'Past') { + return this.errorReply(`${pokemon.name} is not a real Pok\u00e9mon.`); + } - const baseSpecies = pokemon.baseSpecies || pokemon.species; + const baseSpecies = pokemon.baseSpecies || pokemon.name; let forme = pokemon.forme; // Showdown and Veekun have different names for various formes @@ -2134,43 +2218,49 @@ const commands = { if (baseSpecies === 'Mimikyu') forme += forme === 'Busted-Totem' ? '-Busted' : '-Disguised'; } - let link = baseLink + 'pokemon/' + baseSpecies.toLowerCase(); + let link = `${baseLink}pokemon/${baseSpecies.toLowerCase()}`; if (forme) { if (baseSpecies === 'Arceus' || baseSpecies === 'Silvally') link += '/flavor'; - link += '?form=' + forme.toLowerCase(); + link += `?form=${forme.toLowerCase()}`; } - this.sendReplyBox(`${pokemon.species} description by Veekun`); + this.sendReplyBox(`${pokemon.name} description by Veekun`); } // Item if (item.exists) { atLeastOne = true; - if (item.isNonstandard && item.isNonstandard !== 'Past') return this.errorReply(`${item.name} is not a real item.`); - const link = baseLink + 'items/' + item.name.toLowerCase(); + if (item.isNonstandard && item.isNonstandard !== 'Past') { + return this.errorReply(`${item.name} is not a real item.`); + } + const link = `${baseLink}items/${item.name.toLowerCase()}`; this.sendReplyBox(`${item.name} item description by Veekun`); } // Ability if (ability.exists) { atLeastOne = true; - if (ability.isNonstandard && ability.isNonstandard !== 'Past') return this.errorReply(`${ability.name} is not a real ability.`); - const link = baseLink + 'abilities/' + ability.name.toLowerCase(); + if (ability.isNonstandard && ability.isNonstandard !== 'Past') { + return this.errorReply(`${ability.name} is not a real ability.`); + } + const link = `${baseLink}abilities/${ability.name.toLowerCase()}`; this.sendReplyBox(`${ability.name} ability description by Veekun`); } // Move if (move.exists) { atLeastOne = true; - if (move.isNonstandard && move.isNonstandard !== 'Past') return this.errorReply(`${move.name} is not a real move.`); - const link = baseLink + 'moves/' + move.name.toLowerCase(); + if (move.isNonstandard && move.isNonstandard !== 'Past') { + return this.errorReply(`${move.name} is not a real move.`); + } + const link = `${baseLink}moves/${move.name.toLowerCase()}`; this.sendReplyBox(`${move.name} move description by Veekun`); } // Nature if (nature.exists) { atLeastOne = true; - const link = baseLink + 'natures/' + nature.name.toLowerCase(); + const link = `${baseLink}natures/${nature.name.toLowerCase()}`; this.sendReplyBox(`${nature.name} nature description by Veekun`); } @@ -2193,21 +2283,6 @@ const commands = { * Miscellaneous commands *********************************************************/ - potd(target, room, user) { - if (!this.can('potd')) return false; - - Config.potd = target; - // TODO: support eval in new PM - Rooms.PM.eval('Config.potd = \'' + toID(target) + '\''); - if (target) { - if (Rooms.lobby) Rooms.lobby.addRaw(`
The Pokémon of the Day is now ${target}!
This Pokemon will be guaranteed to show up in random battles.
`); - this.modlog('POTD', null, target); - } else { - if (Rooms.lobby) Rooms.lobby.addRaw(`
The Pokémon of the Day was removed!
No pokemon will be guaranteed in random battles.
`); - this.modlog('POTD', null, 'removed'); - } - }, - '!dice': true, roll: 'dice', dice(target, room, user) { @@ -2223,12 +2298,14 @@ const commands = { if (diceDataStart >= 0) { if (diceDataStart) diceQuantity = Number(target.slice(0, diceDataStart)); target = target.slice(diceDataStart + 1); - if (!Number.isInteger(diceQuantity) || diceQuantity <= 0 || diceQuantity > maxDice) return this.sendReply(`The amount of dice rolled should be a natural number up to ${maxDice}.`); + if (!Number.isInteger(diceQuantity) || diceQuantity <= 0 || diceQuantity > maxDice) { + return this.sendReply(`The amount of dice rolled should be a natural number up to ${maxDice}.`); + } } let offset = 0; let removeOutlier = 0; - const modifierData = target.match(/[+-]/); + const modifierData = /[+-]/.exec(target); if (modifierData) { switch (target.slice(modifierData.index).trim().toLowerCase()) { case '-l': @@ -2240,9 +2317,13 @@ const commands = { default: offset = Number(target.slice(modifierData.index)); if (isNaN(offset)) return this.parse('/help dice'); - if (!Number.isSafeInteger(offset)) return this.errorReply(`The specified offset must be an integer up to ${Number.MAX_SAFE_INTEGER}.`); + if (!Number.isSafeInteger(offset)) { + return this.errorReply(`The specified offset must be an integer up to ${Number.MAX_SAFE_INTEGER}.`); + } + } + if (removeOutlier && diceQuantity <= 1) { + return this.errorReply(`More than one dice should be rolled before removing outliers.`); } - if (removeOutlier && diceQuantity <= 1) return this.errorReply(`More than one dice should be rolled before removing outliers.`); target = target.slice(0, modifierData.index); } @@ -2288,7 +2369,7 @@ const commands = { // Reply with relevant information let offsetFragment = ""; - if (offset) offsetFragment += (offset > 0 ? " + " + offset : offset); + if (offset) offsetFragment += `${offset > 0 ? ` + ${offset}` : offset}`; if (diceQuantity === 1) return this.sendReplyBox(`Rolling (1 to ${diceFaces})${offsetFragment}: ${rollSum}`); @@ -2348,7 +2429,7 @@ const commands = { return this.parse('/help showimage'); } - let image = targets[0].trim(); + let image: string | null = targets[0].trim(); if (!image) return this.errorReply(`No image URL was provided!`); image = this.canEmbedURI(image); @@ -2357,11 +2438,11 @@ const commands = { if (targets.length === 3) { let width = targets[1].trim(); if (!width) return this.errorReply(`No width for the image was provided!`); - if (!isNaN(width)) width += `px`; + if (!isNaN(parseInt(width))) width += `px`; let height = targets[2].trim(); if (!height) return this.errorReply(`No height for the image was provided!`); - if (!isNaN(height)) height += `px`; + if (!isNaN(parseInt(height))) height += `px`; const unitRegex = /^\d+(?:p[xtc]|%|[ecm]m|ex|in)$/; if (!unitRegex.test(width)) { @@ -2374,7 +2455,7 @@ const commands = { return this.sendReply(Chat.html`|raw|`); } - Chat.fitImage(image).then(([width, height]) => { + void Chat.fitImage(image).then(([width, height]) => { this.sendReply(Chat.html`|raw|`); room.update(); }); @@ -2396,7 +2477,7 @@ const commands = { // XXX: target is trimmed by Chat#splitMessage. Let's not add another // awful hack like ! or help command keys for whether or not the target // is raw for now. - target = this.message.substr(this.cmdToken.length + this.cmd.length + +this.message.includes(' ')).trimEnd(); + target = this.message.substr(this.cmdToken.length + this.cmd.length + +this.message.includes(' ')).trimRight(); if (!target) return this.parse('/help code'); if (target.length >= 8192) return this.errorReply("Your code must be under 8192 characters long!"); @@ -2434,8 +2515,7 @@ const commands = { ], }; -/** @type {PageTable} */ -const pages = { +export const pages: PageTable = { punishments(query, user) { this.title = 'Punishments'; let buf = ""; @@ -2444,8 +2524,13 @@ const pages = { if (!this.room.chatRoomData) return; if (!this.can('mute', null, this.room)) return; // Ascending order - const sortedPunishments = Array.from(Punishments.getPunishments(this.room.roomid)).sort((a, b) => a[1].expireTime - b[1].expireTime); - buf += Punishments.visualizePunishments(sortedPunishments, user); + const sortedPunishments = Array.from(Punishments.getPunishments(this.room.roomid)) + .sort((a, b) => a[1].expireTime - b[1].expireTime); + const sP = new Map(); + for (const punishment of sortedPunishments) { + sP.set(punishment[0], punishment[1]); + } + buf += Punishments.visualizePunishments(sP, user); return buf; }, globalpunishments(query, user) { @@ -2455,14 +2540,15 @@ const pages = { if (!this.can('lock')) return; // Ascending order const sortedPunishments = Array.from(Punishments.getPunishments()).sort((a, b) => a[1].expireTime - b[1].expireTime); - buf += Punishments.visualizePunishments(sortedPunishments, user); + const sP = new Map(); + for (const punishment of sortedPunishments) { + sP.set(punishment[0], punishment[1]); + } + buf += Punishments.visualizePunishments(sP, user); return buf; }, }; -exports.pages = pages; -exports.commands = commands; - process.nextTick(() => { Dex.includeData(); Chat.multiLinePattern.register( diff --git a/sim/dex-data.ts b/sim/dex-data.ts index d4bce61c52..211fe6fa7a 100644 --- a/sim/dex-data.ts +++ b/sim/dex-data.ts @@ -591,6 +591,11 @@ export class Species extends BasicEffect implements Readonly