This commit is contained in:
Mason S 2024-09-17 20:07:27 -07:00 committed by GitHub
commit 53b9661f9b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 298 additions and 110 deletions

View File

@ -1,23 +1,23 @@
import { Arena } from "../field/arena";
import { Type } from "./type";
import * as Utils from "../utils";
import { MoveCategory, allMoves, MoveTarget, IncrementMovePriorityAttr, applyMoveAttrs } from "./move";
import { getPokemonNameWithAffix } from "../messages";
import Pokemon, { HitResult, PokemonMove } from "../field/pokemon";
import { StatusEffect } from "./status-effect";
import { BattlerIndex } from "../battle";
import { BlockNonDirectDamageAbAttr, ChangeMovePriorityAbAttr, ProtectStatAbAttr, applyAbAttrs } from "./ability";
import { Stat } from "#enums/stat";
import { CommonAnim, CommonBattleAnim } from "./battle-anims";
import i18next from "i18next";
import { Abilities } from "#enums/abilities";
import { ArenaTagType } from "#enums/arena-tag-type";
import { BattlerTagType } from "#enums/battler-tag-type";
import { Moves } from "#enums/moves";
import { BattlerIndex } from "#app/battle";
import { BlockNonDirectDamageAbAttr, ChangeMovePriorityAbAttr, ProtectStatAbAttr, applyAbAttrs } from "#app/data/ability";
import { CommonAnim, CommonBattleAnim } from "#app/data/battle-anims";
import { IncrementMovePriorityAttr, MoveCategory, MoveTarget, allMoves, applyMoveAttrs } from "#app/data/move";
import { StatusEffect } from "#app/data/status-effect";
import { Type } from "#app/data/type";
import { Arena } from "#app/field/arena";
import Pokemon, { HitResult, PokemonMove } from "#app/field/pokemon";
import { getPokemonNameWithAffix } from "#app/messages";
import { MoveEffectPhase } from "#app/phases/move-effect-phase";
import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
import { ShowAbilityPhase } from "#app/phases/show-ability-phase";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import * as Utils from "#app/utils";
import { Abilities } from "#enums/abilities";
import { ArenaTagType } from "#enums/arena-tag-type";
import { BattlerTagType } from "#enums/battler-tag-type";
import { Moves } from "#enums/moves";
import { Stat } from "#enums/stat";
import i18next from "i18next";
export enum ArenaTagSide {
BOTH,
@ -26,20 +26,13 @@ export enum ArenaTagSide {
}
export abstract class ArenaTag {
public tagType: ArenaTagType;
public turnCount: integer;
public sourceMove?: Moves;
public sourceId?: integer;
public side: ArenaTagSide;
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;
}
constructor(
public tagType: ArenaTagType,
public turnCount: number,
public sourceMove?: Moves,
public sourceId?: number,
public side: ArenaTagSide = ArenaTagSide.BOTH
) {}
apply(arena: Arena, args: any[]): boolean {
return true;
@ -64,6 +57,18 @@ export abstract class ArenaTag {
? allMoves[this.sourceMove].name
: 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;
}
}
/**
@ -71,7 +76,7 @@ export abstract class ArenaTag {
* Prevents Pokémon on the opposing side from lowering the stats of the Pokémon in the Mist.
*/
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);
}
@ -115,7 +120,7 @@ export class WeakenMoveScreenTag extends ArenaTag {
* @param side - The side (player or enemy) the tag affects.
* @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);
this.weakenedCategories = weakenedCategories;
@ -146,7 +151,7 @@ export class WeakenMoveScreenTag extends ArenaTag {
* Used by {@linkcode Moves.REFLECT}
*/
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]);
}
@ -162,7 +167,7 @@ class ReflectTag extends WeakenMoveScreenTag {
* Used by {@linkcode Moves.LIGHT_SCREEN}
*/
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]);
}
@ -178,7 +183,7 @@ class LightScreenTag extends WeakenMoveScreenTag {
* Used by {@linkcode Moves.AURORA_VEIL}
*/
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]);
}
@ -201,7 +206,7 @@ export class ConditionalProtectTag extends ArenaTag {
/** Does this apply to all moves, including those that ignore other forms of protection? */
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);
this.protectConditionFunc = condition;
@ -263,7 +268,7 @@ export class ConditionalProtectTag extends ArenaTag {
*/
const QuickGuardConditionFunc: ProtectConditionFunc = (arena, moveId) => {
const move = allMoves[moveId];
const priority = new Utils.IntegerHolder(move.priority);
const priority = new Utils.NumberHolder(move.priority);
const effectPhase = arena.scene.getCurrentPhase();
if (effectPhase instanceof MoveEffectPhase) {
@ -279,7 +284,7 @@ const QuickGuardConditionFunc: ProtectConditionFunc = (arena, moveId) => {
* Condition: The incoming move has increased priority.
*/
class QuickGuardTag extends ConditionalProtectTag {
constructor(sourceId: integer, side: ArenaTagSide) {
constructor(sourceId: number, side: ArenaTagSide) {
super(ArenaTagType.QUICK_GUARD, Moves.QUICK_GUARD, sourceId, side, QuickGuardConditionFunc);
}
}
@ -310,7 +315,7 @@ const WideGuardConditionFunc: ProtectConditionFunc = (arena, moveId) : boolean =
* can be an ally or enemy.
*/
class WideGuardTag extends ConditionalProtectTag {
constructor(sourceId: integer, side: ArenaTagSide) {
constructor(sourceId: number, side: ArenaTagSide) {
super(ArenaTagType.WIDE_GUARD, Moves.WIDE_GUARD, sourceId, side, WideGuardConditionFunc);
}
}
@ -332,7 +337,7 @@ const MatBlockConditionFunc: ProtectConditionFunc = (arena, moveId) : boolean =>
* Condition: The incoming move is a Physical or Special attack move.
*/
class MatBlockTag extends ConditionalProtectTag {
constructor(sourceId: integer, side: ArenaTagSide) {
constructor(sourceId: number, side: ArenaTagSide) {
super(ArenaTagType.MAT_BLOCK, Moves.MAT_BLOCK, sourceId, side, MatBlockConditionFunc);
}
@ -370,7 +375,7 @@ const CraftyShieldConditionFunc: ProtectConditionFunc = (arena, moveId) => {
* not target all Pokemon or sides of the field.
*/
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);
}
}
@ -382,12 +387,12 @@ class CraftyShieldTag extends ConditionalProtectTag {
export class NoCritTag extends ArenaTag {
/**
* 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 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
*/
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);
}
@ -417,7 +422,7 @@ class WishTag extends ArenaTag {
private triggerMessage: string;
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);
}
@ -458,7 +463,7 @@ export class WeakenMoveTypeTag extends ArenaTag {
* @param sourceMove - The move that created 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);
this.weakenedType = type;
@ -479,7 +484,7 @@ export class WeakenMoveTypeTag extends ArenaTag {
* Weakens Electric type moves for a set amount of turns, usually 5.
*/
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);
}
@ -497,7 +502,7 @@ class MudSportTag extends WeakenMoveTypeTag {
* Weakens Fire type moves for a set amount of turns, usually 5.
*/
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);
}
@ -514,8 +519,8 @@ class WaterSportTag extends WeakenMoveTypeTag {
* Abstract class to implement arena traps.
*/
export class ArenaTrapTag extends ArenaTag {
public layers: integer;
public maxLayers: integer;
public layers: number;
public maxLayers: number;
/**
* Creates a new instance of the ArenaTrapTag class.
@ -526,7 +531,7 @@ export class ArenaTrapTag extends ArenaTag {
* @param side - The side (player or enemy) the tag affects.
* @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);
this.layers = 1;
@ -557,6 +562,12 @@ export class ArenaTrapTag extends ArenaTag {
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);
}
loadTag(source: any): void {
super.loadTag(source);
this.layers = source.layers;
this.maxLayers = source.maxLayers;
}
}
/**
@ -565,7 +576,7 @@ export class ArenaTrapTag extends ArenaTag {
* in damage for 1, 2, or 3 layers of Spikes respectively if they are summoned into this trap.
*/
class SpikesTag extends ArenaTrapTag {
constructor(sourceId: integer, side: ArenaTagSide) {
constructor(sourceId: number, side: ArenaTagSide) {
super(ArenaTagType.SPIKES, Moves.SPIKES, sourceId, side, 3);
}
@ -609,7 +620,7 @@ class SpikesTag extends ArenaTrapTag {
class ToxicSpikesTag extends ArenaTrapTag {
private neutralized: boolean;
constructor(sourceId: integer, side: ArenaTagSide) {
constructor(sourceId: number, side: ArenaTagSide) {
super(ArenaTagType.TOXIC_SPIKES, Moves.TOXIC_SPIKES, sourceId, side, 2);
this.neutralized = false;
}
@ -667,7 +678,7 @@ class ToxicSpikesTag extends ArenaTrapTag {
class DelayedAttackTag extends ArenaTag {
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);
this.targetIndex = targetIndex;
@ -692,7 +703,7 @@ class DelayedAttackTag extends ArenaTag {
* who is summoned into the trap, based on the Rock type's type effectiveness.
*/
class StealthRockTag extends ArenaTrapTag {
constructor(sourceId: integer, side: ArenaTagSide) {
constructor(sourceId: number, side: ArenaTagSide) {
super(ArenaTagType.STEALTH_ROCK, Moves.STEALTH_ROCK, sourceId, side, 1);
}
@ -768,7 +779,7 @@ class StealthRockTag extends ArenaTrapTag {
* to any Pokémon who is summoned into this trap.
*/
class StickyWebTag extends ArenaTrapTag {
constructor(sourceId: integer, side: ArenaTagSide) {
constructor(sourceId: number, side: ArenaTagSide) {
super(ArenaTagType.STICKY_WEB, Moves.STICKY_WEB, sourceId, side, 1);
}
@ -802,7 +813,7 @@ class StickyWebTag extends ArenaTrapTag {
* also reversing the turn order for all Pokémon on the field as well.
*/
export class TrickRoomTag extends ArenaTag {
constructor(turnCount: integer, sourceId: integer) {
constructor(turnCount: number, sourceId: number) {
super(ArenaTagType.TRICK_ROOM, turnCount, Moves.TRICK_ROOM, sourceId);
}
@ -830,7 +841,7 @@ export class TrickRoomTag extends ArenaTag {
* {@linkcode Abilities.LEVITATE} for the duration of the arena tag, usually 5 turns.
*/
export class GravityTag extends ArenaTag {
constructor(turnCount: integer) {
constructor(turnCount: number) {
super(ArenaTagType.GRAVITY, turnCount, Moves.GRAVITY);
}
@ -854,7 +865,7 @@ export class GravityTag extends ArenaTag {
* Applies this arena tag for 4 turns (including the turn the move was used).
*/
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);
}
@ -892,7 +903,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}.
*/
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);
}
@ -906,7 +917,7 @@ class HappyHourTag 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);
}
@ -919,8 +930,14 @@ class SafeguardTag extends ArenaTag {
}
}
class NoneTag extends ArenaTag {
constructor() {
super(ArenaTagType.NONE, 0);
}
}
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) {
case ArenaTagType.MIST:
return new MistTag(turnCount, sourceId, side);
@ -971,3 +988,16 @@ export function getArenaTag(tagType: ArenaTagType, turnCount: integer, sourceMov
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;
}

View 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();
}
}

View File

@ -6,7 +6,6 @@ import { StatusEffect } from "#app/enums/status-effect";
import Pokemon from "#app/field/pokemon";
import { getPokemonNameWithAffix } from "#app/messages";
import { PokemonPhase } from "./pokemon-phase";
import { PostTurnStatusEffectPhase } from "./post-turn-status-effect-phase";
export class ObtainStatusEffectPhase extends PokemonPhase {
private statusEffect: StatusEffect | undefined;
@ -33,9 +32,6 @@ export class ObtainStatusEffectPhase extends PokemonPhase {
pokemon.updateInfo(true);
new CommonBattleAnim(CommonAnim.POISON + (this.statusEffect! - 1), pokemon).play(this.scene, false, () => {
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();
});
return;

View File

@ -13,10 +13,10 @@ import { BerryPhase } from "./berry-phase";
import { FieldPhase } from "./field-phase";
import { MoveHeaderPhase } from "./move-header-phase";
import { MovePhase } from "./move-phase";
import { PostTurnStatusEffectPhase } from "./post-turn-status-effect-phase";
import { SwitchSummonPhase } from "./switch-summon-phase";
import { TurnEndPhase } from "./turn-end-phase";
import { WeatherEffectPhase } from "./weather-effect-phase";
import { CheckStatusEffectPhase } from "#app/phases/check-status-effect-phase";
import { BattlerIndex } from "#app/battle";
import { TrickRoomTag } from "#app/data/arena-tag";
@ -207,11 +207,8 @@ export class TurnStartPhase extends FieldPhase {
this.scene.pushPhase(new WeatherEffectPhase(this.scene));
for (const o of moveOrder) {
if (field[o].status && field[o].status.isPostTurn()) {
this.scene.pushPhase(new PostTurnStatusEffectPhase(this.scene, o));
}
}
/** Add a new phase to check who should be taking status damage */
this.scene.pushPhase(new CheckStatusEffectPhase(this.scene, moveOrder));
this.scene.pushPhase(new BerryPhase(this.scene));
this.scene.pushPhase(new TurnEndPhase(this.scene));

View File

@ -1,5 +1,5 @@
import { Arena } from "../field/arena";
import { ArenaTag } from "../data/arena-tag";
import { ArenaTag, loadArenaTag } from "../data/arena-tag";
import { Biome } from "#enums/biome";
import { Weather } from "../data/weather";
import { Terrain } from "#app/data/terrain";
@ -15,6 +15,10 @@ export default class ArenaData {
this.biome = sourceArena ? sourceArena.biomeType : source.biome;
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.tags = sourceArena ? sourceArena.tags : [];
this.tags = [];
if (source.tags) {
this.tags = source.tags.map(t => loadArenaTag(t));
}
}
}

View File

@ -28,8 +28,8 @@ import { speciesEggMoves } from "../data/egg-moves";
import { allMoves } from "../data/move";
import { TrainerVariant } from "../field/trainer";
import { Variant } from "#app/data/variant";
import {setSettingGamepad, SettingGamepad, settingGamepadDefaults} from "./settings/settings-gamepad";
import {setSettingKeyboard, SettingKeyboard} from "#app/system/settings/settings-keyboard";
import { setSettingGamepad, SettingGamepad, settingGamepadDefaults } from "./settings/settings-gamepad";
import { setSettingKeyboard, SettingKeyboard } from "#app/system/settings/settings-keyboard";
import { TerrainChangedEvent, WeatherChangedEvent } from "#app/events/arena";
import * as Modifier from "../modifier/modifier";
import { StatusEffect } from "#app/data/status-effect";
@ -45,6 +45,8 @@ import { TerrainType } from "#app/data/terrain";
import { OutdatedPhase } from "#app/phases/outdated-phase";
import { ReloadSessionPhase } from "#app/phases/reload-session-phase";
import { RUN_HISTORY_LIMIT } from "#app/ui/run-history-ui-handler";
import { TagAddedEvent } from "#app/events/arena";
import { ArenaTrapTag } from "#app/data/arena-tag";
import { applySessionDataPatches, applySettingsDataPatches, applySystemDataPatches } from "./version-converter";
import { MysteryEncounterSaveData } from "../data/mystery-encounters/mystery-encounter-save-data";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
@ -1071,8 +1073,18 @@ export class GameData {
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?
// 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) {
const modifier = modifierData.toModifier(scene, Modifier[modifierData.className]);

View File

@ -1,7 +1,4 @@
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, { initI18n } from "#app/plugins/i18n";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
@ -10,6 +7,7 @@ import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
const TIMEOUT = 20 * 1000;
describe("Items - Toxic orb", () => {
let phaserGame: Phaser.Game;
@ -27,42 +25,34 @@ describe("Items - Toxic orb", () => {
beforeEach(() => {
game = new GameManager(phaserGame);
const moveToUse = Moves.GROWTH;
const oppMoveToUse = Moves.TACKLE;
game.override.battleType("single");
game.override.enemySpecies(Species.RATTATA);
game.override.ability(Abilities.INSOMNIA);
game.override.enemyAbility(Abilities.INSOMNIA);
game.override.startingLevel(2000);
game.override.moveset([moveToUse]);
game.override.enemyMoveset([oppMoveToUse, oppMoveToUse, oppMoveToUse, oppMoveToUse]);
game.override.startingHeldItems([{
name: "TOXIC_ORB",
}]);
game.override
.battleType("single")
.enemySpecies(Species.RATTATA)
.ability(Abilities.BALL_FETCH)
.enemyAbility(Abilities.BALL_FETCH)
.moveset([Moves.SPLASH])
.enemyMoveset(Moves.SPLASH)
.startingHeldItems([{
name: "TOXIC_ORB",
}]);
});
it("TOXIC ORB", async () => {
it("badly poisons the holder", async () => {
initI18n();
i18next.changeLanguage("en");
const moveToUse = Moves.GROWTH;
await game.startBattle([
Species.MIGHTYENA,
Species.MIGHTYENA,
]);
expect(game.scene.modifiers[0].type.id).toBe("TOXIC_ORB");
await game.classicMode.startBattle([Species.MIGHTYENA]);
game.move.select(moveToUse);
const player = game.scene.getPlayerField()[0];
// will run the 13 phase from enemyCommandPhase to TurnEndPhase
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(TurnEndPhase);
game.move.select(Moves.SPLASH);
await game.phaseInterceptor.to("TurnEndPhase");
// Toxic orb should trigger here
await game.phaseInterceptor.run(MessagePhase);
await game.phaseInterceptor.run("MessagePhase");
const message = game.textInterceptor.getLatestMessage();
expect(message).toContain("was badly poisoned by the Toxic Orb");
await game.phaseInterceptor.run(MessagePhase);
const message2 = game.textInterceptor.getLatestMessage();
expect(message2).toContain("is hurt");
expect(message2).toContain("by poison");
expect(game.scene.getParty()[0].status!.effect).toBe(StatusEffect.TOXIC);
}, 20000);
expect(player.status?.effect).toBe(StatusEffect.TOXIC);
// Damage should not have ticked yet.
expect(player.status?.turnCount).toBe(0);
}, TIMEOUT);
});

View 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);
});