diff --git a/sim/dex-formats.ts b/sim/dex-formats.ts index 74cf990c48..ff9e2f420b 100644 --- a/sim/dex-formats.ts +++ b/sim/dex-formats.ts @@ -97,6 +97,11 @@ export class RuleTable extends Map { return this.has(`*pokemontag:allpokemon`); } + /** + * - non-empty string: banned, string is the reason + * - '': whitelisted + * - null: neither whitelisted nor banned + */ check(thing: string, setHas: {[id: string]: true} | null = null) { if (this.has(`+${thing}`)) return ''; if (setHas) setHas[thing] = true; @@ -619,8 +624,6 @@ export class DexFormats { getTagRules(ruleTable: RuleTable) { const tagRules = []; - const specificExistenceTagRules = []; - const existenceTagRules = []; for (const ruleid of ruleTable.keys()) { if (/^[+*-]pokemontag:/.test(ruleid)) { const banid = ruleid.slice(12); @@ -629,25 +632,14 @@ export class DexFormats { banid === 'allabilities' || banid === 'allnatures' ) { // hardcoded and not a part of the ban rule system - } else if (!ruleid.startsWith('+') && ( - banid === 'past' || banid === 'future' || banid === 'lgpe' || - banid === 'unobtainable' || banid === 'cap' || banid === 'custom' - )) { - specificExistenceTagRules.push(ruleid); - } else if (!ruleid.startsWith('+') && banid === 'nonexistent') { - existenceTagRules.push(ruleid); } else { tagRules.push(ruleid); } } else if ('+*-'.includes(ruleid.charAt(0)) && ruleid.slice(1) === 'nonexistent') { - if (!ruleid.startsWith('+')) { - existenceTagRules.push(ruleid.charAt(0) + 'pokemontag:nonexistent'); - } else { - tagRules.push('+pokemontag:nonexistent'); - } + tagRules.push(ruleid.charAt(0) + 'pokemontag:nonexistent'); } } - ruleTable.tagRules = [...tagRules, ...existenceTagRules, ...specificExistenceTagRules].reverse(); + ruleTable.tagRules = tagRules.reverse(); } validateRule(rule: string, format: Format | null = null) { diff --git a/sim/team-validator.ts b/sim/team-validator.ts index d64de8bd88..d987d04196 100644 --- a/sim/team-validator.ts +++ b/sim/team-validator.ts @@ -1334,15 +1334,25 @@ export class TeamValidator { } } + // We can't return here because the `-nonexistent` rule is a bit + // complicated in terms of what trumps it. We don't want e.g. + // +Mythical to unban Shaymin in Gen 1, for instance. + const nonexistentCheck = Tags.nonexistent.genericFilter!(tierSpecies) && ruleTable.check('nonexistent'); + const EXISTENCE_TAG = ['past', 'future', 'lgpe', 'unobtainable', 'cap', 'custom', 'nonexistent']; - console.log(ruleTable.tagRules); + for (const ruleid of ruleTable.tagRules) { if (ruleid.startsWith('*')) continue; const tagid = ruleid.slice(12); const tag = Tags[tagid]; if ((tag.speciesFilter || tag.genericFilter)!(tierSpecies)) { - if (ruleid.startsWith('+')) return null; - if (EXISTENCE_TAG.includes(tagid)) { + const existenceTag = EXISTENCE_TAG.includes(tagid); + if (ruleid.startsWith('+')) { + // we want rules like +CAP to trump -Nonexistent, but most tags shouldn't + if (!existenceTag && nonexistentCheck) continue; + return null; + } + if (existenceTag) { if (tierSpecies.isNonstandard === 'Past' || tierSpecies.isNonstandard === 'Future') { return `${tierSpecies.name} does not exist in Gen ${dex.gen}.`; } @@ -1363,6 +1373,11 @@ export class TeamValidator { } } + if (nonexistentCheck) { + return `Despite being whitelisted by a tag, ${tierSpecies.name} does not exist in this game.`; + } + if (nonexistentCheck === '') return null; + // Special casing for Pokemon that can Gmax, but their Gmax factor cannot be legally obtained if (tierSpecies.gmaxUnreleased && set.gigantamax) { banReason = ruleTable.check('pokemontag:unobtainable'); diff --git a/test/sim/team-validator.js b/test/sim/team-validator.js index c8f3dfb19f..cf10f6833a 100644 --- a/test/sim/team-validator.js +++ b/test/sim/team-validator.js @@ -767,16 +767,22 @@ describe('Team Validator', function () { it('should support banning/unbanning tag combinations', function () { let team = [ - {species: 'Blaziken-Mega', ability: 'Speed Boost', moves: ['protect'], evs: {hp: 1}}, + {species: 'Crucibelle-Mega', ability: 'Regenerator', moves: ['protect'], evs: {hp: 1}}, ]; let illegal = TeamValidator.get('gen8customgame@@@-nonexistent,+mega').validateTeam(team); assert(illegal, "Nonexistent should override all tags that aren't existence-related"); team = [ - {species: 'Blaziken-Mega', ability: 'Speed Boost', moves: ['protect'], evs: {hp: 1}}, + {species: 'Crucibelle-Mega', ability: 'Regenerator', moves: ['protect'], evs: {hp: 1}}, ]; illegal = TeamValidator.get('gen8customgame@@@+mega,-nonexistent').validateTeam(team); assert(illegal, "Nonexistent should override all tags that aren't existence-related"); + + team = [ + {species: 'Crucibelle-Mega', ability: 'Regenerator', moves: ['protect'], evs: {hp: 1}}, + ]; + illegal = TeamValidator.get('gen8customgame@@@-nonexistent,+crucibellemega').validateTeam(team); + assert.equal(illegal, null, "Nonexistent should override all tags that aren't existence-related"); }); it('should allow moves to be banned', function () {