Skip to content

Commit e2a32a5

Browse files
committed
Support /ds stacking multiple rules
1 parent c652c4a commit e2a32a5

File tree

1 file changed

+70
-50
lines changed

1 file changed

+70
-50
lines changed

server/chat-plugins/datasearch.ts

+70-50
Original file line numberDiff line numberDiff line change
@@ -632,23 +632,33 @@ function getMod(target: string) {
632632

633633
function getRule(target: string) {
634634
const arr = target.split(',').map(x => x.trim());
635-
const ruleTerm = arr.find(x => {
636-
const sanitizedStr = x.toLowerCase().replace(/[^a-z0-9=]+/g, '');
637-
return sanitizedStr.startsWith('rule=') && Dex.data.Rulesets[toID(sanitizedStr.split('=')[1])];
638-
});
635+
const ruleTerms: string[] = [];
636+
for (const term of arr) {
637+
const sanitizedStr = term.toLowerCase().replace(/[^a-z0-9=]+/g, '');
638+
if (sanitizedStr.startsWith('rule=') && Dex.data.Rulesets[toID(sanitizedStr.split('=')[1])]) {
639+
ruleTerms.push(term);
640+
}
641+
}
639642
const count = arr.filter(x => {
640643
const sanitizedStr = x.toLowerCase().replace(/[^a-z0-9=]+/g, '');
641644
return sanitizedStr.startsWith('rule=');
642645
}).length;
643-
if (ruleTerm) arr.splice(arr.indexOf(ruleTerm), 1);
644-
return { splitTarget: arr, usedRule: ruleTerm ? toID(ruleTerm.split(/ ?= ?/)[1]) : '', count };
646+
if (ruleTerms.length > 0) {
647+
for (const rule of ruleTerms) {
648+
arr.splice(arr.indexOf(rule), 1);
649+
}
650+
}
651+
return { splitTarget: arr, usedRules: ruleTerms.map(
652+
x => x.toLowerCase().replace(/[^a-z0-9=]+/g, '').split('rule=')[1]), count };
645653
}
646654

647-
function prepareDexsearchValidator(usedMod: string | undefined, rule: FormatData, nationalSearch: boolean | null) {
655+
function prepareDexsearchValidator(usedMod: string | undefined, rules: FormatData[], nationalSearch: boolean | null) {
648656
const format = Object.entries(Dex.data.Rulesets).find(([a, f]) => f.mod === usedMod)?.[1].name || 'gen9ou';
649657
const ruleTable = Dex.formats.getRuleTable(Dex.formats.get(format));
650658
const additionalRules = [];
651-
if (rule && !ruleTable.has(toID(rule.name))) additionalRules.push(toID(rule.name));
659+
for (const rule of rules) {
660+
if (!ruleTable.has(toID(rule.name))) additionalRules.push(toID(rule.name));
661+
}
652662
if (nationalSearch && !ruleTable.has('natdexmod')) additionalRules.push('natdexmod');
653663
if (nationalSearch && ruleTable.valueRules.has('minsourcegen')) additionalRules.push('!!minsourcegen=3');
654664
return TeamValidator.get(`${format}${additionalRules.length ? `@@@${additionalRules.join(',')}` : ''}`);
@@ -657,28 +667,23 @@ function prepareDexsearchValidator(usedMod: string | undefined, rule: FormatData
657667
function runDexsearch(target: string, cmd: string, canAll: boolean, message: string, isTest: boolean) {
658668
const searches: DexOrGroup[] = [];
659669
const { splitTarget: remainingTargets, usedMod, count: modCount } = getMod(target);
660-
const { splitTarget, usedRule, count: ruleCount } = getRule(remainingTargets.join(','));
661-
if (modCount > 1 || ruleCount > 1) {
662-
return { error: `You can't run searches for multiple mods or rules.` };
670+
const { splitTarget, usedRules } = getRule(remainingTargets.join(','));
671+
if (modCount > 1) {
672+
return { error: `You can't run searches for multiple mods.` };
663673
}
664674
for (const str of splitTarget) {
665675
const sanatizedStr = str.toLowerCase().replace(/[^a-z0-9=]+/g, '');
666676
if (sanatizedStr.startsWith('mod=') || sanatizedStr.startsWith('rule=')) {
667-
return { error: `Invalid mod or rule, see /dexsearchhelp.` };
668-
}
669-
}
670-
const ruleType = [];
671-
for (const key of Object.keys(supportedDexsearchRules)) {
672-
if (supportedDexsearchRules[key].includes(usedRule)) {
673-
ruleType.push(key);
674-
break;
677+
return { error: `${sanatizedStr.split('=')[1]} is an invalid mod or rule, see /dexsearchhelp.` };
675678
}
676679
}
677-
if (usedRule && !ruleType.length) {
678-
return { error: `Invalid rule, see /dexsearchhelp` };
679-
}
680680
const mod = Dex.mod(usedMod || 'base');
681-
const rule = Dex.data.Rulesets[usedRule];
681+
const rules: FormatData[] = [];
682+
for (const rule of usedRules) {
683+
if (!dexsearchHelpRules.includes(rule))
684+
return { error: `${rule} is an unsupported rule, see /dexsearchhelp` };
685+
rules.push(Dex.data.Rulesets[rule]);
686+
}
682687

683688
const allTiers: { [k: string]: TierTypes.Singles | TierTypes.Other } = Object.assign(Object.create(null), {
684689
anythinggoes: 'AG', ag: 'AG',
@@ -1205,28 +1210,30 @@ function runDexsearch(target: string, cmd: string, canAll: boolean, message: str
12051210
// These only ever get accessed if there are moves or banlists to filter by.
12061211
let validator;
12071212
let pokemonSource;
1208-
if (Object.values(searches).some(search => Object.keys(search.moves).length !== 0) ||
1209-
ruleType.includes('movevalidation') || ruleType.includes('banlist')) {
1210-
validator = prepareDexsearchValidator(usedMod, rule, nationalSearch);
1213+
if (Object.values(searches).some(search => !!Object.keys(search.moves).length)) {
1214+
validator = prepareDexsearchValidator(usedMod, rules, nationalSearch);
12111215
}
12121216

12131217
const dex: { [k: string]: Species } = {};
1214-
for (const species of mod.species.all()) {
1218+
for (let species of mod.species.all()) {
12151219
const megaSearchResult = megaSearch === null || megaSearch === !!species.isMega;
12161220
const gmaxSearchResult = gmaxSearch === null || gmaxSearch === species.name.endsWith('-Gmax');
12171221
const fullyEvolvedSearchResult = fullyEvolvedSearch === null || fullyEvolvedSearch !== species.nfe;
12181222
const restrictedSearchResult = restrictedSearch === null ||
12191223
restrictedSearch === species.tags.includes('Restricted Legendary');
1220-
let ruleResult = !rule?.banlist?.includes(species.name);
12211224

12221225
/**
12231226
* Not every ruleset with an onValidateSet function is specifically to exclude mons.
12241227
* In the current list of supported rules only the Pokedex rules do such which is
12251228
* why this step is ignored for other rules. Rules can be added for this functionality
12261229
* in the supportedDexSearchTypes mapping at the top of the function.
12271230
*/
1228-
if (ruleResult && validator && ruleType.includes('banlist') && rule?.onValidateSet) {
1229-
ruleResult = !rule.onValidateSet.call(validator,
1231+
let ruleResult = true;
1232+
for (const rule of rules) {
1233+
if (!ruleResult) break;
1234+
if (!supportedDexsearchRules['banlist'].includes(toID(rule.name))) continue;
1235+
if (!validator) validator = prepareDexsearchValidator(usedMod, rules, nationalSearch);
1236+
ruleResult = !rule.onValidateSet?.call(validator,
12301237
{ name: species.name, species: species.id } as PokemonSet, validator.format, {}, {});
12311238
}
12321239

@@ -1243,8 +1250,11 @@ function runDexsearch(target: string, cmd: string, canAll: boolean, message: str
12431250
restrictedSearchResult &&
12441251
ruleResult
12451252
) {
1246-
dex[species.id] = rule?.onModifySpecies?.call({ dex: mod, clampIntRange: Utils.clampIntRange, toID } as Battle,
1247-
species) || species;
1253+
for (const rule of rules) {
1254+
species = rule?.onModifySpecies?.call({ dex: mod, clampIntRange: Utils.clampIntRange, toID } as Battle,
1255+
species) || species;
1256+
}
1257+
dex[species.id] = species;
12481258
}
12491259
}
12501260

@@ -1418,19 +1428,29 @@ function runDexsearch(target: string, cmd: string, canAll: boolean, message: str
14181428
}
14191429
if (matched) continue;
14201430

1421-
for (const move of altsMoves) {
1422-
pokemonSource = validator?.allSources();
1423-
// Match rule must be tried first due to NOT searches providing failed hits
1424-
// when only checking vanilla legality first.
1425-
const matchRule = ruleType.includes('movevalidation') &&
1426-
!validator?.omCheckCanLearn(move, dex[mon], pokemonSource, {}) === alts.moves[move.id];
1427-
const matchNormally = !matchRule && !validator?.checkCanLearn(move, dex[mon], pokemonSource) === alts.moves[move.id];
1431+
if (validator) {
1432+
for (const move of altsMoves) {
1433+
pokemonSource = validator.allSources();
1434+
const isNotSearch = !alts.moves[move.id];
1435+
1436+
let matchRule = false;
1437+
let numMoveValidationRules = 0;
1438+
for (const rule of rules) {
1439+
if (!supportedDexsearchRules['movevalidation'].includes(toID(rule.name))) continue;
1440+
else numMoveValidationRules++;
1441+
matchRule = !rule.checkCanLearn?.call(
1442+
validator, move, dex[mon], pokemonSource, {} as PokemonSet) === !isNotSearch;
1443+
if (matchRule === !isNotSearch) break;
1444+
}
1445+
const matchNormally = !validator.checkCanLearn(move, dex[mon], pokemonSource) === !isNotSearch;
14281446

1429-
if (matchNormally || matchRule) {
1430-
matched = true;
1431-
break;
1447+
if ((!isNotSearch && (matchNormally || (numMoveValidationRules > 0 && matchRule))) ||
1448+
(isNotSearch && matchNormally && (numMoveValidationRules === 0 || matchRule))) {
1449+
matched = true;
1450+
break;
1451+
}
1452+
if (pokemonSource && !pokemonSource.size()) break;
14321453
}
1433-
if (pokemonSource && !pokemonSource.size()) break;
14341454
}
14351455
if (matched) continue;
14361456

@@ -1441,9 +1461,9 @@ function runDexsearch(target: string, cmd: string, canAll: boolean, message: str
14411461
const stat = sort?.slice(0, -1);
14421462

14431463
function getSortValue(species: Species) {
1444-
if (!stat) return 0;
1445-
1446-
if (stat === 'bst') {
1464+
if (!stat) {
1465+
return 0;
1466+
} else if (stat === 'bst') {
14471467
return species.bst;
14481468
} else if (stat === 'weight') {
14491469
return species.weighthg;
@@ -1463,12 +1483,12 @@ function runDexsearch(target: string, cmd: string, canAll: boolean, message: str
14631483
mon.baseSpecies !== "Pikachu";
14641484
const maskForm = mon.baseSpecies === "Ogerpon" && !mon.forme.endsWith("Tera");
14651485
const allowGmax = (gmaxSearch || tierSearch);
1466-
if (!isRegionalForm && !maskForm && mon.baseSpecies && results.includes(Dex.species.get(mon.baseSpecies)) &&
1467-
getSortValue(mon) === getSortValue(Dex.species.get(mon.baseSpecies))) continue;
1486+
if (!isRegionalForm && !maskForm && mon.baseSpecies && results.includes(mod.species.get(mon.baseSpecies)) &&
1487+
getSortValue(mon) === getSortValue(mod.species.get(mon.baseSpecies))) continue;
14681488
const teraFormeChangesFrom = mon.forme.endsWith("Tera") ? !Array.isArray(mon.battleOnly) ?
14691489
mon.battleOnly! : null : null;
1470-
if (teraFormeChangesFrom && results.includes(Dex.species.get(teraFormeChangesFrom)) &&
1471-
getSortValue(mon) === getSortValue(Dex.species.get(teraFormeChangesFrom))) continue;
1490+
if (teraFormeChangesFrom && results.includes(mod.species.get(teraFormeChangesFrom)) &&
1491+
getSortValue(mon) === getSortValue(mod.species.get(teraFormeChangesFrom))) continue;
14721492
if (mon.isNonstandard === 'Gigantamax' && !allowGmax) continue;
14731493
results.push(mon);
14741494
}

0 commit comments

Comments
 (0)