Finalize BattleStat Removal

This commit is contained in:
xsn34kzx 2024-08-17 22:41:56 -04:00
parent 1f130f85a7
commit 2edb67b56d
36 changed files with 170 additions and 542 deletions

View File

@ -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$

View File

@ -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

View File

@ -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));
}
}
}

View File

@ -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
));
}
}

View File

@ -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) => {

View File

@ -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()

View File

@ -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";

View File

@ -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";

View File

@ -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;

View File

@ -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";

View File

@ -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
}));
}
});

View File

@ -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);

View File

@ -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([

View File

@ -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";

View File

@ -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";

View File

@ -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";

View File

@ -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";

View File

@ -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");
});
});
});
});

View File

@ -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";

View File

@ -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);

View File

@ -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);

View File

@ -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";

View File

@ -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";

View File

@ -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";

View File

@ -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";

View File

@ -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";

View File

@ -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);
});
});
});
});

View File

@ -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";

View File

@ -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";

View File

@ -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);

View File

@ -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";

View File

@ -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";

View File

@ -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";

View File

@ -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],

View File

@ -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());
}
});
}

View File

@ -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);