[Ability] Stall + Mycelium Might (#3484)
* Implemented Stall * Fixed implementation * AbAttr Name Change * Wrote test for Stall * Update src/test/abilities/stall.test.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Update src/data/ability.ts Co-authored-by: Adrian T. <68144167+torranx@users.noreply.github.com> * Updated ability variables and test * Apply suggestions from code review Co-authored-by: Amani H. <109637146+xsn34kzx@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Amani H. <109637146+xsn34kzx@users.noreply.github.com> * eslint fixes * Update src/test/abilities/stall.test.ts Co-authored-by: Adrian T. <68144167+torranx@users.noreply.github.com> * added documentation and implemented mycelium might * added note on quick claw * Documentation + Quick Claw implementation * This is where I would test quick claw-stall/m.m. if i could override modifierstacks * Forgot to add edits oops --------- Co-authored-by: Frutescens <info@laptop> Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> Co-authored-by: Adrian T. <68144167+torranx@users.noreply.github.com> Co-authored-by: Amani H. <109637146+xsn34kzx@users.noreply.github.com>
This commit is contained in:
parent
73d60f5e6e
commit
513adf779f
|
@ -16,7 +16,7 @@ import { TextStyle, addTextObject, getTextColor } from "./ui/text";
|
|||
import { allMoves } from "./data/move";
|
||||
import { ModifierPoolType, getDefaultModifierTypeForTier, getEnemyModifierTypesForWave, getLuckString, getLuckTextTint, getModifierPoolForType, getModifierType, getPartyLuckValue, modifierTypes } from "./modifier/modifier-type";
|
||||
import AbilityBar from "./ui/ability-bar";
|
||||
import { BlockItemTheftAbAttr, DoubleBattleChanceAbAttr, IncrementMovePriorityAbAttr, PostBattleInitAbAttr, applyAbAttrs, applyPostBattleInitAbAttrs } from "./data/ability";
|
||||
import { BlockItemTheftAbAttr, DoubleBattleChanceAbAttr, ChangeMovePriorityAbAttr, PostBattleInitAbAttr, applyAbAttrs, applyPostBattleInitAbAttrs } from "./data/ability";
|
||||
import { allAbilities } from "./data/ability";
|
||||
import Battle, { BattleType, FixedBattleConfig } from "./battle";
|
||||
import { GameMode, GameModes, getGameMode } from "./game-mode";
|
||||
|
@ -2121,7 +2121,7 @@ export default class BattleScene extends SceneBase {
|
|||
|
||||
pushMovePhase(movePhase: MovePhase, priorityOverride?: integer): void {
|
||||
const movePriority = new Utils.IntegerHolder(priorityOverride !== undefined ? priorityOverride : movePhase.move.getMove().priority);
|
||||
applyAbAttrs(IncrementMovePriorityAbAttr, movePhase.pokemon, null, movePhase.move.getMove(), movePriority);
|
||||
applyAbAttrs(ChangeMovePriorityAbAttr, movePhase.pokemon, null, movePhase.move.getMove(), movePriority);
|
||||
const lowerPriorityPhase = this.phaseQueue.find(p => p instanceof MovePhase && p.move.getMove().priority < movePriority.value);
|
||||
if (lowerPriorityPhase) {
|
||||
this.phaseQueue.splice(this.phaseQueue.indexOf(lowerPriorityPhase), 0, movePhase);
|
||||
|
|
|
@ -525,7 +525,7 @@ export class FieldPriorityMoveImmunityAbAttr extends PreDefendAbAttr {
|
|||
applyPreDefend(pokemon: Pokemon, passive: boolean, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, args: any[]): boolean {
|
||||
const attackPriority = new Utils.IntegerHolder(move.priority);
|
||||
applyMoveAttrs(IncrementMovePriorityAttr,attacker,null,move,attackPriority);
|
||||
applyAbAttrs(IncrementMovePriorityAbAttr, attacker, null, move, attackPriority);
|
||||
applyAbAttrs(ChangeMovePriorityAbAttr, attacker, null, move, attackPriority);
|
||||
|
||||
if (move.moveTarget===MoveTarget.USER || move.moveTarget===MoveTarget.NEAR_ALLY) {
|
||||
return false;
|
||||
|
@ -2682,23 +2682,32 @@ export class BlockOneHitKOAbAttr extends AbAttr {
|
|||
}
|
||||
}
|
||||
|
||||
export class IncrementMovePriorityAbAttr extends AbAttr {
|
||||
private moveIncrementFunc: (pokemon: Pokemon, move: Move) => boolean;
|
||||
private increaseAmount: integer;
|
||||
/**
|
||||
* This governs abilities that alter the priority of moves
|
||||
* Abilities: Prankster, Gale Wings, Triage, Mycelium Might, Stall
|
||||
* Note - Quick Claw has a separate and distinct implementation outside of priority
|
||||
*/
|
||||
export class ChangeMovePriorityAbAttr extends AbAttr {
|
||||
private moveFunc: (pokemon: Pokemon, move: Move) => boolean;
|
||||
private changeAmount: number;
|
||||
|
||||
constructor(moveIncrementFunc: (pokemon: Pokemon, move: Move) => boolean, increaseAmount = 1) {
|
||||
/**
|
||||
* @param {(pokemon, move) => boolean} moveFunc applies priority-change to moves within a provided category
|
||||
* @param {number} changeAmount the amount of priority added or subtracted
|
||||
*/
|
||||
constructor(moveFunc: (pokemon: Pokemon, move: Move) => boolean, changeAmount: number) {
|
||||
super(true);
|
||||
|
||||
this.moveIncrementFunc = moveIncrementFunc;
|
||||
this.increaseAmount = increaseAmount;
|
||||
this.moveFunc = moveFunc;
|
||||
this.changeAmount = changeAmount;
|
||||
}
|
||||
|
||||
apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean {
|
||||
if (!this.moveIncrementFunc(pokemon, args[0] as Move)) {
|
||||
if (!this.moveFunc(pokemon, args[0] as Move)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
(args[1] as Utils.IntegerHolder).value += this.increaseAmount;
|
||||
(args[1] as Utils.IntegerHolder).value += this.changeAmount;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -4092,6 +4101,41 @@ export class BypassSpeedChanceAbAttr extends AbAttr {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This attribute checks if a Pokemon's move meets a provided condition to determine if the Pokemon can use Quick Claw
|
||||
* It was created because Pokemon with the ability Mycelium Might cannot access Quick Claw's benefits when using status moves.
|
||||
*/
|
||||
export class PreventBypassSpeedChanceAbAttr extends AbAttr {
|
||||
private condition: ((pokemon: Pokemon, move: Move) => boolean);
|
||||
|
||||
/**
|
||||
* @param {function} condition - checks if a move meets certain conditions
|
||||
*/
|
||||
constructor(condition: (pokemon: Pokemon, move: Move) => boolean) {
|
||||
super(true);
|
||||
this.condition = condition;
|
||||
}
|
||||
|
||||
/**
|
||||
* @argument {boolean} bypassSpeed - determines if a Pokemon is able to bypass speed at the moment
|
||||
* @argument {boolean} canCheckHeldItems - determines if a Pokemon has access to Quick Claw's effects or not
|
||||
*/
|
||||
apply(pokemon: Pokemon, passive: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean {
|
||||
const bypassSpeed = args[0] as Utils.BooleanHolder;
|
||||
const canCheckHeldItems = args[1] as Utils.BooleanHolder;
|
||||
|
||||
const turnCommand = pokemon.scene.currentBattle.turnCommands[pokemon.getBattlerIndex()];
|
||||
const isCommandFight = turnCommand?.command === Command.FIGHT;
|
||||
const move = turnCommand?.move?.move ? allMoves[turnCommand.move.move] : null;
|
||||
if (this.condition(pokemon, move!) && isCommandFight) {
|
||||
bypassSpeed.value = false;
|
||||
canCheckHeldItems.value = false;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
async function applyAbAttrsInternal<TAttr extends AbAttr>(
|
||||
attrType: Constructor<TAttr>,
|
||||
pokemon: Pokemon | null,
|
||||
|
@ -4613,7 +4657,7 @@ export function initAbilities() {
|
|||
.attr(AlwaysHitAbAttr)
|
||||
.attr(DoubleBattleChanceAbAttr),
|
||||
new Ability(Abilities.STALL, 4)
|
||||
.unimplemented(),
|
||||
.attr(ChangeMovePriorityAbAttr, (pokemon, move: Move) => true, -0.5),
|
||||
new Ability(Abilities.TECHNICIAN, 4)
|
||||
.attr(MovePowerBoostAbAttr, (user, target, move) => {
|
||||
const power = new Utils.NumberHolder(move.power);
|
||||
|
@ -4790,7 +4834,7 @@ export function initAbilities() {
|
|||
.attr(TypeImmunityStatChangeAbAttr, Type.GRASS, BattleStat.ATK, 1)
|
||||
.ignorable(),
|
||||
new Ability(Abilities.PRANKSTER, 5)
|
||||
.attr(IncrementMovePriorityAbAttr, (pokemon, move: Move) => move.category === MoveCategory.STATUS),
|
||||
.attr(ChangeMovePriorityAbAttr, (pokemon, move: Move) => move.category === MoveCategory.STATUS, 1),
|
||||
new Ability(Abilities.SAND_FORCE, 5)
|
||||
.attr(MoveTypePowerBoostAbAttr, Type.ROCK, 1.3)
|
||||
.attr(MoveTypePowerBoostAbAttr, Type.GROUND, 1.3)
|
||||
|
@ -4855,7 +4899,7 @@ export function initAbilities() {
|
|||
.attr(UnsuppressableAbilityAbAttr)
|
||||
.attr(NoFusionAbilityAbAttr),
|
||||
new Ability(Abilities.GALE_WINGS, 6)
|
||||
.attr(IncrementMovePriorityAbAttr, (pokemon, move) => pokemon.isFullHp() && move.type === Type.FLYING),
|
||||
.attr(ChangeMovePriorityAbAttr, (pokemon, move) => pokemon.isFullHp() && move.type === Type.FLYING, 1),
|
||||
new Ability(Abilities.MEGA_LAUNCHER, 6)
|
||||
.attr(MovePowerBoostAbAttr, (user, target, move) => move.hasFlag(MoveFlags.PULSE_MOVE), 1.5),
|
||||
new Ability(Abilities.GRASS_PELT, 6)
|
||||
|
@ -4944,7 +4988,7 @@ export function initAbilities() {
|
|||
new Ability(Abilities.LIQUID_VOICE, 7)
|
||||
.attr(MoveTypeChangeAttr, Type.WATER, 1, (user, target, move) => move.hasFlag(MoveFlags.SOUND_BASED)),
|
||||
new Ability(Abilities.TRIAGE, 7)
|
||||
.attr(IncrementMovePriorityAbAttr, (pokemon, move) => move.hasFlag(MoveFlags.TRIAGE_MOVE), 3),
|
||||
.attr(ChangeMovePriorityAbAttr, (pokemon, move) => move.hasFlag(MoveFlags.TRIAGE_MOVE), 3),
|
||||
new Ability(Abilities.GALVANIZE, 7)
|
||||
.attr(MoveTypeChangeAttr, Type.ELECTRIC, 1.2, (user, target, move) => move.type === Type.NORMAL),
|
||||
new Ability(Abilities.SURGE_SURFER, 7)
|
||||
|
@ -5299,8 +5343,9 @@ export function initAbilities() {
|
|||
.partial() // Healing not blocked by Heal Block
|
||||
.ignorable(),
|
||||
new Ability(Abilities.MYCELIUM_MIGHT, 9)
|
||||
.attr(MoveAbilityBypassAbAttr, (pokemon, move: Move) => move.category === MoveCategory.STATUS)
|
||||
.partial(),
|
||||
.attr(ChangeMovePriorityAbAttr, (pokemon, move) => move.category === MoveCategory.STATUS, -0.5)
|
||||
.attr(PreventBypassSpeedChanceAbAttr, (pokemon, move) => move.category === MoveCategory.STATUS)
|
||||
.attr(MoveAbilityBypassAbAttr, (pokemon, move: Move) => move.category === MoveCategory.STATUS),
|
||||
new Ability(Abilities.MINDS_EYE, 9)
|
||||
.attr(IgnoreTypeImmunityAbAttr, Type.GHOST, [Type.NORMAL, Type.FIGHTING])
|
||||
.attr(ProtectStatAbAttr, BattleStat.ACC)
|
||||
|
|
|
@ -2,7 +2,7 @@ import Pokemon from "../field/pokemon";
|
|||
import Move from "./move";
|
||||
import { Type } from "./type";
|
||||
import * as Utils from "../utils";
|
||||
import { IncrementMovePriorityAbAttr, applyAbAttrs } from "./ability";
|
||||
import { ChangeMovePriorityAbAttr, applyAbAttrs } from "./ability";
|
||||
import { ProtectAttr } from "./move";
|
||||
import { BattlerIndex } from "#app/battle.js";
|
||||
import i18next from "i18next";
|
||||
|
@ -59,7 +59,7 @@ export class Terrain {
|
|||
case TerrainType.PSYCHIC:
|
||||
if (!move.hasAttr(ProtectAttr)) {
|
||||
const priority = new Utils.IntegerHolder(move.priority);
|
||||
applyAbAttrs(IncrementMovePriorityAbAttr, user, null, move, priority);
|
||||
applyAbAttrs(ChangeMovePriorityAbAttr, user, null, move, priority);
|
||||
// Cancels move if the move has positive priority and targets a Pokemon grounded on the Psychic Terrain
|
||||
return priority.value > 0 && user.getOpponents().some(o => targets.includes(o.getBattlerIndex()) && o.isGrounded());
|
||||
}
|
||||
|
|
|
@ -25,7 +25,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, StatChangeMultiplierAbAttr, SuppressWeatherEffectAbAttr, SyncEncounterNatureAbAttr, applyAbAttrs, applyCheckTrappedAbAttrs, applyPostAttackAbAttrs, applyPostBattleAbAttrs, applyPostDefendAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreSwitchOutAbAttrs, applyPreWeatherEffectAbAttrs, ChangeMovePriorityAbAttr, applyPostVictoryAbAttrs, PostVictoryAbAttr, BlockNonDirectDamageAbAttr as BlockNonDirectDamageAbAttr, applyPostKnockOutAbAttrs, PostKnockOutAbAttr, PostBiomeChangeAbAttr, PreventBypassSpeedChanceAbAttr, applyPostFaintAbAttrs, PostFaintAbAttr, IncreasePpAbAttr, PostStatChangeAbAttr, applyPostStatChangeAbAttrs, AlwaysHitAbAttr, PreventBerryUseAbAttr, StatChangeCopyAbAttr, 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";
|
||||
|
@ -2315,8 +2315,12 @@ export class TurnStartPhase extends FieldPhase {
|
|||
|
||||
this.scene.getField(true).filter(p => p.summonData).map(p => {
|
||||
const bypassSpeed = new Utils.BooleanHolder(false);
|
||||
const canCheckHeldItems = new Utils.BooleanHolder(true);
|
||||
applyAbAttrs(BypassSpeedChanceAbAttr, p, null, bypassSpeed);
|
||||
this.scene.applyModifiers(BypassSpeedChanceModifier, p.isPlayer(), p, bypassSpeed);
|
||||
applyAbAttrs(PreventBypassSpeedChanceAbAttr, p, null, bypassSpeed, canCheckHeldItems);
|
||||
if (canCheckHeldItems.value) {
|
||||
this.scene.applyModifiers(BypassSpeedChanceModifier, p.isPlayer(), p, bypassSpeed);
|
||||
}
|
||||
battlerBypassSpeed[p.getBattlerIndex()] = bypassSpeed;
|
||||
});
|
||||
|
||||
|
@ -2342,10 +2346,15 @@ export class TurnStartPhase extends FieldPhase {
|
|||
applyMoveAttrs(IncrementMovePriorityAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === a)!, null, aMove, aPriority); //TODO: is the bang correct here?
|
||||
applyMoveAttrs(IncrementMovePriorityAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === b)!, null, bMove, bPriority); //TODO: is the bang correct here?
|
||||
|
||||
applyAbAttrs(IncrementMovePriorityAbAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === a)!, null, aMove, aPriority); //TODO: is the bang correct here?
|
||||
applyAbAttrs(IncrementMovePriorityAbAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === b)!, null, bMove, bPriority); //TODO: is the bang correct here?
|
||||
applyAbAttrs(ChangeMovePriorityAbAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === a)!, null, aMove, aPriority); //TODO: is the bang correct here?
|
||||
applyAbAttrs(ChangeMovePriorityAbAttr, this.scene.getField().find(p => p?.isActive() && p.getBattlerIndex() === b)!, null, bMove, bPriority); //TODO: is the bang correct here?
|
||||
|
||||
if (aPriority.value !== bPriority.value) {
|
||||
const bracketDifference = Math.ceil(aPriority.value) - Math.ceil(bPriority.value);
|
||||
const hasSpeedDifference = battlerBypassSpeed[a].value !== battlerBypassSpeed[b].value;
|
||||
if (bracketDifference === 0 && hasSpeedDifference) {
|
||||
return battlerBypassSpeed[a].value ? -1 : 1;
|
||||
}
|
||||
return aPriority.value < bPriority.value ? 1 : -1;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
import { MovePhase, TurnEndPhase } from "#app/phases";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import { getMovePosition } from "#test/utils/gameManagerUtils";
|
||||
import { Abilities } from "#enums/abilities";
|
||||
import { BattleStat } from "#app/data/battle-stat";
|
||||
import { Moves } from "#enums/moves";
|
||||
import { Species } from "#enums/species";
|
||||
import Phaser from "phaser";
|
||||
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||
|
||||
|
||||
describe("Abilities - Mycelium Might", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override.battleType("single");
|
||||
game.override.disableCrits();
|
||||
game.override.enemySpecies(Species.SHUCKLE);
|
||||
game.override.enemyAbility(Abilities.CLEAR_BODY);
|
||||
game.override.enemyMoveset([Moves.QUICK_ATTACK, Moves.QUICK_ATTACK, Moves.QUICK_ATTACK, Moves.QUICK_ATTACK]);
|
||||
game.override.ability(Abilities.MYCELIUM_MIGHT);
|
||||
game.override.moveset([Moves.QUICK_ATTACK, Moves.BABY_DOLL_EYES]);
|
||||
});
|
||||
|
||||
/**
|
||||
* Bulbapedia References:
|
||||
* https://bulbapedia.bulbagarden.net/wiki/Mycelium_Might_(Ability)
|
||||
* https://bulbapedia.bulbagarden.net/wiki/Priority
|
||||
* https://www.smogon.com/forums/threads/scarlet-violet-battle-mechanics-research.3709545/page-24
|
||||
**/
|
||||
|
||||
it("If a Pokemon with Mycelium Might uses a status move, it will always move last but the status move will ignore protective abilities", async() => {
|
||||
await game.startBattle([ Species.REGIELEKI ]);
|
||||
|
||||
const leadIndex = game.scene.getPlayerPokemon()!.getBattlerIndex();
|
||||
const enemyPokemon = game.scene.getEnemyPokemon();
|
||||
const enemyIndex = enemyPokemon?.getBattlerIndex();
|
||||
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.BABY_DOLL_EYES));
|
||||
|
||||
await game.phaseInterceptor.to(MovePhase, false);
|
||||
// The opponent Pokemon (without Mycelium Might) goes first despite having lower speed than the player Pokemon.
|
||||
expect((game.scene.getCurrentPhase() as MovePhase).pokemon.getBattlerIndex()).toBe(enemyIndex);
|
||||
|
||||
await game.phaseInterceptor.run(MovePhase);
|
||||
await game.phaseInterceptor.to(MovePhase, false);
|
||||
|
||||
// The player Pokemon (with Mycelium Might) goes last despite having higher speed than the opponent.
|
||||
expect((game.scene.getCurrentPhase() as MovePhase).pokemon.getBattlerIndex()).toBe(leadIndex);
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
expect(enemyPokemon?.summonData.battleStats[BattleStat.ATK]).toBe(-1);
|
||||
}, 20000);
|
||||
|
||||
it("Pokemon with Mycelium Might will go first if a status move that is in a higher priority bracket than the opponent's move is used", async() => {
|
||||
game.override.enemyMoveset([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE]);
|
||||
await game.startBattle([ Species.REGIELEKI ]);
|
||||
|
||||
const leadIndex = game.scene.getPlayerPokemon()!.getBattlerIndex();
|
||||
const enemyPokemon = game.scene.getEnemyPokemon();
|
||||
const enemyIndex = enemyPokemon?.getBattlerIndex();
|
||||
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.BABY_DOLL_EYES));
|
||||
|
||||
await game.phaseInterceptor.to(MovePhase, false);
|
||||
// The player Pokemon (with M.M.) goes first because its move is still within a higher priority bracket than its opponent.
|
||||
expect((game.scene.getCurrentPhase() as MovePhase).pokemon.getBattlerIndex()).toBe(leadIndex);
|
||||
|
||||
await game.phaseInterceptor.run(MovePhase);
|
||||
await game.phaseInterceptor.to(MovePhase, false);
|
||||
// The enemy Pokemon goes second because its move is in a lower priority bracket.
|
||||
expect((game.scene.getCurrentPhase() as MovePhase).pokemon.getBattlerIndex()).toBe(enemyIndex);
|
||||
await game.phaseInterceptor.to(TurnEndPhase);
|
||||
expect(enemyPokemon?.summonData.battleStats[BattleStat.ATK]).toBe(-1);
|
||||
}, 20000);
|
||||
|
||||
it("Order is established normally if the Pokemon uses a non-status move", async() => {
|
||||
await game.startBattle([ Species.REGIELEKI ]);
|
||||
|
||||
const leadIndex = game.scene.getPlayerPokemon()!.getBattlerIndex();
|
||||
const enemyIndex = game.scene.getEnemyPokemon()!.getBattlerIndex();
|
||||
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.QUICK_ATTACK));
|
||||
|
||||
await game.phaseInterceptor.to(MovePhase, false);
|
||||
// The player Pokemon (with M.M.) goes first because it has a higher speed and did not use a status move.
|
||||
expect((game.scene.getCurrentPhase() as MovePhase).pokemon.getBattlerIndex()).toBe(leadIndex);
|
||||
|
||||
await game.phaseInterceptor.run(MovePhase);
|
||||
await game.phaseInterceptor.to(MovePhase, false);
|
||||
// The enemy Pokemon (without M.M.) goes second because its speed is lower.
|
||||
expect((game.scene.getCurrentPhase() as MovePhase).pokemon.getBattlerIndex()).toBe(enemyIndex);
|
||||
}, 20000);
|
||||
});
|
|
@ -0,0 +1,95 @@
|
|||
import { MovePhase } from "#app/phases";
|
||||
import GameManager from "#test/utils/gameManager";
|
||||
import { getMovePosition } from "#test/utils/gameManagerUtils";
|
||||
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";
|
||||
|
||||
|
||||
describe("Abilities - Stall", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override.battleType("single");
|
||||
game.override.disableCrits();
|
||||
game.override.enemySpecies(Species.REGIELEKI);
|
||||
game.override.enemyAbility(Abilities.STALL);
|
||||
game.override.enemyMoveset([Moves.QUICK_ATTACK, Moves.QUICK_ATTACK, Moves.QUICK_ATTACK, Moves.QUICK_ATTACK]);
|
||||
game.override.moveset([Moves.QUICK_ATTACK, Moves.TACKLE]);
|
||||
});
|
||||
|
||||
/**
|
||||
* Bulbapedia References:
|
||||
* https://bulbapedia.bulbagarden.net/wiki/Stall_(Ability)
|
||||
* https://bulbapedia.bulbagarden.net/wiki/Priority
|
||||
**/
|
||||
|
||||
it("Pokemon with Stall should move last in its priority bracket regardless of speed", async() => {
|
||||
await game.startBattle([ Species.SHUCKLE ]);
|
||||
|
||||
const leadIndex = game.scene.getPlayerPokemon()!.getBattlerIndex();
|
||||
const enemyIndex = game.scene.getEnemyPokemon()!.getBattlerIndex();
|
||||
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.QUICK_ATTACK));
|
||||
|
||||
await game.phaseInterceptor.to(MovePhase, false);
|
||||
// The player Pokemon (without Stall) goes first despite having lower speed than the opponent.
|
||||
expect((game.scene.getCurrentPhase() as MovePhase).pokemon.getBattlerIndex()).toBe(leadIndex);
|
||||
|
||||
await game.phaseInterceptor.run(MovePhase);
|
||||
await game.phaseInterceptor.to(MovePhase, false);
|
||||
// The opponent Pokemon (with Stall) goes last despite having higher speed than the player Pokemon.
|
||||
expect((game.scene.getCurrentPhase() as MovePhase).pokemon.getBattlerIndex()).toBe(enemyIndex);
|
||||
}, 20000);
|
||||
|
||||
it("Pokemon with Stall will go first if a move that is in a higher priority bracket than the opponent's move is used", async() => {
|
||||
await game.startBattle([ Species.SHUCKLE ]);
|
||||
|
||||
const leadIndex = game.scene.getPlayerPokemon()!.getBattlerIndex();
|
||||
const enemyIndex = game.scene.getEnemyPokemon()!.getBattlerIndex();
|
||||
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE));
|
||||
|
||||
await game.phaseInterceptor.to(MovePhase, false);
|
||||
// The opponent Pokemon (with Stall) goes first because its move is still within a higher priority bracket than its opponent.
|
||||
expect((game.scene.getCurrentPhase() as MovePhase).pokemon.getBattlerIndex()).toBe(enemyIndex);
|
||||
|
||||
await game.phaseInterceptor.run(MovePhase);
|
||||
await game.phaseInterceptor.to(MovePhase, false);
|
||||
// The player Pokemon goes second because its move is in a lower priority bracket.
|
||||
expect((game.scene.getCurrentPhase() as MovePhase).pokemon.getBattlerIndex()).toBe(leadIndex);
|
||||
}, 20000);
|
||||
|
||||
it("If both Pokemon have stall and use the same move, speed is used to determine who goes first.", async() => {
|
||||
game.override.ability(Abilities.STALL);
|
||||
await game.startBattle([ Species.SHUCKLE ]);
|
||||
|
||||
const leadIndex = game.scene.getPlayerPokemon()!.getBattlerIndex();
|
||||
const enemyIndex = game.scene.getEnemyPokemon()!.getBattlerIndex();
|
||||
|
||||
game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE));
|
||||
|
||||
await game.phaseInterceptor.to(MovePhase, false);
|
||||
// The opponent Pokemon (with Stall) goes first because it has a higher speed.
|
||||
expect((game.scene.getCurrentPhase() as MovePhase).pokemon.getBattlerIndex()).toBe(enemyIndex);
|
||||
|
||||
await game.phaseInterceptor.run(MovePhase);
|
||||
await game.phaseInterceptor.to(MovePhase, false);
|
||||
// The player Pokemon (with Stall) goes second because its speed is lower.
|
||||
expect((game.scene.getCurrentPhase() as MovePhase).pokemon.getBattlerIndex()).toBe(leadIndex);
|
||||
}, 20000);
|
||||
});
|
Loading…
Reference in New Issue