Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

/weakness: Add fuzzy matching #10316

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 90 additions & 40 deletions server/chat-commands/info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -912,40 +912,94 @@ export const commands: Chat.ChatCommands = {
targets.pop();
}

let species: {types: string[], [k: string]: any} = dex.species.get(targets[0]);
const type1 = dex.types.get(targets[0]);
const type2 = dex.types.get(targets[1]);
const type3 = dex.types.get(targets[2]);
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(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(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(Utils.html`${originalSearch} isn't a recognized type or Pokemon${Dex.gen > dex.gen ? ` in Gen ${dex.gen}` : ""}.`);
}
}

const types = [];
if (species.exists) {
target = species.name;
} else {
const types = [];
if (type1.exists) {
types.push(type1.name);
if (type2.exists && type2 !== type1) {
types.push(type2.name);
}
if (type3.exists && type3 !== type1 && type3 !== type2) {
types.push(type3.name);
}
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;
}

if (types.length === 0) {
return this.sendReplyBox(Utils.html`${target} isn't a recognized type or Pokemon${Dex.gen > dex.gen ? ` in Gen ${dex.gen}` : ""}.`);
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);
}
species = {types: types};
target = types.join("/");
target = searchTarget.join("/");
}

if (imperfectMatch) {
this.sendReply(`No Pok\u00e9mon 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, species);
const notImmune = dex.getImmunity(type, types);
if (notImmune || isInverse) {
let typeMod = !notImmune && isInverse ? 1 : 0;
typeMod += (isInverse ? -1 : 1) * dex.getEffectiveness(type, species);
typeMod += (isInverse ? -1 : 1) * dex.getEffectiveness(type, types);
switch (typeMod) {
case 1:
weaknesses.push(type);
Expand Down Expand Up @@ -983,23 +1037,25 @@ export const commands: Chat.ChatCommands = {
trapped: "Trapping",
};
for (const status in statuses) {
if (!dex.getImmunity(status, species)) {
if (!dex.getImmunity(status, types)) {
immunities.push(statuses[status]);
}
}

const buffer = [];
buffer.push(`${species.exists ? `${species.name} (ignoring abilities):` : `${target}:`}`);
buffer.push(`${species.exists ? `${target} (ignoring abilities):` : `${target}:`}`);
buffer.push(`<span class="message-effect-weak">Weaknesses</span>: ${weaknesses.join(', ') || '<font color=#999999>None</font>'}`);
buffer.push(`<span class="message-effect-resist">Resistances</span>: ${resistances.join(', ') || '<font color=#999999>None</font>'}`);
buffer.push(`<span class="message-effect-immune">Immunities</span>: ${immunities.join(', ') || '<font color=#999999>None</font>'}`);
this.sendReplyBox(buffer.join('<br />'));
},
weaknesshelp: [
`/weakness [pokemon] - Provides a Pok\u00e9mon'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 [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\u00e9mon's type and type combination's resistances, weaknesses, and immunities, ignoring abilities.`,
`!weakness [pokemon] - Shows everyone a Pok\u00e9mon'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 [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\u00e9mon's type and type combination's resistances, weaknesses, and immunities, ignoring abilities. Requires: + % @ # &`,
],

eff: 'effectiveness',
Expand Down Expand Up @@ -1903,17 +1959,8 @@ export const commands: Chat.ChatCommands = {
rulesetHtml = `No ruleset found for ${format.name}`;
}
}
let formatType: string = (format.gameType || "singles");
formatType = formatType.charAt(0).toUpperCase() + formatType.slice(1).toLowerCase();
if (!format.desc && !format.threads) {
if (format.effectType === 'Format') {
return this.sendReplyBox(`No description found for this ${formatType} ${format.section} format.<br />${rulesetHtml}`);
} else {
return this.sendReplyBox(`No description found for this rule.<br />${rulesetHtml}`);
}
}
const formatDesc = format.desc || '';
let descHtml = [];
const descHtml: string[] = [];
const data = await getFormatResources(format.id);
if (data) {
for (const {resource_name, url} of data.resources) {
Expand All @@ -1923,11 +1970,14 @@ export const commands: Chat.ChatCommands = {
rn = rn.split(' ').map((x: string) => x[0].toUpperCase() + x.substr(1)).join(' ');
descHtml.push(`&bullet; <a href="${url}">${rn}</a>`);
}
} else if (format.threads?.length) {
descHtml.push(...format.threads);
} else {
const genID = ['rb', 'gs', 'rs', 'dp', 'bw', 'xy', 'sm', 'ss', 'sv'];
descHtml.push(`This format has no resources linked on its <a href="https://www.smogon.com/dex/${genID[format.gen - 1] || 'sv'}/formats/">Smogon Dex page</a>.` +
`Please contact a <a href="https://www.smogon.com/forums/forums/757/">C&amp;C Leader</a> to resolve this.<br />`);
}
if (!descHtml.length && format.threads) {
descHtml = format.threads;
}
return this.sendReplyBox(`<h1>${format.name}</h1><hr />${formatDesc ? formatDesc + '<hr />' : ''}${descHtml.join("<br />")}${rulesetHtml ? `<br />${rulesetHtml}` : ''}`);
return this.sendReplyBox(`<h2>${format.name}</h2><hr />${formatDesc ? formatDesc + '<hr />' : ''}${descHtml.join("<br />")}${rulesetHtml ? `<br />${rulesetHtml}` : ''}`);
}

