mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2024-11-25 16:26:25 +00:00
[P2] Fix first-turn status damage and arena hazards (#3528)
* [Bug] Toxic Spikes implementation issues fixed Adjusted MoveEffectPhase.start() so that ENEMY_SIDE targeted moves no longer occur twice per use in double battles. Updated Toxic Orb test to no longer expect a tick of damage turn 1. Fixed Toxic/Poison dealing damage immediately when applied. Fixed Hazards not persisting through save Added unit tests Fixed flyout not displaying correct number of Spikes/Toxic Spikes after a refresh * Update Toxic Orb test * Updates Toxic Spikes tests * Apply suggestions from code review * Fix merge issues Replace `integer` with `number` in `arena-tag.ts` * Remove partial Magic Bounce implementation * Remove stray newline * Remove extra change in safeguard test --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
This commit is contained in:
parent
4f456339f4
commit
70b9a43c8b
@ -28,20 +28,13 @@ export enum ArenaTagSide {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export abstract class ArenaTag {
|
export abstract class ArenaTag {
|
||||||
public tagType: ArenaTagType;
|
constructor(
|
||||||
public turnCount: integer;
|
public tagType: ArenaTagType,
|
||||||
public sourceMove?: Moves;
|
public turnCount: number,
|
||||||
public sourceId?: integer;
|
public sourceMove?: Moves,
|
||||||
public side: ArenaTagSide;
|
public sourceId?: number,
|
||||||
|
public side: ArenaTagSide = ArenaTagSide.BOTH
|
||||||
|
) {}
|
||||||
constructor(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves | undefined, sourceId?: integer, side: ArenaTagSide = ArenaTagSide.BOTH) {
|
|
||||||
this.tagType = tagType;
|
|
||||||
this.turnCount = turnCount;
|
|
||||||
this.sourceMove = sourceMove;
|
|
||||||
this.sourceId = sourceId;
|
|
||||||
this.side = side;
|
|
||||||
}
|
|
||||||
|
|
||||||
apply(arena: Arena, args: any[]): boolean {
|
apply(arena: Arena, args: any[]): boolean {
|
||||||
return true;
|
return true;
|
||||||
@ -66,6 +59,18 @@ export abstract class ArenaTag {
|
|||||||
? allMoves[this.sourceMove].name
|
? allMoves[this.sourceMove].name
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When given a arena tag or json representing one, load the data for it.
|
||||||
|
* This is meant to be inherited from by any arena tag with custom attributes
|
||||||
|
* @param {ArenaTag | any} source An arena tag
|
||||||
|
*/
|
||||||
|
loadTag(source : ArenaTag | any) : void {
|
||||||
|
this.turnCount = source.turnCount;
|
||||||
|
this.sourceMove = source.sourceMove;
|
||||||
|
this.sourceId = source.sourceId;
|
||||||
|
this.side = source.side;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -73,7 +78,7 @@ export abstract class ArenaTag {
|
|||||||
* Prevents Pokémon on the opposing side from lowering the stats of the Pokémon in the Mist.
|
* Prevents Pokémon on the opposing side from lowering the stats of the Pokémon in the Mist.
|
||||||
*/
|
*/
|
||||||
export class MistTag extends ArenaTag {
|
export class MistTag extends ArenaTag {
|
||||||
constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) {
|
constructor(turnCount: number, sourceId: number, side: ArenaTagSide) {
|
||||||
super(ArenaTagType.MIST, turnCount, Moves.MIST, sourceId, side);
|
super(ArenaTagType.MIST, turnCount, Moves.MIST, sourceId, side);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,7 +122,7 @@ export class WeakenMoveScreenTag extends ArenaTag {
|
|||||||
* @param side - The side (player or enemy) the tag affects.
|
* @param side - The side (player or enemy) the tag affects.
|
||||||
* @param weakenedCategories - The categories of moves that are weakened by this tag.
|
* @param weakenedCategories - The categories of moves that are weakened by this tag.
|
||||||
*/
|
*/
|
||||||
constructor(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves, sourceId: integer, side: ArenaTagSide, weakenedCategories: MoveCategory[]) {
|
constructor(tagType: ArenaTagType, turnCount: number, sourceMove: Moves, sourceId: number, side: ArenaTagSide, weakenedCategories: MoveCategory[]) {
|
||||||
super(tagType, turnCount, sourceMove, sourceId, side);
|
super(tagType, turnCount, sourceMove, sourceId, side);
|
||||||
|
|
||||||
this.weakenedCategories = weakenedCategories;
|
this.weakenedCategories = weakenedCategories;
|
||||||
@ -148,7 +153,7 @@ export class WeakenMoveScreenTag extends ArenaTag {
|
|||||||
* Used by {@linkcode Moves.REFLECT}
|
* Used by {@linkcode Moves.REFLECT}
|
||||||
*/
|
*/
|
||||||
class ReflectTag extends WeakenMoveScreenTag {
|
class ReflectTag extends WeakenMoveScreenTag {
|
||||||
constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) {
|
constructor(turnCount: number, sourceId: number, side: ArenaTagSide) {
|
||||||
super(ArenaTagType.REFLECT, turnCount, Moves.REFLECT, sourceId, side, [ MoveCategory.PHYSICAL ]);
|
super(ArenaTagType.REFLECT, turnCount, Moves.REFLECT, sourceId, side, [ MoveCategory.PHYSICAL ]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,7 +169,7 @@ class ReflectTag extends WeakenMoveScreenTag {
|
|||||||
* Used by {@linkcode Moves.LIGHT_SCREEN}
|
* Used by {@linkcode Moves.LIGHT_SCREEN}
|
||||||
*/
|
*/
|
||||||
class LightScreenTag extends WeakenMoveScreenTag {
|
class LightScreenTag extends WeakenMoveScreenTag {
|
||||||
constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) {
|
constructor(turnCount: number, sourceId: number, side: ArenaTagSide) {
|
||||||
super(ArenaTagType.LIGHT_SCREEN, turnCount, Moves.LIGHT_SCREEN, sourceId, side, [ MoveCategory.SPECIAL ]);
|
super(ArenaTagType.LIGHT_SCREEN, turnCount, Moves.LIGHT_SCREEN, sourceId, side, [ MoveCategory.SPECIAL ]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,7 +185,7 @@ class LightScreenTag extends WeakenMoveScreenTag {
|
|||||||
* Used by {@linkcode Moves.AURORA_VEIL}
|
* Used by {@linkcode Moves.AURORA_VEIL}
|
||||||
*/
|
*/
|
||||||
class AuroraVeilTag extends WeakenMoveScreenTag {
|
class AuroraVeilTag extends WeakenMoveScreenTag {
|
||||||
constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) {
|
constructor(turnCount: number, sourceId: number, side: ArenaTagSide) {
|
||||||
super(ArenaTagType.AURORA_VEIL, turnCount, Moves.AURORA_VEIL, sourceId, side, [ MoveCategory.SPECIAL, MoveCategory.PHYSICAL ]);
|
super(ArenaTagType.AURORA_VEIL, turnCount, Moves.AURORA_VEIL, sourceId, side, [ MoveCategory.SPECIAL, MoveCategory.PHYSICAL ]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,7 +208,7 @@ export class ConditionalProtectTag extends ArenaTag {
|
|||||||
/** Does this apply to all moves, including those that ignore other forms of protection? */
|
/** Does this apply to all moves, including those that ignore other forms of protection? */
|
||||||
protected ignoresBypass: boolean;
|
protected ignoresBypass: boolean;
|
||||||
|
|
||||||
constructor(tagType: ArenaTagType, sourceMove: Moves, sourceId: integer, side: ArenaTagSide, condition: ProtectConditionFunc, ignoresBypass: boolean = false) {
|
constructor(tagType: ArenaTagType, sourceMove: Moves, sourceId: number, side: ArenaTagSide, condition: ProtectConditionFunc, ignoresBypass: boolean = false) {
|
||||||
super(tagType, 1, sourceMove, sourceId, side);
|
super(tagType, 1, sourceMove, sourceId, side);
|
||||||
|
|
||||||
this.protectConditionFunc = condition;
|
this.protectConditionFunc = condition;
|
||||||
@ -265,7 +270,7 @@ export class ConditionalProtectTag extends ArenaTag {
|
|||||||
*/
|
*/
|
||||||
const QuickGuardConditionFunc: ProtectConditionFunc = (arena, moveId) => {
|
const QuickGuardConditionFunc: ProtectConditionFunc = (arena, moveId) => {
|
||||||
const move = allMoves[moveId];
|
const move = allMoves[moveId];
|
||||||
const priority = new Utils.IntegerHolder(move.priority);
|
const priority = new Utils.NumberHolder(move.priority);
|
||||||
const effectPhase = arena.scene.getCurrentPhase();
|
const effectPhase = arena.scene.getCurrentPhase();
|
||||||
|
|
||||||
if (effectPhase instanceof MoveEffectPhase) {
|
if (effectPhase instanceof MoveEffectPhase) {
|
||||||
@ -281,7 +286,7 @@ const QuickGuardConditionFunc: ProtectConditionFunc = (arena, moveId) => {
|
|||||||
* Condition: The incoming move has increased priority.
|
* Condition: The incoming move has increased priority.
|
||||||
*/
|
*/
|
||||||
class QuickGuardTag extends ConditionalProtectTag {
|
class QuickGuardTag extends ConditionalProtectTag {
|
||||||
constructor(sourceId: integer, side: ArenaTagSide) {
|
constructor(sourceId: number, side: ArenaTagSide) {
|
||||||
super(ArenaTagType.QUICK_GUARD, Moves.QUICK_GUARD, sourceId, side, QuickGuardConditionFunc);
|
super(ArenaTagType.QUICK_GUARD, Moves.QUICK_GUARD, sourceId, side, QuickGuardConditionFunc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -312,7 +317,7 @@ const WideGuardConditionFunc: ProtectConditionFunc = (arena, moveId) : boolean =
|
|||||||
* can be an ally or enemy.
|
* can be an ally or enemy.
|
||||||
*/
|
*/
|
||||||
class WideGuardTag extends ConditionalProtectTag {
|
class WideGuardTag extends ConditionalProtectTag {
|
||||||
constructor(sourceId: integer, side: ArenaTagSide) {
|
constructor(sourceId: number, side: ArenaTagSide) {
|
||||||
super(ArenaTagType.WIDE_GUARD, Moves.WIDE_GUARD, sourceId, side, WideGuardConditionFunc);
|
super(ArenaTagType.WIDE_GUARD, Moves.WIDE_GUARD, sourceId, side, WideGuardConditionFunc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -334,7 +339,7 @@ const MatBlockConditionFunc: ProtectConditionFunc = (arena, moveId) : boolean =>
|
|||||||
* Condition: The incoming move is a Physical or Special attack move.
|
* Condition: The incoming move is a Physical or Special attack move.
|
||||||
*/
|
*/
|
||||||
class MatBlockTag extends ConditionalProtectTag {
|
class MatBlockTag extends ConditionalProtectTag {
|
||||||
constructor(sourceId: integer, side: ArenaTagSide) {
|
constructor(sourceId: number, side: ArenaTagSide) {
|
||||||
super(ArenaTagType.MAT_BLOCK, Moves.MAT_BLOCK, sourceId, side, MatBlockConditionFunc);
|
super(ArenaTagType.MAT_BLOCK, Moves.MAT_BLOCK, sourceId, side, MatBlockConditionFunc);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -372,7 +377,7 @@ const CraftyShieldConditionFunc: ProtectConditionFunc = (arena, moveId) => {
|
|||||||
* not target all Pokemon or sides of the field.
|
* not target all Pokemon or sides of the field.
|
||||||
*/
|
*/
|
||||||
class CraftyShieldTag extends ConditionalProtectTag {
|
class CraftyShieldTag extends ConditionalProtectTag {
|
||||||
constructor(sourceId: integer, side: ArenaTagSide) {
|
constructor(sourceId: number, side: ArenaTagSide) {
|
||||||
super(ArenaTagType.CRAFTY_SHIELD, Moves.CRAFTY_SHIELD, sourceId, side, CraftyShieldConditionFunc, true);
|
super(ArenaTagType.CRAFTY_SHIELD, Moves.CRAFTY_SHIELD, sourceId, side, CraftyShieldConditionFunc, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -384,12 +389,12 @@ class CraftyShieldTag extends ConditionalProtectTag {
|
|||||||
export class NoCritTag extends ArenaTag {
|
export class NoCritTag extends ArenaTag {
|
||||||
/**
|
/**
|
||||||
* Constructor method for the NoCritTag class
|
* Constructor method for the NoCritTag class
|
||||||
* @param turnCount `integer` the number of turns this effect lasts
|
* @param turnCount `number` the number of turns this effect lasts
|
||||||
* @param sourceMove {@linkcode Moves} the move that created this effect
|
* @param sourceMove {@linkcode Moves} the move that created this effect
|
||||||
* @param sourceId `integer` the ID of the {@linkcode Pokemon} that created this effect
|
* @param sourceId `number` the ID of the {@linkcode Pokemon} that created this effect
|
||||||
* @param side {@linkcode ArenaTagSide} the side to which this effect belongs
|
* @param side {@linkcode ArenaTagSide} the side to which this effect belongs
|
||||||
*/
|
*/
|
||||||
constructor(turnCount: integer, sourceMove: Moves, sourceId: integer, side: ArenaTagSide) {
|
constructor(turnCount: number, sourceMove: Moves, sourceId: number, side: ArenaTagSide) {
|
||||||
super(ArenaTagType.NO_CRIT, turnCount, sourceMove, sourceId, side);
|
super(ArenaTagType.NO_CRIT, turnCount, sourceMove, sourceId, side);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -419,7 +424,7 @@ class WishTag extends ArenaTag {
|
|||||||
private triggerMessage: string;
|
private triggerMessage: string;
|
||||||
private healHp: number;
|
private healHp: number;
|
||||||
|
|
||||||
constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) {
|
constructor(turnCount: number, sourceId: number, side: ArenaTagSide) {
|
||||||
super(ArenaTagType.WISH, turnCount, Moves.WISH, sourceId, side);
|
super(ArenaTagType.WISH, turnCount, Moves.WISH, sourceId, side);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -460,7 +465,7 @@ export class WeakenMoveTypeTag extends ArenaTag {
|
|||||||
* @param sourceMove - The move that created the tag.
|
* @param sourceMove - The move that created the tag.
|
||||||
* @param sourceId - The ID of the source of the tag.
|
* @param sourceId - The ID of the source of the tag.
|
||||||
*/
|
*/
|
||||||
constructor(tagType: ArenaTagType, turnCount: integer, type: Type, sourceMove: Moves, sourceId: integer) {
|
constructor(tagType: ArenaTagType, turnCount: number, type: Type, sourceMove: Moves, sourceId: number) {
|
||||||
super(tagType, turnCount, sourceMove, sourceId);
|
super(tagType, turnCount, sourceMove, sourceId);
|
||||||
|
|
||||||
this.weakenedType = type;
|
this.weakenedType = type;
|
||||||
@ -481,7 +486,7 @@ export class WeakenMoveTypeTag extends ArenaTag {
|
|||||||
* Weakens Electric type moves for a set amount of turns, usually 5.
|
* Weakens Electric type moves for a set amount of turns, usually 5.
|
||||||
*/
|
*/
|
||||||
class MudSportTag extends WeakenMoveTypeTag {
|
class MudSportTag extends WeakenMoveTypeTag {
|
||||||
constructor(turnCount: integer, sourceId: integer) {
|
constructor(turnCount: number, sourceId: number) {
|
||||||
super(ArenaTagType.MUD_SPORT, turnCount, Type.ELECTRIC, Moves.MUD_SPORT, sourceId);
|
super(ArenaTagType.MUD_SPORT, turnCount, Type.ELECTRIC, Moves.MUD_SPORT, sourceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -499,7 +504,7 @@ class MudSportTag extends WeakenMoveTypeTag {
|
|||||||
* Weakens Fire type moves for a set amount of turns, usually 5.
|
* Weakens Fire type moves for a set amount of turns, usually 5.
|
||||||
*/
|
*/
|
||||||
class WaterSportTag extends WeakenMoveTypeTag {
|
class WaterSportTag extends WeakenMoveTypeTag {
|
||||||
constructor(turnCount: integer, sourceId: integer) {
|
constructor(turnCount: number, sourceId: number) {
|
||||||
super(ArenaTagType.WATER_SPORT, turnCount, Type.FIRE, Moves.WATER_SPORT, sourceId);
|
super(ArenaTagType.WATER_SPORT, turnCount, Type.FIRE, Moves.WATER_SPORT, sourceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -550,8 +555,8 @@ export class IonDelugeTag extends ArenaTag {
|
|||||||
* Abstract class to implement arena traps.
|
* Abstract class to implement arena traps.
|
||||||
*/
|
*/
|
||||||
export class ArenaTrapTag extends ArenaTag {
|
export class ArenaTrapTag extends ArenaTag {
|
||||||
public layers: integer;
|
public layers: number;
|
||||||
public maxLayers: integer;
|
public maxLayers: number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance of the ArenaTrapTag class.
|
* Creates a new instance of the ArenaTrapTag class.
|
||||||
@ -562,7 +567,7 @@ export class ArenaTrapTag extends ArenaTag {
|
|||||||
* @param side - The side (player or enemy) the tag affects.
|
* @param side - The side (player or enemy) the tag affects.
|
||||||
* @param maxLayers - The maximum amount of layers this tag can have.
|
* @param maxLayers - The maximum amount of layers this tag can have.
|
||||||
*/
|
*/
|
||||||
constructor(tagType: ArenaTagType, sourceMove: Moves, sourceId: integer, side: ArenaTagSide, maxLayers: integer) {
|
constructor(tagType: ArenaTagType, sourceMove: Moves, sourceId: number, side: ArenaTagSide, maxLayers: number) {
|
||||||
super(tagType, 0, sourceMove, sourceId, side);
|
super(tagType, 0, sourceMove, sourceId, side);
|
||||||
|
|
||||||
this.layers = 1;
|
this.layers = 1;
|
||||||
@ -593,6 +598,12 @@ export class ArenaTrapTag extends ArenaTag {
|
|||||||
getMatchupScoreMultiplier(pokemon: Pokemon): number {
|
getMatchupScoreMultiplier(pokemon: Pokemon): number {
|
||||||
return pokemon.isGrounded() ? 1 : Phaser.Math.Linear(0, 1 / Math.pow(2, this.layers), Math.min(pokemon.getHpRatio(), 0.5) * 2);
|
return pokemon.isGrounded() ? 1 : Phaser.Math.Linear(0, 1 / Math.pow(2, this.layers), Math.min(pokemon.getHpRatio(), 0.5) * 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadTag(source: any): void {
|
||||||
|
super.loadTag(source);
|
||||||
|
this.layers = source.layers;
|
||||||
|
this.maxLayers = source.maxLayers;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -601,7 +612,7 @@ export class ArenaTrapTag extends ArenaTag {
|
|||||||
* in damage for 1, 2, or 3 layers of Spikes respectively if they are summoned into this trap.
|
* in damage for 1, 2, or 3 layers of Spikes respectively if they are summoned into this trap.
|
||||||
*/
|
*/
|
||||||
class SpikesTag extends ArenaTrapTag {
|
class SpikesTag extends ArenaTrapTag {
|
||||||
constructor(sourceId: integer, side: ArenaTagSide) {
|
constructor(sourceId: number, side: ArenaTagSide) {
|
||||||
super(ArenaTagType.SPIKES, Moves.SPIKES, sourceId, side, 3);
|
super(ArenaTagType.SPIKES, Moves.SPIKES, sourceId, side, 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -645,7 +656,7 @@ class SpikesTag extends ArenaTrapTag {
|
|||||||
class ToxicSpikesTag extends ArenaTrapTag {
|
class ToxicSpikesTag extends ArenaTrapTag {
|
||||||
private neutralized: boolean;
|
private neutralized: boolean;
|
||||||
|
|
||||||
constructor(sourceId: integer, side: ArenaTagSide) {
|
constructor(sourceId: number, side: ArenaTagSide) {
|
||||||
super(ArenaTagType.TOXIC_SPIKES, Moves.TOXIC_SPIKES, sourceId, side, 2);
|
super(ArenaTagType.TOXIC_SPIKES, Moves.TOXIC_SPIKES, sourceId, side, 2);
|
||||||
this.neutralized = false;
|
this.neutralized = false;
|
||||||
}
|
}
|
||||||
@ -703,7 +714,7 @@ class ToxicSpikesTag extends ArenaTrapTag {
|
|||||||
class DelayedAttackTag extends ArenaTag {
|
class DelayedAttackTag extends ArenaTag {
|
||||||
public targetIndex: BattlerIndex;
|
public targetIndex: BattlerIndex;
|
||||||
|
|
||||||
constructor(tagType: ArenaTagType, sourceMove: Moves | undefined, sourceId: integer, targetIndex: BattlerIndex) {
|
constructor(tagType: ArenaTagType, sourceMove: Moves | undefined, sourceId: number, targetIndex: BattlerIndex) {
|
||||||
super(tagType, 3, sourceMove, sourceId);
|
super(tagType, 3, sourceMove, sourceId);
|
||||||
|
|
||||||
this.targetIndex = targetIndex;
|
this.targetIndex = targetIndex;
|
||||||
@ -728,7 +739,7 @@ class DelayedAttackTag extends ArenaTag {
|
|||||||
* who is summoned into the trap, based on the Rock type's type effectiveness.
|
* who is summoned into the trap, based on the Rock type's type effectiveness.
|
||||||
*/
|
*/
|
||||||
class StealthRockTag extends ArenaTrapTag {
|
class StealthRockTag extends ArenaTrapTag {
|
||||||
constructor(sourceId: integer, side: ArenaTagSide) {
|
constructor(sourceId: number, side: ArenaTagSide) {
|
||||||
super(ArenaTagType.STEALTH_ROCK, Moves.STEALTH_ROCK, sourceId, side, 1);
|
super(ArenaTagType.STEALTH_ROCK, Moves.STEALTH_ROCK, sourceId, side, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -804,7 +815,7 @@ class StealthRockTag extends ArenaTrapTag {
|
|||||||
* to any Pokémon who is summoned into this trap.
|
* to any Pokémon who is summoned into this trap.
|
||||||
*/
|
*/
|
||||||
class StickyWebTag extends ArenaTrapTag {
|
class StickyWebTag extends ArenaTrapTag {
|
||||||
constructor(sourceId: integer, side: ArenaTagSide) {
|
constructor(sourceId: number, side: ArenaTagSide) {
|
||||||
super(ArenaTagType.STICKY_WEB, Moves.STICKY_WEB, sourceId, side, 1);
|
super(ArenaTagType.STICKY_WEB, Moves.STICKY_WEB, sourceId, side, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -838,7 +849,7 @@ class StickyWebTag extends ArenaTrapTag {
|
|||||||
* also reversing the turn order for all Pokémon on the field as well.
|
* also reversing the turn order for all Pokémon on the field as well.
|
||||||
*/
|
*/
|
||||||
export class TrickRoomTag extends ArenaTag {
|
export class TrickRoomTag extends ArenaTag {
|
||||||
constructor(turnCount: integer, sourceId: integer) {
|
constructor(turnCount: number, sourceId: number) {
|
||||||
super(ArenaTagType.TRICK_ROOM, turnCount, Moves.TRICK_ROOM, sourceId);
|
super(ArenaTagType.TRICK_ROOM, turnCount, Moves.TRICK_ROOM, sourceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -866,7 +877,7 @@ export class TrickRoomTag extends ArenaTag {
|
|||||||
* {@linkcode Abilities.LEVITATE} for the duration of the arena tag, usually 5 turns.
|
* {@linkcode Abilities.LEVITATE} for the duration of the arena tag, usually 5 turns.
|
||||||
*/
|
*/
|
||||||
export class GravityTag extends ArenaTag {
|
export class GravityTag extends ArenaTag {
|
||||||
constructor(turnCount: integer) {
|
constructor(turnCount: number) {
|
||||||
super(ArenaTagType.GRAVITY, turnCount, Moves.GRAVITY);
|
super(ArenaTagType.GRAVITY, turnCount, Moves.GRAVITY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -890,7 +901,7 @@ export class GravityTag extends ArenaTag {
|
|||||||
* Applies this arena tag for 4 turns (including the turn the move was used).
|
* Applies this arena tag for 4 turns (including the turn the move was used).
|
||||||
*/
|
*/
|
||||||
class TailwindTag extends ArenaTag {
|
class TailwindTag extends ArenaTag {
|
||||||
constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) {
|
constructor(turnCount: number, sourceId: number, side: ArenaTagSide) {
|
||||||
super(ArenaTagType.TAILWIND, turnCount, Moves.TAILWIND, sourceId, side);
|
super(ArenaTagType.TAILWIND, turnCount, Moves.TAILWIND, sourceId, side);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -928,7 +939,7 @@ class TailwindTag extends ArenaTag {
|
|||||||
* Doubles the prize money from trainers and money moves like {@linkcode Moves.PAY_DAY} and {@linkcode Moves.MAKE_IT_RAIN}.
|
* Doubles the prize money from trainers and money moves like {@linkcode Moves.PAY_DAY} and {@linkcode Moves.MAKE_IT_RAIN}.
|
||||||
*/
|
*/
|
||||||
class HappyHourTag extends ArenaTag {
|
class HappyHourTag extends ArenaTag {
|
||||||
constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) {
|
constructor(turnCount: number, sourceId: number, side: ArenaTagSide) {
|
||||||
super(ArenaTagType.HAPPY_HOUR, turnCount, Moves.HAPPY_HOUR, sourceId, side);
|
super(ArenaTagType.HAPPY_HOUR, turnCount, Moves.HAPPY_HOUR, sourceId, side);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -942,7 +953,7 @@ class HappyHourTag extends ArenaTag {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class SafeguardTag extends ArenaTag {
|
class SafeguardTag extends ArenaTag {
|
||||||
constructor(turnCount: integer, sourceId: integer, side: ArenaTagSide) {
|
constructor(turnCount: number, sourceId: number, side: ArenaTagSide) {
|
||||||
super(ArenaTagType.SAFEGUARD, turnCount, Moves.SAFEGUARD, sourceId, side);
|
super(ArenaTagType.SAFEGUARD, turnCount, Moves.SAFEGUARD, sourceId, side);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -955,6 +966,11 @@ class SafeguardTag extends ArenaTag {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class NoneTag extends ArenaTag {
|
||||||
|
constructor() {
|
||||||
|
super(ArenaTagType.NONE, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* This arena tag facilitates the application of the move Imprison
|
* This arena tag facilitates the application of the move Imprison
|
||||||
* Imprison remains in effect as long as the source Pokemon is active and present on the field.
|
* Imprison remains in effect as long as the source Pokemon is active and present on the field.
|
||||||
@ -1102,7 +1118,8 @@ class GrassWaterPledgeTag extends ArenaTag {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves | undefined, sourceId: integer, targetIndex?: BattlerIndex, side: ArenaTagSide = ArenaTagSide.BOTH): ArenaTag | null {
|
// TODO: swap `sourceMove` and `sourceId` and make `sourceMove` an optional parameter
|
||||||
|
export function getArenaTag(tagType: ArenaTagType, turnCount: number, sourceMove: Moves | undefined, sourceId: number, targetIndex?: BattlerIndex, side: ArenaTagSide = ArenaTagSide.BOTH): ArenaTag | null {
|
||||||
switch (tagType) {
|
switch (tagType) {
|
||||||
case ArenaTagType.MIST:
|
case ArenaTagType.MIST:
|
||||||
return new MistTag(turnCount, sourceId, side);
|
return new MistTag(turnCount, sourceId, side);
|
||||||
@ -1163,3 +1180,16 @@ export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMov
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When given a battler tag or json representing one, creates an actual ArenaTag object with the same data.
|
||||||
|
* @param {ArenaTag | any} source An arena tag
|
||||||
|
* @return {ArenaTag} The valid arena tag
|
||||||
|
*/
|
||||||
|
export function loadArenaTag(source: ArenaTag | any): ArenaTag {
|
||||||
|
const tag = getArenaTag(source.tagType, source.turnCount, source.sourceMove, source.sourceId, source.targetIndex, source.side)
|
||||||
|
?? new NoneTag();
|
||||||
|
tag.loadTag(source);
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
23
src/phases/check-status-effect-phase.ts
Normal file
23
src/phases/check-status-effect-phase.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { PostTurnStatusEffectPhase } from "#app/phases/post-turn-status-effect-phase";
|
||||||
|
import { Phase } from "#app/phase";
|
||||||
|
import { BattlerIndex } from "#app/battle";
|
||||||
|
import BattleScene from "#app/battle-scene";
|
||||||
|
|
||||||
|
export class CheckStatusEffectPhase extends Phase {
|
||||||
|
private order : BattlerIndex[];
|
||||||
|
constructor(scene : BattleScene, order : BattlerIndex[]) {
|
||||||
|
super(scene);
|
||||||
|
this.scene = scene;
|
||||||
|
this.order = order;
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
const field = this.scene.getField();
|
||||||
|
for (const o of this.order) {
|
||||||
|
if (field[o].status && field[o].status.isPostTurn()) {
|
||||||
|
this.scene.unshiftPhase(new PostTurnStatusEffectPhase(this.scene, o));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.end();
|
||||||
|
}
|
||||||
|
}
|
@ -6,7 +6,6 @@ import { StatusEffect } from "#app/enums/status-effect";
|
|||||||
import Pokemon from "#app/field/pokemon";
|
import Pokemon from "#app/field/pokemon";
|
||||||
import { getPokemonNameWithAffix } from "#app/messages";
|
import { getPokemonNameWithAffix } from "#app/messages";
|
||||||
import { PokemonPhase } from "./pokemon-phase";
|
import { PokemonPhase } from "./pokemon-phase";
|
||||||
import { PostTurnStatusEffectPhase } from "./post-turn-status-effect-phase";
|
|
||||||
|
|
||||||
export class ObtainStatusEffectPhase extends PokemonPhase {
|
export class ObtainStatusEffectPhase extends PokemonPhase {
|
||||||
private statusEffect?: StatusEffect | undefined;
|
private statusEffect?: StatusEffect | undefined;
|
||||||
@ -33,9 +32,6 @@ export class ObtainStatusEffectPhase extends PokemonPhase {
|
|||||||
pokemon.updateInfo(true);
|
pokemon.updateInfo(true);
|
||||||
new CommonBattleAnim(CommonAnim.POISON + (this.statusEffect! - 1), pokemon).play(this.scene, false, () => {
|
new CommonBattleAnim(CommonAnim.POISON + (this.statusEffect! - 1), pokemon).play(this.scene, false, () => {
|
||||||
this.scene.queueMessage(getStatusEffectObtainText(this.statusEffect, getPokemonNameWithAffix(pokemon), this.sourceText ?? undefined));
|
this.scene.queueMessage(getStatusEffectObtainText(this.statusEffect, getPokemonNameWithAffix(pokemon), this.sourceText ?? undefined));
|
||||||
if (pokemon.status?.isPostTurn()) {
|
|
||||||
this.scene.pushPhase(new PostTurnStatusEffectPhase(this.scene, this.battlerIndex));
|
|
||||||
}
|
|
||||||
this.end();
|
this.end();
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
|
@ -13,10 +13,10 @@ import { BerryPhase } from "./berry-phase";
|
|||||||
import { FieldPhase } from "./field-phase";
|
import { FieldPhase } from "./field-phase";
|
||||||
import { MoveHeaderPhase } from "./move-header-phase";
|
import { MoveHeaderPhase } from "./move-header-phase";
|
||||||
import { MovePhase } from "./move-phase";
|
import { MovePhase } from "./move-phase";
|
||||||
import { PostTurnStatusEffectPhase } from "./post-turn-status-effect-phase";
|
|
||||||
import { SwitchSummonPhase } from "./switch-summon-phase";
|
import { SwitchSummonPhase } from "./switch-summon-phase";
|
||||||
import { TurnEndPhase } from "./turn-end-phase";
|
import { TurnEndPhase } from "./turn-end-phase";
|
||||||
import { WeatherEffectPhase } from "./weather-effect-phase";
|
import { WeatherEffectPhase } from "./weather-effect-phase";
|
||||||
|
import { CheckStatusEffectPhase } from "#app/phases/check-status-effect-phase";
|
||||||
import { BattlerIndex } from "#app/battle";
|
import { BattlerIndex } from "#app/battle";
|
||||||
import { TrickRoomTag } from "#app/data/arena-tag";
|
import { TrickRoomTag } from "#app/data/arena-tag";
|
||||||
import { SwitchType } from "#enums/switch-type";
|
import { SwitchType } from "#enums/switch-type";
|
||||||
@ -206,11 +206,8 @@ export class TurnStartPhase extends FieldPhase {
|
|||||||
|
|
||||||
this.scene.pushPhase(new WeatherEffectPhase(this.scene));
|
this.scene.pushPhase(new WeatherEffectPhase(this.scene));
|
||||||
|
|
||||||
for (const o of moveOrder) {
|
/** Add a new phase to check who should be taking status damage */
|
||||||
if (field[o].status && field[o].status.isPostTurn()) {
|
this.scene.pushPhase(new CheckStatusEffectPhase(this.scene, moveOrder));
|
||||||
this.scene.pushPhase(new PostTurnStatusEffectPhase(this.scene, o));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.scene.pushPhase(new BerryPhase(this.scene));
|
this.scene.pushPhase(new BerryPhase(this.scene));
|
||||||
this.scene.pushPhase(new TurnEndPhase(this.scene));
|
this.scene.pushPhase(new TurnEndPhase(this.scene));
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Arena } from "../field/arena";
|
import { Arena } from "../field/arena";
|
||||||
import { ArenaTag } from "../data/arena-tag";
|
import { ArenaTag, loadArenaTag } from "../data/arena-tag";
|
||||||
import { Biome } from "#enums/biome";
|
import { Biome } from "#enums/biome";
|
||||||
import { Weather } from "../data/weather";
|
import { Weather } from "../data/weather";
|
||||||
import { Terrain } from "#app/data/terrain";
|
import { Terrain } from "#app/data/terrain";
|
||||||
@ -15,6 +15,10 @@ export default class ArenaData {
|
|||||||
this.biome = sourceArena ? sourceArena.biomeType : source.biome;
|
this.biome = sourceArena ? sourceArena.biomeType : source.biome;
|
||||||
this.weather = sourceArena ? sourceArena.weather : source.weather ? new Weather(source.weather.weatherType, source.weather.turnsLeft) : null;
|
this.weather = sourceArena ? sourceArena.weather : source.weather ? new Weather(source.weather.weatherType, source.weather.turnsLeft) : null;
|
||||||
this.terrain = sourceArena ? sourceArena.terrain : source.terrain ? new Terrain(source.terrain.terrainType, source.terrain.turnsLeft) : null;
|
this.terrain = sourceArena ? sourceArena.terrain : source.terrain ? new Terrain(source.terrain.terrainType, source.terrain.turnsLeft) : null;
|
||||||
this.tags = sourceArena ? sourceArena.tags : [];
|
this.tags = [];
|
||||||
|
|
||||||
|
if (source.tags) {
|
||||||
|
this.tags = source.tags.map(t => loadArenaTag(t));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ import { TrainerVariant } from "#app/field/trainer";
|
|||||||
import { Variant } from "#app/data/variant";
|
import { Variant } from "#app/data/variant";
|
||||||
import { setSettingGamepad, SettingGamepad, settingGamepadDefaults } from "#app/system/settings/settings-gamepad";
|
import { setSettingGamepad, SettingGamepad, settingGamepadDefaults } from "#app/system/settings/settings-gamepad";
|
||||||
import { setSettingKeyboard, SettingKeyboard } from "#app/system/settings/settings-keyboard";
|
import { setSettingKeyboard, SettingKeyboard } from "#app/system/settings/settings-keyboard";
|
||||||
import { TerrainChangedEvent, WeatherChangedEvent } from "#app/events/arena";
|
import { TagAddedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#app/events/arena";
|
||||||
import * as Modifier from "#app/modifier/modifier";
|
import * as Modifier from "#app/modifier/modifier";
|
||||||
import { StatusEffect } from "#app/data/status-effect";
|
import { StatusEffect } from "#app/data/status-effect";
|
||||||
import ChallengeData from "#app/system/challenge-data";
|
import ChallengeData from "#app/system/challenge-data";
|
||||||
@ -50,6 +50,7 @@ import { applySessionDataPatches, applySettingsDataPatches, applySystemDataPatch
|
|||||||
import { MysteryEncounterSaveData } from "#app/data/mystery-encounters/mystery-encounter-save-data";
|
import { MysteryEncounterSaveData } from "#app/data/mystery-encounters/mystery-encounter-save-data";
|
||||||
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
|
||||||
import { PokerogueApiClearSessionData } from "#app/@types/pokerogue-api";
|
import { PokerogueApiClearSessionData } from "#app/@types/pokerogue-api";
|
||||||
|
import { ArenaTrapTag } from "#app/data/arena-tag";
|
||||||
|
|
||||||
export const defaultStarterSpecies: Species[] = [
|
export const defaultStarterSpecies: Species[] = [
|
||||||
Species.BULBASAUR, Species.CHARMANDER, Species.SQUIRTLE,
|
Species.BULBASAUR, Species.CHARMANDER, Species.SQUIRTLE,
|
||||||
@ -1085,8 +1086,18 @@ export class GameData {
|
|||||||
|
|
||||||
scene.arena.terrain = sessionData.arena.terrain;
|
scene.arena.terrain = sessionData.arena.terrain;
|
||||||
scene.arena.eventTarget.dispatchEvent(new TerrainChangedEvent(TerrainType.NONE, scene.arena.terrain?.terrainType!, scene.arena.terrain?.turnsLeft!)); // TODO: is this bang correct?
|
scene.arena.eventTarget.dispatchEvent(new TerrainChangedEvent(TerrainType.NONE, scene.arena.terrain?.terrainType!, scene.arena.terrain?.turnsLeft!)); // TODO: is this bang correct?
|
||||||
// TODO
|
|
||||||
//scene.arena.tags = sessionData.arena.tags;
|
scene.arena.tags = sessionData.arena.tags;
|
||||||
|
if (scene.arena.tags) {
|
||||||
|
for (const tag of scene.arena.tags) {
|
||||||
|
if (tag instanceof ArenaTrapTag) {
|
||||||
|
const { tagType, side, turnCount, layers, maxLayers } = tag as ArenaTrapTag;
|
||||||
|
scene.arena.eventTarget.dispatchEvent(new TagAddedEvent(tagType, side, turnCount, layers, maxLayers));
|
||||||
|
} else {
|
||||||
|
scene.arena.eventTarget.dispatchEvent(new TagAddedEvent(tag.tagType, tag.side, tag.turnCount));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const modifierData of sessionData.modifiers) {
|
for (const modifierData of sessionData.modifiers) {
|
||||||
const modifier = modifierData.toModifier(scene, Modifier[modifierData.className]);
|
const modifier = modifierData.toModifier(scene, Modifier[modifierData.className]);
|
||||||
|
@ -1,7 +1,4 @@
|
|||||||
import { StatusEffect } from "#app/data/status-effect";
|
import { StatusEffect } from "#app/data/status-effect";
|
||||||
import { EnemyCommandPhase } from "#app/phases/enemy-command-phase";
|
|
||||||
import { MessagePhase } from "#app/phases/message-phase";
|
|
||||||
import { TurnEndPhase } from "#app/phases/turn-end-phase";
|
|
||||||
import i18next from "#app/plugins/i18n";
|
import i18next from "#app/plugins/i18n";
|
||||||
import { Abilities } from "#enums/abilities";
|
import { Abilities } from "#enums/abilities";
|
||||||
import { Moves } from "#enums/moves";
|
import { Moves } from "#enums/moves";
|
||||||
@ -10,6 +7,7 @@ import GameManager from "#test/utils/gameManager";
|
|||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
|
const TIMEOUT = 20 * 1000;
|
||||||
|
|
||||||
describe("Items - Toxic orb", () => {
|
describe("Items - Toxic orb", () => {
|
||||||
let phaserGame: Phaser.Game;
|
let phaserGame: Phaser.Game;
|
||||||
@ -27,40 +25,36 @@ describe("Items - Toxic orb", () => {
|
|||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
game = new GameManager(phaserGame);
|
game = new GameManager(phaserGame);
|
||||||
const moveToUse = Moves.GROWTH;
|
game.override
|
||||||
const oppMoveToUse = Moves.TACKLE;
|
.battleType("single")
|
||||||
game.override.battleType("single");
|
.enemySpecies(Species.RATTATA)
|
||||||
game.override.enemySpecies(Species.RATTATA);
|
.ability(Abilities.BALL_FETCH)
|
||||||
game.override.ability(Abilities.INSOMNIA);
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
game.override.enemyAbility(Abilities.INSOMNIA);
|
.moveset([ Moves.SPLASH ])
|
||||||
game.override.startingLevel(2000);
|
.enemyMoveset(Moves.SPLASH)
|
||||||
game.override.moveset([ moveToUse ]);
|
.startingHeldItems([{
|
||||||
game.override.enemyMoveset([ oppMoveToUse, oppMoveToUse, oppMoveToUse, oppMoveToUse ]);
|
name: "TOXIC_ORB",
|
||||||
game.override.startingHeldItems([{
|
}]);
|
||||||
name: "TOXIC_ORB",
|
|
||||||
}]);
|
|
||||||
|
|
||||||
vi.spyOn(i18next, "t");
|
vi.spyOn(i18next, "t");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("TOXIC ORB", async () => {
|
it("badly poisons the holder", async () => {
|
||||||
const moveToUse = Moves.GROWTH;
|
await game.classicMode.startBattle([ Species.MIGHTYENA ]);
|
||||||
await game.startBattle([
|
|
||||||
Species.MIGHTYENA,
|
|
||||||
Species.MIGHTYENA,
|
|
||||||
]);
|
|
||||||
expect(game.scene.modifiers[0].type.id).toBe("TOXIC_ORB");
|
|
||||||
|
|
||||||
game.move.select(moveToUse);
|
const player = game.scene.getPlayerField()[0];
|
||||||
|
|
||||||
// will run the 13 phase from enemyCommandPhase to TurnEndPhase
|
game.move.select(Moves.SPLASH);
|
||||||
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnEndPhase);
|
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
// Toxic orb should trigger here
|
// Toxic orb should trigger here
|
||||||
await game.phaseInterceptor.run(MessagePhase);
|
await game.phaseInterceptor.run("MessagePhase");
|
||||||
expect(i18next.t).toHaveBeenCalledWith("statusEffect:toxic.obtainSource", expect.anything());
|
expect(i18next.t).toHaveBeenCalledWith("statusEffect:toxic.obtainSource", expect.anything());
|
||||||
|
|
||||||
await game.phaseInterceptor.run(MessagePhase);
|
await game.toNextTurn();
|
||||||
expect(i18next.t).toHaveBeenCalledWith("statusEffect:toxic.activation", expect.anything());
|
|
||||||
expect(game.scene.getParty()[0].status!.effect).toBe(StatusEffect.TOXIC);
|
expect(player.status?.effect).toBe(StatusEffect.TOXIC);
|
||||||
}, 20000);
|
// Damage should not have ticked yet.
|
||||||
|
expect(player.status?.turnCount).toBe(0);
|
||||||
|
}, TIMEOUT);
|
||||||
});
|
});
|
||||||
|
136
src/test/moves/toxic_spikes.test.ts
Normal file
136
src/test/moves/toxic_spikes.test.ts
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag";
|
||||||
|
import { StatusEffect } from "#app/data/status-effect";
|
||||||
|
import { decrypt, encrypt, GameData, SessionSaveData } from "#app/system/game-data";
|
||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { ArenaTagType } from "#enums/arena-tag-type";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import GameManager from "#test/utils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
const TIMEOUT = 20 * 1000;
|
||||||
|
|
||||||
|
describe("Moves - Toxic Spikes", () => {
|
||||||
|
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")
|
||||||
|
.startingWave(5)
|
||||||
|
.enemySpecies(Species.RATTATA)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.ability(Abilities.BALL_FETCH)
|
||||||
|
.enemyMoveset(Moves.SPLASH)
|
||||||
|
.moveset([ Moves.TOXIC_SPIKES, Moves.SPLASH, Moves.ROAR ]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not affect the opponent if they do not switch", async() => {
|
||||||
|
await game.classicMode.runToSummon([ Species.MIGHTYENA, Species.POOCHYENA ]);
|
||||||
|
|
||||||
|
const enemy = game.scene.getEnemyField()[0];
|
||||||
|
|
||||||
|
game.move.select(Moves.TOXIC_SPIKES);
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
game.doSwitchPokemon(1);
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
|
||||||
|
expect(enemy.hp).toBe(enemy.getMaxHp());
|
||||||
|
expect(enemy.status?.effect).toBeUndefined();
|
||||||
|
}, TIMEOUT);
|
||||||
|
|
||||||
|
it("should poison the opponent if they switch into 1 layer", async() => {
|
||||||
|
await game.classicMode.runToSummon([ Species.MIGHTYENA ]);
|
||||||
|
|
||||||
|
game.move.select(Moves.TOXIC_SPIKES);
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
game.move.select(Moves.ROAR);
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
|
||||||
|
const enemy = game.scene.getEnemyField()[0];
|
||||||
|
|
||||||
|
expect(enemy.hp).toBeLessThan(enemy.getMaxHp());
|
||||||
|
expect(enemy.status?.effect).toBe(StatusEffect.POISON);
|
||||||
|
}, TIMEOUT);
|
||||||
|
|
||||||
|
it("should badly poison the opponent if they switch into 2 layers", async() => {
|
||||||
|
await game.classicMode.runToSummon([ Species.MIGHTYENA ]);
|
||||||
|
|
||||||
|
game.move.select(Moves.TOXIC_SPIKES);
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
game.move.select(Moves.TOXIC_SPIKES);
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
game.move.select(Moves.ROAR);
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
|
||||||
|
const enemy = game.scene.getEnemyField()[0];
|
||||||
|
expect(enemy.hp).toBeLessThan(enemy.getMaxHp());
|
||||||
|
expect(enemy.status?.effect).toBe(StatusEffect.TOXIC);
|
||||||
|
}, TIMEOUT);
|
||||||
|
|
||||||
|
it("should be removed if a grounded poison pokemon switches in", async() => {
|
||||||
|
game.override.enemySpecies(Species.GRIMER);
|
||||||
|
await game.classicMode.runToSummon([ Species.MIGHTYENA ]);
|
||||||
|
|
||||||
|
game.move.select(Moves.TOXIC_SPIKES);
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
game.move.select(Moves.TOXIC_SPIKES);
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
game.move.select(Moves.ROAR);
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
|
||||||
|
const enemy = game.scene.getEnemyField()[0];
|
||||||
|
expect(enemy.hp).toBe(enemy.getMaxHp());
|
||||||
|
expect(enemy.status?.effect).toBeUndefined();
|
||||||
|
|
||||||
|
expect(game.scene.arena.tags.length).toBe(0);
|
||||||
|
}, TIMEOUT);
|
||||||
|
|
||||||
|
it("shouldn't create multiple layers per use in doubles", async() => {
|
||||||
|
await game.classicMode.runToSummon([ Species.MIGHTYENA, Species.POOCHYENA ]);
|
||||||
|
|
||||||
|
game.move.select(Moves.TOXIC_SPIKES);
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
|
||||||
|
const arenaTags = (game.scene.arena.getTagOnSide(ArenaTagType.TOXIC_SPIKES, ArenaTagSide.ENEMY) as ArenaTrapTag);
|
||||||
|
expect(arenaTags.tagType).toBe(ArenaTagType.TOXIC_SPIKES);
|
||||||
|
expect(arenaTags.layers).toBe(1);
|
||||||
|
}, TIMEOUT);
|
||||||
|
|
||||||
|
it("should persist through reload", async() => {
|
||||||
|
game.override.startingWave(1);
|
||||||
|
const scene = game.scene;
|
||||||
|
const gameData = new GameData(scene);
|
||||||
|
|
||||||
|
await game.classicMode.runToSummon([ Species.MIGHTYENA ]);
|
||||||
|
|
||||||
|
game.move.select(Moves.TOXIC_SPIKES);
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase");
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.doKillOpponents();
|
||||||
|
await game.phaseInterceptor.to("BattleEndPhase");
|
||||||
|
await game.toNextWave();
|
||||||
|
|
||||||
|
const sessionData : SessionSaveData = gameData["getSessionSaveData"](game.scene);
|
||||||
|
localStorage.setItem("sessionTestData", encrypt(JSON.stringify(sessionData), true));
|
||||||
|
const recoveredData : SessionSaveData = gameData.parseSessionData(decrypt(localStorage.getItem("sessionTestData")!, true));
|
||||||
|
gameData.loadSession(game.scene, 0, recoveredData);
|
||||||
|
|
||||||
|
expect(sessionData.arena.tags).toEqual(recoveredData.arena.tags);
|
||||||
|
localStorage.removeItem("sessionTestData");
|
||||||
|
}, TIMEOUT);
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user