mirror of
https://github.com/smogon/pokemon-showdown.git
synced 2026-03-21 17:25:10 -05:00
Make "All Pokemon" rules more convenient (#10932)
Some checks are pending
Node.js CI / build (18.x) (push) Waiting to run
Some checks are pending
Node.js CI / build (18.x) (push) Waiting to run
* Make "All Pokemon" rules more convenient Previously, "+All Pokemon" did nothing except override "-All Pokemon", which switched from a default-allow to default-deny system. They still do that, but they now also override all previous pokemon bans/unbans. This makes it easier to replace a banlist/whitelist from an inherited ruleset without needing to reverse every previous ban/unban. This also adds an error if you use `+All Pokemon` in a ruleset where it doesn't do anything. Fixes #10772
This commit is contained in:
parent
7b32d0f8c7
commit
0cb51158aa
|
|
@ -83,10 +83,20 @@ Syntax is identical to bans, just replace `-` with `+`, like:
|
|||
|
||||
More specific always trumps less specific:
|
||||
|
||||
`- all Pokemon, + Uber, - Giratina, + Giratina-Altered` - allow only Ubers other than Giratina-Origin
|
||||
`- all pokemon, + Uber, - Giratina, + Giratina-Altered` - allow only Ubers other than Giratina-Origin
|
||||
|
||||
`- all pokemon, + Giratina-Altered, - Giratina, + Uber` - allow only Ubers other than Giratina-Origin
|
||||
|
||||
`- Nonexistent, + Necturna` - don't allow anything from outside the game, except the CAP Necturna
|
||||
|
||||
Except `all pokemon`, which removes all bans/unbans of pokemon before it:
|
||||
|
||||
`- all pokemon, + Pikachu, + Raichu` - allow Pikachu and Raichu
|
||||
|
||||
`+ Pikachu, - all pokemon, + Raichu` - allow only Raichu
|
||||
|
||||
(Note that `all pokemon` does not affect obtainability rules. `+ all pokemon` will not allow CAPs or anything like that.)
|
||||
|
||||
For equally specific rules, the last rule wins:
|
||||
|
||||
`- Pikachu, - Pikachu, + Pikachu` - allow Pikachu
|
||||
|
|
@ -128,7 +138,7 @@ Whitelisting
|
|||
|
||||
Instead of a banlist, you can have a list of allowed things:
|
||||
|
||||
`- all Pokemon, + Charmander, + Squirtle, + Bulbasaur` - allow only Kanto starters
|
||||
`- all pokemon, + Charmander, + Squirtle, + Bulbasaur` - allow only Kanto starters
|
||||
|
||||
`- all moves, + move: Metronome` - allow only the move Metronome
|
||||
|
||||
|
|
|
|||
|
|
@ -2714,7 +2714,6 @@ export const Formats: import('../sim/dex-formats').FormatList = [
|
|||
team: 'randomHC',
|
||||
ruleset: ['HP Percentage Mod', 'Cancel Mod'],
|
||||
banlist: ['CAP', 'LGPE', 'MissingNo.', 'Pikachu-Cosplay', 'Pichu-Spiky-eared', 'Pokestar Smeargle', 'Pokestar UFO', 'Pokestar UFO-2', 'Pokestar Brycen-Man', 'Pokestar MT', 'Pokestar MT2', 'Pokestar Transport', 'Pokestar Giant', 'Pokestar Humanoid', 'Pokestar Monster', 'Pokestar F-00', 'Pokestar F-002', 'Pokestar Spirit', 'Pokestar Black Door', 'Pokestar White Door', 'Pokestar Black Belt', 'Pokestar UFO-PropU2', 'Xerneas-Neutral'],
|
||||
unbanlist: ['All Pokemon'],
|
||||
},
|
||||
{
|
||||
name: "[Gen 9] Doubles Hackmons Cup",
|
||||
|
|
|
|||
|
|
@ -2613,7 +2613,6 @@ export const Rulesets: import('../sim/dex-formats').FormatDataTable = {
|
|||
effectType: 'ValidatorRule',
|
||||
name: "Hackmons Forme Legality",
|
||||
desc: `Enforces proper forme legality for hackmons-based metagames.`,
|
||||
unbanlist: ['All Pokemon'],
|
||||
banlist: ['CAP', 'LGPE', 'Future'],
|
||||
onChangeSet(set, format, setHas, teamHas) {
|
||||
let species = this.dex.species.get(set.species);
|
||||
|
|
|
|||
|
|
@ -631,19 +631,33 @@ export class DexFormats {
|
|||
if (format.effectType !== 'Format') throw new Error(`Unrecognized format "${formatName}"`);
|
||||
if (!customRulesString) return format.id;
|
||||
const ruleTable = this.getRuleTable(format);
|
||||
let hasCustomRules = false;
|
||||
let hasPokemonRule = false;
|
||||
const customRules = customRulesString.split(',').map(rule => {
|
||||
rule = rule.replace(/[\r\n|]*/g, '').trim();
|
||||
const ruleSpec = this.validateRule(rule);
|
||||
if (typeof ruleSpec === 'string' && ruleTable.has(ruleSpec)) return null;
|
||||
if (typeof ruleSpec === 'string') {
|
||||
if (ruleSpec === '-pokemontag:allpokemon' || ruleSpec === '+pokemontag:allpokemon') {
|
||||
if (hasPokemonRule) throw new Error(`You can't ban/unban pokemon before banning/unbanning all Pokemon.`);
|
||||
}
|
||||
if (this.isPokemonRule(ruleSpec)) hasPokemonRule = true;
|
||||
}
|
||||
if (typeof ruleSpec !== 'string' || !ruleTable.has(ruleSpec)) hasCustomRules = true;
|
||||
return rule;
|
||||
}).filter(Boolean);
|
||||
if (!customRules.length) throw new Error(`The format already has your custom rules`);
|
||||
});
|
||||
if (!hasCustomRules) throw new Error(`None of your custom rules change anything`);
|
||||
const validatedFormatid = format.id + '@@@' + customRules.join(',');
|
||||
const moddedFormat = this.get(validatedFormatid, true);
|
||||
this.getRuleTable(moddedFormat);
|
||||
return validatedFormatid;
|
||||
}
|
||||
|
||||
/**
|
||||
* The default mode is `isTrusted = false`, which is a bit of a
|
||||
* footgun. PS will never do anything unsafe, but `isTrusted = true`
|
||||
* will throw if the format string is invalid, while
|
||||
* `isTrusted = false` will silently fall back to the original format.
|
||||
*/
|
||||
get(name?: string | Format, isTrusted = false): Format {
|
||||
if (name && typeof name !== 'string') return name;
|
||||
|
||||
|
|
@ -694,6 +708,12 @@ export class DexFormats {
|
|||
return this.formatsListCache!;
|
||||
}
|
||||
|
||||
isPokemonRule(ruleSpec: string) {
|
||||
return (
|
||||
ruleSpec.slice(1).startsWith('pokemontag:') || ruleSpec.slice(1).startsWith('pokemon:') ||
|
||||
ruleSpec.slice(1).startsWith('basepokemon:')
|
||||
);
|
||||
}
|
||||
getRuleTable(format: Format, depth = 1, repeals?: Map<string, number>): RuleTable {
|
||||
if (format.ruleTable && !repeals) return format.ruleTable;
|
||||
if (format.name.length > 50) {
|
||||
|
|
@ -726,17 +746,25 @@ export class DexFormats {
|
|||
|
||||
// apply rule repeals before other rules
|
||||
// repeals is a ruleid:depth map (positive: unused, negative: used)
|
||||
for (const rule of ruleset) {
|
||||
if (rule.startsWith('!') && !rule.startsWith('!!')) {
|
||||
const ruleSpec = this.validateRule(rule, format) as string;
|
||||
if (!repeals) repeals = new Map();
|
||||
const ruleSpecs = ruleset.map(rule => this.validateRule(rule, format));
|
||||
for (let ruleSpec of ruleSpecs) {
|
||||
if (typeof ruleSpec !== 'string') continue;
|
||||
if (ruleSpec.startsWith('^')) ruleSpec = ruleSpec.slice(1);
|
||||
if (ruleSpec.startsWith('!') && !ruleSpec.startsWith('!!')) {
|
||||
repeals ||= new Map();
|
||||
repeals.set(ruleSpec.slice(1), depth);
|
||||
}
|
||||
}
|
||||
|
||||
for (const rule of ruleset) {
|
||||
const ruleSpec = this.validateRule(rule, format);
|
||||
let skipPokemonBans = ruleSpecs.filter(r => r === '+pokemontag:allpokemon').length;
|
||||
let hasPokemonBans = false;
|
||||
const warnForNoPokemonBans = !!skipPokemonBans && !format.customRules;
|
||||
skipPokemonBans += ruleSpecs.filter(r => r === '-pokemontag:allpokemon').length;
|
||||
|
||||
// if (format.customRules) console.log(`${format.id}: ${format.customRules.join(', ')}`);
|
||||
|
||||
for (let ruleSpec of ruleSpecs) {
|
||||
// complex ban/unban
|
||||
if (typeof ruleSpec !== 'string') {
|
||||
if (ruleSpec[0] === 'complexTeamBan') {
|
||||
const complexTeamBan: ComplexTeamBan = ruleSpec.slice(1) as ComplexTeamBan;
|
||||
|
|
@ -750,24 +778,42 @@ export class DexFormats {
|
|||
continue;
|
||||
}
|
||||
|
||||
if (rule.startsWith('!') && !rule.startsWith('!!')) {
|
||||
// ^ is undocumented because I really don't want it used outside of tests
|
||||
const noWarn = ruleSpec.startsWith('^');
|
||||
if (noWarn) ruleSpec = ruleSpec.slice(1);
|
||||
|
||||
// repeal rule
|
||||
if (ruleSpec.startsWith('!') && !ruleSpec.startsWith('!!')) {
|
||||
const repealDepth = repeals!.get(ruleSpec.slice(1));
|
||||
if (repealDepth === undefined) throw new Error(`Multiple "${rule}" rules in ${format.name}`);
|
||||
if (repealDepth === depth) {
|
||||
throw new Error(`Rule "${rule}" did nothing because "${rule.slice(1)}" is not in effect`);
|
||||
if (repealDepth === undefined) throw new Error(`Multiple "${ruleSpec}" rules in ${format.name}`);
|
||||
if (repealDepth === depth && !noWarn) {
|
||||
throw new Error(`Rule "${ruleSpec}" did nothing because "${ruleSpec.slice(1)}" is not in effect`);
|
||||
}
|
||||
if (repealDepth === -depth) repeals!.delete(ruleSpec.slice(1));
|
||||
continue;
|
||||
}
|
||||
|
||||
// individual ban/unban
|
||||
if ('+*-'.includes(ruleSpec.charAt(0))) {
|
||||
if (ruleTable.has(ruleSpec)) {
|
||||
throw new Error(`Rule "${rule}" in "${format.name}" already exists in "${ruleTable.get(ruleSpec) || format.name}"`);
|
||||
throw new Error(`Rule "${ruleSpec}" in "${format.name}" already exists in "${ruleTable.get(ruleSpec) || format.name}"`);
|
||||
}
|
||||
if (skipPokemonBans) {
|
||||
if (ruleSpec === '-pokemontag:allpokemon' || ruleSpec === '+pokemontag:allpokemon') {
|
||||
skipPokemonBans--;
|
||||
} else if (this.isPokemonRule(ruleSpec)) {
|
||||
if (!format.customRules) {
|
||||
throw new Error(`Rule "${ruleSpec}" must go after any "All Pokemon" rule in ${format.name} ("+All Pokemon" should go in ruleset, not unbanlist)`);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
for (const prefix of '+*-') ruleTable.delete(prefix + ruleSpec.slice(1));
|
||||
ruleTable.set(ruleSpec, '');
|
||||
continue;
|
||||
}
|
||||
|
||||
// rule
|
||||
let [formatid, value] = ruleSpec.split('=');
|
||||
const subformat = this.get(formatid);
|
||||
const repealAndReplace = ruleSpec.startsWith('!!');
|
||||
|
|
@ -797,7 +843,9 @@ export class DexFormats {
|
|||
|
||||
const oldValue = ruleTable.valueRules.get(subformat.id);
|
||||
if (oldValue === value) {
|
||||
throw new Error(`Rule "${ruleSpec}" is redundant with existing rule "${subformat.id}=${value}"${ruleTable.blame(subformat.id)}.`);
|
||||
if (!noWarn) {
|
||||
throw new Error(`Rule "${ruleSpec}" is redundant with existing rule "${subformat.id}=${value}"${ruleTable.blame(subformat.id)}.`);
|
||||
}
|
||||
} else if (repealAndReplace) {
|
||||
if (oldValue === undefined) {
|
||||
if (subformat.mutuallyExclusiveWith && ruleTable.valueRules.has(subformat.mutuallyExclusiveWith)) {
|
||||
|
|
@ -823,8 +871,8 @@ export class DexFormats {
|
|||
} else {
|
||||
if (value !== undefined) throw new Error(`Rule "${ruleSpec}" should not have a value (no equals sign)`);
|
||||
if (repealAndReplace) throw new Error(`"!!" is not supported for this rule`);
|
||||
if (ruleTable.has(subformat.id) && !repealAndReplace) {
|
||||
throw new Error(`Rule "${rule}" in "${format.name}" already exists in "${ruleTable.get(subformat.id) || format.name}"`);
|
||||
if (ruleTable.has(subformat.id) && !repealAndReplace && !noWarn) {
|
||||
throw new Error(`Rule "${ruleSpec}" in "${format.name}" already exists in "${ruleTable.get(subformat.id) || format.name}"`);
|
||||
}
|
||||
}
|
||||
ruleTable.set(subformat.id, '');
|
||||
|
|
@ -834,26 +882,33 @@ export class DexFormats {
|
|||
const subRuleTable = this.getRuleTable(subformat, depth + 1, repeals);
|
||||
for (const [ruleid, sourceFormat] of subRuleTable) {
|
||||
// don't check for "already exists" here; multiple inheritance is allowed
|
||||
if (!repeals?.has(ruleid)) {
|
||||
const newValue = subRuleTable.valueRules.get(ruleid);
|
||||
const oldValue = ruleTable.valueRules.get(ruleid);
|
||||
if (newValue !== undefined) {
|
||||
// set a value
|
||||
const subSubFormat = this.get(ruleid);
|
||||
if (subSubFormat.mutuallyExclusiveWith && ruleTable.valueRules.has(subSubFormat.mutuallyExclusiveWith)) {
|
||||
// mutually exclusive conflict!
|
||||
throw new Error(`Rule "${ruleid}=${newValue}" from ${subformat.name}${subRuleTable.blame(ruleid)} conflicts with "${subSubFormat.mutuallyExclusiveWith}=${ruleTable.valueRules.get(subSubFormat.mutuallyExclusiveWith)}"${ruleTable.blame(subSubFormat.mutuallyExclusiveWith)} (Repeal one with ! before adding another)`);
|
||||
}
|
||||
if (newValue !== oldValue) {
|
||||
if (oldValue !== undefined) {
|
||||
// conflict!
|
||||
throw new Error(`Rule "${ruleid}=${newValue}" from ${subformat.name}${subRuleTable.blame(ruleid)} conflicts with "${ruleid}=${oldValue}"${ruleTable.blame(ruleid)} (Repeal one with ! before adding another)`);
|
||||
}
|
||||
ruleTable.valueRules.set(ruleid, newValue);
|
||||
}
|
||||
if (repeals?.has(ruleid)) continue;
|
||||
|
||||
if (skipPokemonBans && '+*-'.includes(ruleid.charAt(0))) {
|
||||
if (this.isPokemonRule(ruleid)) {
|
||||
hasPokemonBans = true;
|
||||
continue;
|
||||
}
|
||||
ruleTable.set(ruleid, sourceFormat || subformat.name);
|
||||
}
|
||||
|
||||
const newValue = subRuleTable.valueRules.get(ruleid);
|
||||
const oldValue = ruleTable.valueRules.get(ruleid);
|
||||
if (newValue !== undefined) {
|
||||
// set a value
|
||||
const subSubFormat = this.get(ruleid);
|
||||
if (subSubFormat.mutuallyExclusiveWith && ruleTable.valueRules.has(subSubFormat.mutuallyExclusiveWith)) {
|
||||
// mutually exclusive conflict!
|
||||
throw new Error(`Rule "${ruleid}=${newValue}" from ${subformat.name}${subRuleTable.blame(ruleid)} conflicts with "${subSubFormat.mutuallyExclusiveWith}=${ruleTable.valueRules.get(subSubFormat.mutuallyExclusiveWith)}"${ruleTable.blame(subSubFormat.mutuallyExclusiveWith)} (Repeal one with ! before adding another)`);
|
||||
}
|
||||
if (newValue !== oldValue) {
|
||||
if (oldValue !== undefined) {
|
||||
// conflict!
|
||||
throw new Error(`Rule "${ruleid}=${newValue}" from ${subformat.name}${subRuleTable.blame(ruleid)} conflicts with "${ruleid}=${oldValue}"${ruleTable.blame(ruleid)} (Repeal one with ! before adding another)`);
|
||||
}
|
||||
ruleTable.valueRules.set(ruleid, newValue);
|
||||
}
|
||||
}
|
||||
ruleTable.set(ruleid, sourceFormat || subformat.name);
|
||||
}
|
||||
for (const [subRule, source, limit, bans] of subRuleTable.complexBans) {
|
||||
ruleTable.addComplexBan(subRule, source || subformat.name, limit, bans);
|
||||
|
|
@ -871,6 +926,9 @@ export class DexFormats {
|
|||
ruleTable.checkCanLearn = subRuleTable.checkCanLearn;
|
||||
}
|
||||
}
|
||||
if (!hasPokemonBans && warnForNoPokemonBans) {
|
||||
throw new Error(`"+All Pokemon" rule has no effect (no species are banned by default, and it does not override obtainability rules)`);
|
||||
}
|
||||
ruleTable.getTagRules();
|
||||
|
||||
ruleTable.resolveNumbers(format, this.dex);
|
||||
|
|
@ -937,6 +995,8 @@ export class DexFormats {
|
|||
throw new Error(`Unrecognized rule "${rule}"`);
|
||||
}
|
||||
if (typeof value === 'string') id = `${id}=${value.trim()}`;
|
||||
if (rule.startsWith('^!')) return `^!${id}`;
|
||||
if (rule.startsWith('^')) return `^${id}`;
|
||||
if (rule.startsWith('!!')) return `!!${id}`;
|
||||
if (rule.startsWith('!')) return `!${id}`;
|
||||
return id;
|
||||
|
|
|
|||
|
|
@ -47,8 +47,11 @@ assert.atMost = function (value, threshold, message) {
|
|||
});
|
||||
};
|
||||
|
||||
assert.legalTeam = function (team, format, message) {
|
||||
const actual = require('../dist/sim/team-validator').TeamValidator.get(format).validateTeam(team);
|
||||
assert.legalTeam = function (team, formatName, message) {
|
||||
require('../dist/sim/dex').Dex.formats.validate(formatName);
|
||||
const format = require('../dist/sim/team-validator').TeamValidator.get(formatName);
|
||||
// console.log(`${formatName}: ${[...format.ruleTable.keys()].join(', ')}`);
|
||||
const actual = format.validateTeam(team);
|
||||
if (actual === null) return;
|
||||
throw new AssertionError({
|
||||
message: message || "Expected team to be valid, but it was rejected because:\n" + actual.join("\n"),
|
||||
|
|
|
|||
|
|
@ -49,19 +49,19 @@ class TestTools {
|
|||
}
|
||||
|
||||
const gameType = Dex.toID(options.gameType || 'singles');
|
||||
let basicFormat = this.currentMod === 'base' && gameType === 'singles' ? 'Anything Goes' : 'Custom Game';
|
||||
const customRules = [
|
||||
options.pokemon && '-Nonexistent',
|
||||
options.legality && 'Obtainable',
|
||||
!options.preview && '!Team Preview',
|
||||
!options.pokemon && '+Nonexistent',
|
||||
options.legality ? '^Obtainable' : '^!Obtainable',
|
||||
options.preview ? '^Team Preview' : '^!Team Preview',
|
||||
options.sleepClause && 'Sleep Clause Mod',
|
||||
!options.cancel && '!Cancel Mod',
|
||||
options.endlessBattleClause && 'Endless Battle Clause',
|
||||
options.endlessBattleClause ? '^Endless Battle Clause' : '^!Endless Battle Clause',
|
||||
options.inverseMod && 'Inverse Mod',
|
||||
options.overflowStatMod && 'Overflow Stat Mod',
|
||||
].filter(Boolean);
|
||||
const customRulesID = customRules.length ? `@@@${customRules.join(',')}` : ``;
|
||||
|
||||
let basicFormat = this.currentMod === 'base' && gameType === 'singles' ? 'Anything Goes' : 'Custom Game';
|
||||
let modPrefix = this.modPrefix;
|
||||
if (this.currentMod === 'gen1stadium') basicFormat = 'OU';
|
||||
if (gameType === 'multi') {
|
||||
|
|
@ -76,7 +76,7 @@ class TestTools {
|
|||
let format = formatsCache.get(formatName);
|
||||
if (format) return format;
|
||||
|
||||
format = Dex.formats.get(formatName);
|
||||
format = Dex.formats.get(formatName, true);
|
||||
if (format.effectType !== 'Format') throw new Error(`Unidentified format: ${formatName}`);
|
||||
|
||||
formatsCache.set(formatName, format);
|
||||
|
|
|
|||
|
|
@ -100,17 +100,18 @@ describe('value rule support (slow)', () => {
|
|||
|
||||
for (const format of Dex.formats.all()) {
|
||||
if (!format.team) continue;
|
||||
if (Dex.formats.getRuleTable(format).has('adjustleveldown') || Dex.formats.getRuleTable(format).has('adjustlevel')) continue; // already adjusts level
|
||||
it(`${format.name} should support Adjust Level`, () => {
|
||||
const ruleTable = Dex.formats.getRuleTable(format);
|
||||
if (ruleTable.has('adjustleveldown') || ruleTable.has('adjustlevel')) return; // already adjusts level
|
||||
|
||||
for (const level of [1, 99999]) {
|
||||
it(`${format.name} should support Adjust Level = ${level}`, () => {
|
||||
for (const level of [1, 99999]) {
|
||||
testTeam({ format: `${format.id}@@@Adjust Level = ${level}`, rounds: 50 }, team => {
|
||||
for (const set of team) {
|
||||
assert.equal(set.level, level);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
const assert = require('../../assert');
|
||||
const Dex = require('../../../dist/sim/dex').Dex;
|
||||
|
||||
describe("Custom Rules", () => {
|
||||
it('should support legality tags', () => {
|
||||
|
|
@ -57,10 +58,46 @@ describe("Custom Rules", () => {
|
|||
];
|
||||
assert.false.legalTeam(team, 'gen7ubers@@@-allpokemon,+giratinaaltered');
|
||||
|
||||
// -allpokemon should override +past
|
||||
team = [
|
||||
{ species: 'tyrantrum', ability: 'strongjaw', moves: ['protect'], evs: { hp: 1 } },
|
||||
];
|
||||
assert.false.legalTeam(team, 'gen8nationaldex@@@-allpokemon');
|
||||
|
||||
// +pikachu should not override -past
|
||||
team = [
|
||||
{ species: 'pikachu-belle', ability: 'lightningrod', moves: ['thunderbolt'], evs: { hp: 1 } },
|
||||
];
|
||||
assert.false.legalTeam(team, 'gen7ou@@@-allpokemon,+pikachu');
|
||||
});
|
||||
|
||||
it('should allow Pokemon to be force-whitelisted', () => {
|
||||
// -allpokemon should override +cloyster before it, but not after it
|
||||
let team = [
|
||||
{ species: 'cloyster', ability: 'skilllink', moves: ['iciclespear'], evs: { hp: 1 } },
|
||||
];
|
||||
|
||||
assert.legalTeam(team, 'gen5monotype');
|
||||
assert.false.legalTeam(team, 'gen5monotype@@@-allpokemon');
|
||||
assert.throws(() => Dex.formats.validate('gen5monotype@@@+cloyster,-allpokemon'));
|
||||
assert.legalTeam(team, 'gen5monotype@@@-allpokemon,+cloyster');
|
||||
|
||||
team = [
|
||||
{ species: 'pikachu', ability: 'lightningrod', moves: ['thunderbolt'], evs: { hp: 1 } },
|
||||
];
|
||||
assert.legalTeam(team, 'gen9ou@@@-allpokemon,+pikachu');
|
||||
assert.throws(() => Dex.formats.validate('gen9ou@@@+pikachu,-allpokemon'));
|
||||
});
|
||||
|
||||
it('should warn when rules do nothing', () => {
|
||||
assert.throws(() => Dex.formats.validate('gen9anythinggoes@@@obtainable'));
|
||||
Dex.formats.validate('gen9anythinggoes@@@!obtainable');
|
||||
|
||||
assert.throws(() => Dex.formats.validate('gen9customgame@@@!obtainable'));
|
||||
Dex.formats.validate('gen9customgame@@@obtainable');
|
||||
|
||||
assert.throws(() => Dex.formats.validate('gen9customgame@@@+cloyster,-allpokemon'));
|
||||
Dex.formats.validate('gen9customgame@@@-allpokemon,+cloyster');
|
||||
});
|
||||
|
||||
it('should support banning/unbanning tag combinations', () => {
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ describe('Team Validator', () => {
|
|||
team = [
|
||||
{ species: 'raichualola', ability: 'surgesurfer', moves: ['fakeout'], evs: { hp: 1 } },
|
||||
];
|
||||
assert.legalTeam(team, 'gen9anythinggoes@@@minsourcegen=9');
|
||||
assert.legalTeam(team, 'gen9anythinggoes');
|
||||
});
|
||||
|
||||
it('should prevent Pokemon that don\'t evolve via level-up and evolve from a Pokemon that does evolve via level-up from being underleveled.', () => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user