[Ability] Add form change support for Flower Gift (#3941)

* add form change support for flower gift

* fix nits
This commit is contained in:
Adrian T. 2024-09-02 10:39:42 +08:00 committed by GitHub
parent 3bcee779e2
commit 64368b62bc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 193 additions and 18 deletions

View File

@ -2428,7 +2428,7 @@ export class PostSummonWeatherSuppressedFormChangeAbAttr extends PostSummonAbAtt
/**
* Triggers weather-based form change when summoned into an active weather.
* Used by Forecast.
* Used by Forecast and Flower Gift.
* @extends PostSummonAbAttr
*/
export class PostSummonFormChangeByWeatherAbAttr extends PostSummonAbAttr {
@ -2451,7 +2451,10 @@ export class PostSummonFormChangeByWeatherAbAttr extends PostSummonAbAttr {
* @returns whether the form change was triggered
*/
applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean {
if (pokemon.species.speciesId === Species.CASTFORM && this.ability === Abilities.FORECAST) {
const isCastformWithForecast = (pokemon.species.speciesId === Species.CASTFORM && this.ability === Abilities.FORECAST);
const isCherrimWithFlowerGift = (pokemon.species.speciesId === Species.CHERRIM && this.ability === Abilities.FLOWER_GIFT);
if (isCastformWithForecast || isCherrimWithFlowerGift) {
if (simulated) {
return simulated;
}
@ -3124,37 +3127,41 @@ export class PostWeatherChangeAbAttr extends AbAttr {
/**
* Triggers weather-based form change when weather changes.
* Used by Forecast.
* Used by Forecast and Flower Gift.
* @extends PostWeatherChangeAbAttr
*/
export class PostWeatherChangeFormChangeAbAttr extends PostWeatherChangeAbAttr {
private ability: Abilities;
private formRevertingWeathers: WeatherType[];
constructor(ability: Abilities) {
constructor(ability: Abilities, formRevertingWeathers: WeatherType[]) {
super(false);
this.ability = ability;
this.formRevertingWeathers = formRevertingWeathers;
}
/**
* Calls {@linkcode Arena.triggerWeatherBasedFormChangesToNormal | triggerWeatherBasedFormChangesToNormal} when the
* weather changed to form-reverting weather, otherwise calls {@linkcode Arena.triggerWeatherBasedFormChanges | triggerWeatherBasedFormChanges}
* @param {Pokemon} pokemon the Pokemon that changed the weather
* @param {Pokemon} pokemon the Pokemon with this ability
* @param passive n/a
* @param weather n/a
* @param args n/a
* @returns whether the form change was triggered
*/
applyPostWeatherChange(pokemon: Pokemon, passive: boolean, simulated: boolean, weather: WeatherType, args: any[]): boolean {
if (pokemon.species.speciesId === Species.CASTFORM && this.ability === Abilities.FORECAST) {
const isCastformWithForecast = (pokemon.species.speciesId === Species.CASTFORM && this.ability === Abilities.FORECAST);
const isCherrimWithFlowerGift = (pokemon.species.speciesId === Species.CHERRIM && this.ability === Abilities.FLOWER_GIFT);
if (isCastformWithForecast || isCherrimWithFlowerGift) {
if (simulated) {
return simulated;
}
const formRevertingWeathers: WeatherType[] = [ WeatherType.NONE, WeatherType.SANDSTORM, WeatherType.STRONG_WINDS, WeatherType.FOG ];
const weatherType = pokemon.scene.arena.weather?.weatherType;
if (weatherType && formRevertingWeathers.includes(weatherType)) {
if (weatherType && this.formRevertingWeathers.includes(weatherType)) {
pokemon.scene.arena.triggerWeatherBasedFormChangesToNormal();
} else {
pokemon.scene.arena.triggerWeatherBasedFormChanges();
@ -4744,7 +4751,8 @@ function setAbilityRevealed(pokemon: Pokemon): void {
*/
function getPokemonWithWeatherBasedForms(scene: BattleScene) {
return scene.getField(true).filter(p =>
p.hasAbility(Abilities.FORECAST) && p.species.speciesId === Species.CASTFORM
(p.hasAbility(Abilities.FORECAST) && p.species.speciesId === Species.CASTFORM)
|| (p.hasAbility(Abilities.FLOWER_GIFT) && p.species.speciesId === Species.CHERRIM)
);
}
@ -4943,7 +4951,7 @@ export function initAbilities() {
.attr(UncopiableAbilityAbAttr)
.attr(NoFusionAbilityAbAttr)
.attr(PostSummonFormChangeByWeatherAbAttr, Abilities.FORECAST)
.attr(PostWeatherChangeFormChangeAbAttr, Abilities.FORECAST),
.attr(PostWeatherChangeFormChangeAbAttr, Abilities.FORECAST, [ WeatherType.NONE, WeatherType.SANDSTORM, WeatherType.STRONG_WINDS, WeatherType.FOG ]),
new Ability(Abilities.STICKY_HOLD, 3)
.attr(BlockItemTheftAbAttr)
.bypassFaint()
@ -5136,8 +5144,10 @@ export function initAbilities() {
.conditionalAttr(getWeatherCondition(WeatherType.SUNNY || WeatherType.HARSH_SUN), BattleStatMultiplierAbAttr, BattleStat.SPDEF, 1.5)
.attr(UncopiableAbilityAbAttr)
.attr(NoFusionAbilityAbAttr)
.ignorable()
.partial(),
.attr(PostSummonFormChangeByWeatherAbAttr, Abilities.FLOWER_GIFT)
.attr(PostWeatherChangeFormChangeAbAttr, Abilities.FLOWER_GIFT, [ WeatherType.NONE, WeatherType.SANDSTORM, WeatherType.STRONG_WINDS, WeatherType.FOG, WeatherType.HAIL, WeatherType.HEAVY_RAIN, WeatherType.SNOW, WeatherType.RAIN ])
.partial() // Should also boosts stats of ally
.ignorable(),
new Ability(Abilities.BAD_DREAMS, 4)
.attr(PostTurnHurtIfSleepingAbAttr),
new Ability(Abilities.PICKPOCKET, 5)

View File

@ -5888,9 +5888,9 @@ export class SwitchAbilitiesAttr extends MoveEffectAttr {
target.summonData.ability = tempAbilityId;
user.scene.queueMessage(i18next.t("moveTriggers:swappedAbilitiesWithTarget", {pokemonName: getPokemonNameWithAffix(user)}));
// Swaps Forecast from Castform
// Swaps Forecast/Flower Gift from Castform/Cherrim
user.scene.arena.triggerWeatherBasedFormChangesToNormal();
// Swaps Forecast to Castform (edge case)
// Swaps Forecast/Flower Gift to Castform/Cherrim (edge case)
user.scene.arena.triggerWeatherBasedFormChanges();
return true;

View File

@ -359,7 +359,7 @@ export class SpeciesDefaultFormMatchTrigger extends SpeciesFormChangeTrigger {
/**
* Class used for triggering form changes based on weather.
* Used by Castform.
* Used by Castform and Cherrim.
* @extends SpeciesFormChangeTrigger
*/
export class SpeciesFormChangeWeatherTrigger extends SpeciesFormChangeTrigger {
@ -392,7 +392,7 @@ export class SpeciesFormChangeWeatherTrigger extends SpeciesFormChangeTrigger {
/**
* 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.
* Used by Castform and Cherrim.
* @extends SpeciesFormChangeTrigger
*/
export class SpeciesFormChangeRevertWeatherFormTrigger extends SpeciesFormChangeTrigger {
@ -930,6 +930,11 @@ export const pokemonFormChanges: PokemonFormChanges = {
new SpeciesFormChange(Species.CASTFORM, "rainy", "", new SpeciesFormChangeActiveTrigger(), true),
new SpeciesFormChange(Species.CASTFORM, "snowy", "", new SpeciesFormChangeActiveTrigger(), true),
],
[Species.CHERRIM]: [
new SpeciesFormChange(Species.CHERRIM, "overcast", "sunshine", new SpeciesFormChangeWeatherTrigger(Abilities.FLOWER_GIFT, [ WeatherType.SUNNY, WeatherType.HARSH_SUN ]), true),
new SpeciesFormChange(Species.CHERRIM, "sunshine", "overcast", new SpeciesFormChangeRevertWeatherFormTrigger(Abilities.FLOWER_GIFT, [ WeatherType.NONE, WeatherType.SANDSTORM, WeatherType.STRONG_WINDS, WeatherType.FOG, WeatherType.HAIL, WeatherType.HEAVY_RAIN, WeatherType.SNOW, WeatherType.RAIN ]), true),
new SpeciesFormChange(Species.CHERRIM, "sunshine", "overcast", new SpeciesFormChangeActiveTrigger(), true),
],
};
export function initPokemonForms() {

View File

@ -339,7 +339,10 @@ export class Arena {
*/
triggerWeatherBasedFormChanges(): void {
this.scene.getField(true).forEach( p => {
if (p.hasAbility(Abilities.FORECAST) && p.species.speciesId === Species.CASTFORM) {
const isCastformWithForecast = (p.hasAbility(Abilities.FORECAST) && p.species.speciesId === Species.CASTFORM);
const isCherrimWithFlowerGift = (p.hasAbility(Abilities.FLOWER_GIFT) && p.species.speciesId === Species.CHERRIM);
if (isCastformWithForecast || isCherrimWithFlowerGift) {
new ShowAbilityPhase(this.scene, p.getBattlerIndex());
this.scene.triggerPokemonFormChange(p, SpeciesFormChangeWeatherTrigger);
}
@ -351,7 +354,10 @@ export class Arena {
*/
triggerWeatherBasedFormChangesToNormal(): void {
this.scene.getField(true).forEach( p => {
if (p.hasAbility(Abilities.FORECAST, false, true) && p.species.speciesId === Species.CASTFORM) {
const isCastformWithForecast = (p.hasAbility(Abilities.FORECAST, false, true) && p.species.speciesId === Species.CASTFORM);
const isCherrimWithFlowerGift = (p.hasAbility(Abilities.FLOWER_GIFT, false, true) && p.species.speciesId === Species.CHERRIM);
if (isCastformWithForecast || isCherrimWithFlowerGift) {
new ShowAbilityPhase(this.scene, p.getBattlerIndex());
return this.scene.triggerPokemonFormChange(p, SpeciesFormChangeRevertWeatherFormTrigger);
}

View File

@ -0,0 +1,154 @@
import { BattlerIndex } from "#app/battle";
import { Abilities } from "#app/enums/abilities";
import { Stat } from "#app/enums/stat";
import { WeatherType } from "#app/enums/weather-type";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import GameManager from "#test/utils/gameManager";
import { SPLASH_ONLY } from "#test/utils/testUtils";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
describe("Abilities - Flower Gift", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
const OVERCAST_FORM = 0;
const SUNSHINE_FORM = 1;
/**
* Tests reverting to normal form when Cloud Nine/Air Lock is active on the field
* @param {GameManager} game The game manager instance
* @param {Abilities} ability The ability that is active on the field
*/
const testRevertFormAgainstAbility = async (game: GameManager, ability: Abilities) => {
game.override.starterForms({ [Species.CASTFORM]: SUNSHINE_FORM }).enemyAbility(ability);
await game.classicMode.startBattle([Species.CASTFORM]);
game.move.select(Moves.SPLASH);
expect(game.scene.getPlayerPokemon()?.formIndex).toBe(OVERCAST_FORM);
};
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
game.override
.moveset([Moves.SPLASH, Moves.RAIN_DANCE, Moves.SUNNY_DAY, Moves.SKILL_SWAP])
.enemySpecies(Species.MAGIKARP)
.enemyMoveset(SPLASH_ONLY)
.enemyAbility(Abilities.BALL_FETCH);
});
// TODO: Uncomment expect statements when the ability is implemented - currently does not increase stats of allies
it("increases the Attack and Special Defense stats of the Pokémon with this Ability and its allies by 1.5× during Harsh Sunlight", async () => {
game.override.battleType("double");
await game.classicMode.startBattle([Species.CHERRIM, Species.MAGIKARP]);
const [ cherrim ] = game.scene.getPlayerField();
const cherrimAtkStat = cherrim.getBattleStat(Stat.ATK);
const cherrimSpDefStat = cherrim.getBattleStat(Stat.SPDEF);
// const magikarpAtkStat = magikarp.getBattleStat(Stat.ATK);;
// const magikarpSpDefStat = magikarp.getBattleStat(Stat.SPDEF);
game.move.select(Moves.SUNNY_DAY, 0);
game.move.select(Moves.SPLASH, 1);
await game.setTurnOrder([BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2]);
await game.phaseInterceptor.to("TurnEndPhase");
expect(cherrim.formIndex).toBe(SUNSHINE_FORM);
expect(cherrim.getBattleStat(Stat.ATK)).toBe(Math.floor(cherrimAtkStat * 1.5));
expect(cherrim.getBattleStat(Stat.SPDEF)).toBe(Math.floor(cherrimSpDefStat * 1.5));
// expect(magikarp.getBattleStat(Stat.ATK)).toBe(Math.floor(magikarpAtkStat * 1.5));
// expect(magikarp.getBattleStat(Stat.SPDEF)).toBe(Math.floor(magikarpSpDefStat * 1.5));
});
it("changes the Pokemon's form during Harsh Sunlight", async () => {
game.override.weather(WeatherType.HARSH_SUN);
await game.classicMode.startBattle([Species.CHERRIM]);
const cherrim = game.scene.getPlayerPokemon()!;
expect(cherrim.formIndex).toBe(SUNSHINE_FORM);
game.move.select(Moves.SPLASH);
});
it("reverts to Overcast Form if a Pokémon on the field has Air Lock", async () => {
await testRevertFormAgainstAbility(game, Abilities.AIR_LOCK);
});
it("reverts to Overcast Form if a Pokémon on the field has Cloud Nine", async () => {
await testRevertFormAgainstAbility(game, Abilities.CLOUD_NINE);
});
it("reverts to Overcast Form when the Pokémon loses Flower Gift, changes form under Harsh Sunlight/Sunny when it regains it", async () => {
game.override.enemyMoveset(Array(4).fill(Moves.SKILL_SWAP)).weather(WeatherType.HARSH_SUN);
await game.classicMode.startBattle([Species.CHERRIM]);
const cherrim = game.scene.getPlayerPokemon()!;
game.move.select(Moves.SKILL_SWAP);
await game.phaseInterceptor.to("TurnStartPhase");
expect(cherrim.formIndex).toBe(SUNSHINE_FORM);
await game.phaseInterceptor.to("MoveEndPhase");
expect(cherrim.formIndex).toBe(OVERCAST_FORM);
await game.phaseInterceptor.to("MoveEndPhase");
expect(cherrim.formIndex).toBe(SUNSHINE_FORM);
});
it("reverts to Overcast Form when the Flower Gift is suppressed, changes form under Harsh Sunlight/Sunny when it regains it", async () => {
game.override.enemyMoveset(Array(4).fill(Moves.GASTRO_ACID)).weather(WeatherType.HARSH_SUN);
await game.classicMode.startBattle([Species.CHERRIM, Species.MAGIKARP]);
const cherrim = game.scene.getPlayerPokemon()!;
expect(cherrim.formIndex).toBe(SUNSHINE_FORM);
game.move.select(Moves.SPLASH);
await game.setTurnOrder([BattlerIndex.ENEMY, BattlerIndex.PLAYER]);
await game.phaseInterceptor.to("TurnEndPhase");
expect(cherrim.summonData.abilitySuppressed).toBe(true);
expect(cherrim.formIndex).toBe(OVERCAST_FORM);
await game.toNextTurn();
game.doSwitchPokemon(1);
await game.toNextTurn();
game.doSwitchPokemon(1);
await game.phaseInterceptor.to("MovePhase");
expect(cherrim.summonData.abilitySuppressed).toBe(false);
expect(cherrim.formIndex).toBe(SUNSHINE_FORM);
});
it("should be in Overcast Form after the user is switched out", async () => {
game.override.weather(WeatherType.SUNNY);
await game.classicMode.startBattle([Species.CASTFORM, Species.MAGIKARP]);
const cherrim = game.scene.getPlayerPokemon()!;
expect(cherrim.formIndex).toBe(SUNSHINE_FORM);
game.doSwitchPokemon(1);
await game.toNextTurn();
expect(cherrim.formIndex).toBe(OVERCAST_FORM);
});
});