/**
* 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 license
*/
const RESULTS_MAX_LENGTH = 10;
var commands = {
ip: 'whois',
rooms: 'whois',
alt: 'whois',
alts: 'whois',
whoare: 'whois',
whois: function (target, room, user, connection, cmd) {
if (room.id === 'staff' && !this.canBroadcast()) return;
var targetUser = this.targetUserOrSelf(target, user.group === ' ');
if (!targetUser) {
return this.sendReply("User " + this.targetUsername + " not found.");
}
var showAll = (cmd === 'ip' || cmd === 'whoare' || cmd === 'alt' || cmd === 'alts');
if (showAll && !user.can('lock') && targetUser !== user) {
return this.errorReply("/alts - Access denied.");
}
var buf = '' + targetUser.group + '' + Tools.escapeHTML(targetUser.name) + ' ' + (!targetUser.connected ? ' (offline)' : '');
if (Config.groups[targetUser.group] && Config.groups[targetUser.group].name) {
buf += "
" + Config.groups[targetUser.group].name + " (" + targetUser.group + ")";
}
if (targetUser.isSysop) {
buf += "
(Pokémon Showdown System Operator)";
}
if (!targetUser.registered) {
buf += "
(Unregistered)";
}
var publicrooms = "";
var hiddenrooms = "";
var privaterooms = "";
for (var i in targetUser.roomCount) {
if (i === 'global') continue;
var targetRoom = Rooms.get(i);
var output = (targetRoom.auth && targetRoom.auth[targetUser.userid] ? targetRoom.auth[targetUser.userid] : '') + '' + i + '';
if (targetRoom.isPrivate === true) {
if (privaterooms) privaterooms += " | ";
privaterooms += output;
} else if (targetRoom.isPrivate) {
if (hiddenrooms) hiddenrooms += " | ";
hiddenrooms += output;
} else {
if (publicrooms) publicrooms += " | ";
publicrooms += output;
}
}
buf += '
Rooms: ' + (publicrooms || '(no public rooms)');
if (!showAll) {
return this.sendReplyBox(buf);
}
buf += '
';
if (user.can('alts', targetUser) || user.can('alts') && user === targetUser) {
var alts = targetUser.getAlts(true);
var output = Object.keys(targetUser.prevNames).join(", ");
if (output) buf += "
Previous names: " + Tools.escapeHTML(output);
for (var j = 0; j < alts.length; ++j) {
var targetAlt = Users.get(alts[j]);
if (!targetAlt.named && !targetAlt.connected) continue;
if (targetAlt.group === '~' && user.group !== '~') continue;
buf += '
Alt: ' + Tools.escapeHTML(targetAlt.name) + '' + (!targetAlt.connected ? " (offline)" : "");
output = Object.keys(targetAlt.prevNames).join(", ");
if (output) buf += "
Previous names: " + output;
}
if (targetUser.locked) {
buf += '
Locked: ' + targetUser.locked;
switch (targetUser.locked) {
case '#dnsbl':
buf += " - IP is in a DNS-based blacklist";
break;
case '#range':
buf += " - IP or host is in a temporary range-lock";
break;
case '#hostfilter':
buf += " - host is permanently locked for being a proxy";
break;
}
}
if (targetUser.semilocked) {
buf += '
Semilocked: ' + targetUser.semilocked;
}
}
if ((user.can('ip', targetUser) || user === targetUser)) {
var ips = Object.keys(targetUser.ips);
buf += "
IP" + ((ips.length > 1) ? "s" : "") + ": " + ips.join(", ") +
(user.group !== ' ' && targetUser.latestHost ? "
Host: " + Tools.escapeHTML(targetUser.latestHost) : "");
}
if ((user === targetUser || user.can('alts')) && hiddenrooms) {
buf += '
Hidden rooms: ' + hiddenrooms;
}
if ((user === targetUser || user.hasConsoleAccess(connection)) && privaterooms) {
buf += '
Private rooms: ' + privaterooms;
}
this.sendReplyBox(buf);
},
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."],
host: function (target, room, user, connection, cmd) {
if (!target) return this.parse('/help host');
if (!this.can('rangeban')) return;
if (!/[0-9.]+/.test(target)) return this.sendReply('You must pass a valid IPv4 IP to /host.');
var self = this;
Dnsbl.reverse(target, function (err, hosts) {
self.sendReply('IP ' + target + ': ' + (hosts ? hosts[0] : 'NULL'));
});
},
hosthelp: ["/host [ip] - Gets the host for a given IP. Requires: & ~"],
ipsearchall: 'ipsearch',
hostsearch: 'ipsearch',
ipsearch: function (target, room, user, connection, cmd) {
if (!target.trim()) return this.parse('/help ipsearch');
if (!this.can('rangeban')) return;
var results = [];
var isAll = (cmd === 'ipsearchall');
if (/[a-z]/.test(target)) {
// host
this.sendReply("Users with host " + target + ":");
for (var userid in Users.users) {
var curUser = Users.users[userid];
if (!curUser.latestHost || !curUser.latestHost.endsWith(target)) continue;
if (results.push((curUser.connected ? " \u25C9 " : " \u25CC ") + " " + curUser.name) > 100 && !isAll) {
return this.sendReply("More than 100 users match the specified IP range. Use /ipsearchall to retrieve the full list.");
}
}
} else if (target.slice(-1) === '*') {
// IP range
this.sendReply("Users in IP range " + target + ":");
target = target.slice(0, -1);
for (var userid in Users.users) {
var curUser = Users.users[userid];
if (!curUser.latestIp.startsWith(target)) continue;
if (results.push((curUser.connected ? " \u25C9 " : " \u25CC ") + " " + curUser.name) > 100 && !isAll) {
return this.sendReply("More than 100 users match the specified IP range. Use /ipsearchall to retrieve the full list.");
}
}
} else {
this.sendReply("Users with IP " + target + ":");
for (var userid in Users.users) {
var curUser = Users.users[userid];
if (curUser.latestIp === target) {
results.push((curUser.connected ? " \u25C9 " : " \u25CC ") + " " + curUser.name);
}
}
}
if (!results.length) return this.sendReply("No results found.");
return this.sendReply(results.join('; '));
},
ipsearchhelp: ["/ipsearch [ip|range|host] - Find all users with specified IP, IP range, or host. Requires: & ~"],
/*********************************************************
* Shortcuts
*********************************************************/
inv: 'invite',
invite: function (target, room, user) {
if (!target) return this.parse('/help invite');
target = this.splitTarget(target);
if (!this.targetUser) {
return this.sendReply("User " + this.targetUsername + " not found.");
}
var targetRoom = (target ? Rooms.search(target) : room);
if (!targetRoom) {
return this.sendReply("Room " + target + " not found.");
}
return this.parse('/msg ' + this.targetUsername + ', /invite ' + targetRoom.id);
},
invitehelp: ["/invite [username], [roomname] - Invites the player [username] to join the room [roomname]."],
/*********************************************************
* Data Search Tools
*********************************************************/
pstats: 'data',
stats: 'data',
dex: 'data',
pokedex: 'data',
data: function (target, room, user, connection, cmd) {
if (!this.canBroadcast()) return;
var buffer = '';
var targetId = toId(target);
if (!targetId) return this.parse('/help data');
if (targetId === '' + parseInt(targetId)) {
for (var p in Tools.data.Pokedex) {
var pokemon = Tools.getTemplate(p);
if (pokemon.num === parseInt(target)) {
target = pokemon.species;
targetId = pokemon.id;
break;
}
}
}
var newTargets = Tools.dataSearch(target);
var showDetails = (cmd === 'dt' || cmd === 'details');
if (newTargets && newTargets.length) {
for (var i = 0; i < newTargets.length; ++i) {
if (newTargets[i].id !== targetId && !Tools.data.Aliases[targetId] && !i) {
buffer = "No Pok\u00e9mon, item, move, ability or nature named '" + target + "' was found. Showing the data of '" + newTargets[0].name + "' instead.\n";
}
if (newTargets[i].searchType === 'nature') {
buffer += "" + newTargets[i].name + " nature: ";
if (newTargets[i].plus) {
var statNames = {'atk': "Attack", 'def': "Defense", 'spa': "Special Attack", 'spd': "Special Defense", 'spe': "Speed"};
buffer += "+10% " + statNames[newTargets[i].plus] + ", -10% " + statNames[newTargets[i].minus] + ".";
} else {
buffer += "No effect.";
}
return this.sendReply(buffer);
} else {
buffer += '|c|~|/data-' + newTargets[i].searchType + ' ' + newTargets[i].name + '\n';
}
}
} else {
return this.sendReply("No Pok\u00e9mon, item, move, ability or nature named '" + target + "' was found. (Check your spelling?)");
}
if (showDetails) {
var details;
var isSnatch = false;
var isMirrorMove = false;
if (newTargets[0].searchType === 'pokemon') {
var pokemon = Tools.getTemplate(newTargets[0].name);
var weighthit = 20;
if (pokemon.weightkg >= 200) {
weighthit = 120;
} else if (pokemon.weightkg >= 100) {
weighthit = 100;
} else if (pokemon.weightkg >= 50) {
weighthit = 80;
} else if (pokemon.weightkg >= 25) {
weighthit = 60;
} else if (pokemon.weightkg >= 10) {
weighthit = 40;
}
details = {
"Dex#": pokemon.num,
"Gen": pokemon.gen,
"Height": pokemon.heightm + " m",
"Weight": pokemon.weightkg + " kg (" + weighthit + " BP)",
"Dex Colour": pokemon.color,
"Egg Group(s)": pokemon.eggGroups.join(", ")
};
if (!pokemon.evos.length) {
details["Does Not Evolve"] = "";
} else {
details["Evolution"] = pokemon.evos.map(function (evo) {
evo = Tools.getTemplate(evo);
return evo.name + " (" + evo.evoLevel + ")";
}).join(", ");
}
} else if (newTargets[0].searchType === 'move') {
var move = Tools.getMove(newTargets[0].name);
details = {
"Priority": move.priority,
"Gen": move.gen
};
if (move.secondary || move.secondaries) details["✓ Secondary effect"] = "";
if (move.flags['contact']) details["✓ Contact"] = "";
if (move.flags['sound']) details["✓ Sound"] = "";
if (move.flags['bullet']) details["✓ Bullet"] = "";
if (move.flags['pulse']) details["✓ Pulse"] = "";
if (!move.flags['protect'] && !/(ally|self)/i.test(move.target)) details["✓ Bypasses Protect"] = "";
if (move.flags['authentic']) details["✓ Bypasses Substitutes"] = "";
if (move.flags['defrost']) details["✓ Thaws user"] = "";
if (move.flags['bite']) details["✓ Bite"] = "";
if (move.flags['punch']) details["✓ Punch"] = "";
if (move.flags['powder']) details["✓ Powder"] = "";
if (move.flags['reflectable']) details["✓ Bounceable"] = "";
if (move.flags['gravity']) details["✗ Suppressed by Gravity"] = "";
if (move.id === 'snatch') isSnatch = true;
if (move.id === 'mirrormove') isMirrorMove = true;
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";
} else if (newTargets[0].searchType === 'item') {
var item = Tools.getItem(newTargets[0].name);
details = {
"Gen": item.gen
};
if (item.fling) {
details["Fling Base Power"] = 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.";
} else {
details["Fling"] = "This item cannot be used with Fling.";
}
if (item.naturalGift) {
details["Natural Gift Type"] = item.naturalGift.type;
details["Natural Gift Base Power"] = item.naturalGift.basePower;
}
} else {
details = {};
}
buffer += '|raw|' + Object.keys(details).map(function (detail) {
return '' + detail + (details[detail] !== '' ? ': ' + details[detail] : '');
}).join(" |  ") + '';
if (isSnatch) buffer += ' |  Snatchable Moves';
if (isMirrorMove) buffer += ' |  Mirrorable Moves';
}
this.sendReply(buffer);
},
datahelp: ["/data [pokemon/item/move/ability] - Get details on this pokemon/item/move/ability/nature.",
"!data [pokemon/item/move/ability] - Show everyone these details. Requires: + % @ # & ~"],
dt: 'details',
details: function (target) {
if (!target) return this.parse('/help details');
this.run('data');
},
detailshelp: ["/details [pokemon] - Get additional details on this pokemon/item/move/ability/nature.",
"!details [pokemon] - Show everyone these details. Requires: + % @ # & ~"],
ds: 'dexsearch',
dsearch: 'dexsearch',
dexsearch: function (target, room, user, connection, cmd, message) {
if (!this.canBroadcast()) return;
if (!target) return this.parse('/help dexsearch');
var targets = target.split(',');
var searches = {};
var allTiers = {'uber':1, 'ou':1, 'bl':1, 'uu':1, 'bl2':1, 'ru':1, 'bl3':1, 'nu':1, 'bl4':1, 'pu':1, 'nfe':1, 'lc uber':1, 'lc':1, 'cap':1};
var allColours = {'green':1, 'red':1, 'blue':1, 'white':1, 'brown':1, 'yellow':1, 'purple':1, 'pink':1, 'gray':1, 'black':1};
var allStats = {'hp':1, 'atk':1, 'def':1, 'spa':1, 'spd':1, 'spe':1, 'bst':1};
var showAll = false;
var megaSearch = null;
var randomOutput = 0;
var categories = ['gen', 'tier', 'color', 'types', 'ability', 'stats', 'compileLearnsets', 'moves', 'recovery', 'priority'];
for (var i = 0; i < targets.length; i++) {
var isNotSearch = false;
target = targets[i].trim().toLowerCase();
if (target.charAt(0) === '!') {
isNotSearch = true;
target = target.substr(1);
}
var targetAbility = Tools.getAbility(targets[i]);
if (targetAbility.exists) {
if (!searches['ability']) searches['ability'] = {};
if (Object.count(searches['ability'], true) === 1 && !isNotSearch) return this.sendReplyBox("Specify only one ability.");
if ((searches['ability'][targetAbility.name] && isNotSearch) || (searches['ability'][targetAbility.name] === false && !isNotSearch)) return this.sendReplyBox("A search cannot both exclude and include an ability.");
searches['ability'][targetAbility.name] = !isNotSearch;
continue;
}
if (target in allTiers) {
if (!searches['tier']) searches['tier'] = {};
if ((searches['tier'][target] && isNotSearch) || (searches['tier'][target] === false && !isNotSearch)) return this.sendReplyBox('A search cannot both exclude and include a tier.');
searches['tier'][target] = !isNotSearch;
continue;
}
if (target in allColours) {
if (!searches['color']) searches['color'] = {};
if ((searches['color'][target] && isNotSearch) || (searches['color'][target] === false && !isNotSearch)) return this.sendReplyBox('A search cannot both exclude and include a color.');
searches['color'][target] = !isNotSearch;
continue;
}
if (target.substr(0, 3) === 'gen' && Number.isInteger(parseFloat(target.substr(3)))) target = target.substr(3).trim();
var targetInt = parseInt(target);
if (0 < targetInt && targetInt < 7) {
if (!searches['gen']) searches['gen'] = {};
if ((searches['gen'][target] && isNotSearch) || (searches['gen'][target] === false && !isNotSearch)) return this.sendReplyBox('A search cannot both exclude and include a generation.');
searches['gen'][target] = !isNotSearch;
continue;
}
if (target === 'all') {
if (this.broadcasting) return this.sendReplyBox("A search with the parameter 'all' cannot be broadcast.");
showAll = true;
continue;
}
if (target.substr(0, 6) === 'random' && cmd === 'randpoke') {
randomOutput = parseInt(target.substr(6));
continue;
}
if (target === 'megas' || target === 'mega') {
if ((megaSearch && isNotSearch) || (megaSearch === false && !isNotSearch)) return this.sendReplyBox('A search cannot both exclude and include Mega Evolutions.');
megaSearch = !isNotSearch;
continue;
}
if (target === 'recovery') {
if ((searches['recovery'] && isNotSearch) || (searches['recovery'] === false && !isNotSearch)) return this.sendReplyBox('A search cannot both exclude and recovery moves.');
searches['recovery'] = !isNotSearch;
continue;
}
if (target === 'priority') {
if ((searches['priority'] && isNotSearch) || (searches['priority'] === false && !isNotSearch)) return this.sendReplyBox('A search cannot both exclude and recovery moves.');
searches['priority'] = !isNotSearch;
continue;
}
var targetMove = Tools.getMove(target);
if (targetMove.exists) {
if (!searches['moves']) searches['moves'] = {};
if (Object.count(searches['moves'], true) === 4 && !isNotSearch) return this.sendReplyBox("Specify a maximum of 4 moves.");
if ((searches['moves'][targetMove.id] && isNotSearch) || (searches['moves'][targetMove.id] === false && !isNotSearch)) return this.sendReplyBox("A search cannot both exclude and include a move.");
searches['moves'][targetMove.id] = !isNotSearch;
continue;
}
var typeIndex = target.indexOf(' type');
if (typeIndex >= 0) {
target = target.charAt(0).toUpperCase() + target.substring(1, typeIndex);
if (target in Tools.data.TypeChart) {
if (!searches['types']) searches['types'] = {};
if (Object.count(searches['types'], true) === 2 && !isNotSearch) return this.sendReplyBox("Specify a maximum of two types.");
if ((searches['types'][target] && isNotSearch) || (searches['types'][target] === false && !isNotSearch)) return this.sendReplyBox("A search cannot both exclude and include a type.");
searches['types'][target] = !isNotSearch;
continue;
}
}
var inequality = target.search(/>|<|=/);
if (inequality >= 0) {
if (isNotSearch) return this.sendReplyBox("You cannot use the negation symbol '!' in stat ranges.");
if (target.charAt(inequality + 1) === '=') {
inequality = target.substr(inequality, 2);
} else {
inequality = target.charAt(inequality);
}
var inequalityOffset = (inequality.charAt(1) === '=' ? 0 : -1);
var targetParts = target.replace(/\s/g, '').split(inequality);
var num, stat, direction;
if (!isNaN(targetParts[0])) {
// e.g. 100 < spe
num = parseFloat(targetParts[0]);
stat = targetParts[1];
switch (inequality.charAt(0)) {
case '>': direction = 'less'; num += inequalityOffset; break;
case '<': direction = 'greater'; num -= inequalityOffset; break;
case '=': direction = 'equal'; break;
}
} else if (!isNaN(targetParts[1])) {
// e.g. spe > 100
num = parseFloat(targetParts[1]);
stat = targetParts[0];
switch (inequality.charAt(0)) {
case '<': direction = 'less'; num += inequalityOffset; break;
case '>': direction = 'greater'; num -= inequalityOffset; break;
case '=': direction = 'equal'; break;
}
} else {
return this.sendReplyBox("No value given to compare with '" + Tools.escapeHTML(target) + "'.");
}
switch (toId(stat)) {
case 'attack': stat = 'atk'; break;
case 'defense': stat = 'def'; break;
case 'specialattack': stat = 'spa'; break;
case 'spatk': stat = 'spa'; break;
case 'specialdefense': stat = 'spd'; break;
case 'spdef': stat = 'spd'; break;
case 'speed': stat = 'spe'; break;
}
if (!(stat in allStats)) return this.sendReplyBox("'" + Tools.escapeHTML(target) + "' did not contain a valid stat.");
if (!searches['stats']) searches['stats'] = {};
if (direction === 'equal') {
if (searches['stats'][stat]) return this.sendReplyBox("Invalid stat range for " + stat + ".");
searches['stats'][stat] = {};
searches['stats'][stat]['less'] = num;
searches['stats'][stat]['greater'] = num;
} else {
if (!searches['stats'][stat]) searches['stats'][stat] = {};
if (searches['stats'][stat][direction]) return this.sendReplyBox("Invalid stat range for " + stat + ".");
searches['stats'][stat][direction] = num;
}
continue;
}
return this.sendReplyBox("'" + Tools.escapeHTML(target) + "' could not be found in any of the search categories.");
}
if (showAll && Object.size(searches) === 0 && megaSearch === null) return this.sendReplyBox("No search parameters other than 'all' were found. Try '/help dexsearch' for more information on this command.");
var dex = {};
for (var pokemon in Tools.data.Pokedex) {
var template = Tools.getTemplate(pokemon);
var megaSearchResult = (megaSearch === null || (megaSearch === true && template.isMega) || (megaSearch === false && !template.isMega));
if (template.tier !== 'Unreleased' && template.tier !== 'Illegal' && (template.tier !== 'CAP' || (searches['tier'] && searches['tier']['cap'])) && megaSearchResult) {
dex[pokemon] = template;
}
}
//Only construct full learnsets for Pokemon if learnsets are used in the search
if (searches.moves || searches.recovery || searches.priority) searches['compileLearnsets'] = true;
for (var cat = 0; cat < categories.length; cat++) {
var search = categories[cat];
if (!searches[search]) continue;
switch (search) {
case 'types':
for (var mon in dex) {
if (Object.count(searches[search], true) === 2) {
if (!(searches[search][dex[mon].types[0]]) || !(searches[search][dex[mon].types[1]])) delete dex[mon];
} else {
if (searches[search][dex[mon].types[0]] === false || searches[search][dex[mon].types[1]] === false || (Object.count(searches[search], true) > 0 &&
(!(searches[search][dex[mon].types[0]]) && !(searches[search][dex[mon].types[1]])))) delete dex[mon];
}
}
break;
case 'tier':
for (var mon in dex) {
if ('lc' in searches[search]) {
// some LC legal Pokemon are stored in other tiers (Ferroseed/Murkrow etc)
// this checks for LC legality using the going criteria, instead of dex[mon].tier
var isLC = (dex[mon].evos && dex[mon].evos.length > 0) && !dex[mon].prevo && dex[mon].tier !== "LC Uber" && Tools.data.Formats['lc'].banlist.indexOf(dex[mon].species) < 0;
if ((searches[search]['lc'] && !isLC) || (!searches[search]['lc'] && isLC)) {
delete dex[mon];
continue;
}
}
if (searches[search][String(dex[mon][search]).toLowerCase()] === false ||
Object.count(searches[search], true) > 0 && !searches[search][String(dex[mon][search]).toLowerCase()]) {
delete dex[mon];
}
}
break;
case 'gen':
case 'color':
for (var mon in dex) {
if (searches[search][String(dex[mon][search]).toLowerCase()] === false ||
Object.count(searches[search], true) > 0 && !searches[search][String(dex[mon][search]).toLowerCase()]) {
delete dex[mon];
}
}
break;
case 'ability':
for (var mon in dex) {
for (var ability in searches[search]) {
var needsAbility = searches[search][ability];
var hasAbility = Object.count(dex[mon].abilities, ability) > 0;
if (hasAbility !== needsAbility) {
delete dex[mon];
break;
}
}
}
break;
case 'compileLearnsets':
for (var mon in dex) {
var template = dex[mon];
if (!template.learnset) template = Tools.getTemplate(template.baseSpecies);
if (!template.learnset) continue;
var fullLearnset = template.learnset;
while (template.prevo) {
template = Tools.getTemplate(template.prevo);
for (var move in template.learnset) {
if (!fullLearnset[move]) fullLearnset[move] = template.learnset[move];
}
}
dex[mon].learnset = fullLearnset;
}
break;
case 'moves':
for (var mon in dex) {
if (!dex[mon].learnset) continue;
for (var move in searches[search]) {
var canLearn = (dex[mon].learnset.sketch && ['chatter', 'struggle', 'magikarpsrevenge'].indexOf(move) < 0) || dex[mon].learnset[move];
if ((!canLearn && searches[search][move]) || (searches[search][move] === false && canLearn)) {
delete dex[mon];
break;
}
}
}
break;
case 'recovery':
for (var mon in dex) {
if (!dex[mon].learnset) continue;
var recoveryMoves = ["recover", "roost", "moonlight", "morningsun", "synthesis", "milkdrink", "slackoff", "softboiled", "wish", "healorder"];
var canLearn = false;
for (var i = 0; i < recoveryMoves.length; i++) {
canLearn = (dex[mon].learnset.sketch) || dex[mon].learnset[recoveryMoves[i]];
if (canLearn) break;
}
if ((!canLearn && searches[search]) || (searches[search] === false && canLearn)) delete dex[mon];
}
break;
case 'priority':
var priorityMoves = [];
for (var move in Tools.data.Movedex) {
var moveData = Tools.getMove(move);
if (moveData.category === "Status" || moveData.id === "bide") continue;
if (moveData.priority > 0) priorityMoves.push(move);
}
for (var mon in dex) {
if (!dex[mon].learnset) continue;
var canLearn = false;
for (var i = 0; i < priorityMoves.length; i++) {
canLearn = (dex[mon].learnset.sketch) || dex[mon].learnset[priorityMoves[i]];
if (canLearn) break;
}
if ((!canLearn && searches[search]) || (searches[search] === false && canLearn)) delete dex[mon];
}
break;
case 'stats':
for (var stat in searches[search]) {
for (var mon in dex) {
var monStat = 0;
if (stat === 'bst') {
for (var monStats in dex[mon].baseStats) {
monStat += dex[mon].baseStats[monStats];
}
} else {
monStat = dex[mon].baseStats[stat];
}
if (typeof searches[search][stat].less === 'number') {
if (monStat > searches[search][stat].less) {
delete dex[mon];
continue;
}
}
if (typeof searches[search][stat].greater === 'number') {
if (monStat < searches[search][stat].greater) {
delete dex[mon];
continue;
}
}
}
}
break;
default:
throw new Error("/dexsearch search category '" + search + "' was unrecognised.");
}
}
var results = [];
for (var mon in dex) {
if (dex[mon].baseSpecies && results.indexOf(dex[mon].baseSpecies) >= 0) continue;
results.push(dex[mon].species);
}
if (randomOutput && randomOutput < results.length) {
results = results.randomize().slice(0, randomOutput);
}
var resultsStr = this.broadcasting ? "" : ("" + message + ":
");
if (results.length > 1) {
if (showAll || results.length <= RESULTS_MAX_LENGTH + 5) {
results.sort();
resultsStr += results.join(", ");
} else {
resultsStr += results.slice(0, RESULTS_MAX_LENGTH).join(", ") + ", and " + (results.length - RESULTS_MAX_LENGTH) + " more. Redo the search with 'all' as a search parameter to show all results.";
}
} else if (results.length === 1) {
return CommandParser.commands.data.call(this, results[0], room, user, connection, 'dt');
} else {
resultsStr += "No Pokémon found.";
}
return this.sendReplyBox(resultsStr);
},
dexsearchhelp: ["/dexsearch [type], [move], [move], ... - Searches for Pok\u00e9mon that fulfill the selected criteria",
"Search categories are: type, tier, color, moves, ability, gen, recovery, priority, stat.",
"Valid colors are: green, red, blue, white, brown, yellow, purple, pink, gray and black.",
"Valid tiers are: Uber/OU/BL/UU/BL2/RU/BL3/NU/PU/NFE/LC/CAP.",
"Types must be followed by ' type', e.g., 'dragon type'.",
"Inequality ranges use the characters '>' and '<' though they behave as '≥' and '≤', e.g., 'speed > 100' searches for all Pokemon equal to and greater than 100 speed.",
"Parameters can be excluded through the use of '!', e.g., '!water type' excludes all water types.",
"The parameter 'mega' can be added to search for Mega Evolutions only, and the parameter 'NFE' can be added to search not-fully evolved Pokemon only.",
"The order of the parameters does not matter."],
rollpokemon: 'randompokemon',
randpoke: 'randompokemon',
randompokemon: function (target, room, user, connection, cmd, message) {
var targets = target.split(",");
var targetsBuffer = [];
var qty;
for (var i = 0; i < targets.length; i++) {
if (!targets[i]) continue;
var num = Number(targets[i]);
if (Number.isInteger(num)) {
if (qty) return this.sendReply("Only specify the number of Pokemon once.");
qty = num;
if (qty < 1 || 15 < qty) return this.sendReply("Number of random Pokemon must be between 1 and 15.");
targetsBuffer.push("random" + qty);
} else {
targetsBuffer.push(targets[i]);
}
}
if (!qty) targetsBuffer.push("random1");
CommandParser.commands.dexsearch.call(this, targetsBuffer.join(","), room, user, connection, "randpoke", message);
},
randompokemonhelp: ["/randompokemon - Generates random Pokemon based on given search conditions.",
"/randompokemon uses the same parameters as /dexsearch (see '/help ds').",
"Adding a number as a parameter returns that many random Pokemon, e.g., '/randpoke 6' returns 6 random Pokemon."],
ms: 'movesearch',
msearch: 'movesearch',
movesearch: function (target, room, user, connection, cmd, message) {
if (!this.canBroadcast()) return;
if (!target) return this.parse('/help movesearch');
var targets = target.split(',');
var searches = {};
var allCategories = {'physical':1, 'special':1, 'status':1};
var allProperties = {'basePower':1, 'accuracy':1, 'priority':1, 'pp':1};
var allFlags = {'authentic':1, 'bite':1, 'bullet':1, 'contact':1, 'defrost':1, 'powder':1, 'pulse':1, 'punch':1, 'secondary':1, 'snatch':1, 'sound':1};
var allStatus = {'psn':1, 'tox':1, 'brn':1, 'par':1, 'frz':1, 'slp':1};
var allVolatileStatus = {'flinch':1, 'confusion':1, 'partiallytrapped':1};
var allBoosts = {'hp':1, 'atk':1, 'def':1, 'spa':1, 'spd':1, 'spe':1, 'accuracy':1, 'evasion':1};
var showAll = false;
var lsetData = {};
var targetMon = '';
for (var i = 0; i < targets.length; i++) {
var isNotSearch = false;
target = targets[i].toLowerCase().trim();
if (target.charAt(0) === '!') {
isNotSearch = true;
target = target.substr(1);
}
var typeIndex = target.indexOf(' type');
if (typeIndex >= 0) {
target = target.charAt(0).toUpperCase() + target.substring(1, typeIndex);
if (!(target in Tools.data.TypeChart)) return this.sendReplyBox("Type '" + Tools.escapeHTML(target) + "' not found.");
if (!searches['type']) searches['type'] = {};
if ((searches['type'][target] && isNotSearch) || (searches['type'][target] === false && !isNotSearch)) return this.sendReplyBox('A search cannot both exclude and include a type.');
searches['type'][target] = !isNotSearch;
continue;
}
if (target in allCategories) {
target = target.charAt(0).toUpperCase() + target.substr(1);
if (!searches['category']) searches['category'] = {};
if ((searches['category'][target] && isNotSearch) || (searches['category'][target] === false && !isNotSearch)) return this.sendReplyBox('A search cannot both exclude and include a category.');
searches['category'][target] = !isNotSearch;
continue;
}
if (target === 'bypassessubstitute') target = 'authentic';
if (target in allFlags) {
if (!searches['flags']) searches['flags'] = {};
if ((searches['flags'][target] && isNotSearch) || (searches['flags'][target] === false && !isNotSearch)) return this.sendReplyBox('A search cannot both exclude and include \'' + target + '\'.');
searches['flags'][target] = !isNotSearch;
continue;
}
if (target === 'all') {
if (this.broadcasting) return this.sendReplyBox("A search with the parameter 'all' cannot be broadcast.");
showAll = true;
continue;
}
if (target === 'recovery') {
if (!searches['recovery']) {
searches['recovery'] = !isNotSearch;
} else if ((searches['recovery'] && isNotSearch) || (searches['recovery'] === false && !isNotSearch)) {
return this.sendReplyBox('A search cannot both exclude and include recovery moves.');
}
continue;
}
var template = Tools.getTemplate(target);
if (template.exists) {
if (Object.size(lsetData) !== 0) return this.sendReplyBox("A search can only include one Pok\u00e9mon learnset.");
if (!template.learnset) template = Tools.getTemplate(template.baseSpecies);
lsetData = template.learnset;
targetMon = template.name;
while (template.prevo) {
template = Tools.getTemplate(template.prevo);
for (var move in template.learnset) {
if (!lsetData[move]) lsetData[move] = template.learnset[move];
}
}
continue;
}
var inequality = target.search(/>|<|=/);
if (inequality >= 0) {
if (isNotSearch) return this.sendReplyBox("You cannot use the negation symbol '!' in quality ranges.");
inequality = target.charAt(inequality);
var targetParts = target.replace(/\s/g, '').split(inequality);
var numSide, propSide, direction;
if (!isNaN(targetParts[0])) {
numSide = 0;
propSide = 1;
switch (inequality) {
case '>': direction = 'less'; break;
case '<': direction = 'greater'; break;
case '=': direction = 'equal'; break;
}
} else if (!isNaN(targetParts[1])) {
numSide = 1;
propSide = 0;
switch (inequality) {
case '<': direction = 'less'; break;
case '>': direction = 'greater'; break;
case '=': direction = 'equal'; break;
}
} else {
return this.sendReplyBox("No value given to compare with '" + Tools.escapeHTML(target) + "'.");
}
var prop = targetParts[propSide];
switch (toId(targetParts[propSide])) {
case 'basepower': prop = 'basePower'; break;
case 'bp': prop = 'basePower'; break;
case 'acc': prop = 'accuracy'; break;
}
if (!(prop in allProperties)) return this.sendReplyBox("'" + Tools.escapeHTML(target) + "' did not contain a valid property.");
if (!searches['property']) searches['property'] = {};
if (direction === 'equal') {
if (searches['property'][prop]) return this.sendReplyBox("Invalid property range for " + prop + ".");
searches['property'][prop] = {};
searches['property'][prop]['less'] = parseFloat(targetParts[numSide]);
searches['property'][prop]['greater'] = parseFloat(targetParts[numSide]);
} else {
if (!searches['property'][prop]) searches['property'][prop] = {};
if (searches['property'][prop][direction]) {
return this.sendReplyBox("Invalid property range for " + prop + ".");
} else {
searches['property'][prop][direction] = parseFloat(targetParts[numSide]);
}
}
continue;
}
if (target.substr(0, 8) === 'priority') {
var sign = '';
target = target.substr(8).trim();
if (target === "+") {
sign = 'greater';
} else if (target === "-") {
sign = 'less';
} else {
return this.sendReplyBox("Priority type '" + target + "' not recognized.");
}
if (!searches['property']) searches['property'] = {};
if (searches['property']['priority']) {
return this.sendReplyBox("Priority cannot be set with both shorthand and inequality range.");
} else {
searches['property']['priority'] = {};
searches['property']['priority'][sign] = (sign === 'less' ? -1 : 1);
}
continue;
}
if (target.substr(0, 7) === 'boosts ') {
switch (target.substr(7)) {
case 'attack': target = 'atk'; break;
case 'defense': target = 'def'; break;
case 'specialattack': target = 'spa'; break;
case 'spatk': target = 'spa'; break;
case 'specialdefense': target = 'spd'; break;
case 'spdef': target = 'spd'; break;
case 'speed': target = 'spe'; break;
case 'acc': target = 'accuracy'; break;
case 'evasiveness': target = 'evasion'; break;
default: target = target.substr(7);
}
if (!(target in allBoosts)) return this.sendReplyBox("'" + Tools.escapeHTML(target.substr(7)) + "' is not a recognized stat.");
if (!searches['boost']) searches['boost'] = {};
if ((searches['boost'][target] && isNotSearch) || (searches['boost'][target] === false && !isNotSearch)) return this.sendReplyBox('A search cannot both exclude and include a stat boost.');
searches['boost'][target] = !isNotSearch;
continue;
}
var oldTarget = target;
if (target.charAt(target.length - 1) === 's') target = target.substr(0, target.length - 1);
switch (target) {
case 'toxic': target = 'tox'; break;
case 'poison': target = 'psn'; break;
case 'burn': target = 'brn'; break;
case 'paralyze': target = 'par'; break;
case 'freeze': target = 'frz'; break;
case 'sleep': target = 'slp'; break;
case 'confuse': target = 'confusion'; break;
case 'trap': target = 'partiallytrapped'; break;
case 'flinche': target = 'flinch'; break;
}
if (target in allStatus) {
if (!searches['status']) searches['status'] = {};
if ((searches['status'][target] && isNotSearch) || (searches['status'][target] === false && !isNotSearch)) return this.sendReplyBox('A search cannot both exclude and include a status.');
searches['status'][target] = !isNotSearch;
continue;
}
if (target in allVolatileStatus) {
if (!searches['volatileStatus']) searches['volatileStatus'] = {};
if ((searches['volatileStatus'][target] && isNotSearch) || (searches['volatileStatus'][target] === false && !isNotSearch)) return this.sendReplyBox('A search cannot both exclude and include a volitile status.');
searches['volatileStatus'][target] = !isNotSearch;
continue;
}
return this.sendReplyBox("'" + Tools.escapeHTML(oldTarget) + "' could not be found in any of the search categories.");
}
if (showAll && Object.size(searches) === 0 && !targetMon) return this.sendReplyBox("No search parameters other than 'all' were found. Try '/help movesearch' for more information on this command.");
var dex = {};
if (targetMon) {
for (var move in lsetData) {
dex[move] = Tools.getMove(move);
}
} else {
for (var move in Tools.data.Movedex) {
dex[move] = Tools.getMove(move);
}
delete dex.magikarpsrevenge;
}
for (var search in searches) {
switch (search) {
case 'type':
case 'category':
for (var move in dex) {
if (searches[search][String(dex[move][search])] === false ||
Object.count(searches[search], true) > 0 && !searches[search][String(dex[move][search])]) {
delete dex[move];
}
}
break;
case 'flags':
for (var flag in searches[search]) {
for (var move in dex) {
if (flag !== 'secondary') {
if ((!dex[move].flags[flag] && searches[search][flag]) || (dex[move].flags[flag] && !searches[search][flag])) delete dex[move];
} else {
if (searches[search][flag]) {
if (!dex[move].secondary && !dex[move].secondaries) delete dex[move];
} else {
if (dex[move].secondary && dex[move].secondaries) delete dex[move];
}
}
}
}
break;
case 'recovery':
for (var move in dex) {
var hasRecovery = (dex[move].drain || dex[move].flags.heal);
if ((!hasRecovery && searches[search]) || (hasRecovery && !searches[search])) delete dex[move];
}
break;
case 'property':
for (var prop in searches[search]) {
for (var move in dex) {
if (typeof searches[search][prop].less === "number") {
if (dex[move][prop] === true) {
delete dex[move];
continue;
}
if (dex[move][prop] > searches[search][prop].less) {
delete dex[move];
continue;
}
}
if (typeof searches[search][prop].greater === "number") {
if (dex[move][prop] === true) {
if (dex[move].category === "Status") delete dex[move];
continue;
}
if (dex[move][prop] < searches[search][prop].greater) {
delete dex[move];
continue;
}
}
}
}
break;
case 'boost':
for (var boost in searches[search]) {
for (var move in dex) {
if (dex[move].boosts) {
if ((dex[move].boosts[boost] > 0 && searches[search][boost]) ||
(dex[move].boosts[boost] < 1 && !searches[search][boost])) continue;
} else if (dex[move].secondary && dex[move].secondary.self && dex[move].secondary.self.boosts) {
if ((dex[move].secondary.self.boosts[boost] > 0 && searches[search][boost]) ||
(dex[move].secondary.self.boosts[boost] < 1 && !searches[search][boost])) continue;
}
delete dex[move];
}
}
break;
case 'status':
case 'volatileStatus':
for (var searchStatus in searches[search]) {
for (var move in dex) {
if (dex[move][search] !== searchStatus) {
if (!dex[move].secondaries) {
if (!dex[move].secondary) {
if (searches[search][searchStatus]) delete dex[move];
} else {
if ((dex[move].secondary[search] !== searchStatus && searches[search][searchStatus]) ||
(dex[move].secondary[search] === searchStatus && !searches[search][searchStatus])) delete dex[move];
}
} else {
var hasSecondary = false;
for (var i = 0; i < dex[move].secondaries.length; i++) {
if (dex[move].secondaries[i][search] === searchStatus) hasSecondary = true;
}
if ((!hasSecondary && searches[search][searchStatus]) || (hasSecondary && !searches[search][searchStatus])) delete dex[move];
}
} else {
if (!searches[search][searchStatus]) delete dex[move];
}
}
}
break;
default:
throw new Error("/movesearch search category '" + search + "' was unrecognised.");
}
}
var results = [];
for (var move in dex) {
results.push(dex[move].name);
}
var resultsStr = "";
if (targetMon) {
resultsStr += "Matching moves found in learnset for " + targetMon + ":
";
} else {
resultsStr += this.broadcasting ? "" : ("" + message + ":
");
}
if (results.length > 0) {
if (showAll || results.length <= RESULTS_MAX_LENGTH + 5) {
results.sort();
resultsStr += results.join(", ");
} else {
resultsStr += results.slice(0, RESULTS_MAX_LENGTH).join(", ") + ", and " + (results.length - RESULTS_MAX_LENGTH) + " more. Redo the search with 'all' as a search parameter to show all results.";
}
} else {
resultsStr += "No moves found.";
}
return this.sendReplyBox(resultsStr);
},
movesearchhelp: ["/movesearch [parameter], [parameter], [parameter], ... - Searches for moves that fulfill the selected criteria.",
"Search categories are: type, category, flag, status inflicted, type boosted, and numeric range for base power, pp, and accuracy.",
"Types must be followed by ' type', e.g., 'dragon type'.",
"Stat boosts must be preceded with 'boosts ', e.g., 'boosts attack' searches for moves that boost the attack stat.",
"Inequality ranges use the characters '>' and '<' though they behave as '≥' and '≤', e.g., 'bp > 100' searches for all moves equal to and greater than 100 base power.",
"Parameters can be excluded through the use of '!', e.g., !water type' excludes all water type moves.",
"Valid flags are: authentic (bypasses substitute), bite, bullet, contact, defrost, powder, pulse, punch, secondary, snatch, sound",
"If a Pok\u00e9mon is included as a parameter, moves will be searched from it's movepool.",
"The order of the parameters does not matter."],
itemsearch: function (target, room, user, connection, cmd, message) {
if (!target) return this.parse('/help itemsearch');
if (!this.canBroadcast()) return;
var showAll = false;
target = target.trim();
if (target.substr(target.length - 5) === ', all' || target.substr(target.length - 4) === ',all') {
showAll = true;
target = target.substr(0, target.length - 5);
}
target = target.toLowerCase().replace('-', ' ').replace(/[^a-z0-9.\s\/]/g, '');
var rawSearch = target.split(' ');
var searchedWords = [];
var foundItems = [];
//refine searched words
for (var i = 0; i < rawSearch.length; i++) {
var newWord = rawSearch[i].trim();
if (isNaN(newWord)) newWord = newWord.replace('.', '');
switch (newWord) {
// words that don't really help identify item removed to speed up search
case 'a':
case 'an':
case 'is':
case 'it':
case 'its':
case 'the':
case 'that':
case 'which':
case 'user':
case 'holder':
case 'holders':
newWord = '';
break;
// replace variations of common words with standardized versions
case 'opponent': newWord = 'attacker'; break;
case 'flung': newWord = 'fling'; break;
case 'heal': case 'heals':
case 'recovers': newWord = 'restores'; break;
case 'boost':
case 'boosts': newWord = 'raises'; break;
case 'weakens': newWord = 'halves'; break;
case 'more': newWord = 'increases'; break;
case 'super':
if (rawSearch[i + 1] === 'effective') {
newWord = 'supereffective';
rawSearch.splice(i + 1, 1);
}
break;
case 'special': newWord = 'sp'; break;
case 'spa':
newWord = 'sp';
rawSearch.splice(i, 0, 'atk');
break;
case 'atk':
case 'attack':
if (rawSearch[i - 1] === 'sp') {
newWord = 'atk';
} else {
newWord = 'attack';
}
break;
case 'spd':
newWord = 'sp';
rawSearch.splice(i, 0, 'def');
break;
case 'def':
case 'defense':
if (rawSearch[i - 1] === 'sp') {
newWord = 'def';
} else {
newWord = 'defense';
}
break;
case 'burns': newWord = 'burn'; break;
case 'poisons': newWord = 'poison'; break;
default:
if (/x[\d\.]+/.test(newWord)) {
newWord = newWord.substr(1) + 'x';
}
}
if (!newWord || searchedWords.indexOf(newWord) >= 0) continue;
searchedWords.push(newWord);
}
if (searchedWords.length === 0) return this.sendReplyBox("No distinguishing words were used. Try a more specific search.");
if (searchedWords.indexOf('fling') >= 0) {
var basePower = 0;
var effect;
for (var k = 0; k < searchedWords.length; k++) {
var wordEff = "";
switch (searchedWords[k]) {
case 'burn': case 'burns':
case 'brn': wordEff = 'brn'; break;
case 'paralyze': case 'paralyzes':
case 'par': wordEff = 'par'; break;
case 'poison': case 'poisons':
case 'psn': wordEff = 'psn'; break;
case 'toxic':
case 'tox': wordEff = 'tox'; break;
case 'flinches':
case 'flinch': wordEff = 'flinch'; break;
case 'badly': wordEff = 'tox'; break;
}
if (wordEff && effect) {
if (!(wordEff === 'psn' && effect === 'tox')) return this.sendReplyBox("Only specify fling effect once.");
} else if (wordEff) {
effect = wordEff;
} else {
if (searchedWords[k].substr(searchedWords[k].length - 2) === 'bp' && searchedWords[k].length > 2) searchedWords[k] = searchedWords[k].substr(0, searchedWords[k].length - 2);
if (Number.isInteger(Number(searchedWords[k]))) {
if (basePower) return this.sendReplyBox("Only specify a number for base power once.");
basePower = parseInt(searchedWords[k]);
}
}
}
for (var n in Tools.data.Items) {
var item = Tools.getItem(n);
if (!item.fling) continue;
if (basePower && effect) {
if (item.fling.basePower === basePower &&
(item.fling.status === effect || item.fling.volatileStatus === effect)) foundItems.push(item.name);
} else if (basePower) {
if (item.fling.basePower === basePower) foundItems.push(item.name);
} else {
if (item.fling.status === effect || item.fling.volatileStatus === effect) foundItems.push(item.name);
}
}
if (foundItems.length === 0) return this.sendReplyBox('No items inflict ' + basePower + 'bp damage when used with Fling.');
} else if (target.search(/natural ?gift/i) >= 0) {
var basePower = 0;
var type = "";
for (var k = 0; k < searchedWords.length; k++) {
searchedWords[k] = searchedWords[k].capitalize();
if (searchedWords[k] in Tools.data.TypeChart) {
if (type) return this.sendReplyBox("Only specify natural gift type once.");
type = searchedWords[k];
} else {
if (searchedWords[k].substr(searchedWords[k].length - 2) === 'bp' && searchedWords[k].length > 2) searchedWords[k] = searchedWords[k].substr(0, searchedWords[k].length - 2);
if (Number.isInteger(Number(searchedWords[k]))) {
if (basePower) return this.sendReplyBox("Only specify a number for base power once.");
basePower = parseInt(searchedWords[k]);
}
}
}
for (var n in Tools.data.Items) {
var item = Tools.getItem(n);
if (!item.isBerry) continue;
if (basePower && type) {
if (item.naturalGift.basePower === basePower && item.naturalGift.type === type) foundItems.push(item.name);
} else if (basePower) {
if (item.naturalGift.basePower === basePower) foundItems.push(item.name);
} else {
if (item.naturalGift.type === type) foundItems.push(item.name);
}
}
if (foundItems.length === 0) return this.sendReplyBox('No berries inflict ' + basePower + 'bp damage when used with Natural Gift.');
} else {
var bestMatched = 0;
for (var n in Tools.data.Items) {
var item = Tools.getItem(n);
var matched = 0;
// splits words in the description into a toId()-esk format except retaining / and . in numbers
var descWords = item.desc;
// add more general quantifier words to descriptions
if (/[1-9\.]+x/.test(descWords)) descWords += ' increases';
if (item.isBerry) descWords += ' berry';
descWords = descWords.replace(/super[\-\s]effective/g, 'supereffective');
descWords = descWords.toLowerCase().replace('-', ' ').replace(/[^a-z0-9\s\/]/g, '').replace(/(\D)\./, function (p0, p1) { return p1; }).split(' ');
for (var k = 0; k < searchedWords.length; k++) {
if (descWords.indexOf(searchedWords[k]) >= 0) matched++;
}
if (matched >= bestMatched && matched >= (searchedWords.length * 3 / 5)) foundItems.push(item.name);
if (matched > bestMatched) bestMatched = matched;
}
// iterate over found items again to make sure they all are the best match
for (var l = 0; l < foundItems.length; l++) {
var item = Tools.getItem(foundItems[l]);
var matched = 0;
var descWords = item.desc;
if (/[1-9\.]+x/.test(descWords)) descWords += ' increases';
if (item.isBerry) descWords += ' berry';
descWords = descWords.replace(/super[\-\s]effective/g, 'supereffective');
descWords = descWords.toLowerCase().replace('-', ' ').replace(/[^a-z0-9\s\/]/g, '').replace(/(\D)\./, function (p0, p1) { return p1; }).split(' ');
for (var k = 0; k < searchedWords.length; k++) {
if (descWords.indexOf(searchedWords[k]) >= 0) matched++;
}
if (matched !== bestMatched) {
foundItems.splice(l, 1);
l--;
}
}
}
var resultsStr = this.broadcasting ? "" : ("" + message + ":
");
if (foundItems.length > 0) {
if (showAll || foundItems.length <= RESULTS_MAX_LENGTH + 5) {
foundItems.sort();
resultsStr += foundItems.join(", ");
} else {
resultsStr += foundItems.slice(0, RESULTS_MAX_LENGTH).join(", ") + ", and " + (foundItems.length - RESULTS_MAX_LENGTH) + " more. Redo the search with ', all' at the end to show all results.";
}
} else {
resultsStr += "No items found. Try a more general search";
}
return this.sendReplyBox(resultsStr);
},
itemsearchhelp: ["/itemsearch [move description] - finds items that match the given key words.",
"Command accepts natural language. (tip: fewer words tend to work better)",
"Searches with \"fling\" in them will find items with the specified Fling behavior.",
"Searches with \"natural gift\" in them will find items with the specified Natural Gift behavior."],
learnset: 'learn',
learnall: 'learn',
learn5: 'learn',
g6learn: 'learn',
rbylearn: 'learn',
gsclearn: 'learn',
advlearn: 'learn',
dpplearn: 'learn',
bw2learn: 'learn',
learn: function (target, room, user, connection, cmd) {
if (!target) return this.parse('/help learn');
if (!this.canBroadcast()) return;
var lsetData = {set:{}};
var targets = target.split(',');
var template = Tools.getTemplate(targets[0]);
var move = {};
var problem;
var format = {rby:'gen1ou', gsc:'gen2ou', adv:'gen3ou', dpp:'gen4ou', bw2:'gen5ou'}[cmd.substring(0, 3)];
var all = (cmd === 'learnall');
if (cmd === 'learn5') lsetData.set.level = 5;
if (cmd === 'g6learn') lsetData.format = {noPokebank: true};
if (!template.exists) {
return this.sendReply("Pok\u00e9mon '" + template.id + "' not found.");
}
if (targets.length < 2) {
return this.sendReply("You must specify at least one move.");
}
for (var i = 1, len = targets.length; i < len; ++i) {
move = Tools.getMove(targets[i]);
if (!move.exists) {
return this.sendReply("Move '" + move.id + "' not found.");
}
problem = TeamValidator.checkLearnsetSync(format, move, template.species, lsetData);
if (problem) break;
}
var buffer = template.name + (problem ? " learn " : " learn ") + (targets.length > 2 ? "these moves" : move.name);
if (format) buffer += ' on ' + cmd.substring(0, 3).toUpperCase();
if (!problem) {
var sourceNames = {E:"egg", S:"event", D:"dream world"};
if (lsetData.sources || lsetData.sourcesBefore) buffer += " only when obtained from:
| '; var icon = {}; for (var type in Tools.data.TypeChart) { icon[type] = ' | ' + icon[type] + ' | '; } buffer += '
|---|---|
| ' + icon[type1] + ' | '; for (var type2 in Tools.data.TypeChart) { var typing; var cell = 'bestEff) bestEff = curEff; } if (bestEff === -5) { bestEff = 0; } else { bestEff = Math.pow(2, bestEff); } } switch (bestEff) { case 0: cell += 'bgcolor=#666666 title="' + typing + '">' + bestEff + ''; break; case 0.25: case 0.5: cell += 'bgcolor=#AA5544 title="' + typing + '">' + bestEff + ''; break; case 1: cell += 'bgcolor=#6688AA title="' + typing + '">' + bestEff + ''; break; case 2: case 4: cell += 'bgcolor=#559955 title="' + typing + '">' + bestEff + ''; break; default: throw new Error("/coverage effectiveness of " + bestEff + " from parameters: " + target); } cell += ' | '; buffer += cell; } } buffer += '
| Name | Description | "); for (var i = 0; i < sections[sectionId].formats.length; i++) { var format = Tools.getFormat(sections[sectionId].formats[i]); var mod = format.mod && format.mod !== 'base' ? " - " + Tools.escapeHTML(format.mod === format.id ? format.name : format.mod).capitalize() : ""; buf.push("
|---|---|
| " + Tools.escapeHTML(format.name) + " | " + (format.desc ? format.desc.join(" ") : "—") + " |