Remove BattleStat, Use "Stat Stages" & New Names

This commit is contained in:
xsn34kzx 2024-08-16 21:09:11 -04:00
parent 8461ca3fc2
commit db9925ec0f
73 changed files with 1312 additions and 1670 deletions

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,7 @@ import { MoveEffectPhase, PokemonHealPhase, ShowAbilityPhase, StatChangePhase }
import { StatusEffect } from "./status-effect";
import { BattlerIndex } from "../battle";
import { BlockNonDirectDamageAbAttr, ProtectStatAbAttr, applyAbAttrs } from "./ability";
import { BattleStat } from "./battle-stat";
import { Stat } from "#enums/stat";
import { CommonAnim, CommonBattleAnim } from "./battle-anims";
import i18next from "i18next";
import { Abilities } from "#enums/abilities";
@ -726,7 +726,7 @@ class StickyWebTag extends ArenaTrapTag {
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, [BattleStat.SPD], statLevels.value));
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), false, [ Stat.SPD ], statLevels.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, [BattleStat.ATK], 1, true));
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ Stat.ATK ], 1, true));
}
}
}

View File

@ -1,72 +0,0 @@
import i18next, { ParseKeys } from "i18next";
export enum BattleStat {
ATK,
DEF,
SPATK,
SPDEF,
SPD,
ACC,
EVA,
RAND,
HP
}
export function getBattleStatName(stat: BattleStat) {
switch (stat) {
case BattleStat.ATK:
return i18next.t("pokemonInfo:Stat.ATK");
case BattleStat.DEF:
return i18next.t("pokemonInfo:Stat.DEF");
case BattleStat.SPATK:
return i18next.t("pokemonInfo:Stat.SPATK");
case BattleStat.SPDEF:
return i18next.t("pokemonInfo:Stat.SPDEF");
case BattleStat.SPD:
return i18next.t("pokemonInfo:Stat.SPD");
case BattleStat.ACC:
return i18next.t("pokemonInfo:Stat.ACC");
case BattleStat.EVA:
return i18next.t("pokemonInfo:Stat.EVA");
case BattleStat.HP:
return i18next.t("pokemonInfo:Stat.HPStat");
default:
return "???";
}
}
// TODO: BattleStat
export function getBattleStatLevelChangeDescription(pokemonNameWithAffix: string, stats: string, levels: integer, up: boolean, count: number = 1) {
const stringKey = (() => {
if (up) {
switch (levels) {
case 1:
return "battle:statRose";
case 2:
return "battle:statSharplyRose";
case 3:
case 4:
case 5:
case 6:
return "battle:statRoseDrastically";
default:
return "battle:statWontGoAnyHigher";
}
} else {
switch (levels) {
case 1:
return "battle:statFell";
case 2:
return "battle:statHarshlyFell";
case 3:
case 4:
case 5:
case 6:
return "battle:statSeverelyFell";
default:
return "battle:statWontGoAnyLower";
}
}
})();
return i18next.t(stringKey as ParseKeys, { pokemonNameWithAffix, stats, count });
}

View File

@ -1,8 +1,7 @@
import { ChargeAnim, CommonAnim, CommonBattleAnim, MoveChargeAnim } from "./battle-anims";
import { CommonAnimPhase, MoveEffectPhase, MovePhase, PokemonHealPhase, ShowAbilityPhase, StatChangeCallback, StatChangePhase } from "../phases";
import { CommonAnimPhase, MoveEffectPhase, MovePhase, PokemonHealPhase, ShowAbilityPhase, StatChangeCallback, StatStageChangePhase } from "../phases";
import { getPokemonNameWithAffix } from "../messages";
import Pokemon, { MoveResult, HitResult } from "../field/pokemon";
import { Stat, getStatName } from "./pokemon-stat";
import { StatusEffect } from "./status-effect";
import * as Utils from "../utils";
import { ChargeAttr, MoveFlags, allMoves } from "./move";
@ -10,7 +9,6 @@ import { Type } from "./type";
import { BlockNonDirectDamageAbAttr, FlinchEffectAbAttr, ReverseDrainAbAttr, applyAbAttrs } from "./ability";
import { TerrainType } from "./terrain";
import { WeatherType } from "./weather";
import { BattleStat } from "./battle-stat";
import { allAbilities } from "./ability";
import { SpeciesFormChangeManualTrigger } from "./pokemon-forms";
import { Abilities } from "#enums/abilities";
@ -18,6 +16,7 @@ import { BattlerTagType } from "#enums/battler-tag-type";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import i18next from "#app/plugins/i18n.js";
import { Stat, BattleStat, EFFECTIVE_STATS, EffectiveStat, getStatKey } from "#app/enums/stat";
export enum BattlerTagLapseType {
FAINT,
@ -296,8 +295,8 @@ export class ConfusedTag extends BattlerTag {
// 1/3 chance of hitting self with a 40 base power move
if (pokemon.randSeedInt(3) === 0) {
const atk = pokemon.getBattleStat(Stat.ATK);
const def = pokemon.getBattleStat(Stat.DEF);
const atk = pokemon.getEffectiveStat(Stat.ATK);
const def = pokemon.getEffectiveStat(Stat.DEF);
const damage = Math.ceil(((((2 * pokemon.level / 5 + 2) * 40 * atk / def) / 50) + 2) * (pokemon.randSeedInt(15, 85) / 100));
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsConfusedLapseHurtItself"));
pokemon.damageAndUpdate(damage);
@ -701,7 +700,7 @@ export class OctolockTag extends TrappedTag {
const shouldLapse = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType);
if (shouldLapse) {
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [BattleStat.DEF, BattleStat.SPDEF], -1));
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ Stat.DEF, Stat.SPDEF ], -1));
return true;
}
@ -1027,7 +1026,7 @@ export class ContactDamageProtectedTag extends ProtectedTag {
}
}
export class ContactStatChangeProtectedTag extends ProtectedTag {
export class ContactStatStageChangeProtectedTag extends ProtectedTag {
private stat: BattleStat;
private levels: number;
@ -1044,7 +1043,7 @@ export class ContactStatChangeProtectedTag extends ProtectedTag {
*/
loadTag(source: BattlerTag | any): void {
super.loadTag(source);
this.stat = source.stat as BattleStat;
this.stat = source.stat;
this.levels = source.levels;
}
@ -1055,7 +1054,7 @@ export class ContactStatChangeProtectedTag extends ProtectedTag {
const effectPhase = pokemon.scene.getCurrentPhase();
if (effectPhase instanceof MoveEffectPhase && effectPhase.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) {
const attacker = effectPhase.getPokemon();
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, attacker.getBattlerIndex(), true, [ this.stat ], this.levels));
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, attacker.getBattlerIndex(), true, [ this.stat ], this.levels));
}
}
@ -1282,11 +1281,10 @@ export class HighestStatBoostTag extends AbilityBattlerTag {
onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon);
const stats = [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ];
let highestStat: Stat;
stats.map(s => pokemon.getBattleStat(s)).reduce((highestValue: number, value: number, i: number) => {
let highestStat: EffectiveStat;
EFFECTIVE_STATS.map(s => pokemon.getEffectiveStat(s)).reduce((highestValue: number, value: number, i: number) => {
if (value > highestValue) {
highestStat = stats[i];
highestStat = EFFECTIVE_STATS[i];
return value;
}
return highestValue;
@ -1304,7 +1302,7 @@ export class HighestStatBoostTag extends AbilityBattlerTag {
break;
}
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsHighestStatBoostOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), statName: getStatName(highestStat) }), null, false, null, true);
pokemon.scene.queueMessage(i18next.t("battle:battlerTagsHighestStatBoostOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), statName: getStatKey(highestStat) }), null, false, null, true);
}
onRemove(pokemon: Pokemon): void {
@ -1622,9 +1620,9 @@ export class IceFaceTag extends BattlerTag {
*/
export class StockpilingTag extends BattlerTag {
public stockpiledCount: number = 0;
public statChangeCounts: { [BattleStat.DEF]: number; [BattleStat.SPDEF]: number } = {
[BattleStat.DEF]: 0,
[BattleStat.SPDEF]: 0
public statChangeCounts: { [Stat.DEF]: number; [Stat.SPDEF]: number } = {
[Stat.DEF]: 0,
[Stat.SPDEF]: 0
};
constructor(sourceMove: Moves = Moves.NONE) {
@ -1632,15 +1630,15 @@ export class StockpilingTag extends BattlerTag {
}
private onStatsChanged: StatChangeCallback = (_, statsChanged, statChanges) => {
const defChange = statChanges[statsChanged.indexOf(BattleStat.DEF)] ?? 0;
const spDefChange = statChanges[statsChanged.indexOf(BattleStat.SPDEF)] ?? 0;
const defChange = statChanges[statsChanged.indexOf(Stat.DEF)] ?? 0;
const spDefChange = statChanges[statsChanged.indexOf(Stat.SPDEF)] ?? 0;
if (defChange) {
this.statChangeCounts[BattleStat.DEF]++;
this.statChangeCounts[Stat.DEF]++;
}
if (spDefChange) {
this.statChangeCounts[BattleStat.SPDEF]++;
this.statChangeCounts[Stat.SPDEF]++;
}
};
@ -1648,8 +1646,8 @@ export class StockpilingTag extends BattlerTag {
super.loadTag(source);
this.stockpiledCount = source.stockpiledCount || 0;
this.statChangeCounts = {
[ BattleStat.DEF ]: source.statChangeCounts?.[ BattleStat.DEF ] ?? 0,
[ BattleStat.SPDEF ]: source.statChangeCounts?.[ BattleStat.SPDEF ] ?? 0,
[ Stat.DEF ]: source.statChangeCounts?.[ Stat.DEF ] ?? 0,
[ Stat.SPDEF ]: source.statChangeCounts?.[ Stat.SPDEF ] ?? 0,
};
}
@ -1669,9 +1667,9 @@ export class StockpilingTag extends BattlerTag {
}));
// Attempt to increase DEF and SPDEF by one stage, keeping track of successful changes.
pokemon.scene.unshiftPhase(new StatChangePhase(
pokemon.scene.unshiftPhase(new StatStageChangePhase(
pokemon.scene, pokemon.getBattlerIndex(), true,
[BattleStat.SPDEF, BattleStat.DEF], 1, true, false, true, this.onStatsChanged
[Stat.SPDEF, Stat.DEF], 1, true, false, true, this.onStatsChanged
));
}
}
@ -1685,15 +1683,15 @@ export class StockpilingTag extends BattlerTag {
* one stage for each stack which had successfully changed that particular stat during onAdd.
*/
onRemove(pokemon: Pokemon): void {
const defChange = this.statChangeCounts[BattleStat.DEF];
const spDefChange = this.statChangeCounts[BattleStat.SPDEF];
const defChange = this.statChangeCounts[Stat.DEF];
const spDefChange = this.statChangeCounts[Stat.SPDEF];
if (defChange) {
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [BattleStat.DEF], -defChange, true, false, true));
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ Stat.DEF ], -defChange, true, false, true));
}
if (spDefChange) {
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [BattleStat.SPDEF], -spDefChange, true, false, true));
pokemon.scene.unshiftPhase(new StatStageChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ Stat.SPDEF ], -spDefChange, true, false, true));
}
}
}
@ -1831,11 +1829,11 @@ export function getBattlerTag(tagType: BattlerTagType, turnCount: number, source
case BattlerTagType.SPIKY_SHIELD:
return new ContactDamageProtectedTag(sourceMove, 8);
case BattlerTagType.KINGS_SHIELD:
return new ContactStatChangeProtectedTag(sourceMove, tagType, BattleStat.ATK, -1);
return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.ATK, -1);
case BattlerTagType.OBSTRUCT:
return new ContactStatChangeProtectedTag(sourceMove, tagType, BattleStat.DEF, -2);
return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.DEF, -2);
case BattlerTagType.SILK_TRAP:
return new ContactStatChangeProtectedTag(sourceMove, tagType, BattleStat.SPD, -1);
return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.SPD, -1);
case BattlerTagType.BANEFUL_BUNKER:
return new ContactPoisonProtectedTag(sourceMove);
case BattlerTagType.BURNING_BULWARK:

View File

