diff --git a/src/battle-scene.ts b/src/battle-scene.ts index a7953a598cc..5586691a48d 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -103,12 +103,11 @@ import type UIPlugin from "phaser3-rex-plugins/templates/ui/ui-plugin"; import { addUiThemeOverrides } from "#app/ui/ui-theme"; import type PokemonData from "#app/system/pokemon-data"; import { Nature } from "#enums/nature"; -import type { SpeciesFormChange, SpeciesFormChangeTrigger } from "#app/data/pokemon-forms"; -import { - pokemonFormChanges, - SpeciesFormChangeManualTrigger, - SpeciesFormChangeTimeOfDayTrigger, -} from "#app/data/pokemon-forms"; +import type { SpeciesFormChange } from "#app/data/pokemon-forms"; +import type { SpeciesFormChangeTrigger } from "./data/pokemon-forms/form-change-triggers"; +import { pokemonFormChanges } from "#app/data/pokemon-forms"; +import { SpeciesFormChangeTimeOfDayTrigger } from "./data/pokemon-forms/form-change-triggers"; +import { SpeciesFormChangeManualTrigger } from "./data/pokemon-forms/form-change-triggers"; import { FormChangeItem } from "#enums/form-change-item"; import { getTypeRgb } from "#app/data/type"; import { PokemonType } from "#enums/pokemon-type"; diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index ce72c913f34..2bf5f5da341 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -40,10 +40,10 @@ import { ArenaTagSide } from "#enums/arena-tag-side"; import { BerryModifier, HitHealModifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; import { TerrainType } from "#app/data/terrain"; import { - SpeciesFormChangeAbilityTrigger, SpeciesFormChangeRevertWeatherFormTrigger, SpeciesFormChangeWeatherTrigger, -} from "#app/data/pokemon-forms"; +} from "../pokemon-forms/form-change-triggers"; +import { SpeciesFormChangeAbilityTrigger } from "../pokemon-forms/form-change-triggers"; import i18next from "i18next"; import { Command } from "#enums/command"; import { BerryModifierType } from "#app/modifier/modifier-type"; diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 9ca23e12a3c..593550d7d26 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -21,7 +21,7 @@ import { import { allMoves } from "./data-lists"; import { MoveFlags } from "#enums/MoveFlags"; import { MoveCategory } from "#enums/MoveCategory"; -import { SpeciesFormChangeAbilityTrigger } from "#app/data/pokemon-forms"; +import { SpeciesFormChangeAbilityTrigger } from "./pokemon-forms/form-change-triggers"; import { getStatusEffectHealText } from "#app/data/status-effect"; import { TerrainType } from "#app/data/terrain"; import { PokemonType } from "#enums/pokemon-type"; diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index cb3c789f53a..35742d5c672 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -107,7 +107,7 @@ import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { SwitchPhase } from "#app/phases/switch-phase"; import { SwitchSummonPhase } from "#app/phases/switch-summon-phase"; -import { SpeciesFormChangeRevertWeatherFormTrigger } from "../pokemon-forms"; +import { SpeciesFormChangeRevertWeatherFormTrigger } from "../pokemon-forms/form-change-triggers"; import type { GameMode } from "#app/game-mode"; import { applyChallenges } from "../challenge"; import { ChallengeType } from "#enums/challenge-type"; diff --git a/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts b/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts index 245b4db2b7a..456562ca70e 100644 --- a/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts +++ b/src/data/mystery-encounters/encounters/fun-and-games-encounter.ts @@ -25,7 +25,7 @@ import { getPokemonNameWithAffix } from "#app/messages"; import { PlayerGender } from "#enums/player-gender"; import { getPokeballAtlasKey, getPokeballTintColor } from "#app/data/pokeball"; import { addPokeballOpenParticles } from "#app/field/anims"; -import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms"; +import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms/form-change-triggers"; import { modifierTypes } from "#app/modifier/modifier-type"; import { Nature } from "#enums/nature"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; diff --git a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts index 4b17260098f..91754629821 100644 --- a/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts +++ b/src/data/mystery-encounters/encounters/the-winstrate-challenge-encounter.ts @@ -23,7 +23,7 @@ import { Nature } from "#enums/nature"; import { PokemonType } from "#enums/pokemon-type"; import { BerryType } from "#enums/berry-type"; import { Stat } from "#enums/stat"; -import { SpeciesFormChangeAbilityTrigger } from "#app/data/pokemon-forms"; +import { SpeciesFormChangeAbilityTrigger } from "#app/data/pokemon-forms/form-change-triggers"; import { applyPostBattleInitAbAttrs, PostBattleInitAbAttr } from "#app/data/abilities/ability"; import { showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; diff --git a/src/data/mystery-encounters/mystery-encounter-requirements.ts b/src/data/mystery-encounters/mystery-encounter-requirements.ts index b6e8af49008..bca34be723b 100644 --- a/src/data/mystery-encounters/mystery-encounter-requirements.ts +++ b/src/data/mystery-encounters/mystery-encounter-requirements.ts @@ -2,7 +2,8 @@ import { globalScene } from "#app/global-scene"; import { allAbilities } from "../data-lists"; import { EvolutionItem, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import { Nature } from "#enums/nature"; -import { pokemonFormChanges, SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms"; +import { pokemonFormChanges } from "#app/data/pokemon-forms"; +import { SpeciesFormChangeItemTrigger } from "../pokemon-forms/form-change-triggers"; import { FormChangeItem } from "#enums/form-change-item"; import { StatusEffect } from "#enums/status-effect"; import { PokemonType } from "#enums/pokemon-type"; diff --git a/src/data/pokemon-forms.ts b/src/data/pokemon-forms.ts index 7a169519b83..ea129454034 100644 --- a/src/data/pokemon-forms.ts +++ b/src/data/pokemon-forms.ts @@ -1,20 +1,30 @@ -import { PokemonFormChangeItemModifier } from "../modifier/modifier"; import type Pokemon from "../field/pokemon"; -import { StatusEffect } from "#enums/status-effect"; import { allMoves } from "./data-lists"; import { MoveCategory } from "#enums/MoveCategory"; import type { Constructor, nil } from "#app/utils/common"; import { AbilityId } from "#enums/ability-id"; import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; -import type { TimeOfDay } from "#enums/time-of-day"; -import { getPokemonNameWithAffix } from "#app/messages"; -import i18next from "i18next"; import { WeatherType } from "#enums/weather-type"; -import { Challenges } from "#app/enums/challenges"; import { SpeciesFormKey } from "#enums/species-form-key"; import { globalScene } from "#app/global-scene"; import { FormChangeItem } from "#enums/form-change-item"; +import { + MeloettaFormChangePostMoveTrigger, + SpeciesDefaultFormMatchTrigger, + SpeciesFormChangeAbilityTrigger, + SpeciesFormChangeActiveTrigger, + SpeciesFormChangeCompoundTrigger, + SpeciesFormChangeItemTrigger, + SpeciesFormChangeLapseTeraTrigger, + SpeciesFormChangeManualTrigger, + SpeciesFormChangeMoveLearnedTrigger, + SpeciesFormChangePreMoveTrigger, + SpeciesFormChangeRevertWeatherFormTrigger, + SpeciesFormChangeTeraTrigger, + type SpeciesFormChangeTrigger, + SpeciesFormChangeWeatherTrigger, +} from "./pokemon-forms/form-change-triggers"; export type SpeciesFormChangeConditionPredicate = (p: Pokemon) => boolean; export type SpeciesFormChangeConditionEnforceFunc = (p: Pokemon) => void; @@ -95,347 +105,6 @@ export class SpeciesFormChangeCondition { } } -export abstract class SpeciesFormChangeTrigger { - public description = ""; - - canChange(_pokemon: Pokemon): boolean { - return true; - } - - hasTriggerType(triggerType: Constructor): boolean { - return this instanceof triggerType; - } -} - -export class SpeciesFormChangeManualTrigger extends SpeciesFormChangeTrigger {} - -export class SpeciesFormChangeAbilityTrigger extends SpeciesFormChangeTrigger { - public description: string = i18next.t("pokemonEvolutions:Forms.ability"); -} - -export class SpeciesFormChangeCompoundTrigger { - public description = ""; - public triggers: SpeciesFormChangeTrigger[]; - - constructor(...triggers: SpeciesFormChangeTrigger[]) { - this.triggers = triggers; - this.description = this.triggers - .filter(trigger => trigger?.description?.length > 0) - .map(trigger => trigger.description) - .join(", "); - } - - canChange(pokemon: Pokemon): boolean { - for (const trigger of this.triggers) { - if (!trigger.canChange(pokemon)) { - return false; - } - } - - return true; - } - - hasTriggerType(triggerType: Constructor): boolean { - return !!this.triggers.find(t => t.hasTriggerType(triggerType)); - } -} - -export class SpeciesFormChangeItemTrigger extends SpeciesFormChangeTrigger { - public item: FormChangeItem; - public active: boolean; - - constructor(item: FormChangeItem, active = true) { - super(); - this.item = item; - this.active = active; - this.description = this.active - ? i18next.t("pokemonEvolutions:Forms.item", { - item: i18next.t(`modifierType:FormChangeItem.${FormChangeItem[this.item]}`), - }) - : i18next.t("pokemonEvolutions:Forms.deactivateItem", { - item: i18next.t(`modifierType:FormChangeItem.${FormChangeItem[this.item]}`), - }); - } - - canChange(pokemon: Pokemon): boolean { - return !!globalScene.findModifier( - m => - m instanceof PokemonFormChangeItemModifier && - m.pokemonId === pokemon.id && - m.formChangeItem === this.item && - m.active === this.active, - ); - } -} - -export class SpeciesFormChangeTimeOfDayTrigger extends SpeciesFormChangeTrigger { - public timesOfDay: TimeOfDay[]; - - constructor(...timesOfDay: TimeOfDay[]) { - super(); - this.timesOfDay = timesOfDay; - this.description = i18next.t("pokemonEvolutions:Forms.timeOfDay"); - } - - canChange(_pokemon: Pokemon): boolean { - return this.timesOfDay.indexOf(globalScene.arena.getTimeOfDay()) > -1; - } -} - -export class SpeciesFormChangeActiveTrigger extends SpeciesFormChangeTrigger { - public active: boolean; - - constructor(active = false) { - super(); - this.active = active; - this.description = this.active - ? i18next.t("pokemonEvolutions:Forms.enter") - : i18next.t("pokemonEvolutions:Forms.leave"); - } - - canChange(pokemon: Pokemon): boolean { - return pokemon.isActive(true) === this.active; - } -} - -export class SpeciesFormChangeStatusEffectTrigger extends SpeciesFormChangeTrigger { - public statusEffects: StatusEffect[]; - public invert: boolean; - - constructor(statusEffects: StatusEffect | StatusEffect[], invert = false) { - super(); - if (!Array.isArray(statusEffects)) { - statusEffects = [statusEffects]; - } - this.statusEffects = statusEffects; - this.invert = invert; - this.description = i18next.t("pokemonEvolutions:Forms.statusEffect"); - } - - canChange(pokemon: Pokemon): boolean { - return this.statusEffects.indexOf(pokemon.status?.effect || StatusEffect.NONE) > -1 !== this.invert; - } -} - -export class SpeciesFormChangeMoveLearnedTrigger extends SpeciesFormChangeTrigger { - public move: MoveId; - public known: boolean; - - constructor(move: MoveId, known = true) { - super(); - this.move = move; - this.known = known; - const moveKey = MoveId[this.move] - .split("_") - .filter(f => f) - .map((f, i) => (i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase())) - .join("") as unknown as string; - this.description = known - ? i18next.t("pokemonEvolutions:Forms.moveLearned", { - move: i18next.t(`move:${moveKey}.name`), - }) - : i18next.t("pokemonEvolutions:Forms.moveForgotten", { - move: i18next.t(`move:${moveKey}.name`), - }); - } - - canChange(pokemon: Pokemon): boolean { - return !!pokemon.moveset.filter(m => m.moveId === this.move).length === this.known; - } -} - -export abstract class SpeciesFormChangeMoveTrigger extends SpeciesFormChangeTrigger { - public movePredicate: (m: MoveId) => boolean; - public used: boolean; - - constructor(move: MoveId | ((m: MoveId) => boolean), used = true) { - super(); - this.movePredicate = typeof move === "function" ? move : (m: MoveId) => m === move; - this.used = used; - } -} - -export class SpeciesFormChangePreMoveTrigger extends SpeciesFormChangeMoveTrigger { - description = i18next.t("pokemonEvolutions:Forms.preMove"); - - canChange(pokemon: Pokemon): boolean { - const command = globalScene.currentBattle.turnCommands[pokemon.getBattlerIndex()]; - return !!command?.move && this.movePredicate(command.move.move) === this.used; - } -} - -export class SpeciesFormChangePostMoveTrigger extends SpeciesFormChangeMoveTrigger { - description = i18next.t("pokemonEvolutions:Forms.postMove"); - - canChange(pokemon: Pokemon): boolean { - return ( - pokemon.summonData && !!pokemon.getLastXMoves(1).filter(m => this.movePredicate(m.move)).length === this.used - ); - } -} - -export class MeloettaFormChangePostMoveTrigger extends SpeciesFormChangePostMoveTrigger { - override canChange(pokemon: Pokemon): boolean { - if (globalScene.gameMode.hasChallenge(Challenges.SINGLE_TYPE)) { - return false; - } - // Meloetta will not transform if it has the ability Sheer Force when using Relic Song - if (pokemon.hasAbility(AbilityId.SHEER_FORCE)) { - return false; - } - return super.canChange(pokemon); - } -} - -export class SpeciesDefaultFormMatchTrigger extends SpeciesFormChangeTrigger { - private formKey: string; - - constructor(formKey: string) { - super(); - this.formKey = formKey; - this.description = ""; - } - - canChange(pokemon: Pokemon): boolean { - return ( - this.formKey === - pokemon.species.forms[globalScene.getSpeciesFormIndex(pokemon.species, pokemon.gender, pokemon.getNature(), true)] - .formKey - ); - } -} - -/** - * Class used for triggering form changes based on the user's Tera type. - * Used by Ogerpon and Terapagos. - * @extends SpeciesFormChangeTrigger - */ -export class SpeciesFormChangeTeraTrigger extends SpeciesFormChangeTrigger { - description = i18next.t("pokemonEvolutions:Forms.tera"); -} - -/** - * Class used for triggering form changes based on the user's lapsed Tera type. - * Used by Ogerpon and Terapagos. - * @extends SpeciesFormChangeTrigger - */ -export class SpeciesFormChangeLapseTeraTrigger extends SpeciesFormChangeTrigger { - description = i18next.t("pokemonEvolutions:Forms.teraLapse"); -} - -/** - * Class used for triggering form changes based on weather. - * Used by Castform and Cherrim. - * @extends SpeciesFormChangeTrigger - */ -export class SpeciesFormChangeWeatherTrigger extends SpeciesFormChangeTrigger { - /** The ability that triggers the form change */ - public ability: AbilityId; - /** The list of weathers that trigger the form change */ - public weathers: WeatherType[]; - - constructor(ability: AbilityId, weathers: WeatherType[]) { - super(); - this.ability = ability; - this.weathers = weathers; - this.description = i18next.t("pokemonEvolutions:Forms.weather"); - } - - /** - * Checks if the Pokemon has the required ability and is in the correct weather while - * the weather or ability is also not suppressed. - * @param {Pokemon} pokemon the pokemon that is trying to do the form change - * @returns `true` if the Pokemon can change forms, `false` otherwise - */ - canChange(pokemon: Pokemon): boolean { - const currentWeather = globalScene.arena.weather?.weatherType ?? WeatherType.NONE; - const isWeatherSuppressed = globalScene.arena.weather?.isEffectSuppressed(); - const isAbilitySuppressed = pokemon.summonData.abilitySuppressed; - - return ( - !isAbilitySuppressed && - !isWeatherSuppressed && - pokemon.hasAbility(this.ability) && - this.weathers.includes(currentWeather) - ); - } -} - -/** - * Class used for reverting to the original form when the weather runs out - * or when the user loses the ability/is suppressed. - * Used by Castform and Cherrim. - * @extends SpeciesFormChangeTrigger - */ -export class SpeciesFormChangeRevertWeatherFormTrigger extends SpeciesFormChangeTrigger { - /** The ability that triggers the form change*/ - public ability: AbilityId; - /** The list of weathers that will also trigger a form change to original form */ - public weathers: WeatherType[]; - - constructor(ability: AbilityId, weathers: WeatherType[]) { - super(); - this.ability = ability; - this.weathers = weathers; - this.description = i18next.t("pokemonEvolutions:Forms.weatherRevert"); - } - - /** - * Checks if the Pokemon has the required ability and the weather is one that will revert - * the Pokemon to its original form or the weather or ability is suppressed - * @param {Pokemon} pokemon the pokemon that is trying to do the form change - * @returns `true` if the Pokemon will revert to its original form, `false` otherwise - */ - canChange(pokemon: Pokemon): boolean { - if (pokemon.hasAbility(this.ability, false, true)) { - const currentWeather = globalScene.arena.weather?.weatherType ?? WeatherType.NONE; - const isWeatherSuppressed = globalScene.arena.weather?.isEffectSuppressed(); - const isAbilitySuppressed = pokemon.summonData.abilitySuppressed; - const summonDataAbility = pokemon.summonData.ability; - const isAbilityChanged = summonDataAbility !== this.ability && summonDataAbility !== AbilityId.NONE; - - if (this.weathers.includes(currentWeather) || isWeatherSuppressed || isAbilitySuppressed || isAbilityChanged) { - return true; - } - } - return false; - } -} - -export function getSpeciesFormChangeMessage(pokemon: Pokemon, formChange: SpeciesFormChange, preName: string): string { - const isMega = formChange.formKey.indexOf(SpeciesFormKey.MEGA) > -1; - const isGmax = formChange.formKey.indexOf(SpeciesFormKey.GIGANTAMAX) > -1; - const isEmax = formChange.formKey.indexOf(SpeciesFormKey.ETERNAMAX) > -1; - const isRevert = !isMega && formChange.formKey === pokemon.species.forms[0].formKey; - if (isMega) { - return i18next.t("battlePokemonForm:megaChange", { - preName, - pokemonName: pokemon.name, - }); - } - if (isGmax) { - return i18next.t("battlePokemonForm:gigantamaxChange", { - preName, - pokemonName: pokemon.name, - }); - } - if (isEmax) { - return i18next.t("battlePokemonForm:eternamaxChange", { - preName, - pokemonName: pokemon.name, - }); - } - if (isRevert) { - return i18next.t("battlePokemonForm:revertChange", { - pokemonName: getPokemonNameWithAffix(pokemon), - }); - } - if (pokemon.getAbility().id === AbilityId.DISGUISE) { - return i18next.t("battlePokemonForm:disguiseChange"); - } - return i18next.t("battlePokemonForm:formChange", { preName }); -} - /** * Gives a condition for form changing checking if a species is registered as caught in the player's dex data. * Used for fusion forms such as Kyurem and Necrozma. diff --git a/src/data/pokemon-forms/form-change-triggers.ts b/src/data/pokemon-forms/form-change-triggers.ts new file mode 100644 index 00000000000..eb2c0a557c2 --- /dev/null +++ b/src/data/pokemon-forms/form-change-triggers.ts @@ -0,0 +1,348 @@ +import i18next from "i18next"; +import type { Constructor } from "#app/utils/common"; +import type { TimeOfDay } from "#enums/time-of-day"; +import type Pokemon from "#app/field/pokemon"; +import type { SpeciesFormChange } from "#app/data/pokemon-forms"; +import type { PokemonFormChangeItemModifier } from "#app/modifier/modifier"; +import { getPokemonNameWithAffix } from "#app/messages"; +import { globalScene } from "#app/global-scene"; +import { FormChangeItem } from "#enums/form-change-item"; +import { AbilityId } from "#enums/ability-id"; +import { Challenges } from "#enums/challenges"; +import { MoveId } from "#enums/move-id"; +import { SpeciesFormKey } from "#enums/species-form-key"; +import { StatusEffect } from "#enums/status-effect"; +import { WeatherType } from "#enums/weather-type"; + +export abstract class SpeciesFormChangeTrigger { + public description = ""; + + canChange(_pokemon: Pokemon): boolean { + return true; + } + + hasTriggerType(triggerType: Constructor): boolean { + return this instanceof triggerType; + } +} + +export class SpeciesFormChangeManualTrigger extends SpeciesFormChangeTrigger {} + +export class SpeciesFormChangeAbilityTrigger extends SpeciesFormChangeTrigger { + public description: string = i18next.t("pokemonEvolutions:Forms.ability"); +} + +export class SpeciesFormChangeCompoundTrigger { + public description = ""; + public triggers: SpeciesFormChangeTrigger[]; + + constructor(...triggers: SpeciesFormChangeTrigger[]) { + this.triggers = triggers; + this.description = this.triggers + .filter(trigger => trigger?.description?.length > 0) + .map(trigger => trigger.description) + .join(", "); + } + + canChange(pokemon: Pokemon): boolean { + for (const trigger of this.triggers) { + if (!trigger.canChange(pokemon)) { + return false; + } + } + + return true; + } + + hasTriggerType(triggerType: Constructor): boolean { + return !!this.triggers.find(t => t.hasTriggerType(triggerType)); + } +} + +export class SpeciesFormChangeItemTrigger extends SpeciesFormChangeTrigger { + public item: FormChangeItem; + public active: boolean; + + constructor(item: FormChangeItem, active = true) { + super(); + this.item = item; + this.active = active; + this.description = this.active + ? i18next.t("pokemonEvolutions:Forms.item", { + item: i18next.t(`modifierType:FormChangeItem.${FormChangeItem[this.item]}`), + }) + : i18next.t("pokemonEvolutions:Forms.deactivateItem", { + item: i18next.t(`modifierType:FormChangeItem.${FormChangeItem[this.item]}`), + }); + } + + canChange(pokemon: Pokemon): boolean { + return !!globalScene.findModifier(r => { + // Assume that if m has the `formChangeItem` property, then it is a PokemonFormChangeItemModifier + const m = r as PokemonFormChangeItemModifier; + return ( + "formChangeItem" in m && + m.pokemonId === pokemon.id && + m.formChangeItem === this.item && + m.active === this.active + ); + }); + } +} + +export class SpeciesFormChangeTimeOfDayTrigger extends SpeciesFormChangeTrigger { + public timesOfDay: TimeOfDay[]; + + constructor(...timesOfDay: TimeOfDay[]) { + super(); + this.timesOfDay = timesOfDay; + this.description = i18next.t("pokemonEvolutions:Forms.timeOfDay"); + } + + canChange(_pokemon: Pokemon): boolean { + return this.timesOfDay.indexOf(globalScene.arena.getTimeOfDay()) > -1; + } +} +export class SpeciesFormChangeActiveTrigger extends SpeciesFormChangeTrigger { + public active: boolean; + + constructor(active = false) { + super(); + this.active = active; + this.description = this.active + ? i18next.t("pokemonEvolutions:Forms.enter") + : i18next.t("pokemonEvolutions:Forms.leave"); + } + + canChange(pokemon: Pokemon): boolean { + return pokemon.isActive(true) === this.active; + } +} + +export class SpeciesFormChangeStatusEffectTrigger extends SpeciesFormChangeTrigger { + public statusEffects: StatusEffect[]; + public invert: boolean; + + constructor(statusEffects: StatusEffect | StatusEffect[], invert = false) { + super(); + if (!Array.isArray(statusEffects)) { + statusEffects = [statusEffects]; + } + this.statusEffects = statusEffects; + this.invert = invert; + // this.description = i18next.t("pokemonEvolutions:Forms.statusEffect"); + } + + canChange(pokemon: Pokemon): boolean { + return this.statusEffects.indexOf(pokemon.status?.effect || StatusEffect.NONE) > -1 !== this.invert; + } +} + +export class SpeciesFormChangeMoveLearnedTrigger extends SpeciesFormChangeTrigger { + public move: MoveId; + public known: boolean; + + constructor(move: MoveId, known = true) { + super(); + this.move = move; + this.known = known; + const moveKey = MoveId[this.move] + .split("_") + .filter(f => f) + .map((f, i) => (i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase())) + .join("") as unknown as string; + this.description = known + ? i18next.t("pokemonEvolutions:Forms.moveLearned", { + move: i18next.t(`move:${moveKey}.name`), + }) + : i18next.t("pokemonEvolutions:Forms.moveForgotten", { + move: i18next.t(`move:${moveKey}.name`), + }); + } + + canChange(pokemon: Pokemon): boolean { + return !!pokemon.moveset.filter(m => m.moveId === this.move).length === this.known; + } +} + +export abstract class SpeciesFormChangeMoveTrigger extends SpeciesFormChangeTrigger { + public movePredicate: (m: MoveId) => boolean; + public used: boolean; + + constructor(move: MoveId | ((m: MoveId) => boolean), used = true) { + super(); + this.movePredicate = typeof move === "function" ? move : (m: MoveId) => m === move; + this.used = used; + } +} + +export class SpeciesFormChangePreMoveTrigger extends SpeciesFormChangeMoveTrigger { + description = i18next.t("pokemonEvolutions:Forms.preMove"); + canChange(pokemon: Pokemon): boolean { + const command = globalScene.currentBattle.turnCommands[pokemon.getBattlerIndex()]; + return !!command?.move && this.movePredicate(command.move.move) === this.used; + } +} + +export class SpeciesFormChangePostMoveTrigger extends SpeciesFormChangeMoveTrigger { + description = i18next.t("pokemonEvolutions:Forms.postMove"); + canChange(pokemon: Pokemon): boolean { + return ( + pokemon.summonData && !!pokemon.getLastXMoves(1).filter(m => this.movePredicate(m.move)).length === this.used + ); + } +} + +export class MeloettaFormChangePostMoveTrigger extends SpeciesFormChangePostMoveTrigger { + override canChange(pokemon: Pokemon): boolean { + if (globalScene.gameMode.hasChallenge(Challenges.SINGLE_TYPE)) { + return false; + } + // Meloetta will not transform if it has the ability Sheer Force when using Relic Song + if (pokemon.hasAbility(AbilityId.SHEER_FORCE)) { + return false; + } + return super.canChange(pokemon); + } +} + +export class SpeciesDefaultFormMatchTrigger extends SpeciesFormChangeTrigger { + private formKey: string; + + constructor(formKey: string) { + super(); + this.formKey = formKey; + this.description = ""; + } + + canChange(pokemon: Pokemon): boolean { + return ( + this.formKey === + pokemon.species.forms[globalScene.getSpeciesFormIndex(pokemon.species, pokemon.gender, pokemon.getNature(), true)] + .formKey + ); + } +} + +/** + * Class used for triggering form changes based on the user's Tera type. + * Used by Ogerpon and Terapagos. + */ +export class SpeciesFormChangeTeraTrigger extends SpeciesFormChangeTrigger {} + +/** + * Class used for triggering form changes based on the user's lapsed Tera type. + * Used by Ogerpon and Terapagos. + */ +export class SpeciesFormChangeLapseTeraTrigger extends SpeciesFormChangeTrigger {} + +/** + * Class used for triggering form changes based on weather. + * Used by Castform and Cherrim. + */ +export class SpeciesFormChangeWeatherTrigger extends SpeciesFormChangeTrigger { + /** The ability that triggers the form change */ + public ability: AbilityId; + /** The list of weathers that trigger the form change */ + public weathers: WeatherType[]; + + constructor(ability: AbilityId, weathers: WeatherType[]) { + super(); + this.ability = ability; + this.weathers = weathers; + this.description = i18next.t("pokemonEvolutions:Forms.weather"); + } + + /** + * Checks if the Pokemon has the required ability and is in the correct weather while + * the weather or ability is also not suppressed. + * @param pokemon - The pokemon that is trying to do the form change + * @returns `true` if the Pokemon can change forms, `false` otherwise + */ + canChange(pokemon: Pokemon): boolean { + const currentWeather = globalScene.arena.weather?.weatherType ?? WeatherType.NONE; + const isWeatherSuppressed = globalScene.arena.weather?.isEffectSuppressed(); + const isAbilitySuppressed = pokemon.summonData.abilitySuppressed; + + return ( + !isAbilitySuppressed && + !isWeatherSuppressed && + pokemon.hasAbility(this.ability) && + this.weathers.includes(currentWeather) + ); + } +} + +/** + * Class used for reverting to the original form when the weather runs out + * or when the user loses the ability/is suppressed. + * Used by Castform and Cherrim. + */ +export class SpeciesFormChangeRevertWeatherFormTrigger extends SpeciesFormChangeTrigger { + /** The ability that triggers the form change*/ + public ability: AbilityId; + /** The list of weathers that will also trigger a form change to original form */ + public weathers: WeatherType[]; + + constructor(ability: AbilityId, weathers: WeatherType[]) { + super(); + this.ability = ability; + this.weathers = weathers; + this.description = i18next.t("pokemonEvolutions:Forms.weatherRevert"); + } + + /** + * Checks if the Pokemon has the required ability and the weather is one that will revert + * the Pokemon to its original form or the weather or ability is suppressed + * @param {Pokemon} pokemon the pokemon that is trying to do the form change + * @returns `true` if the Pokemon will revert to its original form, `false` otherwise + */ + canChange(pokemon: Pokemon): boolean { + if (pokemon.hasAbility(this.ability, false, true)) { + const currentWeather = globalScene.arena.weather?.weatherType ?? WeatherType.NONE; + const isWeatherSuppressed = globalScene.arena.weather?.isEffectSuppressed(); + const isAbilitySuppressed = pokemon.summonData.abilitySuppressed; + const summonDataAbility = pokemon.summonData.ability; + const isAbilityChanged = summonDataAbility !== this.ability && summonDataAbility !== AbilityId.NONE; + + if (this.weathers.includes(currentWeather) || isWeatherSuppressed || isAbilitySuppressed || isAbilityChanged) { + return true; + } + } + return false; + } +} + +export function getSpeciesFormChangeMessage(pokemon: Pokemon, formChange: SpeciesFormChange, preName: string): string { + const isMega = formChange.formKey.indexOf(SpeciesFormKey.MEGA) > -1; + const isGmax = formChange.formKey.indexOf(SpeciesFormKey.GIGANTAMAX) > -1; + const isEmax = formChange.formKey.indexOf(SpeciesFormKey.ETERNAMAX) > -1; + const isRevert = !isMega && formChange.formKey === pokemon.species.forms[0].formKey; + if (isMega) { + return i18next.t("battlePokemonForm:megaChange", { + preName, + pokemonName: pokemon.name, + }); + } + if (isGmax) { + return i18next.t("battlePokemonForm:gigantamaxChange", { + preName, + pokemonName: pokemon.name, + }); + } + if (isEmax) { + return i18next.t("battlePokemonForm:eternamaxChange", { + preName, + pokemonName: pokemon.name, + }); + } + if (isRevert) { + return i18next.t("battlePokemonForm:revertChange", { + pokemonName: getPokemonNameWithAffix(pokemon), + }); + } + if (pokemon.getAbility().id === AbilityId.DISGUISE) { + return i18next.t("battlePokemonForm:disguiseChange"); + } + return i18next.t("battlePokemonForm:formChange", { preName }); +} diff --git a/src/field/arena.ts b/src/field/arena.ts index 588b8833aff..fffbf8dfa26 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -38,7 +38,10 @@ import { SpeciesId } from "#enums/species-id"; import { TimeOfDay } from "#enums/time-of-day"; import { TrainerType } from "#enums/trainer-type"; import { AbilityId } from "#enums/ability-id"; -import { SpeciesFormChangeRevertWeatherFormTrigger, SpeciesFormChangeWeatherTrigger } from "#app/data/pokemon-forms"; +import { + SpeciesFormChangeRevertWeatherFormTrigger, + SpeciesFormChangeWeatherTrigger, +} from "#app/data/pokemon-forms/form-change-triggers"; import { WeatherType } from "#enums/weather-type"; import { FieldEffectModifier } from "#app/modifier/modifier"; diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 8e5d1cca2b1..7dbd7d5ba75 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -214,7 +214,7 @@ import { SpeciesFormChangeLapseTeraTrigger, SpeciesFormChangeMoveLearnedTrigger, SpeciesFormChangePostMoveTrigger, -} from "#app/data/pokemon-forms"; +} from "#app/data/pokemon-forms/form-change-triggers"; import { TerrainType } from "#app/data/terrain"; import type { TrainerSlot } from "#enums/trainer-slot"; import Overrides from "#app/overrides"; diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index ceb20b0032f..ab21dd49325 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -6,7 +6,8 @@ import { AttackMove } from "#app/data/moves/move"; import { allMoves } from "#app/data/data-lists"; import { getNatureName, getNatureStatMultiplier } from "#app/data/nature"; import { getPokeballCatchMultiplier, getPokeballName, MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball"; -import { pokemonFormChanges, SpeciesFormChangeCondition, SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms"; +import { pokemonFormChanges, SpeciesFormChangeCondition } from "#app/data/pokemon-forms"; +import { SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms/form-change-triggers"; import { FormChangeItem } from "#enums/form-change-item"; import { getStatusEffectDescriptor } from "#app/data/status-effect"; import { PokemonType } from "#enums/pokemon-type"; diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 66f2200e5ca..bfea061b1e8 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -3,7 +3,7 @@ import { getBerryEffectFunc, getBerryPredicate } from "#app/data/berry"; import { getLevelTotalExp } from "#app/data/exp"; import { allMoves } from "#app/data/data-lists"; import { MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball"; -import { SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms"; +import { SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms/form-change-triggers"; import type { FormChangeItem } from "#enums/form-change-item"; import { getStatusEffectHealText } from "#app/data/status-effect"; import Pokemon, { type PlayerPokemon } from "#app/field/pokemon"; diff --git a/src/phases/faint-phase.ts b/src/phases/faint-phase.ts index 55f5433c390..90f3f0aabbd 100644 --- a/src/phases/faint-phase.ts +++ b/src/phases/faint-phase.ts @@ -13,7 +13,7 @@ import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type"; import { battleSpecDialogue } from "#app/data/dialogue"; import { PostVictoryStatStageChangeAttr } from "#app/data/moves/move"; import { allMoves } from "#app/data/data-lists"; -import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms"; +import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms/form-change-triggers"; import { BattleSpec } from "#app/enums/battle-spec"; import { StatusEffect } from "#app/enums/status-effect"; import type { EnemyPokemon } from "#app/field/pokemon"; diff --git a/src/phases/form-change-phase.ts b/src/phases/form-change-phase.ts index 3813359d432..13cd410ef87 100644 --- a/src/phases/form-change-phase.ts +++ b/src/phases/form-change-phase.ts @@ -2,7 +2,7 @@ import { globalScene } from "#app/global-scene"; import { fixedInt } from "#app/utils/common"; import { achvs } from "../system/achv"; import type { SpeciesFormChange } from "../data/pokemon-forms"; -import { getSpeciesFormChangeMessage } from "../data/pokemon-forms"; +import { getSpeciesFormChangeMessage } from "#app/data/pokemon-forms/form-change-triggers"; import type { PlayerPokemon } from "../field/pokemon"; import { UiMode } from "#enums/ui-mode"; import type PartyUiHandler from "../ui/party-ui-handler"; diff --git a/src/phases/learn-move-phase.ts b/src/phases/learn-move-phase.ts index e734ae92859..e24efa63b5a 100644 --- a/src/phases/learn-move-phase.ts +++ b/src/phases/learn-move-phase.ts @@ -2,7 +2,7 @@ import { globalScene } from "#app/global-scene"; import { initMoveAnim, loadMoveAnimAssets } from "#app/data/battle-anims"; import type Move from "#app/data/moves/move"; import { allMoves } from "#app/data/data-lists"; -import { SpeciesFormChangeMoveLearnedTrigger } from "#app/data/pokemon-forms"; +import { SpeciesFormChangeMoveLearnedTrigger } from "#app/data/pokemon-forms/form-change-triggers"; import { MoveId } from "#enums/move-id"; import { getPokemonNameWithAffix } from "#app/messages"; import Overrides from "#app/overrides"; diff --git a/src/phases/move-effect-phase.ts b/src/phases/move-effect-phase.ts index 4c35d4f564a..957f3b62277 100644 --- a/src/phases/move-effect-phase.ts +++ b/src/phases/move-effect-phase.ts @@ -49,7 +49,7 @@ import { MoveEffectTrigger } from "#enums/MoveEffectTrigger"; import { MoveFlags } from "#enums/MoveFlags"; import { MoveTarget } from "#enums/MoveTarget"; import { MoveCategory } from "#enums/MoveCategory"; -import { SpeciesFormChangePostMoveTrigger } from "#app/data/pokemon-forms"; +import { SpeciesFormChangePostMoveTrigger } from "#app/data/pokemon-forms/form-change-triggers"; import { PokemonType } from "#enums/pokemon-type"; import type { DamageResult, TurnMove } from "#app/field/pokemon"; import { PokemonMove } from "#app/data/moves/pokemon-move"; diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index 93921863645..d2bd82cae0d 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -29,7 +29,7 @@ import { } from "#app/data/moves/move"; import { allMoves } from "#app/data/data-lists"; import { MoveFlags } from "#enums/MoveFlags"; -import { SpeciesFormChangePreMoveTrigger } from "#app/data/pokemon-forms"; +import { SpeciesFormChangePreMoveTrigger } from "#app/data/pokemon-forms/form-change-triggers"; import { getStatusEffectActivationText, getStatusEffectHealText } from "#app/data/status-effect"; import { PokemonType } from "#enums/pokemon-type"; import { getTerrainBlockMessage, getWeatherBlockMessage } from "#app/data/weather"; diff --git a/src/phases/obtain-status-effect-phase.ts b/src/phases/obtain-status-effect-phase.ts index b4d74341c2e..bf172269d5f 100644 --- a/src/phases/obtain-status-effect-phase.ts +++ b/src/phases/obtain-status-effect-phase.ts @@ -7,7 +7,7 @@ import { StatusEffect } from "#app/enums/status-effect"; import type Pokemon from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; import { PokemonPhase } from "./pokemon-phase"; -import { SpeciesFormChangeStatusEffectTrigger } from "#app/data/pokemon-forms"; +import { SpeciesFormChangeStatusEffectTrigger } from "#app/data/pokemon-forms/form-change-triggers"; import { applyPostSetStatusAbAttrs, PostSetStatusAbAttr } from "#app/data/abilities/ability"; import { isNullOrUndefined } from "#app/utils/common"; diff --git a/src/phases/quiet-form-change-phase.ts b/src/phases/quiet-form-change-phase.ts index 6b65c2a5140..cfd9c521e2b 100644 --- a/src/phases/quiet-form-change-phase.ts +++ b/src/phases/quiet-form-change-phase.ts @@ -1,7 +1,10 @@ import { globalScene } from "#app/global-scene"; import { SemiInvulnerableTag } from "#app/data/battler-tags"; import type { SpeciesFormChange } from "#app/data/pokemon-forms"; -import { getSpeciesFormChangeMessage, SpeciesFormChangeTeraTrigger } from "#app/data/pokemon-forms"; +import { + getSpeciesFormChangeMessage, + SpeciesFormChangeTeraTrigger, +} from "#app/data/pokemon-forms/form-change-triggers"; import { getTypeRgb } from "#app/data/type"; import { BattleSpec } from "#app/enums/battle-spec"; import { BattlerTagType } from "#app/enums/battler-tag-type"; diff --git a/src/phases/return-phase.ts b/src/phases/return-phase.ts index 6365256d40a..a8233f98bd1 100644 --- a/src/phases/return-phase.ts +++ b/src/phases/return-phase.ts @@ -1,5 +1,5 @@ import { globalScene } from "#app/global-scene"; -import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms"; +import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms/form-change-triggers"; import { SwitchType } from "#enums/switch-type"; import { SwitchSummonPhase } from "./switch-summon-phase"; diff --git a/src/phases/select-starter-phase.ts b/src/phases/select-starter-phase.ts index 0c180a6a248..e7e87f5a25f 100644 --- a/src/phases/select-starter-phase.ts +++ b/src/phases/select-starter-phase.ts @@ -2,7 +2,7 @@ import { globalScene } from "#app/global-scene"; import { applyChallenges } from "#app/data/challenge"; import { ChallengeType } from "#enums/challenge-type"; import { Gender } from "#app/data/gender"; -import { SpeciesFormChangeMoveLearnedTrigger } from "#app/data/pokemon-forms"; +import { SpeciesFormChangeMoveLearnedTrigger } from "#app/data/pokemon-forms/form-change-triggers"; import { getPokemonSpecies } from "#app/data/pokemon-species"; import { overrideHeldItems, overrideModifiers } from "#app/modifier/modifier"; import Overrides from "#app/overrides"; diff --git a/src/phases/summon-phase.ts b/src/phases/summon-phase.ts index ba7bda0f06f..e902fd0183e 100644 --- a/src/phases/summon-phase.ts +++ b/src/phases/summon-phase.ts @@ -1,6 +1,6 @@ import { BattleType } from "#enums/battle-type"; import { getPokeballAtlasKey, getPokeballTintColor } from "#app/data/pokeball"; -import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms"; +import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms/form-change-triggers"; import { TrainerSlot } from "#enums/trainer-slot"; import { PlayerGender } from "#app/enums/player-gender"; import { addPokeballOpenParticles } from "#app/field/anims"; diff --git a/src/phases/switch-summon-phase.ts b/src/phases/switch-summon-phase.ts index 8cd106ea6b7..fcc6a6bcbe0 100644 --- a/src/phases/switch-summon-phase.ts +++ b/src/phases/switch-summon-phase.ts @@ -9,7 +9,7 @@ import { import { ForceSwitchOutAttr } from "#app/data/moves/move"; import { allMoves } from "#app/data/data-lists"; import { getPokeballTintColor } from "#app/data/pokeball"; -import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms"; +import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms/form-change-triggers"; import { TrainerSlot } from "#enums/trainer-slot"; import type Pokemon from "#app/field/pokemon"; import { getPokemonNameWithAffix } from "#app/messages"; diff --git a/src/phases/tera-phase.ts b/src/phases/tera-phase.ts index 02a82c5893b..a6025e20488 100644 --- a/src/phases/tera-phase.ts +++ b/src/phases/tera-phase.ts @@ -5,7 +5,7 @@ import i18next from "i18next"; import { globalScene } from "#app/global-scene"; import { PokemonType } from "#enums/pokemon-type"; import { achvs } from "#app/system/achv"; -import { SpeciesFormChangeTeraTrigger } from "#app/data/pokemon-forms"; +import { SpeciesFormChangeTeraTrigger } from "#app/data/pokemon-forms/form-change-triggers"; import { CommonBattleAnim } from "#app/data/battle-anims"; import { CommonAnim } from "#enums/move-anims-common"; diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index 990dd342225..ab0bb0d2c7e 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -19,7 +19,7 @@ import { StatusEffect } from "#enums/status-effect"; import PokemonIconAnimHandler, { PokemonIconAnimMode } from "#app/ui/pokemon-icon-anim-handler"; import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import { addWindow } from "#app/ui/ui-theme"; -import { SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms"; +import { SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms/form-change-triggers"; import { FormChangeItem } from "#enums/form-change-item"; import { getVariantTint } from "#app/sprites/variant"; import { Button } from "#enums/buttons";