diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index b76717d7d44..0fb74d51304 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -1162,7 +1162,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return (!cancelled.value ? Number(typeMultiplier.value) : 0) as TypeDamageMultiplier; } - getAttackTypeEffectiveness(moveType: Type, source?: Pokemon, ignoreStrongWinds: boolean = false): TypeDamageMultiplier { + /** + * Calculates the type effectiveness multiplier for an attack type + * @param moveType Type of the move + * @param source the Pokemon using the move + * @param ignoreStrongWinds whether or not this ignores strong winds (anticipation, forewarn, stealth rocks) + * @param simulated tag to only apply the strong winds effect message when the move is used + * @returns a multiplier for the type effectiveness + */ + getAttackTypeEffectiveness(moveType: Type, source?: Pokemon, ignoreStrongWinds: boolean = false, simulated: boolean = true): TypeDamageMultiplier { if (moveType === Type.STELLAR) { return this.isTerastallized() ? 2 : 1; } @@ -1183,8 +1191,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { }).reduce((acc, cur) => acc * cur, 1) as TypeDamageMultiplier; // Handle strong winds lowering effectiveness of types super effective against pure flying - if (!ignoreStrongWinds && this.scene.arena.weather?.weatherType === WeatherType.STRONG_WINDS && !this.scene.arena.weather.isEffectSuppressed(this.scene) && multiplier >= 2 && this.isOfType(Type.FLYING) && getTypeDamageMultiplier(moveType, Type.FLYING) === 2) { + if (!ignoreStrongWinds && this.scene.arena.weather?.weatherType === WeatherType.STRONG_WINDS && !this.scene.arena.weather.isEffectSuppressed(this.scene) && this.isOfType(Type.FLYING) && getTypeDamageMultiplier(moveType, Type.FLYING) === 2) { multiplier /= 2; + if (!simulated) { + this.scene.queueMessage(i18next.t("weather:strongWindsEffectMessage")); + } } if (!!this.summonData?.tags.find((tag) => tag instanceof TypeImmuneTag && tag.immuneType === moveType)) { @@ -1739,7 +1750,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const cancelled = new Utils.BooleanHolder(false); const typeless = move.hasAttr(TypelessAttr); const typeMultiplier = new Utils.NumberHolder(!typeless && (moveCategory !== MoveCategory.STATUS || move.getAttrs(StatusMoveTypeImmunityAttr).find(attr => types.includes(attr.immuneType))) - ? this.getAttackTypeEffectiveness(move.type, source) + ? this.getAttackTypeEffectiveness(move.type, source, false, false) : 1); applyMoveAttrs(VariableMoveTypeMultiplierAttr, source, this, move, typeMultiplier); if (typeless) { diff --git a/src/locales/de/weather.ts b/src/locales/de/weather.ts index 1132c35c430..305fd7e7827 100644 --- a/src/locales/de/weather.ts +++ b/src/locales/de/weather.ts @@ -40,5 +40,6 @@ export const weather: SimpleTranslationEntries = { "strongWindsStartMessage": "Alle Flug-Pokémon werden von rätselhaften Luftströmungen geschützt!", "strongWindsLapseMessage": "Die rätselhafte Luftströmung hält an.", + "strongWindsEffectMessage": "Rätselhafte Luftströmungen haben den Angriff abgeschwächt!", "strongWindsClearMessage": "Die rätselhafte Luftströmung hat sich wieder geleget.", }; diff --git a/src/locales/en/weather.ts b/src/locales/en/weather.ts index f50c1ee907e..c7b2963ccd8 100644 --- a/src/locales/en/weather.ts +++ b/src/locales/en/weather.ts @@ -40,5 +40,6 @@ export const weather: SimpleTranslationEntries = { "strongWindsStartMessage": "A heavy wind began!", "strongWindsLapseMessage": "The wind blows intensely.", + "strongWindsEffectMessage": "The mysterious air current weakened the attack!", "strongWindsClearMessage": "The heavy wind stopped." }; diff --git a/src/locales/es/weather.ts b/src/locales/es/weather.ts index 5565779a7bd..37f574878dc 100644 --- a/src/locales/es/weather.ts +++ b/src/locales/es/weather.ts @@ -40,5 +40,6 @@ export const weather: SimpleTranslationEntries = { "strongWindsStartMessage": "¡Comenzó un fuerte viento!", "strongWindsLapseMessage": "El viento sopla intensamente.", + "strongWindsEffectMessage": "¡Las misteriosas turbulencias atenúan el ataque!", "strongWindsClearMessage": "El fuerte viento cesó." }; diff --git a/src/locales/fr/weather.ts b/src/locales/fr/weather.ts index fa6090a3ad5..3df8d0e20c9 100644 --- a/src/locales/fr/weather.ts +++ b/src/locales/fr/weather.ts @@ -40,5 +40,6 @@ export const weather: SimpleTranslationEntries = { "strongWindsStartMessage": "Un vent mystérieux se lève !", "strongWindsLapseMessage": "Le vent mystérieux souffle violemment !", + "strongWindsEffectMessage": "Le courant aérien mystérieux affaiblit l’attaque !", "strongWindsClearMessage": "Le vent mystérieux s’est dissipé…" }; diff --git a/src/locales/it/weather.ts b/src/locales/it/weather.ts index 2d169421a38..f5d8e3b9397 100644 --- a/src/locales/it/weather.ts +++ b/src/locales/it/weather.ts @@ -40,5 +40,6 @@ export const weather: SimpleTranslationEntries = { "strongWindsStartMessage": "È apparsa una corrente d'aria misteriosa!", "strongWindsLapseMessage": "La corrente d'aria soffia intensamente.", + "strongWindsEffectMessage": "La corrente misteriosa indebolisce l’attacco!", "strongWindsClearMessage": "La corrente d'aria è cessata." }; diff --git a/src/locales/ko/weather.ts b/src/locales/ko/weather.ts index 7fbd1eaf20b..9aca57c49d2 100644 --- a/src/locales/ko/weather.ts +++ b/src/locales/ko/weather.ts @@ -41,5 +41,6 @@ export const weather: SimpleTranslationEntries = { "strongWindsStartMessage": "수수께끼의 난기류가\n비행포켓몬을 지킨다!", "strongWindsLapseMessage": "수수께끼의 난기류가 강렬하게 불고 있다", + "strongWindsEffectMessage": "수수께끼의 난기류가 공격을 약하게 만들었다!", "strongWindsClearMessage": "수수께끼의 난기류가 멈췄다!" // 임의번역 }; diff --git a/src/locales/pt_BR/weather.ts b/src/locales/pt_BR/weather.ts index 6aaab6d3cd9..0787b32e416 100644 --- a/src/locales/pt_BR/weather.ts +++ b/src/locales/pt_BR/weather.ts @@ -40,5 +40,6 @@ export const weather: SimpleTranslationEntries = { "strongWindsStartMessage": "Ventos fortes apareceram!", "strongWindsLapseMessage": "Os ventos fortes continuam.", + "strongWindsEffectMessage": "The mysterious air current weakened the attack!", "strongWindsClearMessage": "Os ventos fortes diminuíram.", }; diff --git a/src/locales/zh_CN/weather.ts b/src/locales/zh_CN/weather.ts index ad1ecc65007..d280dfccb95 100644 --- a/src/locales/zh_CN/weather.ts +++ b/src/locales/zh_CN/weather.ts @@ -40,5 +40,6 @@ export const weather: SimpleTranslationEntries = { "strongWindsStartMessage": "吹起了神秘的乱流!", "strongWindsLapseMessage": "神秘的乱流势头不减。", + "strongWindsEffectMessage": "The mysterious air current weakened the attack!", "strongWindsClearMessage": "神秘的乱流停止了。" }; diff --git a/src/locales/zh_TW/weather.ts b/src/locales/zh_TW/weather.ts index 7efdc8af0ad..ae0646ce33d 100644 --- a/src/locales/zh_TW/weather.ts +++ b/src/locales/zh_TW/weather.ts @@ -40,5 +40,6 @@ export const weather: SimpleTranslationEntries = { "strongWindsStartMessage": "吹起了神秘的亂流!", "strongWindsLapseMessage": "神秘的亂流勢頭不減。", + "strongWindsEffectMessage": "The mysterious air current weakened the attack!", "strongWindsClearMessage": "神秘的亂流停止了。" }; diff --git a/src/test/arena/weather_strong_winds.test.ts b/src/test/arena/weather_strong_winds.test.ts new file mode 100644 index 00000000000..d022d69a772 --- /dev/null +++ b/src/test/arena/weather_strong_winds.test.ts @@ -0,0 +1,82 @@ +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; +import Phaser from "phaser"; +import GameManager from "#app/test/utils/gameManager"; +import * as overrides from "#app/overrides"; +import { Species } from "#enums/species"; +import { + TurnStartPhase, +} from "#app/phases"; +import { Moves } from "#enums/moves"; +import { getMovePosition } from "#app/test/utils/gameManagerUtils"; +import { Abilities } from "#enums/abilities"; +import { allMoves } from "#app/data/move.js"; + +describe("Weather - Strong Winds", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true); + vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(10); + vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.TAILLOW); + vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.DELTA_STREAM); + vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.THUNDERBOLT, Moves.ICE_BEAM, Moves.ROCK_SLIDE]); + }); + + it("electric type move is not very effective on Rayquaza", async () => { + vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.RAYQUAZA); + + await game.startBattle([Species.PIKACHU]); + const pikachu = game.scene.getPlayerPokemon(); + const enemy = game.scene.getEnemyPokemon(); + + game.doAttack(getMovePosition(game.scene, 0, Moves.THUNDERBOLT)); + + await game.phaseInterceptor.to(TurnStartPhase); + expect(enemy.getAttackTypeEffectiveness(allMoves[Moves.THUNDERBOLT].type, pikachu)).toBe(0.5); + }); + + it("electric type move is neutral for flying type pokemon", async () => { + await game.startBattle([Species.PIKACHU]); + const pikachu = game.scene.getPlayerPokemon(); + const enemy = game.scene.getEnemyPokemon(); + + game.doAttack(getMovePosition(game.scene, 0, Moves.THUNDERBOLT)); + + await game.phaseInterceptor.to(TurnStartPhase); + expect(enemy.getAttackTypeEffectiveness(allMoves[Moves.THUNDERBOLT].type, pikachu)).toBe(1); + }); + + it("ice type move is neutral for flying type pokemon", async () => { + await game.startBattle([Species.PIKACHU]); + const pikachu = game.scene.getPlayerPokemon(); + const enemy = game.scene.getEnemyPokemon(); + + game.doAttack(getMovePosition(game.scene, 0, Moves.ICE_BEAM)); + + await game.phaseInterceptor.to(TurnStartPhase); + expect(enemy.getAttackTypeEffectiveness(allMoves[Moves.ICE_BEAM].type, pikachu)).toBe(1); + }); + + it("rock type move is neutral for flying type pokemon", async () => { + await game.startBattle([Species.PIKACHU]); + const pikachu = game.scene.getPlayerPokemon(); + const enemy = game.scene.getEnemyPokemon(); + + game.doAttack(getMovePosition(game.scene, 0, Moves.ROCK_SLIDE)); + + await game.phaseInterceptor.to(TurnStartPhase); + expect(enemy.getAttackTypeEffectiveness(allMoves[Moves.ROCK_SLIDE].type, pikachu)).toBe(1); + }); +});