"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var info_exports = {};
__export(info_exports, {
commands: () => commands,
destroy: () => destroy,
findFormats: () => findFormats,
formatsDataCache: () => formatsDataCache,
getCommonBattles: () => getCommonBattles,
getFormatResources: () => getFormatResources,
handlers: () => handlers,
pages: () => pages
});
module.exports = __toCommonJS(info_exports);
var net = __toESM(require("net"));
var import_youtube = require("../chat-plugins/youtube");
var import_lib = require("../../lib");
var import_room_settings = require("./room-settings");
/**
* Informational Commands
* Pokemon Showdown - https://pokemonshowdown.com/
*
* These are informational commands. For instance, you can define the command
* 'whois' here, then use it by typing /whois into Pokemon Showdown.
* For the API, see chat-plugins/COMMANDS.md
*
* @license MIT
*/
const ONLINE_SYMBOL = ` \u25C9 `;
const OFFLINE_SYMBOL = ` \u25CC `;
function getCommonBattles(userID1, user1, userID2, user2, connection) {
const battles = [];
for (const curRoom of Rooms.rooms.values()) {
if (!curRoom.battle)
continue;
if ((user1?.inRooms.has(curRoom.roomid) || curRoom.auth.get(userID1) === Users.PLAYER_SYMBOL) && (user2?.inRooms.has(curRoom.roomid) || curRoom.auth.get(userID2) === Users.PLAYER_SYMBOL)) {
if (connection) {
void curRoom.uploadReplay(connection.user, connection, "forpunishment");
}
battles.push(curRoom.roomid);
}
}
return battles;
}
function findFormats(targetId, isOMSearch = false) {
const exactFormat = Dex.formats.get(targetId);
const formatList = exactFormat.exists ? [exactFormat] : Dex.formats.all();
const sections = {};
let totalMatches = 0;
for (const format of formatList) {
const sectionId = toID(format.section);
const formatId = /^gen\d+/.test(targetId) ? format.id : format.id.slice(4);
if (!targetId || format[targetId + "Show"] || sectionId === targetId || formatId.startsWith(targetId) || exactFormat.exists) {
if (isOMSearch) {
const officialFormats = [
"ou",
"uu",
"ru",
"nu",
"pu",
"ubers",
"lc",
"monotype",
"customgame",
"doublescustomgame",
"gbusingles",
"gbudoubles"
];
if (format.id.startsWith("gen") && officialFormats.includes(format.id.slice(4))) {
continue;
}
}
totalMatches++;
if (!sections[sectionId])
sections[sectionId] = { name: format.section, formats: [] };
sections[sectionId].formats.push(format.id);
}
}
return { totalMatches, sections };
}
const formatsDataCache = /* @__PURE__ */ new Map();
async function getFormatResources(format) {
const cached = formatsDataCache.get(format);
if (cached !== void 0)
return cached;
try {
const raw = await (0, import_lib.Net)(`https://www.smogon.com/dex/api/formats/by-ps-name/${format}`).get();
const data = JSON.parse(raw);
formatsDataCache.set(format, data);
return data;
} catch {
formatsDataCache.set(format, null);
return null;
}
}
const resourceRefreshInterval = setInterval(() => formatsDataCache.clear(), 15 * 60 * 1e3);
function destroy() {
clearInterval(resourceRefreshInterval);
}
const commands = {
ip: "whois",
rooms: "whois",
alt: "whois",
alts: "whois",
whoare: "whois",
altsnorecurse: "whois",
profile: "whois",
whois(target, room, user, connection, cmd) {
if (room?.roomid === "staff" && !this.runBroadcast())
return;
const targetUser = this.getUserOrSelf(target, { exactName: user.tempGroup === " " });
const showAll = cmd === "ip" || cmd === "whoare" || cmd === "alt" || cmd === "alts" || cmd === "altsnorecurse";
const showRecursiveAlts = showAll && cmd !== "altsnorecurse";
if (!targetUser) {
if (showAll)
return this.parse("/offlinewhois " + target);
return this.errorReply(`User ${target} not found.`);
}
if (showAll && !user.trusted && targetUser !== user) {
return this.errorReply(`/${cmd} - Access denied.`);
}
let buf = import_lib.Utils.html`${targetUser.tempGroup}${targetUser.name} `;
const ac = targetUser.autoconfirmed;
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)`;
const roomauth = room?.auth.getDirect(targetUser.id);
if (roomauth && Config.groups[roomauth]?.name) {
buf += import_lib.Utils.html`
${Config.groups[roomauth].name} (${roomauth})`;
}
if (Config.groups[targetUser.tempGroup]?.name) {
buf += import_lib.Utils.html`
Global ${Config.groups[targetUser.tempGroup].name} (${targetUser.tempGroup})`;
}
if (Users.globalAuth.sectionLeaders.has(targetUser.id)) {
buf += import_lib.Utils.html`
Section Leader (${import_room_settings.RoomSections.sectionNames[Users.globalAuth.sectionLeaders.get(targetUser.id)]})`;
}
if (targetUser.isSysop) {
buf += `
(Pokémon Showdown System Operator)`;
}
if (!targetUser.registered) {
buf += `
(Unregistered)`;
}
let publicrooms = ``;
let hiddenrooms = ``;
let privaterooms = ``;
for (const roomid of targetUser.inRooms) {
const targetRoom = Rooms.get(roomid);
const authSymbol = targetRoom.auth.getDirect(targetUser.id).trim();
const battleTitle = targetRoom.battle ? ` title="${targetRoom.title}"` : "";
const output = `${authSymbol}${roomid}`;
if (targetRoom.settings.isPrivate === true) {
if (targetRoom.settings.modjoin === "~")
continue;
if (privaterooms)
privaterooms += ` | `;
privaterooms += output;
} else if (targetRoom.settings.isPrivate) {
if (hiddenrooms)
hiddenrooms += ` | `;
hiddenrooms += output;
} else {
if (publicrooms)
publicrooms += ` | `;
publicrooms += output;
}
}
buf += `
Rooms: ${publicrooms || `(no public rooms)`}`;
if (!showAll) {
return this.sendReplyBox(buf);
}
const canViewAlts = user === targetUser ? user.can("altsself") : user.can("alts", targetUser);
const canViewPunishments = canViewAlts || room && room.settings.isPrivate !== true && user.can("mute", targetUser, room) && targetUser.id in room.users;
const canViewSecretRooms = user === targetUser || canViewAlts && targetUser.locked || user.can("makeroom");
buf += `
`;
if (canViewAlts) {
let prevNames = targetUser.previousIDs.map((userid) => {
const punishments = Punishments.userids.get(userid);
if (!punishments || !user.can("alts"))
return userid;
return punishments.map(
(punishment) => `${userid}${punishment ? ` (${Punishments.punishmentTypes.get(punishment.type)?.desc || `punished`}${punishment.id !== targetUser.id ? ` as ${punishment.id}` : ``})` : ``}`
).join(" | ");
}).join(", ");
if (prevNames)
buf += import_lib.Utils.html`
Previous names: ${prevNames}`;
for (const targetAlt of targetUser.getAltUsers(true)) {
if (!targetAlt.named && !targetAlt.connected)
continue;
if (targetAlt.tempGroup === "~" && user.tempGroup !== "~")
continue;
const punishments = Punishments.userids.get(targetAlt.id) || [];
const punishMsg = !user.can("alts") ? "" : punishments.map((punishment) => ` (${Punishments.punishmentTypes.get(punishment.type)?.desc || "punished"}${punishment.id !== targetAlt.id ? ` as ${punishment.id}` : ""})`).join(" | ");
buf += import_lib.Utils.html`
Alt: ${targetAlt.name}${punishMsg}`;
if (!targetAlt.connected)
buf += ` (offline)`;
prevNames = targetAlt.previousIDs.map((userid) => {
const p = Punishments.userids.get(userid);
if (!p || !user.can("alts"))
return userid;
return p.map(
(cur) => `${userid} (${Punishments.punishmentTypes.get(cur.type)?.desc || "punished"}${cur.id !== targetAlt.id ? ` as ${cur.id}` : ``})`
).join(" | ");
}).join(", ");
if (prevNames)
buf += `
Previous names: ${prevNames}`;
}
}
if (canViewPunishments) {
if (targetUser.namelocked) {
buf += `
NAMELOCKED: ${targetUser.namelocked}`;
const punishment = Punishments.userids.getByType(targetUser.locked, "NAMELOCK");
if (punishment) {
const expiresIn = Punishments.checkLockExpiration(targetUser.locked);
if (expiresIn)
buf += expiresIn;
if (punishment.reason)
buf += import_lib.Utils.html` (reason: ${punishment.reason})`;
}
} else if (targetUser.locked) {
buf += `
LOCKED: ${targetUser.locked}`;
switch (targetUser.locked) {
case "#rangelock":
buf += ` - IP or host is in a temporary range-lock`;
break;
case "#hostfilter":
buf += ` - host is permanently locked for being a proxy`;
break;
}
const punishment = Punishments.userids.getByType(targetUser.locked, "LOCK");
if (punishment) {
const expiresIn = Punishments.checkLockExpiration(targetUser.locked);
if (expiresIn)
buf += expiresIn;
if (punishment.reason)
buf += import_lib.Utils.html` (reason: ${punishment.reason})`;
}
}
if (user.can("lock")) {
const battlebanned = Punishments.isBattleBanned(targetUser);
if (battlebanned) {
buf += `
BATTLEBANNED: ${battlebanned.id}`;
buf += ` ${Punishments.checkPunishmentExpiration(battlebanned)}`;
if (battlebanned.reason)
buf += import_lib.Utils.html` (reason: ${battlebanned.reason})`;
}
const groupchatbanned = Punishments.isGroupchatBanned(targetUser);
if (groupchatbanned) {
buf += `
Banned from using groupchats${groupchatbanned.id !== targetUser.id ? `: ${groupchatbanned.id}` : ``}`;
buf += ` ${Punishments.checkPunishmentExpiration(groupchatbanned)}`;
if (groupchatbanned.reason)
buf += import_lib.Utils.html` (reason: ${groupchatbanned.reason})`;
}
const ticketbanned = Punishments.isTicketBanned(targetUser.id);
if (ticketbanned) {
buf += `
Banned from creating help tickets${ticketbanned.id !== targetUser.id ? `: ${ticketbanned.id}` : ``}`;
buf += ` ${Punishments.checkPunishmentExpiration(ticketbanned)}`;
if (ticketbanned.reason)
buf += import_lib.Utils.html` (reason: ${ticketbanned.reason})`;
}
}
if (targetUser.semilocked) {
buf += `
Semilocked: ${user.can("lock") ? targetUser.semilocked : "(reason hidden)"}`;
}
}
if (user === targetUser ? user.can("ipself") : user.can("ip", targetUser)) {
const ips = targetUser.ips.map((ip) => {
const status = [];
const punishments = Punishments.ips.get(ip);
if (user.can("alts") && punishments) {
for (const punishment of punishments) {
const { type, id } = punishment;
let punishMsg = Punishments.punishmentTypes.get(type)?.desc || type;
if (id !== targetUser.id)
punishMsg += ` as ${id}`;
status.push(punishMsg);
}
}
if (Punishments.isSharedIp(ip)) {
let sharedStr = "shared";
if (Punishments.sharedIps.get(ip)) {
sharedStr += `: ${Punishments.sharedIps.get(ip)}`;
}
status.push(sharedStr);
}
return `${ip}` + (status.length ? ` (${status.join("; ")})` : "");
});
buf += `
IP${Chat.plural(ips)}: ${ips.join(", ")}`;
if (user.tempGroup !== " " && targetUser.latestHost) {
buf += import_lib.Utils.html`
Host: ${targetUser.latestHost} [${targetUser.latestHostType}]`;
}
} else if (user === targetUser) {
buf += `
IP: ${connection.ip}`;
}
if ((user === targetUser || canViewAlts) && hiddenrooms) {
buf += `
Hidden rooms: ${hiddenrooms}`;
}
if (canViewSecretRooms && privaterooms) {
buf += `
Secret rooms: ${privaterooms}`;
}
const gameRooms = [];
for (const curRoom of Rooms.rooms.values()) {
if (!curRoom.game)
continue;
const inPlayerTable = targetUser.id in curRoom.game.playerTable && !targetUser.inRooms.has(curRoom.roomid);
const hasPlayerSymbol = curRoom.auth.getDirect(targetUser.id) === Users.PLAYER_SYMBOL;
const canSeeRoom = canViewAlts || user === targetUser || !curRoom.settings.isPrivate;
if ((inPlayerTable || hasPlayerSymbol) && canSeeRoom) {
gameRooms.push(curRoom.roomid);
}
}
if (gameRooms.length) {
buf += `
Recent games: ${gameRooms.map((id) => {
const shortId = id.startsWith("battle-") ? id.slice(7) : id;
return import_lib.Utils.html`${shortId}`;
}).join(" | ")}`;
}
if (canViewPunishments) {
const punishments = Punishments.getRoomPunishments(targetUser, { checkIps: true });
if (punishments.length) {
buf += `
Room punishments: `;
buf += punishments.map(([curRoom, curPunishment]) => {
const { type: punishType, id: punishUserid, expireTime, reason } = curPunishment;
let punishDesc = Punishments.roomPunishmentTypes.get(punishType)?.desc || punishType;
if (punishUserid !== targetUser.id)
punishDesc += ` as ${punishUserid}`;
const expiresIn = new Date(expireTime).getTime() - Date.now();
const expireString = Chat.toDurationString(expiresIn, { precision: 1 });
punishDesc += ` for ${expireString}`;
if (reason)
punishDesc += `: ${reason}`;
return `${curRoom} (${punishDesc})`;
}).join(", ");
}
}
this.sendReplyBox(buf);
if (showRecursiveAlts && canViewAlts) {
const targetId = toID(target);
for (const alt of Users.users.values()) {
if (alt !== targetUser && alt.previousIDs.includes(targetId)) {
void this.parse(`/altsnorecurse ${alt.name}`);
}
}
}
},
whoishelp: [
`/whois - Get details on yourself: alts, group, IP address, and rooms.`,
`/whois [username] - Get details on a username: alts (Requires: % @ ~), group, IP address (Requires: @ ~), and rooms.`
],
"chp": "offlinewhois",
checkpunishment: "offlinewhois",
offlinewhois(target, room, user) {
if (!user.trusted) {
return this.errorReply("/offlinewhois - Access denied.");
}
const userid = toID(target);
if (!userid)
return this.errorReply("Please enter a valid username.");
const targetUser = Users.get(userid);
let buf = import_lib.Utils.html`${target}`;
if (!targetUser?.connected)
buf += ` (offline)`;
const roomauth = room?.auth.getDirect(userid);
if (roomauth && Config.groups[roomauth]?.name) {
buf += `
${Config.groups[roomauth].name} (${roomauth})`;
}
const group = Users.globalAuth.get(userid);
if (Config.groups[group]?.name) {
buf += `
Global ${Config.groups[group].name} (${group})`;
}
if (Users.globalAuth.sectionLeaders.has(userid)) {
buf += `
Section Leader (${import_room_settings.RoomSections.sectionNames[Users.globalAuth.sectionLeaders.get(userid)]})`;
}
buf += `
`;
let atLeastOne = false;
const idPunishments = Punishments.userids.get(userid);
if (idPunishments) {
for (const p of idPunishments) {
const { type: punishType, id: punishUserid, reason } = p;
if (!user.can("alts") && !["LOCK", "BAN"].includes(punishType))
continue;
const punishDesc = Punishments.punishmentTypes.get(punishType)?.desc || punishType;
buf += `${punishDesc}: ${punishUserid}`;
const expiresIn = Punishments.checkLockExpiration(userid);
if (expiresIn)
buf += expiresIn;
if (reason)
buf += import_lib.Utils.html` (reason: ${reason})`;
buf += "
";
atLeastOne = true;
}
}
if (!user.can("alts") && !atLeastOne) {
const hasJurisdiction = room && user.can("mute", null, room) && Punishments.roomUserids.nestedHas(room.roomid, userid);
if (!hasJurisdiction) {
return this.errorReply("/checkpunishment - User not found.");
}
}
const punishments = Punishments.getRoomPunishments(targetUser || { id: userid });
if (punishments?.length) {
buf += `
Room punishments: `;
buf += punishments.map(([curRoom, curPunishment]) => {
const { type: punishType, id: punishUserid, expireTime, reason } = curPunishment;
let punishDesc = Punishments.roomPunishmentTypes.get(punishType)?.desc || punishType;
if (punishUserid !== userid)
punishDesc += ` as ${punishUserid}`;
const expiresIn = new Date(expireTime).getTime() - Date.now();
const expireString = Chat.toDurationString(expiresIn, { precision: 1 });
punishDesc += ` for ${expireString}`;
if (reason)
punishDesc += `: ${reason}`;
return `${curRoom} (${punishDesc})`;
}).join(", ");
atLeastOne = true;
}
if (!atLeastOne) {
buf += `This username has no punishments associated with it.`;
}
this.sendReplyBox(buf);
},
offlinewhoishelp: [
`/offlinewhois [username] - Get details on a username without requiring them to be online.`,
`Requires: trusted user. `
],
sbtl: "sharedbattles",
sharedbattles(target, room) {
this.checkCan("lock");
const [targetUsername1, targetUsername2] = target.split(",");
if (!targetUsername1 || !targetUsername2)
return this.parse(`/help sharedbattles`);
const user1 = Users.get(targetUsername1);
const user2 = Users.get(targetUsername2);
const userID1 = toID(targetUsername1);
const userID2 = toID(targetUsername2);
const battles = getCommonBattles(userID1, user1, userID2, user2, this.connection);
if (!battles.length)
return this.sendReply(`${targetUsername1} and ${targetUsername2} have no common battles.`);
this.sendReplyBox(import_lib.Utils.html`Common battles between ${targetUsername1} and ${targetUsername2}:
` + battles.map((id) => {
const shortId = id.startsWith("battle-") ? id.slice(7) : id;
return import_lib.Utils.html`${shortId}`;
}).join(" | "));
},
sharedbattleshelp: [`/sharedbattles [user1], [user2] - Finds recent battles common to [user1] and [user2]. Requires % @ ~`],
sp: "showpunishments",
showpunishments(target, room, user) {
room = this.requireRoom();
if (!room.persist) {
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: % @ # ~`],
sgp: "showglobalpunishments",
showglobalpunishments(target, room, user) {
this.checkCan("lock");
return this.parse(`/join view-globalpunishments`);
},
showglobalpunishmentshelp: [`/showpunishments - Shows the current global punishments. Requires: % @ # ~`],
async host(target, room, user, connection, cmd) {
if (!target)
return this.parse("/help host");
this.checkCan("alts");
target = target.trim();
if (!net.isIPv4(target))
return this.errorReply("You must pass a valid IPv4 IP to /host.");
const { dnsbl, host, hostType } = await IPTools.lookup(target);
const dnsblMessage = dnsbl ? ` [${dnsbl}]` : ``;
this.sendReply(`IP ${target}: ${host || "ERROR"} [${hostType}]${dnsblMessage}`);
},
hosthelp: [`/host [ip] - Gets the host for a given IP. Requires: % @ ~`],
searchip: "ipsearch",
ipsearchall: "ipsearch",
hostsearch: "ipsearch",
ipsearch(target, room, user, connection, cmd) {
if (!target.trim())
return this.parse(`/help ipsearch`);
this.checkCan("rangeban");
const [ipOrHost, roomid] = this.splitOne(target);
const targetRoom = roomid ? Rooms.get(roomid) : null;
if (typeof targetRoom === "undefined") {
return this.errorReply(`The room "${roomid}" does not exist.`);
}
const results = [];
const isAll = cmd === "ipsearchall";
if (/[a-z]/.test(ipOrHost)) {
this.sendReply(`Users with host ${ipOrHost}${targetRoom ? ` in the room ${targetRoom.title}` : ``}:`);
for (const curUser of Users.users.values()) {
if (results.length > 100 && !isAll)
break;
if (!curUser.latestHost?.endsWith(ipOrHost))
continue;
if (targetRoom && !curUser.inRooms.has(targetRoom.roomid))
continue;
results.push(`${curUser.connected ? ONLINE_SYMBOL : OFFLINE_SYMBOL} ${curUser.name}`);
}
} else if (IPTools.ipRegex.test(ipOrHost)) {
this.sendReply(`Users with IP ${ipOrHost}${targetRoom ? ` in the room ${targetRoom.title}` : ``}:`);
for (const curUser of Users.users.values()) {
if (!curUser.ips.some((ip) => ip === ipOrHost))
continue;
if (targetRoom && !curUser.inRooms.has(targetRoom.roomid))
continue;
results.push(`${curUser.connected ? ONLINE_SYMBOL : OFFLINE_SYMBOL} ${curUser.name}`);
}
} else if (IPTools.isValidRange(ipOrHost)) {
this.sendReply(`Users in IP range ${ipOrHost}${targetRoom ? ` in the room ${targetRoom.title}` : ``}:`);
const checker = IPTools.checker(ipOrHost);
for (const curUser of Users.users.values()) {
if (results.length > 100 && !isAll)
continue;
if (!curUser.ips.some((ip) => checker(ip)))
continue;
if (targetRoom && !curUser.inRooms.has(targetRoom.roomid))
continue;
results.push(`${curUser.connected ? ONLINE_SYMBOL : OFFLINE_SYMBOL} ${curUser.name}`);
}
} else {
return this.errorReply(`${ipOrHost} is not a valid IP, IP range, or host.`);
}
if (!results.length) {
return this.sendReply(`No users found.`);
}
this.sendReply(results.slice(0, 100).join("; "));
if (results.length > 100 && !isAll) {
this.sendReply(`More than 100 users found. Use /ipsearchall for the full list.`);
}
},
ipsearchhelp: [`/ipsearch [ip|range|host], (room) - Find all users with specified IP, IP range, or host. If a room is provided only users in the room will be shown. Requires: ~`],
checkchallenges(target, room, user) {
room = this.requireRoom();
if (!user.can("addhtml", null, room))
this.checkCan("ban", null, room);
if (!this.runBroadcast(true))
return;
if (!this.broadcasting) {
this.errorReply(`This command must be broadcast:`);
return this.parse(`/help checkchallenges`);
}
if (!target?.includes(","))
return this.parse(`/help checkchallenges`);
const { targetUser: user1, rest } = this.requireUser(target);
const { targetUser: user2, rest: rest2 } = this.requireUser(rest);
if (user1 === user2 || rest2)
return this.parse(`/help checkchallenges`);
if (!(user1.id in room.users) || !(user2.id in room.users)) {
return this.errorReply(`Both users must be in this room.`);
}
const chall = Ladders.challenges.search(user1.id, user2.id);
if (!chall) {
return this.sendReplyBox(import_lib.Utils.html`${user1.name} and ${user2.name} are not challenging each other.`);
}
const [from, to] = user1.id === chall.from ? [user1, user2] : [user2, user1];
this.sendReplyBox(import_lib.Utils.html`${from.name} is challenging ${to.name} in ${Dex.formats.get(chall.format).name}.`);
},
checkchallengeshelp: [`!checkchallenges [user1], [user2] - Check if the specified users are challenging each other. Requires: * @ # ~`],
/*********************************************************
* Client fallback
*********************************************************/
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}"`);
}
this.errorReply(`You're using a custom client that doesn't support the ignore command.`);
},
ignorehelp: [`/ignore [user] - Ignore the given [user].`],
/*********************************************************
* Data Search Dex
*********************************************************/
pstats: "data",
stats: "data",
dex: "data",
pokedex: "data",
data(target, room, user, connection, cmd) {
if (!this.runBroadcast())
return;
target = target.trim();
const gen = parseInt(cmd.substr(-1));
if (gen)
target += `, gen${gen}`;
const { dex, format, targets } = this.splitFormat(target, true, true);
let buffer = "";
target = targets.join(",");
const targetId = toID(target);
if (!targetId)
return this.parse("/help data");
const targetNum = parseInt(target);
if (!isNaN(targetNum) && `${targetNum}` === target) {
for (const pokemon of Dex.species.all()) {
if (pokemon.num === targetNum) {
target = pokemon.baseSpecies;
break;
}
}
}
const newTargets = dex.dataSearch(target);
const showDetails = cmd.startsWith("dt") || cmd === "details";
if (!newTargets?.length) {
return this.errorReply(`No Pok\xE9mon, item, move, ability or nature named '${target}' was found${Dex.gen > dex.gen ? ` in Gen ${dex.gen}` : ""}. (Check your spelling?)`);
}
for (const [i, newTarget] of newTargets.entries()) {
if (newTarget.isInexact && !i) {
buffer = `No Pok\xE9mon, 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.
`;
}
let details = {};
switch (newTarget.searchType) {
case "nature":
const nature = Dex.natures.get(newTarget.name);
buffer += `${nature.name} nature: `;
if (nature.plus) {
buffer += `+10% ${Dex.stats.names[nature.plus]}, -10% ${Dex.stats.names[nature.minus]}.`;
} else {
buffer += `No effect.`;
}
return this.sendReply(buffer);
case "pokemon":
let pokemon = dex.species.get(newTarget.name);
if (format?.onModifySpecies) {
pokemon = format.onModifySpecies.call({ dex, clampIntRange: import_lib.Utils.clampIntRange, toID }, pokemon) || pokemon;
}
let tierDisplay = room?.settings.dataCommandTierDisplay;
if (!tierDisplay && room?.battle) {
if (room.battle.format.includes("doubles") || room.battle.format.includes("vgc")) {
tierDisplay = "doubles tiers";
} else if (room.battle.format.includes("nationaldex")) {
tierDisplay = "National Dex tiers";
}
}
if (!tierDisplay)
tierDisplay = "tiers";
const displayedTier = tierDisplay === "tiers" ? pokemon.tier : tierDisplay === "doubles tiers" ? pokemon.doublesTier : tierDisplay === "National Dex tiers" ? pokemon.natDexTier : pokemon.num >= 0 ? String(pokemon.num) : pokemon.tier;
buffer += `|raw|${Chat.getDataPokemonHTML(pokemon, dex.gen, displayedTier)}
`;
if (showDetails) {
let weighthit = 20;
if (pokemon.weighthg >= 2e3) {
weighthit = 120;
} else if (pokemon.weighthg >= 1e3) {
weighthit = 100;
} else if (pokemon.weighthg >= 500) {
weighthit = 80;
} else if (pokemon.weighthg >= 250) {
weighthit = 60;
} else if (pokemon.weighthg >= 100) {
weighthit = 40;
}
details = {
"Dex#": String(pokemon.num),
Gen: String(pokemon.gen) || "CAP",
Height: `${pokemon.heightm} m`
};
details["Weight"] = `${pokemon.weighthg / 10} kg (${weighthit} BP)`;
const gmaxMove = pokemon.canGigantamax || dex.species.get(pokemon.changesFrom).canGigantamax;
if (gmaxMove && dex.gen === 8)
details["G-Max Move"] = gmaxMove;
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 = [];
for (const evoName of pokemon.evos) {
const evo = dex.species.get(evoName);
if (evo.gen <= dex.gen) {
const condition = evo.evoCondition ? ` ${evo.evoCondition}` : ``;
switch (evo.evoType) {
case "levelExtra":
evos.push(`${evo.name} (level-up${condition})`);
break;
case "levelFriendship":
evos.push(`${evo.name} (level-up with high Friendship${condition})`);
break;
case "levelHold":
evos.push(`${evo.name} (level-up holding ${evo.evoItem}${condition})`);
break;
case "useItem":
evos.push(`${evo.name} (${evo.evoItem})`);
break;
case "levelMove":
evos.push(`${evo.name} (level-up with ${evo.evoMove}${condition})`);
break;
case "other":
evos.push(`${evo.name} (${evo.evoCondition})`);
break;
case "trade":
evos.push(`${evo.name} (trade${evo.evoItem ? ` holding ${evo.evoItem}` : condition})`);
break;
default:
evos.push(`${evo.name} (${evo.evoLevel}${condition})`);
}
}
}
if (pokemon.prevo) {
details["Pre-Evolution"] = pokemon.prevo;
}
if (!evos.length) {
details[`Does Not Evolve`] = "";
} else {
details["Evolution"] = evos.join(", ");
}
}
break;
case "item":
const item = dex.items.get(newTarget.name);
buffer += `|raw|${Chat.getDataItemHTML(item)}
`;
if (showDetails) {
details = {
Gen: String(item.gen)
};
if (dex.gen >= 4) {
if (item.fling) {
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") {
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"] = String(item.naturalGift.basePower);
}
if (item.isNonstandard) {
details[`Unobtainable in Gen ${dex.gen}`] = "";
}
}
break;
case "move":
const move = dex.moves.get(newTarget.name);
buffer += `|raw|${Chat.getDataMoveHTML(move)}
`;
if (showDetails) {
details = {
Priority: String(move.priority),
Gen: String(move.gen) || "CAP"
};
const pastGensOnly = move.isNonstandard === "Past" && dex.gen >= 8 || move.isNonstandard === "Gigantamax" && dex.gen !== 8;
if (pastGensOnly)
details["✗ Past Gens Only"] = "";
if (move.secondary || move.secondaries || move.hasSheerForce)
details["✓ Boosted by Sheer Force"] = "";
if (move.flags["contact"] && dex.gen >= 3)
details["✓ Contact"] = "";
if (move.flags["sound"] && dex.gen >= 3)
details["✓ Sound"] = "";
if (move.flags["bullet"] && dex.gen >= 6)
details["✓ Bullet"] = "";
if (move.flags["pulse"] && dex.gen >= 6)
details["✓ Pulse"] = "";
if (!move.flags["protect"] && move.target !== "self")
details["✓ Bypasses Protect"] = "";
if (move.flags["bypasssub"])
details["✓ Bypasses Substitutes"] = "";
if (move.flags["defrost"])
details["✓ Thaws user"] = "";
if (move.flags["bite"] && dex.gen >= 6)
details["✓ Bite"] = "";
if (move.flags["punch"] && dex.gen >= 4)
details["✓ Punch"] = "";
if (move.flags["powder"] && dex.gen >= 6)
details["✓ Powder"] = "";
if (move.flags["reflectable"] && dex.gen >= 3)
details["✓ Bounceable"] = "";
if (move.flags["charge"])
details["✓ Two-turn move"] = "";
if (move.flags["recharge"])
details["✓ Has recharge turn"] = "";
if (move.flags["gravity"] && dex.gen >= 4)
details["✗ Suppressed by Gravity"] = "";
if (move.flags["dance"] && dex.gen >= 7)
details["✓ Dance move"] = "";
if (move.flags["slicing"] && dex.gen >= 9)
details["✓ Slicing move"] = "";
if (move.flags["wind"] && dex.gen >= 9)
details["✓ Wind move"] = "";
if (dex.gen >= 7) {
if (move.gen >= 8 && move.isMax) {
} else if (move.zMove?.basePower) {
details["Z-Power"] = String(move.zMove.basePower);
} else if (move.zMove?.effect) {
const zEffects = {
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"
};
let h;
for (h in boost) {
details["Z-Effect"] += ` ${stats[h]} +${boost[h]}`;
}
} else if (move.isZ && typeof move.isZ === "string") {
details["✓ Z-Move"] = "";
const zCrystal = dex.items.get(move.isZ);
details["Z-Crystal"] = zCrystal.name;
if (zCrystal.itemUser) {
details["User"] = zCrystal.itemUser.join(", ");
details["Required Move"] = dex.items.get(move.isZ).zMoveFrom;
}
} else {
details["Z-Effect"] = "None";
}
}
if (move.isMax) {
details["✓ Max Move"] = "";
if (typeof move.isMax === "string")
details["User"] = `${move.isMax}`;
} else if (dex.gen === 8 && move.maxMove?.basePower) {
details["Dynamax Power"] = String(move.maxMove.basePower);
}
const targetTypes = {
normal: "One Adjacent Pok\xE9mon",
self: "User",
adjacentAlly: "One Ally",
adjacentAllyOrSelf: "User or Ally",
adjacentFoe: "One Adjacent Opposing Pok\xE9mon",
allAdjacentFoes: "All Adjacent Opponents",
foeSide: "Opposing Side",
allySide: "User's Side",
allyTeam: "User's Team",
allAdjacent: "All Adjacent Pok\xE9mon",
any: "Any Pok\xE9mon",
all: "All Pok\xE9mon",
scripted: "Chosen Automatically",
randomNormal: "Random Adjacent Opposing Pok\xE9mon",
allies: "User and Allies"
};
details["Target"] = targetTypes[move.target] || "Unknown";
if (move.id === "snatch" && dex.gen >= 3) {
details[`Non-Snatchable Moves`] = "";
}
if (move.id === "mirrormove") {
details[`Non-Mirrorable Moves`] = "";
}
if (move.isNonstandard === "Unobtainable") {
details[`Unobtainable in Gen ${dex.gen}`] = "";
}
}
break;
case "ability":
const ability = dex.abilities.get(newTarget.name);
buffer += `|raw|${Chat.getDataAbilityHTML(ability)}
`;
if (showDetails) {
details = {
Gen: String(ability.gen) || "CAP"
};
if (ability.flags["cantsuppress"])
details["✓ Not affected by Gastro Acid"] = "";
if (ability.flags["breakable"])
details["✓ Ignored by Mold Breaker"] = "";
}
break;
default:
throw new Error(`Unrecognized searchType`);
}
if (showDetails) {
buffer += `|raw|${Object.entries(details).map(([detail, value]) => value === "" ? detail : `${detail}: ${value}`).join(" |  ")}
`;
}
}
this.sendReply(buffer);
},
datahelp: [
`/data [pokemon/item/move/ability/nature] - Get details on this pokemon/item/move/ability/nature.`,
`/data [pokemon/item/move/ability/nature], Gen [generation number/format name] - Get details on this pokemon/item/move/ability/nature for that generation/format.`,
`!data [pokemon/item/move/ability/nature] - Show everyone these details. Requires: + % @ # ~`
],
dt: "details",
dt1: "details",
dt2: "details",
dt3: "details",
dt4: "details",
dt5: "details",
dt6: "details",
dt7: "details",
dt8: "details",
dt9: "details",
details(target) {
if (!target)
return this.parse("/help details");
this.run("data");
},
detailshelp() {
this.sendReplyBox(
`/details [Pok\xE9mon/item/move/ability/nature]
: get additional details on this Pok\xE9mon/item/move/ability/nature./details [Pok\xE9mon/item/move/ability/nature], Gen [generation number]
: get details on this Pok\xE9mon/item/move/ability/nature in that generation.
You can also append the generation number to /dt
; for example, /dt1 Mewtwo
gets details on Mewtwo in Gen 1./details [Pok\xE9mon/item/move/ability/nature], [format]
: get details on this Pok\xE9mon/item/move/ability/nature in that format.!details [Pok\xE9mon/item/move/ability/nature]
: show everyone these details. Requires: + % @ # ~`
);
},
weaknesses: "weakness",
weak: "weakness",
resist: "weakness",
weakness(target, room, user) {
if (!target)
return this.parse("/help weakness");
if (!this.runBroadcast())
return;
const { format, dex, targets } = this.splitFormat(target.split(/[,/]/).map(toID));
let isInverse = false;
if (format && Dex.formats.getRuleTable(format).has("inversemod")) {
isInverse = true;
} else if (targets[targets.length - 1] === "inverse") {
isInverse = true;
targets.pop();
}
const originalSearch = target;
let imperfectMatch = false;
let isMatch = false;
let species = dex.species.get(targets[0]);
let type1 = dex.types.get(targets[0]);
let type2 = dex.types.get(targets[1]);
let type3 = dex.types.get(targets[2]);
if (species.name !== "" && !species.exists && type1.name !== "" && !type1.exists) {
const typeSearchResults = dex.dataSearch(targets[0], ["TypeChart"]);
const speciesSearchResults = dex.dataSearch(targets[0], ["Pokedex"]);
if (typeSearchResults && typeSearchResults[0].name !== "") {
type1 = dex.types.get(typeSearchResults[0].name);
imperfectMatch = true;
} else if (speciesSearchResults && speciesSearchResults[0].name !== "") {
species = dex.species.get(speciesSearchResults[0].name);
imperfectMatch = true;
} else {
return this.sendReplyBox(import_lib.Utils.html`${originalSearch} isn't a recognized type or Pokemon${Dex.gen > dex.gen ? ` in Gen ${dex.gen}` : ""}.`);
}
}
if (type2.name !== "" && !type2.exists) {
const searchResults = dex.dataSearch(targets[1], ["TypeChart"]);
if (searchResults && searchResults[0].name !== "") {
type2 = dex.types.get(searchResults[0].name);
imperfectMatch = true;
} else {
return this.sendReplyBox(import_lib.Utils.html`${originalSearch} isn't a recognized type or Pokemon${Dex.gen > dex.gen ? ` in Gen ${dex.gen}` : ""}.`);
}
}
if (type3.name !== "" && !type3.exists) {
const searchResults = dex.dataSearch(targets[2], ["TypeChart"]);
if (searchResults && searchResults[0].name !== "") {
type3 = dex.types.get(searchResults[0].name);
imperfectMatch = true;
} else {
return this.sendReplyBox(import_lib.Utils.html`${originalSearch} isn't a recognized type or Pokemon${Dex.gen > dex.gen ? ` in Gen ${dex.gen}` : ""}.`);
}
}
const types = [];
if (species.exists) {
for (const type of species.types) {
types.push(type);
}
target = species.name;
isMatch = true;
} else if (type1.exists) {
types.push(type1.name);
target = type1.name;
isMatch = true;
}
let alreadyFoundType2 = false;
let alreadyFoundType3 = false;
if (types.toString().toLowerCase().includes(type2.name.toLowerCase())) {
alreadyFoundType2 = true;
}
if (types.toString().toLowerCase().includes(type3.name.toLowerCase())) {
alreadyFoundType3 = true;
}
if (isMatch) {
const searchTarget = [];
searchTarget.push(target);
if (type2.exists && !alreadyFoundType2) {
types.push(type2.name);
searchTarget.push(type2.name);
}
if (type3.exists && !alreadyFoundType3) {
types.push(type3.name);
searchTarget.push(type3.name);
}
target = searchTarget.join("/");
}
if (imperfectMatch) {
this.sendReply(`No Pok\xE9mon or type named '${originalSearch}' was found${Dex.gen > dex.gen ? ` in Gen ${dex.gen}` : ""}. Searching for '${target}' instead.`);
}
const weaknesses = [];
const resistances = [];
const immunities = [];
for (const type of dex.types.names()) {
const notImmune = dex.getImmunity(type, types);
if (notImmune || isInverse) {
let typeMod = !notImmune && isInverse ? 1 : 0;
typeMod += (isInverse ? -1 : 1) * dex.getEffectiveness(type, types);
switch (typeMod) {
case 1:
weaknesses.push(type);
break;
case 2:
weaknesses.push(`${type}`);
break;
case 3:
weaknesses.push(`${type}`);
break;
case -1:
resistances.push(type);
break;
case -2:
resistances.push(`${type}`);
break;
case -3:
resistances.push(`${type}`);
break;
}
} else {
immunities.push(type);
}
}
const statuses = {
brn: "Burn",
frz: "Frozen",
hail: "Hail damage",
par: "Paralysis",
powder: "Powder moves",
prankster: "Prankster",
sandstorm: "Sandstorm damage",
tox: "Toxic",
trapped: "Trapping"
};
for (const status in statuses) {
if (!dex.getImmunity(status, types)) {
immunities.push(statuses[status]);
}
}
const buffer = [];
buffer.push(`${species.exists ? `${target} (ignoring abilities):` : `${target}:`}`);
buffer.push(` : ${weaknesses.join(", ") || "None"}`);
buffer.push(` : ${resistances.join(", ") || "None"}`);
buffer.push(` : ${immunities.join(", ") || "None"}`);
this.sendReplyBox(buffer.join("
"));
},
weaknesshelp: [
`/weakness [pokemon] - Provides a Pok\xE9mon's resistances, weaknesses, and immunities, ignoring abilities.`,
`/weakness [type 1], [type 2] - Provides a type or type combination's resistances, weaknesses, and immunities, ignoring abilities.`,
`/weakness [pokemon], [type 1], [type 2] - Provides a Pok\xE9mon's type and type combination's resistances, weaknesses, and immunities, ignoring abilities.`,
`!weakness [pokemon] - Shows everyone a Pok\xE9mon's resistances, weaknesses, and immunities, ignoring abilities. Requires: + % @ # &`,
`!weakness [type 1], [type 2] - Shows everyone a type or type combination's resistances, weaknesses, and immunities, ignoring abilities. Requires: + % @ # &`,
`!weakness [pokemon], [type 1], [type 2] - Shows everyone a Pok\xE9mon's type and type combination's resistances, weaknesses, and immunities, ignoring abilities. Requires: + % @ # &`
],
eff: "effectiveness",
type: "effectiveness",
matchup: "effectiveness",
effectiveness(target, room, user) {
const { dex, targets } = this.splitFormat(target.split(/[,/]/));
if (targets.length !== 2)
return this.errorReply("Attacker and defender must be separated with a comma.");
let searchMethods = ["types", "moves", "species"];
const sourceMethods = ["types", "moves"];
const targetMethods = ["types", "species"];
let source;
let defender;
let foundData;
let atkName;
let defName;
for (let i = 0; i < 2; ++i) {
let method;
for (const m of searchMethods) {
foundData = dex[m].get(targets[i]);
if (foundData.exists) {
method = m;
break;
}
}
if (!foundData.exists)
return this.parse("/help effectiveness");
if (!source && sourceMethods.includes(method)) {
if (foundData.type) {
source = foundData;
atkName = foundData.name;
} else {
source = foundData.name;
atkName = foundData.name;
}
searchMethods = targetMethods;
} else if (!defender && targetMethods.includes(method)) {
if (foundData.types) {
defender = foundData;
defName = `${foundData.name} (not counting abilities)`;
} else {
defender = { types: [foundData.name] };
defName = foundData.name;
}
searchMethods = sourceMethods;
}
}
if (!this.runBroadcast())
return;
let factor = 0;
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?.call({ dex: Dex }, baseMod, null, type, source);
totalTypeMod += typeof moveMod === "number" ? moveMod : baseMod;
}
}
factor = 2 ** totalTypeMod;
}
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}`);
},
effectivenesshelp: [
`/effectiveness [attack], [defender] - Provides the effectiveness of a move or type on another type or a Pok\xE9mon.`,
`!effectiveness [attack], [defender] - Shows everyone the effectiveness of a move or type on another type or a Pok\xE9mon.`
],
cover: "coverage",
coverage(target, room, user) {
if (!this.runBroadcast())
return;
if (!target)
return this.parse("/help coverage");
const { dex, targets } = this.splitFormat(target.split(/[,+/]/));
const sources = [];
let dispTable = false;
const bestCoverage = {};
let hasThousandArrows = false;
for (const type of dex.types.names()) {
bestCoverage[type] = -5;
}
for (let arg of targets) {
arg = toID(arg);
if (arg === dex.currentMod)
continue;
if (arg === "table" || arg === "all") {
if (this.broadcasting)
return this.sendReplyBox("The full table cannot be broadcast.");
dispTable = true;
continue;
}
const argType = arg.charAt(0).toUpperCase() + arg.slice(1);
let eff;
if (dex.types.isName(argType)) {
sources.push(argType);
for (const type in bestCoverage) {
if (!dex.getImmunity(argType, type))
continue;
eff = dex.getEffectiveness(argType, type);
if (eff > bestCoverage[type])
bestCoverage[type] = eff;
}
continue;
}
const move = dex.moves.get(arg);
if (!move.exists) {
return this.errorReply(`Type or move '${arg}' not found.`);
} else if (move.gen > dex.gen) {
return this.errorReply(`Move '${arg}' is not available in Gen ${dex.gen}.`);
}
if (!move.basePower && !move.basePowerCallback)
continue;
if (move.id === "thousandarrows")
hasThousandArrows = true;
sources.push(move);
for (const type in bestCoverage) {
if (move.id === "struggle") {
eff = 0;
} else {
if (!dex.getImmunity(move.type, type) && !move.ignoreImmunity)
continue;
const baseMod = dex.getEffectiveness(move, type);
const moveMod = move.onEffectiveness?.call({ dex }, baseMod, null, type, move);
eff = typeof moveMod === "number" ? moveMod : baseMod;
}
if (eff > bestCoverage[type])
bestCoverage[type] = eff;
}
}
if (sources.length === 0)
return this.errorReply("No moves using a type table for determining damage were specified.");
if (sources.length > 4)
return this.errorReply("Specify a maximum of 4 moves or types.");
for (const type in bestCoverage) {
if (bestCoverage[type] === -5) {
bestCoverage[type] = 0;
continue;
}
bestCoverage[type] = 2 ** bestCoverage[type];
}
if (!dispTable) {
const buffer = [];
const superEff = [];
const neutral = [];
const resists = [];
const immune = [];
for (const type in bestCoverage) {
if (bestCoverage[type] === 0) {
immune.push(type);
} else if (bestCoverage[type] < 1) {
resists.push(type);
} else if (bestCoverage[type] > 1) {
superEff.push(type);
} else {
neutral.push(type);
}
}
buffer.push(`Coverage for ${sources.join(" + ")}:`);
buffer.push(`Super Effective: ${superEff.join(", ") || "None"}`);
buffer.push(` : ${neutral.join(", ") || "None"}`);
buffer.push(` : ${resists.join(", ") || "None"}`);
buffer.push(` : ${immune.join(", ") || "None"}`);
return this.sendReplyBox(buffer.join("
"));
} else {
let buffer = '
'; const icon = {}; for (const type of dex.types.names()) { icon[type] = ` | ${icon[type]} | `; } buffer += "
---|---|
${icon[type1]} | `; for (const type2 of dex.types.names()) { let typing; let cell = "bestEff) bestEff = curEff; } if (bestEff === -5) { bestEff = 0; } else { bestEff = 2 ** bestEff; } } if (bestEff === 0) { cell += `bgcolor=#666666 title="${typing}">${bestEff}`; } else if (bestEff < 1) { cell += `bgcolor=#AA5544 title="${typing}">${bestEff}`; } else if (bestEff > 1) { cell += `bgcolor=#559955 title="${typing}">${bestEff}`; } else { cell += `bgcolor=#6688AA title="${typing}">${bestEff}`; } cell += " | "; buffer += cell; } } buffer += "
${sections[sectionId].name} | `); for (const section of sections[sectionId].formats) { const subformat = Dex.formats.get(section); const nameHTML = import_lib.Utils.escapeHTML(subformat.name); const desc = subformat.desc ? [subformat.desc] : []; const data = await getFormatResources(subformat.id); if (data) { for (const { resource_name, url } of data.resources) { desc.push(`• ${resource_name}`); } } const descHTML = desc.length ? desc.join("|
---|---|
${nameHTML} | ${descHTML} |
/tour join
in the room's chat. You can check if your team is legal for the tournament by clicking the Validate button once you've joined and selected a team. To battle your opponent in the tournament, click the Ready! button when it appears. There are two different types of room tournaments: elimination (if a user loses more than a certain number of times, they are eliminated) and round robin (all users play against each other, and the user with the most wins is the winner).`);
}
if (showAll || ["vpn", "proxy"].includes(target)) {
buffer.push(`${this.tr`Proxy lock help`}`);
}
if (showAll || ["ca", "customavatar", "customavatars"].includes(target)) {
buffer.push(this.tr`Custom avatars are given to Global Staff members, contributors (coders and spriters) to Pokemon Showdown, and Smogon badgeholders at the discretion of the PS! Administrators. They are also sometimes given out as rewards for major events such as PSPL (Pokemon Showdown Premier League). If you're curious, you can view the entire list of custom avatars.`);
}
if (showAll || ["privacy", "private"].includes(target)) {
buffer.push(`${this.tr`Pokémon Showdown privacy policy`}`);
}
if (showAll || ["lostpassword", "password", "lostpass"].includes(target)) {
buffer.push(`If you need your Pok\xE9mon Showdown password reset, you can fill out a ${this.tr`Password Reset Form`}. You will need to make a Smogon account to be able to fill out a form; that's what the email address you sign in to Smogon with is for (PS accounts for regular users don't have emails associated with them).`);
}
if (!buffer.length && target) {
this.errorReply(`'${target}' is an invalid FAQ.`);
return this.parse(`/help faq`);
}
if (!target || showAll) {
buffer.unshift(`${this.tr`Frequently Asked Questions`}`);
}
this.sendReplyBox(buffer.join(`${fullFormat}
/challenge [user],${fullFormat}
to challenge someone with it!`
);
}
},
adminhelp(target, room, user) {
this.checkCan("rangeban");
let cmds = Chat.allCommands();
const canExecute = (perm) => !// gotta make sure no lower group has it
Object.values(Config.groups).slice(1).some((f) => f[perm]);
cmds = cmds.filter(
(f) => f.requiredPermission && canExecute(f.requiredPermission) && f.fullCmd !== this.handler?.fullCmd
);
cmds = import_lib.Utils.sortBy(cmds, (f) => f.fullCmd);
let namespaces = /* @__PURE__ */ new Map();
for (const cmd of cmds) {
const namespace = cmd.fullCmd.split(" ")[0];
const list = namespaces.get(namespace) || [];
list.push(cmd.fullCmd.trim());
namespaces.set(namespace, list);
}
let buf = `Admin commands:${text}
)` : ` (no help found)`;
}
}
buf += `Userid | ${showIPs ? `Latest IP | ` : ""}
---|---|
`; buf += `${id} | `; const ipStr = ips.map((f) => `${f}`).join(", "); buf += `${showIPs ? `${ipStr} | ` : ""}
IP | Times Used |
---|---|
${ip} | ${ipTable[ip]} |
Pokémon Showdown! supports custom rules in three ways:
`, `/challenge USERNAME, FORMAT @@@ RULES
/tour rules RULES
(see the Tournament command help)Bans are just a -
followed by the thing you want to ban.
- Arceus
: Ban a Pokémon (including all formes)- Arceus-Flying
or - Giratina-Altered
: Ban a Pokémon forme- Baton Pass
: Ban a move/item/ability/etc- OU
or - DUU
: Ban a tier- Mega
or - CAP
: Ban a Pokémon category- Blaziken + Speed Boost
: Ban a combination of things in a single Pokemon (you can have a Blaziken, and you can have Speed Boost on the same team, but the Blaziken can't have Speed Boost)- Drizzle ++ Swift Swim
: Ban a combination of things in a team (if any Pok\xE9mon on your team have Drizzle, no Pok\xE9mon can have Swift Swim)Using a +
instead of a -
unbans that category.
+ Blaziken
: Unban/unrestrict a Pokémon.The following rules can be added to challenges/tournaments to modify the style of play. Alternatively, already present rules can be removed from formats by preceding the rule name with !
However, some rules, like Obtainable
, are made of subrules, that can be individually turned on and off.
Rule Name | Description |
---|---|
${rule.name} | ${desc} |
![rule name]
.!! [Name] = [new value]
. For example, overriding the Min Source Gen on [Gen 8] VGC 2021 from 8 to 3 would look like !! Min Source Gen = 3
.Rule Name | Description |
---|---|
${rule.name} | ${desc} |