From f4889cac746787fb8156278722302220e45b0076 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Bastos=20Dias?= <80102738+andrebastosdias@users.noreply.github.com> Date: Wed, 18 Feb 2026 01:34:52 +0000 Subject: [PATCH] Implement Zacian and Zamazenta transformations as species conditions (#11748) * Implement Zacian and Zamazenta transformations as species conditions * Remove custom transformation from gen8linked --- data/conditions.ts | 59 +++++++++++++++++++++++++++++++ data/mods/gen8linked/scripts.ts | 37 +------------------ data/mods/gen9ssb/conditions.ts | 58 ++++++++++++++++++++++++++++++ data/mods/gen9ssb/scripts.ts | 37 +------------------ data/mods/monkeyspaw/scripts.ts | 37 +------------------ sim/battle.ts | 37 +------------------ sim/dex-conditions.ts | 1 + test/sim/team-validator/formes.js | 7 ++-- 8 files changed, 125 insertions(+), 148 deletions(-) diff --git a/data/conditions.ts b/data/conditions.ts index 44406ca01a..cf749e1f66 100644 --- a/data/conditions.ts +++ b/data/conditions.ts @@ -879,6 +879,65 @@ export const Conditions: import('../sim/dex-conditions').ConditionDataTable = { return [type]; }, }, + zacian: { + name: 'Zacian', + onBattleStart(pokemon) { + if (pokemon.item !== 'rustedsword') return; + const rawSpecies = this.dex.species.get('Zacian-Crowned'); + const species = pokemon.setSpecies(rawSpecies); + if (!species) return; + pokemon.baseSpecies = rawSpecies; + pokemon.details = pokemon.getUpdatedDetails(); + pokemon.setAbility(species.abilities['0'], null, null, true); + pokemon.baseAbility = pokemon.ability; + + const ironHeadIndex = pokemon.baseMoves.indexOf('ironhead'); + if (ironHeadIndex >= 0) { + const move = this.dex.moves.get('behemothblade'); + pokemon.baseMoveSlots[ironHeadIndex] = { + 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(); + } + }, + }, + zamazenta: { + name: 'Zamazenta', + onBattleStart(pokemon) { + if (pokemon.item !== 'rustedshield') return; + const rawSpecies = this.dex.species.get('Zamazenta-Crowned'); + const species = pokemon.setSpecies(rawSpecies); + if (!species) return; + pokemon.baseSpecies = rawSpecies; + pokemon.details = pokemon.getUpdatedDetails(); + pokemon.setAbility(species.abilities['0'], null, null, true); + pokemon.baseAbility = pokemon.ability; + + const ironHeadIndex = pokemon.baseMoves.indexOf('ironhead'); + if (ironHeadIndex >= 0) { + const move = this.dex.moves.get('behemothbash'); + pokemon.baseMoveSlots[ironHeadIndex] = { + 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(); + } + }, + }, + rolloutstorage: { name: 'rolloutstorage', duration: 2, diff --git a/data/mods/gen8linked/scripts.ts b/data/mods/gen8linked/scripts.ts index 1962e1d16f..7a9766829f 100644 --- a/data/mods/gen8linked/scripts.ts +++ b/data/mods/gen8linked/scripts.ts @@ -70,40 +70,8 @@ export const Scripts: ModdedBattleScriptsData = { this.add('start'); - // 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, 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.singleEvent('BattleStart', this.dex.conditions.getByID(pokemon.species.id), pokemon.speciesState, pokemon); } this.format.onBattleStart?.call(this); @@ -125,9 +93,6 @@ export const Scripts: ModdedBattleScriptsData = { } } } - for (const pokemon of this.getAllPokemon()) { - this.singleEvent('Start', this.dex.conditions.getByID(pokemon.species.id), pokemon.speciesState, pokemon); - } this.midTurn = true; break; } diff --git a/data/mods/gen9ssb/conditions.ts b/data/mods/gen9ssb/conditions.ts index 7a6f4cc3ce..a5984188f1 100644 --- a/data/mods/gen9ssb/conditions.ts +++ b/data/mods/gen9ssb/conditions.ts @@ -3,6 +3,64 @@ import { changeSet, getName, enemyStaff } from './scripts'; import type { ModdedConditionData } from "../../../sim/dex-conditions"; export const Conditions: { [id: IDEntry]: ModdedConditionData & { innateName?: string } } = { + zacian: { + inherit: true, + onBattleStart(pokemon) { + if (pokemon.item !== 'rustedsword') return; + const rawSpecies = this.dex.species.get('Zacian-Crowned'); + const species = pokemon.setSpecies(rawSpecies); + if (!species) return; + pokemon.baseSpecies = rawSpecies; + pokemon.details = pokemon.getUpdatedDetails(); + // pokemon.setAbility(species.abilities['0'], null, null, true); + // pokemon.baseAbility = pokemon.ability; + + const ironHeadIndex = pokemon.baseMoves.indexOf('ironhead'); + if (ironHeadIndex >= 0) { + const move = this.dex.moves.get('behemothblade'); + pokemon.baseMoveSlots[ironHeadIndex] = { + 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(); + } + }, + }, + zamazenta: { + inherit: true, + onBattleStart(pokemon) { + if (pokemon.item !== 'rustedshield') return; + const rawSpecies = this.dex.species.get('Zamazenta-Crowned'); + const species = pokemon.setSpecies(rawSpecies); + if (!species) return; + pokemon.baseSpecies = rawSpecies; + pokemon.details = pokemon.getUpdatedDetails(); + // pokemon.setAbility(species.abilities['0'], null, null, true); + // pokemon.baseAbility = pokemon.ability; + + const ironHeadIndex = pokemon.baseMoves.indexOf('ironhead'); + if (ironHeadIndex >= 0) { + const move = this.dex.moves.get('behemothbash'); + pokemon.baseMoveSlots[ironHeadIndex] = { + 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(); + } + }, + }, /* // Example: userid: { diff --git a/data/mods/gen9ssb/scripts.ts b/data/mods/gen9ssb/scripts.ts index 6be6a31470..4113928614 100644 --- a/data/mods/gen9ssb/scripts.ts +++ b/data/mods/gen9ssb/scripts.ts @@ -374,40 +374,8 @@ export const Scripts: ModdedBattleScriptsData = { this.add('start'); - // 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, 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.singleEvent('BattleStart', this.dex.conditions.getByID(pokemon.species.id), pokemon.speciesState, pokemon); } this.format.onBattleStart?.call(this); @@ -429,9 +397,6 @@ export const Scripts: ModdedBattleScriptsData = { } } } - for (const pokemon of this.getAllPokemon()) { - this.singleEvent('Start', this.dex.conditions.getByID(pokemon.species.id), pokemon.speciesState, pokemon); - } this.midTurn = true; break; } diff --git a/data/mods/monkeyspaw/scripts.ts b/data/mods/monkeyspaw/scripts.ts index ce494c72bd..86a484e7ae 100644 --- a/data/mods/monkeyspaw/scripts.ts +++ b/data/mods/monkeyspaw/scripts.ts @@ -193,40 +193,8 @@ export const Scripts: ModdedBattleScriptsData = { this.add('start'); - // 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, 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.singleEvent('BattleStart', this.dex.conditions.getByID(pokemon.species.id), pokemon.speciesState, pokemon); } this.format.onBattleStart?.call(this); @@ -248,9 +216,6 @@ export const Scripts: ModdedBattleScriptsData = { } } } - for (const pokemon of this.getAllPokemon()) { - this.singleEvent('Start', this.dex.conditions.getByID(pokemon.species.id), pokemon.speciesState, pokemon); - } this.midTurn = true; break; } diff --git a/sim/battle.ts b/sim/battle.ts index cbfbda0de2..f06c223980 100644 --- a/sim/battle.ts +++ b/sim/battle.ts @@ -2672,40 +2672,8 @@ export class Battle { this.add('start'); - // 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, null, true); - pokemon.baseAbility = pokemon.ability; - - const behemothMove: { [k: string]: string } = { - 'Zacian-Crowned': 'behemothblade', 'Zamazenta-Crowned': 'behemothbash', - }; - const ironHeadIndex = pokemon.baseMoves.indexOf('ironhead'); - if (ironHeadIndex >= 0) { - const move = this.dex.moves.get(behemothMove[rawSpecies.name]); - pokemon.baseMoveSlots[ironHeadIndex] = { - 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.singleEvent('BattleStart', this.dex.conditions.getByID(pokemon.species.id), pokemon.speciesState, pokemon); } this.format.onBattleStart?.call(this); @@ -2727,9 +2695,6 @@ export class Battle { } } } - for (const pokemon of this.getAllPokemon()) { - this.singleEvent('Start', this.dex.conditions.getByID(pokemon.species.id), pokemon.speciesState, pokemon); - } this.midTurn = true; break; } diff --git a/sim/dex-conditions.ts b/sim/dex-conditions.ts index 057d6f3284..f900795ad6 100644 --- a/sim/dex-conditions.ts +++ b/sim/dex-conditions.ts @@ -639,6 +639,7 @@ export class Condition extends BasicEffect implements declare readonly onStart?: ( this: Battle, target: Pokemon, source: Pokemon, sourceEffect: Effect ) => boolean | null | void; + declare readonly onBattleStart?: (this: Battle, pokemon: Pokemon) => void; constructor(data: AnyObject) { super(data); diff --git a/test/sim/team-validator/formes.js b/test/sim/team-validator/formes.js index 518f782fb6..f405477b79 100644 --- a/test/sim/team-validator/formes.js +++ b/test/sim/team-validator/formes.js @@ -100,17 +100,16 @@ describe('Team Validator', () => { assert.legalTeam(team, 'gen7anythinggoes'); }); - // Zamazenta is unreleased currently - it.skip('should tier Zacian and Zamazenta formes separately', () => { + it('should tier Zacian and Zamazenta formes separately', () => { team = [ { species: 'zamazenta-crowned', ability: 'dauntlessshield', item: 'rustedshield', moves: ['howl'], evs: { hp: 1 } }, ]; - assert.legalTeam(team, 'gen9almostanyability'); + assert.false.legalTeam(team, 'gen9ou'); team = [ { species: 'zamazenta', ability: 'dauntlessshield', item: 'lifeorb', moves: ['howl'], evs: { hp: 1 } }, ]; - assert.false.legalTeam(team, 'gen9almostanyability'); + assert.legalTeam(team, 'gen9ou'); }); it('should validate Unown formes in Gen 2 based on DVs', () => {