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

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