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 { StatusEffect } from "./status-effect";
import { BattlerIndex } from "../battle"; import { BattlerIndex } from "../battle";
import { BlockNonDirectDamageAbAttr, ProtectStatAbAttr, applyAbAttrs } from "./ability"; import { BlockNonDirectDamageAbAttr, ProtectStatAbAttr, applyAbAttrs } from "./ability";
import { BattleStat } from "./battle-stat"; import { Stat } from "#enums/stat";
import { CommonAnim, CommonBattleAnim } from "./battle-anims"; import { CommonAnim, CommonBattleAnim } from "./battle-anims";
import i18next from "i18next"; import i18next from "i18next";
import { Abilities } from "#enums/abilities"; import { Abilities } from "#enums/abilities";
@ -726,7 +726,7 @@ class StickyWebTag extends ArenaTrapTag {
if (!cancelled.value) { if (!cancelled.value) {
pokemon.scene.queueMessage(i18next.t("arenaTag:stickyWebActivateTrap", { pokemonName: pokemon.getNameToRender() })); pokemon.scene.queueMessage(i18next.t("arenaTag:stickyWebActivateTrap", { pokemonName: pokemon.getNameToRender() }));
const statLevels = new Utils.NumberHolder(-1); 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 // Raise attack by one stage if party member has WIND_RIDER ability
if (pokemon.hasAbility(Abilities.WIND_RIDER)) { if (pokemon.hasAbility(Abilities.WIND_RIDER)) {
pokemon.scene.unshiftPhase(new ShowAbilityPhase(pokemon.scene, pokemon.getBattlerIndex())); 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 { 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 { getPokemonNameWithAffix } from "../messages";
import Pokemon, { MoveResult, HitResult } from "../field/pokemon"; import Pokemon, { MoveResult, HitResult } from "../field/pokemon";
import { Stat, getStatName } from "./pokemon-stat";
import { StatusEffect } from "./status-effect"; import { StatusEffect } from "./status-effect";
import * as Utils from "../utils"; import * as Utils from "../utils";
import { ChargeAttr, MoveFlags, allMoves } from "./move"; import { ChargeAttr, MoveFlags, allMoves } from "./move";
@ -10,7 +9,6 @@ import { Type } from "./type";
import { BlockNonDirectDamageAbAttr, FlinchEffectAbAttr, ReverseDrainAbAttr, applyAbAttrs } from "./ability"; import { BlockNonDirectDamageAbAttr, FlinchEffectAbAttr, ReverseDrainAbAttr, applyAbAttrs } from "./ability";
import { TerrainType } from "./terrain"; import { TerrainType } from "./terrain";
import { WeatherType } from "./weather"; import { WeatherType } from "./weather";
import { BattleStat } from "./battle-stat";
import { allAbilities } from "./ability"; import { allAbilities } from "./ability";
import { SpeciesFormChangeManualTrigger } from "./pokemon-forms"; import { SpeciesFormChangeManualTrigger } from "./pokemon-forms";
import { Abilities } from "#enums/abilities"; import { Abilities } from "#enums/abilities";
@ -18,6 +16,7 @@ import { BattlerTagType } from "#enums/battler-tag-type";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import i18next from "#app/plugins/i18n.js"; import i18next from "#app/plugins/i18n.js";
import { Stat, BattleStat, EFFECTIVE_STATS, EffectiveStat, getStatKey } from "#app/enums/stat";
export enum BattlerTagLapseType { export enum BattlerTagLapseType {
FAINT, FAINT,
@ -296,8 +295,8 @@ export class ConfusedTag extends BattlerTag {
// 1/3 chance of hitting self with a 40 base power move // 1/3 chance of hitting self with a 40 base power move
if (pokemon.randSeedInt(3) === 0) { if (pokemon.randSeedInt(3) === 0) {
const atk = pokemon.getBattleStat(Stat.ATK); const atk = pokemon.getEffectiveStat(Stat.ATK);
const def = pokemon.getBattleStat(Stat.DEF); 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)); 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.scene.queueMessage(i18next.t("battle:battlerTagsConfusedLapseHurtItself"));
pokemon.damageAndUpdate(damage); pokemon.damageAndUpdate(damage);
@ -701,7 +700,7 @@ export class OctolockTag extends TrappedTag {
const shouldLapse = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType); const shouldLapse = lapseType !== BattlerTagLapseType.CUSTOM || super.lapse(pokemon, lapseType);
if (shouldLapse) { 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; 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 stat: BattleStat;
private levels: number; private levels: number;
@ -1044,7 +1043,7 @@ export class ContactStatChangeProtectedTag extends ProtectedTag {
*/ */
loadTag(source: BattlerTag | any): void { loadTag(source: BattlerTag | any): void {
super.loadTag(source); super.loadTag(source);
this.stat = source.stat as BattleStat; this.stat = source.stat;
this.levels = source.levels; this.levels = source.levels;
} }
@ -1055,7 +1054,7 @@ export class ContactStatChangeProtectedTag extends ProtectedTag {
const effectPhase = pokemon.scene.getCurrentPhase(); const effectPhase = pokemon.scene.getCurrentPhase();
if (effectPhase instanceof MoveEffectPhase && effectPhase.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) { if (effectPhase instanceof MoveEffectPhase && effectPhase.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) {
const attacker = effectPhase.getPokemon(); 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 { onAdd(pokemon: Pokemon): void {
super.onAdd(pokemon); super.onAdd(pokemon);
const stats = [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.SPD ]; let highestStat: EffectiveStat;
let highestStat: Stat; EFFECTIVE_STATS.map(s => pokemon.getEffectiveStat(s)).reduce((highestValue: number, value: number, i: number) => {
stats.map(s => pokemon.getBattleStat(s)).reduce((highestValue: number, value: number, i: number) => {
if (value > highestValue) { if (value > highestValue) {
highestStat = stats[i]; highestStat = EFFECTIVE_STATS[i];
return value; return value;
} }
return highestValue; return highestValue;
@ -1304,7 +1302,7 @@ export class HighestStatBoostTag extends AbilityBattlerTag {
break; 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 { onRemove(pokemon: Pokemon): void {
@ -1622,9 +1620,9 @@ export class IceFaceTag extends BattlerTag {
*/ */
export class StockpilingTag extends BattlerTag { export class StockpilingTag extends BattlerTag {
public stockpiledCount: number = 0; public stockpiledCount: number = 0;
public statChangeCounts: { [BattleStat.DEF]: number; [BattleStat.SPDEF]: number } = { public statChangeCounts: { [Stat.DEF]: number; [Stat.SPDEF]: number } = {
[BattleStat.DEF]: 0, [Stat.DEF]: 0,
[BattleStat.SPDEF]: 0 [Stat.SPDEF]: 0
}; };
constructor(sourceMove: Moves = Moves.NONE) { constructor(sourceMove: Moves = Moves.NONE) {
@ -1632,15 +1630,15 @@ export class StockpilingTag extends BattlerTag {
} }
private onStatsChanged: StatChangeCallback = (_, statsChanged, statChanges) => { private onStatsChanged: StatChangeCallback = (_, statsChanged, statChanges) => {
const defChange = statChanges[statsChanged.indexOf(BattleStat.DEF)] ?? 0; const defChange = statChanges[statsChanged.indexOf(Stat.DEF)] ?? 0;
const spDefChange = statChanges[statsChanged.indexOf(BattleStat.SPDEF)] ?? 0; const spDefChange = statChanges[statsChanged.indexOf(Stat.SPDEF)] ?? 0;
if (defChange) { if (defChange) {
this.statChangeCounts[BattleStat.DEF]++; this.statChangeCounts[Stat.DEF]++;
} }
if (spDefChange) { if (spDefChange) {
this.statChangeCounts[BattleStat.SPDEF]++; this.statChangeCounts[Stat.SPDEF]++;
} }
}; };
@ -1648,8 +1646,8 @@ export class StockpilingTag extends BattlerTag {
super.loadTag(source); super.loadTag(source);
this.stockpiledCount = source.stockpiledCount || 0; this.stockpiledCount = source.stockpiledCount || 0;
this.statChangeCounts = { this.statChangeCounts = {
[ BattleStat.DEF ]: source.statChangeCounts?.[ BattleStat.DEF ] ?? 0, [ Stat.DEF ]: source.statChangeCounts?.[ Stat.DEF ] ?? 0,
[ BattleStat.SPDEF ]: source.statChangeCounts?.[ BattleStat.SPDEF ] ?? 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. // 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, 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. * one stage for each stack which had successfully changed that particular stat during onAdd.
*/ */
onRemove(pokemon: Pokemon): void { onRemove(pokemon: Pokemon): void {
const defChange = this.statChangeCounts[BattleStat.DEF]; const defChange = this.statChangeCounts[Stat.DEF];
const spDefChange = this.statChangeCounts[BattleStat.SPDEF]; const spDefChange = this.statChangeCounts[Stat.SPDEF];
if (defChange) { 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) { 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: case BattlerTagType.SPIKY_SHIELD:
return new ContactDamageProtectedTag(sourceMove, 8); return new ContactDamageProtectedTag(sourceMove, 8);
case BattlerTagType.KINGS_SHIELD: case BattlerTagType.KINGS_SHIELD:
return new ContactStatChangeProtectedTag(sourceMove, tagType, BattleStat.ATK, -1); return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.ATK, -1);
case BattlerTagType.OBSTRUCT: case BattlerTagType.OBSTRUCT:
return new ContactStatChangeProtectedTag(sourceMove, tagType, BattleStat.DEF, -2); return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.DEF, -2);
case BattlerTagType.SILK_TRAP: case BattlerTagType.SILK_TRAP:
return new ContactStatChangeProtectedTag(sourceMove, tagType, BattleStat.SPD, -1); return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.SPD, -1);
case BattlerTagType.BANEFUL_BUNKER: case BattlerTagType.BANEFUL_BUNKER:
return new ContactPoisonProtectedTag(sourceMove); return new ContactPoisonProtectedTag(sourceMove);
case BattlerTagType.BURNING_BULWARK: case BattlerTagType.BURNING_BULWARK:

View File

@ -1,13 +1,13 @@
import { PokemonHealPhase, StatChangePhase } from "../phases"; import { PokemonHealPhase, StatChangePhase } from "../phases";
import { getPokemonNameWithAffix } from "../messages"; import { getPokemonNameWithAffix } from "../messages";
import Pokemon, { HitResult } from "../field/pokemon"; import Pokemon, { HitResult } from "../field/pokemon";
import { BattleStat } from "./battle-stat";
import { getStatusEffectHealText } from "./status-effect"; import { getStatusEffectHealText } from "./status-effect";
import * as Utils from "../utils"; import * as Utils from "../utils";
import { DoubleBerryEffectAbAttr, ReduceBerryUseThresholdAbAttr, applyAbAttrs } from "./ability"; import { DoubleBerryEffectAbAttr, ReduceBerryUseThresholdAbAttr, applyAbAttrs } from "./ability";
import i18next from "i18next"; import i18next from "i18next";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { BerryType } from "#enums/berry-type"; import { BerryType } from "#enums/berry-type";
import { BattleStat, Stat } from "#app/enums/stat";
export function getBerryName(berryType: BerryType): string { export function getBerryName(berryType: BerryType): string {
return i18next.t(`berry:${BerryType[berryType]}.name`); return i18next.t(`berry:${BerryType[berryType]}.name`);
@ -34,9 +34,10 @@ export function getBerryPredicate(berryType: BerryType): BerryPredicate {
case BerryType.SALAC: case BerryType.SALAC:
return (pokemon: Pokemon) => { return (pokemon: Pokemon) => {
const threshold = new Utils.NumberHolder(0.25); 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); 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: case BerryType.LANSAT:
return (pokemon: Pokemon) => { return (pokemon: Pokemon) => {
@ -94,10 +95,11 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
if (pokemon.battleData) { if (pokemon.battleData) {
pokemon.battleData.berriesEaten.push(berryType); pokemon.battleData.berriesEaten.push(berryType);
} }
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 statLevels = new Utils.NumberHolder(1); const stat: BattleStat = berryType - BerryType.ENIGMA;
applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, statLevels); const statStages = new Utils.NumberHolder(1);
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ battleStat ], statLevels.value)); applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, statStages);
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ stat ], statStages.value));
}; };
case BerryType.LANSAT: case BerryType.LANSAT:
return (pokemon: Pokemon) => { return (pokemon: Pokemon) => {
@ -111,9 +113,10 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc {
if (pokemon.battleData) { if (pokemon.battleData) {
pokemon.battleData.berriesEaten.push(berryType); pokemon.battleData.berriesEaten.push(berryType);
} }
const randStat = Utils.randSeedInt(Stat.EVA, Stat.ATK);
const statLevels = new Utils.NumberHolder(2); const statLevels = new Utils.NumberHolder(2);
applyAbAttrs(DoubleBerryEffectAbAttr, pokemon, null, statLevels); 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: case BerryType.LEPPA:
return (pokemon: Pokemon) => { 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 * as Utils from "../utils";
import { TextStyle, getBBCodeFrag } from "../ui/text"; import { TextStyle, getBBCodeFrag } from "../ui/text";
import { Nature } from "#enums/nature"; import { Nature } from "#enums/nature";
import { UiTheme } from "#enums/ui-theme"; import { UiTheme } from "#enums/ui-theme";
import i18next from "i18next"; import i18next from "i18next";
import { Stat, EFFECTIVE_STATS, getShortenedStatKey } from "#app/enums/stat.js";
export { Nature }; export { Nature };
@ -14,10 +14,9 @@ export function getNatureName(nature: Nature, includeStatEffects: boolean = fals
ret = i18next.t("nature:" + ret as any); ret = i18next.t("nature:" + ret as any);
} }
if (includeStatEffects) { if (includeStatEffects) {
const stats = Utils.getEnumValues(Stat).slice(1);
let increasedStat: Stat | null = null; let increasedStat: Stat | null = null;
let decreasedStat: Stat | null = null; let decreasedStat: Stat | null = null;
for (const stat of stats) { for (const stat of EFFECTIVE_STATS) {
const multiplier = getNatureStatMultiplier(nature, stat); const multiplier = getNatureStatMultiplier(nature, stat);
if (multiplier > 1) { if (multiplier > 1) {
increasedStat = stat; increasedStat = stat;
@ -28,7 +27,7 @@ export function getNatureName(nature: Nature, includeStatEffects: boolean = fals
const textStyle = forStarterSelect ? TextStyle.SUMMARY_ALT : TextStyle.WINDOW; const textStyle = forStarterSelect ? TextStyle.SUMMARY_ALT : TextStyle.WINDOW;
const getTextFrag = !ignoreBBCode ? (text: string, style: TextStyle) => getBBCodeFrag(text, style, uiTheme) : (text: string, style: TextStyle) => text; const getTextFrag = !ignoreBBCode ? (text: string, style: TextStyle) => getBBCodeFrag(text, style, uiTheme) : (text: string, style: TextStyle) => text;
if (increasedStat && decreasedStat) { 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 { } else {
ret = getTextFrag(`${ret}${!forStarterSelect ? "\n" : " "}(-)`, textStyle); 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 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 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 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 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 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 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 { Variant, VariantSet, variantColorCache } from "#app/data/variant";
import { variantData } from "#app/data/variant"; import { variantData } from "#app/data/variant";
import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from "../ui/battle-info"; 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 { default as PokemonSpecies, PokemonSpeciesForm, SpeciesFormKey, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm, getStarterValueFriendshipCap, speciesStarters, starterPassiveAbilities } from "../data/pokemon-species";
import { Constructor } from "#app/utils"; import { Constructor } from "#app/utils";
import * as Utils from "../utils"; import * as Utils from "../utils";
import { Type, TypeDamageMultiplier, getTypeDamageMultiplier, getTypeRgb } from "../data/type"; import { Type, TypeDamageMultiplier, getTypeDamageMultiplier, getTypeRgb } from "../data/type";
import { getLevelTotalExp } from "../data/exp"; 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 { 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 { PokeballType } from "../data/pokeball";
import { Gender } from "../data/gender"; 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 { Status, StatusEffect, getRandomStatus } from "../data/status-effect";
import { pokemonEvolutions, pokemonPrevolutions, SpeciesFormEvolution, SpeciesEvolutionCondition, FusionSpeciesFormEvolution } from "../data/pokemon-evolutions"; import { pokemonEvolutions, pokemonPrevolutions, SpeciesFormEvolution, SpeciesEvolutionCondition, FusionSpeciesFormEvolution } from "../data/pokemon-evolutions";
import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "../data/tms"; 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 { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, ExposedTag } from "../data/battler-tags";
import { WeatherType } from "../data/weather"; import { WeatherType } from "../data/weather";
import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "../data/arena-tag"; import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "../data/arena-tag";
import { Ability, AbAttr, BattleStatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldBattleStatMultiplierAbAttrs, FieldMultiplyBattleStatAbAttr, AddSecondStrikeAbAttr, IgnoreOpponentEvasionAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr } from "../data/ability"; import { 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 PokemonData from "../system/pokemon-data";
import { BattlerIndex } from "../battle"; import { BattlerIndex } from "../battle";
import { Mode } from "../ui/ui"; import { Mode } from "../ui/ui";
@ -49,7 +49,7 @@ import { Biome } from "#enums/biome";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { getPokemonNameWithAffix } from "#app/messages.js"; 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 { export enum FieldPosition {
CENTER, 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)); const statLevel = new Utils.IntegerHolder(this.getStatStage(stat));
if (opponent) { if (opponent) {
if (isCritical) { if (isCritical) {
@ -750,7 +750,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
break; break;
} }
} }
applyAbAttrs(IgnoreOpponentStatChangesAbAttr, opponent, null, statLevel); applyAbAttrs(IgnoreOpponentStatStageChangesAbAttr, opponent, null, statLevel);
if (move) { if (move) {
applyMoveAttrs(IgnoreOpponentStatChangesAttr, this, opponent, move, statLevel); 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); const fieldApplied = new Utils.BooleanHolder(false);
for (const pokemon of this.scene.getField(true)) { 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) { if (fieldApplied.value) {
break; 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)); let ret = statValue.value * (Math.max(2, 2 + statLevel.value) / Math.max(2, 2 - statLevel.value));
switch (stat) { switch (stat) {
case Stat.ATK: case Stat.ATK:
@ -1374,7 +1374,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const types = this.getTypes(true); const types = this.getTypes(true);
const enemyTypes = opponent.getTypes(true, true); const enemyTypes = opponent.getTypes(true, true);
/** Is this Pokemon faster than the opponent? */ /** 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. * 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. * 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(SacrificialAttr) ? 0.5 : 1)]);
movePool = movePool.map(m => [m[0], m[1] * (!!allMoves[m[0]].hasAttr(SacrificialAttrOnHit) ? 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 // 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 // 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)]); 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 userAccStage = new Utils.IntegerHolder(this.getStatStage(Stat.ACC));
const targetEvaStage = new Utils.IntegerHolder(target.getStatStage(Stat.EVA)); const targetEvaStage = new Utils.IntegerHolder(target.getStatStage(Stat.EVA));
applyAbAttrs(IgnoreOpponentStatChangesAbAttr, target, null, userAccStage); applyAbAttrs(IgnoreOpponentStatStageChangesAbAttr, target, null, userAccStage);
applyAbAttrs(IgnoreOpponentStatChangesAbAttr, this, null, targetEvaStage); applyAbAttrs(IgnoreOpponentStatStageChangesAbAttr, this, null, targetEvaStage);
applyAbAttrs(IgnoreOpponentEvasionAbAttr, this, null, targetEvaStage); applyAbAttrs(IgnoreOpponentEvasionAbAttr, this, null, targetEvaStage);
applyMoveAttrs(IgnoreOpponentStatChangesAttr, this, target, sourceMove, targetEvaStage); applyMoveAttrs(IgnoreOpponentStatChangesAttr, this, target, sourceMove, targetEvaStage);
this.scene.applyModifiers(TempStatStageBoosterModifier, this.isPlayer(), Stat.ACC, userAccStage); 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)); : 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); 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; accuracyMultiplier.value /= evasionMultiplier.value;
@ -2087,8 +2087,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
isCritical = false; isCritical = false;
} }
} }
const sourceAtk = new Utils.IntegerHolder(source.getBattleStat(isPhysical ? Stat.ATK : Stat.SPATK, this, undefined, isCritical)); const sourceAtk = new Utils.IntegerHolder(source.getEffectiveStat(isPhysical ? Stat.ATK : Stat.SPATK, this, undefined, isCritical));
const targetDef = new Utils.IntegerHolder(this.getBattleStat(isPhysical ? Stat.DEF : Stat.SPDEF, source, move, 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); const criticalMultiplier = new Utils.NumberHolder(isCritical ? 1.5 : 1);
applyAbAttrs(MultCritAbAttr, source, null, criticalMultiplier); applyAbAttrs(MultCritAbAttr, source, null, criticalMultiplier);
const screenMultiplier = new Utils.NumberHolder(1); 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 * @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 { transferSummon(source: Pokemon): void {
/** TODO: BattleStats // Copy all stat stages
for (const stat of battleStats) { for (const s of BATTLE_STATS) {
this.summonData.battleStats[stat] = source.summonData.battleStats[stat]; 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) { for (const tag of source.summonData.tags) {
// bypass yawn, and infatuation as those can not be passed via Baton Pass // 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); 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(); this.updateInfo();
} }
@ -4161,45 +4161,46 @@ export class EnemyPokemon extends Pokemon {
return true; return true;
} }
handleBossSegmentCleared(segmentIndex: integer): void { // TODO: BattleStat handleBossSegmentCleared(segmentIndex: integer): void {
while (segmentIndex - 1 < this.bossSegmentIndex) { 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); let boostedStat: EffectiveStat;
const statWeights = new Array().fill(battleStats.length).filter((bs: BattleStat) => this.summonData.battleStats[bs] < 6).map((bs: BattleStat) => this.getStat(bs + 1)); const statThresholds: number[] = [];
const statThresholds: integer[] = [];
let totalWeight = 0; let totalWeight = 0;
for (const bs of battleStats) {
totalWeight += statWeights[bs]; for (const i in statWeights) {
totalWeight += statWeights[i];
statThresholds.push(totalWeight); statThresholds.push(totalWeight);
} }
// Pick a random stat from the leftover stats to increase its stages
const randInt = Utils.randSeedInt(totalWeight); const randInt = Utils.randSeedInt(totalWeight);
for (const i in statThresholds) {
for (const bs of battleStats) { if (randInt < statThresholds[i]) {
if (randInt < statThresholds[bs]) { boostedStat = leftoverStats[i];
boostedStat = bs;
break; break;
} }
} }
let statLevels = 1; // Increment the amount of stages the chosen stat will be raised
let stages = 1;
switch (segmentIndex) { switch (segmentIndex) {
case 1: case 1:
if (this.bossSegments >= 3) { if (this.bossSegments >= 3) {
statLevels++; stages++;
} }
break; break;
case 2: case 2:
if (this.bossSegments >= 5) { if (this.bossSegments >= 5) {
statLevels++; stages++;
} }
break; 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--; this.bossSegmentIndex--;
} }
} }

View File

@ -89,9 +89,9 @@ export const PGMachv: AchievementTranslationEntries = {
name: "Master League Champion", name: "Master League Champion",
}, },
"TRANSFER_MAX_BATTLE_STAT": { "TRANSFER_MAX_STAT_STAGE": {
name: "Teamwork", 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": { "MAX_FRIENDSHIP": {
name: "Friendmaxxing", name: "Friendmaxxing",

View File

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

View File

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

View File

@ -89,9 +89,9 @@ export const PGMachv: AchievementTranslationEntries = {
name: "Master League Champion", name: "Master League Champion",
}, },
"TRANSFER_MAX_BATTLE_STAT": { "TRANSFER_MAX_STAT_STAGE": {
name: "Teamwork", 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": { "MAX_FRIENDSHIP": {
name: "Friendmaxxing", name: "Friendmaxxing",

View File

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

View File

@ -89,7 +89,7 @@ export const PGMachv: AchievementTranslationEntries = {
name: "Campeón Liga Master", name: "Campeón Liga Master",
}, },
"TRANSFER_MAX_BATTLE_STAT": { "TRANSFER_MAX_STAT_STAGE": {
name: "Trabajo en Equipo", name: "Trabajo en Equipo",
description: "Haz relevo a otro miembro del equipo con al menos una estadística al máximo.", 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", name: "Master Maitre de la Ligue",
}, },
"TRANSFER_MAX_BATTLE_STAT": { "TRANSFER_MAX_STAT_STAGE": {
name: "Travail déquipe", name: "Travail déquipe",
description: "Utiliser Relais avec au moins une statistique montée à fond", 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", name: "Master Maitresse de la Ligue",
}, },
"TRANSFER_MAX_BATTLE_STAT": { "TRANSFER_MAX_STAT_STAGE": {
name: "Travail déquipe", name: "Travail déquipe",
description: "Utiliser Relais avec au moins une statistique montée à fond", 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", name: "Campione Lega Assoluta",
}, },
"TRANSFER_MAX_BATTLE_STAT": { "TRANSFER_MAX_STAT_STAGE": {
name: "Lavoro di Squadra", name: "Lavoro di Squadra",
description: "Trasferisci almeno sei bonus statistiche tramite staffetta", description: "Trasferisci almeno sei bonus statistiche tramite staffetta",
}, },

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -26,7 +26,7 @@ import { BerryType } from "#enums/berry-type";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { getPokemonNameWithAffix } from "#app/messages.js"; 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 outputModifierData = false;
const useMaxWeightForOutput = false; const useMaxWeightForOutput = false;
@ -443,7 +443,7 @@ export class TempStatStageBoosterModifierType extends ModifierType implements Ge
} }
getDescription(_scene: BattleScene): string { 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[] { getPregenArgs(): any[] {
@ -609,7 +609,7 @@ export class BaseStatBoosterModifierType extends PokemonHeldItemModifierType imp
} }
getDescription(_scene: BattleScene): string { 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[] { getPregenArgs(): any[] {

View File

@ -1,10 +1,9 @@
import BattleScene, { bypassLogin } from "./battle-scene"; import BattleScene, { bypassLogin } from "./battle-scene";
import { default as Pokemon, PlayerPokemon, EnemyPokemon, PokemonMove, MoveResult, DamageResult, FieldPosition, HitResult, TurnMove } from "./field/pokemon"; import { default as Pokemon, PlayerPokemon, EnemyPokemon, PokemonMove, MoveResult, DamageResult, FieldPosition, HitResult, TurnMove } from "./field/pokemon";
import * as Utils from "./utils"; 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 { Mode } from "./ui/ui";
import { Command } from "./ui/command-ui-handler"; 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 { 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 PartyUiHandler, { PartyOption, PartyUiMode } from "./ui/party-ui-handler";
import { doPokeballBounceAnim, getPokeballAtlasKey, getPokeballCatchMultiplier, getPokeballTintColor, PokeballType } from "./data/pokeball"; 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 EvolutionSceneHandler from "./ui/evolution-scene-handler";
import { EvolutionPhase } from "./evolution-phase"; import { EvolutionPhase } from "./evolution-phase";
import { Phase } from "./phase"; import { Phase } from "./phase";
import { BattleStat, getBattleStatLevelChangeDescription, getBattleStatName } from "./data/battle-stat";
import { biomeLinks, getBiomeName } from "./data/biomes"; import { biomeLinks, getBiomeName } from "./data/biomes";
import { ModifierTier } from "./modifier/modifier-tier"; 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"; 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 { Gender } from "./data/gender";
import { Weather, WeatherType, getRandomWeatherType, getTerrainBlockMessage, getWeatherDamageMessage, getWeatherLapseMessage } from "./data/weather"; import { Weather, WeatherType, getRandomWeatherType, getTerrainBlockMessage, getWeatherDamageMessage, getWeatherLapseMessage } from "./data/weather";
import { ArenaTagSide, ArenaTrapTag, MistTag, TrickRoomTag } from "./data/arena-tag"; 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 { Unlockables, getUnlockableName } from "./system/unlockables";
import { getBiomeKey } from "./field/arena"; import { getBiomeKey } from "./field/arena";
import { BattleType, BattlerIndex, TurnCommand } from "./battle"; import { BattleType, BattlerIndex, TurnCommand } from "./battle";
@ -66,6 +64,7 @@ import { Species } from "#enums/species";
import { TrainerType } from "#enums/trainer-type"; import { TrainerType } from "#enums/trainer-type";
import { applyChallenges, ChallengeType } from "./data/challenge"; import { applyChallenges, ChallengeType } from "./data/challenge";
import { pokemonEvolutions } from "./data/pokemon-evolutions"; import { pokemonEvolutions } from "./data/pokemon-evolutions";
import { Stat, BattleStat, getStatKey, getStatStageChangeDescriptionKey } from "#enums/stat";
const { t } = i18next; const { t } = i18next;
@ -718,8 +717,8 @@ export abstract class FieldPhase extends BattlePhase {
}, this.scene.currentBattle.turn, this.scene.waveSeed); }, this.scene.currentBattle.turn, this.scene.waveSeed);
orderedTargets.sort((a: Pokemon, b: Pokemon) => { orderedTargets.sort((a: Pokemon, b: Pokemon) => {
const aSpeed = a?.getBattleStat(Stat.SPD) || 0; const aSpeed = a?.getEffectiveStat(Stat.SPD) || 0;
const bSpeed = b?.getBattleStat(Stat.SPD) || 0; const bSpeed = b?.getEffectiveStat(Stat.SPD) || 0;
return bSpeed - aSpeed; return bSpeed - aSpeed;
}); });
@ -3449,22 +3448,22 @@ export class ShowAbilityPhase extends PokemonPhase {
export type StatChangeCallback = (target: Pokemon | null, changed: BattleStat[], relativeChanges: number[]) => void; 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 stats: BattleStat[];
private selfTarget: boolean; private selfTarget: boolean;
private levels: integer; private stages: integer;
private showMessage: boolean; private showMessage: boolean;
private ignoreAbilities: boolean; private ignoreAbilities: boolean;
private canBeCopied: boolean; private canBeCopied: boolean;
private onChange: StatChangeCallback | null; 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); super(scene, battlerIndex);
this.selfTarget = selfTarget; this.selfTarget = selfTarget;
this.stats = stats; this.stats = stats;
this.levels = levels; this.stages = stages;
this.showMessage = showMessage; this.showMessage = showMessage;
this.ignoreAbilities = ignoreAbilities; this.ignoreAbilities = ignoreAbilities;
this.canBeCopied = canBeCopied; 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 filteredStats = this.stats.map(s => s !== BattleStat.RAND ? s : this.getRandomStat()).filter(stat => {
const cancelled = new Utils.BooleanHolder(false); 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); this.scene.arena.applyTagsForSide(MistTag, pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, cancelled);
} }
if (!cancelled.value && !this.selfTarget && this.levels < 0) { if (!cancelled.value && !this.selfTarget && this.stages < 0) {
applyPreStatChangeAbAttrs(ProtectStatAbAttr, this.getPokemon(), stat, cancelled); applyPreStatStageChangeAbAttrs(ProtectStatAbAttr, this.getPokemon(), stat, cancelled);
} }
return !cancelled.value; return !cancelled.value;
}); });
const levels = new Utils.IntegerHolder(this.levels); const levels = new Utils.IntegerHolder(this.stages);
if (!this.ignoreAbilities) { if (!this.ignoreAbilities) {
applyAbAttrs(StatChangeMultiplierAbAttr, pokemon, null, levels); applyAbAttrs(StatStageChangeMultiplierAbAttr, pokemon, null, levels);
} }
const battleStats = this.getPokemon().summonData.battleStats; const battleStats = this.getPokemon().summonData.battleStats;
@ -3526,20 +3525,20 @@ export class StatChangePhase extends PokemonPhase {
if (levels.value > 0 && this.canBeCopied) { if (levels.value > 0 && this.canBeCopied) {
for (const opponent of pokemon.getOpponents()) { 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 // 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); const existingPhase = this.scene.findPhase(p => p instanceof StatStageChangePhase && p.battlerIndex === this.battlerIndex);
if (!(existingPhase instanceof StatChangePhase)) { if (!(existingPhase instanceof StatStageChangePhase)) {
// Apply White Herb if needed // 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 the White Herb was applied, consume it
if (whiteHerb) { if (whiteHerb) {
--whiteHerb.stackCount; whiteHerb.stackCount--;
if (whiteHerb.stackCount <= 0) { if (whiteHerb.stackCount <= 0) {
this.scene.removeModifier(whiteHerb); this.scene.removeModifier(whiteHerb);
} }
@ -3563,7 +3562,7 @@ export class StatChangePhase extends PokemonPhase {
// On increase, show the red sprite located at ATK // On increase, show the red sprite located at ATK
// On decrease, show the blue sprite located at SPD // 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); const statSprite = this.scene.add.tileSprite(tileX, tileY, tileWidth, tileHeight, "battle_stats", spriteColor);
statSprite.setPipeline(this.scene.fieldSpritePipeline); statSprite.setPipeline(this.scene.fieldSpritePipeline);
statSprite.setAlpha(0); statSprite.setAlpha(0);
@ -3657,11 +3656,19 @@ export class StatChangePhase extends PokemonPhase {
if (relLevelStats.length > 1) { if (relLevelStats.length > 1) {
statsFragment = relLevelStats.length >= 5 statsFragment = relLevelStats.length >= 5
? i18next.t("battle:stats") ? 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])}`; : `${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(getBattleStatLevelChangeDescription(getPokemonNameWithAffix(this.getPokemon()), statsFragment, Math.abs(parseInt(rl)), levels >= 1,relLevelStats.length)); messages.push(i18next.t(getStatStageChangeDescriptionKey(Math.abs(parseInt(rl)), levels >= 1), {
pokemonNameWithAffix: getPokemonNameWithAffix(this.getPokemon()),
stats: statsFragment,
count: relLevelStats.length
}));
} else { } else {
statsFragment = getBattleStatName(relLevelStats[0]); statsFragment = i18next.t(getStatKey(relLevelStats[0]));
messages.push(getBattleStatLevelChangeDescription(getPokemonNameWithAffix(this.getPokemon()), statsFragment, Math.abs(parseInt(rl)), levels >= 1,relLevelStats.length)); 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()) { if (defeatSource?.isOnField()) {
applyPostVictoryAbAttrs(PostVictoryAbAttr, defeatSource); applyPostVictoryAbAttrs(PostVictoryAbAttr, defeatSource);
const pvmove = allMoves[pokemon.turnData.attacksReceived[0].move]; const pvmove = allMoves[pokemon.turnData.attacksReceived[0].move];
const pvattrs = pvmove.getAttrs(PostVictoryStatChangeAttr); const pvattrs = pvmove.getAttrs(PostVictoryStatStageChangeAttr);
if (pvattrs.length) { if (pvattrs.length) {
for (const pvattr of pvattrs) { for (const pvattr of pvattrs) {
pvattr.applyPostVictory(defeatSource, defeatSource, pvmove); pvattr.applyPostVictory(defeatSource, defeatSource, pvmove);

View File

@ -8,6 +8,7 @@ import { PlayerGender } from "#enums/player-gender";
import { ParseKeys } from "i18next"; import { ParseKeys } from "i18next";
import { Challenge, FreshStartChallenge, SingleGenerationChallenge, SingleTypeChallenge } from "#app/data/challenge.js"; import { Challenge, FreshStartChallenge, SingleGenerationChallenge, SingleTypeChallenge } from "#app/data/challenge.js";
import { ConditionFn } from "#app/@types/common.js"; import { ConditionFn } from "#app/@types/common.js";
import { getShortenedStatKey, Stat } from "#app/enums/stat.js";
export enum AchvTier { export enum AchvTier {
COMMON, COMMON,
@ -178,13 +179,13 @@ export function getAchievementDescription(localizationKey: string): string {
case "10000_DMG": case "10000_DMG":
return i18next.t(`${genderPrefix}achv:DamageAchv.description` as ParseKeys, {"damageAmount": achvs._10000_DMG.damageAmount.toLocaleString("en-US")}); return i18next.t(`${genderPrefix}achv:DamageAchv.description` as ParseKeys, {"damageAmount": achvs._10000_DMG.damageAmount.toLocaleString("en-US")});
case "250_HEAL": 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": 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": 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": 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": case "LV_100":
return i18next.t(`${genderPrefix}achv:LevelAchv.description` as ParseKeys, {"level": achvs.LV_100.level}); return i18next.t(`${genderPrefix}achv:LevelAchv.description` as ParseKeys, {"level": achvs.LV_100.level});
case "LV_250": 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")}); return i18next.t(`${genderPrefix}achv:RibbonAchv.description` as ParseKeys, {"ribbonAmount": achvs._75_RIBBONS.ribbonAmount.toLocaleString("en-US")});
case "100_RIBBONS": case "100_RIBBONS":
return i18next.t(`${genderPrefix}achv:RibbonAchv.description` as ParseKeys, {"ribbonAmount": achvs._100_RIBBONS.ribbonAmount.toLocaleString("en-US")}); return i18next.t(`${genderPrefix}achv:RibbonAchv.description` as ParseKeys, {"ribbonAmount": achvs._100_RIBBONS.ribbonAmount.toLocaleString("en-US")});
case "TRANSFER_MAX_BATTLE_STAT": case "TRANSFER_MAX_STAT_STAGE":
return i18next.t(`${genderPrefix}achv:TRANSFER_MAX_BATTLE_STAT.description` as ParseKeys); return i18next.t(`${genderPrefix}achv:TRANSFER_MAX_STAT_STAGE.description` as ParseKeys);
case "MAX_FRIENDSHIP": case "MAX_FRIENDSHIP":
return i18next.t(`${genderPrefix}achv:MAX_FRIENDSHIP.description` as ParseKeys); return i18next.t(`${genderPrefix}achv:MAX_FRIENDSHIP.description` as ParseKeys);
case "MEGA_EVOLVE": case "MEGA_EVOLVE":
@ -309,7 +310,7 @@ export const achvs = {
_50_RIBBONS: new RibbonAchv("50_RIBBONS","", 50, "ultra_ribbon", 50).setSecret(true), _50_RIBBONS: new RibbonAchv("50_RIBBONS","", 50, "ultra_ribbon", 50).setSecret(true),
_75_RIBBONS: new RibbonAchv("75_RIBBONS","", 75, "rogue_ribbon", 75).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), _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), MAX_FRIENDSHIP: new Achv("MAX_FRIENDSHIP", "", "MAX_FRIENDSHIP.description","soothe_bell", 25),
MEGA_EVOLVE: new Achv("MEGA_EVOLVE", "", "MEGA_EVOLVE.description","mega_bracelet", 50), MEGA_EVOLVE: new Achv("MEGA_EVOLVE", "", "MEGA_EVOLVE.description","mega_bracelet", 50),
GIGANTAMAX: new Achv("GIGANTAMAX", "", "GIGANTAMAX.description","dynamax_band", 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 { Abilities } from "#app/enums/abilities.js";
import { Moves } from "#app/enums/moves.js"; import { Moves } from "#app/enums/moves.js";
import { Species } from "#app/enums/species.js"; import { Species } from "#app/enums/species.js";
@ -35,7 +35,7 @@ describe("Abilities - COSTAR", () => {
test( test(
"ability copies positive stat changes", "ability copies positive stat stages",
async () => { async () => {
game.override.enemyAbility(Abilities.BALL_FETCH); game.override.enemyAbility(Abilities.BALL_FETCH);
@ -48,8 +48,8 @@ describe("Abilities - COSTAR", () => {
game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH));
await game.toNextTurn(); await game.toNextTurn();
expect(leftPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(+2); expect(leftPokemon.getStatStage(Stat.SPATK)).toBe(2);
expect(rightPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(0); expect(rightPokemon.getStatStage(Stat.SPATK)).toBe(0);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH)); game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.phaseInterceptor.to(CommandPhase); await game.phaseInterceptor.to(CommandPhase);
@ -57,14 +57,14 @@ describe("Abilities - COSTAR", () => {
await game.phaseInterceptor.to(MessagePhase); await game.phaseInterceptor.to(MessagePhase);
[leftPokemon, rightPokemon] = game.scene.getPlayerField(); [leftPokemon, rightPokemon] = game.scene.getPlayerField();
expect(leftPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(+2); expect(leftPokemon.getStatStage(Stat.SPATK)).toBe(2);
expect(rightPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(+2); expect(rightPokemon.getStatStage(Stat.SPATK)).toBe(2);
}, },
TIMEOUT, TIMEOUT,
); );
test( test(
"ability copies negative stat changes", "ability copies negative stat stages",
async () => { async () => {
game.override.enemyAbility(Abilities.INTIMIDATE); game.override.enemyAbility(Abilities.INTIMIDATE);
@ -72,8 +72,8 @@ describe("Abilities - COSTAR", () => {
let [leftPokemon, rightPokemon] = game.scene.getPlayerField(); let [leftPokemon, rightPokemon] = game.scene.getPlayerField();
expect(leftPokemon.summonData.battleStats[BattleStat.ATK]).toBe(-2); expect(leftPokemon.getStatStage(Stat.ATK)).toBe(-2);
expect(leftPokemon.summonData.battleStats[BattleStat.ATK]).toBe(-2); expect(leftPokemon.getStatStage(Stat.ATK)).toBe(-2);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH)); game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.phaseInterceptor.to(CommandPhase); await game.phaseInterceptor.to(CommandPhase);
@ -81,8 +81,8 @@ describe("Abilities - COSTAR", () => {
await game.phaseInterceptor.to(MessagePhase); await game.phaseInterceptor.to(MessagePhase);
[leftPokemon, rightPokemon] = game.scene.getPlayerField(); [leftPokemon, rightPokemon] = game.scene.getPlayerField();
expect(leftPokemon.summonData.battleStats[BattleStat.ATK]).toBe(-2); expect(leftPokemon.getStatStage(Stat.ATK)).toBe(-2);
expect(rightPokemon.summonData.battleStats[BattleStat.ATK]).toBe(-2); expect(rightPokemon.getStatStage(Stat.ATK)).toBe(-2);
}, },
TIMEOUT, TIMEOUT,
); );

View File

@ -13,7 +13,7 @@ import { Species } from "#enums/species";
import Phaser from "phaser"; import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import { SPLASH_ONLY } from "../utils/testUtils"; 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 { StatusEffect } from "#app/enums/status-effect.js";
import Pokemon from "#app/field/pokemon.js"; import Pokemon from "#app/field/pokemon.js";
@ -95,7 +95,7 @@ describe("Abilities - Gulp Missile", () => {
expect(cramorant.formIndex).toBe(GULPING_FORM); 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)); game.override.enemyMoveset(Array(4).fill(Moves.TACKLE));
await game.startBattle([Species.CRAMORANT]); await game.startBattle([Species.CRAMORANT]);
@ -127,7 +127,7 @@ describe("Abilities - Gulp Missile", () => {
expect(cramorant.formIndex).toBe(GULPING_FORM); 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)); game.override.enemyMoveset(Array(4).fill(Moves.TACKLE));
await game.startBattle([Species.CRAMORANT]); await game.startBattle([Species.CRAMORANT]);
@ -146,7 +146,7 @@ describe("Abilities - Gulp Missile", () => {
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
expect(enemy.damageAndUpdate).toHaveReturnedWith(getEffectDamage(enemy)); 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.getTag(BattlerTagType.GULP_MISSILE_ARROKUDA)).toBeUndefined();
expect(cramorant.formIndex).toBe(NORMAL_FORM); expect(cramorant.formIndex).toBe(NORMAL_FORM);
}); });
@ -207,7 +207,7 @@ describe("Abilities - Gulp Missile", () => {
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
expect(enemy.hp).toBe(enemyHpPreEffect); 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.getTag(BattlerTagType.GULP_MISSILE_ARROKUDA)).toBeUndefined();
expect(cramorant.formIndex).toBe(NORMAL_FORM); expect(cramorant.formIndex).toBe(NORMAL_FORM);
}); });

View File

@ -41,13 +41,13 @@ describe("Abilities - Hustle", () => {
const pikachu = game.scene.getPlayerPokemon()!; const pikachu = game.scene.getPlayerPokemon()!;
const atk = pikachu.stats[Stat.ATK]; const atk = pikachu.stats[Stat.ATK];
vi.spyOn(pikachu, "getBattleStat"); vi.spyOn(pikachu, "getEffectiveStat");
game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE)); game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE));
await game.move.forceHit(); await game.move.forceHit();
await game.phaseInterceptor.to(DamagePhase); 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 () => { 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 pikachu = game.scene.getPlayerPokemon()!;
const spatk = pikachu.stats[Stat.SPATK]; const spatk = pikachu.stats[Stat.SPATK];
vi.spyOn(pikachu, "getBattleStat"); vi.spyOn(pikachu, "getEffectiveStat");
vi.spyOn(pikachu, "getAccuracyMultiplier"); vi.spyOn(pikachu, "getAccuracyMultiplier");
game.doAttack(getMovePosition(game.scene, 0, Moves.GIGA_DRAIN)); game.doAttack(getMovePosition(game.scene, 0, Moves.GIGA_DRAIN));
await game.phaseInterceptor.to(DamagePhase); await game.phaseInterceptor.to(DamagePhase);
expect(pikachu.getBattleStat).toHaveReturnedWith(spatk); expect(pikachu.getEffectiveStat).toHaveReturnedWith(spatk);
expect(pikachu.getAccuracyMultiplier).toHaveReturnedWith(1); 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 Phaser from "phaser";
import GameManager from "#test/utils/gameManager"; import GameManager from "#test/utils/gameManager";
import { Mode } from "#app/ui/ui"; import { Mode } from "#app/ui/ui";
import { BattleStat } from "#app/data/battle-stat"; import { Stat } from "#enums/stat";
import { generateStarter, getMovePosition } from "#test/utils/gameManagerUtils"; import { getMovePosition } from "#test/utils/gameManagerUtils";
import { Command } from "#app/ui/command-ui-handler"; import { CommandPhase, TurnInitPhase } from "#app/phases";
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 { Abilities } from "#enums/abilities"; import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
@ -38,7 +35,7 @@ describe("Abilities - Intimidate", () => {
game.override.enemyMoveset(SPLASH_ONLY); 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]); await game.runToSummon([Species.MIGHTYENA, Species.POOCHYENA]);
game.onNextPrompt( game.onNextPrompt(
"CheckSwitchPhase", "CheckSwitchPhase",
@ -50,108 +47,25 @@ describe("Abilities - Intimidate", () => {
() => game.isCurrentPhase(CommandPhase) || game.isCurrentPhase(TurnInitPhase) () => game.isCurrentPhase(CommandPhase) || game.isCurrentPhase(TurnInitPhase)
); );
await game.phaseInterceptor.to(CommandPhase, false); 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; let playerPokemon = game.scene.getPlayerPokemon();
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1); const enemyPokemon = game.scene.getEnemyPokemon();
let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1); expect(playerPokemon!.species.speciesId).toBe(Species.MIGHTYENA);
expect(enemyPokemon!.getStatStage(Stat.ATK)).toBe(-1);
expect(playerPokemon!.getStatStage(Stat.ATK)).toBe(-1);
game.doSwitchPokemon(1); game.doSwitchPokemon(1);
await game.phaseInterceptor.run(CommandPhase); await game.phaseInterceptor.run(CommandPhase);
await game.phaseInterceptor.to(CommandPhase); await game.phaseInterceptor.to(CommandPhase);
expect(game.scene.getParty()[0].species.speciesId).toBe(Species.POOCHYENA);
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats; playerPokemon = game.scene.getPlayerPokemon();
expect(battleStatsPokemon[BattleStat.ATK]).toBe(0); expect(playerPokemon!.species.speciesId).toBe(Species.POOCHYENA);
expect(playerPokemon!.getStatStage(Stat.ATK)).toBe(0);
battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats; expect(enemyPokemon!.getStatStage(Stat.ATK)).toBe(-2);
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-2);
}, 20000); }, 20000);
it("single - boss should only trigger once then switch", async () => { it("should lower ATK stat stage by 1 for every enemy Pokemon in a double battle on entry", 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 () => {
game.override.battleType("double"); game.override.battleType("double");
game.override.startingWave(3); game.override.startingWave(3);
await game.runToSummon([Species.MIGHTYENA, Species.POOCHYENA]); await game.runToSummon([Species.MIGHTYENA, Species.POOCHYENA]);
@ -165,210 +79,60 @@ describe("Abilities - Intimidate", () => {
() => game.isCurrentPhase(CommandPhase) || game.isCurrentPhase(TurnInitPhase) () => game.isCurrentPhase(CommandPhase) || game.isCurrentPhase(TurnInitPhase)
); );
await game.phaseInterceptor.to(CommandPhase, false); 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; const playerField = game.scene.getPlayerField()!;
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-2); const enemyField = game.scene.getEnemyPokemon()!;
const battleStatsPokemon2 = game.scene.getParty()[1].summonData.battleStats; expect(enemyField[0].getStatStage(Stat.ATK)).toBe(-2);
expect(battleStatsPokemon2[BattleStat.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); }, 20000);
it("double - boss: should only trigger once per pokemon", async () => { it("should not activate again if there is no switch or new entry", 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 () => {
game.override.startingWave(2); game.override.startingWave(2);
game.override.moveset([Moves.SPLASH]); game.override.moveset([Moves.SPLASH]);
await game.startBattle([Species.MIGHTYENA, Species.POOCHYENA]); 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, () => { const playerPokemon = game.scene.getPlayerPokemon()!;
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex()); const enemyPokemon = game.scene.getEnemyPokemon()!;
});
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => { expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-1);
const movePosition = getMovePosition(game.scene, 0, Moves.AERIAL_ACE); expect(playerPokemon.getStatStage(Stat.ATK)).toBe(-1);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
}); game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
console.log("===to new turn===");
await game.toNextTurn(); await game.toNextTurn();
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1); expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-1);
battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats; expect(playerPokemon.getStatStage(Stat.ATK)).toBe(-1);
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
}, 20000); }, 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.moveset([Moves.SPLASH]);
game.override.enemyMoveset([Moves.VOLT_SWITCH, Moves.VOLT_SWITCH, Moves.VOLT_SWITCH, Moves.VOLT_SWITCH]); game.override.enemyMoveset([Moves.VOLT_SWITCH, Moves.VOLT_SWITCH, Moves.VOLT_SWITCH, Moves.VOLT_SWITCH]);
game.override.startingWave(5); game.override.startingWave(5);
await game.startBattle([Species.MIGHTYENA, Species.POOCHYENA]); 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, () => { const playerPokemon = game.scene.getPlayerPokemon()!;
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex()); let enemyPokemon = game.scene.getEnemyPokemon()!;
});
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);
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => { expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-1);
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex()); expect(playerPokemon.getStatStage(Stat.ATK)).toBe(-1);
});
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => { game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
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(); await game.toNextTurn();
battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-3); enemyPokemon = game.scene.getEnemyPokemon()!;
battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.ATK]).toBe(0); 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); }, 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 { CommandPhase } from "#app/phases";
import GameManager from "#test/utils/gameManager"; import GameManager from "#test/utils/gameManager";
import { Abilities } from "#enums/abilities"; import { Abilities } from "#enums/abilities";
@ -29,14 +29,17 @@ describe("Abilities - Intrepid Sword", () => {
game.override.ability(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([ await game.runToSummon([
Species.ZACIAN, Species.ZACIAN,
]); ]);
const playerPokemon = game.scene.getPlayerPokemon()!;
const enemyPokemon = game.scene.getEnemyPokemon()!;
await game.phaseInterceptor.to(CommandPhase, false); await game.phaseInterceptor.to(CommandPhase, false);
const battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.ATK]).toBe(1); expect(playerPokemon.getStatStage(Stat.ATK)).toBe(1);
const battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats; expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(1);
expect(battleStatsOpponent[BattleStat.ATK]).toBe(1);
}, 20000); }, 20000);
}); });

View File

@ -1,16 +1,14 @@
import { BattleStat } from "#app/data/battle-stat"; import { Stat } from "#enums/stat";
import { Stat } from "#app/data/pokemon-stat"; import { EnemyCommandPhase, TurnEndPhase, VictoryPhase } from "#app/phases";
import { CommandPhase, EnemyCommandPhase, VictoryPhase } from "#app/phases";
import GameManager from "#test/utils/gameManager"; import GameManager from "#test/utils/gameManager";
import { getMovePosition } from "#test/utils/gameManagerUtils"; 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 { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import Phaser from "phaser"; import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
import { SPLASH_ONLY } from "../utils/testUtils";
import { BattlerIndex } from "#app/battle";
describe("Abilities - Moxie", () => { describe("Abilities - Moxie", () => {
let phaserGame: Phaser.Game; let phaserGame: Phaser.Game;
@ -34,29 +32,46 @@ describe("Abilities - Moxie", () => {
game.override.enemyAbility(Abilities.MOXIE); game.override.enemyAbility(Abilities.MOXIE);
game.override.ability(Abilities.MOXIE); game.override.ability(Abilities.MOXIE);
game.override.startingLevel(2000); game.override.startingLevel(2000);
game.override.moveset([moveToUse]); game.override.moveset([ moveToUse ]);
game.override.enemyMoveset([Moves.TACKLE,Moves.TACKLE,Moves.TACKLE,Moves.TACKLE]); 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; const moveToUse = Moves.AERIAL_ACE;
await game.startBattle([ await game.startBattle([
Species.MIGHTYENA, Species.MIGHTYENA,
Species.MIGHTYENA, Species.MIGHTYENA,
]); ]);
let battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats; const playerPokemon = game.scene.getPlayerPokemon()!;
expect(battleStatsPokemon[Stat.ATK]).toBe(0);
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => { expect(playerPokemon.getStatStage(Stat.ATK)).toBe(0);
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
}); game.doAttack(getMovePosition(game.scene, 0, moveToUse));
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
const movePosition = getMovePosition(game.scene, 0, moveToUse);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(VictoryPhase); 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); }, 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 { StatusEffect } from "#app/data/status-effect.js";
import { Type } from "#app/data/type.js"; import { Type } from "#app/data/type.js";
import { BattlerTagType } from "#app/enums/battler-tag-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); await game.phaseInterceptor.to(BerryPhase, false);
expect(leadPokemon.turnData.hitCount).toBe(2); expect(leadPokemon.turnData.hitCount).toBe(2);
expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(2); expect(leadPokemon.getStatStage(Stat.ATK)).toBe(2);
}, TIMEOUT }, TIMEOUT
); );
@ -111,7 +111,7 @@ describe("Abilities - Parental Bond", () => {
game.doAttack(getMovePosition(game.scene, 0, Moves.BABY_DOLL_EYES)); game.doAttack(getMovePosition(game.scene, 0, Moves.BABY_DOLL_EYES));
await game.phaseInterceptor.to(BerryPhase, false); await game.phaseInterceptor.to(BerryPhase, false);
expect(enemyPokemon.summonData.battleStats[BattleStat.ATK]).toBe(-1); expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(-1);
}, TIMEOUT }, TIMEOUT
); );
@ -563,7 +563,7 @@ describe("Abilities - Parental Bond", () => {
await game.phaseInterceptor.to(BerryPhase, false); await game.phaseInterceptor.to(BerryPhase, false);
expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(-1); expect(leadPokemon.getStatStage(Stat.ATK)).toBe(-1);
}, TIMEOUT }, TIMEOUT
); );
@ -585,7 +585,7 @@ describe("Abilities - Parental Bond", () => {
await game.phaseInterceptor.to(BerryPhase, false); await game.phaseInterceptor.to(BerryPhase, false);
expect(enemyPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(1); expect(enemyPokemon.getStatStage(Stat.SPATK)).toBe(1);
}, TIMEOUT }, TIMEOUT
); );

View File

@ -1,5 +1,5 @@
import { BattleStatMultiplierAbAttr, allAbilities } from "#app/data/ability.js"; import { StatStageMultiplierAbAttr, allAbilities } from "#app/data/ability.js";
import { BattleStat } from "#app/data/battle-stat.js"; import { Stat } from "#enums/stat";
import { WeatherType } from "#app/data/weather.js"; import { WeatherType } from "#app/data/weather.js";
import { CommandPhase, MoveEffectPhase, MoveEndPhase } from "#app/phases.js"; import { CommandPhase, MoveEffectPhase, MoveEndPhase } from "#app/phases.js";
import { Abilities } from "#enums/abilities"; import { Abilities } from "#enums/abilities";
@ -48,10 +48,10 @@ describe("Abilities - Sand Veil", () => {
vi.spyOn(leadPokemon[0], "getAbility").mockReturnValue(allAbilities[Abilities.SAND_VEIL]); vi.spyOn(leadPokemon[0], "getAbility").mockReturnValue(allAbilities[Abilities.SAND_VEIL]);
const sandVeilAttr = allAbilities[Abilities.SAND_VEIL].getAttrs(BattleStatMultiplierAbAttr)[0]; const sandVeilAttr = allAbilities[Abilities.SAND_VEIL].getAttrs(StatStageMultiplierAbAttr)[0];
vi.spyOn(sandVeilAttr, "applyBattleStat").mockImplementation( vi.spyOn(sandVeilAttr, "applyStatStage").mockImplementation(
(pokemon, passive, battleStat, statValue, args) => { (_pokemon, _passive, stat, statValue, _args) => {
if (battleStat === BattleStat.EVA && game.scene.arena.weather?.weatherType === WeatherType.SANDSTORM) { if (stat === Stat.EVA && game.scene.arena.weather?.weatherType === WeatherType.SANDSTORM) {
statValue.value *= -1; // will make all attacks miss statValue.value *= -1; // will make all attacks miss
return true; 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 { TerrainType } from "#app/data/terrain.js";
import { MoveEndPhase, TurnEndPhase } from "#app/phases"; import { MoveEndPhase, TurnEndPhase } from "#app/phases";
import GameManager from "#test/utils/gameManager"; import GameManager from "#test/utils/gameManager";
@ -9,6 +9,7 @@ import { Moves } from "#enums/moves";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import Phaser from "phaser"; import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
import { SPLASH_ONLY } from "../utils/testUtils";
// See also: TypeImmunityAbAttr // See also: TypeImmunityAbAttr
describe("Abilities - Sap Sipper", () => { describe("Abilities - Sap Sipper", () => {
@ -31,52 +32,55 @@ describe("Abilities - Sap Sipper", () => {
game.override.disableCrits(); 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 moveToUse = Moves.LEAFAGE;
const enemyAbility = Abilities.SAP_SIPPER; const enemyAbility = Abilities.SAP_SIPPER;
game.override.moveset([moveToUse]); game.override.moveset([ moveToUse ]);
game.override.enemyMoveset([Moves.SPLASH, Moves.NONE, Moves.NONE, Moves.NONE]); game.override.enemyMoveset(SPLASH_ONLY);
game.override.enemySpecies(Species.DUSKULL); game.override.enemySpecies(Species.DUSKULL);
game.override.enemyAbility(enemyAbility); game.override.enemyAbility(enemyAbility);
await game.startBattle(); 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)); game.doAttack(getMovePosition(game.scene, 0, moveToUse));
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
expect(startingOppHp - game.scene.getEnemyParty()[0].hp).toBe(0); expect(initialEnemyHp - enemyPokemon.hp).toBe(0);
expect(game.scene.getEnemyParty()[0].summonData.battleStats[BattleStat.ATK]).toBe(1); 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 moveToUse = Moves.SPORE;
const enemyAbility = Abilities.SAP_SIPPER; const enemyAbility = Abilities.SAP_SIPPER;
game.override.moveset([moveToUse]); game.override.moveset([ moveToUse ]);
game.override.enemyMoveset([Moves.SPLASH, Moves.NONE, Moves.NONE, Moves.NONE]); game.override.enemyMoveset(SPLASH_ONLY);
game.override.enemySpecies(Species.RATTATA); game.override.enemySpecies(Species.RATTATA);
game.override.enemyAbility(enemyAbility); game.override.enemyAbility(enemyAbility);
await game.startBattle(); await game.startBattle();
const enemyPokemon = game.scene.getEnemyPokemon()!;
game.doAttack(getMovePosition(game.scene, 0, moveToUse)); game.doAttack(getMovePosition(game.scene, 0, moveToUse));
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
expect(game.scene.getEnemyParty()[0].status).toBeUndefined(); expect(enemyPokemon.status).toBeUndefined();
expect(game.scene.getEnemyParty()[0].summonData.battleStats[BattleStat.ATK]).toBe(1); expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(1);
}); });
it("do not activate against status moves that target the field", async() => { it("do not activate against status moves that target the field", async() => {
const moveToUse = Moves.GRASSY_TERRAIN; const moveToUse = Moves.GRASSY_TERRAIN;
const enemyAbility = Abilities.SAP_SIPPER; const enemyAbility = Abilities.SAP_SIPPER;
game.override.moveset([moveToUse]); game.override.moveset([ moveToUse ]);
game.override.enemyMoveset([Moves.SPLASH, Moves.NONE, Moves.NONE, Moves.NONE]); game.override.enemyMoveset(SPLASH_ONLY);
game.override.enemySpecies(Species.RATTATA); game.override.enemySpecies(Species.RATTATA);
game.override.enemyAbility(enemyAbility); game.override.enemyAbility(enemyAbility);
@ -88,51 +92,54 @@ describe("Abilities - Sap Sipper", () => {
expect(game.scene.arena.terrain).toBeDefined(); expect(game.scene.arena.terrain).toBeDefined();
expect(game.scene.arena.terrain!.terrainType).toBe(TerrainType.GRASSY); 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() => { it("activate once against multi-hit grass attacks", async() => {
const moveToUse = Moves.BULLET_SEED; const moveToUse = Moves.BULLET_SEED;
const enemyAbility = Abilities.SAP_SIPPER; const enemyAbility = Abilities.SAP_SIPPER;
game.override.moveset([moveToUse]); game.override.moveset([ moveToUse ]);
game.override.enemyMoveset([Moves.SPLASH, Moves.NONE, Moves.NONE, Moves.NONE]); game.override.enemyMoveset(SPLASH_ONLY);
game.override.enemySpecies(Species.RATTATA); game.override.enemySpecies(Species.RATTATA);
game.override.enemyAbility(enemyAbility); game.override.enemyAbility(enemyAbility);
await game.startBattle(); 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)); game.doAttack(getMovePosition(game.scene, 0, moveToUse));
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
expect(startingOppHp - game.scene.getEnemyParty()[0].hp).toBe(0); expect(initialEnemyHp - enemyPokemon.hp).toBe(0);
expect(game.scene.getEnemyParty()[0].summonData.battleStats[BattleStat.ATK]).toBe(1); expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(1);
}); });
it("do not activate against status moves that target the user", async() => { it("do not activate against status moves that target the user", async() => {
const moveToUse = Moves.SPIKY_SHIELD; const moveToUse = Moves.SPIKY_SHIELD;
const ability = Abilities.SAP_SIPPER; const ability = Abilities.SAP_SIPPER;
game.override.moveset([moveToUse]); game.override.moveset([ moveToUse ]);
game.override.ability(ability); 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.enemySpecies(Species.RATTATA);
game.override.enemyAbility(Abilities.NONE); game.override.enemyAbility(Abilities.NONE);
await game.startBattle(); await game.startBattle();
const playerPokemon = game.scene.getPlayerPokemon()!;
game.doAttack(getMovePosition(game.scene, 0, moveToUse)); game.doAttack(getMovePosition(game.scene, 0, moveToUse));
await game.phaseInterceptor.to(MoveEndPhase); 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); 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"); expect(game.phaseInterceptor.log).not.toContain("ShowAbilityPhase");
}); });
@ -149,13 +156,14 @@ describe("Abilities - Sap Sipper", () => {
await game.startBattle(); 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)); game.doAttack(getMovePosition(game.scene, 0, moveToUse));
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
expect(startingOppHp - game.scene.getEnemyParty()[0].hp).toBe(0); expect(initialEnemyHp - enemyPokemon.hp).toBe(0);
expect(game.scene.getEnemyParty()[0].summonData.battleStats[BattleStat.ATK]).toBe(1); 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 { TurnEndPhase } from "#app/phases";
import GameManager from "#test/utils/gameManager"; import GameManager from "#test/utils/gameManager";
import { getMovePosition } from "#test/utils/gameManagerUtils"; import { getMovePosition } from "#test/utils/gameManagerUtils";
@ -42,12 +42,14 @@ describe("Abilities - Volt Absorb", () => {
await game.startBattle(); await game.startBattle();
const playerPokemon = game.scene.getPlayerPokemon()!;
game.doAttack(getMovePosition(game.scene, 0, moveToUse)); game.doAttack(getMovePosition(game.scene, 0, moveToUse));
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
expect(game.scene.getParty()[0].summonData.battleStats[BattleStat.SPDEF]).toBe(1); expect(playerPokemon.getStatStage(Stat.SPDEF)).toBe(1);
expect(game.scene.getParty()[0].getTag(BattlerTagType.CHARGED)).toBeDefined(); expect(playerPokemon.getTag(BattlerTagType.CHARGED)).toBeDefined();
expect(game.phaseInterceptor.log).not.toContain("ShowAbilityPhase"); 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 { TurnEndPhase } from "#app/phases";
import GameManager from "#test/utils/gameManager"; import GameManager from "#test/utils/gameManager";
import { getMovePosition } from "#test/utils/gameManagerUtils"; import { getMovePosition } from "#test/utils/gameManagerUtils";
@ -32,37 +32,37 @@ describe("Abilities - Wind Rider", () => {
game.override.enemyMoveset(SPLASH_ONLY); 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]); await game.startBattle([Species.MAGIKARP]);
const shiftry = game.scene.getEnemyPokemon()!; 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)); game.doAttack(getMovePosition(game.scene, 0, Moves.PETAL_BLIZZARD));
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
expect(shiftry.isFullHp()).toBe(true); 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.ability(Abilities.WIND_RIDER);
game.override.enemySpecies(Species.MAGIKARP); game.override.enemySpecies(Species.MAGIKARP);
await game.startBattle([Species.SHIFTRY]); await game.startBattle([Species.SHIFTRY]);
const shiftry = game.scene.getPlayerPokemon()!; 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)); game.doAttack(getMovePosition(game.scene, 0, Moves.TAILWIND));
await game.phaseInterceptor.to(TurnEndPhase); 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.ability(Abilities.WIND_RIDER);
game.override.enemySpecies(Species.MAGIKARP); game.override.enemySpecies(Species.MAGIKARP);
@ -70,33 +70,33 @@ describe("Abilities - Wind Rider", () => {
const magikarp = game.scene.getEnemyPokemon()!; const magikarp = game.scene.getEnemyPokemon()!;
const shiftry = game.scene.getPlayerPokemon()!; const shiftry = game.scene.getPlayerPokemon()!;
expect(shiftry.summonData.battleStats[BattleStat.ATK]).toBe(0); expect(shiftry.getStatStage(Stat.ATK)).toBe(0);
expect(magikarp.summonData.battleStats[BattleStat.ATK]).toBe(0); expect(magikarp.getStatStage(Stat.ATK)).toBe(0);
game.doAttack(getMovePosition(game.scene, 0, Moves.TAILWIND)); game.doAttack(getMovePosition(game.scene, 0, Moves.TAILWIND));
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
expect(shiftry.summonData.battleStats[BattleStat.ATK]).toBe(1); expect(shiftry.getStatStage(Stat.ATK)).toBe(1);
expect(magikarp.summonData.battleStats[BattleStat.ATK]).toBe(0); 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); game.override.enemySpecies(Species.MAGIKARP);
await game.startBattle([Species.SHIFTRY]); await game.startBattle([Species.SHIFTRY]);
const magikarp = game.scene.getEnemyPokemon()!; const magikarp = game.scene.getEnemyPokemon()!;
const shiftry = game.scene.getPlayerPokemon()!; const shiftry = game.scene.getPlayerPokemon()!;
expect(shiftry.summonData.battleStats[BattleStat.ATK]).toBe(0); expect(shiftry.getStatStage(Stat.ATK)).toBe(0);
expect(magikarp.summonData.battleStats[BattleStat.ATK]).toBe(0); expect(magikarp.getStatStage(Stat.ATK)).toBe(0);
game.doAttack(getMovePosition(game.scene, 0, Moves.TAILWIND)); game.doAttack(getMovePosition(game.scene, 0, Moves.TAILWIND));
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
expect(shiftry.summonData.battleStats[BattleStat.ATK]).toBe(1); expect(shiftry.getStatStage(Stat.ATK)).toBe(1);
expect(magikarp.summonData.battleStats[BattleStat.ATK]).toBe(0); expect(magikarp.getStatStage(Stat.ATK)).toBe(0);
}); });
it("does not interact with Sandstorm", async () => { it("does not interact with Sandstorm", async () => {
@ -105,14 +105,14 @@ describe("Abilities - Wind Rider", () => {
await game.startBattle([Species.SHIFTRY]); await game.startBattle([Species.SHIFTRY]);
const shiftry = game.scene.getPlayerPokemon()!; 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); expect(shiftry.isFullHp()).toBe(true);
game.doAttack(getMovePosition(game.scene, 0, Moves.SANDSTORM)); game.doAttack(getMovePosition(game.scene, 0, Moves.SANDSTORM));
await game.phaseInterceptor.to(TurnEndPhase); 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()); expect(shiftry.hp).lessThan(shiftry.getMaxHp());
}); });
}); });

View File

@ -224,7 +224,7 @@ describe("achvs", () => {
expect(achvs._50_RIBBONS).toBeInstanceOf(RibbonAchv); expect(achvs._50_RIBBONS).toBeInstanceOf(RibbonAchv);
expect(achvs._75_RIBBONS).toBeInstanceOf(RibbonAchv); expect(achvs._75_RIBBONS).toBeInstanceOf(RibbonAchv);
expect(achvs._100_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.MAX_FRIENDSHIP).toBeInstanceOf(Achv);
expect(achvs.MEGA_EVOLVE).toBeInstanceOf(Achv); expect(achvs.MEGA_EVOLVE).toBeInstanceOf(Achv);
expect(achvs.GIGANTAMAX).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 { describe, expect, it } from "vitest";
import { arrayOfRange, mockI18next } from "./utils/testUtils"; 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_POKEMON = "Testmon";
const TEST_STAT = "Teststat"; const TEST_STAT = "Teststat";
@ -25,7 +25,7 @@ describe("battle-stat", () => {
}); });
it("should fall back to ??? for an unknown BattleStat", () => { 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 { 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 { GameModes } from "#app/game-mode";
import { getGameMode } from "#app/game-mode.js"; import { getGameMode } from "#app/game-mode.js";
import { import {
@ -339,7 +339,7 @@ describe("Test Battle Phase", () => {
.startingLevel(100) .startingLevel(100)
.moveset([moveToUse]) .moveset([moveToUse])
.enemyMoveset(SPLASH_ONLY) .enemyMoveset(SPLASH_ONLY)
.startingHeldItems([{ name: "TEMP_STAT_BOOSTER", type: TempBattleStat.ACC }]); .startingHeldItems([{ name: "TEMP_STAT_BOOSTER", type: Stat.ACC }]);
await game.startBattle(); await game.startBattle();
game.scene.getPlayerPokemon()!.hp = 1; 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 BattleScene from "#app/battle-scene.js";
import { BattlerTag, BattlerTagLapseType, OctolockTag, TrappedTag } from "#app/data/battler-tags.js"; import { BattlerTag, BattlerTagLapseType, OctolockTag, TrappedTag } from "#app/data/battler-tags.js";
import { StatChangePhase } from "#app/phases.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 { BattlerTagType } from "#app/enums/battler-tag-type.js";
import { Stat } from "#enums/stat";
vi.mock("#app/battle-scene.js"); vi.mock("#app/battle-scene.js");
describe("BattlerTag - OctolockTag", () => { describe("BattlerTag - OctolockTag", () => {
describe("lapse behavior", () => { 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 = { const mockPokemon = {
scene: new BattleScene(), scene: new BattleScene(),
getBattlerIndex: () => 0, getBattlerIndex: () => 0,
@ -20,8 +20,8 @@ describe("BattlerTag - OctolockTag", () => {
vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementation(phase => { vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementation(phase => {
expect(phase).toBeInstanceOf(StatChangePhase); expect(phase).toBeInstanceOf(StatChangePhase);
expect((phase as StatChangePhase)["levels"]).toEqual(-1); expect((phase as StatChangePhase)["stages"]).toEqual(-1);
expect((phase as StatChangePhase)["stats"]).toEqual([BattleStat.DEF, BattleStat.SPDEF]); expect((phase as StatChangePhase)["stats"]).toEqual([ Stat.DEF, Stat.SPDEF ]);
}); });
subject.lapse(mockPokemon, BattlerTagLapseType.TURN_END); 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 BattleScene from "#app/battle-scene.js";
import { StockpilingTag } from "#app/data/battler-tags.js"; import { StockpilingTag } from "#app/data/battler-tags.js";
import { StatChangePhase } from "#app/phases.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"; import * as messages from "#app/messages.js";
beforeEach(() => { beforeEach(() => {
@ -12,7 +12,7 @@ beforeEach(() => {
describe("BattlerTag - StockpilingTag", () => { describe("BattlerTag - StockpilingTag", () => {
describe("onAdd", () => { 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 = { const mockPokemon = {
scene: vi.mocked(new BattleScene()) as BattleScene, scene: vi.mocked(new BattleScene()) as BattleScene,
getBattlerIndex: () => 0, getBattlerIndex: () => 0,
@ -24,10 +24,10 @@ describe("BattlerTag - StockpilingTag", () => {
vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementation(phase => { vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementation(phase => {
expect(phase).toBeInstanceOf(StatChangePhase); expect(phase).toBeInstanceOf(StatChangePhase);
expect((phase as StatChangePhase)["levels"]).toEqual(1); expect((phase as StatChangePhase)["stages"]).toEqual(1);
expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([BattleStat.DEF, BattleStat.SPDEF])); 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); subject.onAdd(mockPokemon);
@ -44,17 +44,17 @@ describe("BattlerTag - StockpilingTag", () => {
vi.spyOn(mockPokemon.scene, "queueMessage").mockImplementation(() => {}); vi.spyOn(mockPokemon.scene, "queueMessage").mockImplementation(() => {});
mockPokemon.summonData.battleStats[BattleStat.DEF] = 6; mockPokemon.setStatStage(Stat.DEF, 6);
mockPokemon.summonData.battleStats[BattleStat.SPDEF] = 5; mockPokemon.setStatStage(Stat.SPDEF, 5);
const subject = new StockpilingTag(1); const subject = new StockpilingTag(1);
vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementation(phase => { vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementation(phase => {
expect(phase).toBeInstanceOf(StatChangePhase); expect(phase).toBeInstanceOf(StatChangePhase);
expect((phase as StatChangePhase)["levels"]).toEqual(1); expect((phase as StatChangePhase)["stages"]).toEqual(1);
expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([BattleStat.DEF, BattleStat.SPDEF])); 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); subject.onAdd(mockPokemon);
@ -76,10 +76,10 @@ describe("BattlerTag - StockpilingTag", () => {
vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementation(phase => { vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementation(phase => {
expect(phase).toBeInstanceOf(StatChangePhase); expect(phase).toBeInstanceOf(StatChangePhase);
expect((phase as StatChangePhase)["levels"]).toEqual(1); expect((phase as StatChangePhase)["stages"]).toEqual(1);
expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([BattleStat.DEF, BattleStat.SPDEF])); 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); subject.onOverlap(mockPokemon);
@ -98,18 +98,18 @@ describe("BattlerTag - StockpilingTag", () => {
vi.spyOn(mockPokemon.scene, "queueMessage").mockImplementation(() => {}); vi.spyOn(mockPokemon.scene, "queueMessage").mockImplementation(() => {});
mockPokemon.summonData.battleStats[BattleStat.DEF] = 5; mockPokemon.setStatStage(Stat.DEF, 5);
mockPokemon.summonData.battleStats[BattleStat.SPDEF] = 4; mockPokemon.setStatStage(Stat.SPDEF, 4);
const subject = new StockpilingTag(1); const subject = new StockpilingTag(1);
vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementationOnce(phase => { vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementationOnce(phase => {
expect(phase).toBeInstanceOf(StatChangePhase); expect(phase).toBeInstanceOf(StatChangePhase);
expect((phase as StatChangePhase)["levels"]).toEqual(1); expect((phase as StatChangePhase)["stages"]).toEqual(1);
expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([BattleStat.DEF, BattleStat.SPDEF])); expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([ Stat.DEF, Stat.SPDEF ]));
// def doesn't change // def doesn't change
(phase as StatChangePhase)["onChange"]!(mockPokemon, [BattleStat.SPDEF], [1]); (phase as StatChangePhase)["onChange"]!(mockPokemon, [ Stat.SPDEF ], [1]);
}); });
subject.onAdd(mockPokemon); subject.onAdd(mockPokemon);
@ -117,11 +117,11 @@ describe("BattlerTag - StockpilingTag", () => {
vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementationOnce(phase => { vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementationOnce(phase => {
expect(phase).toBeInstanceOf(StatChangePhase); expect(phase).toBeInstanceOf(StatChangePhase);
expect((phase as StatChangePhase)["levels"]).toEqual(1); expect((phase as StatChangePhase)["stages"]).toEqual(1);
expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([BattleStat.DEF, BattleStat.SPDEF])); expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([ Stat.DEF, Stat.SPDEF ]));
// def doesn't change // def doesn't change
(phase as StatChangePhase)["onChange"]!(mockPokemon, [BattleStat.SPDEF], [1]); (phase as StatChangePhase)["onChange"]!(mockPokemon, [ Stat.SPDEF ], [1]);
}); });
subject.onOverlap(mockPokemon); subject.onOverlap(mockPokemon);
@ -129,8 +129,8 @@ describe("BattlerTag - StockpilingTag", () => {
vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementationOnce(phase => { vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementationOnce(phase => {
expect(phase).toBeInstanceOf(StatChangePhase); expect(phase).toBeInstanceOf(StatChangePhase);
expect((phase as StatChangePhase)["levels"]).toEqual(1); expect((phase as StatChangePhase)["stages"]).toEqual(1);
expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([BattleStat.DEF, BattleStat.SPDEF])); expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([ Stat.DEF, Stat.SPDEF ]));
// neither stat changes, stack count should still increase // neither stat changes, stack count should still increase
}); });
@ -138,20 +138,20 @@ describe("BattlerTag - StockpilingTag", () => {
subject.onOverlap(mockPokemon); subject.onOverlap(mockPokemon);
expect(subject.stockpiledCount).toBe(3); 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"); throw new Error("Should not be called a fourth time");
}); });
// fourth stack should not be applied // fourth stack should not be applied
subject.onOverlap(mockPokemon); subject.onOverlap(mockPokemon);
expect(subject.stockpiledCount).toBe(3); 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 // removing tag should reverse stat changes
vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementationOnce(phase => { vi.spyOn(mockPokemon.scene, "unshiftPhase").mockImplementationOnce(phase => {
expect(phase).toBeInstanceOf(StatChangePhase); expect(phase).toBeInstanceOf(StatChangePhase);
expect((phase as StatChangePhase)["levels"]).toEqual(-2); expect((phase as StatChangePhase)["stages"]).toEqual(-2);
expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([BattleStat.SPDEF])); expect((phase as StatChangePhase)["stats"]).toEqual(expect.arrayContaining([Stat.SPDEF]));
}); });
subject.onRemove(mockPokemon); subject.onRemove(mockPokemon);

View File

@ -37,29 +37,29 @@ describe("Items - Eviolite", () => {
const partyMember = game.scene.getParty()[0]; const partyMember = game.scene.getParty()[0];
// Checking consoe log to make sure Eviolite is applied when getBattleStat (with the appropriate stat) is called // Checking consoe log to make sure Eviolite is applied when getEffectiveStat (with the appropriate stat) is called
partyMember.getBattleStat(Stat.DEF); partyMember.getEffectiveStat(Stat.DEF);
expect(consoleSpy).toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:ModifierType.EVIOLITE.name"), ""); 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 // Printing dummy console messages along the way so subsequent checks don't pass because of the first
console.log(""); console.log("");
partyMember.getBattleStat(Stat.SPDEF); partyMember.getEffectiveStat(Stat.SPDEF);
expect(consoleSpy).toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:ModifierType.EVIOLITE.name"), ""); expect(consoleSpy).toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:ModifierType.EVIOLITE.name"), "");
console.log(""); console.log("");
partyMember.getBattleStat(Stat.ATK); partyMember.getEffectiveStat(Stat.ATK);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:ModifierType.EVIOLITE.name"), ""); expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:ModifierType.EVIOLITE.name"), "");
console.log(""); console.log("");
partyMember.getBattleStat(Stat.SPATK); partyMember.getEffectiveStat(Stat.SPATK);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:ModifierType.EVIOLITE.name"), ""); expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:ModifierType.EVIOLITE.name"), "");
console.log(""); console.log("");
partyMember.getBattleStat(Stat.SPD); partyMember.getEffectiveStat(Stat.SPD);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:ModifierType.EVIOLITE.name"), ""); 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]; const partyMember = game.scene.getParty()[0];
// Checking consoe log to make sure Light Ball is applied when getBattleStat (with the appropriate stat) is called // Checking consoe log to make sure Light Ball is applied when getEffectiveStat (with the appropriate stat) is called
partyMember.getBattleStat(Stat.DEF); partyMember.getEffectiveStat(Stat.DEF);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.LIGHT_BALL.name"), ""); 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 // Printing dummy console messages along the way so subsequent checks don't pass because of the first
console.log(""); console.log("");
partyMember.getBattleStat(Stat.SPDEF); partyMember.getEffectiveStat(Stat.SPDEF);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.LIGHT_BALL.name"), ""); expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.LIGHT_BALL.name"), "");
console.log(""); console.log("");
partyMember.getBattleStat(Stat.ATK); partyMember.getEffectiveStat(Stat.ATK);
expect(consoleSpy).toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.LIGHT_BALL.name"), ""); expect(consoleSpy).toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.LIGHT_BALL.name"), "");
console.log(""); console.log("");
partyMember.getBattleStat(Stat.SPATK); partyMember.getEffectiveStat(Stat.SPATK);
expect(consoleSpy).toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.LIGHT_BALL.name"), ""); expect(consoleSpy).toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.LIGHT_BALL.name"), "");
console.log(""); console.log("");
partyMember.getBattleStat(Stat.SPD); partyMember.getEffectiveStat(Stat.SPD);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.LIGHT_BALL.name"), ""); 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]; const partyMember = game.scene.getParty()[0];
// Checking consoe log to make sure Metal Powder is applied when getBattleStat (with the appropriate stat) is called // Checking consoe log to make sure Metal Powder is applied when getEffectiveStat (with the appropriate stat) is called
partyMember.getBattleStat(Stat.DEF); partyMember.getEffectiveStat(Stat.DEF);
expect(consoleSpy).toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.METAL_POWDER.name"), ""); 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 // Printing dummy console messages along the way so subsequent checks don't pass because of the first
console.log(""); console.log("");
partyMember.getBattleStat(Stat.SPDEF); partyMember.getEffectiveStat(Stat.SPDEF);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.METAL_POWDER.name"), ""); expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.METAL_POWDER.name"), "");
console.log(""); console.log("");
partyMember.getBattleStat(Stat.ATK); partyMember.getEffectiveStat(Stat.ATK);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.METAL_POWDER.name"), ""); expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.METAL_POWDER.name"), "");
console.log(""); console.log("");
partyMember.getBattleStat(Stat.SPATK); partyMember.getEffectiveStat(Stat.SPATK);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.METAL_POWDER.name"), ""); expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.METAL_POWDER.name"), "");
console.log(""); console.log("");
partyMember.getBattleStat(Stat.SPD); partyMember.getEffectiveStat(Stat.SPD);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.METAL_POWDER.name"), ""); 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]; const partyMember = game.scene.getParty()[0];
// Checking consoe log to make sure Quick Powder is applied when getBattleStat (with the appropriate stat) is called // Checking consoe log to make sure Quick Powder is applied when getEffectiveStat (with the appropriate stat) is called
partyMember.getBattleStat(Stat.DEF); partyMember.getEffectiveStat(Stat.DEF);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.QUICK_POWDER.name"), ""); 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 // Printing dummy console messages along the way so subsequent checks don't pass because of the first
console.log(""); console.log("");
partyMember.getBattleStat(Stat.SPDEF); partyMember.getEffectiveStat(Stat.SPDEF);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.QUICK_POWDER.name"), ""); expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.QUICK_POWDER.name"), "");
console.log(""); console.log("");
partyMember.getBattleStat(Stat.ATK); partyMember.getEffectiveStat(Stat.ATK);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.QUICK_POWDER.name"), ""); expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.QUICK_POWDER.name"), "");
console.log(""); console.log("");
partyMember.getBattleStat(Stat.SPATK); partyMember.getEffectiveStat(Stat.SPATK);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.QUICK_POWDER.name"), ""); expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.QUICK_POWDER.name"), "");
console.log(""); console.log("");
partyMember.getBattleStat(Stat.SPD); partyMember.getEffectiveStat(Stat.SPD);
expect(consoleSpy).toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.QUICK_POWDER.name"), ""); 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]; const partyMember = game.scene.getParty()[0];
// Checking consoe log to make sure Thick Club is applied when getBattleStat (with the appropriate stat) is called // Checking consoe log to make sure Thick Club is applied when getEffectiveStat (with the appropriate stat) is called
partyMember.getBattleStat(Stat.DEF); partyMember.getEffectiveStat(Stat.DEF);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.THICK_CLUB.name"), ""); 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 // Printing dummy console messages along the way so subsequent checks don't pass because of the first
console.log(""); console.log("");
partyMember.getBattleStat(Stat.SPDEF); partyMember.getEffectiveStat(Stat.SPDEF);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.THICK_CLUB.name"), ""); expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.THICK_CLUB.name"), "");
console.log(""); console.log("");
partyMember.getBattleStat(Stat.ATK); partyMember.getEffectiveStat(Stat.ATK);
expect(consoleSpy).toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.THICK_CLUB.name"), ""); expect(consoleSpy).toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.THICK_CLUB.name"), "");
console.log(""); console.log("");
partyMember.getBattleStat(Stat.SPATK); partyMember.getEffectiveStat(Stat.SPATK);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.THICK_CLUB.name"), ""); expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.THICK_CLUB.name"), "");
console.log(""); console.log("");
partyMember.getBattleStat(Stat.SPD); partyMember.getEffectiveStat(Stat.SPD);
expect(consoleSpy).not.toHaveBeenLastCalledWith("Applied", i18next.t("modifierType:SpeciesBoosterItem.THICK_CLUB.name"), ""); 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 { PostSummonPhase, TurnEndPhase } from "#app/phases.js";
import GameManager from "#app/test/utils/gameManager"; import GameManager from "#app/test/utils/gameManager";
import { getMovePosition } from "#app/test/utils/gameManagerUtils"; import { getMovePosition } from "#app/test/utils/gameManagerUtils";
@ -35,7 +35,7 @@ describe("Moves - Baton Pass", () => {
.disableCrits(); .disableCrits();
}); });
it("passes stat stage buffs when player uses it", async() => { it("transfers all stat stages when player uses it", async() => {
// arrange // arrange
await game.startBattle([ await game.startBattle([
Species.RAICHU, Species.RAICHU,
@ -45,7 +45,10 @@ describe("Moves - Baton Pass", () => {
// round 1 - buff // round 1 - buff
game.doAttack(getMovePosition(game.scene, 0, Moves.NASTY_PLOT)); game.doAttack(getMovePosition(game.scene, 0, Moves.NASTY_PLOT));
await game.toNextTurn(); 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 // round 2 - baton pass
game.doAttack(getMovePosition(game.scene, 0, Moves.BATON_PASS)); game.doAttack(getMovePosition(game.scene, 0, Moves.BATON_PASS));
@ -53,16 +56,16 @@ describe("Moves - Baton Pass", () => {
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
// assert // assert
const playerPkm = game.scene.getPlayerPokemon()!; playerPokemon = game.scene.getPlayerPokemon()!;
expect(playerPkm.species.speciesId).toEqual(Species.SHUCKLE); expect(playerPokemon.species.speciesId).toEqual(Species.SHUCKLE);
expect(playerPkm.summonData.battleStats[BattleStat.SPATK]).toEqual(2); expect(playerPokemon.getStatStage(Stat.SPATK)).toEqual(2);
}, 20000); }, 20000);
it("passes stat stage buffs when AI uses it", async() => { it("passes stat stage buffs when AI uses it", async() => {
// arrange // arrange
game.override game.override
.startingWave(5) .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([ await game.startBattle([
Species.RAICHU, Species.RAICHU,
Species.SHUCKLE Species.SHUCKLE
@ -74,13 +77,13 @@ describe("Moves - Baton Pass", () => {
// round 2 - baton pass // round 2 - baton pass
game.scene.getEnemyPokemon()!.hp = 100; 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)); game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.phaseInterceptor.to(PostSummonPhase, false); await game.phaseInterceptor.to(PostSummonPhase, false);
// assert // assert
// check buffs are still there // 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 // 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 // can't find a way to override trainer parties with more than 1 pokemon species
expect(game.scene.getEnemyPokemon()!.hp).not.toEqual(100); 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 { getMovePosition } from "#test/utils/gameManagerUtils";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { BattleStat } from "#app/data/battle-stat"; import { Stat } from "#enums/stat";
const TIMEOUT = 20 * 1000; const TIMEOUT = 20 * 1000;
// RATIO : HP Cost of Move // RATIO : HP Cost of Move
@ -39,7 +39,7 @@ describe("Moves - BELLY DRUM", () => {
// Bulbapedia Reference: https://bulbapedia.bulbagarden.net/wiki/Belly_Drum_(move) // 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() => { async() => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
@ -50,47 +50,47 @@ describe("Moves - BELLY DRUM", () => {
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp() - hpLost); expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp() - hpLost);
expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(6); expect(leadPokemon.getStatStage(Stat.ATK)).toBe(6);
}, TIMEOUT }, 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() => { async() => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon()!; const leadPokemon = game.scene.getPlayerPokemon()!;
const hpLost = Math.floor(leadPokemon.getMaxHp() / RATIO); const hpLost = Math.floor(leadPokemon.getMaxHp() / RATIO);
// Here - BattleStat.ATK -> -3 and BattleStat.SPATK -> 6 // Here - Stat.ATK -> -3 and Stat.SPATK -> 6
leadPokemon.summonData.battleStats[BattleStat.ATK] = -3; leadPokemon.setStatStage(Stat.ATK, -3);
leadPokemon.summonData.battleStats[BattleStat.SPATK] = 6; leadPokemon.setStatStage(Stat.SPATK, 6);
game.doAttack(getMovePosition(game.scene, 0, Moves.BELLY_DRUM)); game.doAttack(getMovePosition(game.scene, 0, Moves.BELLY_DRUM));
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp() - hpLost); expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp() - hpLost);
expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(6); expect(leadPokemon.getStatStage(Stat.ATK)).toBe(6);
expect(leadPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(6); expect(leadPokemon.getStatStage(Stat.SPATK)).toBe(6);
}, TIMEOUT }, 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() => { async() => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon()!; 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)); game.doAttack(getMovePosition(game.scene, 0, Moves.BELLY_DRUM));
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp()); expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp());
expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(6); expect(leadPokemon.getStatStage(Stat.ATK)).toBe(6);
}, TIMEOUT }, 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() => { async() => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
@ -102,7 +102,7 @@ describe("Moves - BELLY DRUM", () => {
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.hp).toBe(hpLost - PREDAMAGE); expect(leadPokemon.hp).toBe(hpLost - PREDAMAGE);
expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(0); expect(leadPokemon.getStatStage(Stat.ATK)).toBe(0);
}, TIMEOUT }, 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 Phaser from "phaser";
import GameManager from "#test/utils/gameManager"; import GameManager from "#test/utils/gameManager";
import { TurnEndPhase } from "#app/phases"; import { TurnEndPhase } from "#app/phases";
import { getMovePosition } from "#test/utils/gameManagerUtils"; import { getMovePosition } from "#test/utils/gameManagerUtils";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { BattleStat } from "#app/data/battle-stat"; import { Stat } from "#enums/stat";
import { SPLASH_ONLY } from "#test/utils/testUtils"; import { SPLASH_ONLY } from "#test/utils/testUtils";
const TIMEOUT = 20 * 1000; const TIMEOUT = 20 * 1000;
@ -14,7 +14,7 @@ const RATIO = 3;
/** Amount of extra HP lost */ /** Amount of extra HP lost */
const PREDAMAGE = 15; const PREDAMAGE = 15;
describe("Moves - CLANGOROUS_SOUL", () => { describe("Moves - Clangorous Soul", () => {
let phaserGame: Phaser.Game; let phaserGame: Phaser.Game;
let game: GameManager; let game: GameManager;
@ -40,75 +40,75 @@ describe("Moves - CLANGOROUS_SOUL", () => {
//Bulbapedia Reference: https://bulbapedia.bulbagarden.net/wiki/Clangorous_Soul_(move) //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() => { async() => {
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon()!;
const hpLost = Math.floor(leadPokemon.getMaxHp() / RATIO);
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(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);
}, TIMEOUT
);
test("Clangorous Soul will still take effect if one or more of the involved stats are not at max",
async() => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon()!; const leadPokemon = game.scene.getPlayerPokemon()!;
const hpLost = Math.floor(leadPokemon.getMaxHp() / RATIO); 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;
game.doAttack(getMovePosition(game.scene, 0, Moves.CLANGOROUS_SOUL)); game.doAttack(getMovePosition(game.scene, 0, Moves.CLANGOROUS_SOUL));
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp() - hpLost); expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp() - hpLost);
expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(6); expect(leadPokemon.getStatStage(Stat.ATK)).toBe(1);
expect(leadPokemon.summonData.battleStats[BattleStat.DEF]).toBe(6); expect(leadPokemon.getStatStage(Stat.DEF)).toBe(1);
expect(leadPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(6); expect(leadPokemon.getStatStage(Stat.SPATK)).toBe(1);
expect(leadPokemon.summonData.battleStats[BattleStat.SPDEF]).toBe(5); expect(leadPokemon.getStatStage(Stat.SPDEF)).toBe(1);
expect(leadPokemon.summonData.battleStats[BattleStat.SPD]).toBe(1); expect(leadPokemon.getStatStage(Stat.SPD)).toBe(1);
}, TIMEOUT }, TIMEOUT
); );
test("Clangorous Soul fails if all stats involved are 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 - 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.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
);
it("fails if all stat stages involved are at max",
async() => { async() => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon()!; const leadPokemon = game.scene.getPlayerPokemon()!;
leadPokemon.summonData.battleStats[BattleStat.ATK] = 6; leadPokemon.setStatStage(Stat.ATK, 6);
leadPokemon.summonData.battleStats[BattleStat.DEF] = 6; leadPokemon.setStatStage(Stat.DEF, 6);
leadPokemon.summonData.battleStats[BattleStat.SPATK] = 6; leadPokemon.setStatStage(Stat.SPATK, 6);
leadPokemon.summonData.battleStats[BattleStat.SPDEF] = 6; leadPokemon.setStatStage(Stat.SPDEF, 6);
leadPokemon.summonData.battleStats[BattleStat.SPD] = 6; leadPokemon.setStatStage(Stat.SPD, 6);
game.doAttack(getMovePosition(game.scene, 0, Moves.CLANGOROUS_SOUL)); game.doAttack(getMovePosition(game.scene, 0, Moves.CLANGOROUS_SOUL));
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp()); expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp());
expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(6); expect(leadPokemon.getStatStage(Stat.ATK)).toBe(6);
expect(leadPokemon.summonData.battleStats[BattleStat.DEF]).toBe(6); expect(leadPokemon.getStatStage(Stat.DEF)).toBe(6);
expect(leadPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(6); expect(leadPokemon.getStatStage(Stat.SPATK)).toBe(6);
expect(leadPokemon.summonData.battleStats[BattleStat.SPDEF]).toBe(6); expect(leadPokemon.getStatStage(Stat.SPDEF)).toBe(6);
expect(leadPokemon.summonData.battleStats[BattleStat.SPD]).toBe(6); expect(leadPokemon.getStatStage(Stat.SPD)).toBe(6);
}, TIMEOUT }, 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() => { async() => {
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
@ -120,11 +120,11 @@ describe("Moves - CLANGOROUS_SOUL", () => {
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.hp).toBe(hpLost - PREDAMAGE); expect(leadPokemon.hp).toBe(hpLost - PREDAMAGE);
expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(0); expect(leadPokemon.getStatStage(Stat.ATK)).toBe(0);
expect(leadPokemon.summonData.battleStats[BattleStat.DEF]).toBe(0); expect(leadPokemon.getStatStage(Stat.DEF)).toBe(0);
expect(leadPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(0); expect(leadPokemon.getStatStage(Stat.SPATK)).toBe(0);
expect(leadPokemon.summonData.battleStats[BattleStat.SPDEF]).toBe(0); expect(leadPokemon.getStatStage(Stat.SPDEF)).toBe(0);
expect(leadPokemon.summonData.battleStats[BattleStat.SPD]).toBe(0); expect(leadPokemon.getStatStage(Stat.SPD)).toBe(0);
}, TIMEOUT }, 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 { Abilities } from "#app/enums/abilities.js";
import { TurnEndPhase } from "#app/phases"; import { TurnEndPhase } from "#app/phases";
import GameManager from "#test/utils/gameManager"; 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]); 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]); await game.startBattle([Species.MAGIKARP]);
const ally = game.scene.getPlayerPokemon()!; const ally = game.scene.getPlayerPokemon()!;
const enemy = game.scene.getEnemyPokemon()!; const enemy = game.scene.getEnemyPokemon()!;
vi.spyOn(enemy, "getAccuracyMultiplier"); 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)); game.doAttack(getMovePosition(game.scene, 0, Moves.DOUBLE_TEAM));
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
await game.toNextTurn(); await game.toNextTurn();
expect(ally.summonData.battleStats[BattleStat.EVA]).toBe(1); expect(ally.getStatStage(Stat.EVA)).toBe(1);
expect(enemy.getAccuracyMultiplier).toHaveReturnedWith(.75); 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 { Type } from "#app/data/type";
import { Species } from "#app/enums/species.js"; import { Species } from "#app/enums/species.js";
import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
@ -64,9 +64,8 @@ describe("Moves - Dragon Rage", () => {
game.doAttack(getMovePosition(game.scene, 0, Moves.DRAGON_RAGE)); game.doAttack(getMovePosition(game.scene, 0, Moves.DRAGON_RAGE));
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
const damageDealt = enemyPokemon.getMaxHp() - enemyPokemon.hp;
expect(damageDealt).toBe(dragonRageDamage); expect(enemyPokemon.getInverseHp()).toBe(dragonRageDamage);
}); });
it("ignores resistances", async () => { it("ignores resistances", async () => {
@ -75,20 +74,18 @@ describe("Moves - Dragon Rage", () => {
game.doAttack(getMovePosition(game.scene, 0, Moves.DRAGON_RAGE)); game.doAttack(getMovePosition(game.scene, 0, Moves.DRAGON_RAGE));
await game.phaseInterceptor.to(TurnEndPhase); 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(); game.override.disableCrits();
partyPokemon.summonData.battleStats[BattleStat.SPATK] = 2; partyPokemon.setStatStage(Stat.SPATK, 2);
game.doAttack(getMovePosition(game.scene, 0, Moves.DRAGON_RAGE)); game.doAttack(getMovePosition(game.scene, 0, Moves.DRAGON_RAGE));
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
const damageDealt = enemyPokemon.getMaxHp() - enemyPokemon.hp;
expect(damageDealt).toBe(dragonRageDamage); expect(enemyPokemon.getInverseHp()).toBe(dragonRageDamage);
}); });
it("ignores stab", async () => { it("ignores stab", async () => {
@ -97,9 +94,8 @@ describe("Moves - Dragon Rage", () => {
game.doAttack(getMovePosition(game.scene, 0, Moves.DRAGON_RAGE)); game.doAttack(getMovePosition(game.scene, 0, Moves.DRAGON_RAGE));
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
const damageDealt = enemyPokemon.getMaxHp() - enemyPokemon.hp;
expect(damageDealt).toBe(dragonRageDamage); expect(enemyPokemon.getInverseHp()).toBe(dragonRageDamage);
}); });
it("ignores criticals", async () => { it("ignores criticals", async () => {
@ -107,20 +103,18 @@ describe("Moves - Dragon Rage", () => {
game.doAttack(getMovePosition(game.scene, 0, Moves.DRAGON_RAGE)); game.doAttack(getMovePosition(game.scene, 0, Moves.DRAGON_RAGE));
await game.phaseInterceptor.to(TurnEndPhase); 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.disableCrits();
game.override.enemyAbility(Abilities.ICE_SCALES); game.override.enemyAbility(Abilities.ICE_SCALES);
game.doAttack(getMovePosition(game.scene, 0, Moves.DRAGON_RAGE)); game.doAttack(getMovePosition(game.scene, 0, Moves.DRAGON_RAGE));
await game.phaseInterceptor.to(TurnEndPhase); 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 () => { it("ignores multi hit", async () => {
@ -129,8 +123,7 @@ describe("Moves - Dragon Rage", () => {
game.doAttack(getMovePosition(game.scene, 0, Moves.DRAGON_RAGE)); game.doAttack(getMovePosition(game.scene, 0, Moves.DRAGON_RAGE));
await game.phaseInterceptor.to(TurnEndPhase); 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 { getMovePosition } from "#test/utils/gameManagerUtils";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { BattleStat } from "#app/data/battle-stat"; import { Stat } from "#enums/stat";
import { SPLASH_ONLY } from "#test/utils/testUtils"; import { SPLASH_ONLY } from "#test/utils/testUtils";
const TIMEOUT = 20 * 1000; const TIMEOUT = 20 * 1000;
@ -51,9 +51,9 @@ describe("Moves - FILLET AWAY", () => {
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp() - hpLost); expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp() - hpLost);
expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(2); expect(leadPokemon.getStatStage(Stat.ATK)).toBe(2);
expect(leadPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(2); expect(leadPokemon.getStatStage(Stat.SPATK)).toBe(2);
expect(leadPokemon.summonData.battleStats[BattleStat.SPD]).toBe(2); expect(leadPokemon.getStatStage(Stat.SPD)).toBe(2);
}, TIMEOUT }, TIMEOUT
); );
@ -64,17 +64,17 @@ describe("Moves - FILLET AWAY", () => {
const leadPokemon = game.scene.getPlayerPokemon()!; const leadPokemon = game.scene.getPlayerPokemon()!;
const hpLost = Math.floor(leadPokemon.getMaxHp() / RATIO); const hpLost = Math.floor(leadPokemon.getMaxHp() / RATIO);
//Here - BattleStat.SPD -> 0 and BattleStat.SPATK -> 3 //Here - Stat.SPD -> 0 and Stat.SPATK -> 3
leadPokemon.summonData.battleStats[BattleStat.ATK] = 6; leadPokemon.setStatStage(Stat.ATK, 6);
leadPokemon.summonData.battleStats[BattleStat.SPATK] = 3; leadPokemon.setStatStage(Stat.SPATK, 3);
game.doAttack(getMovePosition(game.scene, 0, Moves.FILLET_AWAY)); game.doAttack(getMovePosition(game.scene, 0, Moves.FILLET_AWAY));
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp() - hpLost); expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp() - hpLost);
expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(6); expect(leadPokemon.getStatStage(Stat.ATK)).toBe(6);
expect(leadPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(5); expect(leadPokemon.getStatStage(Stat.SPATK)).toBe(5);
expect(leadPokemon.summonData.battleStats[BattleStat.SPD]).toBe(2); expect(leadPokemon.getStatStage(Stat.SPD)).toBe(2);
}, TIMEOUT }, TIMEOUT
); );
@ -84,17 +84,17 @@ describe("Moves - FILLET AWAY", () => {
const leadPokemon = game.scene.getPlayerPokemon()!; const leadPokemon = game.scene.getPlayerPokemon()!;
leadPokemon.summonData.battleStats[BattleStat.ATK] = 6; leadPokemon.setStatStage(Stat.ATK, 6);
leadPokemon.summonData.battleStats[BattleStat.SPATK] = 6; leadPokemon.setStatStage(Stat.SPATK, 6);
leadPokemon.summonData.battleStats[BattleStat.SPD] = 6; leadPokemon.setStatStage(Stat.SPD, 6);
game.doAttack(getMovePosition(game.scene, 0, Moves.FILLET_AWAY)); game.doAttack(getMovePosition(game.scene, 0, Moves.FILLET_AWAY));
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp()); expect(leadPokemon.hp).toBe(leadPokemon.getMaxHp());
expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(6); expect(leadPokemon.getStatStage(Stat.ATK)).toBe(6);
expect(leadPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(6); expect(leadPokemon.getStatStage(Stat.SPATK)).toBe(6);
expect(leadPokemon.summonData.battleStats[BattleStat.SPD]).toBe(6); expect(leadPokemon.getStatStage(Stat.SPD)).toBe(6);
}, TIMEOUT }, TIMEOUT
); );
@ -110,9 +110,9 @@ describe("Moves - FILLET AWAY", () => {
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.hp).toBe(hpLost - PREDAMAGE); expect(leadPokemon.hp).toBe(hpLost - PREDAMAGE);
expect(leadPokemon.summonData.battleStats[BattleStat.ATK]).toBe(0); expect(leadPokemon.getStatStage(Stat.ATK)).toBe(0);
expect(leadPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(0); expect(leadPokemon.getStatStage(Stat.SPATK)).toBe(0);
expect(leadPokemon.summonData.battleStats[BattleStat.SPD]).toBe(0); expect(leadPokemon.getStatStage(Stat.SPD)).toBe(0);
}, TIMEOUT }, 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 { Species } from "#app/enums/species.js";
import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon"; import { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
import { DamagePhase, TurnEndPhase } from "#app/phases"; import { DamagePhase, TurnEndPhase } from "#app/phases";
@ -52,7 +52,7 @@ describe("Moves - Fissure", () => {
game.scene.clearEnemyHeldItemModifiers(); 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.ability(Abilities.NO_GUARD);
game.override.enemyAbility(Abilities.FUR_COAT); game.override.enemyAbility(Abilities.FUR_COAT);
@ -62,10 +62,10 @@ describe("Moves - Fissure", () => {
expect(enemyPokemon.isFainted()).toBe(true); expect(enemyPokemon.isFainted()).toBe(true);
}); });
it("ignores accuracy stat", async () => { it("ignores user's ACC stat stage", async () => {
vi.spyOn(partyPokemon, "getAccuracyMultiplier"); vi.spyOn(partyPokemon, "getAccuracyMultiplier");
enemyPokemon.summonData.battleStats[BattleStat.ACC] = -6; partyPokemon.setStatStage(Stat.ACC, -6);
game.doAttack(getMovePosition(game.scene, 0, Moves.FISSURE)); game.doAttack(getMovePosition(game.scene, 0, Moves.FISSURE));
@ -75,10 +75,10 @@ describe("Moves - Fissure", () => {
expect(partyPokemon.getAccuracyMultiplier).toHaveReturnedWith(1); expect(partyPokemon.getAccuracyMultiplier).toHaveReturnedWith(1);
}); });
it("ignores evasion stat", async () => { it("ignores target's EVA stat stage", async () => {
vi.spyOn(partyPokemon, "getAccuracyMultiplier"); vi.spyOn(partyPokemon, "getAccuracyMultiplier");
enemyPokemon.summonData.battleStats[BattleStat.EVA] = 6; enemyPokemon.setStatStage(Stat.EVA, 6);
game.doAttack(getMovePosition(game.scene, 0, Moves.FISSURE)); 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 { SemiInvulnerableTag } from "#app/data/battler-tags.js";
import { Type } from "#app/data/type.js"; import { Type } from "#app/data/type.js";
import { Biome } from "#app/enums/biome.js"; import { Biome } from "#app/enums/biome.js";
@ -35,24 +35,24 @@ describe("Moves - Flower Shield", () => {
game.override.enemyMoveset(SPLASH_ONLY); 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); game.override.enemySpecies(Species.CHERRIM);
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const cherrim = game.scene.getEnemyPokemon()!; const cherrim = game.scene.getEnemyPokemon()!;
const magikarp = game.scene.getPlayerPokemon()!; const magikarp = game.scene.getPlayerPokemon()!;
expect(magikarp.summonData.battleStats[BattleStat.DEF]).toBe(0); expect(magikarp.getStatStage(Stat.DEF)).toBe(0);
expect(cherrim.summonData.battleStats[BattleStat.DEF]).toBe(0); expect(cherrim.getStatStage(Stat.DEF)).toBe(0);
game.doAttack(getMovePosition(game.scene, 0, Moves.FLOWER_SHIELD)); game.doAttack(getMovePosition(game.scene, 0, Moves.FLOWER_SHIELD));
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
expect(magikarp.summonData.battleStats[BattleStat.DEF]).toBe(0); expect(magikarp.getStatStage(Stat.DEF)).toBe(0);
expect(cherrim.summonData.battleStats[BattleStat.DEF]).toBe(1); 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"); game.override.enemySpecies(Species.MAGIKARP).startingBiome(Biome.GRASS).battleType("double");
await game.startBattle([Species.CHERRIM, Species.MAGIKARP]); 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 grassPokemons = field.filter(p => p.getTypes().includes(Type.GRASS));
const nonGrassPokemons = field.filter(pokemon => !grassPokemons.includes(pokemon)); const nonGrassPokemons = field.filter(pokemon => !grassPokemons.includes(pokemon));
grassPokemons.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.summonData.battleStats[BattleStat.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, 0, Moves.FLOWER_SHIELD));
game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH));
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
grassPokemons.forEach(p => expect(p.summonData.battleStats[BattleStat.DEF]).toBe(1)); grassPokemons.forEach(p => expect(p.getStatStage(Stat.DEF)).toBe(1));
nonGrassPokemons.forEach(p => expect(p.summonData.battleStats[BattleStat.DEF]).toBe(0)); nonGrassPokemons.forEach(p => expect(p.getStatStage(Stat.DEF)).toBe(0));
}); });
/** /**
* See semi-vulnerable state tags. {@linkcode SemiInvulnerableTag} * 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.enemySpecies(Species.PARAS);
game.override.enemyMoveset([Moves.DIG, Moves.DIG, Moves.DIG, Moves.DIG]); game.override.enemyMoveset([Moves.DIG, Moves.DIG, Moves.DIG, Moves.DIG]);
game.override.enemyLevel(50); game.override.enemyLevel(50);
@ -84,32 +84,32 @@ describe("Moves - Flower Shield", () => {
const paras = game.scene.getEnemyPokemon()!; const paras = game.scene.getEnemyPokemon()!;
const cherrim = game.scene.getPlayerPokemon()!; const cherrim = game.scene.getPlayerPokemon()!;
expect(paras.summonData.battleStats[BattleStat.DEF]).toBe(0); expect(paras.getStatStage(Stat.DEF)).toBe(0);
expect(cherrim.summonData.battleStats[BattleStat.DEF]).toBe(0); expect(cherrim.getStatStage(Stat.DEF)).toBe(0);
expect(paras.getTag(SemiInvulnerableTag)).toBeUndefined; expect(paras.getTag(SemiInvulnerableTag)).toBeUndefined;
game.doAttack(getMovePosition(game.scene, 0, Moves.FLOWER_SHIELD)); game.doAttack(getMovePosition(game.scene, 0, Moves.FLOWER_SHIELD));
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
expect(paras.getTag(SemiInvulnerableTag)).toBeDefined(); expect(paras.getTag(SemiInvulnerableTag)).toBeDefined();
expect(paras.summonData.battleStats[BattleStat.DEF]).toBe(0); expect(paras.getStatStage(Stat.DEF)).toBe(0);
expect(cherrim.summonData.battleStats[BattleStat.DEF]).toBe(1); 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); game.override.enemySpecies(Species.MAGIKARP);
await game.startBattle([Species.MAGIKARP]); await game.startBattle([Species.MAGIKARP]);
const enemy = game.scene.getEnemyPokemon()!; const enemy = game.scene.getEnemyPokemon()!;
const ally = game.scene.getPlayerPokemon()!; const ally = game.scene.getPlayerPokemon()!;
expect(enemy.summonData.battleStats[BattleStat.DEF]).toBe(0); expect(enemy.getStatStage(Stat.DEF)).toBe(0);
expect(ally.summonData.battleStats[BattleStat.DEF]).toBe(0); expect(ally.getStatStage(Stat.DEF)).toBe(0);
game.doAttack(getMovePosition(game.scene, 0, Moves.FLOWER_SHIELD)); game.doAttack(getMovePosition(game.scene, 0, Moves.FLOWER_SHIELD));
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
expect(enemy.summonData.battleStats[BattleStat.DEF]).toBe(0); expect(enemy.getStatStage(Stat.DEF)).toBe(0);
expect(ally.summonData.battleStats[BattleStat.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)); game.doAttack(getMovePosition(game.scene, 1, Moves.FOLLOW_ME));
await game.phaseInterceptor.to(TurnEndPhase, false); 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[1].hp).toBeLessThan(playerStartingHp[1]);
expect(playerPokemon[0].hp).toBe(playerStartingHp[0]); expect(playerPokemon[0].hp).toBe(playerStartingHp[0]);

View File

@ -1,5 +1,5 @@
import { BattleStat } from "#app/data/battle-stat"; import { Stat } from "#enums/stat";
import { MoveEndPhase, TurnInitPhase } from "#app/phases"; import { TurnInitPhase } from "#app/phases";
import GameManager from "#test/utils/gameManager"; import GameManager from "#test/utils/gameManager";
import { getMovePosition } from "#test/utils/gameManagerUtils"; import { getMovePosition } from "#test/utils/gameManagerUtils";
import { Abilities } from "#enums/abilities"; import { Abilities } from "#enums/abilities";
@ -11,72 +11,52 @@ import { SPLASH_ONLY } from "#test/utils/testUtils";
import { allMoves } from "#app/data/move.js"; import { allMoves } from "#app/data/move.js";
describe("Moves - Freezy Frost", () => { describe("Moves - Freezy Frost", () => {
describe("integration tests", () => { let phaserGame: Phaser.Game;
let phaserGame: Phaser.Game; let game: GameManager;
let game: GameManager;
beforeAll(() => { beforeAll(() => {
phaserGame = new Phaser.Game({ type: Phaser.HEADLESS }); phaserGame = new Phaser.Game({ type: Phaser.HEADLESS });
}); });
afterEach(() => { afterEach(() => {
game.phaseInterceptor.restoreOg(); game.phaseInterceptor.restoreOg();
}); });
beforeEach(() => { beforeEach(() => {
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
game.override.battleType("single"); game.override.battleType("single");
game.override.enemySpecies(Species.RATTATA); game.override.enemySpecies(Species.RATTATA);
game.override.enemyLevel(100); game.override.enemyLevel(100);
game.override.enemyMoveset(SPLASH_ONLY); game.override.enemyMoveset(SPLASH_ONLY);
game.override.enemyAbility(Abilities.NONE); game.override.enemyAbility(Abilities.NONE);
game.override.startingLevel(100); game.override.startingLevel(100);
game.override.moveset([Moves.FREEZY_FROST, Moves.SWORDS_DANCE, Moves.CHARM, Moves.SPLASH]); game.override.moveset([Moves.FREEZY_FROST, Moves.SWORDS_DANCE, Moves.CHARM, Moves.SPLASH]);
vi.spyOn(allMoves[Moves.FREEZY_FROST], "accuracy", "get").mockReturnValue(100); vi.spyOn(allMoves[Moves.FREEZY_FROST], "accuracy", "get").mockReturnValue(100);
game.override.ability(Abilities.NONE); 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]); await game.startBattle([Species.RATTATA]);
const user = game.scene.getPlayerPokemon()!; const user = game.scene.getPlayerPokemon()!;
const enemy = game.scene.getEnemyPokemon()!; const enemy = game.scene.getEnemyPokemon()!;
expect(user.summonData.battleStats[BattleStat.ATK]).toBe(0);
expect(enemy.summonData.battleStats[BattleStat.ATK]).toBe(0);
game.doAttack(getMovePosition(game.scene, 0, Moves.SWORDS_DANCE)); expect(user.getStatStage(Stat.ATK)).toBe(0);
await game.phaseInterceptor.to(TurnInitPhase); expect(enemy.getStatStage(Stat.ATK)).toBe(0);
game.doAttack(getMovePosition(game.scene, 0, Moves.CHARM)); game.doAttack(getMovePosition(game.scene, 0, Moves.SWORDS_DANCE));
await game.phaseInterceptor.to(TurnInitPhase); 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);
game.doAttack(getMovePosition(game.scene, 0, Moves.FREEZY_FROST)); game.doAttack(getMovePosition(game.scene, 0, Moves.CHARM));
await game.phaseInterceptor.to(TurnInitPhase); await game.phaseInterceptor.to(TurnInitPhase);
expect(user.summonData.battleStats[BattleStat.ATK]).toBe(0); expect(user.getStatStage(Stat.ATK)).toBe(2);
expect(enemy.summonData.battleStats[BattleStat.ATK]).toBe(0); expect(enemy.getStatStage(Stat.ATK)).toBe(-2);
});
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.doAttack(getMovePosition(game.scene, 0, Moves.FREEZY_FROST));
game.override.enemyMoveset([Moves.FREEZY_FROST, Moves.FREEZY_FROST, Moves.FREEZY_FROST, Moves.FREEZY_FROST]); await game.phaseInterceptor.to(TurnInitPhase);
await game.startBattle([Species.SHUCKLE]); // Shuckle for slower Swords Dance on first turn so Freezy Frost doesn't affect it. expect(user.getStatStage(Stat.ATK)).toBe(0);
const user = game.scene.getPlayerPokemon()!; expect(enemy.getStatStage(Stat.ATK)).toBe(0);
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);
});
}); });
}); });

View File

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

View File

@ -1,5 +1,5 @@
import { BattleStat } from "#app/data/battle-stat"; import { Stat } from "#enums/stat";
import { MoveEndPhase, TurnInitPhase } from "#app/phases"; import { TurnInitPhase } from "#app/phases";
import GameManager from "#test/utils/gameManager"; import GameManager from "#test/utils/gameManager";
import { getMovePosition } from "#test/utils/gameManagerUtils"; import { getMovePosition } from "#test/utils/gameManagerUtils";
import { Abilities } from "#enums/abilities"; import { Abilities } from "#enums/abilities";
@ -37,44 +37,28 @@ describe("Moves - Haze", () => {
game.override.ability(Abilities.NONE); 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]); await game.startBattle([Species.RATTATA]);
const user = game.scene.getPlayerPokemon()!; const user = game.scene.getPlayerPokemon()!;
const enemy = game.scene.getEnemyPokemon()!; 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)); game.doAttack(getMovePosition(game.scene, 0, Moves.SWORDS_DANCE));
await game.phaseInterceptor.to(TurnInitPhase); await game.phaseInterceptor.to(TurnInitPhase);
game.doAttack(getMovePosition(game.scene, 0, Moves.CHARM)); game.doAttack(getMovePosition(game.scene, 0, Moves.CHARM));
await game.phaseInterceptor.to(TurnInitPhase); await game.phaseInterceptor.to(TurnInitPhase);
const userAtkBefore = user.summonData.battleStats[BattleStat.ATK];
const enemyAtkBefore = enemy.summonData.battleStats[BattleStat.ATK]; expect(user.getStatStage(Stat.ATK)).toBe(2);
expect(userAtkBefore).toBe(2); expect(enemy.getStatStage(Stat.ATK)).toBe(-2);
expect(enemyAtkBefore).toBe(-2);
game.doAttack(getMovePosition(game.scene, 0, Moves.HAZE)); game.doAttack(getMovePosition(game.scene, 0, Moves.HAZE));
await game.phaseInterceptor.to(TurnInitPhase); 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 () => { expect(user.getStatStage(Stat.ATK)).toBe(0);
game.override.enemyMoveset([Moves.HAZE, Moves.HAZE, Moves.HAZE, Moves.HAZE]); expect(enemy.getStatStage(Stat.ATK)).toBe(0);
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);
}); });
}); });
}); });

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 { MoveEndPhase, StatChangePhase } from "#app/phases";
import GameManager from "#test/utils/gameManager"; import GameManager from "#test/utils/gameManager";
import { getMovePosition } from "#test/utils/gameManagerUtils"; import { getMovePosition } from "#test/utils/gameManagerUtils";
@ -36,17 +36,17 @@ describe("Moves - Make It Rain", () => {
game.override.enemyLevel(100); 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]); 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, 0, Moves.MAKE_IT_RAIN));
game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH));
await game.phaseInterceptor.to(MoveEndPhase); await game.phaseInterceptor.to(MoveEndPhase);
expect(playerPokemon[0].summonData.battleStats[BattleStat.SPATK]).toBe(-1); expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(-1);
}, TIMEOUT); }, TIMEOUT);
it("should apply effects even if the target faints", async () => { it("should apply effects even if the target faints", async () => {
@ -63,7 +63,7 @@ describe("Moves - Make It Rain", () => {
await game.phaseInterceptor.to(StatChangePhase); await game.phaseInterceptor.to(StatChangePhase);
expect(enemyPokemon.isFainted()).toBe(true); expect(enemyPokemon.isFainted()).toBe(true);
expect(playerPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(-1); expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(-1);
}, TIMEOUT); }, TIMEOUT);
it("should reduce Sp. Atk. once after KOing two enemies", async () => { 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]); await game.startBattle([Species.CHARIZARD, Species.BLASTOISE]);
const playerPokemon = game.scene.getPlayerField(); const playerPokemon = game.scene.getPlayerPokemon()!;
const enemyPokemon = game.scene.getEnemyField(); const enemyPokemon = game.scene.getEnemyField();
game.doAttack(getMovePosition(game.scene, 0, Moves.MAKE_IT_RAIN)); game.doAttack(getMovePosition(game.scene, 0, Moves.MAKE_IT_RAIN));
@ -80,13 +80,13 @@ describe("Moves - Make It Rain", () => {
await game.phaseInterceptor.to(StatChangePhase); await game.phaseInterceptor.to(StatChangePhase);
enemyPokemon.forEach(p => expect(p.isFainted()).toBe(true)); 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); }, 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]); 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, 0, Moves.MAKE_IT_RAIN));
game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH));
@ -96,6 +96,6 @@ describe("Moves - Make It Rain", () => {
await game.phaseInterceptor.to(MoveEndPhase); await game.phaseInterceptor.to(MoveEndPhase);
expect(playerPokemon[0].summonData.battleStats[BattleStat.SPATK]).toBe(-1); expect(playerPokemon.getStatStage(Stat.SPATK)).toBe(-1);
}, TIMEOUT); }, 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 { TrappedTag } from "#app/data/battler-tags.js";
import { CommandPhase, MoveEndPhase, TurnInitPhase } from "#app/phases"; import { CommandPhase, MoveEndPhase, TurnInitPhase } from "#app/phases";
import GameManager from "#test/utils/gameManager"; import GameManager from "#test/utils/gameManager";
@ -39,40 +39,40 @@ describe("Moves - Octolock", () => {
game.override.ability(Abilities.BALL_FETCH); 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]); 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 // use Octolock and advance to init phase of next turn to check for stat changes
game.doAttack(getMovePosition(game.scene, 0, Moves.OCTOLOCK)); game.doAttack(getMovePosition(game.scene, 0, Moves.OCTOLOCK));
await game.phaseInterceptor.to(TurnInitPhase); await game.phaseInterceptor.to(TurnInitPhase);
expect(enemyPokemon[0].summonData.battleStats[BattleStat.DEF]).toBe(-1); expect(enemyPokemon.getStatStage(Stat.DEF)).toBe(-1);
expect(enemyPokemon[0].summonData.battleStats[BattleStat.SPDEF]).toBe(-1); expect(enemyPokemon.getStatStage(Stat.SPDEF)).toBe(-1);
// take a second turn to make sure stat changes occur again // take a second turn to make sure stat changes occur again
await game.phaseInterceptor.to(CommandPhase); await game.phaseInterceptor.to(CommandPhase);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH)); game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.phaseInterceptor.to(TurnInitPhase); await game.phaseInterceptor.to(TurnInitPhase);
expect(enemyPokemon[0].summonData.battleStats[BattleStat.DEF]).toBe(-2); expect(enemyPokemon.getStatStage(Stat.DEF)).toBe(-2);
expect(enemyPokemon[0].summonData.battleStats[BattleStat.SPDEF]).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]); await game.startBattle([Species.GRAPPLOCT]);
const enemyPokemon = game.scene.getEnemyField(); const enemyPokemon = game.scene.getEnemyPokemon()!;
// before Octolock - enemy should not be trapped // 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)); game.doAttack(getMovePosition(game.scene, 0, Moves.OCTOLOCK));
// after Octolock - enemy should be trapped // after Octolock - enemy should be trapped
await game.phaseInterceptor.to(MoveEndPhase); 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 { StockpilingTag } from "#app/data/battler-tags.js";
import { allMoves } from "#app/data/move.js"; import { allMoves } from "#app/data/move.js";
import { BattlerTagType } from "#app/enums/battler-tag-type.js"; import { BattlerTagType } from "#app/enums/battler-tag-type.js";
@ -16,6 +16,8 @@ describe("Moves - Spit Up", () => {
let phaserGame: Phaser.Game; let phaserGame: Phaser.Game;
let game: GameManager; let game: GameManager;
const spitUp = allMoves[Moves.SPIT_UP];
beforeAll(() => { beforeAll(() => {
phaserGame = new Phaser.Game({ type: Phaser.HEADLESS }); phaserGame = new Phaser.Game({ type: Phaser.HEADLESS });
}); });
@ -34,8 +36,10 @@ describe("Moves - Spit Up", () => {
game.override.enemyAbility(Abilities.NONE); game.override.enemyAbility(Abilities.NONE);
game.override.enemyLevel(2000); 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); game.override.ability(Abilities.NONE);
vi.spyOn(spitUp, "calculateBattlePower");
}); });
describe("consumes all stockpile stacks to deal damage (scaling with stacks)", () => { describe("consumes all stockpile stacks to deal damage (scaling with stacks)", () => {
@ -52,13 +56,11 @@ describe("Moves - Spit Up", () => {
expect(stockpilingTag).toBeDefined(); expect(stockpilingTag).toBeDefined();
expect(stockpilingTag.stockpiledCount).toBe(stacksToSetup); expect(stockpilingTag.stockpiledCount).toBe(stacksToSetup);
vi.spyOn(allMoves[Moves.SPIT_UP], "calculateBattlePower");
game.doAttack(0); game.doAttack(0);
await game.phaseInterceptor.to(TurnInitPhase); await game.phaseInterceptor.to(TurnInitPhase);
expect(allMoves[Moves.SPIT_UP].calculateBattlePower).toHaveBeenCalledOnce(); expect(spitUp.calculateBattlePower).toHaveBeenCalledOnce();
expect(allMoves[Moves.SPIT_UP].calculateBattlePower).toHaveReturnedWith(expectedPower); expect(spitUp.calculateBattlePower).toHaveReturnedWith(expectedPower);
expect(pokemon.getTag(StockpilingTag)).toBeUndefined(); expect(pokemon.getTag(StockpilingTag)).toBeUndefined();
}); });
@ -77,13 +79,11 @@ describe("Moves - Spit Up", () => {
expect(stockpilingTag).toBeDefined(); expect(stockpilingTag).toBeDefined();
expect(stockpilingTag.stockpiledCount).toBe(stacksToSetup); expect(stockpilingTag.stockpiledCount).toBe(stacksToSetup);
vi.spyOn(allMoves[Moves.SPIT_UP], "calculateBattlePower");
game.doAttack(0); game.doAttack(0);
await game.phaseInterceptor.to(TurnInitPhase); await game.phaseInterceptor.to(TurnInitPhase);
expect(allMoves[Moves.SPIT_UP].calculateBattlePower).toHaveBeenCalledOnce(); expect(spitUp.calculateBattlePower).toHaveBeenCalledOnce();
expect(allMoves[Moves.SPIT_UP].calculateBattlePower).toHaveReturnedWith(expectedPower); expect(spitUp.calculateBattlePower).toHaveReturnedWith(expectedPower);
expect(pokemon.getTag(StockpilingTag)).toBeUndefined(); expect(pokemon.getTag(StockpilingTag)).toBeUndefined();
}); });
@ -103,13 +103,11 @@ describe("Moves - Spit Up", () => {
expect(stockpilingTag).toBeDefined(); expect(stockpilingTag).toBeDefined();
expect(stockpilingTag.stockpiledCount).toBe(stacksToSetup); expect(stockpilingTag.stockpiledCount).toBe(stacksToSetup);
vi.spyOn(allMoves[Moves.SPIT_UP], "calculateBattlePower");
game.doAttack(0); game.doAttack(0);
await game.phaseInterceptor.to(TurnInitPhase); await game.phaseInterceptor.to(TurnInitPhase);
expect(allMoves[Moves.SPIT_UP].calculateBattlePower).toHaveBeenCalledOnce(); expect(spitUp.calculateBattlePower).toHaveBeenCalledOnce();
expect(allMoves[Moves.SPIT_UP].calculateBattlePower).toHaveReturnedWith(expectedPower); expect(spitUp.calculateBattlePower).toHaveReturnedWith(expectedPower);
expect(pokemon.getTag(StockpilingTag)).toBeUndefined(); expect(pokemon.getTag(StockpilingTag)).toBeUndefined();
}); });
@ -123,14 +121,12 @@ describe("Moves - Spit Up", () => {
const stockpilingTag = pokemon.getTag(StockpilingTag)!; const stockpilingTag = pokemon.getTag(StockpilingTag)!;
expect(stockpilingTag).toBeUndefined(); expect(stockpilingTag).toBeUndefined();
vi.spyOn(allMoves[Moves.SPIT_UP], "calculateBattlePower");
game.doAttack(0); game.doAttack(0);
await game.phaseInterceptor.to(TurnInitPhase); await game.phaseInterceptor.to(TurnInitPhase);
expect(pokemon.getMoveHistory().at(-1)).toMatchObject<TurnMove>({ move: Moves.SPIT_UP, result: MoveResult.FAIL }); 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", () => { describe("restores stat boosts granted by stacks", () => {
@ -143,22 +139,20 @@ describe("Moves - Spit Up", () => {
const stockpilingTag = pokemon.getTag(StockpilingTag)!; const stockpilingTag = pokemon.getTag(StockpilingTag)!;
expect(stockpilingTag).toBeDefined(); expect(stockpilingTag).toBeDefined();
vi.spyOn(allMoves[Moves.SPIT_UP], "calculateBattlePower");
game.doAttack(0); game.doAttack(0);
await game.phaseInterceptor.to(MovePhase); await game.phaseInterceptor.to(MovePhase);
expect(pokemon.summonData.battleStats[BattleStat.DEF]).toBe(1); expect(pokemon.getStatStage(Stat.DEF)).toBe(1);
expect(pokemon.summonData.battleStats[BattleStat.SPDEF]).toBe(1); expect(pokemon.getStatStage(Stat.SPDEF)).toBe(1);
await game.phaseInterceptor.to(TurnInitPhase); await game.phaseInterceptor.to(TurnInitPhase);
expect(pokemon.getMoveHistory().at(-1)).toMatchObject<TurnMove>({ move: Moves.SPIT_UP, result: MoveResult.SUCCESS }); 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.getStatStage(Stat.DEF)).toBe(0);
expect(pokemon.summonData.battleStats[BattleStat.SPDEF]).toBe(0); expect(pokemon.getStatStage(Stat.SPDEF)).toBe(0);
expect(pokemon.getTag(StockpilingTag)).toBeUndefined(); 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 // for the sake of simplicity (and because other tests cover the setup), set boost amounts directly
stockpilingTag.statChangeCounts = { stockpilingTag.statChangeCounts = {
[BattleStat.DEF]: -1, [Stat.DEF]: -1,
[BattleStat.SPDEF]: 2, [Stat.SPDEF]: 2,
}; };
expect(stockpilingTag.statChangeCounts).toMatchObject({
[BattleStat.DEF]: -1,
[BattleStat.SPDEF]: 2,
});
vi.spyOn(allMoves[Moves.SPIT_UP], "calculateBattlePower");
game.doAttack(0); game.doAttack(0);
await game.phaseInterceptor.to(TurnInitPhase); await game.phaseInterceptor.to(TurnInitPhase);
expect(pokemon.getMoveHistory().at(-1)).toMatchObject<TurnMove>({ move: Moves.SPIT_UP, result: MoveResult.SUCCESS }); 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.getStatStage(Stat.DEF)).toBe(1);
expect(pokemon.summonData.battleStats[BattleStat.SPDEF]).toBe(-2); expect(pokemon.getStatStage(Stat.SPDEF)).toBe(-2);
expect(pokemon.getTag(StockpilingTag)).toBeUndefined(); 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, * 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. * 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 spotTarget = enemyPokemon[1].getBattlerIndex();
const attackTarget = enemyPokemon[0].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 { StockpilingTag } from "#app/data/battler-tags.js";
import { MoveResult, TurnMove } from "#app/field/pokemon.js"; import { MoveResult, TurnMove } from "#app/field/pokemon.js";
import { CommandPhase, TurnInitPhase } from "#app/phases"; import { CommandPhase, TurnInitPhase } from "#app/phases";
@ -38,7 +38,7 @@ describe("Moves - Stockpile", () => {
game.override.ability(Abilities.NONE); 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]); await game.startBattle([Species.ABOMASNOW]);
const user = game.scene.getPlayerPokemon()!; const user = game.scene.getPlayerPokemon()!;
@ -47,8 +47,8 @@ describe("Moves - Stockpile", () => {
// we just have to know that they're implemented as a BattlerTag. // we just have to know that they're implemented as a BattlerTag.
expect(user.getTag(StockpilingTag)).toBeUndefined(); expect(user.getTag(StockpilingTag)).toBeUndefined();
expect(user.summonData.battleStats[BattleStat.DEF]).toBe(0); expect(user.getStatStage(Stat.DEF)).toBe(0);
expect(user.summonData.battleStats[BattleStat.SPDEF]).toBe(0); expect(user.getStatStage(Stat.SPDEF)).toBe(0);
// use Stockpile four times // use Stockpile four times
for (let i = 0; i < 4; i++) { for (let i = 0; i < 4; i++) {
@ -60,18 +60,16 @@ describe("Moves - Stockpile", () => {
await game.phaseInterceptor.to(TurnInitPhase); await game.phaseInterceptor.to(TurnInitPhase);
const stockpilingTag = user.getTag(StockpilingTag)!; 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 if (i < 3) { // first three uses should behave normally
expect(def).toBe(i + 1); expect(user.getStatStage(Stat.DEF)).toBe(i + 1);
expect(spdef).toBe(i + 1); expect(user.getStatStage(Stat.SPDEF)).toBe(i + 1);
expect(stockpilingTag).toBeDefined(); expect(stockpilingTag).toBeDefined();
expect(stockpilingTag.stockpiledCount).toBe(i + 1); expect(stockpilingTag.stockpiledCount).toBe(i + 1);
} else { // fourth should have failed } else { // fourth should have failed
expect(def).toBe(3); expect(user.getStatStage(Stat.DEF)).toBe(3);
expect(spdef).toBe(3); expect(user.getStatStage(Stat.SPDEF)).toBe(3);
expect(stockpilingTag).toBeDefined(); expect(stockpilingTag).toBeDefined();
expect(stockpilingTag.stockpiledCount).toBe(3); expect(stockpilingTag.stockpiledCount).toBe(3);
expect(user.getMoveHistory().at(-1)).toMatchObject<TurnMove>({ result: MoveResult.FAIL, move: Moves.STOCKPILE }); 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]); await game.startBattle([Species.ABOMASNOW]);
const user = game.scene.getPlayerPokemon()!; const user = game.scene.getPlayerPokemon()!;
user.summonData.battleStats[BattleStat.DEF] = 6; user.setStatStage(Stat.DEF, 6);
user.summonData.battleStats[BattleStat.SPDEF] = 6; user.setStatStage(Stat.SPDEF, 6);
expect(user.getTag(StockpilingTag)).toBeUndefined(); expect(user.getTag(StockpilingTag)).toBeUndefined();
expect(user.summonData.battleStats[BattleStat.DEF]).toBe(6); expect(user.getStatStage(Stat.DEF)).toBe(6);
expect(user.summonData.battleStats[BattleStat.SPDEF]).toBe(6); expect(user.getStatStage(Stat.SPDEF)).toBe(6);
game.doAttack(getMovePosition(game.scene, 0, Moves.STOCKPILE)); game.doAttack(getMovePosition(game.scene, 0, Moves.STOCKPILE));
await game.phaseInterceptor.to(TurnInitPhase); await game.phaseInterceptor.to(TurnInitPhase);
@ -97,8 +95,8 @@ describe("Moves - Stockpile", () => {
const stockpilingTag = user.getTag(StockpilingTag)!; const stockpilingTag = user.getTag(StockpilingTag)!;
expect(stockpilingTag).toBeDefined(); expect(stockpilingTag).toBeDefined();
expect(stockpilingTag.stockpiledCount).toBe(1); expect(stockpilingTag.stockpiledCount).toBe(1);
expect(user.summonData.battleStats[BattleStat.DEF]).toBe(6); expect(user.getStatStage(Stat.DEF)).toBe(6);
expect(user.summonData.battleStats[BattleStat.SPDEF]).toBe(6); expect(user.getStatStage(Stat.SPDEF)).toBe(6);
// do it again, just for good measure // do it again, just for good measure
await game.phaseInterceptor.to(CommandPhase); await game.phaseInterceptor.to(CommandPhase);
@ -109,8 +107,8 @@ describe("Moves - Stockpile", () => {
const stockpilingTagAgain = user.getTag(StockpilingTag)!; const stockpilingTagAgain = user.getTag(StockpilingTag)!;
expect(stockpilingTagAgain).toBeDefined(); expect(stockpilingTagAgain).toBeDefined();
expect(stockpilingTagAgain.stockpiledCount).toBe(2); expect(stockpilingTagAgain.stockpiledCount).toBe(2);
expect(user.summonData.battleStats[BattleStat.DEF]).toBe(6); expect(user.getStatStage(Stat.DEF)).toBe(6);
expect(user.summonData.battleStats[BattleStat.SPDEF]).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 { StockpilingTag } from "#app/data/battler-tags.js";
import { BattlerTagType } from "#app/enums/battler-tag-type.js"; import { BattlerTagType } from "#app/enums/battler-tag-type.js";
import { MoveResult, TurnMove } from "#app/field/pokemon.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 }); 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 () => { it("decreases stats based on stored values (both boosts equal)", { timeout: 10000 }, async () => {
await game.startBattle([Species.ABOMASNOW]); await game.startBattle([Species.ABOMASNOW]);
@ -150,20 +150,20 @@ describe("Moves - Swallow", () => {
game.doAttack(0); game.doAttack(0);
await game.phaseInterceptor.to(MovePhase); await game.phaseInterceptor.to(MovePhase);
expect(pokemon.summonData.battleStats[BattleStat.DEF]).toBe(1); expect(pokemon.getStatStage(Stat.DEF)).toBe(1);
expect(pokemon.summonData.battleStats[BattleStat.SPDEF]).toBe(1); expect(pokemon.getStatStage(Stat.SPDEF)).toBe(1);
await game.phaseInterceptor.to(TurnInitPhase); await game.phaseInterceptor.to(TurnInitPhase);
expect(pokemon.getMoveHistory().at(-1)).toMatchObject<TurnMove>({ move: Moves.SWALLOW, result: MoveResult.SUCCESS }); expect(pokemon.getMoveHistory().at(-1)).toMatchObject<TurnMove>({ move: Moves.SWALLOW, result: MoveResult.SUCCESS });
expect(pokemon.summonData.battleStats[BattleStat.DEF]).toBe(0); expect(pokemon.getStatStage(Stat.DEF)).toBe(0);
expect(pokemon.summonData.battleStats[BattleStat.SPDEF]).toBe(0); expect(pokemon.getStatStage(Stat.SPDEF)).toBe(0);
expect(pokemon.getTag(StockpilingTag)).toBeUndefined(); 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]); await game.startBattle([Species.ABOMASNOW]);
const pokemon = game.scene.getPlayerPokemon()!; 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 // for the sake of simplicity (and because other tests cover the setup), set boost amounts directly
stockpilingTag.statChangeCounts = { stockpilingTag.statChangeCounts = {
[BattleStat.DEF]: -1, [Stat.DEF]: -1,
[BattleStat.SPDEF]: 2, [Stat.SPDEF]: 2,
}; };
expect(stockpilingTag.statChangeCounts).toMatchObject({
[BattleStat.DEF]: -1,
[BattleStat.SPDEF]: 2,
});
game.doAttack(0); game.doAttack(0);
await game.phaseInterceptor.to(TurnInitPhase); await game.phaseInterceptor.to(TurnInitPhase);
expect(pokemon.getMoveHistory().at(-1)).toMatchObject<TurnMove>({ move: Moves.SWALLOW, result: MoveResult.SUCCESS }); expect(pokemon.getMoveHistory().at(-1)).toMatchObject<TurnMove>({ move: Moves.SWALLOW, result: MoveResult.SUCCESS });
expect(pokemon.summonData.battleStats[BattleStat.DEF]).toBe(1); expect(pokemon.getStatStage(Stat.DEF)).toBe(1);
expect(pokemon.summonData.battleStats[BattleStat.SPDEF]).toBe(-2); expect(pokemon.getStatStage(Stat.SPDEF)).toBe(-2);
expect(pokemon.getTag(StockpilingTag)).toBeUndefined(); expect(pokemon.getTag(StockpilingTag)).toBeUndefined();
}); });

View File

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

View File

@ -39,16 +39,16 @@ describe("Abilities - Wind Rider", () => {
const magikarpSpd = magikarp.getStat(Stat.SPD); const magikarpSpd = magikarp.getStat(Stat.SPD);
const meowthSpd = meowth.getStat(Stat.SPD); const meowthSpd = meowth.getStat(Stat.SPD);
expect(magikarp.getBattleStat(Stat.SPD)).equal(magikarpSpd); expect(magikarp.getEffectiveStat(Stat.SPD)).equal(magikarpSpd);
expect(meowth.getBattleStat(Stat.SPD)).equal(meowthSpd); expect(meowth.getEffectiveStat(Stat.SPD)).equal(meowthSpd);
game.doAttack(getMovePosition(game.scene, 0, Moves.TAILWIND)); game.doAttack(getMovePosition(game.scene, 0, Moves.TAILWIND));
game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH));
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
expect(magikarp.getBattleStat(Stat.SPD)).toBe(magikarpSpd * 2); expect(magikarp.getEffectiveStat(Stat.SPD)).toBe(magikarpSpd * 2);
expect(meowth.getBattleStat(Stat.SPD)).toBe(meowthSpd * 2); expect(meowth.getEffectiveStat(Stat.SPD)).toBe(meowthSpd * 2);
expect(game.scene.arena.getTagOnSide(ArenaTagType.TAILWIND, ArenaTagSide.PLAYER)).toBeDefined(); expect(game.scene.arena.getTagOnSide(ArenaTagType.TAILWIND, ArenaTagSide.PLAYER)).toBeDefined();
}); });
@ -87,8 +87,8 @@ describe("Abilities - Wind Rider", () => {
const enemySpd = enemy.getStat(Stat.SPD); const enemySpd = enemy.getStat(Stat.SPD);
expect(ally.getBattleStat(Stat.SPD)).equal(allySpd); expect(ally.getEffectiveStat(Stat.SPD)).equal(allySpd);
expect(enemy.getBattleStat(Stat.SPD)).equal(enemySpd); 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.PLAYER)).toBeUndefined();
expect(game.scene.arena.getTagOnSide(ArenaTagType.TAILWIND, ArenaTagSide.ENEMY)).toBeUndefined(); expect(game.scene.arena.getTagOnSide(ArenaTagType.TAILWIND, ArenaTagSide.ENEMY)).toBeUndefined();
@ -96,8 +96,8 @@ describe("Abilities - Wind Rider", () => {
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
expect(ally.getBattleStat(Stat.SPD)).toBe(allySpd * 2); expect(ally.getEffectiveStat(Stat.SPD)).toBe(allySpd * 2);
expect(enemy.getBattleStat(Stat.SPD)).equal(enemySpd); 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.PLAYER)).toBeDefined();
expect(game.scene.arena.getTagOnSide(ArenaTagType.TAILWIND, ArenaTagSide.ENEMY)).toBeUndefined(); 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 { ArenaTagType } from "#app/enums/arena-tag-type.js";
import { MoveEndPhase, TurnEndPhase } from "#app/phases"; import { MoveEndPhase, TurnEndPhase } from "#app/phases";
import GameManager from "#test/utils/gameManager"; import GameManager from "#test/utils/gameManager";
@ -60,7 +60,6 @@ describe("Moves - Tidy Up", () => {
game.doAttack(getMovePosition(game.scene, 0, Moves.TIDY_UP)); game.doAttack(getMovePosition(game.scene, 0, Moves.TIDY_UP));
await game.phaseInterceptor.to(MoveEndPhase); await game.phaseInterceptor.to(MoveEndPhase);
expect(game.scene.arena.getTag(ArenaTagType.STEALTH_ROCK)).toBeUndefined(); expect(game.scene.arena.getTag(ArenaTagType.STEALTH_ROCK)).toBeUndefined();
}, 20000); }, 20000);
it("toxic spikes are cleared", async() => { it("toxic spikes are cleared", async() => {
@ -73,7 +72,6 @@ describe("Moves - Tidy Up", () => {
game.doAttack(getMovePosition(game.scene, 0, Moves.TIDY_UP)); game.doAttack(getMovePosition(game.scene, 0, Moves.TIDY_UP));
await game.phaseInterceptor.to(MoveEndPhase); await game.phaseInterceptor.to(MoveEndPhase);
expect(game.scene.arena.getTag(ArenaTagType.TOXIC_SPIKES)).toBeUndefined(); expect(game.scene.arena.getTag(ArenaTagType.TOXIC_SPIKES)).toBeUndefined();
}, 20000); }, 20000);
it("sticky webs are cleared", async() => { it("sticky webs are cleared", async() => {
@ -87,7 +85,6 @@ describe("Moves - Tidy Up", () => {
game.doAttack(getMovePosition(game.scene, 0, Moves.TIDY_UP)); game.doAttack(getMovePosition(game.scene, 0, Moves.TIDY_UP));
await game.phaseInterceptor.to(MoveEndPhase); await game.phaseInterceptor.to(MoveEndPhase);
expect(game.scene.arena.getTag(ArenaTagType.STICKY_WEB)).toBeUndefined(); expect(game.scene.arena.getTag(ArenaTagType.STICKY_WEB)).toBeUndefined();
}, 20000); }, 20000);
it.skip("substitutes are cleared", async() => { it.skip("substitutes are cleared", async() => {
@ -101,22 +98,20 @@ describe("Moves - Tidy Up", () => {
game.doAttack(getMovePosition(game.scene, 0, Moves.TIDY_UP)); game.doAttack(getMovePosition(game.scene, 0, Moves.TIDY_UP));
await game.phaseInterceptor.to(MoveEndPhase); await game.phaseInterceptor.to(MoveEndPhase);
// TODO: check for subs here once the move is implemented // TODO: check for subs here once the move is implemented
}, 20000); }, 20000);
it("user's stats are raised with no traps set", async() => { it("user's stats are raised with no traps set", async() => {
await game.startBattle(); await game.startBattle();
const player = game.scene.getPlayerPokemon()!.summonData.battleStats;
expect(player[BattleStat.ATK]).toBe(0); const playerPokemon = game.scene.getPlayerPokemon()!;
expect(player[BattleStat.SPD]).toBe(0);
expect(playerPokemon.getStatStage(Stat.ATK)).toBe(0);
expect(playerPokemon.getStatStage(Stat.SPD)).toBe(0);
game.doAttack(getMovePosition(game.scene, 0, Moves.TIDY_UP)); game.doAttack(getMovePosition(game.scene, 0, Moves.TIDY_UP));
await game.phaseInterceptor.to(TurnEndPhase); await game.phaseInterceptor.to(TurnEndPhase);
expect(player[BattleStat.ATK]).toBe(+1); expect(playerPokemon.getStatStage(Stat.ATK)).toBe(1);
expect(player[BattleStat.SPD]).toBe(+1); expect(playerPokemon.getStatStage(Stat.SPD)).toBe(1);
}, 20000); }, 20000);
}); });

View File

@ -7,7 +7,7 @@ import { StatusEffect } from "../data/status-effect";
import BattleScene from "../battle-scene"; import BattleScene from "../battle-scene";
import { Type, getTypeRgb } from "../data/type"; import { Type, getTypeRgb } from "../data/type";
import { getVariantTint } from "#app/data/variant"; import { getVariantTint } from "#app/data/variant";
import { BattleStat } from "#app/data/battle-stat"; import { Stat } from "#enums/stat";
import BattleFlyout from "./battle-flyout"; import BattleFlyout from "./battle-flyout";
import { WindowVariant, addWindow } from "./ui-theme"; import { WindowVariant, addWindow } from "./ui-theme";
import i18next from "i18next"; import i18next from "i18next";
@ -30,7 +30,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
private lastLevelExp: integer; private lastLevelExp: integer;
private lastLevel: integer; private lastLevel: integer;
private lastLevelCapped: boolean; private lastLevelCapped: boolean;
private lastBattleStats: string; private lastStats: string;
private box: Phaser.GameObjects.Sprite; private box: Phaser.GameObjects.Sprite;
private nameText: Phaser.GameObjects.Text; private nameText: Phaser.GameObjects.Text;
@ -68,9 +68,9 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
public flyoutMenu?: BattleFlyout; public flyoutMenu?: BattleFlyout;
private battleStatOrder: BattleStat[]; private statOrder: Stat[];
private battleStatOrderPlayer = [BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.ACC, BattleStat.EVA, BattleStat.SPD]; private statOrderPlayer = [ Stat.ATK, Stat.DEF, Stat.SPATK, Stat.SPDEF, Stat.ACC, Stat.EVA, Stat.SPD ];
private battleStatOrderEnemy = [BattleStat.HP, BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.ACC, BattleStat.EVA, BattleStat.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) { constructor(scene: Phaser.Scene, x: number, y: number, player: boolean) {
super(scene, x, y); 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 startingX = this.player ? -this.statsBox.width + 8 : -this.statsBox.width + 5;
const paddingX = this.player ? 4 : 2; const paddingX = this.player ? 4 : 2;
const statOverflow = this.player ? 1 : 0; 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 // 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 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 // 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 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 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; statY = baseY + 5;
} else { } 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 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.setName("icon_stat_label_" + i.toString());
statLabel.setOrigin(0, 0); statLabel.setOrigin(0, 0);
statLabels.push(statLabel); statLabels.push(statLabel);
this.statValuesContainer.add(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.setName("icon_stat_number_" + i.toString());
statNumber.setOrigin(0, 0); statNumber.setOrigin(0, 0);
this.statNumbers.push(statNumber); this.statNumbers.push(statNumber);
this.statValuesContainer.add(statNumber); this.statValuesContainer.add(statNumber);
if (this.battleStatOrder[i] === BattleStat.HP) { if (this.statOrder[i] === Stat.HP) {
statLabel.setVisible(false); statLabel.setVisible(false);
statNumber.setVisible(false); statNumber.setVisible(false);
} }
@ -433,10 +433,10 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
this.statValuesContainer.setPosition(8, 7); this.statValuesContainer.setPosition(8, 7);
} }
const battleStats = this.battleStatOrder.map(() => 0); const stats = this.statOrder.map(() => 0);
this.lastBattleStats = battleStats.join(""); this.lastStats = stats.join("");
this.updateBattleStats(battleStats); this.updateStats(stats);
} }
getTextureName(): string { getTextureName(): string {
@ -653,9 +653,9 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
const battleStats = pokemon.getStatStages(); const battleStats = pokemon.getStatStages();
const battleStatsStr = battleStats.join(""); const battleStatsStr = battleStats.join("");
if (this.lastBattleStats !== battleStatsStr) { if (this.lastStats !== battleStatsStr) {
this.updateBattleStats(battleStats); this.updateStats(battleStats);
this.lastBattleStats = battleStatsStr; this.lastStats = battleStatsStr;
} }
this.shinyIcon.setVisible(pokemon.isShiny()); this.shinyIcon.setVisible(pokemon.isShiny());
@ -767,10 +767,10 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
} }
} }
updateBattleStats(battleStats: integer[]): void { updateStats(stats: integer[]): void {
this.battleStatOrder.map((s, i) => { this.statOrder.map((s, i) => {
if (s !== BattleStat.HP) { if (s !== Stat.HP) {
this.statNumbers[i].setFrame(battleStats[s].toString()); 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 { Mode } from "./ui";
import * as Utils from "../utils"; import * as Utils from "../utils";
import MessageUiHandler from "./message-ui-handler"; import MessageUiHandler from "./message-ui-handler";
import { getStatName, Stat } from "../data/pokemon-stat";
import { addWindow } from "./ui-theme"; import { addWindow } from "./ui-theme";
import BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext"; import BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext";
import {Button} from "#enums/buttons"; import {Button} from "#enums/buttons";
import i18next from "i18next"; import i18next from "i18next";
import { Stat, getStatKey, PERMANENT_STATS } from "#app/enums/stat.js";
export default class BattleMessageUiHandler extends MessageUiHandler { export default class BattleMessageUiHandler extends MessageUiHandler {
private levelUpStatsContainer: Phaser.GameObjects.Container; 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 }); const levelUpStatsLabelsContent = addTextObject(this.scene, (this.scene.game.canvas.width / 6) - 73, -94, "", TextStyle.WINDOW, { maxLines: 6 });
let levelUpStatsLabelText = ""; let levelUpStatsLabelText = "";
const stats = Utils.getEnumValues(Stat); for (const s of PERMANENT_STATS) {
for (const s of stats) { levelUpStatsLabelText += `${getStatKey(s)}\n`;
levelUpStatsLabelText += `${getStatName(s)}\n`;
} }
levelUpStatsLabelsContent.text = levelUpStatsLabelText; levelUpStatsLabelsContent.text = levelUpStatsLabelText;
levelUpStatsLabelsContent.x -= levelUpStatsLabelsContent.displayWidth; levelUpStatsLabelsContent.x -= levelUpStatsLabelsContent.displayWidth;

View File

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

View File

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