[Move] Implement (or re-implement?) Lucky Chant (#3352)

* Implement Lucky Chant

* Add i18n keys for NoCritTag messages

* Add Lucky Chant message translations (DE, FR, KO, PT-BR)

Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com>
Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr>
Co-authored-by: Enoch <enoch.jwsong@gmail.com>
Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br>

* Add ZH translations

Co-authored-by: Sonny Ding <93831983+sonnyding1@users.noreply.github.com>

* Add keys for JA locale

---------

Co-authored-by: Jannik Tappert <38758606+CodeTappert@users.noreply.github.com>
Co-authored-by: Lugiad' <adrien.grivel@hotmail.fr>
Co-authored-by: Enoch <enoch.jwsong@gmail.com>
Co-authored-by: José Ricardo Fleury Oliveira <josefleury@discente.ufg.br>
Co-authored-by: Sonny Ding <93831983+sonnyding1@users.noreply.github.com>
This commit is contained in:
innerthunder 2024-08-06 23:31:54 -07:00 committed by GitHub
parent f555dd6dc8
commit a4c913d963
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 194 additions and 15 deletions

View File

@ -303,6 +303,39 @@ class CraftyShieldTag extends ConditionalProtectTag {
}
}
/**
* Arena Tag class for {@link https://bulbapedia.bulbagarden.net/wiki/Lucky_Chant_(move) Lucky Chant}.
* Prevents critical hits against the tag's side.
*/
export class NoCritTag extends ArenaTag {
/**
* Constructor method for the NoCritTag class
* @param turnCount `integer` the number of turns this effect lasts
* @param sourceMove {@linkcode Moves} the move that created this effect
* @param sourceId `integer` the ID of the {@linkcode Pokemon} that created this effect
* @param side {@linkcode ArenaTagSide} the side to which this effect belongs
*/
constructor(turnCount: integer, sourceMove: Moves, sourceId: integer, side: ArenaTagSide) {
super(ArenaTagType.NO_CRIT, turnCount, sourceMove, sourceId, side);
}
/** Queues a message upon adding this effect to the field */
onAdd(arena: Arena): void {
arena.scene.queueMessage(i18next.t(`arenaTag:noCritOnAdd${this.side === ArenaTagSide.PLAYER ? "Player" : "Enemy"}`, {
moveName: this.getMoveName()
}));
}
/** Queues a message upon removing this effect from the field */
onRemove(arena: Arena): void {
const source = arena.scene.getPokemonById(this.sourceId);
arena.scene.queueMessage(i18next.t("arenaTag:noCritOnRemove", {
pokemonNameWithAffix: getPokemonNameWithAffix(source),
moveName: this.getMoveName()
}));
}
}
/**
* Arena Tag class for {@link https://bulbapedia.bulbagarden.net/wiki/Wish_(move) Wish}.
* Heals the Pokémon in the user's position the turn after Wish is used.
@ -803,6 +836,8 @@ export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMov
return new MatBlockTag(sourceId, side);
case ArenaTagType.CRAFTY_SHIELD:
return new CraftyShieldTag(sourceId, side);
case ArenaTagType.NO_CRIT:
return new NoCritTag(turnCount, sourceMove, sourceId, side);
case ArenaTagType.MUD_SPORT:
return new MudSportTag(turnCount, sourceId);
case ArenaTagType.WATER_SPORT:

View File

@ -1774,8 +1774,6 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
case BattlerTagType.ALWAYS_CRIT:
case BattlerTagType.IGNORE_ACCURACY:
return new BattlerTag(tagType, BattlerTagLapseType.TURN_END, 2, sourceMove);
case BattlerTagType.NO_CRIT:
return new BattlerTag(tagType, BattlerTagLapseType.AFTER_MOVE, turnCount, sourceMove);
case BattlerTagType.ALWAYS_GET_HIT:
case BattlerTagType.RECEIVE_DOUBLE_DAMAGE:
return new BattlerTag(tagType, BattlerTagLapseType.PRE_MOVE, 1, sourceMove);

View File

@ -4258,7 +4258,6 @@ export class AddBattlerTagAttr extends MoveEffectAttr {
case BattlerTagType.INFATUATED:
case BattlerTagType.NIGHTMARE:
case BattlerTagType.DROWSY:
case BattlerTagType.NO_CRIT:
return -5;
case BattlerTagType.SEEDED:
case BattlerTagType.SALT_CURED:
@ -7120,9 +7119,8 @@ export function initMoves() {
new StatusMove(Moves.GASTRO_ACID, Type.POISON, 100, 10, -1, 0, 4)
.attr(SuppressAbilitiesAttr),
new StatusMove(Moves.LUCKY_CHANT, Type.NORMAL, -1, 30, -1, 0, 4)
.attr(AddBattlerTagAttr, BattlerTagType.NO_CRIT, false, false, 5)
.target(MoveTarget.USER_SIDE)
.unimplemented(),
.attr(AddArenaTagAttr, ArenaTagType.NO_CRIT, 5, true, true)
.target(MoveTarget.USER_SIDE),
new StatusMove(Moves.ME_FIRST, Type.NORMAL, -1, 20, -1, 0, 4)
.ignoresVirtual()
.target(MoveTarget.NEAR_ENEMY)

View File

@ -21,5 +21,6 @@ export enum ArenaTagType {
MAT_BLOCK = "MAT_BLOCK",
CRAFTY_SHIELD = "CRAFTY_SHIELD",
TAILWIND = "TAILWIND",
HAPPY_HOUR = "HAPPY_HOUR"
HAPPY_HOUR = "HAPPY_HOUR",
NO_CRIT = "NO_CRIT"
}

View File

@ -48,7 +48,6 @@ export enum BattlerTagType {
FIRE_BOOST = "FIRE_BOOST",
CRIT_BOOST = "CRIT_BOOST",
ALWAYS_CRIT = "ALWAYS_CRIT",
NO_CRIT = "NO_CRIT",
IGNORE_ACCURACY = "IGNORE_ACCURACY",
BYPASS_SLEEP = "BYPASS_SLEEP",
IGNORE_FLYING = "IGNORE_FLYING",

View File

@ -22,7 +22,7 @@ import { BattleStat } from "../data/battle-stat";
import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag } from "../data/battler-tags";
import { WeatherType } from "../data/weather";
import { TempBattleStat } from "../data/temp-battle-stat";
import { ArenaTagSide, WeakenMoveScreenTag } from "../data/arena-tag";
import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "../data/arena-tag";
import { Ability, AbAttr, BattleStatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldBattleStatMultiplierAbAttrs, FieldMultiplyBattleStatAbAttr, AddSecondStrikeAbAttr, IgnoreOpponentEvasionAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr } from "../data/ability";
import PokemonData from "../system/pokemon-data";
import { BattlerIndex } from "../battle";
@ -1885,6 +1885,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
apply(source: Pokemon, move: Move): HitResult {
let result: HitResult;
const damage = new Utils.NumberHolder(0);
const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
const defendingSidePlayField = this.isPlayer() ? this.scene.getPlayerField() : this.scene.getEnemyField();
const variableCategory = new Utils.IntegerHolder(move.category);
@ -1911,7 +1912,6 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
// Apply arena tags for conditional protection
if (!move.checkFlag(MoveFlags.IGNORE_PROTECT, source, this) && !move.isAllyTarget()) {
const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
this.scene.arena.applyTagsForSide(ArenaTagType.QUICK_GUARD, defendingSide, cancelled, this, move.priority);
this.scene.arena.applyTagsForSide(ArenaTagType.WIDE_GUARD, defendingSide, cancelled, this, move.moveTarget);
this.scene.arena.applyTagsForSide(ArenaTagType.MAT_BLOCK, defendingSide, cancelled, this, move.category);
@ -1978,15 +1978,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
console.log(`crit stage: +${critLevel.value}`);
const critChance = [24, 8, 2, 1][Math.max(0, Math.min(critLevel.value, 3))];
isCritical = !source.getTag(BattlerTagType.NO_CRIT) && (critChance === 1 || !this.scene.randBattleSeedInt(critChance));
isCritical = critChance === 1 || !this.scene.randBattleSeedInt(critChance);
if (Overrides.NEVER_CRIT_OVERRIDE) {
isCritical = false;
}
}
if (isCritical) {
const noCritTag = this.scene.arena.getTagOnSide(NoCritTag, defendingSide);
const blockCrit = new Utils.BooleanHolder(false);
applyAbAttrs(BlockCritAbAttr, this, null, blockCrit);
if (blockCrit.value) {
if (noCritTag || blockCrit.value) {
isCritical = false;
}
}
@ -1996,7 +1997,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
applyAbAttrs(MultCritAbAttr, source, null, criticalMultiplier);
const screenMultiplier = new Utils.NumberHolder(1);
if (!isCritical) {
this.scene.arena.applyTagsForSide(WeakenMoveScreenTag, this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, move.category, this.scene.currentBattle.double, screenMultiplier);
this.scene.arena.applyTagsForSide(WeakenMoveScreenTag, defendingSide, move.category, this.scene.currentBattle.double, screenMultiplier);
}
const isTypeImmune = (typeMultiplier.value * arenaAttackTypeMultiplier.value) === 0;
const sourceTypes = source.getTypes();

View File

@ -22,6 +22,9 @@ export const arenaTag: SimpleTranslationEntries = {
"conditionalProtectOnAddEnemy": "Die Pokémon auf der gegnerischen Seite werden von {{moveName}} behütet!",
"conditionalProtectApply": "{{pokemonNameWithAffix}} wird durch {{moveName}} geschützt!",
"matBlockOnAdd": "{{pokemonNameWithAffix}} bringt seinen Tatami-Schild in Position!",
"noCritOnAddPlayer": "{{moveName}} schützt dein Team vor Volltreffern!",
"noCritOnAddEnemy": "{{moveName}} schützt das gegnerische Team vor Volltreffern!",
"noCritOnRemove": "{{moveName}} von {{pokemonNameWithAffix}} hört auf zu wirken!",
"wishTagOnAdd": "Der Wunschtraum von {{pokemonNameWithAffix}} erfüllt sich!",
"mudSportOnAdd": "Die Stärke aller Elektro-Attacken wurde reduziert!",
"mudSportOnRemove": "Lehmsuhler hört auf zu wirken!",

View File

@ -22,6 +22,9 @@ export const arenaTag: SimpleTranslationEntries = {
"conditionalProtectOnAddEnemy": "{{moveName}} protected the\nopposing team!",
"conditionalProtectApply": "{{moveName}} protected {{pokemonNameWithAffix}}!",
"matBlockOnAdd": "{{pokemonNameWithAffix}} intends to flip up a mat\nand block incoming attacks!",
"noCritOnAddPlayer": "The {{moveName}} shielded your\nteam from critical hits!",
"noCritOnAddEnemy": "The {{moveName}} shielded the opposing\nteam from critical hits!",
"noCritOnRemove": "{{pokemonNameWithAffix}}'s {{moveName}}\nwore off!",
"wishTagOnAdd": "{{pokemonNameWithAffix}}'s wish\ncame true!",
"mudSportOnAdd": "Electricity's power was weakened!",
"mudSportOnRemove": "The effects of Mud Sport\nhave faded.",

View File

@ -22,6 +22,9 @@ export const arenaTag: SimpleTranslationEntries = {
"conditionalProtectOnAddEnemy": "{{moveName}} protected the\nopposing team!",
"conditionalProtectApply": "{{moveName}} protected {{pokemonNameWithAffix}}!",
"matBlockOnAdd": "{{pokemonNameWithAffix}} intends to flip up a mat\nand block incoming attacks!",
"noCritOnAddPlayer": "The {{moveName}} shielded your\nteam from critical hits!",
"noCritOnAddEnemy": "The {{moveName}} shielded the opposing\nteam from critical hits!",
"noCritOnRemove": "{{pokemonNameWithAffix}}'s {{moveName}}\nwore off!",
"wishTagOnAdd": "{{pokemonNameWithAffix}}'s wish\ncame true!",
"mudSportOnAdd": "Electricity's power was weakened!",
"mudSportOnRemove": "The effects of Mud Sport\nhave faded.",

View File

@ -22,6 +22,9 @@ export const arenaTag: SimpleTranslationEntries = {
"conditionalProtectOnAddEnemy": "La capacité {{moveName}}\nprotège léquipe ennemie !",
"conditionalProtectApply": "{{pokemonNameWithAffix}} est protégé\npar {{moveName}} !",
"matBlockOnAdd": "{{pokemonNameWithAffix}} se prépare\nà utiliser un tatami pour bloquer les attaques !",
"noCritOnAddPlayer": "{{moveName}} immunise votre équipe\ncontre les coups critiques !",
"noCritOnAddEnemy": "{{moveName}} immunise léquipe ennemie\ncontre les coups critiques !",
"noCritOnRemove": "Les effets d{{moveName}}\nsur {{pokemonNameWithAffix}} prennent fin !",
"wishTagOnAdd": "Le vœu de{{pokemonNameWithAffix}}\nse réalise !",
"mudSportOnAdd": "La puissance des capacités\nde type Électrik diminue !",
"mudSportOnRemove": "Leffet de Lance-Boue se dissipe !",

View File

@ -22,6 +22,9 @@ export const arenaTag: SimpleTranslationEntries = {
"conditionalProtectOnAddEnemy": "{{moveName}} protected the\nopposing team!",
"conditionalProtectApply": "{{moveName}} protected {{pokemonNameWithAffix}}!",
"matBlockOnAdd": "{{pokemonNameWithAffix}} intends to flip up a mat\nand block incoming attacks!",
"noCritOnAddPlayer": "The {{moveName}} shielded your\nteam from critical hits!",
"noCritOnAddEnemy": "The {{moveName}} shielded the opposing\nteam from critical hits!",
"noCritOnRemove": "{{pokemonNameWithAffix}}'s {{moveName}}\nwore off!",
"wishTagOnAdd": "{{pokemonNameWithAffix}}'s wish\ncame true!",
"mudSportOnAdd": "Electricity's power was weakened!",
"mudSportOnRemove": "The effects of Mud Sport\nhave faded.",

View File

@ -22,6 +22,9 @@ export const arenaTag: SimpleTranslationEntries = {
"conditionalProtectOnAddEnemy": "{{moveName}} protected the\nopposing team!",
"conditionalProtectApply": "{{moveName}} protected {{pokemonNameWithAffix}}!",
"matBlockOnAdd": "{{pokemonNameWithAffix}} intends to flip up a mat\nand block incoming attacks!",
"noCritOnAddPlayer": "The {{moveName}} shielded your\nteam from critical hits!",
"noCritOnAddEnemy": "The {{moveName}} shielded the opposing\nteam from critical hits!",
"noCritOnRemove": "{{pokemonNameWithAffix}}'s {{moveName}}\nwore off!",
"wishTagOnAdd": "{{pokemonNameWithAffix}}'s wish\ncame true!",
"mudSportOnAdd": "Electricity's power was weakened!",
"mudSportOnRemove": "The effects of Mud Sport\nhave faded.",

View File

@ -22,6 +22,9 @@ export const arenaTag: SimpleTranslationEntries = {
"conditionalProtectOnAddEnemy": "상대 주변을\n{{moveName}}[[가]] 보호하고 있다!",
"conditionalProtectApply": "{{pokemonNameWithAffix}}[[를]]\n{{moveName}}[[가]] 지켜주고 있다!",
"matBlockOnAdd": "{{pokemonNameWithAffix}}[[는]]\n마룻바닥세워막기를 노리고 있다!",
"noCritOnAddPlayer": "{{moveName}}의 힘으로\n우리 편의 급소가 숨겨졌다!",
"noCritOnAddEnemy": "{{moveName}}의 힘으로\n상대의 급소가 숨겨졌다!",
"noCritOnRemove": "{{pokemonNameWithAffix}}의 {{moveName}}[[가]] 풀렸다!",
"wishTagOnAdd": "{{pokemonNameWithAffix}}의\n희망사항이 이루어졌다!",
"mudSportOnAdd": "전기의 위력이 약해졌다!",
"mudSportOnRemove": "흙놀이의 효과가\n없어졌다!",

View File

@ -22,6 +22,9 @@ export const arenaTag: SimpleTranslationEntries = {
"conditionalProtectOnAddEnemy": "{{moveName}} protegeu a\nequipe adversária!",
"conditionalProtectApply": "{{moveName}} protegeu {{pokemonNameWithAffix}}!",
"matBlockOnAdd": "{{pokemonNameWithAffix}} pretende levantar um tapete\npara bloquear ataques!",
"noCritOnAddPlayer": "{{moveName}} protegeu sua\equipe de acertos críticos!",
"noCritOnAddEnemy": "{{moveName}} protegeu a\equipe adversária de acertos críticos",
"noCritOnRemove": "{{moveName}} de {{pokemonNameWithAffix}}\nacabou!",
"wishTagOnAdd": "O desejo de {{pokemonNameWithAffix}}\nfoi concedido!",
"mudSportOnAdd": "O poder de movimentos elétricos foi enfraquecido!",
"mudSportOnRemove": "Os efeitos de Mud Sport\nsumiram.",

View File

@ -22,6 +22,9 @@ export const arenaTag: SimpleTranslationEntries = {
"conditionalProtectOnAddEnemy": "{{moveName}}\n保护了敌方",
"conditionalProtectApply": "{{moveName}}\n保护了{{pokemonNameWithAffix}}",
"matBlockOnAdd": "{{pokemonNameWithAffix}}正在\n伺机使出掀榻榻米",
"noCritOnAddPlayer": "{{moveName}}保护了你的\n队伍不被击中要害",
"noCritOnAddEnemy": "{{moveName}}保护了对方的\n队伍不被击中要害",
"noCritOnRemove": "{{pokemonNameWithAffix}}的{{moveName}}\n效果消失了",
"wishTagOnAdd": "{{pokemonNameWithAffix}}的\n祈愿实现了",
"mudSportOnAdd": "电气的威力减弱了!",
"mudSportOnRemove": "玩泥巴的效果消失了!",

View File

@ -22,6 +22,9 @@ export const arenaTag: SimpleTranslationEntries = {
"conditionalProtectOnAddEnemy": "{{moveName}} protected the\nopposing team!",
"conditionalProtectApply": "{{moveName}} protected {{pokemonNameWithAffix}}!",
"matBlockOnAdd": "{{pokemonNameWithAffix}} intends to flip up a mat\nand block incoming attacks!",
"noCritOnAddPlayer": "{{moveName}}保護了你的\n隊伍不被擊中要害",
"noCritOnAddEnemy": "{{moveName}}保護了對方的\n隊伍不被擊中要害",
"noCritOnRemove": "{{pokemonNameWithAffix}}的{{moveName}}\n效果消失了",
"wishTagOnAdd": "{{pokemonNameWithAffix}}'s wish\ncame true!",
"mudSportOnAdd": "Electricity's power was weakened!",
"mudSportOnRemove": "The effects of Mud Sport\nhave faded.",

View File

@ -35,7 +35,6 @@ describe("Moves - Dragon Rage", () => {
game = new GameManager(phaserGame);
game.override.battleType("single");
game.override.disableCrits();
game.override.starterSpecies(Species.SNORLAX);
game.override.moveset([Moves.DRAGON_RAGE]);
@ -60,6 +59,7 @@ describe("Moves - Dragon Rage", () => {
});
it("ignores weaknesses", async () => {
game.override.disableCrits();
vi.spyOn(enemyPokemon, "getTypes").mockReturnValue([Type.DRAGON]);
game.doAttack(getMovePosition(game.scene, 0, Moves.DRAGON_RAGE));
@ -70,6 +70,7 @@ describe("Moves - Dragon Rage", () => {
});
it("ignores resistances", async () => {
game.override.disableCrits();
vi.spyOn(enemyPokemon, "getTypes").mockReturnValue([Type.STEEL]);
game.doAttack(getMovePosition(game.scene, 0, Moves.DRAGON_RAGE));
@ -80,6 +81,7 @@ describe("Moves - Dragon Rage", () => {
});
it("ignores stat changes", async () => {
game.override.disableCrits();
partyPokemon.summonData.battleStats[BattleStat.SPATK] = 2;
game.doAttack(getMovePosition(game.scene, 0, Moves.DRAGON_RAGE));
@ -90,6 +92,7 @@ describe("Moves - Dragon Rage", () => {
});
it("ignores stab", async () => {
game.override.disableCrits();
vi.spyOn(partyPokemon, "getTypes").mockReturnValue([Type.DRAGON]);
game.doAttack(getMovePosition(game.scene, 0, Moves.DRAGON_RAGE));
@ -100,7 +103,6 @@ describe("Moves - Dragon Rage", () => {
});
it("ignores criticals", async () => {
partyPokemon.removeTag(BattlerTagType.NO_CRIT);
partyPokemon.addTag(BattlerTagType.ALWAYS_CRIT, 99);
game.doAttack(getMovePosition(game.scene, 0, Moves.DRAGON_RAGE));
@ -111,6 +113,7 @@ describe("Moves - Dragon Rage", () => {
});
it("ignores damage modification from abilities such as ice scales", async () => {
game.override.disableCrits();
game.override.enemyAbility(Abilities.ICE_SCALES);
game.doAttack(getMovePosition(game.scene, 0, Moves.DRAGON_RAGE));
@ -121,6 +124,7 @@ describe("Moves - Dragon Rage", () => {
});
it("ignores multi hit", async () => {
game.override.disableCrits();
game.scene.addModifier(modifierTypes.MULTI_LENS().newModifier(partyPokemon), false);
game.doAttack(getMovePosition(game.scene, 0, Moves.DRAGON_RAGE));

View File

@ -0,0 +1,113 @@
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
import GameManager from "../utils/gameManager";
import { getMovePosition } from "../utils/gameManagerUtils";
import { Moves } from "#app/enums/moves.js";
import { Species } from "#app/enums/species.js";
import { Abilities } from "#app/enums/abilities.js";
import { BerryPhase, TurnEndPhase } from "#app/phases.js";
import { BattlerTagType } from "#app/enums/battler-tag-type.js";
const TIMEOUT = 20 * 1000;
describe("Moves - Lucky Chant", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
game.override
.battleType("single")
.moveset([Moves.LUCKY_CHANT, Moves.SPLASH, Moves.FOLLOW_ME])
.enemySpecies(Species.SNORLAX)
.enemyAbility(Abilities.INSOMNIA)
.enemyMoveset(Array(4).fill(Moves.FLOWER_TRICK))
.startingLevel(100)
.enemyLevel(100);
});
it(
"should prevent critical hits from moves",
async () => {
await game.startBattle([Species.CHARIZARD]);
const playerPokemon = game.scene.getPlayerPokemon();
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.phaseInterceptor.to(TurnEndPhase);
const firstTurnDamage = playerPokemon.getMaxHp() - playerPokemon.hp;
game.doAttack(getMovePosition(game.scene, 0, Moves.LUCKY_CHANT));
await game.phaseInterceptor.to(BerryPhase, false);
const secondTurnDamage = playerPokemon.getMaxHp() - playerPokemon.hp - firstTurnDamage;
expect(secondTurnDamage).toBeLessThan(firstTurnDamage);
}, TIMEOUT
);
it(
"should prevent critical hits against the user's ally",
async () => {
game.override.battleType("double");
await game.startBattle([Species.CHARIZARD, Species.BLASTOISE]);
const playerPokemon = game.scene.getPlayerField();
game.doAttack(getMovePosition(game.scene, 0, Moves.FOLLOW_ME));
game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH));
await game.phaseInterceptor.to(TurnEndPhase);
const firstTurnDamage = playerPokemon[0].getMaxHp() - playerPokemon[0].hp;
game.doAttack(getMovePosition(game.scene, 0, Moves.FOLLOW_ME));
game.doAttack(getMovePosition(game.scene, 1, Moves.LUCKY_CHANT));
await game.phaseInterceptor.to(BerryPhase, false);
const secondTurnDamage = playerPokemon[0].getMaxHp() - playerPokemon[0].hp - firstTurnDamage;
expect(secondTurnDamage).toBeLessThan(firstTurnDamage);
}, TIMEOUT
);
it(
"should prevent critical hits from field effects",
async () => {
game.override.enemyMoveset(Array(4).fill(Moves.TACKLE));
await game.startBattle([Species.CHARIZARD]);
const playerPokemon = game.scene.getPlayerPokemon();
const enemyPokemon = game.scene.getEnemyPokemon();
enemyPokemon.addTag(BattlerTagType.ALWAYS_CRIT, 2, Moves.NONE, 0);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.phaseInterceptor.to(TurnEndPhase);
const firstTurnDamage = playerPokemon.getMaxHp() - playerPokemon.hp;
game.doAttack(getMovePosition(game.scene, 0, Moves.LUCKY_CHANT));
await game.phaseInterceptor.to(BerryPhase, false);
const secondTurnDamage = playerPokemon.getMaxHp() - playerPokemon.hp - firstTurnDamage;
expect(secondTurnDamage).toBeLessThan(firstTurnDamage);
}, TIMEOUT
);
});