Allow Picked Team Size to be used without Team Preview (#11224)

* Add Pick Team rule

* Make Team Preview and Pick Team mutually exclusive

* Automatically apply rule if Picked Team Size exists without preview

* Remove mutually exclusive part

* Move teamsize to start action
This commit is contained in:
Karthik Bandagonda
2025-07-01 03:20:57 -04:00
committed by GitHub
parent bc832df368
commit d3d834c91a
9 changed files with 105 additions and 39 deletions

View File

@@ -4653,7 +4653,7 @@ export const Formats: import('../sim/dex-formats').FormatList = [
mod: 'gen4',
gameType: 'doubles',
searchShow: false,
ruleset: ['Flat Rules', 'Min Team Size = 4', 'Max Team Size = 4', 'Limit Two Restricted'],
ruleset: ['Flat Rules', 'Limit Two Restricted'],
restricted: ['Restricted Legendary'],
banlist: ['Soul Dew'],
},
@@ -4662,7 +4662,7 @@ export const Formats: import('../sim/dex-formats').FormatList = [
mod: 'gen4pt',
gameType: 'doubles',
searchShow: false,
ruleset: ['Flat Rules', '! Adjust Level Down', 'Max Level = 50', 'Min Team Size = 4', 'Max Team Size = 4'],
ruleset: ['Flat Rules', '! Adjust Level Down', 'Max Level = 50'],
banlist: ['Tyranitar', 'Rotom', 'Judgment', 'Soul Dew'],
},
{

View File

@@ -5,7 +5,7 @@ export const Rulesets: import('../../../sim/dex-formats').ModdedFormatDataTable
},
flatrules: {
inherit: true,
ruleset: ['Obtainable', 'Species Clause', 'Nickname Clause', 'Item Clause = 1', 'Adjust Level Down = 50', 'Cancel Mod'],
ruleset: ['Obtainable', 'Species Clause', 'Nickname Clause', 'Item Clause = 1', 'Adjust Level Down = 50', 'Picked Team Size = Auto', 'Cancel Mod'],
},
teampreview: {
inherit: true,

View File

@@ -65,15 +65,52 @@ export const Scripts: ModdedBattleScriptsData = {
case 'start': {
for (const side of this.sides) {
if (side.pokemonLeft) side.pokemonLeft = side.pokemon.length;
this.add('teamsize', side.id, side.pokemon.length);
}
this.add('start');
if (this.format.onBattleStart) this.format.onBattleStart.call(this);
// Change Zacian/Zamazenta into their Crowned formes
for (const pokemon of this.getAllPokemon()) {
let rawSpecies: Species | null = null;
if (pokemon.species.id === 'zacian' && pokemon.item === 'rustedsword') {
rawSpecies = this.dex.species.get('Zacian-Crowned');
} else if (pokemon.species.id === 'zamazenta' && pokemon.item === 'rustedshield') {
rawSpecies = this.dex.species.get('Zamazenta-Crowned');
}
if (!rawSpecies) continue;
const species = pokemon.setSpecies(rawSpecies);
if (!species) continue;
pokemon.baseSpecies = rawSpecies;
pokemon.details = pokemon.getUpdatedDetails();
// pokemon.setAbility(species.abilities['0'], null, true);
// pokemon.baseAbility = pokemon.ability;
const behemothMove: { [k: string]: string } = {
'Zacian-Crowned': 'behemothblade', 'Zamazenta-Crowned': 'behemothbash',
};
const ironHead = pokemon.baseMoves.indexOf('ironhead');
if (ironHead >= 0) {
const move = this.dex.moves.get(behemothMove[rawSpecies.name]);
pokemon.baseMoveSlots[ironHead] = {
move: move.name,
id: move.id,
pp: move.noPPBoosts ? move.pp : move.pp * 8 / 5,
maxpp: move.noPPBoosts ? move.pp : move.pp * 8 / 5,
target: move.target,
disabled: false,
disabledSource: '',
used: false,
};
pokemon.moveSlots = pokemon.baseMoveSlots.slice();
}
}
this.format.onBattleStart?.call(this);
for (const rule of this.ruleTable.keys()) {
if ('+*-!'.includes(rule.charAt(0))) continue;
const subFormat = this.dex.formats.get(rule);
if (subFormat.onBattleStart) subFormat.onBattleStart.call(this);
subFormat.onBattleStart?.call(this);
}
for (const side of this.sides) {

View File

@@ -369,6 +369,7 @@ export const Scripts: ModdedBattleScriptsData = {
case 'start': {
for (const side of this.sides) {
if (side.pokemonLeft) side.pokemonLeft = side.pokemon.length;
this.add('teamsize', side.id, side.pokemon.length);
}
this.add('start');
@@ -409,11 +410,11 @@ export const Scripts: ModdedBattleScriptsData = {
}
}
if (this.format.onBattleStart) this.format.onBattleStart.call(this);
this.format.onBattleStart?.call(this);
for (const rule of this.ruleTable.keys()) {
if ('+*-!'.includes(rule.charAt(0))) continue;
const subFormat = this.dex.formats.get(rule);
if (subFormat.onBattleStart) subFormat.onBattleStart.call(this);
subFormat.onBattleStart?.call(this);
}
for (const side of this.sides) {

View File

@@ -42,10 +42,6 @@ export const Scripts: ModdedBattleScriptsData = {
}
}
for (const side of this.sides) {
this.add('teamsize', side.id, side.pokemon.length);
}
this.add('gen', this.gen);
this.add('tier', format.name);
@@ -54,11 +50,11 @@ export const Scripts: ModdedBattleScriptsData = {
this.add('rated', typeof this.rated === 'string' ? this.rated : '');
}
if (format.onBegin) format.onBegin.call(this);
format.onBegin?.call(this);
for (const rule of this.ruleTable.keys()) {
if ('+*-!'.includes(rule.charAt(0))) continue;
const subFormat = this.dex.formats.get(rule);
if (subFormat.onBegin) subFormat.onBegin.call(this);
subFormat.onBegin?.call(this);
}
for (const pokemon of this.getAllPokemon()) {
const item = pokemon.getItem();
@@ -87,11 +83,25 @@ export const Scripts: ModdedBattleScriptsData = {
this.add(`raw|<div class="infobox"><details class="readmore"${open}><summary><strong>${format.customRules.length} custom rule${plural}:</strong></summary> ${format.customRules.join(', ')}</details></div>`);
}
if (format.onTeamPreview) format.onTeamPreview.call(this);
format.onTeamPreview?.call(this);
for (const rule of this.ruleTable.keys()) {
if ('+*-!'.includes(rule.charAt(0))) continue;
const subFormat = this.dex.formats.get(rule);
if (subFormat.onTeamPreview) subFormat.onTeamPreview.call(this);
subFormat.onTeamPreview?.call(this);
}
if (this.requestState !== 'teampreview' && this.ruleTable.pickedTeamSize) {
this.add('clearpoke');
for (const side of this.sides) {
for (const pokemon of side.pokemon) {
// Still need to hide these formes since they change on battle start
const details = pokemon.details.replace(', shiny', '')
.replace(/(Zacian|Zamazenta)(?!-Crowned)/g, '$1-*')
.replace(/(Xerneas)(-[a-zA-Z?-]+)?/g, '$1-*');
this.addSplit(side.id, ['poke', pokemon.side.id, details, '']);
}
}
this.makeRequest('teampreview');
}
this.queue.addChoice({ choice: 'start' });
@@ -106,6 +116,7 @@ export const Scripts: ModdedBattleScriptsData = {
case 'start': {
for (const side of this.sides) {
if (side.pokemonLeft) side.pokemonLeft = side.pokemon.length;
this.add('teamsize', side.id, side.pokemon.length);
}
this.add('start');
@@ -147,11 +158,11 @@ export const Scripts: ModdedBattleScriptsData = {
}
}
if (this.format.onBattleStart) this.format.onBattleStart.call(this);
this.format.onBattleStart?.call(this);
for (const rule of this.ruleTable.keys()) {
if ('+*-!'.includes(rule.charAt(0))) continue;
const subFormat = this.dex.formats.get(rule);
if (subFormat.onBattleStart) subFormat.onBattleStart.call(this);
subFormat.onBattleStart?.call(this);
}
for (const side of this.sides) {

View File

@@ -188,6 +188,7 @@ export const Scripts: ModdedBattleScriptsData = {
case 'start': {
for (const side of this.sides) {
if (side.pokemonLeft) side.pokemonLeft = side.pokemon.length;
this.add('teamsize', side.id, side.pokemon.length);
}
this.add('start');
@@ -228,11 +229,11 @@ export const Scripts: ModdedBattleScriptsData = {
}
}
if (this.format.onBattleStart) this.format.onBattleStart.call(this);
this.format.onBattleStart?.call(this);
for (const rule of this.ruleTable.keys()) {
if ('+*-!'.includes(rule.charAt(0))) continue;
const subFormat = this.dex.formats.get(rule);
if (subFormat.onBattleStart) subFormat.onBattleStart.call(this);
subFormat.onBattleStart?.call(this);
}
for (const side of this.sides) {

View File

@@ -80,10 +80,6 @@ export const Scripts: ModdedBattleScriptsData = {
}
}
for (const side of this.sides) {
this.add('teamsize', side.id, side.pokemon.length);
}
this.add('gen', this.gen);
this.add('tier', format.name);
@@ -92,11 +88,11 @@ export const Scripts: ModdedBattleScriptsData = {
this.add('rated', typeof this.rated === 'string' ? this.rated : '');
}
if (format.onBegin) format.onBegin.call(this);
format.onBegin?.call(this);
for (const rule of this.ruleTable.keys()) {
if ('+*-!'.includes(rule.charAt(0))) continue;
const subFormat = this.dex.formats.get(rule);
if (subFormat.onBegin) subFormat.onBegin.call(this);
subFormat.onBegin?.call(this);
}
if (this.sides.some(side => !side.pokemon[0])) {
@@ -113,11 +109,25 @@ export const Scripts: ModdedBattleScriptsData = {
this.add(`raw|<div class="infobox"><details class="readmore"${open}><summary><strong>${format.customRules.length} custom rule${plural}:</strong></summary> ${format.customRules.join(', ')}</details></div>`);
}
if (format.onTeamPreview) format.onTeamPreview.call(this);
format.onTeamPreview?.call(this);
for (const rule of this.ruleTable.keys()) {
if ('+*-!'.includes(rule.charAt(0))) continue;
const subFormat = this.dex.formats.get(rule);
if (subFormat.onTeamPreview) subFormat.onTeamPreview.call(this);
subFormat.onTeamPreview?.call(this);
}
if (this.requestState !== 'teampreview' && this.ruleTable.pickedTeamSize) {
this.add('clearpoke');
for (const side of this.sides) {
for (const pokemon of side.pokemon) {
// Still need to hide these formes since they change on battle start
const details = pokemon.details.replace(', shiny', '')
.replace(/(Zacian|Zamazenta)(?!-Crowned)/g, '$1-*')
.replace(/(Xerneas)(-[a-zA-Z?-]+)?/g, '$1-*');
this.addSplit(side.id, ['poke', pokemon.side.id, details, '']);
}
}
this.makeRequest('teampreview');
}
this.queue.addChoice({ choice: 'start' });

View File

@@ -1976,12 +1976,7 @@ export const Rulesets: import('../sim/dex-formats').FormatDataTable = {
name: 'Picked Team Size',
desc: "Team size (number of pokemon) that can be brought out of Team Preview",
hasValue: 'positive-integer',
// hardcoded in sim/side
onValidateRule() {
if (!(this.ruleTable.has('teampreview') || this.ruleTable.has('teamtypepreview'))) {
throw new Error(`The "Picked Team Size" rule${this.ruleTable.blame('pickedteamsize')} requires Team Preview.`);
}
},
// hardcoded in sim/side and sim/battle
},
minteamsize: {
effectType: 'ValidatorRule',

View File

@@ -1868,10 +1868,6 @@ export class Battle {
}
}
for (const side of this.sides) {
this.add('teamsize', side.id, side.pokemon.length);
}
this.add('gen', this.gen);
this.add('tier', format.name);
@@ -1908,6 +1904,20 @@ export class Battle {
subFormat.onTeamPreview?.call(this);
}
if (this.requestState !== 'teampreview' && this.ruleTable.pickedTeamSize) {
this.add('clearpoke');
for (const side of this.sides) {
for (const pokemon of side.pokemon) {
// Still need to hide these formes since they change on battle start
const details = pokemon.details.replace(', shiny', '')
.replace(/(Zacian|Zamazenta)(?!-Crowned)/g, '$1-*')
.replace(/(Xerneas)(-[a-zA-Z?-]+)?/g, '$1-*');
this.addSplit(side.id, ['poke', pokemon.side.id, details, '']);
}
}
this.makeRequest('teampreview');
}
this.queue.addChoice({ choice: 'start' });
this.midTurn = true;
if (!this.requestState) this.turnLoop();
@@ -2601,6 +2611,7 @@ export class Battle {
case 'start': {
for (const side of this.sides) {
if (side.pokemonLeft) side.pokemonLeft = side.pokemon.length;
this.add('teamsize', side.id, side.pokemon.length);
}
this.add('start');
@@ -2641,11 +2652,11 @@ export class Battle {
}
}
if (this.format.onBattleStart) this.format.onBattleStart.call(this);
this.format.onBattleStart?.call(this);
for (const rule of this.ruleTable.keys()) {
if ('+*-!'.includes(rule.charAt(0))) continue;
const subFormat = this.dex.formats.get(rule);
if (subFormat.onBattleStart) subFormat.onBattleStart.call(this);
subFormat.onBattleStart?.call(this);
}
for (const side of this.sides) {