From 2edb67b56d30a33014393b3df0f45b476004eb85 Mon Sep 17 00:00:00 2001 From: xsn34kzx Date: Sat, 17 Aug 2024 22:41:56 -0400 Subject: [PATCH] Finalize `BattleStat` Removal --- docs/enemy-ai.md | 8 +- src/data/ability.ts | 7 +- src/data/arena-tag.ts | 8 +- src/data/battler-tags.ts | 6 +- src/data/berry.ts | 10 +- src/data/move.ts | 24 +-- src/data/pokemon-evolutions.ts | 2 +- src/data/pokemon-species.ts | 2 +- src/field/pokemon.ts | 30 +-- src/modifier/modifier.ts | 2 +- src/phases.ts | 114 +++++------- src/test/abilities/intimidate.test.ts | 2 +- src/test/abilities/moxie.test.ts | 3 +- src/test/abilities/serene_grace.test.ts | 2 +- src/test/abilities/sheer_force.test.ts | 2 +- src/test/abilities/shield_dust.test.ts | 2 +- src/test/abilities/zen_mode.test.ts | 2 +- src/test/battle-stat.spec.ts | 145 --------------- src/test/battle/battle-order.test.ts | 2 +- src/test/battlerTags/octolock.test.ts | 10 +- src/test/battlerTags/stockpiling.test.ts | 68 +++---- src/test/items/eviolite.test.ts | 2 +- src/test/items/light_ball.test.ts | 2 +- src/test/items/metal_powder.test.ts | 2 +- src/test/items/quick_powder.test.ts | 2 +- src/test/items/thick_club.test.ts | 2 +- src/test/localization/battle-stat.test.ts | 212 ---------------------- src/test/moves/follow_me.test.ts | 2 +- src/test/moves/fusion_flare_bolt.test.ts | 2 +- src/test/moves/make_it_rain.test.ts | 6 +- src/test/moves/spotlight.test.ts | 2 +- src/test/moves/tackle.test.ts | 2 +- src/test/moves/tailwind.test.ts | 2 +- src/test/utils/phaseInterceptor.ts | 4 +- src/ui/battle-info.ts | 16 +- src/ui/stats-container.ts | 3 +- 36 files changed, 170 insertions(+), 542 deletions(-) delete mode 100644 src/test/battle-stat.spec.ts delete mode 100644 src/test/localization/battle-stat.test.ts diff --git a/docs/enemy-ai.md b/docs/enemy-ai.md index f53a8511893..46482f56a90 100644 --- a/docs/enemy-ai.md +++ b/docs/enemy-ai.md @@ -191,15 +191,15 @@ Now that the enemy Pokémon with the best matchup score is on the field (assumin We then need to apply a 2x multiplier for the move's type effectiveness and a 1.5x multiplier since STAB applies. After applying these multipliers, the final score for this move is **75**. -- **Swords Dance**: As a non-attacking move, this move's benefit score is derived entirely from the sum of its attributes' benefit scores. Swords Dance's `StatChangeAttr` has a user benefit score of 0 and a target benefit score that, in this case, simplifies to +- **Swords Dance**: As a non-attacking move, this move's benefit score is derived entirely from the sum of its attributes' benefit scores. Swords Dance's `StatStageChangeAttr` has a user benefit score of 0 and a target benefit score that, in this case, simplifies to $\text{TBS}=4\times \text{levels} + (-2\times \text{sign(levels)})$ where `levels` is the number of stat stages added by the attribute (in this case, +2). The final score for this move is **6** (Note: because this move is self-targeted, we don't flip the sign of TBS when computing the target score). -- **Crush Claw**: This move is a 75-power Normal-type physical attack with a 50 percent chance to lower the target's Defense by one stage. The additional effect is implemented by the same `StatChangeAttr` as Swords Dance, so we can use the same formulas from before to compute the total TBS and base target score. +- **Crush Claw**: This move is a 75-power Normal-type physical attack with a 50 percent chance to lower the target's Defense by one stage. The additional effect is implemented by the same `StatStageChangeAttr` as Swords Dance, so we can use the same formulas from before to compute the total TBS and base target score. - $\text{TBS}=\text{getTargetBenefitScore(StatChangeAttr)}-\text{attackScore}$ + $\text{TBS}=\text{getTargetBenefitScore(StatStageChangeAttr)}-\text{attackScore}$ $\text{TBS}=(-4 + 2)-(-2\times 2 + \lfloor \frac{75}{5} \rfloor)=-2-11=-13$ @@ -221,4 +221,4 @@ When implementing a new move attribute, it's important to override `MoveAttr`'s - A move's **user benefit score (UBS)** incentivizes (or discourages) the move's usage in general. A positive UBS gives the move more incentive to be used, while a negative UBS gives the move less incentive. - A move's **target benefit score (TBS)** incentivizes (or discourages) the move's usage on a specific target. A positive TBS indicates the move is better used on the user or its allies, while a negative TBS indicates the move is better used on enemies. - **The total benefit score (UBS + TBS) of a move should never be 0.** The move selection algorithm assumes the move's benefit score is unimplemented if the total score is 0 and penalizes the move's usage as a result. With status moves especially, it's important to have some form of implementation among the move's attributes to avoid this scenario. -- **Score functions that use formulas should include comments.** If your attribute requires complex logic or formulas to calculate benefit scores, please add comments to explain how the logic works and its intended effect on the enemy's decision making. \ No newline at end of file +- **Score functions that use formulas should include comments.** If your attribute requires complex logic or formulas to calculate benefit scores, please add comments to explain how the logic works and its intended effect on the enemy's decision making. diff --git a/src/data/ability.ts b/src/data/ability.ts index 993256465ea..b16d1fa5a4a 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -1821,7 +1821,7 @@ export class CopyFaintedAllyAbilityAbAttr extends PostKnockOutAbAttr { } } -export class IgnoreOpponentStatStageChangesAbAttr extends AbAttr { +export class IgnoreOpponentStatStagesAbAttr extends AbAttr { constructor() { super(false); } @@ -2010,8 +2010,7 @@ export class PostSummonStatStageChangeAbAttr extends PostSummonAbAttr { applyAbAttrs(PostIntimidateStatStageChangeAbAttr, opponent, cancelled); } if (!cancelled.value) { - const statChangePhase = new StatStageChangePhase(pokemon.scene, opponent.getBattlerIndex(), false, this.stats, this.stages); - pokemon.scene.unshiftPhase(statChangePhase); + pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, opponent.getBattlerIndex(), false, this.stats, this.stages)); } } return true; @@ -4690,7 +4689,7 @@ export function initAbilities() { new Ability(Abilities.FOREWARN, 4) .attr(ForewarnAbAttr), new Ability(Abilities.UNAWARE, 4) - .attr(IgnoreOpponentStatStageChangesAbAttr) + .attr(IgnoreOpponentStatStagesAbAttr) .ignorable(), new Ability(Abilities.TINTED_LENS, 4) //@ts-ignore diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index ed30178e7c9..bb2ac4c32ae 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -4,7 +4,7 @@ import * as Utils from "../utils"; import { MoveCategory, allMoves, MoveTarget } from "./move"; import { getPokemonNameWithAffix } from "../messages"; import Pokemon, { HitResult, PokemonMove } from "../field/pokemon"; -import { MoveEffectPhase, PokemonHealPhase, ShowAbilityPhase, StatChangePhase } from "../phases"; +import { MoveEffectPhase, PokemonHealPhase, ShowAbilityPhase, StatStageChangePhase } from "../phases"; import { StatusEffect } from "./status-effect"; import { BattlerIndex } from "../battle"; import { BlockNonDirectDamageAbAttr, ProtectStatAbAttr, applyAbAttrs } from "./ability"; @@ -725,8 +725,8 @@ class StickyWebTag extends ArenaTrapTag { applyAbAttrs(ProtectStatAbAttr, pokemon, cancelled); if (!cancelled.value) { pokemon.scene.queueMessage(i18next.t("arenaTag:stickyWebActivateTrap", { pokemonName: pokemon.getNameToRender() })); - const statLevels = new Utils.NumberHolder(-1); - pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), false, [ Stat.SPD ], statLevels.value)); + const stages = new Utils.NumberHolder(-1); + pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), false, [ Stat.SPD ], stages.value)); } } @@ -814,7 +814,7 @@ class TailwindTag extends ArenaTag { // Raise attack by one stage if party member has WIND_RIDER ability if (pokemon.hasAbility(Abilities.WIND_RIDER)) { pokemon.scene.unshiftPhase(new ShowAbilityPhase(pokemon.scene, pokemon.getBattlerIndex())); - pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ Stat.ATK ], 1, true)); + pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ Stat.ATK ], 1, true)); } } } diff --git a/src/data/battler-tags.ts b/src/data/battler-tags.ts index 6215557f3e0..26693ded54c 100644 --- a/src/data/battler-tags.ts +++ b/src/data/battler-tags.ts @@ -1,5 +1,5 @@ import { ChargeAnim, CommonAnim, CommonBattleAnim, MoveChargeAnim } from "./battle-anims"; -import { CommonAnimPhase, MoveEffectPhase, MovePhase, PokemonHealPhase, ShowAbilityPhase, StatChangeCallback, StatStageChangePhase } from "../phases"; +import { CommonAnimPhase, MoveEffectPhase, MovePhase, PokemonHealPhase, ShowAbilityPhase, StatStageChangeCallback, StatStageChangePhase } from "../phases"; import { getPokemonNameWithAffix } from "../messages"; import Pokemon, { MoveResult, HitResult } from "../field/pokemon"; import { StatusEffect } from "./status-effect"; @@ -1629,7 +1629,7 @@ export class StockpilingTag extends BattlerTag { super(BattlerTagType.STOCKPILING, BattlerTagLapseType.CUSTOM, 1, sourceMove); } - private onStatsChanged: StatChangeCallback = (_, statsChanged, statChanges) => { + private onStatStagesChanged: StatStageChangeCallback = (_, statsChanged, statChanges) => { const defChange = statChanges[statsChanged.indexOf(Stat.DEF)] ?? 0; const spDefChange = statChanges[statsChanged.indexOf(Stat.SPDEF)] ?? 0; @@ -1669,7 +1669,7 @@ export class StockpilingTag extends BattlerTag { // Attempt to increase DEF and SPDEF by one stage, keeping track of successful changes. pokemon.scene.unshiftPhase(new StatStageChangePhase( pokemon.scene, pokemon.getBattlerIndex(), true, - [Stat.SPDEF, Stat.DEF], 1, true, false, true, this.onStatsChanged + [Stat.SPDEF, Stat.DEF], 1, true, false, true, this.onStatStagesChanged )); } } diff --git a/src/data/berry.ts b/src/data/berry.ts index c0778a86d8e..8c47760e4cf 100644 --- a/src/data/berry.ts +++ b/src/data/berry.ts @@ -1,4 +1,4 @@ -import { PokemonHealPhase, StatChangePhase } from "../phases"; +import { PokemonHealPhase, StatStageChangePhase } from "../phases"; import { getPokemonNameWithAffix } from "../messages"; import Pokemon, { HitResult } from "../field/pokemon"; import { getStatusEffectHealText } from "./status-effect"; @@ -99,7 +99,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc { const stat: BattleStat = berryType - BerryType.ENIGMA; const statStages = new Utils.NumberHolder(1); applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, statStages); - pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ stat ], statStages.value)); + pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ stat ], statStages.value)); }; case BerryType.LANSAT: return (pokemon: Pokemon) => { @@ -114,9 +114,9 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc { pokemon.battleData.berriesEaten.push(berryType); } const randStat = Utils.randSeedInt(Stat.EVA, Stat.ATK); - const statLevels = new Utils.NumberHolder(2); - applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, statLevels); - pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ randStat ], statLevels.value)); // TODO: BattleStats + const stages = new Utils.NumberHolder(2); + applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, stages); + pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ randStat ], stages.value)); }; case BerryType.LEPPA: return (pokemon: Pokemon) => { diff --git a/src/data/move.ts b/src/data/move.ts index 26913b81010..720a6adac63 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -1066,7 +1066,7 @@ export class StatusMoveTypeImmunityAttr extends MoveAttr { } } -export class IgnoreOpponentStatChangesAttr extends MoveAttr { +export class IgnoreOpponentStatStagesAttr extends MoveAttr { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { (args[0] as Utils.IntegerHolder).value = 0; @@ -2614,14 +2614,14 @@ export class StatStageChangeAttr extends MoveEffectAttr { export class PostVictoryStatStageChangeAttr extends MoveAttr { private stats: BattleStat[]; - private levels: integer; + private stages: number; private condition: MoveConditionFunc | null; private showMessage: boolean; - constructor(stats: BattleStat[], levels: integer, selfTarget?: boolean, condition?: MoveConditionFunc, showMessage: boolean = true, firstHitOnly: boolean = false) { + constructor(stats: BattleStat[], stages: number, selfTarget?: boolean, condition?: MoveConditionFunc, showMessage: boolean = true, firstHitOnly: boolean = false) { super(); this.stats = stats; - this.levels = levels; + this.stages = stages; this.condition = condition!; // TODO: is this bang correct? this.showMessage = showMessage; } @@ -2629,7 +2629,7 @@ export class PostVictoryStatStageChangeAttr extends MoveAttr { if (this.condition && !this.condition(user, target, move)) { return; } - const statChangeAttr = new StatStageChangeAttr(this.stats, this.levels, this.showMessage); + const statChangeAttr = new StatStageChangeAttr(this.stats, this.stages, this.showMessage); statChangeAttr.apply(user, target, move); } } @@ -3313,9 +3313,9 @@ export class HitCountPowerAttr extends VariablePowerAttr { } /** - * Turning a once was (StatChangeCountPowerAttr) statement and making it available to call for any attribute. - * @param {Pokemon} pokemon The pokemon that is being used to calculate the count of positive stats - * @returns {number} Returns the amount of positive stats + * Tallies the number of positive stages for a given {@linkcode Pokemon}. + * @param pokemon The {@linkcode Pokemon} that is being used to calculate the count of positive stats + * @returns the amount of positive stats */ const countPositiveStatStages = (pokemon: Pokemon): number => { return pokemon.getStatStages().reduce((total, stat) => (stat && stat > 0) ? total + stat : total, 0); @@ -6666,7 +6666,7 @@ export function initMoves() { .attr(ConfuseAttr), new SelfStatusMove(Moves.BELLY_DRUM, Type.NORMAL, -1, 10, -1, 0, 2) .attr(CutHpStatStageBoostAttr, [ Stat.ATK ], 12, 2, (user) => { - user.scene.queueMessage(i18next.t("moveTriggers:cutOwnHpAndMaximizedStat", { pokemonName: getPokemonNameWithAffix(user), statName: i18next.t(getStatKey(Stat.ATK)) })); // TODO: BattleStats + user.scene.queueMessage(i18next.t("moveTriggers:cutOwnHpAndMaximizedStat", { pokemonName: getPokemonNameWithAffix(user), statName: i18next.t(getStatKey(Stat.ATK)) })); }), new AttackMove(Moves.SLUDGE_BOMB, Type.POISON, MoveCategory.SPECIAL, 90, 100, 10, 30, 0, 2) .attr(StatusEffectAttr, StatusEffect.POISON) @@ -7544,7 +7544,7 @@ export function initMoves() { .attr(ConsecutiveUseMultiBasePowerAttr, 5, false) .soundBased(), new AttackMove(Moves.CHIP_AWAY, Type.NORMAL, MoveCategory.PHYSICAL, 70, 100, 20, -1, 0, 5) - .attr(IgnoreOpponentStatChangesAttr), + .attr(IgnoreOpponentStatStagesAttr), new AttackMove(Moves.CLEAR_SMOG, Type.POISON, MoveCategory.SPECIAL, 50, -1, 15, -1, 0, 5) .attr(ResetStatsAttr, false), new AttackMove(Moves.STORED_POWER, Type.PSYCHIC, MoveCategory.SPECIAL, 20, 100, 10, -1, 0, 5) @@ -7636,7 +7636,7 @@ export function initMoves() { .attr(HitHealAttr) .triageMove(), new AttackMove(Moves.SACRED_SWORD, Type.FIGHTING, MoveCategory.PHYSICAL, 90, 100, 15, -1, 0, 5) - .attr(IgnoreOpponentStatChangesAttr) + .attr(IgnoreOpponentStatStagesAttr) .slicingMove(), new AttackMove(Moves.RAZOR_SHELL, Type.WATER, MoveCategory.PHYSICAL, 75, 95, 10, 50, 0, 5) .attr(StatStageChangeAttr, [ Stat.DEF ], -1) @@ -8028,7 +8028,7 @@ export function initMoves() { .attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, false, 1, 1, true) .makesContact(false), new AttackMove(Moves.DARKEST_LARIAT, Type.DARK, MoveCategory.PHYSICAL, 85, 100, 10, -1, 0, 7) - .attr(IgnoreOpponentStatChangesAttr), + .attr(IgnoreOpponentStatStagesAttr), new AttackMove(Moves.SPARKLING_ARIA, Type.WATER, MoveCategory.SPECIAL, 90, 100, 10, 100, 0, 7) .attr(HealStatusEffectAttr, false, StatusEffect.BURN) .soundBased() diff --git a/src/data/pokemon-evolutions.ts b/src/data/pokemon-evolutions.ts index e29a4c16c29..0a12ac8190b 100644 --- a/src/data/pokemon-evolutions.ts +++ b/src/data/pokemon-evolutions.ts @@ -1,7 +1,7 @@ import { Gender } from "./gender"; import { PokeballType } from "./pokeball"; import Pokemon from "../field/pokemon"; -import { Stat } from "./pokemon-stat"; +import { Stat } from "#enums/stat"; import { Type } from "./type"; import * as Utils from "../utils"; import { SpeciesFormKey } from "./pokemon-species"; diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index 837cad4a0df..d4a32e64c4f 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -15,7 +15,7 @@ import { QuantizerCelebi, argbFromRgba, rgbaFromArgb } from "@material/material- import { VariantSet } from "./variant"; import i18next from "i18next"; import { Localizable } from "#app/interfaces/locales"; -import { Stat } from "./pokemon-stat"; +import { Stat } from "#enums/stat"; import { Abilities } from "#enums/abilities"; import { PartyMemberStrength } from "#enums/party-member-strength"; import { Species } from "#enums/species"; diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index e8792a22ad1..be92ab4d91e 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -3,7 +3,7 @@ import BattleScene, { AnySound } from "../battle-scene"; import { Variant, VariantSet, variantColorCache } from "#app/data/variant"; import { variantData } from "#app/data/variant"; import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from "../ui/battle-info"; -import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, VariableMoveTypeAttr, StatusMoveTypeImmunityAttr, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatChangesAttr, SacrificialAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatStageChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, MoveFlags, NeutralDamageAgainstFlyingTypeMultiplierAttr, OneHitKOAccuracyAttr } from "../data/move"; +import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, VariableMoveTypeAttr, StatusMoveTypeImmunityAttr, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatStagesAttr, SacrificialAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatStageChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, MoveFlags, NeutralDamageAgainstFlyingTypeMultiplierAttr, OneHitKOAccuracyAttr } from "../data/move"; import { default as PokemonSpecies, PokemonSpeciesForm, SpeciesFormKey, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm, getStarterValueFriendshipCap, speciesStarters, starterPassiveAbilities } from "../data/pokemon-species"; import { Constructor } from "#app/utils"; import * as Utils from "../utils"; @@ -21,7 +21,7 @@ import { DamagePhase, FaintPhase, LearnMovePhase, MoveEffectPhase, ObtainStatusE import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, ExposedTag } from "../data/battler-tags"; import { WeatherType } from "../data/weather"; import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "../data/arena-tag"; -import { Ability, AbAttr, StatStageMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStageChangesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyStatStageMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldStatMultiplierAbAttrs, FieldMultiplyStatAbAttr, AddSecondStrikeAbAttr, IgnoreOpponentEvasionAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr } from "../data/ability"; +import { Ability, AbAttr, StatStageMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyStatStageMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldStatMultiplierAbAttrs, FieldMultiplyStatAbAttr, AddSecondStrikeAbAttr, IgnoreOpponentEvasionAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr } from "../data/ability"; import PokemonData from "../system/pokemon-data"; import { BattlerIndex } from "../battle"; import { Mode } from "../ui/ui"; @@ -681,7 +681,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @returns the numeric value of the desired {@linkcode Stat} */ getStat(stat: PermanentStat, ignoreOverride: boolean = true): number { - if (!ignoreOverride && (this.summonData?.stats[stat] !== 0)) { + if (!ignoreOverride && this.summonData && (this.summonData.stats[stat] !== 0)) { return this.summonData.stats[stat]; } return this.stats[stat]; @@ -725,13 +725,17 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { /** * Writes the value to the in-battle stage of the corresponding {@linkcode BattleStat} of the {@linkcode Pokemon}. * - * Note that this does nothing if {@linkcode value} is less than -6 and greater than 6. + * Note that, if the value is not within a range of [-6, 6], it will be forced to the closest range bound. * @param stat the {@linkcode BattleStat} whose stage is to be overwritten * @param value the desired numeric value */ setStatStage(stat: BattleStat, value: number): void { - if ((value >= -6) && (value >= 6) && this.summonData) { - this.summonData.statStages[stat - 1] = value; + if (this.summonData) { + if (value >= -6) { + this.summonData.statStages[stat - 1] = Math.min(value, 6); + } else { + this.summonData.statStages[stat - 1] = Math.max(value, -6); + } } } @@ -750,9 +754,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { break; } } - applyAbAttrs(IgnoreOpponentStatStageChangesAbAttr, opponent, null, statLevel); + applyAbAttrs(IgnoreOpponentStatStagesAbAttr, opponent, null, statLevel); if (move) { - applyMoveAttrs(IgnoreOpponentStatChangesAttr, this, opponent, move, statLevel); + applyMoveAttrs(IgnoreOpponentStatStagesAttr, this, opponent, move, statLevel); } } if (this.isPlayer()) { @@ -1950,10 +1954,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { const userAccStage = new Utils.IntegerHolder(this.getStatStage(Stat.ACC)); const targetEvaStage = new Utils.IntegerHolder(target.getStatStage(Stat.EVA)); - applyAbAttrs(IgnoreOpponentStatStageChangesAbAttr, target, null, userAccStage); - applyAbAttrs(IgnoreOpponentStatStageChangesAbAttr, this, null, targetEvaStage); + applyAbAttrs(IgnoreOpponentStatStagesAbAttr, target, null, userAccStage); + applyAbAttrs(IgnoreOpponentStatStagesAbAttr, this, null, targetEvaStage); applyAbAttrs(IgnoreOpponentEvasionAbAttr, this, null, targetEvaStage); - applyMoveAttrs(IgnoreOpponentStatChangesAttr, this, target, sourceMove, targetEvaStage); + applyMoveAttrs(IgnoreOpponentStatStagesAttr, this, target, sourceMove, targetEvaStage); this.scene.applyModifiers(TempStatStageBoosterModifier, this.isPlayer(), Stat.ACC, userAccStage); if (target.findTag(t => t instanceof ExposedTag)) { @@ -1967,10 +1971,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { : 3 / (3 + Math.min(targetEvaStage.value - userAccStage.value, 6)); } - applyStatStageMultiplierAbAttrs(StatStageMultiplierAbAttr, this, Stat.ACC, accuracyMultiplier, sourceMove); // TODO: BattleStat + applyStatStageMultiplierAbAttrs(StatStageMultiplierAbAttr, this, Stat.ACC, accuracyMultiplier, sourceMove); const evasionMultiplier = new Utils.NumberHolder(1); - applyStatStageMultiplierAbAttrs(StatStageMultiplierAbAttr, target, Stat.EVA, evasionMultiplier); // TODO: BattleStat + applyStatStageMultiplierAbAttrs(StatStageMultiplierAbAttr, target, Stat.EVA, evasionMultiplier); accuracyMultiplier.value /= evasionMultiplier.value; diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 2fc535fd37d..ee266e68cd1 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -4,7 +4,7 @@ import BattleScene from "../battle-scene"; import { getLevelTotalExp } from "../data/exp"; import { MAX_PER_TYPE_POKEBALLS, PokeballType } from "../data/pokeball"; import Pokemon, { PlayerPokemon } from "../field/pokemon"; -import { Stat } from "../data/pokemon-stat"; +import { Stat } from "#enums/stat"; import { addTextObject, TextStyle } from "../ui/text"; import { Type } from "../data/type"; import { EvolutionPhase } from "../evolution-phase"; diff --git a/src/phases.ts b/src/phases.ts index 298f8065c3f..52da66e7443 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -3446,19 +3446,19 @@ export class ShowAbilityPhase extends PokemonPhase { } } -export type StatChangeCallback = (target: Pokemon | null, changed: BattleStat[], relativeChanges: number[]) => void; +export type StatStageChangeCallback = (target: Pokemon | null, changed: BattleStat[], relativeChanges: number[]) => void; -export class StatStageChangePhase extends PokemonPhase { // TODO: BattleStat +export class StatStageChangePhase extends PokemonPhase { private stats: BattleStat[]; private selfTarget: boolean; private stages: integer; private showMessage: boolean; private ignoreAbilities: boolean; private canBeCopied: boolean; - private onChange: StatChangeCallback | null; + private onChange: StatStageChangeCallback | null; - constructor(scene: BattleScene, battlerIndex: BattlerIndex, selfTarget: boolean, stats: BattleStat[], stages: integer, showMessage: boolean = true, ignoreAbilities: boolean = false, canBeCopied: boolean = true, onChange: StatChangeCallback | null = null) { + constructor(scene: BattleScene, battlerIndex: BattlerIndex, selfTarget: boolean, stats: BattleStat[], stages: integer, showMessage: boolean = true, ignoreAbilities: boolean = false, canBeCopied: boolean = true, onChange: StatStageChangeCallback | null = null) { super(scene, battlerIndex); this.selfTarget = selfTarget; @@ -3473,20 +3473,11 @@ export class StatStageChangePhase extends PokemonPhase { // TODO: BattleStat start() { const pokemon = this.getPokemon(); - let random = false; - - if (this.stats.length === 1 && this.stats[0] === BattleStat.RAND) { - this.stats[0] = this.getRandomStat(); - random = true; - } - - this.aggregateStatChanges(random); - if (!pokemon.isActive(true)) { return this.end(); } - const filteredStats = this.stats.map(s => s !== BattleStat.RAND ? s : this.getRandomStat()).filter(stat => { + const filteredStats = this.stats.filter(stat => { const cancelled = new Utils.BooleanHolder(false); if (!this.selfTarget && this.stages < 0) { @@ -3494,42 +3485,41 @@ export class StatStageChangePhase extends PokemonPhase { // TODO: BattleStat } if (!cancelled.value && !this.selfTarget && this.stages < 0) { - applyPreStatStageChangeAbAttrs(ProtectStatAbAttr, this.getPokemon(), stat, cancelled); + applyPreStatStageChangeAbAttrs(ProtectStatAbAttr, pokemon, stat, cancelled); } return !cancelled.value; }); - const levels = new Utils.IntegerHolder(this.stages); + const stages = new Utils.IntegerHolder(this.stages); if (!this.ignoreAbilities) { - applyAbAttrs(StatStageChangeMultiplierAbAttr, pokemon, null, levels); + applyAbAttrs(StatStageChangeMultiplierAbAttr, pokemon, null, stages); } - const battleStats = this.getPokemon().summonData.battleStats; - const relLevels = filteredStats.map(stat => (levels.value >= 1 ? Math.min(battleStats![stat] + levels.value, 6) : Math.max(battleStats![stat] + levels.value, -6)) - battleStats![stat]); + const relLevels = filteredStats.map(s => (stages.value >= 1 ? Math.min(pokemon.getStatStage(s) + stages.value, 6) : Math.max(pokemon.getStatStage(s) + stages.value, -6)) - pokemon.getStatStage(s)); this.onChange && this.onChange(this.getPokemon(), filteredStats, relLevels); const end = () => { if (this.showMessage) { - const messages = this.getStatChangeMessages(filteredStats, levels.value, relLevels); + const messages = this.getStatStageChangeMessages(filteredStats, stages.value, relLevels); for (const message of messages) { this.scene.queueMessage(message); } } - for (const stat of filteredStats) { - pokemon.summonData.battleStats[stat] = Math.max(Math.min(pokemon.summonData.battleStats[stat] + levels.value, 6), -6); + for (const s of filteredStats) { + pokemon.setStatStage(s, pokemon.getStatStage(s) + stages.value); } - if (levels.value > 0 && this.canBeCopied) { + if (stages.value > 0 && this.canBeCopied) { for (const opponent of pokemon.getOpponents()) { - applyAbAttrs(StatStageChangeCopyAbAttr, opponent, null, this.stats, levels.value); + applyAbAttrs(StatStageChangeCopyAbAttr, opponent, null, this.stats, stages.value); } } - applyPostStatStageChangeAbAttrs(PostStatStageChangeAbAttr, pokemon, filteredStats, this.levels, this.selfTarget); + applyPostStatStageChangeAbAttrs(PostStatStageChangeAbAttr, pokemon, filteredStats, this.stages, this.selfTarget); // Look for any other stat change phases; if this is the last one, do White Herb check const existingPhase = this.scene.findPhase(p => p instanceof StatStageChangePhase && p.battlerIndex === this.battlerIndex); @@ -3556,20 +3546,20 @@ export class StatStageChangePhase extends PokemonPhase { // TODO: BattleStat const pokemonMaskSprite = pokemon.maskSprite; const tileX = (this.player ? 106 : 236) * pokemon.getSpriteScale() * this.scene.field.scale; - const tileY = ((this.player ? 148 : 84) + (levels.value >= 1 ? 160 : 0)) * pokemon.getSpriteScale() * this.scene.field.scale; + const tileY = ((this.player ? 148 : 84) + (stages.value >= 1 ? 160 : 0)) * pokemon.getSpriteScale() * this.scene.field.scale; const tileWidth = 156 * this.scene.field.scale * pokemon.getSpriteScale(); const tileHeight = 316 * this.scene.field.scale * pokemon.getSpriteScale(); // On increase, show the red sprite located at ATK // On decrease, show the blue sprite located at SPD - const spriteColor = levels.value >= 1 ? Stat[Stat.ATK].toLowerCase() : Stat[Stat.SPD].toLowerCase(); + const spriteColor = stages.value >= 1 ? Stat[Stat.ATK].toLowerCase() : Stat[Stat.SPD].toLowerCase(); const statSprite = this.scene.add.tileSprite(tileX, tileY, tileWidth, tileHeight, "battle_stats", spriteColor); statSprite.setPipeline(this.scene.fieldSpritePipeline); statSprite.setAlpha(0); statSprite.setScale(6); statSprite.setOrigin(0.5, 1); - this.scene.playSound(`stat_${levels.value >= 1 ? "up" : "down"}`); + this.scene.playSound(`stat_${stages.value >= 1 ? "up" : "down"}`); statSprite.setMask(new Phaser.Display.Masks.BitmapMask(this.scene, pokemonMaskSprite ?? undefined)); @@ -3590,7 +3580,7 @@ export class StatStageChangePhase extends PokemonPhase { // TODO: BattleStat this.scene.tweens.add({ targets: statSprite, duration: 1500, - y: `${levels.value >= 1 ? "-" : "+"}=${160 * 6}` + y: `${stages.value >= 1 ? "-" : "+"}=${160 * 6}` }); this.scene.time.delayedCall(1750, () => { @@ -3602,34 +3592,24 @@ export class StatStageChangePhase extends PokemonPhase { // TODO: BattleStat } } - getRandomStat(): BattleStat { - const allStats = Utils.getEnumValues(BattleStat); - return this.getPokemon() ? allStats[this.getPokemon()!.randSeedInt(BattleStat.SPD + 1)] : BattleStat.ATK; // TODO: return default ATK on random? idk... - } - - aggregateStatChanges(random: boolean = false): void { - const isAccEva = [BattleStat.ACC, BattleStat.EVA].some(s => this.stats.includes(s)); - let existingPhase: StatChangePhase; + aggregateStatStageChanges(): void { + const accEva: BattleStat[] = [ Stat.ACC, Stat.EVA ]; + const isAccEva = accEva.some(s => this.stats.includes(s)); + let existingPhase: StatStageChangePhase; if (this.stats.length === 1) { - while ((existingPhase = (this.scene.findPhase(p => p instanceof StatChangePhase && p.battlerIndex === this.battlerIndex && p.stats.length === 1 - && (p.stats[0] === this.stats[0] || (random && p.stats[0] === BattleStat.RAND)) - && p.selfTarget === this.selfTarget && p.showMessage === this.showMessage && p.ignoreAbilities === this.ignoreAbilities) as StatChangePhase))) { - if (existingPhase.stats[0] === BattleStat.RAND) { - existingPhase.stats[0] = this.getRandomStat(); - if (existingPhase.stats[0] !== this.stats[0]) { - continue; - } - } - this.levels += existingPhase.levels; + while ((existingPhase = (this.scene.findPhase(p => p instanceof StatStageChangePhase && p.battlerIndex === this.battlerIndex && p.stats.length === 1 + && (p.stats[0] === this.stats[0]) + && p.selfTarget === this.selfTarget && p.showMessage === this.showMessage && p.ignoreAbilities === this.ignoreAbilities) as StatStageChangePhase))) { + this.stages += existingPhase.stages; if (!this.scene.tryRemovePhase(p => p === existingPhase)) { break; } } } - while ((existingPhase = (this.scene.findPhase(p => p instanceof StatChangePhase && p.battlerIndex === this.battlerIndex && p.selfTarget === this.selfTarget - && ([BattleStat.ACC, BattleStat.EVA].some(s => p.stats.includes(s)) === isAccEva) - && p.levels === this.levels && p.showMessage === this.showMessage && p.ignoreAbilities === this.ignoreAbilities) as StatChangePhase))) { + while ((existingPhase = (this.scene.findPhase(p => p instanceof StatStageChangePhase && p.battlerIndex === this.battlerIndex && p.selfTarget === this.selfTarget + && (accEva.some(s => p.stats.includes(s)) === isAccEva) + && p.stages === this.stages && p.showMessage === this.showMessage && p.ignoreAbilities === this.ignoreAbilities) as StatStageChangePhase))) { this.stats.push(...existingPhase.stats); if (!this.scene.tryRemovePhase(p => p === existingPhase)) { break; @@ -3637,37 +3617,37 @@ export class StatStageChangePhase extends PokemonPhase { // TODO: BattleStat } } - getStatChangeMessages(stats: BattleStat[], levels: integer, relLevels: integer[]): string[] { + getStatStageChangeMessages(stats: BattleStat[], stages: integer, relStages: integer[]): string[] { const messages: string[] = []; - const relLevelStatIndexes = {}; - for (let rl = 0; rl < relLevels.length; rl++) { - const relLevel = relLevels[rl]; - if (!relLevelStatIndexes[relLevel]) { - relLevelStatIndexes[relLevel] = []; + const relStageStatIndexes = {}; + for (let rl = 0; rl < relStages.length; rl++) { + const relStage = relStages[rl]; + if (!relStageStatIndexes[relStage]) { + relStageStatIndexes[relStage] = []; } - relLevelStatIndexes[relLevel].push(rl); + relStageStatIndexes[relStage].push(rl); } - Object.keys(relLevelStatIndexes).forEach(rl => { - const relLevelStats = stats.filter((_, i) => relLevelStatIndexes[rl].includes(i)); + Object.keys(relStageStatIndexes).forEach(rl => { + const relStageStats = stats.filter((_, i) => relStageStatIndexes[rl].includes(i)); let statsFragment = ""; - if (relLevelStats.length > 1) { - statsFragment = relLevelStats.length >= 5 + if (relStageStats.length > 1) { + statsFragment = relStageStats.length >= 5 ? i18next.t("battle:stats") - : `${relLevelStats.slice(0, -1).map(s => i18next.t(getStatKey(s))).join(", ")}${relLevelStats.length > 2 ? "," : ""} ${i18next.t("battle:statsAnd")} ${i18next.t(getStatKey(relLevelStats[relLevelStats.length - 1]))}`; - messages.push(i18next.t(getStatStageChangeDescriptionKey(Math.abs(parseInt(rl)), levels >= 1), { + : `${relStageStats.slice(0, -1).map(s => i18next.t(getStatKey(s))).join(", ")}${relStageStats.length > 2 ? "," : ""} ${i18next.t("battle:statsAnd")} ${i18next.t(getStatKey(relStageStats[relStageStats.length - 1]))}`; + messages.push(i18next.t(getStatStageChangeDescriptionKey(Math.abs(parseInt(rl)), stages >= 1), { pokemonNameWithAffix: getPokemonNameWithAffix(this.getPokemon()), stats: statsFragment, - count: relLevelStats.length + count: relStageStats.length })); } else { - statsFragment = i18next.t(getStatKey(relLevelStats[0])); - messages.push(i18next.t(getStatStageChangeDescriptionKey(Math.abs(parseInt(rl)), levels >= 1), { + statsFragment = i18next.t(getStatKey(relStageStats[0])); + messages.push(i18next.t(getStatStageChangeDescriptionKey(Math.abs(parseInt(rl)), stages >= 1), { pokemonNameWithAffix: getPokemonNameWithAffix(this.getPokemon()), stats: statsFragment, - count: relLevelStats.length + count: relStageStats.length })); } }); diff --git a/src/test/abilities/intimidate.test.ts b/src/test/abilities/intimidate.test.ts index 287a049a337..4195a4580a0 100644 --- a/src/test/abilities/intimidate.test.ts +++ b/src/test/abilities/intimidate.test.ts @@ -81,7 +81,7 @@ describe("Abilities - Intimidate", () => { await game.phaseInterceptor.to(CommandPhase, false); const playerField = game.scene.getPlayerField()!; - const enemyField = game.scene.getEnemyPokemon()!; + const enemyField = game.scene.getEnemyField()!; expect(enemyField[0].getStatStage(Stat.ATK)).toBe(-2); expect(enemyField[1].getStatStage(Stat.ATK)).toBe(-2); diff --git a/src/test/abilities/moxie.test.ts b/src/test/abilities/moxie.test.ts index 4108f00ad5a..487b8732fa4 100644 --- a/src/test/abilities/moxie.test.ts +++ b/src/test/abilities/moxie.test.ts @@ -53,7 +53,8 @@ describe("Abilities - Moxie", () => { expect(playerPokemon.getStatStage(Stat.ATK)).toBe(1); }, 20000); - it("should raise ATK stat stage by 1 when defeating an ally Pokemon", async() => { + // TODO: Activate this test when MOXIE is corrected to work faint and not battle victory + it.todo("should raise ATK stat stage by 1 when defeating an ally Pokemon", async() => { game.override.battleType("double"); const moveToUse = Moves.AERIAL_ACE; await game.startBattle([ diff --git a/src/test/abilities/serene_grace.test.ts b/src/test/abilities/serene_grace.test.ts index d46587e45c7..73b88ddf827 100644 --- a/src/test/abilities/serene_grace.test.ts +++ b/src/test/abilities/serene_grace.test.ts @@ -1,5 +1,5 @@ import { applyAbAttrs, MoveEffectChanceMultiplierAbAttr } from "#app/data/ability"; -import { Stat } from "#app/data/pokemon-stat"; +import { Stat } from "#enums/stat"; import { CommandPhase, MoveEffectPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; diff --git a/src/test/abilities/sheer_force.test.ts b/src/test/abilities/sheer_force.test.ts index 50a0f0b63fb..7b9d238ec6e 100644 --- a/src/test/abilities/sheer_force.test.ts +++ b/src/test/abilities/sheer_force.test.ts @@ -1,5 +1,5 @@ import { applyAbAttrs, applyPostDefendAbAttrs, applyPreAttackAbAttrs, MoveEffectChanceMultiplierAbAttr, MovePowerBoostAbAttr, PostDefendTypeChangeAbAttr } from "#app/data/ability"; -import { Stat } from "#app/data/pokemon-stat"; +import { Stat } from "#enums/stat"; import { CommandPhase, MoveEffectPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; diff --git a/src/test/abilities/shield_dust.test.ts b/src/test/abilities/shield_dust.test.ts index f1534551e92..ab43888583d 100644 --- a/src/test/abilities/shield_dust.test.ts +++ b/src/test/abilities/shield_dust.test.ts @@ -1,5 +1,5 @@ import { applyAbAttrs, applyPreDefendAbAttrs, IgnoreMoveEffectsAbAttr, MoveEffectChanceMultiplierAbAttr } from "#app/data/ability"; -import { Stat } from "#app/data/pokemon-stat"; +import { Stat } from "#enums/stat"; import { CommandPhase, MoveEffectPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; diff --git a/src/test/abilities/zen_mode.test.ts b/src/test/abilities/zen_mode.test.ts index 1bc7a6af4ce..ff0494e4ec7 100644 --- a/src/test/abilities/zen_mode.test.ts +++ b/src/test/abilities/zen_mode.test.ts @@ -1,4 +1,4 @@ -import { Stat } from "#app/data/pokemon-stat"; +import { Stat } from "#enums/stat"; import { Status, StatusEffect } from "#app/data/status-effect.js"; import { QuietFormChangePhase } from "#app/form-change-phase"; import { CommandPhase, DamagePhase, EnemyCommandPhase, MessagePhase, PostSummonPhase, SwitchPhase, SwitchSummonPhase, TurnEndPhase, TurnInitPhase, TurnStartPhase } from "#app/phases"; diff --git a/src/test/battle-stat.spec.ts b/src/test/battle-stat.spec.ts deleted file mode 100644 index 76040094235..00000000000 --- a/src/test/battle-stat.spec.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { BattleStat, getStatKey } from "#enums/stat"; -import { describe, expect, it } from "vitest"; -import { arrayOfRange, mockI18next } from "./utils/testUtils"; - -const TEST_BATTLE_STAT = -99 as BattleStat; -const TEST_POKEMON = "Testmon"; -const TEST_STAT = "Teststat"; - -describe("battle-stat", () => { - describe("getBattleStatName", () => { - it("should return the correct name for each BattleStat", () => { - mockI18next(); - - expect(getBattleStatName(BattleStat.ATK)).toBe("pokemonInfo:Stat.ATK"); - expect(getBattleStatName(BattleStat.DEF)).toBe("pokemonInfo:Stat.DEF"); - expect(getBattleStatName(BattleStat.SPATK)).toBe( - "pokemonInfo:Stat.SPATK" - ); - expect(getBattleStatName(BattleStat.SPDEF)).toBe( - "pokemonInfo:Stat.SPDEF" - ); - expect(getBattleStatName(BattleStat.SPD)).toBe("pokemonInfo:Stat.SPD"); - expect(getBattleStatName(BattleStat.ACC)).toBe("pokemonInfo:Stat.ACC"); - expect(getBattleStatName(BattleStat.EVA)).toBe("pokemonInfo:Stat.EVA"); - }); - - it("should fall back to ??? for an unknown BattleStat", () => { - expect(getStatKey(TEST_BATTLE_STAT)).toBe("???"); - }); - }); - - describe("getBattleStatLevelChangeDescription", () => { - it("should return battle:statRose for +1", () => { - mockI18next(); - - const message = getBattleStatLevelChangeDescription( - TEST_POKEMON, - TEST_STAT, - 1, - true - ); - - expect(message).toBe("battle:statRose"); - }); - - it("should return battle:statSharplyRose for +2", () => { - mockI18next(); - - const message = getBattleStatLevelChangeDescription( - TEST_POKEMON, - TEST_STAT, - 2, - true - ); - - expect(message).toBe("battle:statSharplyRose"); - }); - - it("should return battle:statRoseDrastically for +3 to +6", () => { - mockI18next(); - - arrayOfRange(3, 6).forEach((n) => { - const message = getBattleStatLevelChangeDescription( - TEST_POKEMON, - TEST_STAT, - n, - true - ); - - expect(message).toBe("battle:statRoseDrastically"); - }); - }); - - it("should return battle:statWontGoAnyHigher for 7 or higher", () => { - mockI18next(); - - arrayOfRange(7, 10).forEach((n) => { - const message = getBattleStatLevelChangeDescription( - TEST_POKEMON, - TEST_STAT, - n, - true - ); - - expect(message).toBe("battle:statWontGoAnyHigher"); - }); - }); - - it("should return battle:statFell for -1", () => { - mockI18next(); - - const message = getBattleStatLevelChangeDescription( - TEST_POKEMON, - TEST_STAT, - 1, - false - ); - - expect(message).toBe("battle:statFell"); - }); - - it("should return battle:statHarshlyFell for -2", () => { - mockI18next(); - - const message = getBattleStatLevelChangeDescription( - TEST_POKEMON, - TEST_STAT, - 2, - false - ); - - expect(message).toBe("battle:statHarshlyFell"); - }); - - it("should return battle:statSeverelyFell for -3 to -6", () => { - mockI18next(); - - arrayOfRange(3, 6).forEach((n) => { - const message = getBattleStatLevelChangeDescription( - TEST_POKEMON, - TEST_STAT, - n, - false - ); - - expect(message).toBe("battle:statSeverelyFell"); - }); - }); - - it("should return battle:statWontGoAnyLower for -7 or lower", () => { - mockI18next(); - - arrayOfRange(7, 10).forEach((n) => { - const message = getBattleStatLevelChangeDescription( - TEST_POKEMON, - TEST_STAT, - n, - false - ); - - expect(message).toBe("battle:statWontGoAnyLower"); - }); - }); - }); -}); diff --git a/src/test/battle/battle-order.test.ts b/src/test/battle/battle-order.test.ts index 6aa919186b4..f24114fb737 100644 --- a/src/test/battle/battle-order.test.ts +++ b/src/test/battle/battle-order.test.ts @@ -1,4 +1,4 @@ -import { Stat } from "#app/data/pokemon-stat"; +import { Stat } from "#enums/stat"; import { CommandPhase, EnemyCommandPhase, SelectTargetPhase, TurnStartPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; diff --git a/src/test/battlerTags/octolock.test.ts b/src/test/battlerTags/octolock.test.ts index 7d49850c584..afb26719915 100644 --- a/src/test/battlerTags/octolock.test.ts +++ b/src/test/battlerTags/octolock.test.ts @@ -2,7 +2,7 @@ import { describe, expect, it, vi } from "vitest"; import Pokemon from "#app/field/pokemon.js"; import BattleScene from "#app/battle-scene.js"; import { BattlerTag, BattlerTagLapseType, OctolockTag, TrappedTag } from "#app/data/battler-tags.js"; -import { StatChangePhase } from "#app/phases.js"; +import { StatStageChangePhase } from "#app/phases.js"; import { BattlerTagType } from "#app/enums/battler-tag-type.js"; import { Stat } from "#enums/stat"; @@ -10,7 +10,7 @@ vi.mock("#app/battle-scene.js"); describe("BattlerTag - OctolockTag", () => { describe("lapse behavior", () => { - it("unshifts a StatChangePhase with expected stat stage changes", { timeout: 10000 }, async () => { + it("unshifts a StatStageChangePhase with expected stat stage changes", { timeout: 10000 }, async () => { const mockPokemon = { scene: new BattleScene(), getBattlerIndex: () => 0, @@ -19,9 +19,9 @@ describe("BattlerTag - OctolockTag", () => { const subject = new OctolockTag(1); vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementation(phase => { - expect(phase).toBeInstanceOf(StatChangePhase); - expect((phase as StatChangePhase)["stages"]).toEqual(-1); - expect((phase as StatChangePhase)["stats"]).toEqual([ Stat.DEF, Stat.SPDEF ]); + expect(phase).toBeInstanceOf(StatStageChangePhase); + expect((phase as StatStageChangePhase)["stages"]).toEqual(-1); + expect((phase as StatStageChangePhase)["stats"]).toEqual([ Stat.DEF, Stat.SPDEF ]); }); subject.lapse(mockPokemon, BattlerTagLapseType.TURN_END); diff --git a/src/test/battlerTags/stockpiling.test.ts b/src/test/battlerTags/stockpiling.test.ts index 340c9528633..be5693473b1 100644 --- a/src/test/battlerTags/stockpiling.test.ts +++ b/src/test/battlerTags/stockpiling.test.ts @@ -2,7 +2,7 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; import Pokemon, { PokemonSummonData } from "#app/field/pokemon.js"; import BattleScene from "#app/battle-scene.js"; import { StockpilingTag } from "#app/data/battler-tags.js"; -import { StatChangePhase } from "#app/phases.js"; +import { StatStageChangePhase } from "#app/phases.js"; import { Stat } from "#enums/stat"; import * as messages from "#app/messages.js"; @@ -12,7 +12,7 @@ beforeEach(() => { describe("BattlerTag - StockpilingTag", () => { describe("onAdd", () => { - it("unshifts a StatChangePhase with expected stat stage changes on add", { timeout: 10000 }, async () => { + it("unshifts a StatStageChangePhase with expected stat stage changes on add", { timeout: 10000 }, async () => { const mockPokemon = { scene: vi.mocked(new BattleScene()) as BattleScene, getBattlerIndex: () => 0, @@ -23,11 +23,11 @@ describe("BattlerTag - StockpilingTag", () => { const subject = new StockpilingTag(1); vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementation(phase => { - expect(phase).toBeInstanceOf(StatChangePhase); - expect((phase as StatChangePhase)["stages"]).toEqual(1); - expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([ Stat.DEF, Stat.SPDEF ])); + expect(phase).toBeInstanceOf(StatStageChangePhase); + expect((phase as StatStageChangePhase)["stages"]).toEqual(1); + expect((phase as StatStageChangePhase)["stats"]).toEqual(expect.arrayContaining([ Stat.DEF, Stat.SPDEF ])); - (phase as StatChangePhase)["onChange"]!(mockPokemon, [Stat.DEF, Stat.SPDEF], [1, 1]); + (phase as StatStageChangePhase)["onChange"]!(mockPokemon, [Stat.DEF, Stat.SPDEF], [1, 1]); }); subject.onAdd(mockPokemon); @@ -35,7 +35,7 @@ describe("BattlerTag - StockpilingTag", () => { expect(mockPokemon.scene.unshiftPhase).toBeCalledTimes(1); }); - it("unshifts a StatChangePhase with expected stat changes on add (one stat maxed)", { timeout: 10000 }, async () => { + it("unshifts a StatStageChangePhase with expected stat changes on add (one stat maxed)", { timeout: 10000 }, async () => { const mockPokemon = { scene: new BattleScene(), summonData: new PokemonSummonData(), @@ -44,17 +44,17 @@ describe("BattlerTag - StockpilingTag", () => { vi.spyOn(mockPokemon.scene, "queueMessage").mockImplementation(() => {}); - mockPokemon.setStatStage(Stat.DEF, 6); - mockPokemon.setStatStage(Stat.SPDEF, 5); + mockPokemon.summonData.statStages[Stat.DEF - 1] = 6; + mockPokemon.summonData.statStages[Stat.SPD - 1] = 5; const subject = new StockpilingTag(1); vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementation(phase => { - expect(phase).toBeInstanceOf(StatChangePhase); - expect((phase as StatChangePhase)["stages"]).toEqual(1); - expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([Stat.DEF, Stat.SPDEF])); + expect(phase).toBeInstanceOf(StatStageChangePhase); + expect((phase as StatStageChangePhase)["stages"]).toEqual(1); + expect((phase as StatStageChangePhase)["stats"]).toEqual(expect.arrayContaining([Stat.DEF, Stat.SPDEF])); - (phase as StatChangePhase)["onChange"]!(mockPokemon, [ Stat.DEF, Stat.SPDEF ], [1, 1]); + (phase as StatStageChangePhase)["onChange"]!(mockPokemon, [ Stat.DEF, Stat.SPDEF ], [1, 1]); }); subject.onAdd(mockPokemon); @@ -64,7 +64,7 @@ describe("BattlerTag - StockpilingTag", () => { }); describe("onOverlap", () => { - it("unshifts a StatChangePhase with expected stat changes on overlap", { timeout: 10000 }, async () => { + it("unshifts a StatStageChangePhase with expected stat changes on overlap", { timeout: 10000 }, async () => { const mockPokemon = { scene: new BattleScene(), getBattlerIndex: () => 0, @@ -75,11 +75,11 @@ describe("BattlerTag - StockpilingTag", () => { const subject = new StockpilingTag(1); vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementation(phase => { - expect(phase).toBeInstanceOf(StatChangePhase); - expect((phase as StatChangePhase)["stages"]).toEqual(1); - expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([ Stat.DEF, Stat.SPDEF ])); + expect(phase).toBeInstanceOf(StatStageChangePhase); + expect((phase as StatStageChangePhase)["stages"]).toEqual(1); + expect((phase as StatStageChangePhase)["stats"]).toEqual(expect.arrayContaining([ Stat.DEF, Stat.SPDEF ])); - (phase as StatChangePhase)["onChange"]!(mockPokemon, [ Stat.DEF, Stat.SPDEF ], [1, 1]); + (phase as StatStageChangePhase)["onChange"]!(mockPokemon, [ Stat.DEF, Stat.SPDEF ], [1, 1]); }); subject.onOverlap(mockPokemon); @@ -98,39 +98,39 @@ describe("BattlerTag - StockpilingTag", () => { vi.spyOn(mockPokemon.scene, "queueMessage").mockImplementation(() => {}); - mockPokemon.setStatStage(Stat.DEF, 5); - mockPokemon.setStatStage(Stat.SPDEF, 4); + mockPokemon.summonData.statStages[Stat.DEF - 1] = 5; + mockPokemon.summonData.statStages[Stat.SPD - 1] = 4; const subject = new StockpilingTag(1); vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementationOnce(phase => { - expect(phase).toBeInstanceOf(StatChangePhase); - expect((phase as StatChangePhase)["stages"]).toEqual(1); - expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([ Stat.DEF, Stat.SPDEF ])); + expect(phase).toBeInstanceOf(StatStageChangePhase); + expect((phase as StatStageChangePhase)["stages"]).toEqual(1); + expect((phase as StatStageChangePhase)["stats"]).toEqual(expect.arrayContaining([ Stat.DEF, Stat.SPDEF ])); // def doesn't change - (phase as StatChangePhase)["onChange"]!(mockPokemon, [ Stat.SPDEF ], [1]); + (phase as StatStageChangePhase)["onChange"]!(mockPokemon, [ Stat.SPDEF ], [1]); }); subject.onAdd(mockPokemon); expect(subject.stockpiledCount).toBe(1); vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementationOnce(phase => { - expect(phase).toBeInstanceOf(StatChangePhase); - expect((phase as StatChangePhase)["stages"]).toEqual(1); - expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([ Stat.DEF, Stat.SPDEF ])); + expect(phase).toBeInstanceOf(StatStageChangePhase); + expect((phase as StatStageChangePhase)["stages"]).toEqual(1); + expect((phase as StatStageChangePhase)["stats"]).toEqual(expect.arrayContaining([ Stat.DEF, Stat.SPDEF ])); // def doesn't change - (phase as StatChangePhase)["onChange"]!(mockPokemon, [ Stat.SPDEF ], [1]); + (phase as StatStageChangePhase)["onChange"]!(mockPokemon, [ Stat.SPDEF ], [1]); }); subject.onOverlap(mockPokemon); expect(subject.stockpiledCount).toBe(2); vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementationOnce(phase => { - expect(phase).toBeInstanceOf(StatChangePhase); - expect((phase as StatChangePhase)["stages"]).toEqual(1); - expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([ Stat.DEF, Stat.SPDEF ])); + expect(phase).toBeInstanceOf(StatStageChangePhase); + expect((phase as StatStageChangePhase)["stages"]).toEqual(1); + expect((phase as StatStageChangePhase)["stats"]).toEqual(expect.arrayContaining([ Stat.DEF, Stat.SPDEF ])); // neither stat changes, stack count should still increase }); @@ -149,9 +149,9 @@ describe("BattlerTag - StockpilingTag", () => { // removing tag should reverse stat changes vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementationOnce(phase => { - expect(phase).toBeInstanceOf(StatChangePhase); - expect((phase as StatChangePhase)["stages"]).toEqual(-2); - expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([Stat.SPDEF])); + expect(phase).toBeInstanceOf(StatStageChangePhase); + expect((phase as StatStageChangePhase)["stages"]).toEqual(-2); + expect((phase as StatStageChangePhase)["stats"]).toEqual(expect.arrayContaining([Stat.SPDEF])); }); subject.onRemove(mockPokemon); diff --git a/src/test/items/eviolite.test.ts b/src/test/items/eviolite.test.ts index 88f225d1b5f..af5cec80fa2 100644 --- a/src/test/items/eviolite.test.ts +++ b/src/test/items/eviolite.test.ts @@ -1,4 +1,4 @@ -import { Stat } from "#app/data/pokemon-stat"; +import { Stat } from "#enums/stat"; import { EvolutionStatBoosterModifier } from "#app/modifier/modifier"; import { modifierTypes } from "#app/modifier/modifier-type"; import i18next from "#app/plugins/i18n"; diff --git a/src/test/items/light_ball.test.ts b/src/test/items/light_ball.test.ts index 61ac99d7910..4d98ca65b97 100644 --- a/src/test/items/light_ball.test.ts +++ b/src/test/items/light_ball.test.ts @@ -1,4 +1,4 @@ -import { Stat } from "#app/data/pokemon-stat"; +import { Stat } from "#enums/stat"; import { SpeciesStatBoosterModifier } from "#app/modifier/modifier"; import { modifierTypes } from "#app/modifier/modifier-type"; import i18next from "#app/plugins/i18n"; diff --git a/src/test/items/metal_powder.test.ts b/src/test/items/metal_powder.test.ts index 1ce24446757..cd180572aee 100644 --- a/src/test/items/metal_powder.test.ts +++ b/src/test/items/metal_powder.test.ts @@ -1,4 +1,4 @@ -import { Stat } from "#app/data/pokemon-stat"; +import { Stat } from "#enums/stat"; import { SpeciesStatBoosterModifier } from "#app/modifier/modifier"; import { modifierTypes } from "#app/modifier/modifier-type"; import i18next from "#app/plugins/i18n"; diff --git a/src/test/items/quick_powder.test.ts b/src/test/items/quick_powder.test.ts index 6f165667fad..430b4dff535 100644 --- a/src/test/items/quick_powder.test.ts +++ b/src/test/items/quick_powder.test.ts @@ -1,4 +1,4 @@ -import { Stat } from "#app/data/pokemon-stat"; +import { Stat } from "#enums/stat"; import { SpeciesStatBoosterModifier } from "#app/modifier/modifier"; import { modifierTypes } from "#app/modifier/modifier-type"; import i18next from "#app/plugins/i18n"; diff --git a/src/test/items/thick_club.test.ts b/src/test/items/thick_club.test.ts index c805626790c..9a430d52d02 100644 --- a/src/test/items/thick_club.test.ts +++ b/src/test/items/thick_club.test.ts @@ -1,4 +1,4 @@ -import { Stat } from "#app/data/pokemon-stat"; +import { Stat } from "#enums/stat"; import { SpeciesStatBoosterModifier } from "#app/modifier/modifier"; import { modifierTypes } from "#app/modifier/modifier-type"; import i18next from "#app/plugins/i18n"; diff --git a/src/test/localization/battle-stat.test.ts b/src/test/localization/battle-stat.test.ts deleted file mode 100644 index b99ed2b7064..00000000000 --- a/src/test/localization/battle-stat.test.ts +++ /dev/null @@ -1,212 +0,0 @@ -import { beforeAll, describe, expect, it } from "vitest"; -import { getBattleStatName, getBattleStatLevelChangeDescription } from "#app/data/battle-stat.js"; -import { BattleStat} from "#app/data/battle-stat.js"; -import { pokemonInfo as enPokemonInfo } from "#app/locales/en/pokemon-info.js"; -import { battle as enBattleStat } from "#app/locales/en/battle.js"; -import { pokemonInfo as dePokemonInfo } from "#app/locales/de/pokemon-info.js"; -import { battle as deBattleStat } from "#app/locales/de/battle.js"; -import { pokemonInfo as esPokemonInfo } from "#app/locales/es/pokemon-info.js"; -import { battle as esBattleStat } from "#app/locales/es/battle.js"; -import { pokemonInfo as frPokemonInfo } from "#app/locales/fr/pokemon-info.js"; -import { battle as frBattleStat } from "#app/locales/fr/battle.js"; -import { pokemonInfo as itPokemonInfo } from "#app/locales/it/pokemon-info.js"; -import { battle as itBattleStat } from "#app/locales/it/battle.js"; -import { pokemonInfo as koPokemonInfo } from "#app/locales/ko/pokemon-info.js"; -import { battle as koBattleStat } from "#app/locales/ko/battle.js"; -import { pokemonInfo as ptBrPokemonInfo } from "#app/locales/pt_BR/pokemon-info.js"; -import { battle as ptBrBattleStat } from "#app/locales/pt_BR/battle.js"; -import { pokemonInfo as zhCnPokemonInfo } from "#app/locales/zh_CN/pokemon-info.js"; -import { battle as zhCnBattleStat } from "#app/locales/zh_CN/battle.js"; -import { pokemonInfo as zhTwPokemonInfo } from "#app/locales/zh_TW/pokemon-info.js"; -import { battle as zhTwBattleStat } from "#app/locales/zh_TW/battle.js"; -import i18next, { initI18n } from "#app/plugins/i18n"; -import { KoreanPostpositionProcessor } from "i18next-korean-postposition-processor"; - -interface BattleStatTestUnit { - stat: BattleStat, - key: string -} - -interface BattleStatLevelTestUnit { - levels: integer, - up: boolean, - key: string - changedStats: integer -} - -function testBattleStatName(stat: BattleStat, expectMessage: string) { - const message = getBattleStatName(stat); - console.log(`message ${message}, expected ${expectMessage}`); - expect(message).toBe(expectMessage); -} - -function testBattleStatLevelChangeDescription(levels: integer, up: boolean, expectMessage: string, changedStats: integer) { - const message = getBattleStatLevelChangeDescription("{{pokemonNameWithAffix}}", "{{stats}}", levels, up, changedStats); - console.log(`message ${message}, expected ${expectMessage}`); - expect(message).toBe(expectMessage); -} - -describe("Test for BattleStat Localization", () => { - const battleStatUnits: BattleStatTestUnit[] = []; - const battleStatLevelUnits: BattleStatLevelTestUnit[] = []; - - beforeAll(() => { - initI18n(); - - battleStatUnits.push({stat: BattleStat.ATK, key: "Stat.ATK"}); - battleStatUnits.push({stat: BattleStat.DEF, key: "Stat.DEF"}); - battleStatUnits.push({stat: BattleStat.SPATK, key: "Stat.SPATK"}); - battleStatUnits.push({stat: BattleStat.SPDEF, key: "Stat.SPDEF"}); - battleStatUnits.push({stat: BattleStat.SPD, key: "Stat.SPD"}); - battleStatUnits.push({stat: BattleStat.ACC, key: "Stat.ACC"}); - battleStatUnits.push({stat: BattleStat.EVA, key: "Stat.EVA"}); - - battleStatLevelUnits.push({levels: 1, up: true, key: "statRose_one", changedStats: 1}); - battleStatLevelUnits.push({levels: 2, up: true, key: "statSharplyRose_one", changedStats: 1}); - battleStatLevelUnits.push({levels: 3, up: true, key: "statRoseDrastically_one", changedStats: 1}); - battleStatLevelUnits.push({levels: 4, up: true, key: "statRoseDrastically_one", changedStats: 1}); - battleStatLevelUnits.push({levels: 5, up: true, key: "statRoseDrastically_one", changedStats: 1}); - battleStatLevelUnits.push({levels: 6, up: true, key: "statRoseDrastically_one", changedStats: 1}); - battleStatLevelUnits.push({levels: 7, up: true, key: "statWontGoAnyHigher_one", changedStats: 1}); - battleStatLevelUnits.push({levels: 1, up: false, key: "statFell_one", changedStats: 1}); - battleStatLevelUnits.push({levels: 2, up: false, key: "statHarshlyFell_one", changedStats: 1}); - battleStatLevelUnits.push({levels: 3, up: false, key: "statSeverelyFell_one", changedStats: 1}); - battleStatLevelUnits.push({levels: 4, up: false, key: "statSeverelyFell_one", changedStats: 1}); - battleStatLevelUnits.push({levels: 5, up: false, key: "statSeverelyFell_one", changedStats: 1}); - battleStatLevelUnits.push({levels: 6, up: false, key: "statSeverelyFell_one", changedStats: 1}); - battleStatLevelUnits.push({levels: 7, up: false, key: "statWontGoAnyLower_one", changedStats: 1}); - }); - - it("Test getBattleStatName() in English", async () => { - i18next.changeLanguage("en"); - battleStatUnits.forEach(unit => { - testBattleStatName(unit.stat, enPokemonInfo[unit.key.split(".")[0]][unit.key.split(".")[1]]); - }); - }); - - it("Test getBattleStatLevelChangeDescription() in English", async () => { - i18next.changeLanguage("en"); - battleStatLevelUnits.forEach(unit => { - testBattleStatLevelChangeDescription(unit.levels, unit.up, enBattleStat[unit.key], unit.changedStats); - }); - }); - - it("Test getBattleStatName() in Español", async () => { - i18next.changeLanguage("es"); - battleStatUnits.forEach(unit => { - testBattleStatName(unit.stat, esPokemonInfo[unit.key.split(".")[0]][unit.key.split(".")[1]]); - }); - }); - - it("Test getBattleStatLevelChangeDescription() in Español", async () => { - i18next.changeLanguage("es"); - battleStatLevelUnits.forEach(unit => { - testBattleStatLevelChangeDescription(unit.levels, unit.up, esBattleStat[unit.key], unit.changedStats); - }); - }); - - it("Test getBattleStatName() in Italiano", async () => { - i18next.changeLanguage("it"); - battleStatUnits.forEach(unit => { - testBattleStatName(unit.stat, itPokemonInfo[unit.key.split(".")[0]][unit.key.split(".")[1]]); - }); - }); - - it("Test getBattleStatLevelChangeDescription() in Italiano", async () => { - i18next.changeLanguage("it"); - battleStatLevelUnits.forEach(unit => { - testBattleStatLevelChangeDescription(unit.levels, unit.up, itBattleStat[unit.key], unit.changedStats); - }); - }); - - it("Test getBattleStatName() in Français", async () => { - i18next.changeLanguage("fr"); - battleStatUnits.forEach(unit => { - testBattleStatName(unit.stat, frPokemonInfo[unit.key.split(".")[0]][unit.key.split(".")[1]]); - }); - }); - - it("Test getBattleStatLevelChangeDescription() in Français", async () => { - i18next.changeLanguage("fr"); - battleStatLevelUnits.forEach(unit => { - testBattleStatLevelChangeDescription(unit.levels, unit.up, frBattleStat[unit.key], unit.changedStats); - }); - }); - - it("Test getBattleStatName() in Deutsch", async () => { - i18next.changeLanguage("de"); - battleStatUnits.forEach(unit => { - testBattleStatName(unit.stat, dePokemonInfo[unit.key.split(".")[0]][unit.key.split(".")[1]]); - }); - }); - - it("Test getBattleStatLevelChangeDescription() in Deutsch", async () => { - i18next.changeLanguage("de"); - battleStatLevelUnits.forEach(unit => { - testBattleStatLevelChangeDescription(unit.levels, unit.up, deBattleStat[unit.key], unit.changedStats); - }); - }); - - it("Test getBattleStatName() in Português (BR)", async () => { - i18next.changeLanguage("pt-BR"); - battleStatUnits.forEach(unit => { - testBattleStatName(unit.stat, ptBrPokemonInfo[unit.key.split(".")[0]][unit.key.split(".")[1]]); - }); - }); - - it("Test getBattleStatLevelChangeDescription() in Português (BR)", async () => { - i18next.changeLanguage("pt-BR"); - battleStatLevelUnits.forEach(unit => { - testBattleStatLevelChangeDescription(unit.levels, unit.up, ptBrBattleStat[unit.key], unit.changedStats); - }); - }); - - it("Test getBattleStatName() in 简体中文", async () => { - i18next.changeLanguage("zh-CN"); - battleStatUnits.forEach(unit => { - testBattleStatName(unit.stat, zhCnPokemonInfo[unit.key.split(".")[0]][unit.key.split(".")[1]]); - }); - }); - - it("Test getBattleStatLevelChangeDescription() in 简体中文", async () => { - i18next.changeLanguage("zh-CN"); - battleStatLevelUnits.forEach(unit => { - // In i18next, the pluralization rules are language-specific, and Chinese only supports the _other suffix. - unit.key = unit.key.replace("one", "other"); - testBattleStatLevelChangeDescription(unit.levels, unit.up, zhCnBattleStat[unit.key], unit.changedStats); - }); - }); - - it("Test getBattleStatName() in 繁體中文", async () => { - i18next.changeLanguage("zh-TW"); - battleStatUnits.forEach(unit => { - testBattleStatName(unit.stat, zhTwPokemonInfo[unit.key.split(".")[0]][unit.key.split(".")[1]]); - }); - }); - - it("Test getBattleStatLevelChangeDescription() in 繁體中文", async () => { - i18next.changeLanguage("zh-TW"); - battleStatLevelUnits.forEach(unit => { - // In i18next, the pluralization rules are language-specific, and Chinese only supports the _other suffix. - unit.key = unit.key.replace("one", "other"); - testBattleStatLevelChangeDescription(unit.levels, unit.up, zhTwBattleStat[unit.key], unit.changedStats); - }); - }); - - it("Test getBattleStatName() in 한국어", async () => { - await i18next.changeLanguage("ko"); - battleStatUnits.forEach(unit => { - testBattleStatName(unit.stat, koPokemonInfo[unit.key.split(".")[0]][unit.key.split(".")[1]]); - }); - }); - - it("Test getBattleStatLevelChangeDescription() in 한국어", async () => { - i18next.changeLanguage("ko", () => { - battleStatLevelUnits.forEach(unit => { - const processor = new KoreanPostpositionProcessor(); - const message = processor.process(koBattleStat[unit.key]); - testBattleStatLevelChangeDescription(unit.levels, unit.up, message, unit.changedStats); - }); - }); - }); -}); diff --git a/src/test/moves/follow_me.test.ts b/src/test/moves/follow_me.test.ts index cf207c9bc77..5c5b6cc572a 100644 --- a/src/test/moves/follow_me.test.ts +++ b/src/test/moves/follow_me.test.ts @@ -1,5 +1,5 @@ import { BattlerIndex } from "#app/battle.js"; -import { Stat } from "#app/data/pokemon-stat"; +import { Stat } from "#enums/stat"; import { Abilities } from "#app/enums/abilities.js"; import { CommandPhase, SelectTargetPhase, TurnEndPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; diff --git a/src/test/moves/fusion_flare_bolt.test.ts b/src/test/moves/fusion_flare_bolt.test.ts index c2214d5442b..21ec10428c6 100644 --- a/src/test/moves/fusion_flare_bolt.test.ts +++ b/src/test/moves/fusion_flare_bolt.test.ts @@ -3,7 +3,7 @@ import Phaser from "phaser"; import GameManager from "#test/utils/gameManager"; import { MoveEffectPhase, MovePhase, MoveEndPhase, DamagePhase } from "#app/phases"; import { getMovePosition } from "#test/utils/gameManagerUtils"; -import { Stat } from "#app/data/pokemon-stat"; +import { Stat } from "#enums/stat"; import { allMoves } from "#app/data/move"; import { BattlerIndex } from "#app/battle"; import { Species } from "#enums/species"; diff --git a/src/test/moves/make_it_rain.test.ts b/src/test/moves/make_it_rain.test.ts index aa0a8616c01..e66e658e292 100644 --- a/src/test/moves/make_it_rain.test.ts +++ b/src/test/moves/make_it_rain.test.ts @@ -1,5 +1,5 @@ import { Stat } from "#enums/stat"; -import { MoveEndPhase, StatChangePhase } from "#app/phases"; +import { MoveEndPhase, StatStageChangePhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; import { Abilities } from "#enums/abilities"; @@ -60,7 +60,7 @@ describe("Moves - Make It Rain", () => { game.doAttack(getMovePosition(game.scene, 0, Moves.MAKE_IT_RAIN)); - await game.phaseInterceptor.to(StatChangePhase); + await game.phaseInterceptor.to(StatStageChangePhase); expect(enemyPokemon.isFainted()).toBe(true); expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(-1); @@ -77,7 +77,7 @@ describe("Moves - Make It Rain", () => { game.doAttack(getMovePosition(game.scene, 0, Moves.MAKE_IT_RAIN)); game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); - await game.phaseInterceptor.to(StatChangePhase); + await game.phaseInterceptor.to(StatStageChangePhase); enemyPokemon.forEach(p => expect(p.isFainted()).toBe(true)); expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(-1); diff --git a/src/test/moves/spotlight.test.ts b/src/test/moves/spotlight.test.ts index dcc805dbfb7..346f15c75ba 100644 --- a/src/test/moves/spotlight.test.ts +++ b/src/test/moves/spotlight.test.ts @@ -1,5 +1,5 @@ import { BattlerIndex } from "#app/battle.js"; -import { Stat } from "#app/data/pokemon-stat"; +import { Stat } from "#enums/stat"; import { CommandPhase, SelectTargetPhase, TurnEndPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; diff --git a/src/test/moves/tackle.test.ts b/src/test/moves/tackle.test.ts index 512b23ae363..10676481624 100644 --- a/src/test/moves/tackle.test.ts +++ b/src/test/moves/tackle.test.ts @@ -1,4 +1,4 @@ -import { Stat } from "#app/data/pokemon-stat"; +import { Stat } from "#enums/stat"; import { CommandPhase, EnemyCommandPhase, TurnEndPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; import { getMovePosition } from "#test/utils/gameManagerUtils"; diff --git a/src/test/moves/tailwind.test.ts b/src/test/moves/tailwind.test.ts index 55e2b70d9d9..32f526c1c5c 100644 --- a/src/test/moves/tailwind.test.ts +++ b/src/test/moves/tailwind.test.ts @@ -1,5 +1,5 @@ import { ArenaTagSide } from "#app/data/arena-tag.js"; -import { Stat } from "#app/data/pokemon-stat.js"; +import { Stat } from "#enums/stat"; import { ArenaTagType } from "#app/enums/arena-tag-type.js"; import { TurnEndPhase } from "#app/phases"; import GameManager from "#test/utils/gameManager"; diff --git a/src/test/utils/phaseInterceptor.ts b/src/test/utils/phaseInterceptor.ts index 34f79f93b6e..a6ba91646f4 100644 --- a/src/test/utils/phaseInterceptor.ts +++ b/src/test/utils/phaseInterceptor.ts @@ -22,7 +22,7 @@ import { SelectTargetPhase, ShinySparklePhase, ShowAbilityPhase, - StatChangePhase, + StatStageChangePhase, SummonPhase, SwitchPhase, SwitchSummonPhase, @@ -85,7 +85,7 @@ export default class PhaseInterceptor { [NewBattlePhase, this.startPhase], [VictoryPhase, this.startPhase], [MoveEndPhase, this.startPhase], - [StatChangePhase, this.startPhase], + [StatStageChangePhase, this.startPhase], [ShinySparklePhase, this.startPhase], [SelectTargetPhase, this.startPhase], [UnavailablePhase, this.startPhase], diff --git a/src/ui/battle-info.ts b/src/ui/battle-info.ts index d27d44802ea..f35240127d2 100644 --- a/src/ui/battle-info.ts +++ b/src/ui/battle-info.ts @@ -69,8 +69,8 @@ export default class BattleInfo extends Phaser.GameObjects.Container { public flyoutMenu?: BattleFlyout; private statOrder: Stat[]; - private statOrderPlayer = [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.ACC, Stat.EVA, Stat.SPD ]; - private statOrderEnemy = [ Stat.HP, Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.ACC, Stat.EVA, Stat.SPD ]; + private readonly statOrderPlayer = [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.ACC, Stat.EVA, Stat.SPD ]; + private readonly statOrderEnemy = [ Stat.HP, Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.ACC, Stat.EVA, Stat.SPD ]; constructor(scene: Phaser.Scene, x: number, y: number, player: boolean) { super(scene, x, y); @@ -650,12 +650,12 @@ export default class BattleInfo extends Phaser.GameObjects.Container { this.lastLevel = pokemon.level; } - const battleStats = pokemon.getStatStages(); - const battleStatsStr = battleStats.join(""); + const stats = pokemon.getStatStages(); + const statsStr = stats.join(""); - if (this.lastStats !== battleStatsStr) { - this.updateStats(battleStats); - this.lastStats = battleStatsStr; + if (this.lastStats !== statsStr) { + this.updateStats(stats); + this.lastStats = statsStr; } this.shinyIcon.setVisible(pokemon.isShiny()); @@ -770,7 +770,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container { updateStats(stats: integer[]): void { this.statOrder.map((s, i) => { if (s !== Stat.HP) { - this.statNumbers[i].setFrame(stats[s].toString()); + this.statNumbers[i].setFrame(stats[s - 1].toString()); } }); } diff --git a/src/ui/stats-container.ts b/src/ui/stats-container.ts index 5aab5268c86..8c689064c87 100644 --- a/src/ui/stats-container.ts +++ b/src/ui/stats-container.ts @@ -2,6 +2,7 @@ import BBCodeText from "phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodete import BattleScene from "../battle-scene"; import { TextStyle, addBBCodeTextObject, addTextObject, getTextColor } from "./text"; import { getStatKey, PERMANENT_STATS } from "#app/enums/stat.js"; +import i18next from "i18next"; const ivChartSize = 24; const ivChartStatCoordMultipliers = [[0, -1], [0.825, -0.5], [0.825, 0.5], [-0.825, -0.5], [-0.825, 0.5], [0, 1]]; @@ -54,7 +55,7 @@ export class StatsContainer extends Phaser.GameObjects.Container { this.ivStatValueTexts = []; for (const s of PERMANENT_STATS) { - const statLabel = addTextObject(this.scene, ivChartBg.x + (ivChartSize) * ivChartStatCoordMultipliers[s][0] * 1.325, ivChartBg.y + (ivChartSize) * ivChartStatCoordMultipliers[s][1] * 1.325 - 4 + ivLabelOffset[s], getStatKey(s), TextStyle.TOOLTIP_CONTENT); + const statLabel = addTextObject(this.scene, ivChartBg.x + (ivChartSize) * ivChartStatCoordMultipliers[s][0] * 1.325, ivChartBg.y + (ivChartSize) * ivChartStatCoordMultipliers[s][1] * 1.325 - 4 + ivLabelOffset[s], i18next.t(getStatKey(s)), TextStyle.TOOLTIP_CONTENT); statLabel.setOrigin(0.5); this.ivStatValueTexts[s] = addBBCodeTextObject(this.scene, statLabel.x, statLabel.y + 8, "0", TextStyle.TOOLTIP_CONTENT);