let tableStyle = `border:1px solid gray; border-collapse:collapse`;
Expand Down Expand Up @@ -2138,7 +2188,7 @@ export const commands: Chat.ChatCommands = {
buffer.push(`<a href="https://pokemonshowdown.com/${this.tr`pages/privacy`}">${this.tr`Pokémon Showdown privacy policy`}</a>`);
}
if (showAll || ['lostpassword', 'password', 'lostpass'].includes(target)) {
buffer.push(`If you need your Pokémon Showdown password reset, you can fill out a <a href="https://www.smogon.com/forums/password-reset-form/">${this.tr`Password Reset Form`}</a>. You will need to make a Smogon account to be able to fill out the form, as password resets are processed through the Smogon forums.`);
buffer.push(`If you need your Pokémon Showdown password reset, you can fill out a <a href="https://www.smogon.com/forums/password-reset-form/">${this.tr`Password Reset Form`}</a>. <b>You will need to make a Smogon account to be able to fill out a form</b>; 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.`);
Expand Down
8 changes: 5 additions & 3 deletions sim/dex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -364,17 +364,19 @@ export class ModdedDex {
}

dataSearch(
target: string, searchIn?: ('Pokedex' | 'Moves' | 'Abilities' | 'Items' | 'Natures')[] | null, isInexact?: boolean
target: string,
searchIn?: ('Pokedex' | 'Moves' | 'Abilities' | 'Items' | 'Natures' | 'TypeChart')[] | null,
isInexact?: boolean
): AnyObject[] | null {
if (!target) return null;

searchIn = searchIn || ['Pokedex', 'Moves', 'Abilities', 'Items', 'Natures'];

const searchObjects = {
Pokedex: 'species', Moves: 'moves', Abilities: 'abilities', Items: 'items', Natures: 'natures',
Pokedex: 'species', Moves: 'moves', Abilities: 'abilities', Items: 'items', Natures: 'natures', TypeChart: 'types',
} as const;
const searchTypes = {
Pokedex: 'pokemon', Moves: 'move', Abilities: 'ability', Items: 'item', Natures: 'nature',
Pokedex: 'pokemon', Moves: 'move', Abilities: 'ability', Items: 'item', Natures: 'nature', TypeChart: 'type',
} as const;
let searchResults: AnyObject[] | null = [];
for (const table of searchIn) {
Expand Down
Loading