@ -1,13 +1,13 @@
import { PokemonHealPhase, StatChangePhase } from "../phases";
import { getPokemonNameWithAffix } from "../messages";
import Pokemon, { HitResult } from "../field/pokemon";
import { BattleStat } from "./battle-stat";
import { getStatusEffectHealText } from "./status-effect";
import * as Utils from "../utils";
import { DoubleBerryEffectAbAttr, ReduceBerryUseThresholdAbAttr, applyAbAttrs } from "./ability";
import i18next from "i18next";
import { BattlerTagType } from "#enums/battler-tag-type";
import { BerryType } from "#enums/berry-type";
import { BattleStat, Stat } from "#app/enums/stat";
export function getBerryName(berryType: BerryType): string {
return i18next.t(`berry:${BerryType[berryType]}.name`);
@ -34,9 +34,10 @@ export function getBerryPredicate(berryType: BerryType): BerryPredicate {
case BerryType.SALAC:
return (pokemon: Pokemon) => {
const threshold = new Utils.NumberHolder(0.25);
const battleStat = (berryType - BerryType.LIECHI) as BattleStat;
// Offset BerryType such that LIECHI -> Stat.ATK = 1, GANLON -> Stat.DEF = 2, so on and so forth
const stat: BattleStat = berryType - BerryType.ENIGMA;
applyAbAttrs(ReduceBerryUseThresholdAbAttr, pokemon, null, threshold);
return pokemon.getHpRatio() < threshold.value && pokemon.summonData.battleStats[battleStat] < 6;
return pokemon.getHpRatio() < threshold.value && pokemon.getStatStage(stat) < 6;
};
case BerryType.LANSAT:
return (pokemon: Pokemon) => {
@ -94,10 +95,11 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
if (pokemon.battleData) {
pokemon.battleData.berriesEaten.push(berryType);
}
const battleStat = (berryType - BerryType.LIECHI) as BattleStat;
const statLevels = new Utils.NumberHolder(1);
applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, statLevels);
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ battleStat ], statLevels.value));
// Offset BerryType such that LIECHI -> Stat.ATK = 1, GANLON -> Stat.DEF = 2, so on and so forth
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));
};
case BerryType.LANSAT:
return (pokemon: Pokemon) => {
@ -111,9 +113,10 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
if (pokemon.battleData) {
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, [ BattleStat.RAND ], statLevels.value));
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ randStat ], statLevels.value)); // TODO: BattleStats
};
case BerryType.LEPPA:
return (pokemon: Pokemon) => {

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,9 @@
import { Stat, getStatName } from "./pokemon-stat";
import * as Utils from "../utils";
import { TextStyle, getBBCodeFrag } from "../ui/text";
import { Nature } from "#enums/nature";
import { UiTheme } from "#enums/ui-theme";
import i18next from "i18next";
import { Stat, EFFECTIVE_STATS, getShortenedStatKey } from "#app/enums/stat.js";
export { Nature };
@ -14,10 +14,9 @@ export function getNatureName(nature: Nature, includeStatEffects: boolean = fals
ret = i18next.t("nature:" + ret as any);
}
if (includeStatEffects) {
const stats = Utils.getEnumValues(Stat).slice(1);
let increasedStat: Stat | null = null;
let decreasedStat: Stat | null = null;
for (const stat of stats) {
for (const stat of EFFECTIVE_STATS) {
const multiplier = getNatureStatMultiplier(nature, stat);
if (multiplier > 1) {
increasedStat = stat;
@ -28,7 +27,7 @@ export function getNatureName(nature: Nature, includeStatEffects: boolean = fals
const textStyle = forStarterSelect ? TextStyle.SUMMARY_ALT : TextStyle.WINDOW;
const getTextFrag = !ignoreBBCode ? (text: string, style: TextStyle) => getBBCodeFrag(text, style, uiTheme) : (text: string, style: TextStyle) => text;
if (increasedStat && decreasedStat) {
ret = `${getTextFrag(`${ret}${!forStarterSelect ? "\n" : " "}(`, textStyle)}${getTextFrag(`+${getStatName(increasedStat, true)}`, TextStyle.SUMMARY_PINK)}${getTextFrag("/", textStyle)}${getTextFrag(`-${getStatName(decreasedStat, true)}`, TextStyle.SUMMARY_BLUE)}${getTextFrag(")", textStyle)}`;
ret = `${getTextFrag(`${ret}${!forStarterSelect ? "\n" : " "}(`, textStyle)}${getTextFrag(`+${i18next.t(getShortenedStatKey(increasedStat))}`, TextStyle.SUMMARY_PINK)}${getTextFrag("/", textStyle)}${getTextFrag(`-${getShortenedStatKey(decreasedStat)}`, TextStyle.SUMMARY_BLUE)}${getTextFrag(")", textStyle)}`;
} else {
ret = getTextFrag(`${ret}${!forStarterSelect ? "\n" : " "}(-)`, textStyle);
}

View File

@ -1,29 +0,0 @@
import { Stat } from "#enums/stat";
import i18next from "i18next";
export { Stat };
export function getStatName(stat: Stat, shorten: boolean = false) {
let ret: string = "";
switch (stat) {
case Stat.HP:
ret = !shorten ? i18next.t("pokemonInfo:Stat.HP") : i18next.t("pokemonInfo:Stat.HPshortened");
break;
case Stat.ATK:
ret = !shorten ? i18next.t("pokemonInfo:Stat.ATK") : i18next.t("pokemonInfo:Stat.ATKshortened");
break;
case Stat.DEF:
ret = !shorten ? i18next.t("pokemonInfo:Stat.DEF") : i18next.t("pokemonInfo:Stat.DEFshortened");
break;
case Stat.SPATK:
ret = !shorten ? i18next.t("pokemonInfo:Stat.SPATK") : i18next.t("pokemonInfo:Stat.SPATKshortened");
break;
case Stat.SPDEF:
ret = !shorten ? i18next.t("pokemonInfo:Stat.SPDEF") : i18next.t("pokemonInfo:Stat.SPDEFshortened");
break;
case Stat.SPD:
ret = !shorten ? i18next.t("pokemonInfo:Stat.SPD") : i18next.t("pokemonInfo:Stat.SPDshortened");
break;
}
return ret;
}

View File

@ -20,8 +20,30 @@ export enum Stat {
export const PERMANENT_STATS = [ Stat.HP, Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ] as const;
export type PermanentStat = typeof PERMANENT_STATS[number];
export const EFFECTIVE_STATS = [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ] as const;
export type EffectiveStat = typeof EFFECTIVE_STATS[number];
export const BATTLE_STATS = [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD, Stat.ACC, Stat.EVA ] as const;
export type BattleStat = typeof BATTLE_STATS[number];
export const TEMP_BATTLE_STATS = [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD, Stat.ACC ] as const;
export type TempBattleStat = typeof TEMP_BATTLE_STATS[number];
export function getStatStageChangeDescriptionKey(stages: integer, isIncrease: boolean) {
if (stages === 1) {
return isIncrease ? "battle:statRose" : "battle:statFell";
} else if (stages === 2) {
return isIncrease ? "battle:statSharplyRose" : "battle:statHarshlyFell";
} else if (stages <= 6) {
return isIncrease ? "battle:statRoseDrastically" : "battle:statSeverelyFell";
}
return isIncrease ? "battle:statWontGoAnyHigher" : "battle:statWontGoAnyLower";
}
export function getStatKey(stat: Stat) {
return `pokemonInfo:Stat.${Stat[stat]}`;
}
export function getShortenedStatKey(stat: PermanentStat) {
return `pokemonInfo:Stat.${Stat[stat]}shortened`;
}

View File

@ -3,13 +3,13 @@ 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, StatChangeAttr, 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, IgnoreOpponentStatChangesAttr, 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";
import { Type, TypeDamageMultiplier, getTypeDamageMultiplier, getTypeRgb } from "../data/type";
import { getLevelTotalExp } from "../data/exp";
import { Stat } from "../data/pokemon-stat";
import { BATTLE_STATS, EFFECTIVE_STATS, Stat } from "#enums/stat";
import { DamageMoneyRewardModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, EnemyFusionChanceModifier, HiddenAbilityRateBoosterModifier, BaseStatModifier, PokemonFriendshipBoosterModifier, PokemonHeldItemModifier, PokemonNatureWeightModifier, ShinyRateBoosterModifier, SurviveDamageModifier, TempStatStageBoosterModifier, TempCritBoosterModifier, StatBoosterModifier, CritBoosterModifier, TerastallizeModifier } from "../modifier/modifier";
import { PokeballType } from "../data/pokeball";
import { Gender } from "../data/gender";
@ -17,11 +17,11 @@ import { initMoveAnim, loadMoveAnimAssets } from "../data/battle-anims";
import { Status, StatusEffect, getRandomStatus } from "../data/status-effect";
import { pokemonEvolutions, pokemonPrevolutions, SpeciesFormEvolution, SpeciesEvolutionCondition, FusionSpeciesFormEvolution } from "../data/pokemon-evolutions";
import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "../data/tms";
import { DamagePhase, FaintPhase, LearnMovePhase, MoveEffectPhase, ObtainStatusEffectPhase, StatChangePhase, SwitchSummonPhase, ToggleDoublePositionPhase, MoveEndPhase } from "../phases";
import { DamagePhase, FaintPhase, LearnMovePhase, MoveEffectPhase, ObtainStatusEffectPhase, StatStageChangePhase, SwitchSummonPhase, ToggleDoublePositionPhase, MoveEndPhase } from "../phases";
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, BattleStatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldBattleStatMultiplierAbAttrs, FieldMultiplyBattleStatAbAttr, AddSecondStrikeAbAttr, IgnoreOpponentEvasionAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr } from "../data/ability";
import { 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 PokemonData from "../system/pokemon-data";
import { BattlerIndex } from "../battle";
import { Mode } from "../ui/ui";
@ -49,7 +49,7 @@ import { Biome } from "#enums/biome";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import { getPokemonNameWithAffix } from "#app/messages.js";
import { PermanentStat, BattleStat, PERMANENT_STATS } from "#app/enums/stat"; // TODO: Add type
import { PermanentStat, BattleStat, PERMANENT_STATS, EffectiveStat } from "#app/enums/stat"; // TODO: Add type
export enum FieldPosition {
CENTER,
@ -735,7 +735,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
}
getBattleStat(stat: PermanentStat & BattleStat, opponent?: Pokemon, move?: Move, isCritical: boolean = false): integer {
getEffectiveStat(stat: EffectiveStat, opponent?: Pokemon, move?: Move, isCritical: boolean = false): integer {
const statLevel = new Utils.IntegerHolder(this.getStatStage(stat));
if (opponent) {
if (isCritical) {
@ -750,7 +750,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
break;
}
}
applyAbAttrs(IgnoreOpponentStatChangesAbAttr, opponent, null, statLevel);
applyAbAttrs(IgnoreOpponentStatStageChangesAbAttr, opponent, null, statLevel);
if (move) {
applyMoveAttrs(IgnoreOpponentStatChangesAttr, this, opponent, move, statLevel);
}
@ -763,12 +763,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const fieldApplied = new Utils.BooleanHolder(false);
for (const pokemon of this.scene.getField(true)) {
applyFieldBattleStatMultiplierAbAttrs(FieldMultiplyBattleStatAbAttr, pokemon, stat, statValue, this, fieldApplied);
applyFieldStatMultiplierAbAttrs(FieldMultiplyStatAbAttr, pokemon, stat, statValue, this, fieldApplied);
if (fieldApplied.value) {
break;
}
}
applyBattleStatMultiplierAbAttrs(BattleStatMultiplierAbAttr, this, battleStat, statValue); // TODO: BattleStat
applyStatStageMultiplierAbAttrs(StatStageMultiplierAbAttr, this, stat, statValue);
let ret = statValue.value * (Math.max(2, 2 + statLevel.value) / Math.max(2, 2 - statLevel.value));
switch (stat) {
case Stat.ATK:
@ -1374,7 +1374,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const types = this.getTypes(true);
const enemyTypes = opponent.getTypes(true, true);
/** Is this Pokemon faster than the opponent? */
const outspeed = (this.isActive(true) ? this.getBattleStat(Stat.SPD, opponent) : this.getStat(Stat.SPD)) >= opponent.getBattleStat(Stat.SPD, this);
const outspeed = (this.isActive(true) ? this.getEffectiveStat(Stat.SPD, opponent) : this.getStat(Stat.SPD, false)) >= opponent.getEffectiveStat(Stat.SPD, this);
/**
* Based on how effective this Pokemon's types are offensively against the opponent's types.
* This score is increased by 25 percent if this Pokemon is faster than the opponent.
@ -1730,7 +1730,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
movePool = movePool.map(m => [m[0], m[1] * (!!allMoves[m[0]].hasAttr(SacrificialAttr) ? 0.5 : 1)]);
movePool = movePool.map(m => [m[0], m[1] * (!!allMoves[m[0]].hasAttr(SacrificialAttrOnHit) ? 0.5 : 1)]);
// Trainers get a weight bump to stat buffing moves
movePool = movePool.map(m => [m[0], m[1] * (allMoves[m[0]].getAttrs(StatChangeAttr).some(a => a.levels > 1 && a.selfTarget) ? 1.25 : 1)]);
movePool = movePool.map(m => [m[0], m[1] * (allMoves[m[0]].getAttrs(StatStageChangeAttr).some(a => a.stages > 1 && a.selfTarget) ? 1.25 : 1)]);
// Trainers get a weight decrease to multiturn moves
movePool = movePool.map(m => [m[0], m[1] * (!!allMoves[m[0]].hasAttr(ChargeAttr) || !!allMoves[m[0]].hasAttr(RechargeAttr) ? 0.7 : 1)]);
}
@ -1950,8 +1950,8 @@ 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(IgnoreOpponentStatChangesAbAttr, target, null, userAccStage);
applyAbAttrs(IgnoreOpponentStatChangesAbAttr, this, null, targetEvaStage);
applyAbAttrs(IgnoreOpponentStatStageChangesAbAttr, target, null, userAccStage);
applyAbAttrs(IgnoreOpponentStatStageChangesAbAttr, this, null, targetEvaStage);
applyAbAttrs(IgnoreOpponentEvasionAbAttr, this, null, targetEvaStage);
applyMoveAttrs(IgnoreOpponentStatChangesAttr, this, target, sourceMove, targetEvaStage);
this.scene.applyModifiers(TempStatStageBoosterModifier, this.isPlayer(), Stat.ACC, userAccStage);
@ -1967,10 +1967,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
: 3 / (3 + Math.min(targetEvaStage.value - userAccStage.value, 6));
}
applyBattleStatMultiplierAbAttrs(BattleStatMultiplierAbAttr, this, BattleStat.ACC, accuracyMultiplier, sourceMove); // TODO: BattleStat
applyStatStageMultiplierAbAttrs(StatStageMultiplierAbAttr, this, Stat.ACC, accuracyMultiplier, sourceMove); // TODO: BattleStat
const evasionMultiplier = new Utils.NumberHolder(1);
applyBattleStatMultiplierAbAttrs(BattleStatMultiplierAbAttr, target, BattleStat.EVA, evasionMultiplier); // TODO: BattleStat
applyStatStageMultiplierAbAttrs(StatStageMultiplierAbAttr, target, Stat.EVA, evasionMultiplier); // TODO: BattleStat
accuracyMultiplier.value /= evasionMultiplier.value;
@ -2087,8 +2087,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
isCritical = false;
}
}
const sourceAtk = new Utils.IntegerHolder(source.getBattleStat(isPhysical ? Stat.ATK : Stat.SPATK, this, undefined, isCritical));
const targetDef = new Utils.IntegerHolder(this.getBattleStat(isPhysical ? Stat.DEF : Stat.SPDEF, source, move, isCritical));
const sourceAtk = new Utils.IntegerHolder(source.getEffectiveStat(isPhysical ? Stat.ATK : Stat.SPATK, this, undefined, isCritical));
const targetDef = new Utils.IntegerHolder(this.getEffectiveStat(isPhysical ? Stat.DEF : Stat.SPDEF, source, move, isCritical));
const criticalMultiplier = new Utils.NumberHolder(isCritical ? 1.5 : 1);
applyAbAttrs(MultCritAbAttr, source, null, criticalMultiplier);
const screenMultiplier = new Utils.NumberHolder(1);
@ -2497,11 +2497,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* @param source {@linkcode Pokemon} the pokemon whose stats/Tags are to be passed on from, ie: the Pokemon using Baton Pass
*/
transferSummon(source: Pokemon): void {
/** TODO: BattleStats
for (const stat of battleStats) {
this.summonData.battleStats[stat] = source.summonData.battleStats[stat];
// Copy all stat stages
for (const s of BATTLE_STATS) {
const sourceStage = source.getStatStage(s);
if ((this instanceof PlayerPokemon) && (sourceStage === 6)) {
this.scene.validateAchv(achvs.TRANSFER_MAX_STAT_STAGE);
}
*/
this.setStatStage(s, sourceStage);
}
for (const tag of source.summonData.tags) {
// bypass yawn, and infatuation as those can not be passed via Baton Pass
@ -2511,11 +2515,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.summonData.tags.push(tag);
}
/** TODO: BattleStats
if (this instanceof PlayerPokemon && source.summonData.battleStats.find(bs => bs === 6)) {
this.scene.validateAchv(achvs.TRANSFER_MAX_BATTLE_STAT);
}
*/
this.updateInfo();
}
@ -4161,45 +4161,46 @@ export class EnemyPokemon extends Pokemon {
return true;
}
handleBossSegmentCleared(segmentIndex: integer): void { // TODO: BattleStat
handleBossSegmentCleared(segmentIndex: integer): void {
while (segmentIndex - 1 < this.bossSegmentIndex) {
let boostedStat = BattleStat.RAND;
// Filter out already maxed out stat stages and weigh the rest based on existing stats
const leftoverStats = EFFECTIVE_STATS.filter((s: EffectiveStat) => this.getStatStage(s) < 6);
const statWeights = leftoverStats.map((s: EffectiveStat) => this.getStat(s, false));
const battleStats = Utils.getEnumValues(BattleStat).slice(0, -3);
const statWeights = new Array().fill(battleStats.length).filter((bs: BattleStat) => this.summonData.battleStats[bs] < 6).map((bs: BattleStat) => this.getStat(bs + 1));
const statThresholds: integer[] = [];
let boostedStat: EffectiveStat;
const statThresholds: number[] = [];
let totalWeight = 0;
for (const bs of battleStats) {
totalWeight += statWeights[bs];
for (const i in statWeights) {
totalWeight += statWeights[i];
statThresholds.push(totalWeight);
}
// Pick a random stat from the leftover stats to increase its stages
const randInt = Utils.randSeedInt(totalWeight);
for (const bs of battleStats) {
if (randInt < statThresholds[bs]) {
boostedStat = bs;
for (const i in statThresholds) {
if (randInt < statThresholds[i]) {
boostedStat = leftoverStats[i];
break;
}
}
let statLevels = 1;
// Increment the amount of stages the chosen stat will be raised
let stages = 1;
switch (segmentIndex) {
case 1:
if (this.bossSegments >= 3) {
statLevels++;
stages++;
}
break;
case 2:
if (this.bossSegments >= 5) {
statLevels++;
stages++;
}
break;
}
this.scene.unshiftPhase(new StatChangePhase(this.scene, this.getBattlerIndex(), true, [ boostedStat ], statLevels, true, true));
this.scene.unshiftPhase(new StatStageChangePhase(this.scene, this.getBattlerIndex(), true, [ boostedStat! ], stages, true, true));
this.bossSegmentIndex--;
}
}

View File

@ -89,9 +89,9 @@ export const PGMachv: AchievementTranslationEntries = {
name: "Master League Champion",
},
"TRANSFER_MAX_BATTLE_STAT": {
"TRANSFER_MAX_STAT_STAGE": {
name: "Teamwork",
description: "Baton pass to another party member with at least one stat maxed out",
description: "Baton pass to another party member with at least one stat stage maxed out",
},
"MAX_FRIENDSHIP": {
name: "Friendmaxxing",

View File

@ -90,7 +90,7 @@ export const PGMachv: AchievementTranslationEntries = {
name: "Bänder-Meister",
},
"TRANSFER_MAX_BATTLE_STAT": {
"TRANSFER_MAX_STAT_STAGE": {
name: "Teamwork",
description: "Nutze Staffette, während der Anwender mindestens eines Statuswertes maximiert hat.",
},
@ -333,7 +333,7 @@ export const PGFachv: AchievementTranslationEntries = {
name: "Bänder-Meisterin",
},
"TRANSFER_MAX_BATTLE_STAT": PGMachv.TRANSFER_MAX_BATTLE_STAT,
"TRANSFER_MAX_STAT_STAGE": PGMachv.TRANSFER_MAX_STAT_STAGE,
"MAX_FRIENDSHIP": PGMachv.MAX_FRIENDSHIP,
"MEGA_EVOLVE": PGMachv.MEGA_EVOLVE,
"GIGANTAMAX": PGMachv.GIGANTAMAX,

View File

@ -3,7 +3,6 @@ import { PokemonInfoTranslationEntries } from "#app/interfaces/locales";
export const pokemonInfo: PokemonInfoTranslationEntries = {
Stat: {
"HP": "KP",
"HPStat": "KP",
"HPshortened": "KP",
"ATK": "Angriff",
"ATKshortened": "Ang",

View File

@ -89,9 +89,9 @@ export const PGMachv: AchievementTranslationEntries = {
name: "Master League Champion",
},
"TRANSFER_MAX_BATTLE_STAT": {
"TRANSFER_MAX_STAT_STAGE": {
name: "Teamwork",
description: "Baton pass to another party member with at least one stat maxed out",
description: "Baton pass to another party member with at least one stat stage maxed out",
},
"MAX_FRIENDSHIP": {
name: "Friendmaxxing",

View File

@ -3,7 +3,7 @@ import { PokemonInfoTranslationEntries } from "#app/interfaces/locales";
export const pokemonInfo: PokemonInfoTranslationEntries = {
Stat: {
"HP": "Max. HP",
"HPshortened": "MaxHP",
"HPshortened": "HP",
"ATK": "Attack",
"ATKshortened": "Atk",
"DEF": "Defense",
@ -16,7 +16,6 @@ export const pokemonInfo: PokemonInfoTranslationEntries = {
"SPDshortened": "Spd",
"ACC": "Accuracy",
"EVA": "Evasiveness",
"HPStat": "HP"
},
Type: {

View File

@ -89,7 +89,7 @@ export const PGMachv: AchievementTranslationEntries = {
name: "Campeón Liga Master",
},
"TRANSFER_MAX_BATTLE_STAT": {
"TRANSFER_MAX_STAT_STAGE": {
name: "Trabajo en Equipo",
description: "Haz relevo a otro miembro del equipo con al menos una estadística al máximo.",
},

View File

@ -89,7 +89,7 @@ export const PGMachv: AchievementTranslationEntries = {
name: "Master Maitre de la Ligue",
},
"TRANSFER_MAX_BATTLE_STAT": {
"TRANSFER_MAX_STAT_STAGE": {
name: "Travail déquipe",
description: "Utiliser Relais avec au moins une statistique montée à fond",
},
@ -363,7 +363,7 @@ export const PGFachv: AchievementTranslationEntries = {
name: "Master Maitresse de la Ligue",
},
"TRANSFER_MAX_BATTLE_STAT": {
"TRANSFER_MAX_STAT_STAGE": {
name: "Travail déquipe",
description: "Utiliser Relais avec au moins une statistique montée à fond",
},

View File

@ -89,7 +89,7 @@ export const PGMachv: AchievementTranslationEntries = {
name: "Campione Lega Assoluta",
},
"TRANSFER_MAX_BATTLE_STAT": {
"TRANSFER_MAX_STAT_STAGE": {
name: "Lavoro di Squadra",
description: "Trasferisci almeno sei bonus statistiche tramite staffetta",
},

View File

@ -89,7 +89,7 @@ export const PGMachv: AchievementTranslationEntries = {
name: "マスターリーグチャンピオン",
},
"TRANSFER_MAX_BATTLE_STAT": {
"TRANSFER_MAX_STAT_STAGE": {
name: "同力",
description: "少なくとも 一つの 能力を 最大まで あげて 他の 手持ちポケモンに バトンタッチする",
},

View File

@ -89,7 +89,7 @@ export const PGMachv: AchievementTranslationEntries = {
name: "마스터 리그 챔피언",
},
"TRANSFER_MAX_BATTLE_STAT": {
"TRANSFER_MAX_STAT_STAGE": {
name: "팀워크",
description: "한 개 이상의 능력치가 최대 랭크일 때 배턴터치 사용",
},

View File

@ -89,7 +89,7 @@ export const PGMachv: AchievementTranslationEntries = {
name: "Fita de Diamante",
},
"TRANSFER_MAX_BATTLE_STAT": {
"TRANSFER_MAX_STAT_STAGE": {
name: "Trabalho em Equipe",
description: "Use Baton Pass com pelo menos um atributo aumentado ao máximo",
},
@ -364,7 +364,7 @@ export const PGFachv: AchievementTranslationEntries = {
name: "Fita de Diamante",
},
"TRANSFER_MAX_BATTLE_STAT": {
"TRANSFER_MAX_STAT_STAGE": {
name: "Trabalho em Equipe",
description: "Use Baton Pass com pelo menos um atributo aumentado ao máximo",
},

View File

@ -89,7 +89,7 @@ export const PGMachv: AchievementTranslationEntries = {
name: "大师球联盟冠军",
},
"TRANSFER_MAX_BATTLE_STAT": {
"TRANSFER_MAX_STAT_STAGE": {
name: "团队协作",
description: "在一项属性强化至最大时用接力棒传递给其他宝可梦",
},

View File

@ -3,7 +3,7 @@ import { PokemonInfoTranslationEntries } from "#app/interfaces/locales";
export const pokemonInfo: PokemonInfoTranslationEntries = {
Stat: {
"HP": "最大HP",
"HPshortened": "最大HP",
"HPshortened": "HP",
"ATK": "攻击",
"ATKshortened": "攻击",
"DEF": "防御",

View File

@ -89,7 +89,7 @@ export const PGMachv: AchievementTranslationEntries = {
name: "大師球聯盟冠軍",
},
"TRANSFER_MAX_BATTLE_STAT": {
"TRANSFER_MAX_STAT_STAGE": {
name: "團隊協作",
description: "在一項屬性強化至最大時用接力棒傳遞給其他寶可夢",
},

View File

@ -26,7 +26,7 @@ import { BerryType } from "#enums/berry-type";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import { getPokemonNameWithAffix } from "#app/messages.js";
import { PermanentStat, TEMP_BATTLE_STATS, TempBattleStat, Stat } from "#app/enums/stat";
import { PermanentStat, TEMP_BATTLE_STATS, TempBattleStat, Stat, getStatKey } from "#app/enums/stat";
const outputModifierData = false;
const useMaxWeightForOutput = false;
@ -443,7 +443,7 @@ export class TempStatStageBoosterModifierType extends ModifierType implements Ge
}
getDescription(_scene: BattleScene): string {
return i18next.t("modifierType:ModifierType.TempStatStageBoosterModifierType.description", { stat: i18next.t(`pokemonInfo:Stat.${Stat[this.stat]}`) });
return i18next.t("modifierType:ModifierType.TempStatStageBoosterModifierType.description", { stat: i18next.t(getStatKey(this.stat)) });
}
getPregenArgs(): any[] {
@ -609,7 +609,7 @@ export class BaseStatBoosterModifierType extends PokemonHeldItemModifierType imp
}
getDescription(_scene: BattleScene): string {
return i18next.t("modifierType:ModifierType.BaseStatBoosterModifierType.description", { stat: i18next.t(`pokemonInfo:Stat.${Stat[this.stat]}`) });
return i18next.t("modifierType:ModifierType.BaseStatBoosterModifierType.description", { stat: i18next.t(getStatKey(this.stat)) });
}
getPregenArgs(): any[] {

View File

@ -1,10 +1,9 @@
import BattleScene, { bypassLogin } from "./battle-scene";
import { default as Pokemon, PlayerPokemon, EnemyPokemon, PokemonMove, MoveResult, DamageResult, FieldPosition, HitResult, TurnMove } from "./field/pokemon";
import * as Utils from "./utils";
import { allMoves, applyMoveAttrs, BypassSleepAttr, ChargeAttr, applyFilteredMoveAttrs, HitsTagAttr, MissEffectAttr, MoveAttr, MoveEffectAttr, MoveFlags, MultiHitAttr, OverrideMoveEffectAttr, MoveTarget, getMoveTargets, MoveTargetSet, MoveEffectTrigger, CopyMoveAttr, AttackMove, SelfStatusMove, PreMoveMessageAttr, HealStatusEffectAttr, NoEffectAttr, BypassRedirectAttr, FixedDamageAttr, PostVictoryStatChangeAttr, ForceSwitchOutAttr, VariableTargetAttr, IncrementMovePriorityAttr, MoveHeaderAttr } from "./data/move";
import { allMoves, applyMoveAttrs, BypassSleepAttr, ChargeAttr, applyFilteredMoveAttrs, HitsTagAttr, MissEffectAttr, MoveAttr, MoveEffectAttr, MoveFlags, MultiHitAttr, OverrideMoveEffectAttr, MoveTarget, getMoveTargets, MoveTargetSet, MoveEffectTrigger, CopyMoveAttr, AttackMove, SelfStatusMove, PreMoveMessageAttr, HealStatusEffectAttr, NoEffectAttr, BypassRedirectAttr, FixedDamageAttr, PostVictoryStatStageChangeAttr, ForceSwitchOutAttr, VariableTargetAttr, IncrementMovePriorityAttr, MoveHeaderAttr } from "./data/move";
import { Mode } from "./ui/ui";
import { Command } from "./ui/command-ui-handler";
import { Stat } from "./data/pokemon-stat";
import { BerryModifier, ContactHeldItemTransferChanceModifier, EnemyAttackStatusEffectChanceModifier, EnemyPersistentModifier, EnemyStatusEffectHealChanceModifier, EnemyTurnHealModifier, ExpBalanceModifier, ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, FlinchChanceModifier, HealingBoosterModifier, HitHealModifier, LapsingPersistentModifier, MapModifier, Modifier, MultipleParticipantExpBonusModifier, PokemonExpBoosterModifier, PokemonHeldItemModifier, PokemonInstantReviveModifier, SwitchEffectTransferModifier, TurnHealModifier, TurnHeldItemTransferModifier, MoneyMultiplierModifier, MoneyInterestModifier, IvScannerModifier, LapsingPokemonHeldItemModifier, PokemonMultiHitModifier, overrideModifiers, overrideHeldItems, BypassSpeedChanceModifier, TurnStatusEffectModifier, ResetNegativeStatStageModifier } from "./modifier/modifier";
import PartyUiHandler, { PartyOption, PartyUiMode } from "./ui/party-ui-handler";
import { doPokeballBounceAnim, getPokeballAtlasKey, getPokeballCatchMultiplier, getPokeballTintColor, PokeballType } from "./data/pokeball";
@ -14,7 +13,6 @@ import { SummaryUiMode } from "./ui/summary-ui-handler";
import EvolutionSceneHandler from "./ui/evolution-scene-handler";
import { EvolutionPhase } from "./evolution-phase";
import { Phase } from "./phase";
import { BattleStat, getBattleStatLevelChangeDescription, getBattleStatName } from "./data/battle-stat";
import { biomeLinks, getBiomeName } from "./data/biomes";
import { ModifierTier } from "./modifier/modifier-tier";
import { FusePokemonModifierType, ModifierPoolType, ModifierType, ModifierTypeFunc, ModifierTypeOption, PokemonModifierType, PokemonMoveModifierType, PokemonPpRestoreModifierType, PokemonPpUpModifierType, RememberMoveModifierType, TmModifierType, getDailyRunStarterModifiers, getEnemyBuffModifierForWave, getModifierType, getPlayerModifierTypeOptions, getPlayerShopModifierTypeOptionsForWave, modifierTypes, regenerateModifierPoolThresholds } from "./modifier/modifier-type";
@ -25,7 +23,7 @@ import { Starter } from "./ui/starter-select-ui-handler";
import { Gender } from "./data/gender";
import { Weather, WeatherType, getRandomWeatherType, getTerrainBlockMessage, getWeatherDamageMessage, getWeatherLapseMessage } from "./data/weather";
import { ArenaTagSide, ArenaTrapTag, MistTag, TrickRoomTag } from "./data/arena-tag";
import { CheckTrappedAbAttr, PostAttackAbAttr, PostBattleAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreSwitchOutAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, RedirectMoveAbAttr, BlockRedirectAbAttr, RunSuccessAbAttr, StatChangeMultiplierAbAttr, SuppressWeatherEffectAbAttr, SyncEncounterNatureAbAttr, applyAbAttrs, applyCheckTrappedAbAttrs, applyPostAttackAbAttrs, applyPostBattleAbAttrs, applyPostDefendAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreSwitchOutAbAttrs, applyPreWeatherEffectAbAttrs, IncrementMovePriorityAbAttr, applyPostVictoryAbAttrs, PostVictoryAbAttr, BlockNonDirectDamageAbAttr as BlockNonDirectDamageAbAttr, applyPostKnockOutAbAttrs, PostKnockOutAbAttr, PostBiomeChangeAbAttr, applyPostFaintAbAttrs, PostFaintAbAttr, IncreasePpAbAttr, PostStatChangeAbAttr, applyPostStatChangeAbAttrs, AlwaysHitAbAttr, PreventBerryUseAbAttr, StatChangeCopyAbAttr, PokemonTypeChangeAbAttr, applyPreAttackAbAttrs, applyPostMoveUsedAbAttrs, PostMoveUsedAbAttr, MaxMultiHitAbAttr, HealFromBerryUseAbAttr, IgnoreMoveEffectsAbAttr, BlockStatusDamageAbAttr, BypassSpeedChanceAbAttr, AddSecondStrikeAbAttr } from "./data/ability";
import { CheckTrappedAbAttr, PostAttackAbAttr, PostBattleAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreSwitchOutAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, RedirectMoveAbAttr, BlockRedirectAbAttr, RunSuccessAbAttr, StatStageChangeMultiplierAbAttr, SuppressWeatherEffectAbAttr, SyncEncounterNatureAbAttr, applyAbAttrs, applyCheckTrappedAbAttrs, applyPostAttackAbAttrs, applyPostBattleAbAttrs, applyPostDefendAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatStageChangeAbAttrs, applyPreSwitchOutAbAttrs, applyPreWeatherEffectAbAttrs, IncrementMovePriorityAbAttr, applyPostVictoryAbAttrs, PostVictoryAbAttr, BlockNonDirectDamageAbAttr as BlockNonDirectDamageAbAttr, applyPostKnockOutAbAttrs, PostKnockOutAbAttr, PostBiomeChangeAbAttr, applyPostFaintAbAttrs, PostFaintAbAttr, IncreasePpAbAttr, PostStatStageChangeAbAttr, applyPostStatStageChangeAbAttrs, AlwaysHitAbAttr, PreventBerryUseAbAttr, StatStageChangeCopyAbAttr, PokemonTypeChangeAbAttr, applyPreAttackAbAttrs, applyPostMoveUsedAbAttrs, PostMoveUsedAbAttr, MaxMultiHitAbAttr, HealFromBerryUseAbAttr, IgnoreMoveEffectsAbAttr, BlockStatusDamageAbAttr, BypassSpeedChanceAbAttr, AddSecondStrikeAbAttr } from "./data/ability";
import { Unlockables, getUnlockableName } from "./system/unlockables";
import { getBiomeKey } from "./field/arena";
import { BattleType, BattlerIndex, TurnCommand } from "./battle";
@ -66,6 +64,7 @@ import { Species } from "#enums/species";
import { TrainerType } from "#enums/trainer-type";
import { applyChallenges, ChallengeType } from "./data/challenge";
import { pokemonEvolutions } from "./data/pokemon-evolutions";
import { Stat, BattleStat, getStatKey, getStatStageChangeDescriptionKey } from "#enums/stat";
const { t } = i18next;
@ -718,8 +717,8 @@ export abstract class FieldPhase extends BattlePhase {
}, this.scene.currentBattle.turn, this.scene.waveSeed);
orderedTargets.sort((a: Pokemon, b: Pokemon) => {
const aSpeed = a?.getBattleStat(Stat.SPD) || 0;
const bSpeed = b?.getBattleStat(Stat.SPD) || 0;
const aSpeed = a?.getEffectiveStat(Stat.SPD) || 0;
const bSpeed = b?.getEffectiveStat(Stat.SPD) || 0;
return bSpeed - aSpeed;
});
@ -3449,22 +3448,22 @@ export class ShowAbilityPhase extends PokemonPhase {
export type StatChangeCallback = (target: Pokemon | null, changed: BattleStat[], relativeChanges: number[]) => void;
export class StatChangePhase extends PokemonPhase {
export class StatStageChangePhase extends PokemonPhase { // TODO: BattleStat
private stats: BattleStat[];
private selfTarget: boolean;
private levels: integer;
private stages: integer;
private showMessage: boolean;
private ignoreAbilities: boolean;
private canBeCopied: boolean;
private onChange: StatChangeCallback | null;
constructor(scene: BattleScene, battlerIndex: BattlerIndex, selfTarget: boolean, stats: BattleStat[], levels: 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: StatChangeCallback | null = null) {
super(scene, battlerIndex);
this.selfTarget = selfTarget;
this.stats = stats;
this.levels = levels;
this.stages = stages;
this.showMessage = showMessage;
this.ignoreAbilities = ignoreAbilities;
this.canBeCopied = canBeCopied;
@ -3490,21 +3489,21 @@ export class StatChangePhase extends PokemonPhase {
const filteredStats = this.stats.map(s => s !== BattleStat.RAND ? s : this.getRandomStat()).filter(stat => {
const cancelled = new Utils.BooleanHolder(false);
if (!this.selfTarget && this.levels < 0) {
if (!this.selfTarget && this.stages < 0) {
this.scene.arena.applyTagsForSide(MistTag, pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, cancelled);
}
if (!cancelled.value && !this.selfTarget && this.levels < 0) {
applyPreStatChangeAbAttrs(ProtectStatAbAttr, this.getPokemon(), stat, cancelled);
if (!cancelled.value && !this.selfTarget && this.stages < 0) {
applyPreStatStageChangeAbAttrs(ProtectStatAbAttr, this.getPokemon(), stat, cancelled);
}
return !cancelled.value;
});
const levels = new Utils.IntegerHolder(this.levels);
const levels = new Utils.IntegerHolder(this.stages);
if (!this.ignoreAbilities) {
applyAbAttrs(StatChangeMultiplierAbAttr, pokemon, null, levels);
applyAbAttrs(StatStageChangeMultiplierAbAttr, pokemon, null, levels);
}
const battleStats = this.getPokemon().summonData.battleStats;
@ -3526,20 +3525,20 @@ export class StatChangePhase extends PokemonPhase {
if (levels.value > 0 && this.canBeCopied) {
for (const opponent of pokemon.getOpponents()) {
applyAbAttrs(StatChangeCopyAbAttr, opponent, null, this.stats, levels.value);
applyAbAttrs(StatStageChangeCopyAbAttr, opponent, null, this.stats, levels.value);
}
}
applyPostStatChangeAbAttrs(PostStatChangeAbAttr, pokemon, filteredStats, this.levels, this.selfTarget);
applyPostStatStageChangeAbAttrs(PostStatStageChangeAbAttr, pokemon, filteredStats, this.levels, 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 StatChangePhase && p.battlerIndex === this.battlerIndex);
if (!(existingPhase instanceof StatChangePhase)) {
const existingPhase = this.scene.findPhase(p => p instanceof StatStageChangePhase && p.battlerIndex === this.battlerIndex);
if (!(existingPhase instanceof StatStageChangePhase)) {
// Apply White Herb if needed
const whiteHerb = this.scene.applyModifier(ResetNegativeStatStageModifier, this.player, pokemon) as PokemonResetNegativeStatStageModifier;
const whiteHerb = this.scene.applyModifier(ResetNegativeStatStageModifier, this.player, pokemon) as ResetNegativeStatStageModifier;
// If the White Herb was applied, consume it
if (whiteHerb) {
--whiteHerb.stackCount;
whiteHerb.stackCount--;
if (whiteHerb.stackCount <= 0) {
this.scene.removeModifier(whiteHerb);
}
@ -3563,7 +3562,7 @@ export class StatChangePhase extends PokemonPhase {
// On increase, show the red sprite located at ATK
// On decrease, show the blue sprite located at SPD
const spriteColor = levels.value >= 1 ? BattleStat[BattleStat.ATK].toLowerCase() : BattleStat[BattleStat.SPD].toLowerCase();
const spriteColor = levels.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);
@ -3657,11 +3656,19 @@ export class StatChangePhase extends PokemonPhase {
if (relLevelStats.length > 1) {
statsFragment = relLevelStats.length >= 5
? i18next.t("battle:stats")
: `${relLevelStats.slice(0, -1).map(s => getBattleStatName(s)).join(", ")}${relLevelStats.length > 2 ? "," : ""} ${i18next.t("battle:statsAnd")} ${getBattleStatName(relLevelStats[relLevelStats.length - 1])}`;
messages.push(getBattleStatLevelChangeDescription(getPokemonNameWithAffix(this.getPokemon()), statsFragment, Math.abs(parseInt(rl)), levels >= 1,relLevelStats.length));
: `${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), {
pokemonNameWithAffix: getPokemonNameWithAffix(this.getPokemon()),
stats: statsFragment,
count: relLevelStats.length
}));
} else {
statsFragment = getBattleStatName(relLevelStats[0]);
messages.push(getBattleStatLevelChangeDescription(getPokemonNameWithAffix(this.getPokemon()), statsFragment, Math.abs(parseInt(rl)), levels >= 1,relLevelStats.length));
statsFragment = i18next.t(getStatKey(relLevelStats[0]));
messages.push(i18next.t(getStatStageChangeDescriptionKey(Math.abs(parseInt(rl)), levels >= 1), {
pokemonNameWithAffix: getPokemonNameWithAffix(this.getPokemon()),
stats: statsFragment,
count: relLevelStats.length
}));
}
});
@ -3985,7 +3992,7 @@ export class FaintPhase extends PokemonPhase {
if (defeatSource?.isOnField()) {
applyPostVictoryAbAttrs(PostVictoryAbAttr, defeatSource);
const pvmove = allMoves[pokemon.turnData.attacksReceived[0].move];
const pvattrs = pvmove.getAttrs(PostVictoryStatChangeAttr);
const pvattrs = pvmove.getAttrs(PostVictoryStatStageChangeAttr);
if (pvattrs.length) {
for (const pvattr of pvattrs) {
pvattr.applyPostVictory(defeatSource, defeatSource, pvmove);

View File

@ -8,6 +8,7 @@ import { PlayerGender } from "#enums/player-gender";
import { ParseKeys } from "i18next";
import { Challenge, FreshStartChallenge, SingleGenerationChallenge, SingleTypeChallenge } from "#app/data/challenge.js";
import { ConditionFn } from "#app/@types/common.js";
import { getShortenedStatKey, Stat } from "#app/enums/stat.js";
export enum AchvTier {
COMMON,
@ -178,13 +179,13 @@ export function getAchievementDescription(localizationKey: string): string {
case "10000_DMG":
return i18next.t(`${genderPrefix}achv:DamageAchv.description` as ParseKeys, {"damageAmount": achvs._10000_DMG.damageAmount.toLocaleString("en-US")});
case "250_HEAL":
return i18next.t(`${genderPrefix}achv:HealAchv.description` as ParseKeys, {"healAmount": achvs._250_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t("pokemonInfo:Stat.HPshortened")});
return i18next.t(`${genderPrefix}achv:HealAchv.description` as ParseKeys, {"healAmount": achvs._250_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t(getShortenedStatKey(Stat.HP)) });
case "1000_HEAL":
return i18next.t(`${genderPrefix}achv:HealAchv.description` as ParseKeys, {"healAmount": achvs._1000_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t("pokemonInfo:Stat.HPshortened")});
return i18next.t(`${genderPrefix}achv:HealAchv.description` as ParseKeys, {"healAmount": achvs._1000_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t(getShortenedStatKey(Stat.HP)) });
case "2500_HEAL":
return i18next.t(`${genderPrefix}achv:HealAchv.description` as ParseKeys, {"healAmount": achvs._2500_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t("pokemonInfo:Stat.HPshortened")});
return i18next.t(`${genderPrefix}achv:HealAchv.description` as ParseKeys, {"healAmount": achvs._2500_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t(getShortenedStatKey(Stat.HP)) });
case "10000_HEAL":
return i18next.t(`${genderPrefix}achv:HealAchv.description` as ParseKeys, {"healAmount": achvs._10000_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t("pokemonInfo:Stat.HPshortened")});
return i18next.t(`${genderPrefix}achv:HealAchv.description` as ParseKeys, {"healAmount": achvs._10000_HEAL.healAmount.toLocaleString("en-US"), "HP": i18next.t(getShortenedStatKey(Stat.HP)) });
case "LV_100":
return i18next.t(`${genderPrefix}achv:LevelAchv.description` as ParseKeys, {"level": achvs.LV_100.level});
case "LV_250":
@ -201,8 +202,8 @@ export function getAchievementDescription(localizationKey: string): string {
return i18next.t(`${genderPrefix}achv:RibbonAchv.description` as ParseKeys, {"ribbonAmount": achvs._75_RIBBONS.ribbonAmount.toLocaleString("en-US")});
case "100_RIBBONS":
return i18next.t(`${genderPrefix}achv:RibbonAchv.description` as ParseKeys, {"ribbonAmount": achvs._100_RIBBONS.ribbonAmount.toLocaleString("en-US")});
case "TRANSFER_MAX_BATTLE_STAT":
return i18next.t(`${genderPrefix}achv:TRANSFER_MAX_BATTLE_STAT.description` as ParseKeys);
case "TRANSFER_MAX_STAT_STAGE":
return i18next.t(`${genderPrefix}achv:TRANSFER_MAX_STAT_STAGE.description` as ParseKeys);
case "MAX_FRIENDSHIP":
return i18next.t(`${genderPrefix}achv:MAX_FRIENDSHIP.description` as ParseKeys);
case "MEGA_EVOLVE":
@ -309,7 +310,7 @@ export const achvs = {
_50_RIBBONS: new RibbonAchv("50_RIBBONS","", 50, "ultra_ribbon", 50).setSecret(true),
_75_RIBBONS: new RibbonAchv("75_RIBBONS","", 75, "rogue_ribbon", 75).setSecret(true),
_100_RIBBONS: new RibbonAchv("100_RIBBONS","", 100, "master_ribbon", 100).setSecret(true),
TRANSFER_MAX_BATTLE_STAT: new Achv("TRANSFER_MAX_BATTLE_STAT","", "TRANSFER_MAX_BATTLE_STAT.description","baton", 20),
TRANSFER_MAX_STAT_STAGE: new Achv("TRANSFER_MAX_STAT_STAGE","", "TRANSFER_MAX_STAT_STAGE.description","baton", 20),
MAX_FRIENDSHIP: new Achv("MAX_FRIENDSHIP", "", "MAX_FRIENDSHIP.description","soothe_bell", 25),
MEGA_EVOLVE: new Achv("MEGA_EVOLVE", "", "MEGA_EVOLVE.description","mega_bracelet", 50),
GIGANTAMAX: new Achv("GIGANTAMAX", "", "GIGANTAMAX.description","dynamax_band", 50),

View File

@ -1,4 +1,4 @@
import { BattleStat } from "#app/data/battle-stat.js";
import { Stat } from "#enums/stat";
import { Abilities } from "#app/enums/abilities.js";
import { Moves } from "#app/enums/moves.js";
import { Species } from "#app/enums/species.js";
@ -35,7 +35,7 @@ describe("Abilities - COSTAR", () => {
test(
"ability copies positive stat changes",
"ability copies positive stat stages",
async () => {
game.override.enemyAbility(Abilities.BALL_FETCH);
@ -48,8 +48,8 @@ describe("Abilities - COSTAR", () => {
game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH));
await game.toNextTurn();
expect(leftPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(+2);
expect(rightPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(0);
expect(leftPokemon.getStatStage(Stat.SPATK)).toBe(2);
expect(rightPokemon.getStatStage(Stat.SPATK)).toBe(0);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.phaseInterceptor.to(CommandPhase);
@ -57,14 +57,14 @@ describe("Abilities - COSTAR", () => {
await game.phaseInterceptor.to(MessagePhase);
[leftPokemon, rightPokemon] = game.scene.getPlayerField();
expect(leftPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(+2);
expect(rightPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(+2);
expect(leftPokemon.getStatStage(Stat.SPATK)).toBe(2);
expect(rightPokemon.getStatStage(Stat.SPATK)).toBe(2);
},
TIMEOUT,
);
test(
"ability copies negative stat changes",
"ability copies negative stat stages",
async () => {
game.override.enemyAbility(Abilities.INTIMIDATE);
@ -72,8 +72,8 @@ describe("Abilities - COSTAR", () => {
let [leftPokemon, rightPokemon] = game.scene.getPlayerField();
expect(leftPokemon.summonData.battleStats[BattleStat.ATK]).toBe(-2);
expect(leftPokemon.summonData.battleStats[BattleStat.ATK]).toBe(-2);
expect(leftPokemon.getStatStage(Stat.ATK)).toBe(-2);
expect(leftPokemon.getStatStage(Stat.ATK)).toBe(-2);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.phaseInterceptor.to(CommandPhase);
@ -81,8 +81,8 @@ describe("Abilities - COSTAR", () => {
await game.phaseInterceptor.to(MessagePhase);
[leftPokemon, rightPokemon] = game.scene.getPlayerField();
expect(leftPokemon.summonData.battleStats[BattleStat.ATK]).toBe(-2);
expect(rightPokemon.summonData.battleStats[BattleStat.ATK]).toBe(-2);
expect(leftPokemon.getStatStage(Stat.ATK)).toBe(-2);
expect(rightPokemon.getStatStage(Stat.ATK)).toBe(-2);
},
TIMEOUT,
);

View File

@ -13,7 +13,7 @@ import { Species } from "#enums/species";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { SPLASH_ONLY } from "../utils/testUtils";
import { BattleStat } from "#app/data/battle-stat.js";
import { Stat } from "#enums/stat";
import { StatusEffect } from "#app/enums/status-effect.js";
import Pokemon from "#app/field/pokemon.js";
@ -95,7 +95,7 @@ describe("Abilities - Gulp Missile", () => {
expect(cramorant.formIndex).toBe(GULPING_FORM);
});
it("deals ¼ of the attacker's maximum HP when hit by a damaging attack", async () => {
it("deals 1/4 of the attacker's maximum HP when hit by a damaging attack", async () => {
game.override.enemyMoveset(Array(4).fill(Moves.TACKLE));
await game.startBattle([Species.CRAMORANT]);
@ -127,7 +127,7 @@ describe("Abilities - Gulp Missile", () => {
expect(cramorant.formIndex).toBe(GULPING_FORM);
});
it("lowers the attacker's Defense by 1 stage when hit in Gulping form", async () => {
it("lowers attacker's DEF stat stage by 1 when hit in Gulping form", async () => {
game.override.enemyMoveset(Array(4).fill(Moves.TACKLE));
await game.startBattle([Species.CRAMORANT]);
@ -146,7 +146,7 @@ describe("Abilities - Gulp Missile", () => {
await game.phaseInterceptor.to(TurnEndPhase);
expect(enemy.damageAndUpdate).toHaveReturnedWith(getEffectDamage(enemy));
expect(enemy.summonData.battleStats[BattleStat.DEF]).toBe(-1);
expect(enemy.getStatStage(Stat.DEF)).toBe(-1);
expect(cramorant.getTag(BattlerTagType.GULP_MISSILE_ARROKUDA)).toBeUndefined();
expect(cramorant.formIndex).toBe(NORMAL_FORM);
});
@ -207,7 +207,7 @@ describe("Abilities - Gulp Missile", () => {
await game.phaseInterceptor.to(TurnEndPhase);
expect(enemy.hp).toBe(enemyHpPreEffect);
expect(enemy.summonData.battleStats[BattleStat.DEF]).toBe(-1);
expect(enemy.getStatStage(Stat.DEF)).toBe(-1);
expect(cramorant.getTag(BattlerTagType.GULP_MISSILE_ARROKUDA)).toBeUndefined();
expect(cramorant.formIndex).toBe(NORMAL_FORM);
});

View File

@ -41,13 +41,13 @@ describe("Abilities - Hustle", () => {
const pikachu = game.scene.getPlayerPokemon()!;
const atk = pikachu.stats[Stat.ATK];
vi.spyOn(pikachu, "getBattleStat");
vi.spyOn(pikachu, "getEffectiveStat");
game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE));
await game.move.forceHit();
await game.phaseInterceptor.to(DamagePhase);
expect(pikachu.getBattleStat).toHaveReturnedWith(atk * 1.5);
expect(pikachu.getEffectiveStat).toHaveReturnedWith(atk * 1.5);
});
it("lowers the accuracy of the user's physical moves by 20%", async () => {
@ -67,13 +67,13 @@ describe("Abilities - Hustle", () => {
const pikachu = game.scene.getPlayerPokemon()!;
const spatk = pikachu.stats[Stat.SPATK];
vi.spyOn(pikachu, "getBattleStat");
vi.spyOn(pikachu, "getEffectiveStat");
vi.spyOn(pikachu, "getAccuracyMultiplier");
game.doAttack(getMovePosition(game.scene, 0, Moves.GIGA_DRAIN));
await game.phaseInterceptor.to(DamagePhase);
expect(pikachu.getBattleStat).toHaveReturnedWith(spatk);
expect(pikachu.getEffectiveStat).toHaveReturnedWith(spatk);
expect(pikachu.getAccuracyMultiplier).toHaveReturnedWith(1);
});

View File

@ -2,12 +2,9 @@ import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
import Phaser from "phaser";
import GameManager from "#test/utils/gameManager";
import { Mode } from "#app/ui/ui";
import { BattleStat } from "#app/data/battle-stat";
import { generateStarter, getMovePosition } from "#test/utils/gameManagerUtils";
import { Command } from "#app/ui/command-ui-handler";
import { Status, StatusEffect } from "#app/data/status-effect";
import { GameModes, getGameMode } from "#app/game-mode";
import { CommandPhase, DamagePhase, EncounterPhase, EnemyCommandPhase, SelectStarterPhase, TurnInitPhase } from "#app/phases";
import { Stat } from "#enums/stat";
import { getMovePosition } from "#test/utils/gameManagerUtils";
import { CommandPhase, TurnInitPhase } from "#app/phases";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
@ -38,7 +35,7 @@ describe("Abilities - Intimidate", () => {
game.override.enemyMoveset(SPLASH_ONLY);
});
it("single - wild with switch", async () => {
it("should lower ATK stat stage by 1 of enemy Pokemon on entry and player switch", async () => {
await game.runToSummon([Species.MIGHTYENA, Species.POOCHYENA]);
game.onNextPrompt(
"CheckSwitchPhase",
@ -50,108 +47,25 @@ describe("Abilities - Intimidate", () => {
() => game.isCurrentPhase(CommandPhase) || game.isCurrentPhase(TurnInitPhase)
);
await game.phaseInterceptor.to(CommandPhase, false);
expect(game.scene.getParty()[0].species.speciesId).toBe(Species.MIGHTYENA);
let battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
let playerPokemon = game.scene.getPlayerPokemon();
const enemyPokemon = game.scene.getEnemyPokemon();
expect(playerPokemon!.species.speciesId).toBe(Species.MIGHTYENA);
expect(enemyPokemon!.getStatStage(Stat.ATK)).toBe(-1);
expect(playerPokemon!.getStatStage(Stat.ATK)).toBe(-1);
game.doSwitchPokemon(1);
await game.phaseInterceptor.run(CommandPhase);
await game.phaseInterceptor.to(CommandPhase);
expect(game.scene.getParty()[0].species.speciesId).toBe(Species.POOCHYENA);
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.ATK]).toBe(0);
battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-2);
playerPokemon = game.scene.getPlayerPokemon();
expect(playerPokemon!.species.speciesId).toBe(Species.POOCHYENA);
expect(playerPokemon!.getStatStage(Stat.ATK)).toBe(0);
expect(enemyPokemon!.getStatStage(Stat.ATK)).toBe(-2);
}, 20000);
it("single - boss should only trigger once then switch", async () => {
game.override.startingWave(10);
await game.runToSummon([Species.MIGHTYENA, Species.POOCHYENA]);
game.onNextPrompt(
"CheckSwitchPhase",
Mode.CONFIRM,
() => {
game.setMode(Mode.MESSAGE);
game.endPhase();
},
() => game.isCurrentPhase(CommandPhase) || game.isCurrentPhase(TurnInitPhase)
);
await game.phaseInterceptor.to(CommandPhase, false);
let battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
game.doSwitchPokemon(1);
await game.phaseInterceptor.run(CommandPhase);
await game.phaseInterceptor.to(CommandPhase);
expect(game.scene.getParty()[0].species.speciesId).toBe(Species.POOCHYENA);
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.ATK]).toBe(0);
battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-2);
}, 20000);
it("single - trainer should only trigger once with switch", async () => {
game.override.startingWave(5);
await game.runToSummon([Species.MIGHTYENA, Species.POOCHYENA]);
game.onNextPrompt(
"CheckSwitchPhase",
Mode.CONFIRM,
() => {
game.setMode(Mode.MESSAGE);
game.endPhase();
},
() => game.isCurrentPhase(CommandPhase) || game.isCurrentPhase(TurnInitPhase)
);
await game.phaseInterceptor.to(CommandPhase, false);
let battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
game.doSwitchPokemon(1);
await game.phaseInterceptor.run(CommandPhase);
await game.phaseInterceptor.to(CommandPhase);
expect(game.scene.getParty()[0].species.speciesId).toBe(Species.POOCHYENA);
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.ATK]).toBe(0);
battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-2);
}, 200000);
it("double - trainer should only trigger once per pokemon", async () => {
game.override.battleType("double");
game.override.startingWave(5);
await game.runToSummon([Species.MIGHTYENA, Species.POOCHYENA]);
game.onNextPrompt(
"CheckSwitchPhase",
Mode.CONFIRM,
() => {
game.setMode(Mode.MESSAGE);
game.endPhase();
},
() => game.isCurrentPhase(CommandPhase) || game.isCurrentPhase(TurnInitPhase)
);
await game.phaseInterceptor.to(CommandPhase, false);
const battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-2);
const battleStatsOpponent2 = game.scene.currentBattle.enemyParty[1].summonData.battleStats;
expect(battleStatsOpponent2[BattleStat.ATK]).toBe(-2);
const battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-2);
const battleStatsPokemon2 = game.scene.getParty()[1].summonData.battleStats;
expect(battleStatsPokemon2[BattleStat.ATK]).toBe(-2);
}, 20000);
it("double - wild: should only trigger once per pokemon", async () => {
it("should lower ATK stat stage by 1 for every enemy Pokemon in a double battle on entry", async () => {
game.override.battleType("double");
game.override.startingWave(3);
await game.runToSummon([Species.MIGHTYENA, Species.POOCHYENA]);
@ -165,210 +79,60 @@ describe("Abilities - Intimidate", () => {
() => game.isCurrentPhase(CommandPhase) || game.isCurrentPhase(TurnInitPhase)
);
await game.phaseInterceptor.to(CommandPhase, false);
const battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-2);
const battleStatsOpponent2 = game.scene.currentBattle.enemyParty[1].summonData.battleStats;
expect(battleStatsOpponent2[BattleStat.ATK]).toBe(-2);
const battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-2);
const playerField = game.scene.getPlayerField()!;
const enemyField = game.scene.getEnemyPokemon()!;
const battleStatsPokemon2 = game.scene.getParty()[1].summonData.battleStats;
expect(battleStatsPokemon2[BattleStat.ATK]).toBe(-2);
expect(enemyField[0].getStatStage(Stat.ATK)).toBe(-2);
expect(enemyField[1].getStatStage(Stat.ATK)).toBe(-2);
expect(playerField[0].getStatStage(Stat.ATK)).toBe(-2);
expect(playerField[1].getStatStage(Stat.ATK)).toBe(-2);
}, 20000);
it("double - boss: should only trigger once per pokemon", async () => {
game.override.battleType("double");
game.override.startingWave(10);
await game.runToSummon([Species.MIGHTYENA, Species.POOCHYENA]);
game.onNextPrompt(
"CheckSwitchPhase",
Mode.CONFIRM,
() => {
game.setMode(Mode.MESSAGE);
game.endPhase();
},
() => game.isCurrentPhase(CommandPhase) || game.isCurrentPhase(TurnInitPhase)
);
await game.phaseInterceptor.to(CommandPhase, false);
const battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-2);
const battleStatsOpponent2 = game.scene.currentBattle.enemyParty[1].summonData.battleStats;
expect(battleStatsOpponent2[BattleStat.ATK]).toBe(-2);
const battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-2);
const battleStatsPokemon2 = game.scene.getParty()[1].summonData.battleStats;
expect(battleStatsPokemon2[BattleStat.ATK]).toBe(-2);
}, 20000);
it("single - wild next wave opp triger once, us: none", async () => {
game.override.startingWave(2);
game.override.moveset([Moves.AERIAL_ACE]);
await game.startBattle([Species.MIGHTYENA, Species.POOCHYENA]);
let battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
});
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
const movePosition = getMovePosition(game.scene, 0, Moves.AERIAL_ACE);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(DamagePhase);
await game.killPokemon(game.scene.currentBattle.enemyParty[0]);
expect(game.scene.currentBattle.enemyParty[0].isFainted()).toBe(true);
await game.toNextWave();
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-2);
battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.ATK]).toBe(0);
}, 20000);
it("single - wild next turn - no retrigger on next turn", async () => {
it("should not activate again if there is no switch or new entry", async () => {
game.override.startingWave(2);
game.override.moveset([Moves.SPLASH]);
await game.startBattle([Species.MIGHTYENA, Species.POOCHYENA]);
let battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
});
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
const movePosition = getMovePosition(game.scene, 0, Moves.AERIAL_ACE);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
console.log("===to new turn===");
const playerPokemon = game.scene.getPlayerPokemon()!;
const enemyPokemon = game.scene.getEnemyPokemon()!;
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-1);
expect(playerPokemon.getStatStage(Stat.ATK)).toBe(-1);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.toNextTurn();
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-1);
expect(playerPokemon.getStatStage(Stat.ATK)).toBe(-1);
}, 20000);
it("single - trainer should only trigger once and each time he switch", async () => {
it("should lower ATK stat stage by 1 for every switch", async () => {
game.override.moveset([Moves.SPLASH]);
game.override.enemyMoveset([Moves.VOLT_SWITCH, Moves.VOLT_SWITCH, Moves.VOLT_SWITCH, Moves.VOLT_SWITCH]);
game.override.startingWave(5);
await game.startBattle([Species.MIGHTYENA, Species.POOCHYENA]);
let battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
});
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
const movePosition = getMovePosition(game.scene, 0, Moves.AERIAL_ACE);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
console.log("===to new turn===");
await game.toNextTurn();
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-2);
battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.ATK]).toBe(0);
const playerPokemon = game.scene.getPlayerPokemon()!;
let enemyPokemon = game.scene.getEnemyPokemon()!;
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
});
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
const movePosition = getMovePosition(game.scene, 0, Moves.AERIAL_ACE);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
console.log("===to new turn===");
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-1);
expect(playerPokemon.getStatStage(Stat.ATK)).toBe(-1);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.toNextTurn();
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-3);
battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.ATK]).toBe(0);
enemyPokemon = game.scene.getEnemyPokemon()!;
expect(playerPokemon.getStatStage(Stat.ATK)).toBe(-2);
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(0);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.toNextTurn();
enemyPokemon = game.scene.getEnemyPokemon()!;
expect(playerPokemon.getStatStage(Stat.ATK)).toBe(-3);
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(0);
}, 200000);
it("single - trainer should only trigger once whatever turn we are", async () => {
game.override.moveset([Moves.SPLASH]);
game.override.enemyMoveset(SPLASH_ONLY);
game.override.startingWave(5);
await game.startBattle([Species.MIGHTYENA, Species.POOCHYENA]);
let battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
});
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
const movePosition = getMovePosition(game.scene, 0, Moves.AERIAL_ACE);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
console.log("===to new turn===");
await game.toNextTurn();
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
});
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
const movePosition = getMovePosition(game.scene, 0, Moves.AERIAL_ACE);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
console.log("===to new turn===");
await game.toNextTurn();
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
}, 20000);
it("double - wild vs only 1 on player side", async () => {
game.override.battleType("double");
game.override.startingWave(3);
await game.runToSummon([Species.MIGHTYENA]);
await game.phaseInterceptor.to(CommandPhase, false);
const battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
const battleStatsOpponent2 = game.scene.currentBattle.enemyParty[1].summonData.battleStats;
expect(battleStatsOpponent2[BattleStat.ATK]).toBe(-1);
const battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-2);
}, 20000);
it("double - wild vs only 1 alive on player side", async () => {
game.override.battleType("double");
game.override.startingWave(3);
await game.runToTitle();
game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
game.scene.gameMode = getGameMode(GameModes.CLASSIC);
const starters = generateStarter(game.scene, [Species.MIGHTYENA, Species.POOCHYENA]);
const selectStarterPhase = new SelectStarterPhase(game.scene);
game.scene.pushPhase(new EncounterPhase(game.scene, false));
selectStarterPhase.initBattle(starters);
game.scene.getParty()[1].hp = 0;
game.scene.getParty()[1].status = new Status(StatusEffect.FAINT);
});
await game.phaseInterceptor.run(EncounterPhase);
await game.phaseInterceptor.to(CommandPhase, false);
const battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
const battleStatsOpponent2 = game.scene.currentBattle.enemyParty[1].summonData.battleStats;
expect(battleStatsOpponent2[BattleStat.ATK]).toBe(-1);
const battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-2);
}, 20000);
});

View File

@ -1,4 +1,4 @@
import { BattleStat } from "#app/data/battle-stat";
import { Stat } from "#enums/stat";
import { CommandPhase } from "#app/phases";
import GameManager from "#test/utils/gameManager";
import { Abilities } from "#enums/abilities";
@ -29,14 +29,17 @@ describe("Abilities - Intrepid Sword", () => {
game.override.ability(Abilities.INTREPID_SWORD);
});
it("INTREPID SWORD on player", async() => {
it("should raise ATK stat stage by 1 on entry", async() => {
await game.runToSummon([
Species.ZACIAN,
]);
const playerPokemon = game.scene.getPlayerPokemon()!;
const enemyPokemon = game.scene.getEnemyPokemon()!;
await game.phaseInterceptor.to(CommandPhase, false);
const battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.ATK]).toBe(1);
const battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.ATK]).toBe(1);
expect(playerPokemon.getStatStage(Stat.ATK)).toBe(1);
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(1);
}, 20000);
});

View File

@ -1,16 +1,14 @@
import { BattleStat } from "#app/data/battle-stat";
import { Stat } from "#app/data/pokemon-stat";
import { CommandPhase, EnemyCommandPhase, VictoryPhase } from "#app/phases";
import { Stat } from "#enums/stat";
import { EnemyCommandPhase, TurnEndPhase, VictoryPhase } from "#app/phases";
import GameManager from "#test/utils/gameManager";
import { getMovePosition } from "#test/utils/gameManagerUtils";
import { Command } from "#app/ui/command-ui-handler";
import { Mode } from "#app/ui/ui";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
import { SPLASH_ONLY } from "../utils/testUtils";
import { BattlerIndex } from "#app/battle";
describe("Abilities - Moxie", () => {
let phaserGame: Phaser.Game;
@ -34,29 +32,46 @@ describe("Abilities - Moxie", () => {
game.override.enemyAbility(Abilities.MOXIE);
game.override.ability(Abilities.MOXIE);
game.override.startingLevel(2000);
game.override.moveset([moveToUse]);
game.override.enemyMoveset([Moves.TACKLE,Moves.TACKLE,Moves.TACKLE,Moves.TACKLE]);
game.override.moveset([ moveToUse ]);
game.override.enemyMoveset(SPLASH_ONLY);
});
it("MOXIE", async() => {
it("should raise ATK stat stage by 1 when defeating an enemy Pokemon", async() => {
const moveToUse = Moves.AERIAL_ACE;
await game.startBattle([
Species.MIGHTYENA,
Species.MIGHTYENA,
]);
let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[Stat.ATK]).toBe(0);
const playerPokemon = game.scene.getPlayerPokemon()!;
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
});
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
const movePosition = getMovePosition(game.scene, 0, moveToUse);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
expect(playerPokemon.getStatStage(Stat.ATK)).toBe(0);
game.doAttack(getMovePosition(game.scene, 0, moveToUse));
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(VictoryPhase);
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.ATK]).toBe(1);
expect(playerPokemon.getStatStage(Stat.ATK)).toBe(1);
}, 20000);
it("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([
Species.MIGHTYENA,
Species.MIGHTYENA,
]);
const [ firstPokemon, secondPokemon ] = game.scene.getPlayerField();
expect(firstPokemon.getStatStage(Stat.ATK)).toBe(0);
secondPokemon.hp = 1;
game.doAttack(getMovePosition(game.scene, 0, moveToUse));
game.doSelectTarget(BattlerIndex.PLAYER_2);
await game.phaseInterceptor.to(TurnEndPhase);
expect(firstPokemon.getStatStage(Stat.ATK)).toBe(1);
}, 20000);
});

View File

@ -1,4 +1,4 @@
import { BattleStat } from "#app/data/battle-stat.js";
import { Stat } from "#enums/stat";
import { StatusEffect } from "#app/data/status-effect.js";
import { Type } from "#app/data/type.js";
import { BattlerTagType } from "#app/enums/battler-tag-type.js";
@ -91,7 +91,7 @@ describe("Abilities - Parental Bond", () => {
await game.phaseInterceptor.to(BerryPhase, false);
expect(leadPokemon.turnData.hitCount).toBe(2);
expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(2);
expect(leadPokemon.getStatStage(Stat.ATK)).toBe(2);
}, TIMEOUT
);
@ -111,7 +111,7 @@ describe("Abilities - Parental Bond", () => {
game.doAttack(getMovePosition(game.scene, 0, Moves.BABY_DOLL_EYES));
await game.phaseInterceptor.to(BerryPhase, false);
expect(enemyPokemon.summonData.battleStats[BattleStat.ATK]).toBe(-1);
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-1);
}, TIMEOUT
);
@ -563,7 +563,7 @@ describe("Abilities - Parental Bond", () => {
await game.phaseInterceptor.to(BerryPhase, false);
expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(-1);
expect(leadPokemon.getStatStage(Stat.ATK)).toBe(-1);
}, TIMEOUT
);
@ -585,7 +585,7 @@ describe("Abilities - Parental Bond", () => {
await game.phaseInterceptor.to(BerryPhase, false);
expect(enemyPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(1);
expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(1);
}, TIMEOUT
);

View File

@ -1,5 +1,5 @@
import { BattleStatMultiplierAbAttr, allAbilities } from "#app/data/ability.js";
import { BattleStat } from "#app/data/battle-stat.js";
import { StatStageMultiplierAbAttr, allAbilities } from "#app/data/ability.js";
import { Stat } from "#enums/stat";
import { WeatherType } from "#app/data/weather.js";
import { CommandPhase, MoveEffectPhase, MoveEndPhase } from "#app/phases.js";
import { Abilities } from "#enums/abilities";
@ -48,10 +48,10 @@ describe("Abilities - Sand Veil", () => {
vi.spyOn(leadPokemon[0], "getAbility").mockReturnValue(allAbilities[Abilities.SAND_VEIL]);
const sandVeilAttr = allAbilities[Abilities.SAND_VEIL].getAttrs(BattleStatMultiplierAbAttr)[0];
vi.spyOn(sandVeilAttr, "applyBattleStat").mockImplementation(
(pokemon, passive, battleStat, statValue, args) => {
if (battleStat === BattleStat.EVA && game.scene.arena.weather?.weatherType === WeatherType.SANDSTORM) {
const sandVeilAttr = allAbilities[Abilities.SAND_VEIL].getAttrs(StatStageMultiplierAbAttr)[0];
vi.spyOn(sandVeilAttr, "applyStatStage").mockImplementation(
(_pokemon, _passive, stat, statValue, _args) => {
if (stat === Stat.EVA && game.scene.arena.weather?.weatherType === WeatherType.SANDSTORM) {
statValue.value *= -1; // will make all attacks miss
return true;
}

View File

@ -1,4 +1,4 @@
import { BattleStat } from "#app/data/battle-stat.js";
import { Stat } from "#enums/stat";
import { TerrainType } from "#app/data/terrain.js";
import { MoveEndPhase, TurnEndPhase } from "#app/phases";
import GameManager from "#test/utils/gameManager";
@ -9,6 +9,7 @@ import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
import { SPLASH_ONLY } from "../utils/testUtils";
// See also: TypeImmunityAbAttr
describe("Abilities - Sap Sipper", () => {
@ -31,52 +32,55 @@ describe("Abilities - Sap Sipper", () => {
game.override.disableCrits();
});
it("raise attack 1 level and block effects when activated against a grass attack", async() => {
it("raises ATK stat stage by 1 and block effects when activated against a grass attack", async() => {
const moveToUse = Moves.LEAFAGE;
const enemyAbility = Abilities.SAP_SIPPER;
game.override.moveset([moveToUse]);
game.override.enemyMoveset([Moves.SPLASH, Moves.NONE, Moves.NONE, Moves.NONE]);
game.override.moveset([ moveToUse ]);
game.override.enemyMoveset(SPLASH_ONLY);
game.override.enemySpecies(Species.DUSKULL);
game.override.enemyAbility(enemyAbility);
await game.startBattle();
const startingOppHp = game.scene.currentBattle.enemyParty[0].hp;
const enemyPokemon = game.scene.getEnemyPokemon()!;
const initialEnemyHp = enemyPokemon.hp;
game.doAttack(getMovePosition(game.scene, 0, moveToUse));
await game.phaseInterceptor.to(TurnEndPhase);
expect(startingOppHp - game.scene.getEnemyParty()[0].hp).toBe(0);
expect(game.scene.getEnemyParty()[0].summonData.battleStats[BattleStat.ATK]).toBe(1);
expect(initialEnemyHp - enemyPokemon.hp).toBe(0);
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(1);
});
it("raise attack 1 level and block effects when activated against a grass status move", async() => {
it("raises ATK stat stage by 1 and block effects when activated against a grass status move", async() => {
const moveToUse = Moves.SPORE;
const enemyAbility = Abilities.SAP_SIPPER;
game.override.moveset([moveToUse]);
game.override.enemyMoveset([Moves.SPLASH, Moves.NONE, Moves.NONE, Moves.NONE]);
game.override.moveset([ moveToUse ]);
game.override.enemyMoveset(SPLASH_ONLY);
game.override.enemySpecies(Species.RATTATA);
game.override.enemyAbility(enemyAbility);
await game.startBattle();
const enemyPokemon = game.scene.getEnemyPokemon()!;
game.doAttack(getMovePosition(game.scene, 0, moveToUse));
await game.phaseInterceptor.to(TurnEndPhase);
expect(game.scene.getEnemyParty()[0].status).toBeUndefined();
expect(game.scene.getEnemyParty()[0].summonData.battleStats[BattleStat.ATK]).toBe(1);
expect(enemyPokemon.status).toBeUndefined();
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(1);
});
it("do not activate against status moves that target the field", async() => {
const moveToUse = Moves.GRASSY_TERRAIN;
const enemyAbility = Abilities.SAP_SIPPER;
game.override.moveset([moveToUse]);
game.override.enemyMoveset([Moves.SPLASH, Moves.NONE, Moves.NONE, Moves.NONE]);
game.override.moveset([ moveToUse ]);
game.override.enemyMoveset(SPLASH_ONLY);
game.override.enemySpecies(Species.RATTATA);
game.override.enemyAbility(enemyAbility);
@ -88,51 +92,54 @@ describe("Abilities - Sap Sipper", () => {
expect(game.scene.arena.terrain).toBeDefined();
expect(game.scene.arena.terrain!.terrainType).toBe(TerrainType.GRASSY);
expect(game.scene.getEnemyParty()[0].summonData.battleStats[BattleStat.ATK]).toBe(0);
expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.ATK)).toBe(0);
});
it("activate once against multi-hit grass attacks", async() => {
const moveToUse = Moves.BULLET_SEED;
const enemyAbility = Abilities.SAP_SIPPER;
game.override.moveset([moveToUse]);
game.override.enemyMoveset([Moves.SPLASH, Moves.NONE, Moves.NONE, Moves.NONE]);
game.override.moveset([ moveToUse ]);
game.override.enemyMoveset(SPLASH_ONLY);
game.override.enemySpecies(Species.RATTATA);
game.override.enemyAbility(enemyAbility);
await game.startBattle();
const startingOppHp = game.scene.currentBattle.enemyParty[0].hp;
const enemyPokemon = game.scene.getEnemyPokemon()!;
const initialEnemyHp = enemyPokemon.hp;
game.doAttack(getMovePosition(game.scene, 0, moveToUse));
await game.phaseInterceptor.to(TurnEndPhase);
expect(startingOppHp - game.scene.getEnemyParty()[0].hp).toBe(0);
expect(game.scene.getEnemyParty()[0].summonData.battleStats[BattleStat.ATK]).toBe(1);
expect(initialEnemyHp - enemyPokemon.hp).toBe(0);
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(1);
});
it("do not activate against status moves that target the user", async() => {
const moveToUse = Moves.SPIKY_SHIELD;
const ability = Abilities.SAP_SIPPER;
game.override.moveset([moveToUse]);
game.override.moveset([ moveToUse ]);
game.override.ability(ability);
game.override.enemyMoveset([Moves.SPLASH, Moves.NONE, Moves.NONE, Moves.NONE]);
game.override.enemyMoveset(SPLASH_ONLY);
game.override.enemySpecies(Species.RATTATA);
game.override.enemyAbility(Abilities.NONE);
await game.startBattle();
const playerPokemon = game.scene.getPlayerPokemon()!;
game.doAttack(getMovePosition(game.scene, 0, moveToUse));
await game.phaseInterceptor.to(MoveEndPhase);
expect(game.scene.getParty()[0].getTag(BattlerTagType.SPIKY_SHIELD)).toBeDefined();
expect(playerPokemon.getTag(BattlerTagType.SPIKY_SHIELD)).toBeDefined();
await game.phaseInterceptor.to(TurnEndPhase);
expect(game.scene.getParty()[0].summonData.battleStats[BattleStat.ATK]).toBe(0);
expect(playerPokemon.getStatStage(Stat.ATK)).toBe(0);
expect(game.phaseInterceptor.log).not.toContain("ShowAbilityPhase");
});
@ -149,13 +156,14 @@ describe("Abilities - Sap Sipper", () => {
await game.startBattle();
const startingOppHp = game.scene.currentBattle.enemyParty[0].hp;
const enemyPokemon = game.scene.getEnemyPokemon()!;
const initialEnemyHp = enemyPokemon.hp;
game.doAttack(getMovePosition(game.scene, 0, moveToUse));
await game.phaseInterceptor.to(TurnEndPhase);
expect(startingOppHp - game.scene.getEnemyParty()[0].hp).toBe(0);
expect(game.scene.getEnemyParty()[0].summonData.battleStats[BattleStat.ATK]).toBe(1);
expect(initialEnemyHp - enemyPokemon.hp).toBe(0);
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(1);
});
});

View File

@ -1,4 +1,4 @@
import { BattleStat } from "#app/data/battle-stat.js";
import { Stat } from "#enums/stat";
import { TurnEndPhase } from "#app/phases";
import GameManager from "#test/utils/gameManager";
import { getMovePosition } from "#test/utils/gameManagerUtils";
@ -42,12 +42,14 @@ describe("Abilities - Volt Absorb", () => {
await game.startBattle();
const playerPokemon = game.scene.getPlayerPokemon()!;
game.doAttack(getMovePosition(game.scene, 0, moveToUse));
await game.phaseInterceptor.to(TurnEndPhase);
expect(game.scene.getParty()[0].summonData.battleStats[BattleStat.SPDEF]).toBe(1);
expect(game.scene.getParty()[0].getTag(BattlerTagType.CHARGED)).toBeDefined();
expect(playerPokemon.getStatStage(Stat.SPDEF)).toBe(1);
expect(playerPokemon.getTag(BattlerTagType.CHARGED)).toBeDefined();
expect(game.phaseInterceptor.log).not.toContain("ShowAbilityPhase");
});
});

View File

@ -1,4 +1,4 @@
import { BattleStat } from "#app/data/battle-stat.js";
import { Stat } from "#enums/stat";
import { TurnEndPhase } from "#app/phases";
import GameManager from "#test/utils/gameManager";
import { getMovePosition } from "#test/utils/gameManagerUtils";
@ -32,37 +32,37 @@ describe("Abilities - Wind Rider", () => {
game.override.enemyMoveset(SPLASH_ONLY);
});
it("takes no damage from wind moves and its Attack is increased by one stage when hit by one", async () => {
it("takes no damage from wind moves and its ATK stat stage is raised by 1 when hit by one", async () => {
await game.startBattle([Species.MAGIKARP]);
const shiftry = game.scene.getEnemyPokemon()!;
expect(shiftry.summonData.battleStats[BattleStat.ATK]).toBe(0);
expect(shiftry.getStatStage(Stat.ATK)).toBe(0);
game.doAttack(getMovePosition(game.scene, 0, Moves.PETAL_BLIZZARD));
await game.phaseInterceptor.to(TurnEndPhase);
expect(shiftry.isFullHp()).toBe(true);
expect(shiftry.summonData.battleStats[BattleStat.ATK]).toBe(1);
expect(shiftry.getStatStage(Stat.ATK)).toBe(1);
});
it("Attack is increased by one stage when Tailwind is present on its side", async () => {
it("ATK stat stage is raised by 1 when Tailwind is present on its side", async () => {
game.override.ability(Abilities.WIND_RIDER);
game.override.enemySpecies(Species.MAGIKARP);
await game.startBattle([Species.SHIFTRY]);
const shiftry = game.scene.getPlayerPokemon()!;
expect(shiftry.summonData.battleStats[BattleStat.ATK]).toBe(0);
expect(shiftry.getStatStage(Stat.ATK)).toBe(0);
game.doAttack(getMovePosition(game.scene, 0, Moves.TAILWIND));
await game.phaseInterceptor.to(TurnEndPhase);
expect(shiftry.summonData.battleStats[BattleStat.ATK]).toBe(1);
expect(shiftry.getStatStage(Stat.ATK)).toBe(1);
});
it("does not increase Attack when Tailwind is present on opposing side", async () => {
it("does not raise ATK stat stage when Tailwind is present on opposing side", async () => {
game.override.ability(Abilities.WIND_RIDER);
game.override.enemySpecies(Species.MAGIKARP);
@ -70,33 +70,33 @@ describe("Abilities - Wind Rider", () => {
const magikarp = game.scene.getEnemyPokemon()!;
const shiftry = game.scene.getPlayerPokemon()!;
expect(shiftry.summonData.battleStats[BattleStat.ATK]).toBe(0);
expect(magikarp.summonData.battleStats[BattleStat.ATK]).toBe(0);
expect(shiftry.getStatStage(Stat.ATK)).toBe(0);
expect(magikarp.getStatStage(Stat.ATK)).toBe(0);
game.doAttack(getMovePosition(game.scene, 0, Moves.TAILWIND));
await game.phaseInterceptor.to(TurnEndPhase);
expect(shiftry.summonData.battleStats[BattleStat.ATK]).toBe(1);
expect(magikarp.summonData.battleStats[BattleStat.ATK]).toBe(0);
expect(shiftry.getStatStage(Stat.ATK)).toBe(1);
expect(magikarp.getStatStage(Stat.ATK)).toBe(0);
});
it("does not increase Attack when Tailwind is present on opposing side", async () => {
it("does not raise ATK stat stage when Tailwind is present on opposing side", async () => {
game.override.enemySpecies(Species.MAGIKARP);
await game.startBattle([Species.SHIFTRY]);
const magikarp = game.scene.getEnemyPokemon()!;
const shiftry = game.scene.getPlayerPokemon()!;
expect(shiftry.summonData.battleStats[BattleStat.ATK]).toBe(0);
expect(magikarp.summonData.battleStats[BattleStat.ATK]).toBe(0);
expect(shiftry.getStatStage(Stat.ATK)).toBe(0);
expect(magikarp.getStatStage(Stat.ATK)).toBe(0);
game.doAttack(getMovePosition(game.scene, 0, Moves.TAILWIND));
await game.phaseInterceptor.to(TurnEndPhase);
expect(shiftry.summonData.battleStats[BattleStat.ATK]).toBe(1);
expect(magikarp.summonData.battleStats[BattleStat.ATK]).toBe(0);
expect(shiftry.getStatStage(Stat.ATK)).toBe(1);
expect(magikarp.getStatStage(Stat.ATK)).toBe(0);
});
it("does not interact with Sandstorm", async () => {
@ -105,14 +105,14 @@ describe("Abilities - Wind Rider", () => {
await game.startBattle([Species.SHIFTRY]);
const shiftry = game.scene.getPlayerPokemon()!;
expect(shiftry.summonData.battleStats[BattleStat.ATK]).toBe(0);
expect(shiftry.getStatStage(Stat.ATK)).toBe(0);
expect(shiftry.isFullHp()).toBe(true);
game.doAttack(getMovePosition(game.scene, 0, Moves.SANDSTORM));
await game.phaseInterceptor.to(TurnEndPhase);
expect(shiftry.summonData.battleStats[BattleStat.ATK]).toBe(0);
expect(shiftry.getStatStage(Stat.ATK)).toBe(0);
expect(shiftry.hp).lessThan(shiftry.getMaxHp());
});
});

View File

@ -224,7 +224,7 @@ describe("achvs", () => {
expect(achvs._50_RIBBONS).toBeInstanceOf(RibbonAchv);
expect(achvs._75_RIBBONS).toBeInstanceOf(RibbonAchv);
expect(achvs._100_RIBBONS).toBeInstanceOf(RibbonAchv);
expect(achvs.TRANSFER_MAX_BATTLE_STAT).toBeInstanceOf(Achv);
expect(achvs.TRANSFER_MAX_STAT_STAGE).toBeInstanceOf(Achv);
expect(achvs.MAX_FRIENDSHIP).toBeInstanceOf(Achv);
expect(achvs.MEGA_EVOLVE).toBeInstanceOf(Achv);
expect(achvs.GIGANTAMAX).toBeInstanceOf(Achv);

View File

@ -1,8 +1,8 @@
import { BattleStat, getBattleStatLevelChangeDescription, getBattleStatName } from "#app/data/battle-stat.js";
import { BattleStat, getStatKey } from "#enums/stat";
import { describe, expect, it } from "vitest";
import { arrayOfRange, mockI18next } from "./utils/testUtils";
const TEST_BATTLE_STAT = -99 as unknown as BattleStat;
const TEST_BATTLE_STAT = -99 as BattleStat;
const TEST_POKEMON = "Testmon";
const TEST_STAT = "Teststat";
@ -25,7 +25,7 @@ describe("battle-stat", () => {
});
it("should fall back to ??? for an unknown BattleStat", () => {
expect(getBattleStatName(TEST_BATTLE_STAT)).toBe("???");
expect(getStatKey(TEST_BATTLE_STAT)).toBe("???");
});
});

View File

@ -1,5 +1,5 @@
import { allSpecies } from "#app/data/pokemon-species";
import { TempBattleStat } from "#app/data/temp-battle-stat.js";
import { Stat } from "#enums/stat";
import { GameModes } from "#app/game-mode";
import { getGameMode } from "#app/game-mode.js";
import {
@ -339,7 +339,7 @@ describe("Test Battle Phase", () => {
.startingLevel(100)
.moveset([moveToUse])
.enemyMoveset(SPLASH_ONLY)
.startingHeldItems([{ name: "TEMP_STAT_BOOSTER", type: TempBattleStat.ACC }]);
.startingHeldItems([{ name: "TEMP_STAT_BOOSTER", type: Stat.ACC }]);
await game.startBattle();
game.scene.getPlayerPokemon()!.hp = 1;

View File

@ -3,14 +3,14 @@ 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 { BattleStat } from "#app/data/battle-stat.js";
import { BattlerTagType } from "#app/enums/battler-tag-type.js";
import { Stat } from "#enums/stat";
vi.mock("#app/battle-scene.js");
describe("BattlerTag - OctolockTag", () => {
describe("lapse behavior", () => {
it("unshifts a StatChangePhase with expected stat changes", { timeout: 10000 }, async () => {
it("unshifts a StatChangePhase with expected stat stage changes", { timeout: 10000 }, async () => {
const mockPokemon = {
scene: new BattleScene(),
getBattlerIndex: () => 0,
@ -20,8 +20,8 @@ describe("BattlerTag - OctolockTag", () => {
vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementation(phase => {
expect(phase).toBeInstanceOf(StatChangePhase);
expect((phase as StatChangePhase)["levels"]).toEqual(-1);
expect((phase as StatChangePhase)["stats"]).toEqual([BattleStat.DEF, BattleStat.SPDEF]);
expect((phase as StatChangePhase)["stages"]).toEqual(-1);
expect((phase as StatChangePhase)["stats"]).toEqual([ Stat.DEF, Stat.SPDEF ]);
});
subject.lapse(mockPokemon, BattlerTagLapseType.TURN_END);

View File

@ -3,7 +3,7 @@ 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 { BattleStat } from "#app/data/battle-stat.js";
import { Stat } from "#enums/stat";
import * as messages from "#app/messages.js";
beforeEach(() => {
@ -12,7 +12,7 @@ beforeEach(() => {
describe("BattlerTag - StockpilingTag", () => {
describe("onAdd", () => {
it("unshifts a StatChangePhase with expected stat changes on add", { timeout: 10000 }, async () => {
it("unshifts a StatChangePhase with expected stat stage changes on add", { timeout: 10000 }, async () => {
const mockPokemon = {
scene: vi.mocked(new BattleScene()) as BattleScene,
getBattlerIndex: () => 0,
@ -24,10 +24,10 @@ describe("BattlerTag - StockpilingTag", () => {
vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementation(phase => {
expect(phase).toBeInstanceOf(StatChangePhase);
expect((phase as StatChangePhase)["levels"]).toEqual(1);
expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([BattleStat.DEF, BattleStat.SPDEF]));
expect((phase as StatChangePhase)["stages"]).toEqual(1);
expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([ Stat.DEF, Stat.SPDEF ]));
(phase as StatChangePhase)["onChange"]!(mockPokemon, [BattleStat.DEF, BattleStat.SPDEF], [1, 1]);
(phase as StatChangePhase)["onChange"]!(mockPokemon, [Stat.DEF, Stat.SPDEF], [1, 1]);
});
subject.onAdd(mockPokemon);
@ -44,17 +44,17 @@ describe("BattlerTag - StockpilingTag", () => {
vi.spyOn(mockPokemon.scene, "queueMessage").mockImplementation(() => {});
mockPokemon.summonData.battleStats[BattleStat.DEF] = 6;
mockPokemon.summonData.battleStats[BattleStat.SPDEF] = 5;
mockPokemon.setStatStage(Stat.DEF, 6);
mockPokemon.setStatStage(Stat.SPDEF, 5);
const subject = new StockpilingTag(1);
vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementation(phase => {
expect(phase).toBeInstanceOf(StatChangePhase);
expect((phase as StatChangePhase)["levels"]).toEqual(1);
expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([BattleStat.DEF, BattleStat.SPDEF]));
expect((phase as StatChangePhase)["stages"]).toEqual(1);
expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([Stat.DEF, Stat.SPDEF]));
(phase as StatChangePhase)["onChange"]!(mockPokemon, [BattleStat.DEF, BattleStat.SPDEF], [1, 1]);
(phase as StatChangePhase)["onChange"]!(mockPokemon, [ Stat.DEF, Stat.SPDEF ], [1, 1]);
});
subject.onAdd(mockPokemon);
@ -76,10 +76,10 @@ describe("BattlerTag - StockpilingTag", () => {
vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementation(phase => {
expect(phase).toBeInstanceOf(StatChangePhase);
expect((phase as StatChangePhase)["levels"]).toEqual(1);
expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([BattleStat.DEF, BattleStat.SPDEF]));
expect((phase as StatChangePhase)["stages"]).toEqual(1);
expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([ Stat.DEF, Stat.SPDEF ]));
(phase as StatChangePhase)["onChange"]!(mockPokemon, [BattleStat.DEF, BattleStat.SPDEF], [1, 1]);
(phase as StatChangePhase)["onChange"]!(mockPokemon, [ Stat.DEF, Stat.SPDEF ], [1, 1]);
});
subject.onOverlap(mockPokemon);
@ -98,18 +98,18 @@ describe("BattlerTag - StockpilingTag", () => {
vi.spyOn(mockPokemon.scene, "queueMessage").mockImplementation(() => {});
mockPokemon.summonData.battleStats[BattleStat.DEF] = 5;
mockPokemon.summonData.battleStats[BattleStat.SPDEF] = 4;
mockPokemon.setStatStage(Stat.DEF, 5);
mockPokemon.setStatStage(Stat.SPDEF, 4);
const subject = new StockpilingTag(1);
vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementationOnce(phase => {
expect(phase).toBeInstanceOf(StatChangePhase);
expect((phase as StatChangePhase)["levels"]).toEqual(1);
expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([BattleStat.DEF, BattleStat.SPDEF]));
expect((phase as StatChangePhase)["stages"]).toEqual(1);
expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([ Stat.DEF, Stat.SPDEF ]));
// def doesn't change
(phase as StatChangePhase)["onChange"]!(mockPokemon, [BattleStat.SPDEF], [1]);
(phase as StatChangePhase)["onChange"]!(mockPokemon, [ Stat.SPDEF ], [1]);
});
subject.onAdd(mockPokemon);
@ -117,11 +117,11 @@ describe("BattlerTag - StockpilingTag", () => {
vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementationOnce(phase => {
expect(phase).toBeInstanceOf(StatChangePhase);
expect((phase as StatChangePhase)["levels"]).toEqual(1);
expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([BattleStat.DEF, BattleStat.SPDEF]));
expect((phase as StatChangePhase)["stages"]).toEqual(1);
expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([ Stat.DEF, Stat.SPDEF ]));
// def doesn't change
(phase as StatChangePhase)["onChange"]!(mockPokemon, [BattleStat.SPDEF], [1]);
(phase as StatChangePhase)["onChange"]!(mockPokemon, [ Stat.SPDEF ], [1]);
});
subject.onOverlap(mockPokemon);
@ -129,8 +129,8 @@ describe("BattlerTag - StockpilingTag", () => {
vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementationOnce(phase => {
expect(phase).toBeInstanceOf(StatChangePhase);
expect((phase as StatChangePhase)["levels"]).toEqual(1);
expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([BattleStat.DEF, BattleStat.SPDEF]));
expect((phase as StatChangePhase)["stages"]).toEqual(1);
expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([ Stat.DEF, Stat.SPDEF ]));
// neither stat changes, stack count should still increase
});
@ -138,20 +138,20 @@ describe("BattlerTag - StockpilingTag", () => {
subject.onOverlap(mockPokemon);
expect(subject.stockpiledCount).toBe(3);
vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementationOnce(phase => {
vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementationOnce(_phase => {
throw new Error("Should not be called a fourth time");
});
// fourth stack should not be applied
subject.onOverlap(mockPokemon);
expect(subject.stockpiledCount).toBe(3);
expect(subject.statChangeCounts).toMatchObject({ [BattleStat.DEF]: 0, [BattleStat.SPDEF]: 2 });
expect(subject.statChangeCounts).toMatchObject({ [ Stat.DEF ]: 0, [Stat.SPDEF]: 2 });
// removing tag should reverse stat changes
vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementationOnce(phase => {
expect(phase).toBeInstanceOf(StatChangePhase);
expect((phase as StatChangePhase)["levels"]).toEqual(-2);
expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([BattleStat.SPDEF]));
expect((phase as StatChangePhase)["stages"]).toEqual(-2);
expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([Stat.SPDEF]));
});
subject.onRemove(mockPokemon);

View File

@ -37,29 +37,29 @@ describe("Items - Eviolite", () => {
const partyMember = game.scene.getParty()[0];
// Checking consoe log to make sure Eviolite is applied when getBattleStat (with the appropriate stat) is called
partyMember.getBattleStat(Stat.DEF);
// Checking consoe log to make sure Eviolite is applied when getEffectiveStat (with the appropriate stat) is called
partyMember.getEffectiveStat(Stat.DEF);
expect(consoleSpy).toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:ModifierType.EVIOLITE.name"), "");
// Printing dummy console messages along the way so subsequent checks don't pass because of the first
console.log("");
partyMember.getBattleStat(Stat.SPDEF);
partyMember.getEffectiveStat(Stat.SPDEF);
expect(consoleSpy).toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:ModifierType.EVIOLITE.name"), "");
console.log("");
partyMember.getBattleStat(Stat.ATK);
partyMember.getEffectiveStat(Stat.ATK);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:ModifierType.EVIOLITE.name"), "");
console.log("");
partyMember.getBattleStat(Stat.SPATK);
partyMember.getEffectiveStat(Stat.SPATK);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:ModifierType.EVIOLITE.name"), "");
console.log("");
partyMember.getBattleStat(Stat.SPD);
partyMember.getEffectiveStat(Stat.SPD);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:ModifierType.EVIOLITE.name"), "");
});

View File

@ -37,29 +37,29 @@ describe("Items - Light Ball", () => {
const partyMember = game.scene.getParty()[0];
// Checking consoe log to make sure Light Ball is applied when getBattleStat (with the appropriate stat) is called
partyMember.getBattleStat(Stat.DEF);
// Checking consoe log to make sure Light Ball is applied when getEffectiveStat (with the appropriate stat) is called
partyMember.getEffectiveStat(Stat.DEF);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.LIGHT_BALL.name"), "");
// Printing dummy console messages along the way so subsequent checks don't pass because of the first
console.log("");
partyMember.getBattleStat(Stat.SPDEF);
partyMember.getEffectiveStat(Stat.SPDEF);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.LIGHT_BALL.name"), "");
console.log("");
partyMember.getBattleStat(Stat.ATK);
partyMember.getEffectiveStat(Stat.ATK);
expect(consoleSpy).toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.LIGHT_BALL.name"), "");
console.log("");
partyMember.getBattleStat(Stat.SPATK);
partyMember.getEffectiveStat(Stat.SPATK);
expect(consoleSpy).toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.LIGHT_BALL.name"), "");
console.log("");
partyMember.getBattleStat(Stat.SPD);
partyMember.getEffectiveStat(Stat.SPD);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.LIGHT_BALL.name"), "");
});

View File

@ -37,29 +37,29 @@ describe("Items - Metal Powder", () => {
const partyMember = game.scene.getParty()[0];
// Checking consoe log to make sure Metal Powder is applied when getBattleStat (with the appropriate stat) is called
partyMember.getBattleStat(Stat.DEF);
// Checking consoe log to make sure Metal Powder is applied when getEffectiveStat (with the appropriate stat) is called
partyMember.getEffectiveStat(Stat.DEF);
expect(consoleSpy).toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.METAL_POWDER.name"), "");
// Printing dummy console messages along the way so subsequent checks don't pass because of the first
console.log("");
partyMember.getBattleStat(Stat.SPDEF);
partyMember.getEffectiveStat(Stat.SPDEF);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.METAL_POWDER.name"), "");
console.log("");
partyMember.getBattleStat(Stat.ATK);
partyMember.getEffectiveStat(Stat.ATK);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.METAL_POWDER.name"), "");
console.log("");
partyMember.getBattleStat(Stat.SPATK);
partyMember.getEffectiveStat(Stat.SPATK);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.METAL_POWDER.name"), "");
console.log("");
partyMember.getBattleStat(Stat.SPD);
partyMember.getEffectiveStat(Stat.SPD);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.METAL_POWDER.name"), "");
});

View File

@ -37,29 +37,29 @@ describe("Items - Quick Powder", () => {
const partyMember = game.scene.getParty()[0];
// Checking consoe log to make sure Quick Powder is applied when getBattleStat (with the appropriate stat) is called
partyMember.getBattleStat(Stat.DEF);
// Checking consoe log to make sure Quick Powder is applied when getEffectiveStat (with the appropriate stat) is called
partyMember.getEffectiveStat(Stat.DEF);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.QUICK_POWDER.name"), "");
// Printing dummy console messages along the way so subsequent checks don't pass because of the first
console.log("");
partyMember.getBattleStat(Stat.SPDEF);
partyMember.getEffectiveStat(Stat.SPDEF);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.QUICK_POWDER.name"), "");
console.log("");
partyMember.getBattleStat(Stat.ATK);
partyMember.getEffectiveStat(Stat.ATK);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.QUICK_POWDER.name"), "");
console.log("");
partyMember.getBattleStat(Stat.SPATK);
partyMember.getEffectiveStat(Stat.SPATK);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.QUICK_POWDER.name"), "");
console.log("");
partyMember.getBattleStat(Stat.SPD);
partyMember.getEffectiveStat(Stat.SPD);
expect(consoleSpy).toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.QUICK_POWDER.name"), "");
});

View File

@ -37,29 +37,29 @@ describe("Items - Thick Club", () => {
const partyMember = game.scene.getParty()[0];
// Checking consoe log to make sure Thick Club is applied when getBattleStat (with the appropriate stat) is called
partyMember.getBattleStat(Stat.DEF);
// Checking consoe log to make sure Thick Club is applied when getEffectiveStat (with the appropriate stat) is called
partyMember.getEffectiveStat(Stat.DEF);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.THICK_CLUB.name"), "");
// Printing dummy console messages along the way so subsequent checks don't pass because of the first
console.log("");
partyMember.getBattleStat(Stat.SPDEF);
partyMember.getEffectiveStat(Stat.SPDEF);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.THICK_CLUB.name"), "");
console.log("");
partyMember.getBattleStat(Stat.ATK);
partyMember.getEffectiveStat(Stat.ATK);
expect(consoleSpy).toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.THICK_CLUB.name"), "");
console.log("");
partyMember.getBattleStat(Stat.SPATK);
partyMember.getEffectiveStat(Stat.SPATK);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.THICK_CLUB.name"), "");
console.log("");
partyMember.getBattleStat(Stat.SPD);
partyMember.getEffectiveStat(Stat.SPD);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.THICK_CLUB.name"), "");
});

View File

@ -1,4 +1,4 @@
import { BattleStat } from "#app/data/battle-stat.js";
import { Stat } from "#enums/stat";
import { PostSummonPhase, TurnEndPhase } from "#app/phases.js";
import GameManager from "#app/test/utils/gameManager";
import { getMovePosition } from "#app/test/utils/gameManagerUtils";
@ -35,7 +35,7 @@ describe("Moves - Baton Pass", () => {
.disableCrits();
});
it("passes stat stage buffs when player uses it", async() => {
it("transfers all stat stages when player uses it", async() => {
// arrange
await game.startBattle([
Species.RAICHU,
@ -45,7 +45,10 @@ describe("Moves - Baton Pass", () => {
// round 1 - buff
game.doAttack(getMovePosition(game.scene, 0, Moves.NASTY_PLOT));
await game.toNextTurn();
expect(game.scene.getPlayerPokemon()!.summonData.battleStats[BattleStat.SPATK]).toEqual(2);
let playerPokemon = game.scene.getPlayerPokemon()!;
expect(playerPokemon.getStatStage(Stat.SPATK)).toEqual(2);
// round 2 - baton pass
game.doAttack(getMovePosition(game.scene, 0, Moves.BATON_PASS));
@ -53,16 +56,16 @@ describe("Moves - Baton Pass", () => {
await game.phaseInterceptor.to(TurnEndPhase);
// assert
const playerPkm = game.scene.getPlayerPokemon()!;
expect(playerPkm.species.speciesId).toEqual(Species.SHUCKLE);
expect(playerPkm.summonData.battleStats[BattleStat.SPATK]).toEqual(2);
playerPokemon = game.scene.getPlayerPokemon()!;
expect(playerPokemon.species.speciesId).toEqual(Species.SHUCKLE);
expect(playerPokemon.getStatStage(Stat.SPATK)).toEqual(2);
}, 20000);
it("passes stat stage buffs when AI uses it", async() => {
// arrange
game.override
.startingWave(5)
.enemyMoveset(new Array(4).fill([Moves.NASTY_PLOT]));
.enemyMoveset([ Moves.NASTY_PLOT, Moves.NASTY_PLOT, Moves.NASTY_PLOT, Moves.NASTY_PLOT ]);
await game.startBattle([
Species.RAICHU,
Species.SHUCKLE
@ -74,13 +77,13 @@ describe("Moves - Baton Pass", () => {
// round 2 - baton pass
game.scene.getEnemyPokemon()!.hp = 100;
game.override.enemyMoveset(new Array(4).fill(Moves.BATON_PASS));
game.override.enemyMoveset([ Moves.BATON_PASS, Moves.BATON_PASS, Moves.BATON_PASS, Moves.BATON_PASS ]);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.phaseInterceptor.to(PostSummonPhase, false);
// assert
// check buffs are still there
expect(game.scene.getEnemyPokemon()!.summonData.battleStats[BattleStat.SPATK]).toEqual(2);
expect(game.scene.getEnemyPokemon()!.getStatStage(Stat.SPATK)).toEqual(2);
// confirm that a switch actually happened. can't use species because I
// can't find a way to override trainer parties with more than 1 pokemon species
expect(game.scene.getEnemyPokemon()!.hp).not.toEqual(100);

View File

@ -5,7 +5,7 @@ import { TurnEndPhase } from "#app/phases";
import { getMovePosition } from "#test/utils/gameManagerUtils";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import { BattleStat } from "#app/data/battle-stat";
import { Stat } from "#enums/stat";
const TIMEOUT = 20 * 1000;
// RATIO : HP Cost of Move
@ -39,7 +39,7 @@ describe("Moves - BELLY DRUM", () => {
// Bulbapedia Reference: https://bulbapedia.bulbagarden.net/wiki/Belly_Drum_(move)
test("Belly Drum raises the user's Attack to its max, at the cost of 1/2 of its maximum HP",
test("raises the user's ATK stat stage to its max, at the cost of 1/2 of its maximum HP",
async() => {
await game.startBattle([Species.MAGIKARP]);
@ -50,47 +50,47 @@ describe("Moves - BELLY DRUM", () => {
await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp() - hpLost);
expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(6);
expect(leadPokemon.getStatStage(Stat.ATK)).toBe(6);
}, TIMEOUT
);
test("Belly Drum will still take effect if an uninvolved stat is at max",
test("will still take effect if an uninvolved stat stage is at max",
async() => {
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon()!;
const hpLost = Math.floor(leadPokemon.getMaxHp() / RATIO);
// Here - BattleStat.ATK -> -3 and BattleStat.SPATK -> 6
leadPokemon.summonData.battleStats[BattleStat.ATK] = -3;
leadPokemon.summonData.battleStats[BattleStat.SPATK] = 6;
// Here - Stat.ATK -> -3 and Stat.SPATK -> 6
leadPokemon.setStatStage(Stat.ATK, -3);
leadPokemon.setStatStage(Stat.SPATK, 6);
game.doAttack(getMovePosition(game.scene, 0, Moves.BELLY_DRUM));
await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp() - hpLost);
expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(6);
expect(leadPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(6);
expect(leadPokemon.getStatStage(Stat.ATK)).toBe(6);
expect(leadPokemon.getStatStage(Stat.SPATK)).toBe(6);
}, TIMEOUT
);
test("Belly Drum fails if the pokemon's attack stat is at its maximum",
test("fails if the pokemon's ATK stat stage is at its maximum",
async() => {
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon()!;
leadPokemon.summonData.battleStats[BattleStat.ATK] = 6;
leadPokemon.setStatStage(Stat.ATK, 6);
game.doAttack(getMovePosition(game.scene, 0, Moves.BELLY_DRUM));
await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp());
expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(6);
expect(leadPokemon.getStatStage(Stat.ATK)).toBe(6);
}, TIMEOUT
);
test("Belly Drum fails if the user's health is less than 1/2",
test("fails if the user's health is less than 1/2",
async() => {
await game.startBattle([Species.MAGIKARP]);
@ -102,7 +102,7 @@ describe("Moves - BELLY DRUM", () => {
await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.hp).toBe(hpLost - PREDAMAGE);
expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(0);
expect(leadPokemon.getStatStage(Stat.ATK)).toBe(0);
}, TIMEOUT
);
});

View File

@ -1,11 +1,11 @@
import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
import Phaser from "phaser";
import GameManager from "#test/utils/gameManager";
import { TurnEndPhase } from "#app/phases";
import { getMovePosition } from "#test/utils/gameManagerUtils";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import { BattleStat } from "#app/data/battle-stat";
import { Stat } from "#enums/stat";
import { SPLASH_ONLY } from "#test/utils/testUtils";
const TIMEOUT = 20 * 1000;
@ -14,7 +14,7 @@ const RATIO = 3;
/** Amount of extra HP lost */
const PREDAMAGE = 15;
describe("Moves - CLANGOROUS_SOUL", () => {
describe("Moves - Clangorous Soul", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
@ -40,7 +40,7 @@ describe("Moves - CLANGOROUS_SOUL", () => {
//Bulbapedia Reference: https://bulbapedia.bulbagarden.net/wiki/Clangorous_Soul_(move)
test("Clangorous Soul raises the user's Attack, Defense, Special Attack, Special Defense and Speed by one stage each, at the cost of 1/3 of its maximum HP",
it("raises the user's ATK, DEF, SPATK, SPDEF, and SPD stat stages by 1 each at the cost of 1/3 of its maximum HP",
async() => {
await game.startBattle([Species.MAGIKARP]);
@ -51,64 +51,64 @@ describe("Moves - CLANGOROUS_SOUL", () => {
await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp() - hpLost);
expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(1);
expect(leadPokemon.summonData.battleStats[BattleStat.DEF]).toBe(1);
expect(leadPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(1);
expect(leadPokemon.summonData.battleStats[BattleStat.SPDEF]).toBe(1);
expect(leadPokemon.summonData.battleStats[BattleStat.SPD]).toBe(1);
expect(leadPokemon.getStatStage(Stat.ATK)).toBe(1);
expect(leadPokemon.getStatStage(Stat.DEF)).toBe(1);
expect(leadPokemon.getStatStage(Stat.SPATK)).toBe(1);
expect(leadPokemon.getStatStage(Stat.SPDEF)).toBe(1);
expect(leadPokemon.getStatStage(Stat.SPD)).toBe(1);
}, TIMEOUT
);
test("Clangorous Soul will still take effect if one or more of the involved stats are not at max",
it("will still take effect if one or more of the involved stat stages are not at max",
async() => {
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon()!;
const hpLost = Math.floor(leadPokemon.getMaxHp() / RATIO);
//Here - BattleStat.SPD -> 0 and BattleStat.SPDEF -> 4
leadPokemon.summonData.battleStats[BattleStat.ATK] = 6;
leadPokemon.summonData.battleStats[BattleStat.DEF] = 6;
leadPokemon.summonData.battleStats[BattleStat.SPATK] = 6;
leadPokemon.summonData.battleStats[BattleStat.SPDEF] = 4;
//Here - Stat.SPD -> 0 and Stat.SPDEF -> 4
leadPokemon.setStatStage(Stat.ATK, 6);
leadPokemon.setStatStage(Stat.DEF, 6);
leadPokemon.setStatStage(Stat.SPATK, 6);
leadPokemon.setStatStage(Stat.SPDEF, 4);
game.doAttack(getMovePosition(game.scene, 0, Moves.CLANGOROUS_SOUL));
await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp() - hpLost);
expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(6);
expect(leadPokemon.summonData.battleStats[BattleStat.DEF]).toBe(6);
expect(leadPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(6);
expect(leadPokemon.summonData.battleStats[BattleStat.SPDEF]).toBe(5);
expect(leadPokemon.summonData.battleStats[BattleStat.SPD]).toBe(1);
expect(leadPokemon.getStatStage(Stat.ATK)).toBe(6);
expect(leadPokemon.getStatStage(Stat.DEF)).toBe(6);
expect(leadPokemon.getStatStage(Stat.SPATK)).toBe(6);
expect(leadPokemon.getStatStage(Stat.SPDEF)).toBe(5);
expect(leadPokemon.getStatStage(Stat.SPD)).toBe(1);
}, TIMEOUT
);
test("Clangorous Soul fails if all stats involved are at max",
it("fails if all stat stages involved are at max",
async() => {
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon()!;
leadPokemon.summonData.battleStats[BattleStat.ATK] = 6;
leadPokemon.summonData.battleStats[BattleStat.DEF] = 6;
leadPokemon.summonData.battleStats[BattleStat.SPATK] = 6;
leadPokemon.summonData.battleStats[BattleStat.SPDEF] = 6;
leadPokemon.summonData.battleStats[BattleStat.SPD] = 6;
leadPokemon.setStatStage(Stat.ATK, 6);
leadPokemon.setStatStage(Stat.DEF, 6);
leadPokemon.setStatStage(Stat.SPATK, 6);
leadPokemon.setStatStage(Stat.SPDEF, 6);
leadPokemon.setStatStage(Stat.SPD, 6);
game.doAttack(getMovePosition(game.scene, 0, Moves.CLANGOROUS_SOUL));
await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp());
expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(6);
expect(leadPokemon.summonData.battleStats[BattleStat.DEF]).toBe(6);
expect(leadPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(6);
expect(leadPokemon.summonData.battleStats[BattleStat.SPDEF]).toBe(6);
expect(leadPokemon.summonData.battleStats[BattleStat.SPD]).toBe(6);
expect(leadPokemon.getStatStage(Stat.ATK)).toBe(6);
expect(leadPokemon.getStatStage(Stat.DEF)).toBe(6);
expect(leadPokemon.getStatStage(Stat.SPATK)).toBe(6);
expect(leadPokemon.getStatStage(Stat.SPDEF)).toBe(6);
expect(leadPokemon.getStatStage(Stat.SPD)).toBe(6);
}, TIMEOUT
);
test("Clangorous Soul fails if the user's health is less than 1/3",
it("fails if the user's health is less than 1/3",
async() => {
await game.startBattle([Species.MAGIKARP]);
@ -120,11 +120,11 @@ describe("Moves - CLANGOROUS_SOUL", () => {
await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.hp).toBe(hpLost - PREDAMAGE);
expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(0);
expect(leadPokemon.summonData.battleStats[BattleStat.DEF]).toBe(0);
expect(leadPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(0);
expect(leadPokemon.summonData.battleStats[BattleStat.SPDEF]).toBe(0);
expect(leadPokemon.summonData.battleStats[BattleStat.SPD]).toBe(0);
expect(leadPokemon.getStatStage(Stat.ATK)).toBe(0);
expect(leadPokemon.getStatStage(Stat.DEF)).toBe(0);
expect(leadPokemon.getStatStage(Stat.SPATK)).toBe(0);
expect(leadPokemon.getStatStage(Stat.SPDEF)).toBe(0);
expect(leadPokemon.getStatStage(Stat.SPD)).toBe(0);
}, TIMEOUT
);
});

View File

@ -1,4 +1,4 @@
import { BattleStat } from "#app/data/battle-stat.js";
import { Stat } from "#enums/stat";
import { Abilities } from "#app/enums/abilities.js";
import { TurnEndPhase } from "#app/phases";
import GameManager from "#test/utils/gameManager";
@ -33,20 +33,20 @@ describe("Moves - Double Team", () => {
game.override.enemyMoveset([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE]);
});
it("increases the user's evasion by one stage.", async () => {
it("raises the user's EVA stat stage by 1", async () => {
await game.startBattle([Species.MAGIKARP]);
const ally = game.scene.getPlayerPokemon()!;
const enemy = game.scene.getEnemyPokemon()!;
vi.spyOn(enemy, "getAccuracyMultiplier");
expect(ally.summonData.battleStats[BattleStat.EVA]).toBe(0);
expect(ally.getStatStage(Stat.EVA)).toBe(0);
game.doAttack(getMovePosition(game.scene, 0, Moves.DOUBLE_TEAM));
await game.phaseInterceptor.to(TurnEndPhase);
await game.toNextTurn();
expect(ally.summonData.battleStats[BattleStat.EVA]).toBe(1);
expect(ally.getStatStage(Stat.EVA)).toBe(1);
expect(enemy.getAccuracyMultiplier).toHaveReturnedWith(.75);
});
});

View File

@ -1,4 +1,4 @@
import { BattleStat } from "#app/data/battle-stat";
import { Stat } from "#enums/stat";
import { Type } from "#app/data/type";
import { Species } from "#app/enums/species.js";
import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
@ -64,9 +64,8 @@ describe("Moves - Dragon Rage", () => {
game.doAttack(getMovePosition(game.scene, 0, Moves.DRAGON_RAGE));
await game.phaseInterceptor.to(TurnEndPhase);
const damageDealt = enemyPokemon.getMaxHp() - enemyPokemon.hp;
expect(damageDealt).toBe(dragonRageDamage);
expect(enemyPokemon.getInverseHp()).toBe(dragonRageDamage);
});
it("ignores resistances", async () => {
@ -75,20 +74,18 @@ describe("Moves - Dragon Rage", () => {
game.doAttack(getMovePosition(game.scene, 0, Moves.DRAGON_RAGE));
await game.phaseInterceptor.to(TurnEndPhase);
const damageDealt = enemyPokemon.getMaxHp() - enemyPokemon.hp;
expect(damageDealt).toBe(dragonRageDamage);
expect(enemyPokemon.getInverseHp()).toBe(dragonRageDamage);
});
it("ignores stat changes", async () => {
it("ignores SPATK stat stages", async () => {
game.override.disableCrits();
partyPokemon.summonData.battleStats[BattleStat.SPATK] = 2;
partyPokemon.setStatStage(Stat.SPATK, 2);
game.doAttack(getMovePosition(game.scene, 0, Moves.DRAGON_RAGE));
await game.phaseInterceptor.to(TurnEndPhase);
const damageDealt = enemyPokemon.getMaxHp() - enemyPokemon.hp;
expect(damageDealt).toBe(dragonRageDamage);
expect(enemyPokemon.getInverseHp()).toBe(dragonRageDamage);
});
it("ignores stab", async () => {
@ -97,9 +94,8 @@ describe("Moves - Dragon Rage", () => {
game.doAttack(getMovePosition(game.scene, 0, Moves.DRAGON_RAGE));
await game.phaseInterceptor.to(TurnEndPhase);
const damageDealt = enemyPokemon.getMaxHp() - enemyPokemon.hp;
expect(damageDealt).toBe(dragonRageDamage);
expect(enemyPokemon.getInverseHp()).toBe(dragonRageDamage);
});
it("ignores criticals", async () => {
@ -107,20 +103,18 @@ describe("Moves - Dragon Rage", () => {
game.doAttack(getMovePosition(game.scene, 0, Moves.DRAGON_RAGE));
await game.phaseInterceptor.to(TurnEndPhase);
const damageDealt = enemyPokemon.getMaxHp() - enemyPokemon.hp;
expect(damageDealt).toBe(dragonRageDamage);
expect(enemyPokemon.getInverseHp()).toBe(dragonRageDamage);
});
it("ignores damage modification from abilities such as ice scales", async () => {
it("ignores damage modification from abilities, for example ICE_SCALES", async () => {
game.override.disableCrits();
game.override.enemyAbility(Abilities.ICE_SCALES);
game.doAttack(getMovePosition(game.scene, 0, Moves.DRAGON_RAGE));
await game.phaseInterceptor.to(TurnEndPhase);
const damageDealt = enemyPokemon.getMaxHp() - enemyPokemon.hp;
expect(damageDealt).toBe(dragonRageDamage);
expect(enemyPokemon.getInverseHp()).toBe(dragonRageDamage);
});
it("ignores multi hit", async () => {
@ -129,8 +123,7 @@ describe("Moves - Dragon Rage", () => {
game.doAttack(getMovePosition(game.scene, 0, Moves.DRAGON_RAGE));
await game.phaseInterceptor.to(TurnEndPhase);
const damageDealt = enemyPokemon.getMaxHp() - enemyPokemon.hp;
expect(damageDealt).toBe(dragonRageDamage);
expect(enemyPokemon.getInverseHp()).toBe(dragonRageDamage);
});
});

View File

@ -5,7 +5,7 @@ import { TurnEndPhase } from "#app/phases";
import { getMovePosition } from "#test/utils/gameManagerUtils";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import { BattleStat } from "#app/data/battle-stat";
import { Stat } from "#enums/stat";
import { SPLASH_ONLY } from "#test/utils/testUtils";
const TIMEOUT = 20 * 1000;
@ -51,9 +51,9 @@ describe("Moves - FILLET AWAY", () => {
await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp() - hpLost);
expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(2);
expect(leadPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(2);
expect(leadPokemon.summonData.battleStats[BattleStat.SPD]).toBe(2);
expect(leadPokemon.getStatStage(Stat.ATK)).toBe(2);
expect(leadPokemon.getStatStage(Stat.SPATK)).toBe(2);
expect(leadPokemon.getStatStage(Stat.SPD)).toBe(2);
}, TIMEOUT
);
@ -64,17 +64,17 @@ describe("Moves - FILLET AWAY", () => {
const leadPokemon = game.scene.getPlayerPokemon()!;
const hpLost = Math.floor(leadPokemon.getMaxHp() / RATIO);
//Here - BattleStat.SPD -> 0 and BattleStat.SPATK -> 3
leadPokemon.summonData.battleStats[BattleStat.ATK] = 6;
leadPokemon.summonData.battleStats[BattleStat.SPATK] = 3;
//Here - Stat.SPD -> 0 and Stat.SPATK -> 3
leadPokemon.setStatStage(Stat.ATK, 6);
leadPokemon.setStatStage(Stat.SPATK, 3);
game.doAttack(getMovePosition(game.scene, 0, Moves.FILLET_AWAY));
await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp() - hpLost);
expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(6);
expect(leadPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(5);
expect(leadPokemon.summonData.battleStats[BattleStat.SPD]).toBe(2);
expect(leadPokemon.getStatStage(Stat.ATK)).toBe(6);
expect(leadPokemon.getStatStage(Stat.SPATK)).toBe(5);
expect(leadPokemon.getStatStage(Stat.SPD)).toBe(2);
}, TIMEOUT
);
@ -84,17 +84,17 @@ describe("Moves - FILLET AWAY", () => {
const leadPokemon = game.scene.getPlayerPokemon()!;
leadPokemon.summonData.battleStats[BattleStat.ATK] = 6;
leadPokemon.summonData.battleStats[BattleStat.SPATK] = 6;
leadPokemon.summonData.battleStats[BattleStat.SPD] = 6;
leadPokemon.setStatStage(Stat.ATK, 6);
leadPokemon.setStatStage(Stat.SPATK, 6);
leadPokemon.setStatStage(Stat.SPD, 6);
game.doAttack(getMovePosition(game.scene, 0, Moves.FILLET_AWAY));
await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp());
expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(6);
expect(leadPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(6);
expect(leadPokemon.summonData.battleStats[BattleStat.SPD]).toBe(6);
expect(leadPokemon.getStatStage(Stat.ATK)).toBe(6);
expect(leadPokemon.getStatStage(Stat.SPATK)).toBe(6);
expect(leadPokemon.getStatStage(Stat.SPD)).toBe(6);
}, TIMEOUT
);
@ -110,9 +110,9 @@ describe("Moves - FILLET AWAY", () => {
await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.hp).toBe(hpLost - PREDAMAGE);
expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(0);
expect(leadPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(0);
expect(leadPokemon.summonData.battleStats[BattleStat.SPD]).toBe(0);
expect(leadPokemon.getStatStage(Stat.ATK)).toBe(0);
expect(leadPokemon.getStatStage(Stat.SPATK)).toBe(0);
expect(leadPokemon.getStatStage(Stat.SPD)).toBe(0);
}, TIMEOUT
);
});

View File

@ -1,4 +1,4 @@
import { BattleStat } from "#app/data/battle-stat";
import { Stat } from "#enums/stat";
import { Species } from "#app/enums/species.js";
import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
import { DamagePhase, TurnEndPhase } from "#app/phases";
@ -52,7 +52,7 @@ describe("Moves - Fissure", () => {
game.scene.clearEnemyHeldItemModifiers();
});
it("ignores damage modification from abilities such as fur coat", async () => {
it("ignores damage modification from abilities, for example FUR_COAT", async () => {
game.override.ability(Abilities.NO_GUARD);
game.override.enemyAbility(Abilities.FUR_COAT);
@ -62,10 +62,10 @@ describe("Moves - Fissure", () => {
expect(enemyPokemon.isFainted()).toBe(true);
});
it("ignores accuracy stat", async () => {
it("ignores user's ACC stat stage", async () => {
vi.spyOn(partyPokemon, "getAccuracyMultiplier");
enemyPokemon.summonData.battleStats[BattleStat.ACC] = -6;
partyPokemon.setStatStage(Stat.ACC, -6);
game.doAttack(getMovePosition(game.scene, 0, Moves.FISSURE));
@ -75,10 +75,10 @@ describe("Moves - Fissure", () => {
expect(partyPokemon.getAccuracyMultiplier).toHaveReturnedWith(1);
});
it("ignores evasion stat", async () => {
it("ignores target's EVA stat stage", async () => {
vi.spyOn(partyPokemon, "getAccuracyMultiplier");
enemyPokemon.summonData.battleStats[BattleStat.EVA] = 6;
enemyPokemon.setStatStage(Stat.EVA, 6);
game.doAttack(getMovePosition(game.scene, 0, Moves.FISSURE));

View File

@ -1,4 +1,4 @@
import { BattleStat } from "#app/data/battle-stat.js";
import { Stat } from "#enums/stat";
import { SemiInvulnerableTag } from "#app/data/battler-tags.js";
import { Type } from "#app/data/type.js";
import { Biome } from "#app/enums/biome.js";
@ -35,24 +35,24 @@ describe("Moves - Flower Shield", () => {
game.override.enemyMoveset(SPLASH_ONLY);
});
it("increases defense of all Grass-type Pokemon on the field by one stage - single battle", async () => {
it("raises DEF stat stage by 1 for all Grass-type Pokemon on the field by one stage - single battle", async () => {
game.override.enemySpecies(Species.CHERRIM);
await game.startBattle([Species.MAGIKARP]);
const cherrim = game.scene.getEnemyPokemon()!;
const magikarp = game.scene.getPlayerPokemon()!;
expect(magikarp.summonData.battleStats[BattleStat.DEF]).toBe(0);
expect(cherrim.summonData.battleStats[BattleStat.DEF]).toBe(0);
expect(magikarp.getStatStage(Stat.DEF)).toBe(0);
expect(cherrim.getStatStage(Stat.DEF)).toBe(0);
game.doAttack(getMovePosition(game.scene, 0, Moves.FLOWER_SHIELD));
await game.phaseInterceptor.to(TurnEndPhase);
expect(magikarp.summonData.battleStats[BattleStat.DEF]).toBe(0);
expect(cherrim.summonData.battleStats[BattleStat.DEF]).toBe(1);
expect(magikarp.getStatStage(Stat.DEF)).toBe(0);
expect(cherrim.getStatStage(Stat.DEF)).toBe(1);
});
it("increases defense of all Grass-type Pokemon on the field by one stage - double battle", async () => {
it("raises DEF stat stage by 1 for all Grass-type Pokemon on the field by one stage - double battle", async () => {
game.override.enemySpecies(Species.MAGIKARP).startingBiome(Biome.GRASS).battleType("double");
await game.startBattle([Species.CHERRIM, Species.MAGIKARP]);
@ -61,21 +61,21 @@ describe("Moves - Flower Shield", () => {
const grassPokemons = field.filter(p => p.getTypes().includes(Type.GRASS));
const nonGrassPokemons = field.filter(pokemon => !grassPokemons.includes(pokemon));
grassPokemons.forEach(p => expect(p.summonData.battleStats[BattleStat.DEF]).toBe(0));
nonGrassPokemons.forEach(p => expect(p.summonData.battleStats[BattleStat.DEF]).toBe(0));
grassPokemons.forEach(p => expect(p.getStatStage(Stat.DEF)).toBe(0));
nonGrassPokemons.forEach(p => expect(p.getStatStage(Stat.DEF)).toBe(0));
game.doAttack(getMovePosition(game.scene, 0, Moves.FLOWER_SHIELD));
game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH));
await game.phaseInterceptor.to(TurnEndPhase);
grassPokemons.forEach(p => expect(p.summonData.battleStats[BattleStat.DEF]).toBe(1));
nonGrassPokemons.forEach(p => expect(p.summonData.battleStats[BattleStat.DEF]).toBe(0));
grassPokemons.forEach(p => expect(p.getStatStage(Stat.DEF)).toBe(1));
nonGrassPokemons.forEach(p => expect(p.getStatStage(Stat.DEF)).toBe(0));
});
/**
* See semi-vulnerable state tags. {@linkcode SemiInvulnerableTag}
*/
it("does not increase defense of a pokemon in semi-vulnerable state", async () => {
it("does not raise DEF stat stage for a Pokemon in semi-vulnerable state", async () => {
game.override.enemySpecies(Species.PARAS);
game.override.enemyMoveset([Moves.DIG, Moves.DIG, Moves.DIG, Moves.DIG]);
game.override.enemyLevel(50);
@ -84,32 +84,32 @@ describe("Moves - Flower Shield", () => {
const paras = game.scene.getEnemyPokemon()!;
const cherrim = game.scene.getPlayerPokemon()!;
expect(paras.summonData.battleStats[BattleStat.DEF]).toBe(0);
expect(cherrim.summonData.battleStats[BattleStat.DEF]).toBe(0);
expect(paras.getStatStage(Stat.DEF)).toBe(0);
expect(cherrim.getStatStage(Stat.DEF)).toBe(0);
expect(paras.getTag(SemiInvulnerableTag)).toBeUndefined;
game.doAttack(getMovePosition(game.scene, 0, Moves.FLOWER_SHIELD));
await game.phaseInterceptor.to(TurnEndPhase);
expect(paras.getTag(SemiInvulnerableTag)).toBeDefined();
expect(paras.summonData.battleStats[BattleStat.DEF]).toBe(0);
expect(cherrim.summonData.battleStats[BattleStat.DEF]).toBe(1);
expect(paras.getStatStage(Stat.DEF)).toBe(0);
expect(cherrim.getStatStage(Stat.DEF)).toBe(1);
});
it("does nothing if there are no Grass-type pokemon on the field", async () => {
it("does nothing if there are no Grass-type Pokemon on the field", async () => {
game.override.enemySpecies(Species.MAGIKARP);
await game.startBattle([Species.MAGIKARP]);
const enemy = game.scene.getEnemyPokemon()!;
const ally = game.scene.getPlayerPokemon()!;
expect(enemy.summonData.battleStats[BattleStat.DEF]).toBe(0);
expect(ally.summonData.battleStats[BattleStat.DEF]).toBe(0);
expect(enemy.getStatStage(Stat.DEF)).toBe(0);
expect(ally.getStatStage(Stat.DEF)).toBe(0);
game.doAttack(getMovePosition(game.scene, 0, Moves.FLOWER_SHIELD));
await game.phaseInterceptor.to(TurnEndPhase);
expect(enemy.summonData.battleStats[BattleStat.DEF]).toBe(0);
expect(ally.summonData.battleStats[BattleStat.DEF]).toBe(0);
expect(enemy.getStatStage(Stat.DEF)).toBe(0);
expect(ally.getStatStage(Stat.DEF)).toBe(0);
});
});

View File

@ -86,7 +86,7 @@ describe("Moves - Follow Me", () => {
game.doAttack(getMovePosition(game.scene, 1, Moves.FOLLOW_ME));
await game.phaseInterceptor.to(TurnEndPhase, false);
playerPokemon.sort((a, b) => a.getBattleStat(Stat.SPD) - b.getBattleStat(Stat.SPD));
playerPokemon.sort((a, b) => a.getEffectiveStat(Stat.SPD) - b.getEffectiveStat(Stat.SPD));
expect(playerPokemon[1].hp).toBeLessThan(playerStartingHp[1]);
expect(playerPokemon[0].hp).toBe(playerStartingHp[0]);

View File

@ -1,5 +1,5 @@
import { BattleStat } from "#app/data/battle-stat";
import { MoveEndPhase, TurnInitPhase } from "#app/phases";
import { Stat } from "#enums/stat";
import { TurnInitPhase } from "#app/phases";
import GameManager from "#test/utils/gameManager";
import { getMovePosition } from "#test/utils/gameManagerUtils";
import { Abilities } from "#enums/abilities";
@ -11,7 +11,6 @@ import { SPLASH_ONLY } from "#test/utils/testUtils";
import { allMoves } from "#app/data/move.js";
describe("Moves - Freezy Frost", () => {
describe("integration tests", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
@ -39,44 +38,25 @@ describe("Moves - Freezy Frost", () => {
game.override.ability(Abilities.NONE);
});
it("Uses Swords Dance to raise own ATK by 2, Charm to lower enemy ATK by 2, player uses Freezy Frost to clear all stat changes", { timeout: 10000 }, async () => {
it("should clear all stat stage changes", { timeout: 10000 }, async () => {
await game.startBattle([Species.RATTATA]);
const user = game.scene.getPlayerPokemon()!;
const enemy = game.scene.getEnemyPokemon()!;
expect(user.summonData.battleStats[BattleStat.ATK]).toBe(0);
expect(enemy.summonData.battleStats[BattleStat.ATK]).toBe(0);
expect(user.getStatStage(Stat.ATK)).toBe(0);
expect(enemy.getStatStage(Stat.ATK)).toBe(0);
game.doAttack(getMovePosition(game.scene, 0, Moves.SWORDS_DANCE));
await game.phaseInterceptor.to(TurnInitPhase);
game.doAttack(getMovePosition(game.scene, 0, Moves.CHARM));
await game.phaseInterceptor.to(TurnInitPhase);
const userAtkBefore = user.summonData.battleStats[BattleStat.ATK];
const enemyAtkBefore = enemy.summonData.battleStats[BattleStat.ATK];
expect(userAtkBefore).toBe(2);
expect(enemyAtkBefore).toBe(-2);
expect(user.getStatStage(Stat.ATK)).toBe(2);
expect(enemy.getStatStage(Stat.ATK)).toBe(-2);
game.doAttack(getMovePosition(game.scene, 0, Moves.FREEZY_FROST));
await game.phaseInterceptor.to(TurnInitPhase);
expect(user.summonData.battleStats[BattleStat.ATK]).toBe(0);
expect(enemy.summonData.battleStats[BattleStat.ATK]).toBe(0);
});
it("Uses Swords Dance to raise own ATK by 2, Charm to lower enemy ATK by 2, enemy uses Freezy Frost to clear all stat changes", { timeout: 10000 }, async () => {
game.override.enemyMoveset([Moves.FREEZY_FROST, Moves.FREEZY_FROST, Moves.FREEZY_FROST, Moves.FREEZY_FROST]);
await game.startBattle([Species.SHUCKLE]); // Shuckle for slower Swords Dance on first turn so Freezy Frost doesn't affect it.
const user = game.scene.getPlayerPokemon()!;
expect(user.summonData.battleStats[BattleStat.ATK]).toBe(0);
game.doAttack(getMovePosition(game.scene, 0, Moves.SWORDS_DANCE));
await game.phaseInterceptor.to(TurnInitPhase);
const userAtkBefore = user.summonData.battleStats[BattleStat.ATK];
expect(userAtkBefore).toBe(2);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.phaseInterceptor.to(MoveEndPhase);
expect(user.summonData.battleStats[BattleStat.ATK]).toBe(0);
});
expect(user.getStatStage(Stat.ATK)).toBe(0);
expect(enemy.getStatStage(Stat.ATK)).toBe(0);
});
});

View File

@ -1,16 +1,13 @@
import { BattleStat } from "#app/data/battle-stat";
import { Stat } from "#app/data/pokemon-stat";
import { CommandPhase, EnemyCommandPhase, TurnInitPhase } from "#app/phases";
import { Stat } from "#enums/stat";
import { EnemyCommandPhase, TurnInitPhase } from "#app/phases";
import GameManager from "#test/utils/gameManager";
import { getMovePosition } from "#test/utils/gameManagerUtils";
import { Command } from "#app/ui/command-ui-handler";
import { Mode } from "#app/ui/ui";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
import { SPLASH_ONLY } from "../utils/testUtils";
describe("Moves - Growth", () => {
let phaserGame: Phaser.Game;
@ -28,37 +25,25 @@ describe("Moves - Growth", () => {
beforeEach(() => {
game = new GameManager(phaserGame);
const moveToUse = Moves.GROWTH;
game.override.battleType("single");
game.override.enemySpecies(Species.RATTATA);
game.override.enemyAbility(Abilities.MOXIE);
game.override.ability(Abilities.INSOMNIA);
game.override.startingLevel(2000);
game.override.moveset([moveToUse]);
game.override.enemyMoveset([Moves.TACKLE,Moves.TACKLE,Moves.TACKLE,Moves.TACKLE]);
game.override.moveset([ Moves.GROWTH ]);
game.override.enemyMoveset(SPLASH_ONLY);
});
it("GROWTH", async() => {
const moveToUse = Moves.GROWTH;
it("should raise SPATK stat stage by 1", async() => {
await game.startBattle([
Species.MIGHTYENA,
Species.MIGHTYENA,
Species.MIGHTYENA
]);
let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[Stat.SPATK]).toBe(0);
const battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.SPATK]).toBe(0);
const playerPokemon = game.scene.getPlayerPokemon()!;
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
});
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
const movePosition = getMovePosition(game.scene, 0, moveToUse);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(0);
game.doAttack(getMovePosition(game.scene, 0, Moves.GROWTH));
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnInitPhase);
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.SPATK]).toBe(1);
expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(1);
}, 20000);
});

View File

@ -1,5 +1,5 @@
import { BattleStat } from "#app/data/battle-stat";
import { MoveEndPhase, TurnInitPhase } from "#app/phases";
import { Stat } from "#enums/stat";
import { TurnInitPhase } from "#app/phases";
import GameManager from "#test/utils/gameManager";
import { getMovePosition } from "#test/utils/gameManagerUtils";
import { Abilities } from "#enums/abilities";
@ -37,44 +37,28 @@ describe("Moves - Haze", () => {
game.override.ability(Abilities.NONE);
});
it("Uses Swords Dance to raise own ATK by 2, Charm to lower enemy ATK by 2, player uses Haze to clear all stat changes", { timeout: 10000 }, async () => {
it("should reset all stat changes of all Pokemon on field", { timeout: 10000 }, async () => {
await game.startBattle([Species.RATTATA]);
const user = game.scene.getPlayerPokemon()!;
const enemy = game.scene.getEnemyPokemon()!;
expect(user.summonData.battleStats[BattleStat.ATK]).toBe(0);
expect(enemy.summonData.battleStats[BattleStat.ATK]).toBe(0);
expect(user.getStatStage(Stat.ATK)).toBe(0);
expect(enemy.getStatStage(Stat.ATK)).toBe(0);
game.doAttack(getMovePosition(game.scene, 0, Moves.SWORDS_DANCE));
await game.phaseInterceptor.to(TurnInitPhase);
game.doAttack(getMovePosition(game.scene, 0, Moves.CHARM));
await game.phaseInterceptor.to(TurnInitPhase);
const userAtkBefore = user.summonData.battleStats[BattleStat.ATK];
const enemyAtkBefore = enemy.summonData.battleStats[BattleStat.ATK];
expect(userAtkBefore).toBe(2);
expect(enemyAtkBefore).toBe(-2);
expect(user.getStatStage(Stat.ATK)).toBe(2);
expect(enemy.getStatStage(Stat.ATK)).toBe(-2);
game.doAttack(getMovePosition(game.scene, 0, Moves.HAZE));
await game.phaseInterceptor.to(TurnInitPhase);
expect(user.summonData.battleStats[BattleStat.ATK]).toBe(0);
expect(enemy.summonData.battleStats[BattleStat.ATK]).toBe(0);
});
it("Uses Swords Dance to raise own ATK by 2, Charm to lower enemy ATK by 2, enemy uses Haze to clear all stat changes", { timeout: 10000 }, async () => {
game.override.enemyMoveset([Moves.HAZE, Moves.HAZE, Moves.HAZE, Moves.HAZE]);
await game.startBattle([Species.SHUCKLE]); // Shuckle for slower Swords Dance on first turn so Haze doesn't affect it.
const user = game.scene.getPlayerPokemon()!;
expect(user.summonData.battleStats[BattleStat.ATK]).toBe(0);
game.doAttack(getMovePosition(game.scene, 0, Moves.SWORDS_DANCE));
await game.phaseInterceptor.to(TurnInitPhase);
const userAtkBefore = user.summonData.battleStats[BattleStat.ATK];
expect(userAtkBefore).toBe(2);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.phaseInterceptor.to(MoveEndPhase);
expect(user.summonData.battleStats[BattleStat.ATK]).toBe(0);
expect(user.getStatStage(Stat.ATK)).toBe(0);
expect(enemy.getStatStage(Stat.ATK)).toBe(0);
});
});
});

View File

@ -1,4 +1,4 @@
import { BattleStat } from "#app/data/battle-stat.js";
import { Stat } from "#enums/stat";
import { MoveEndPhase, StatChangePhase } from "#app/phases";
import GameManager from "#test/utils/gameManager";
import { getMovePosition } from "#test/utils/gameManagerUtils";
@ -36,17 +36,17 @@ describe("Moves - Make It Rain", () => {
game.override.enemyLevel(100);
});
it("should only reduce Sp. Atk. once in a double battle", async () => {
it("should only lower SPATK stat stage by 1 once in a double battle", async () => {
await game.startBattle([Species.CHARIZARD, Species.BLASTOISE]);
const playerPokemon = game.scene.getPlayerField();
const playerPokemon = game.scene.getPlayerPokemon()!;
game.doAttack(getMovePosition(game.scene, 0, Moves.MAKE_IT_RAIN));
game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH));
await game.phaseInterceptor.to(MoveEndPhase);
expect(playerPokemon[0].summonData.battleStats[BattleStat.SPATK]).toBe(-1);
expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(-1);
}, TIMEOUT);
it("should apply effects even if the target faints", async () => {
@ -63,7 +63,7 @@ describe("Moves - Make It Rain", () => {
await game.phaseInterceptor.to(StatChangePhase);
expect(enemyPokemon.isFainted()).toBe(true);
expect(playerPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(-1);
expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(-1);
}, TIMEOUT);
it("should reduce Sp. Atk. once after KOing two enemies", async () => {
@ -71,7 +71,7 @@ describe("Moves - Make It Rain", () => {
await game.startBattle([Species.CHARIZARD, Species.BLASTOISE]);
const playerPokemon = game.scene.getPlayerField();
const playerPokemon = game.scene.getPlayerPokemon()!;
const enemyPokemon = game.scene.getEnemyField();
game.doAttack(getMovePosition(game.scene, 0, Moves.MAKE_IT_RAIN));
@ -80,13 +80,13 @@ describe("Moves - Make It Rain", () => {
await game.phaseInterceptor.to(StatChangePhase);
enemyPokemon.forEach(p => expect(p.isFainted()).toBe(true));
expect(playerPokemon[0].summonData.battleStats[BattleStat.SPATK]).toBe(-1);
expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(-1);
}, TIMEOUT);
it("should reduce Sp. Atk if it only hits the second target", async () => {
it("should lower SPATK stat stage by 1 if it only hits the second target", async () => {
await game.startBattle([Species.CHARIZARD, Species.BLASTOISE]);
const playerPokemon = game.scene.getPlayerField();
const playerPokemon = game.scene.getPlayerPokemon()!;
game.doAttack(getMovePosition(game.scene, 0, Moves.MAKE_IT_RAIN));
game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH));
@ -96,6 +96,6 @@ describe("Moves - Make It Rain", () => {
await game.phaseInterceptor.to(MoveEndPhase);
expect(playerPokemon[0].summonData.battleStats[BattleStat.SPATK]).toBe(-1);
expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(-1);
}, TIMEOUT);
});

View File

@ -1,4 +1,4 @@
import { BattleStat } from "#app/data/battle-stat";
import { Stat } from "#enums/stat";
import { TrappedTag } from "#app/data/battler-tags.js";
import { CommandPhase, MoveEndPhase, TurnInitPhase } from "#app/phases";
import GameManager from "#test/utils/gameManager";
@ -39,40 +39,40 @@ describe("Moves - Octolock", () => {
game.override.ability(Abilities.BALL_FETCH);
});
it("Reduces DEf and SPDEF by 1 each turn", { timeout: 10000 }, async () => {
it("lowers DEF and SPDEF stat stages of the target Pokemon by 1 each turn", { timeout: 10000 }, async () => {
await game.startBattle([Species.GRAPPLOCT]);
const enemyPokemon = game.scene.getEnemyField();
const enemyPokemon = game.scene.getEnemyPokemon()!;
// use Octolock and advance to init phase of next turn to check for stat changes
game.doAttack(getMovePosition(game.scene, 0, Moves.OCTOLOCK));
await game.phaseInterceptor.to(TurnInitPhase);
expect(enemyPokemon[0].summonData.battleStats[BattleStat.DEF]).toBe(-1);
expect(enemyPokemon[0].summonData.battleStats[BattleStat.SPDEF]).toBe(-1);
expect(enemyPokemon.getStatStage(Stat.DEF)).toBe(-1);
expect(enemyPokemon.getStatStage(Stat.SPDEF)).toBe(-1);
// take a second turn to make sure stat changes occur again
await game.phaseInterceptor.to(CommandPhase);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.phaseInterceptor.to(TurnInitPhase);
expect(enemyPokemon[0].summonData.battleStats[BattleStat.DEF]).toBe(-2);
expect(enemyPokemon[0].summonData.battleStats[BattleStat.SPDEF]).toBe(-2);
expect(enemyPokemon.getStatStage(Stat.DEF)).toBe(-2);
expect(enemyPokemon.getStatStage(Stat.SPDEF)).toBe(-2);
});
it("Traps the target pokemon", { timeout: 10000 }, async () => {
it("traps the target Pokemon", { timeout: 10000 }, async () => {
await game.startBattle([Species.GRAPPLOCT]);
const enemyPokemon = game.scene.getEnemyField();
const enemyPokemon = game.scene.getEnemyPokemon()!;
// before Octolock - enemy should not be trapped
expect(enemyPokemon[0].findTag(t => t instanceof TrappedTag)).toBeUndefined();
expect(enemyPokemon.findTag(t => t instanceof TrappedTag)).toBeUndefined();
game.doAttack(getMovePosition(game.scene, 0, Moves.OCTOLOCK));
// after Octolock - enemy should be trapped
await game.phaseInterceptor.to(MoveEndPhase);
expect(enemyPokemon[0].findTag(t => t instanceof TrappedTag)).toBeDefined();
expect(enemyPokemon.findTag(t => t instanceof TrappedTag)).toBeDefined();
});
});
});

View File

@ -1,4 +1,4 @@
import { BattleStat } from "#app/data/battle-stat";
import { Stat } from "#enums/stat";
import { StockpilingTag } from "#app/data/battler-tags.js";
import { allMoves } from "#app/data/move.js";
import { BattlerTagType } from "#app/enums/battler-tag-type.js";
@ -16,6 +16,8 @@ describe("Moves - Spit Up", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
const spitUp = allMoves[Moves.SPIT_UP];
beforeAll(() => {
phaserGame = new Phaser.Game({ type: Phaser.HEADLESS });
});
@ -34,8 +36,10 @@ describe("Moves - Spit Up", () => {
game.override.enemyAbility(Abilities.NONE);
game.override.enemyLevel(2000);
game.override.moveset([Moves.SPIT_UP, Moves.SPIT_UP, Moves.SPIT_UP, Moves.SPIT_UP]);
game.override.moveset([ spitUp.id, spitUp.id, spitUp.id, spitUp.id ]);
game.override.ability(Abilities.NONE);
vi.spyOn(spitUp, "calculateBattlePower");
});
describe("consumes all stockpile stacks to deal damage (scaling with stacks)", () => {
@ -52,13 +56,11 @@ describe("Moves - Spit Up", () => {
expect(stockpilingTag).toBeDefined();
expect(stockpilingTag.stockpiledCount).toBe(stacksToSetup);
vi.spyOn(allMoves[Moves.SPIT_UP], "calculateBattlePower");
game.doAttack(0);
await game.phaseInterceptor.to(TurnInitPhase);
expect(allMoves[Moves.SPIT_UP].calculateBattlePower).toHaveBeenCalledOnce();
expect(allMoves[Moves.SPIT_UP].calculateBattlePower).toHaveReturnedWith(expectedPower);
expect(spitUp.calculateBattlePower).toHaveBeenCalledOnce();
expect(spitUp.calculateBattlePower).toHaveReturnedWith(expectedPower);
expect(pokemon.getTag(StockpilingTag)).toBeUndefined();
});
@ -77,13 +79,11 @@ describe("Moves - Spit Up", () => {
expect(stockpilingTag).toBeDefined();
expect(stockpilingTag.stockpiledCount).toBe(stacksToSetup);
vi.spyOn(allMoves[Moves.SPIT_UP], "calculateBattlePower");
game.doAttack(0);
await game.phaseInterceptor.to(TurnInitPhase);
expect(allMoves[Moves.SPIT_UP].calculateBattlePower).toHaveBeenCalledOnce();
expect(allMoves[Moves.SPIT_UP].calculateBattlePower).toHaveReturnedWith(expectedPower);
expect(spitUp.calculateBattlePower).toHaveBeenCalledOnce();
expect(spitUp.calculateBattlePower).toHaveReturnedWith(expectedPower);
expect(pokemon.getTag(StockpilingTag)).toBeUndefined();
});
@ -103,13 +103,11 @@ describe("Moves - Spit Up", () => {
expect(stockpilingTag).toBeDefined();
expect(stockpilingTag.stockpiledCount).toBe(stacksToSetup);
vi.spyOn(allMoves[Moves.SPIT_UP], "calculateBattlePower");
game.doAttack(0);
await game.phaseInterceptor.to(TurnInitPhase);
expect(allMoves[Moves.SPIT_UP].calculateBattlePower).toHaveBeenCalledOnce();
expect(allMoves[Moves.SPIT_UP].calculateBattlePower).toHaveReturnedWith(expectedPower);
expect(spitUp.calculateBattlePower).toHaveBeenCalledOnce();
expect(spitUp.calculateBattlePower).toHaveReturnedWith(expectedPower);
expect(pokemon.getTag(StockpilingTag)).toBeUndefined();
});
@ -123,14 +121,12 @@ describe("Moves - Spit Up", () => {
const stockpilingTag = pokemon.getTag(StockpilingTag)!;
expect(stockpilingTag).toBeUndefined();
vi.spyOn(allMoves[Moves.SPIT_UP], "calculateBattlePower");
game.doAttack(0);
await game.phaseInterceptor.to(TurnInitPhase);
expect(pokemon.getMoveHistory().at(-1)).toMatchObject<TurnMove>({ move: Moves.SPIT_UP, result: MoveResult.FAIL });
expect(allMoves[Moves.SPIT_UP].calculateBattlePower).not.toHaveBeenCalled();
expect(spitUp.calculateBattlePower).not.toHaveBeenCalled();
});
describe("restores stat boosts granted by stacks", () => {
@ -143,22 +139,20 @@ describe("Moves - Spit Up", () => {
const stockpilingTag = pokemon.getTag(StockpilingTag)!;
expect(stockpilingTag).toBeDefined();
vi.spyOn(allMoves[Moves.SPIT_UP], "calculateBattlePower");
game.doAttack(0);
await game.phaseInterceptor.to(MovePhase);
expect(pokemon.summonData.battleStats[BattleStat.DEF]).toBe(1);
expect(pokemon.summonData.battleStats[BattleStat.SPDEF]).toBe(1);
expect(pokemon.getStatStage(Stat.DEF)).toBe(1);
expect(pokemon.getStatStage(Stat.SPDEF)).toBe(1);
await game.phaseInterceptor.to(TurnInitPhase);
expect(pokemon.getMoveHistory().at(-1)).toMatchObject<TurnMove>({ move: Moves.SPIT_UP, result: MoveResult.SUCCESS });
expect(allMoves[Moves.SPIT_UP].calculateBattlePower).toHaveBeenCalledOnce();
expect(spitUp.calculateBattlePower).toHaveBeenCalledOnce();
expect(pokemon.summonData.battleStats[BattleStat.DEF]).toBe(0);
expect(pokemon.summonData.battleStats[BattleStat.SPDEF]).toBe(0);
expect(pokemon.getStatStage(Stat.DEF)).toBe(0);
expect(pokemon.getStatStage(Stat.SPDEF)).toBe(0);
expect(pokemon.getTag(StockpilingTag)).toBeUndefined();
});
@ -174,26 +168,19 @@ describe("Moves - Spit Up", () => {
// for the sake of simplicity (and because other tests cover the setup), set boost amounts directly
stockpilingTag.statChangeCounts = {
[BattleStat.DEF]: -1,
[BattleStat.SPDEF]: 2,
[Stat.DEF]: -1,
[Stat.SPDEF]: 2,
};
expect(stockpilingTag.statChangeCounts).toMatchObject({
[BattleStat.DEF]: -1,
[BattleStat.SPDEF]: 2,
});
vi.spyOn(allMoves[Moves.SPIT_UP], "calculateBattlePower");
game.doAttack(0);
await game.phaseInterceptor.to(TurnInitPhase);
expect(pokemon.getMoveHistory().at(-1)).toMatchObject<TurnMove>({ move: Moves.SPIT_UP, result: MoveResult.SUCCESS });
expect(allMoves[Moves.SPIT_UP].calculateBattlePower).toHaveBeenCalledOnce();
expect(spitUp.calculateBattlePower).toHaveBeenCalledOnce();
expect(pokemon.summonData.battleStats[BattleStat.DEF]).toBe(1);
expect(pokemon.summonData.battleStats[BattleStat.SPDEF]).toBe(-2);
expect(pokemon.getStatStage(Stat.DEF)).toBe(1);
expect(pokemon.getStatStage(Stat.SPDEF)).toBe(-2);
expect(pokemon.getTag(StockpilingTag)).toBeUndefined();
});

View File

@ -84,7 +84,7 @@ describe("Moves - Spotlight", () => {
* Spotlight will target the slower enemy. In this situation without Spotlight being used,
* the faster enemy would normally end up with the Center of Attention tag.
*/
enemyPokemon.sort((a, b) => b.getBattleStat(Stat.SPD) - a.getBattleStat(Stat.SPD));
enemyPokemon.sort((a, b) => b.getEffectiveStat(Stat.SPD) - a.getEffectiveStat(Stat.SPD));
const spotTarget = enemyPokemon[1].getBattlerIndex();
const attackTarget = enemyPokemon[0].getBattlerIndex();

View File

@ -1,4 +1,4 @@
import { BattleStat } from "#app/data/battle-stat";
import { Stat } from "#enums/stat";
import { StockpilingTag } from "#app/data/battler-tags.js";
import { MoveResult, TurnMove } from "#app/field/pokemon.js";
import { CommandPhase, TurnInitPhase } from "#app/phases";
@ -38,7 +38,7 @@ describe("Moves - Stockpile", () => {
game.override.ability(Abilities.NONE);
});
it("Gains a stockpile stack and increases DEF and SPDEF by 1 on each use, fails at max stacks (3)", { timeout: 10000 }, async () => {
it("gains a stockpile stack and raises user's DEF and SPDEF stat stages by 1 on each use, fails at max stacks (3)", { timeout: 10000 }, async () => {
await game.startBattle([Species.ABOMASNOW]);
const user = game.scene.getPlayerPokemon()!;
@ -47,8 +47,8 @@ describe("Moves - Stockpile", () => {
// we just have to know that they're implemented as a BattlerTag.
expect(user.getTag(StockpilingTag)).toBeUndefined();
expect(user.summonData.battleStats[BattleStat.DEF]).toBe(0);
expect(user.summonData.battleStats[BattleStat.SPDEF]).toBe(0);
expect(user.getStatStage(Stat.DEF)).toBe(0);
expect(user.getStatStage(Stat.SPDEF)).toBe(0);
// use Stockpile four times
for (let i = 0; i < 4; i++) {
@ -60,18 +60,16 @@ describe("Moves - Stockpile", () => {
await game.phaseInterceptor.to(TurnInitPhase);
const stockpilingTag = user.getTag(StockpilingTag)!;
const def = user.summonData.battleStats[BattleStat.DEF];
const spdef = user.summonData.battleStats[BattleStat.SPDEF];
if (i < 3) { // first three uses should behave normally
expect(def).toBe(i + 1);
expect(spdef).toBe(i + 1);
expect(user.getStatStage(Stat.DEF)).toBe(i + 1);
expect(user.getStatStage(Stat.SPDEF)).toBe(i + 1);
expect(stockpilingTag).toBeDefined();
expect(stockpilingTag.stockpiledCount).toBe(i + 1);
} else { // fourth should have failed
expect(def).toBe(3);
expect(spdef).toBe(3);
expect(user.getStatStage(Stat.DEF)).toBe(3);
expect(user.getStatStage(Stat.SPDEF)).toBe(3);
expect(stockpilingTag).toBeDefined();
expect(stockpilingTag.stockpiledCount).toBe(3);
expect(user.getMoveHistory().at(-1)).toMatchObject<TurnMove>({ result: MoveResult.FAIL, move: Moves.STOCKPILE });
@ -79,17 +77,17 @@ describe("Moves - Stockpile", () => {
}
});
it("Gains a stockpile stack even if DEF and SPDEF are at +6", { timeout: 10000 }, async () => {
it("gains a stockpile stack even if user's DEF and SPDEF stat stages are at +6", { timeout: 10000 }, async () => {
await game.startBattle([Species.ABOMASNOW]);
const user = game.scene.getPlayerPokemon()!;
user.summonData.battleStats[BattleStat.DEF] = 6;
user.summonData.battleStats[BattleStat.SPDEF] = 6;
user.setStatStage(Stat.DEF, 6);
user.setStatStage(Stat.SPDEF, 6);
expect(user.getTag(StockpilingTag)).toBeUndefined();
expect(user.summonData.battleStats[BattleStat.DEF]).toBe(6);
expect(user.summonData.battleStats[BattleStat.SPDEF]).toBe(6);
expect(user.getStatStage(Stat.DEF)).toBe(6);
expect(user.getStatStage(Stat.SPDEF)).toBe(6);
game.doAttack(getMovePosition(game.scene, 0, Moves.STOCKPILE));
await game.phaseInterceptor.to(TurnInitPhase);
@ -97,8 +95,8 @@ describe("Moves - Stockpile", () => {
const stockpilingTag = user.getTag(StockpilingTag)!;
expect(stockpilingTag).toBeDefined();
expect(stockpilingTag.stockpiledCount).toBe(1);
expect(user.summonData.battleStats[BattleStat.DEF]).toBe(6);
expect(user.summonData.battleStats[BattleStat.SPDEF]).toBe(6);
expect(user.getStatStage(Stat.DEF)).toBe(6);
expect(user.getStatStage(Stat.SPDEF)).toBe(6);
// do it again, just for good measure
await game.phaseInterceptor.to(CommandPhase);
@ -109,8 +107,8 @@ describe("Moves - Stockpile", () => {
const stockpilingTagAgain = user.getTag(StockpilingTag)!;
expect(stockpilingTagAgain).toBeDefined();
expect(stockpilingTagAgain.stockpiledCount).toBe(2);
expect(user.summonData.battleStats[BattleStat.DEF]).toBe(6);
expect(user.summonData.battleStats[BattleStat.SPDEF]).toBe(6);
expect(user.getStatStage(Stat.DEF)).toBe(6);
expect(user.getStatStage(Stat.SPDEF)).toBe(6);
});
});
});

View File

@ -1,4 +1,4 @@
import { BattleStat } from "#app/data/battle-stat";
import { Stat } from "#enums/stat";
import { StockpilingTag } from "#app/data/battler-tags.js";
import { BattlerTagType } from "#app/enums/battler-tag-type.js";
import { MoveResult, TurnMove } from "#app/field/pokemon.js";
@ -137,7 +137,7 @@ describe("Moves - Swallow", () => {
expect(pokemon.getMoveHistory().at(-1)).toMatchObject<TurnMove>({ move: Moves.SWALLOW, result: MoveResult.FAIL });
});
describe("restores stat boosts granted by stacks", () => {
describe("restores stat stage boosts granted by stacks", () => {
it("decreases stats based on stored values (both boosts equal)", { timeout: 10000 }, async () => {
await game.startBattle([Species.ABOMASNOW]);
@ -150,20 +150,20 @@ describe("Moves - Swallow", () => {
game.doAttack(0);
await game.phaseInterceptor.to(MovePhase);
expect(pokemon.summonData.battleStats[BattleStat.DEF]).toBe(1);
expect(pokemon.summonData.battleStats[BattleStat.SPDEF]).toBe(1);
expect(pokemon.getStatStage(Stat.DEF)).toBe(1);
expect(pokemon.getStatStage(Stat.SPDEF)).toBe(1);
await game.phaseInterceptor.to(TurnInitPhase);
expect(pokemon.getMoveHistory().at(-1)).toMatchObject<TurnMove>({ move: Moves.SWALLOW, result: MoveResult.SUCCESS });
expect(pokemon.summonData.battleStats[BattleStat.DEF]).toBe(0);
expect(pokemon.summonData.battleStats[BattleStat.SPDEF]).toBe(0);
expect(pokemon.getStatStage(Stat.DEF)).toBe(0);
expect(pokemon.getStatStage(Stat.SPDEF)).toBe(0);
expect(pokemon.getTag(StockpilingTag)).toBeUndefined();
});
it("decreases stats based on stored values (different boosts)", { timeout: 10000 }, async () => {
it("lower stat stages based on stored values (different boosts)", { timeout: 10000 }, async () => {
await game.startBattle([Species.ABOMASNOW]);
const pokemon = game.scene.getPlayerPokemon()!;
@ -174,22 +174,17 @@ describe("Moves - Swallow", () => {
// for the sake of simplicity (and because other tests cover the setup), set boost amounts directly
stockpilingTag.statChangeCounts = {
[BattleStat.DEF]: -1,
[BattleStat.SPDEF]: 2,
[Stat.DEF]: -1,
[Stat.SPDEF]: 2,
};
expect(stockpilingTag.statChangeCounts).toMatchObject({
[BattleStat.DEF]: -1,
[BattleStat.SPDEF]: 2,
});
game.doAttack(0);
await game.phaseInterceptor.to(TurnInitPhase);
expect(pokemon.getMoveHistory().at(-1)).toMatchObject<TurnMove>({ move: Moves.SWALLOW, result: MoveResult.SUCCESS });
expect(pokemon.summonData.battleStats[BattleStat.DEF]).toBe(1);
expect(pokemon.summonData.battleStats[BattleStat.SPDEF]).toBe(-2);
expect(pokemon.getStatStage(Stat.DEF)).toBe(1);
expect(pokemon.getStatStage(Stat.SPDEF)).toBe(-2);
expect(pokemon.getTag(StockpilingTag)).toBeUndefined();
});

View File

@ -1,14 +1,13 @@
import { BattleStat } from "#app/data/battle-stat";
import { CommandPhase, EnemyCommandPhase, TurnInitPhase } from "#app/phases";
import { Stat } from "#enums/stat";
import { EnemyCommandPhase, TurnInitPhase } from "#app/phases";
import GameManager from "#test/utils/gameManager";
import { getMovePosition } from "#test/utils/gameManagerUtils";
import { Command } from "#app/ui/command-ui-handler";
import { Mode } from "#app/ui/ui";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
import { SPLASH_ONLY } from "../utils/testUtils";
describe("Moves - Tail whip", () => {
@ -33,29 +32,23 @@ describe("Moves - Tail whip", () => {
game.override.enemyAbility(Abilities.INSOMNIA);
game.override.ability(Abilities.INSOMNIA);
game.override.startingLevel(2000);
game.override.moveset([moveToUse]);
game.override.enemyMoveset([Moves.TACKLE,Moves.TACKLE,Moves.TACKLE,Moves.TACKLE]);
game.override.moveset([ moveToUse ]);
game.override.enemyMoveset(SPLASH_ONLY);
});
it("TAIL_WHIP", async() => {
it("should lower DEF stat stage by 1", async() => {
const moveToUse = Moves.TAIL_WHIP;
await game.startBattle([
Species.MIGHTYENA,
Species.MIGHTYENA,
]);
let battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.DEF]).toBe(0);
const enemyPokemon = game.scene.getEnemyPokemon()!;
expect(enemyPokemon.getStatStage(Stat.DEF)).toBe(0);
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
});
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
const movePosition = getMovePosition(game.scene, 0, moveToUse);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
game.doAttack(getMovePosition(game.scene, 0, moveToUse));
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnInitPhase);
battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.DEF]).toBe(-1);
expect(enemyPokemon.getStatStage(Stat.DEF)).toBe(-1);
}, 20000);
});

View File

@ -39,16 +39,16 @@ describe("Abilities - Wind Rider", () => {
const magikarpSpd = magikarp.getStat(Stat.SPD);
const meowthSpd = meowth.getStat(Stat.SPD);
expect(magikarp.getBattleStat(Stat.SPD)).equal(magikarpSpd);
expect(meowth.getBattleStat(Stat.SPD)).equal(meowthSpd);
expect(magikarp.getEffectiveStat(Stat.SPD)).equal(magikarpSpd);
expect(meowth.getEffectiveStat(Stat.SPD)).equal(meowthSpd);
game.doAttack(getMovePosition(game.scene, 0, Moves.TAILWIND));
game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH));
await game.phaseInterceptor.to(TurnEndPhase);
expect(magikarp.getBattleStat(Stat.SPD)).toBe(magikarpSpd * 2);
expect(meowth.getBattleStat(Stat.SPD)).toBe(meowthSpd * 2);
expect(magikarp.getEffectiveStat(Stat.SPD)).toBe(magikarpSpd * 2);
expect(meowth.getEffectiveStat(Stat.SPD)).toBe(meowthSpd * 2);
expect(game.scene.arena.getTagOnSide(ArenaTagType.TAILWIND, ArenaTagSide.PLAYER)).toBeDefined();
});
@ -87,8 +87,8 @@ describe("Abilities - Wind Rider", () => {
const enemySpd = enemy.getStat(Stat.SPD);
expect(ally.getBattleStat(Stat.SPD)).equal(allySpd);
expect(enemy.getBattleStat(Stat.SPD)).equal(enemySpd);
expect(ally.getEffectiveStat(Stat.SPD)).equal(allySpd);
expect(enemy.getEffectiveStat(Stat.SPD)).equal(enemySpd);
expect(game.scene.arena.getTagOnSide(ArenaTagType.TAILWIND, ArenaTagSide.PLAYER)).toBeUndefined();
expect(game.scene.arena.getTagOnSide(ArenaTagType.TAILWIND, ArenaTagSide.ENEMY)).toBeUndefined();
@ -96,8 +96,8 @@ describe("Abilities - Wind Rider", () => {
await game.phaseInterceptor.to(TurnEndPhase);
expect(ally.getBattleStat(Stat.SPD)).toBe(allySpd * 2);
expect(enemy.getBattleStat(Stat.SPD)).equal(enemySpd);
expect(ally.getEffectiveStat(Stat.SPD)).toBe(allySpd * 2);
expect(enemy.getEffectiveStat(Stat.SPD)).equal(enemySpd);
expect(game.scene.arena.getTagOnSide(ArenaTagType.TAILWIND, ArenaTagSide.PLAYER)).toBeDefined();
expect(game.scene.arena.getTagOnSide(ArenaTagType.TAILWIND, ArenaTagSide.ENEMY)).toBeUndefined();
});

View File

@ -1,4 +1,4 @@
import { BattleStat } from "#app/data/battle-stat.js";
import { Stat } from "#enums/stat";
import { ArenaTagType } from "#app/enums/arena-tag-type.js";
import { MoveEndPhase, TurnEndPhase } from "#app/phases";
import GameManager from "#test/utils/gameManager";
@ -60,7 +60,6 @@ describe("Moves - Tidy Up", () => {
game.doAttack(getMovePosition(game.scene, 0, Moves.TIDY_UP));
await game.phaseInterceptor.to(MoveEndPhase);
expect(game.scene.arena.getTag(ArenaTagType.STEALTH_ROCK)).toBeUndefined();
}, 20000);
it("toxic spikes are cleared", async() => {
@ -73,7 +72,6 @@ describe("Moves - Tidy Up", () => {
game.doAttack(getMovePosition(game.scene, 0, Moves.TIDY_UP));
await game.phaseInterceptor.to(MoveEndPhase);
expect(game.scene.arena.getTag(ArenaTagType.TOXIC_SPIKES)).toBeUndefined();
}, 20000);
it("sticky webs are cleared", async() => {
@ -87,7 +85,6 @@ describe("Moves - Tidy Up", () => {
game.doAttack(getMovePosition(game.scene, 0, Moves.TIDY_UP));
await game.phaseInterceptor.to(MoveEndPhase);
expect(game.scene.arena.getTag(ArenaTagType.STICKY_WEB)).toBeUndefined();
}, 20000);
it.skip("substitutes are cleared", async() => {
@ -101,22 +98,20 @@ describe("Moves - Tidy Up", () => {
game.doAttack(getMovePosition(game.scene, 0, Moves.TIDY_UP));
await game.phaseInterceptor.to(MoveEndPhase);
// TODO: check for subs here once the move is implemented
}, 20000);
it("user's stats are raised with no traps set", async() => {
await game.startBattle();
const player = game.scene.getPlayerPokemon()!.summonData.battleStats;
expect(player[BattleStat.ATK]).toBe(0);
expect(player[BattleStat.SPD]).toBe(0);
const playerPokemon = game.scene.getPlayerPokemon()!;
expect(playerPokemon.getStatStage(Stat.ATK)).toBe(0);
expect(playerPokemon.getStatStage(Stat.SPD)).toBe(0);
game.doAttack(getMovePosition(game.scene, 0, Moves.TIDY_UP));
await game.phaseInterceptor.to(TurnEndPhase);
expect(player[BattleStat.ATK]).toBe(+1);
expect(player[BattleStat.SPD]).toBe(+1);
expect(playerPokemon.getStatStage(Stat.ATK)).toBe(1);
expect(playerPokemon.getStatStage(Stat.SPD)).toBe(1);
}, 20000);
});

View File

@ -7,7 +7,7 @@ import { StatusEffect } from "../data/status-effect";
import BattleScene from "../battle-scene";
import { Type, getTypeRgb } from "../data/type";
import { getVariantTint } from "#app/data/variant";
import { BattleStat } from "#app/data/battle-stat";
import { Stat } from "#enums/stat";
import BattleFlyout from "./battle-flyout";
import { WindowVariant, addWindow } from "./ui-theme";
import i18next from "i18next";
@ -30,7 +30,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
private lastLevelExp: integer;
private lastLevel: integer;
private lastLevelCapped: boolean;
private lastBattleStats: string;
private lastStats: string;
private box: Phaser.GameObjects.Sprite;
private nameText: Phaser.GameObjects.Text;
@ -68,9 +68,9 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
public flyoutMenu?: BattleFlyout;
private battleStatOrder: BattleStat[];
private battleStatOrderPlayer = [BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.ACC, BattleStat.EVA, BattleStat.SPD];
private battleStatOrderEnemy = [BattleStat.HP, BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.ACC, BattleStat.EVA, BattleStat.SPD];
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 ];
constructor(scene: Phaser.Scene, x: number, y: number, player: boolean) {
super(scene, x, y);
@ -229,9 +229,9 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
const startingX = this.player ? -this.statsBox.width + 8 : -this.statsBox.width + 5;
const paddingX = this.player ? 4 : 2;
const statOverflow = this.player ? 1 : 0;
this.battleStatOrder = this.player ? this.battleStatOrderPlayer : this.battleStatOrderEnemy; // this tells us whether or not to use the player or enemy battle stat order
this.statOrder = this.player ? this.statOrderPlayer : this.statOrderEnemy; // this tells us whether or not to use the player or enemy battle stat order
this.battleStatOrder.map((s, i) => {
this.statOrder.map((s, i) => {
// we do a check for i > statOverflow to see when the stat labels go onto the next column
// For enemies, we have HP (i=0) by itself then a new column, so we check for i > 0
// For players, we don't have HP, so we start with i = 0 and i = 1 for our first column, and so need to check for i > 1
@ -239,25 +239,25 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
const baseY = -this.statsBox.height / 2 + 4; // this is the baseline for the y-axis
let statY: number; // this will be the y-axis placement for the labels
if (this.battleStatOrder[i] === BattleStat.SPD || this.battleStatOrder[i] === BattleStat.HP) {
if (this.statOrder[i] === Stat.SPD || this.statOrder[i] === Stat.HP) {
statY = baseY + 5;
} else {
statY = baseY + (!!(i % 2) === this.player ? 10 : 0); // we compare i % 2 against this.player to tell us where to place the label; because this.battleStatOrder for enemies has HP, this.battleStatOrder[1]=ATK, but for players this.battleStatOrder[0]=ATK, so this comparing i % 2 to this.player fixes this issue for us
}
const statLabel = this.scene.add.sprite(statX, statY, "pbinfo_stat", BattleStat[s]);
const statLabel = this.scene.add.sprite(statX, statY, "pbinfo_stat", Stat[s]);
statLabel.setName("icon_stat_label_" + i.toString());
statLabel.setOrigin(0, 0);
statLabels.push(statLabel);
this.statValuesContainer.add(statLabel);
const statNumber = this.scene.add.sprite(statX + statLabel.width, statY, "pbinfo_stat_numbers", this.battleStatOrder[i] !== BattleStat.HP ? "3" : "empty");
const statNumber = this.scene.add.sprite(statX + statLabel.width, statY, "pbinfo_stat_numbers", this.statOrder[i] !== Stat.HP ? "3" : "empty");
statNumber.setName("icon_stat_number_" + i.toString());
statNumber.setOrigin(0, 0);
this.statNumbers.push(statNumber);
this.statValuesContainer.add(statNumber);
if (this.battleStatOrder[i] === BattleStat.HP) {
if (this.statOrder[i] === Stat.HP) {
statLabel.setVisible(false);
statNumber.setVisible(false);
}
@ -433,10 +433,10 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
this.statValuesContainer.setPosition(8, 7);
}
const battleStats = this.battleStatOrder.map(() => 0);
const stats = this.statOrder.map(() => 0);
this.lastBattleStats = battleStats.join("");
this.updateBattleStats(battleStats);
this.lastStats = stats.join("");
this.updateStats(stats);
}
getTextureName(): string {
@ -653,9 +653,9 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
const battleStats = pokemon.getStatStages();
const battleStatsStr = battleStats.join("");
if (this.lastBattleStats !== battleStatsStr) {
this.updateBattleStats(battleStats);
this.lastBattleStats = battleStatsStr;
if (this.lastStats !== battleStatsStr) {
this.updateStats(battleStats);
this.lastStats = battleStatsStr;
}
this.shinyIcon.setVisible(pokemon.isShiny());
@ -767,10 +767,10 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
}
}
updateBattleStats(battleStats: integer[]): void {
this.battleStatOrder.map((s, i) => {
if (s !== BattleStat.HP) {
this.statNumbers[i].setFrame(battleStats[s].toString());
updateStats(stats: integer[]): void {
this.statOrder.map((s, i) => {
if (s !== Stat.HP) {
this.statNumbers[i].setFrame(stats[s].toString());
}
});
}

View File

@ -3,11 +3,11 @@ import { addBBCodeTextObject, addTextObject, getTextColor, TextStyle } from "./t
import { Mode } from "./ui";
import * as Utils from "../utils";
import MessageUiHandler from "./message-ui-handler";
import { getStatName, Stat } from "../data/pokemon-stat";
import { addWindow } from "./ui-theme";
import BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext";
import {Button} from "#enums/buttons";
import i18next from "i18next";
import { Stat, getStatKey, PERMANENT_STATS } from "#app/enums/stat.js";
export default class BattleMessageUiHandler extends MessageUiHandler {
private levelUpStatsContainer: Phaser.GameObjects.Container;
@ -98,9 +98,8 @@ export default class BattleMessageUiHandler extends MessageUiHandler {
const levelUpStatsLabelsContent = addTextObject(this.scene, (this.scene.game.canvas.width / 6) - 73, -94, "", TextStyle.WINDOW, { maxLines: 6 });
let levelUpStatsLabelText = "";
const stats = Utils.getEnumValues(Stat);
for (const s of stats) {
levelUpStatsLabelText += `${getStatName(s)}\n`;
for (const s of PERMANENT_STATS) {
levelUpStatsLabelText += `${getStatKey(s)}\n`;
}
levelUpStatsLabelsContent.text = levelUpStatsLabelText;
levelUpStatsLabelsContent.x -= levelUpStatsLabelsContent.displayWidth;

View File

@ -1,7 +1,7 @@
import BBCodeText from "phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/BBCodeText";
import BattleScene from "../battle-scene";
import { Stat, getStatName } from "../data/pokemon-stat";
import { TextStyle, addBBCodeTextObject, addTextObject, getTextColor } from "./text";
import { getStatKey, PERMANENT_STATS } from "#app/enums/stat.js";
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]];
@ -53,16 +53,16 @@ export class StatsContainer extends Phaser.GameObjects.Container {
this.ivStatValueTexts = [];
new Array(6).fill(null).map((_, i: integer) => {
const statLabel = addTextObject(this.scene, ivChartBg.x + (ivChartSize) * ivChartStatCoordMultipliers[i][0] * 1.325, ivChartBg.y + (ivChartSize) * ivChartStatCoordMultipliers[i][1] * 1.325 - 4 + ivLabelOffset[i], getStatName(i as Stat), TextStyle.TOOLTIP_CONTENT);
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);
statLabel.setOrigin(0.5);
this.ivStatValueTexts[i] = addBBCodeTextObject(this.scene, statLabel.x, statLabel.y + 8, "0", TextStyle.TOOLTIP_CONTENT);
this.ivStatValueTexts[i].setOrigin(0.5);
this.ivStatValueTexts[s] = addBBCodeTextObject(this.scene, statLabel.x, statLabel.y + 8, "0", TextStyle.TOOLTIP_CONTENT);
this.ivStatValueTexts[s].setOrigin(0.5);
this.add(statLabel);
this.add(this.ivStatValueTexts[i]);
});
this.add(this.ivStatValueTexts[s]);
}
}
updateIvs(ivs: integer[], originalIvs?: integer[]): void {

View File

@ -11,7 +11,6 @@ import Move, { MoveCategory } from "../data/move";
import { getPokeballAtlasKey } from "../data/pokeball";
import { getGenderColor, getGenderSymbol } from "../data/gender";
import { getLevelRelExp, getLevelTotalExp } from "../data/exp";
import { Stat, getStatName } from "../data/pokemon-stat";
import { PokemonHeldItemModifier } from "../modifier/modifier";
import { StatusEffect } from "../data/status-effect";
import { getBiomeName } from "../data/biomes";
@ -23,6 +22,7 @@ import { Ability } from "../data/ability.js";
import i18next from "i18next";
import {modifierSortFunc} from "../modifier/modifier";
import { PlayerGender } from "#enums/player-gender";
import { Stat, getStatKey, PERMANENT_STATS } from "#app/enums/stat.js";
enum Page {
PROFILE,
@ -838,10 +838,8 @@ export default class SummaryUiHandler extends UiHandler {
const statsContainer = this.scene.add.container(0, -pageBg.height);
pageContainer.add(statsContainer);
const stats = Utils.getEnumValues(Stat) as Stat[];
stats.forEach((stat, s) => {
const statName = getStatName(stat);
PERMANENT_STATS.forEach((stat, s) => {
const statName = getStatKey(stat);
const rowIndex = s % 3;
const colIndex = Math.floor(s / 3);
@ -852,7 +850,7 @@ export default class SummaryUiHandler extends UiHandler {
statsContainer.add(statLabel);
const statValueText = stat !== Stat.HP
? Utils.formatStat(this.pokemon?.stats[s]!) // TODO: is this bang correct?
? Utils.formatStat(this.pokemon?.getStat(stat)!) // TODO: is this bang correct?
: `${Utils.formatStat(this.pokemon?.hp!, true)}/${Utils.formatStat(this.pokemon?.getMaxHp()!, true)}`; // TODO: are those bangs correct?
const statValue = addTextObject(this.scene, 120 + 88 * colIndex, 56 + 16 * rowIndex, statValueText, TextStyle.WINDOW_ALT);