Remove BattleStat, Use "Stat Stages" & New Names

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

File diff suppressed because it is too large Load Diff

View File

@ -8,7 +8,7 @@ import { MoveEffectPhase, PokemonHealPhase, ShowAbilityPhase, StatChangePhase }
import { StatusEffect } from "./status-effect";
import { BattlerIndex } from "../battle";
import { BlockNonDirectDamageAbAttr, ProtectStatAbAttr, applyAbAttrs } from "./ability";
import { BattleStat } from "./battle-stat";
import { Stat } from "#enums/stat";
import { CommonAnim, CommonBattleAnim } from "./battle-anims";
import i18next from "i18next";
import { Abilities } from "#enums/abilities";
@ -726,7 +726,7 @@ class StickyWebTag extends ArenaTrapTag {
if (!cancelled.value) {
pokemon.scene.queueMessage(i18next.t("arenaTag:stickyWebActivateTrap", { pokemonName: pokemon.getNameToRender() }));
const statLevels = new Utils.NumberHolder(-1);
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), false, [BattleStat.SPD], statLevels.value));
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), false, [ Stat.SPD ], statLevels.value));
}
}
@ -814,7 +814,7 @@ class TailwindTag extends ArenaTag {
// Raise attack by one stage if party member has WIND_RIDER ability
if (pokemon.hasAbility(Abilities.WIND_RIDER)) {
pokemon.scene.unshiftPhase(new ShowAbilityPhase(pokemon.scene, pokemon.getBattlerIndex()));
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [BattleStat.ATK], 1, true));
pokemon.scene.unshiftPhase(new StatChangePhase(pokemon.scene, pokemon.getBattlerIndex(), true, [ Stat.ATK ], 1, true));
}
}
}

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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