Compare commits
1 Commits
0a5249c090
...
a0a960ec5a
Author | SHA1 | Date |
---|---|---|
EmberCM | a0a960ec5a |
|
@ -2625,11 +2625,7 @@ export class PreStatStageChangeAbAttr extends AbAttr {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Protect one or all {@linkcode BattleStat} from reductions caused by other Pokémon's moves and Abilities
|
||||
*/
|
||||
export class ProtectStatAbAttr extends PreStatStageChangeAbAttr {
|
||||
/** {@linkcode BattleStat} to protect or `undefined` if **all** {@linkcode BattleStat} are protected */
|
||||
private protectedStat?: BattleStat;
|
||||
|
||||
constructor(protectedStat?: BattleStat) {
|
||||
|
@ -2638,17 +2634,7 @@ export class ProtectStatAbAttr extends PreStatStageChangeAbAttr {
|
|||
this.protectedStat = protectedStat;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the {@linkcode ProtectedStatAbAttr} to an interaction
|
||||
* @param _pokemon
|
||||
* @param _passive
|
||||
* @param simulated
|
||||
* @param stat the {@linkcode BattleStat} being affected
|
||||
* @param cancelled The {@linkcode Utils.BooleanHolder} that will be set to true if the stat is protected
|
||||
* @param _args
|
||||
* @returns true if the stat is protected, false otherwise
|
||||
*/
|
||||
applyPreStatStageChange(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, _args: any[]): boolean {
|
||||
applyPreStatStageChange(_pokemon: Pokemon, _passive: boolean, simulated: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, _args: any[]): boolean {
|
||||
if (Utils.isNullOrUndefined(this.protectedStat) || stat === this.protectedStat) {
|
||||
cancelled.value = true;
|
||||
return true;
|
||||
|
@ -3771,7 +3757,7 @@ export class StatStageChangeMultiplierAbAttr extends AbAttr {
|
|||
this.multiplier = multiplier;
|
||||
}
|
||||
|
||||
override apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean {
|
||||
apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean {
|
||||
(args[0] as Utils.IntegerHolder).value *= this.multiplier;
|
||||
|
||||
return true;
|
||||
|
|
|
@ -488,14 +488,14 @@ export function initMoveAnim(scene: BattleScene, move: Moves): Promise<void> {
|
|||
} else {
|
||||
moveAnims.set(move, null);
|
||||
const defaultMoveAnim = allMoves[move] instanceof AttackMove ? Moves.TACKLE : allMoves[move] instanceof SelfStatusMove ? Moves.FOCUS_ENERGY : Moves.TAIL_WHIP;
|
||||
|
||||
const moveName = Moves[move].toLowerCase().replace(/\_/g, "-");
|
||||
const fetchAnimAndResolve = (move: Moves) => {
|
||||
scene.cachedFetch(`./battle-anims/${Utils.animationFileName(move)}.json`)
|
||||
scene.cachedFetch(`./battle-anims/${moveName}.json`)
|
||||
.then(response => {
|
||||
const contentType = response.headers.get("content-type");
|
||||
if (!response.ok || contentType?.indexOf("application/json") === -1) {
|
||||
useDefaultAnim(move, defaultMoveAnim);
|
||||
logMissingMoveAnim(move, response.status, response.statusText);
|
||||
console.error(`Could not load animation file for move '${moveName}'`, response.status, response.statusText);
|
||||
populateMoveAnim(move, moveAnims.get(defaultMoveAnim));
|
||||
return resolve();
|
||||
}
|
||||
return response.json();
|
||||
|
@ -515,11 +515,6 @@ export function initMoveAnim(scene: BattleScene, move: Moves): Promise<void> {
|
|||
} else {
|
||||
resolve();
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
useDefaultAnim(move, defaultMoveAnim);
|
||||
logMissingMoveAnim(move, error);
|
||||
return resolve();
|
||||
});
|
||||
};
|
||||
fetchAnimAndResolve(move);
|
||||
|
@ -527,29 +522,6 @@ export function initMoveAnim(scene: BattleScene, move: Moves): Promise<void> {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the default animation for the given move.
|
||||
*
|
||||
* @param move the move to populate an animation for
|
||||
* @param defaultMoveAnim the move to use as the default animation
|
||||
*/
|
||||
function useDefaultAnim(move: Moves, defaultMoveAnim: Moves) {
|
||||
populateMoveAnim(move, moveAnims.get(defaultMoveAnim));
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method for printing a warning to the console when a move animation is missing.
|
||||
*
|
||||
* @param move the move to populate an animation for
|
||||
* @param optionalParams parameters to add to the error logging
|
||||
*
|
||||
* @remarks use {@linkcode useDefaultAnim} to use a default animation
|
||||
*/
|
||||
function logMissingMoveAnim(move: Moves, ...optionalParams: any[]) {
|
||||
const moveName = Utils.animationFileName(move);
|
||||
console.warn(`Could not load animation file for move '${moveName}'`, ...optionalParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches animation configs to be used in a Mystery Encounter
|
||||
* @param scene
|
||||
|
|
|
@ -1273,13 +1273,13 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||
* @param attrType {@linkcode AbAttr} The ability attribute to check for.
|
||||
* @param canApply {@linkcode Boolean} If false, it doesn't check whether the ability is currently active
|
||||
* @param ignoreOverride {@linkcode Boolean} If true, it ignores ability changing effects
|
||||
* @returns A list of all the ability attributes on this ability.
|
||||
* @returns {AbAttr[]} A list of all the ability attributes on this ability.
|
||||
*/
|
||||
getAbilityAttrs<T extends AbAttr = AbAttr>(attrType: { new(...args: any[]): T }, canApply: boolean = true, ignoreOverride?: boolean): T[] {
|
||||
const abilityAttrs: T[] = [];
|
||||
getAbilityAttrs(attrType: { new(...args: any[]): AbAttr }, canApply: boolean = true, ignoreOverride?: boolean): AbAttr[] {
|
||||
const abilityAttrs: AbAttr[] = [];
|
||||
|
||||
if (!canApply || this.canApplyAbility()) {
|
||||
abilityAttrs.push(...this.getAbility(ignoreOverride).getAttrs<T>(attrType));
|
||||
abilityAttrs.push(...this.getAbility(ignoreOverride).getAttrs(attrType));
|
||||
}
|
||||
|
||||
if (!canApply || this.canApplyAbility(true)) {
|
||||
|
|
|
@ -6,7 +6,7 @@ import Pokemon from "#app/field/pokemon";
|
|||
import { getPokemonNameWithAffix } from "#app/messages";
|
||||
import { ResetNegativeStatStageModifier } from "#app/modifier/modifier";
|
||||
import { handleTutorial, Tutorial } from "#app/tutorial";
|
||||
import { NumberHolder, BooleanHolder } from "#app/utils";
|
||||
import * as Utils from "#app/utils";
|
||||
import i18next from "i18next";
|
||||
import { PokemonPhase } from "./pokemon-phase";
|
||||
import { Stat, type BattleStat, getStatKey, getStatStageChangeDescriptionKey } from "#enums/stat";
|
||||
|
@ -42,23 +42,17 @@ export class StatStageChangePhase extends PokemonPhase {
|
|||
return this.end();
|
||||
}
|
||||
|
||||
const stages = new NumberHolder(this.stages);
|
||||
|
||||
if (!this.ignoreAbilities) {
|
||||
applyAbAttrs(StatStageChangeMultiplierAbAttr, pokemon, null, false, stages);
|
||||
}
|
||||
|
||||
let simulate = false;
|
||||
|
||||
const filteredStats = this.stats.filter(stat => {
|
||||
const cancelled = new BooleanHolder(false);
|
||||
const cancelled = new Utils.BooleanHolder(false);
|
||||
|
||||
if (!this.selfTarget && stages.value < 0) {
|
||||
if (!this.selfTarget && this.stages < 0) {
|
||||
// TODO: Include simulate boolean when tag applications can be simulated
|
||||
this.scene.arena.applyTagsForSide(MistTag, pokemon.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, cancelled);
|
||||
}
|
||||
|
||||
if (!cancelled.value && !this.selfTarget && stages.value < 0) {
|
||||
if (!cancelled.value && !this.selfTarget && this.stages < 0) {
|
||||
applyPreStatStageChangeAbAttrs(ProtectStatAbAttr, pokemon, stat, cancelled, simulate);
|
||||
}
|
||||
|
||||
|
@ -70,6 +64,12 @@ export class StatStageChangePhase extends PokemonPhase {
|
|||
return !cancelled.value;
|
||||
});
|
||||
|
||||
const stages = new Utils.IntegerHolder(this.stages);
|
||||
|
||||
if (!this.ignoreAbilities) {
|
||||
applyAbAttrs(StatStageChangeMultiplierAbAttr, pokemon, null, false, stages);
|
||||
}
|
||||
|
||||
const relLevels = filteredStats.map(s => (stages.value >= 1 ? Math.min(pokemon.getStatStage(s) + stages.value, 6) : Math.max(pokemon.getStatStage(s) + stages.value, -6)) - pokemon.getStatStage(s));
|
||||
|
||||
this.onChange && this.onChange(this.getPokemon(), filteredStats, relLevels);
|
||||
|
|
|
@ -31,7 +31,7 @@ describe("Abilities - Contrary", () => {
|
|||
});
|
||||
|
||||
it("should invert stat changes when applied", async() => {
|
||||
await game.classicMode.startBattle([
|
||||
await game.startBattle([
|
||||
Species.SLOWBRO
|
||||
]);
|
||||
|
||||
|
@ -39,39 +39,4 @@ describe("Abilities - Contrary", () => {
|
|||
|
||||
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(1);
|
||||
}, 20000);
|
||||
|
||||
describe("With Clear Body", () => {
|
||||
it("should apply positive effects", async () => {
|
||||
game.override
|
||||
.enemyPassiveAbility(Abilities.CLEAR_BODY)
|
||||
.moveset([Moves.TAIL_WHIP]);
|
||||
await game.classicMode.startBattle([Species.SLOWBRO]);
|
||||
|
||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||
|
||||
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(1);
|
||||
|
||||
game.move.select(Moves.TAIL_WHIP);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
|
||||
expect(enemyPokemon.getStatStage(Stat.DEF)).toBe(1);
|
||||
});
|
||||
|
||||
it("should block negative effects", async () => {
|
||||
game.override
|
||||
.enemyPassiveAbility(Abilities.CLEAR_BODY)
|
||||
.enemyMoveset([Moves.HOWL, Moves.HOWL, Moves.HOWL, Moves.HOWL])
|
||||
.moveset([Moves.SPLASH]);
|
||||
await game.classicMode.startBattle([Species.SLOWBRO]);
|
||||
|
||||
const enemyPokemon = game.scene.getEnemyPokemon()!;
|
||||
|
||||
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(1);
|
||||
|
||||
game.move.select(Moves.SPLASH);
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
|
||||
expect(enemyPokemon.getStatStage(Stat.ATK)).toBe(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
10
src/utils.ts
10
src/utils.ts
|
@ -1,5 +1,4 @@
|
|||
import { MoneyFormat } from "#enums/money-format";
|
||||
import { Moves } from "#enums/moves";
|
||||
import i18next from "i18next";
|
||||
|
||||
export const MissingTextureKey = "__MISSING";
|
||||
|
@ -629,12 +628,3 @@ export function getLocalizedSpriteKey(baseKey: string) {
|
|||
export function isBetween(num: number, min: number, max: number): boolean {
|
||||
return num >= min && num <= max;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to return the animation filename for a given move
|
||||
*
|
||||
* @param move the move for which the animation filename is needed
|
||||
*/
|
||||
export function animationFileName(move: Moves): string {
|
||||
return Moves[move].toLowerCase().replace(/\_/g, "-");
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue