Compare commits

...

10 Commits

Author SHA1 Message Date
Matthew
cf06ff3b8d
[QoL] Reorganize Enum Locations (#2142)
* moving enums
2024-06-13 11:11:12 -04:00
Dmitriy K
819fe9b4a1
[Ability] Implement Protean and Libero abilities (#1309)
* Add generic to util holders to reduce manual type casting

* implement protean and libero abilities

* remove use only once per turn trigger

* Revert Attack Attribute Conditions back to requiring unused vars

* Remove conditional before invoking type change ability

* update protean to properly trigger and skip certain moves

* remove some dangerous typecasts

* revert autoformatting changes

* not all autoformatting changes were reverted

* Revert "Add generic to util holders to reduce manual type casting"

This reverts commit 3ee7f1d5ff.

* change some variable names

* remove incorrect comment

* update abilities so they use gen 9 logic

* fix typescript error from missing Terrain type

* update gameManager switchPokemon to match other menu utilities

* add test cases for protean and libero
2024-06-13 10:54:23 -04:00
EmberCM
048993b2c2
[QoL] Add red color to the quantity if the item is at it's held limit (#2117)
* Add red color to the quantity if the item is at it's held limit

* Add shadow back to option text
2024-06-13 10:38:13 -04:00
hayuna
c929bbd23a
[Refactor] Move enum BattleStyle into separated file (#1906)
* Move enum BattleStyle into separated file

* Move enum BattleStyle into separated file

* Move enum BattleStyle into separated file

* Update battle-style.ts

* Replace shift into switch
2024-06-13 10:10:31 -04:00
Zach Day
a85a2a9b8b
[Bug] Fix type-immunity abilities activating on self-targeted status moves (#2168)
* Fix type immunity given by abilities only applying to attacking moves

* Add tests for type immunity granted by abilities

* Use Sap Sipper as base for testing

* Fix type-immunity abilities activating on self-targeted status moves

* Add tests for self-targeted moves on mons with type-immunity abilitiies

* Volt absorb tests
2024-06-13 09:49:40 -04:00
José Ricardo Fleury Oliveira
e0d3279a0b
[Localization] [pt] Fixed Bouncy Bubble description and updated achievements (#2174)
* updated pt achievements

* minor fix

* fixed bouncy bubble translation
2024-06-13 09:39:47 -04:00
Greenlamp2
c908153761
[Bug] Fix PostSummonPhase Logic to Support Single Pokémon Teams (#2153)
* added test for spikes + forceOpponentToSwitch

* fix conditional for intimidate in a double if there is only 1 pokemon available on our side

* fix variable naming and ternary condition

* added a fallback to clear the conditionalQueue if it's a new turn

* speed up tests by skipping LoginPhase
2024-06-13 08:42:25 -04:00
Dmitriy K
0970c2cd4e
[Refactor] Replace forceBypass with bypassFaint flag (#1839)
* replace forceBypass with bypassFaint flag

* add another path alias for src/test

* make form override work for the whole party instead of the first pokemon

* add tests for all abilities that are touched by this change

* remove unnecessary overrides from tests

* move SpeciesFormChangeTimeOfDayTrigger outside arena reset logic

* remove alll resetMock calls, rename it to test
2024-06-13 11:36:12 +02:00
Jannik Tappert
16e14376c6
[Localization] Changed description of BouncyBubble to reflect … (#2166)
* [Localization] Changed german description of BouncyBubble to reflect that is heals 100% of the damage and made it not eveee specific

* Also EN
2024-06-13 03:36:09 -05:00
Blitzy
6be46ec0de
Add Magic Guard to the list (#2076) 2024-06-13 03:32:22 -05:00
158 changed files with 1803 additions and 479 deletions

View File

@ -53,7 +53,10 @@
"node": ">=20.0.0"
},
"imports": {
"#enums": "./enums",
"#enums/*": "./enums/*",
"#app": "./src/main.js",
"#app/*": "./src/*"
"#app/*": "./src/*",
"#test/*": "./src/test/*"
}
}

View File

@ -1,6 +1,6 @@
import re
filenames = [['src/data/enums/moves.ts', 'move'], ['src/data/enums/abilities.ts', 'ability'], ['src/data/enums/species.ts', 'Pokémon']]
filenames = [['src/enums/moves.ts', 'move'], ['src/enums/abilities.ts', 'ability'], ['src/enums/species.ts', 'Pokémon']]
commentBlockStart = re.compile('\/\*[^\*].*') # Regex for the start of a comment block
commentBlockEnd = re.compile('.*,\*\/') # Regex for the end of a comment block

View File

@ -9,6 +9,10 @@ export interface UserInfo {
export let loggedInUser: UserInfo = null;
export const clientSessionId = Utils.randomString(32);
export function initLoggedInUser(): void {
loggedInUser = { username: "Guest", lastSessionSlot: -1 };
}
export function updateUserInfo(): Promise<[boolean, integer]> {
return new Promise<[boolean, integer]>(resolve => {
if (bypassLogin) {

View File

@ -9,12 +9,9 @@ import { PokeballType } from "./data/pokeball";
import { initCommonAnims, initMoveAnim, loadCommonAnimAssets, loadMoveAnimAssets, populateAnims } from "./data/battle-anims";
import { Phase } from "./phase";
import { initGameSpeed } from "./system/game-speed";
import { Biome } from "./data/enums/biome";
import { Arena, ArenaBase } from "./field/arena";
import { GameData } from "./system/game-data";
import { PlayerGender } from "./data/enums/player-gender";
import { TextStyle, addTextObject, getTextColor } from "./ui/text";
import { Moves } from "./data/enums/moves";
import { allMoves } from "./data/move";
import { ModifierPoolType, getDefaultModifierTypeForTier, getEnemyModifierTypesForWave, getLuckString, getLuckTextTint, getModifierPoolForType, getPartyLuckValue } from "./modifier/modifier-type";
import AbilityBar from "./ui/ability-bar";
@ -31,7 +28,6 @@ import TrainerData from "./system/trainer-data";
import SoundFade from "phaser3-rex-plugins/plugins/soundfade";
import { pokemonPrevolutions } from "./data/pokemon-evolutions";
import PokeballTray from "./ui/pokeball-tray";
import { Species } from "./data/enums/species";
import InvertPostFX from "./pipelines/invert";
import { Achv, ModifierAchv, MoneyAchv, achvs } from "./system/achv";
import { Voucher, vouchers } from "./system/voucher";
@ -42,14 +38,12 @@ import PokemonData from "./system/pokemon-data";
import { Nature } from "./data/nature";
import { SpeciesFormChangeTimeOfDayTrigger, SpeciesFormChangeTrigger, pokemonFormChanges } from "./data/pokemon-forms";
import { FormChangePhase, QuietFormChangePhase } from "./form-change-phase";
import { BattleSpec } from "./enums/battle-spec";
import { getTypeRgb } from "./data/type";
import PokemonSpriteSparkleHandler from "./field/pokemon-sprite-sparkle-handler";
import CharSprite from "./ui/char-sprite";
import DamageNumberHandler from "./field/damage-number-handler";
import PokemonInfoContainer from "./ui/pokemon-info-container";
import { biomeDepths, getBiomeName } from "./data/biomes";
import { UiTheme } from "./enums/ui-theme";
import { SceneBase } from "./scene-base";
import CandyBar from "./ui/candy-bar";
import { Variant, variantData } from "./data/variant";
@ -57,12 +51,9 @@ import { Localizable } from "./plugins/i18n";
import * as Overrides from "./overrides";
import {InputsController} from "./inputs-controller";
import {UiInputs} from "./ui-inputs";
import { MoneyFormat } from "./enums/money-format";
import { NewArenaEvent } from "./events/battle-scene";
import { Abilities } from "./data/enums/abilities";
import { Abilities, BattleSpec, BattleStyle, Biome, EaseType, ExpNotification, MoneyFormat, Moves, PlayerGender, UiTheme, Species} from "#enums";
import ArenaFlyout from "./ui/arena-flyout";
import { EaseType } from "./ui/enums/ease-type";
import { ExpNotification } from "./enums/exp-notification";
export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1";
@ -156,10 +147,10 @@ export default class BattleScene extends SceneBase {
public enableVibration: boolean = false;
/**
* Determines the selected battle style.
* - 0 = 'Shift'
* - 0 = 'Switch'
* - 1 = 'Set' - The option to switch the active pokemon at the start of a battle will not display.
*/
public battleStyle: integer = 0;
public battleStyle: integer = BattleStyle.SWITCH;
/**
* Defines whether or not to show type effectiveness hints
@ -1098,18 +1089,18 @@ export default class BattleScene extends SceneBase {
if (pokemon.hasAbility(Abilities.ICE_FACE)) {
pokemon.formIndex = 0;
}
pokemon.resetBattleData();
applyPostBattleInitAbAttrs(PostBattleInitAbAttr, pokemon);
}
this.unshiftPhase(new ShowTrainerPhase(this));
}
for (const pokemon of this.getParty()) {
if (pokemon) {
if (resetArenaState) {
pokemon.resetBattleData();
applyPostBattleInitAbAttrs(PostBattleInitAbAttr, pokemon, true);
}
this.triggerPokemonFormChange(pokemon, SpeciesFormChangeTimeOfDayTrigger);
}
this.triggerPokemonFormChange(pokemon, SpeciesFormChangeTimeOfDayTrigger);
}
if (!this.gameMode.hasRandomBiomes && !isNewBiome) {
this.pushPhase(new NextEncounterPhase(this));
} else {
@ -1922,6 +1913,8 @@ export default class BattleScene extends SceneBase {
}
if (!this.phaseQueue.length) {
this.populatePhaseQueue();
// clear the conditionalQueue if there are no phases left in the phaseQueue
this.conditionalQueue = [];
}
this.currentPhase = this.phaseQueue.shift();

View File

@ -3,12 +3,8 @@ import { EnemyPokemon, PlayerPokemon, QueuedMove } from "./field/pokemon";
import { Command } from "./ui/command-ui-handler";
import * as Utils from "./utils";
import Trainer, { TrainerVariant } from "./field/trainer";
import { Species } from "./data/enums/species";
import { Moves } from "./data/enums/moves";
import { TrainerType } from "./data/enums/trainer-type";
import { GameMode } from "./game-mode";
import { BattleSpec } from "./enums/battle-spec";
import { PlayerGender } from "./data/enums/player-gender";
import { BattleSpec, Moves, PlayerGender, Species, TrainerType } from "#enums";
import { MoneyMultiplierModifier, PokemonHeldItemModifier } from "./modifier/modifier";
import { PokeballType } from "./data/pokeball";
import {trainerConfigs} from "#app/data/trainer-config";

View File

@ -1,4 +1,4 @@
import {Button} from "#app/enums/buttons";
import {Button} from "#enums";
import {SettingKeyboard} from "#app/system/settings/settings-keyboard";
const cfg_keyboard_qwerty = {

View File

@ -1,4 +1,4 @@
import {Device} from "#app/enums/devices";
import {Device} from "#enums";
/**
* Retrieves the key associated with the specified keycode from the mapping.

View File

@ -1,5 +1,5 @@
import {SettingGamepad} from "../../system/settings/settings-gamepad";
import {Button} from "../../enums/buttons";
import {Button} from "#enums";
/**
* Dualshock mapping

View File

@ -1,5 +1,5 @@
import {SettingGamepad} from "../../system/settings/settings-gamepad";
import {Button} from "../../enums/buttons";
import {Button} from "#enums";
/**
* Generic pad mapping

View File

@ -1,5 +1,5 @@
import {SettingGamepad} from "#app/system/settings/settings-gamepad.js";
import {Button} from "#app/enums/buttons";
import {Button} from "#enums";
/**
* Nintendo Pro Controller mapping

View File

@ -1,5 +1,5 @@
import {SettingGamepad} from "../../system/settings/settings-gamepad";
import {Button} from "../../enums/buttons";
import {Button} from "#enums";
/**
* 081f-e401 - UnlicensedSNES

View File

@ -1,5 +1,5 @@
import {SettingGamepad} from "../../system/settings/settings-gamepad";
import {Button} from "#app/enums/buttons";
import {Button} from "#enums";
/**
* Generic pad mapping

View File

@ -6,23 +6,19 @@ import { MovePhase, PokemonHealPhase, ShowAbilityPhase, StatChangePhase } from "
import { getPokemonMessage, getPokemonNameWithAffix } from "../messages";
import { Weather, WeatherType } from "./weather";
import { BattlerTag } from "./battler-tags";
import { BattlerTagType } from "./enums/battler-tag-type";
import { ArenaTagType, Abilities, BattlerTagType, Moves, Species } from "#enums";
import { StatusEffect, getNonVolatileStatusEffects, getStatusEffectDescriptor, getStatusEffectHealText } from "./status-effect";
import { Gender } from "./gender";
import Move, { AttackMove, MoveCategory, MoveFlags, MoveTarget, FlinchAttr, OneHitKOAttr, HitHealAttr, allMoves, StatusMove, SelfStatusMove, VariablePowerAttr, applyMoveAttrs, IncrementMovePriorityAttr } from "./move";
import Move, { AttackMove, MoveCategory, MoveFlags, MoveTarget, FlinchAttr, OneHitKOAttr, HitHealAttr, allMoves, StatusMove, SelfStatusMove, VariablePowerAttr, applyMoveAttrs, IncrementMovePriorityAttr, VariableMoveTypeAttr, RandomMovesetMoveAttr, RandomMoveAttr, NaturePowerAttr, CopyMoveAttr } from "./move";
import { ArenaTagSide, ArenaTrapTag } from "./arena-tag";
import { ArenaTagType } from "./enums/arena-tag-type";
import { Stat, getStatName } from "./pokemon-stat";
import { BerryModifier, PokemonHeldItemModifier } from "../modifier/modifier";
import { Moves } from "./enums/moves";
import { TerrainType } from "./terrain";
import { SpeciesFormChangeManualTrigger } from "./pokemon-forms";
import { Abilities } from "./enums/abilities";
import i18next, { Localizable } from "#app/plugins/i18n.js";
import { Command } from "../ui/command-ui-handler";
import { BerryModifierType } from "#app/modifier/modifier-type";
import { getPokeballName } from "./pokeball";
import { Species } from "./enums/species";
import { BattlerIndex } from "#app/battle";
export class Ability implements Localizable {
@ -372,7 +368,7 @@ export class TypeImmunityAbAttr extends PreDefendAbAttr {
return false;
}
if (move.type === this.immuneType) {
if (attacker !== pokemon && move.type === this.immuneType) {
(args[0] as Utils.NumberHolder).value = 0;
return true;
}
@ -1085,21 +1081,20 @@ export class FieldMultiplyBattleStatAbAttr extends AbAttr {
}
export class MoveTypeChangeAttr extends PreAttackAbAttr {
private newType: Type;
private powerMultiplier: number;
private condition: PokemonAttackCondition;
constructor(newType: Type, powerMultiplier: number, condition: PokemonAttackCondition) {
constructor(
private newType: Type,
private powerMultiplier: number,
private condition?: PokemonAttackCondition
) {
super(true);
this.newType = newType;
this.powerMultiplier = powerMultiplier;
this.condition = condition;
}
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean {
if (this.condition(pokemon, defender, move)) {
if (this.condition && this.condition(pokemon, defender, move)) {
move.type = this.newType;
(args[0] as Utils.NumberHolder).value *= this.powerMultiplier;
if (args[0] && args[0] instanceof Utils.NumberHolder) {
args[0].value *= this.powerMultiplier;
}
return true;
}
@ -1107,6 +1102,58 @@ export class MoveTypeChangeAttr extends PreAttackAbAttr {
}
}
/** Ability attribute for changing a pokemon's type before using a move */
export class PokemonTypeChangeAbAttr extends PreAttackAbAttr {
private moveType: Type;
constructor() {
super(true);
}
applyPreAttack(pokemon: Pokemon, passive: boolean, defender: Pokemon, move: Move, args: any[]): boolean {
if (
!pokemon.isTerastallized() &&
move.id !== Moves.STRUGGLE &&
/**
* Skip moves that call other moves because these moves generate a following move that will trigger this ability attribute
* @see {@link https://bulbapedia.bulbagarden.net/wiki/Category:Moves_that_call_other_moves}
*/
!move.findAttr((attr) =>
attr instanceof RandomMovesetMoveAttr ||
attr instanceof RandomMoveAttr ||
attr instanceof NaturePowerAttr ||
attr instanceof CopyMoveAttr
)
) {
// TODO remove this copy when phase order is changed so that damage, type, category, etc.
// TODO are all calculated prior to playing the move animation.
const moveCopy = new Move(move.id, move.type, move.category, move.moveTarget, move.power, move.accuracy, move.pp, move.chance, move.priority, move.generation);
moveCopy.attrs = move.attrs;
// Moves like Weather Ball ignore effects of abilities like Normalize and Refrigerate
if (move.findAttr(attr => attr instanceof VariableMoveTypeAttr)) {
applyMoveAttrs(VariableMoveTypeAttr, pokemon, null, moveCopy);
} else {
applyPreAttackAbAttrs(MoveTypeChangeAttr, pokemon, null, moveCopy);
}
if (pokemon.getTypes().some((t) => t !== moveCopy.type)) {
this.moveType = moveCopy.type;
pokemon.summonData.types = [moveCopy.type];
pokemon.updateInfo();
return true;
}
}
return false;
}
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]): string {
return getPokemonMessage(pokemon, ` transformed into the ${Type[this.moveType]} type!`);
}
}
/**
* Class for abilities that boost the damage of moves
* For abilities that boost the base power of moves, see VariableMovePowerAbAttr
@ -3525,7 +3572,7 @@ export class IceFaceMoveImmunityAbAttr extends MoveImmunityAbAttr {
function applyAbAttrsInternal<TAttr extends AbAttr>(attrType: { new(...args: any[]): TAttr },
pokemon: Pokemon, applyFunc: AbAttrApplyFunc<TAttr>, args: any[], isAsync: boolean = false, showAbilityInstant: boolean = false, quiet: boolean = false, passive: boolean = false): Promise<void> {
return new Promise(resolve => {
if (!pokemon.canApplyAbility(passive, args[0])) {
if (!pokemon.canApplyAbility(passive)) {
if (!passive) {
return applyAbAttrsInternal(attrType, pokemon, applyFunc, args, isAsync, showAbilityInstant, quiet, true).then(() => resolve());
} else {
@ -3557,6 +3604,9 @@ function applyAbAttrsInternal<TAttr extends AbAttr>(attrType: { new(...args: any
}
pokemon.scene.setPhaseQueueSplice();
const onApplySuccess = () => {
if (pokemon.summonData && !pokemon.summonData.abilitiesApplied.includes(ability.id)) {
pokemon.summonData.abilitiesApplied.push(ability.id);
}
if (pokemon.battleData && !pokemon.battleData.abilitiesApplied.includes(ability.id)) {
pokemon.battleData.abilitiesApplied.push(ability.id);
}
@ -4039,8 +4089,9 @@ export function initAbilities() {
.conditionalAttr(pokemon => pokemon.status ? pokemon.status.effect === StatusEffect.PARALYSIS : false, BattleStatMultiplierAbAttr, BattleStat.SPD, 2)
.conditionalAttr(pokemon => !!pokemon.status || pokemon.hasAbility(Abilities.COMATOSE), BattleStatMultiplierAbAttr, BattleStat.SPD, 1.5),
new Ability(Abilities.NORMALIZE, 4)
.attr(MoveTypeChangeAttr, Type.NORMAL, 1.2, (user, target, move) => move.id !== Moves.HIDDEN_POWER && move.id !== Moves.WEATHER_BALL &&
move.id !== Moves.NATURAL_GIFT && move.id !== Moves.JUDGMENT && move.id !== Moves.TECHNO_BLAST),
.attr(MoveTypeChangeAttr, Type.NORMAL, 1.2, (user, target, move) => {
return ![Moves.HIDDEN_POWER, Moves.WEATHER_BALL, Moves.NATURAL_GIFT, Moves.JUDGMENT, Moves.TECHNO_BLAST].includes(move.id);
}),
new Ability(Abilities.SNIPER, 4)
.attr(MultCritAbAttr, 1.5),
new Ability(Abilities.MAGIC_GUARD, 4)
@ -4238,7 +4289,8 @@ export function initAbilities() {
.attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr)
.attr(UnsuppressableAbilityAbAttr)
.attr(NoFusionAbilityAbAttr),
.attr(NoFusionAbilityAbAttr)
.bypassFaint(),
new Ability(Abilities.VICTORY_STAR, 5)
.attr(BattleStatMultiplierAbAttr, BattleStat.ACC, 1.1)
.partial(),
@ -4258,7 +4310,8 @@ export function initAbilities() {
.attr(HealFromBerryUseAbAttr, 1/3)
.partial(), // Healing not blocked by Heal Block
new Ability(Abilities.PROTEAN, 6)
.unimplemented(),
.attr(PokemonTypeChangeAbAttr)
.condition((p) => !p.summonData?.abilitiesApplied.includes(Abilities.PROTEAN)),
new Ability(Abilities.FUR_COAT, 6)
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => move.category === MoveCategory.PHYSICAL, 0.5)
.ignorable(),
@ -4347,6 +4400,7 @@ export function initAbilities() {
.attr(UnswappableAbilityAbAttr)
.attr(UnsuppressableAbilityAbAttr)
.attr(NoFusionAbilityAbAttr)
.bypassFaint()
.partial(),
new Ability(Abilities.STAKEOUT, 7)
.attr(MovePowerBoostAbAttr, (user, target, move) => user.scene.currentBattle.turnCommands[target.getBattlerIndex()].command === Command.POKEMON, 2),
@ -4379,7 +4433,8 @@ export function initAbilities() {
.attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr)
.attr(UnsuppressableAbilityAbAttr)
.attr(NoFusionAbilityAbAttr),
.attr(NoFusionAbilityAbAttr)
.bypassFaint(),
new Ability(Abilities.DISGUISE, 7)
.attr(PreDefendMovePowerToOneAbAttr, (target, user, move) => target.formIndex === 0 && target.getAttackTypeEffectiveness(move.type, user) > 0)
.attr(PostSummonFormChangeAbAttr, p => p.battleData.hitCount === 0 ? 0 : 1)
@ -4392,6 +4447,7 @@ export function initAbilities() {
.attr(UnsuppressableAbilityAbAttr)
.attr(NoTransformAbilityAbAttr)
.attr(NoFusionAbilityAbAttr)
.bypassFaint()
.ignorable()
.partial(),
new Ability(Abilities.BATTLE_BOND, 7)
@ -4400,7 +4456,8 @@ export function initAbilities() {
.attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr)
.attr(UnsuppressableAbilityAbAttr)
.attr(NoFusionAbilityAbAttr),
.attr(NoFusionAbilityAbAttr)
.bypassFaint(),
new Ability(Abilities.POWER_CONSTRUCT, 7) // TODO: 10% Power Construct Zygarde isn't accounted for yet. If changed, update Zygarde's getSpeciesFormIndex entry accordingly
.attr(PostBattleInitFormChangeAbAttr, () => 2)
.attr(PostSummonFormChangeAbAttr, p => p.getHpRatio() <= 0.5 || p.getFormKey() === "complete" ? 4 : 2)
@ -4409,6 +4466,7 @@ export function initAbilities() {
.attr(UnswappableAbilityAbAttr)
.attr(UnsuppressableAbilityAbAttr)
.attr(NoFusionAbilityAbAttr)
.bypassFaint()
.partial(),
new Ability(Abilities.CORROSION, 7) // TODO: Test Corrosion against Magic Bounce once it is implemented
.attr(IgnoreTypeStatusEffectImmunityAbAttr, [StatusEffect.POISON, StatusEffect.TOXIC], [Type.STEEL, Type.POISON])
@ -4492,7 +4550,8 @@ export function initAbilities() {
.attr(PostSummonStatChangeAbAttr, BattleStat.DEF, 1, true)
.condition(getOncePerBattleCondition(Abilities.DAUNTLESS_SHIELD)),
new Ability(Abilities.LIBERO, 8)
.unimplemented(),
.attr(PokemonTypeChangeAbAttr)
.condition((p) => !p.summonData?.abilitiesApplied.includes(Abilities.LIBERO)),
new Ability(Abilities.BALL_FETCH, 8)
.attr(FetchBallAbAttr)
.condition(getOncePerBattleCondition(Abilities.BALL_FETCH)),
@ -4639,7 +4698,8 @@ export function initAbilities() {
.attr(NoTransformAbilityAbAttr)
.attr(NoFusionAbilityAbAttr)
.attr(PostBattleInitFormChangeAbAttr, () => 0)
.attr(PreSwitchOutFormChangeAbAttr, () => 1),
.attr(PreSwitchOutFormChangeAbAttr, () => 1)
.bypassFaint(),
new Ability(Abilities.COMMANDER, 9)
.attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr)

View File

@ -6,11 +6,9 @@ import PokemonSpecies, { PokemonForm, SpeciesFormKey, allSpecies } from "./pokem
import { GrowthRate } from "./exp";
import { Type } from "./type";
import { allAbilities } from "./ability";
import { Abilities } from "./enums/abilities";
import { Species } from "./enums/species";
import { Abilities, Moves, Species } from "#enums";
import { pokemonFormLevelMoves } from "./pokemon-level-moves";
import { tmSpecies } from "./tms";
import { Moves } from "./enums/moves";
const targetMap = {
"specific-move": MoveTarget.ATTACKER,

View File

@ -7,13 +7,10 @@ import Pokemon, { HitResult, PokemonMove } from "../field/pokemon";
import { MoveEffectPhase, PokemonHealPhase, ShowAbilityPhase, StatChangePhase} from "../phases";
import { StatusEffect } from "./status-effect";
import { BattlerIndex } from "../battle";
import { Moves } from "./enums/moves";
import { ArenaTagType } from "./enums/arena-tag-type";
import { Abilities, ArenaTagType, BattlerTagType, Moves } from "#enums";
import { BlockNonDirectDamageAbAttr, ProtectStatAbAttr, applyAbAttrs } from "./ability";
import { BattleStat } from "./battle-stat";
import { CommonAnim, CommonBattleAnim } from "./battle-anims";
import { Abilities } from "./enums/abilities";
import { BattlerTagType } from "./enums/battler-tag-type";
import i18next from "i18next";
export enum ArenaTagSide {

View File

@ -5,7 +5,7 @@ import Pokemon from "../field/pokemon";
import * as Utils from "../utils";
import { BattlerIndex } from "../battle";
import { Element } from "json-stable-stringify";
import { Moves } from "./enums/moves";
import { Moves } from "#enums";
//import fs from 'vite-plugin-fs/browser';
export enum AnimFrameTarget {

View File

@ -5,18 +5,15 @@ import Pokemon, { MoveResult, HitResult } from "../field/pokemon";
import { Stat, getStatName } from "./pokemon-stat";
import { StatusEffect } from "./status-effect";
import * as Utils from "../utils";
import { Moves } from "./enums/moves";
import { ChargeAttr, MoveFlags, allMoves } from "./move";
import { Type } from "./type";
import { BlockNonDirectDamageAbAttr, FlinchEffectAbAttr, ReverseDrainAbAttr, applyAbAttrs } from "./ability";
import { Abilities } from "./enums/abilities";
import { BattlerTagType } from "./enums/battler-tag-type";
import { Abilities, BattlerTagType, Moves, Species } from "#enums";
import { TerrainType } from "./terrain";
import { WeatherType } from "./weather";
import { BattleStat } from "./battle-stat";
import { allAbilities } from "./ability";
import { SpeciesFormChangeManualTrigger } from "./pokemon-forms";
import { Species } from "./enums/species";
export enum BattlerTagLapseType {
FAINT,

View File

@ -2,12 +2,11 @@ import { PokemonHealPhase, StatChangePhase } from "../phases";
import { getPokemonMessage } from "../messages";
import Pokemon, { HitResult } from "../field/pokemon";
import { BattleStat } from "./battle-stat";
import { BattlerTagType } from "./enums/battler-tag-type";
import { BattlerTagType, BerryType } from "#enums";
import { getStatusEffectHealText } from "./status-effect";
import * as Utils from "../utils";
import { DoubleBerryEffectAbAttr, ReduceBerryUseThresholdAbAttr, applyAbAttrs } from "./ability";
import i18next from "../plugins/i18n";
import { BerryType } from "./enums/berry-type";
export function getBerryName(berryType: BerryType): string {
return i18next.t(`berry:${BerryType[berryType]}.name`);

View File

@ -1,10 +1,7 @@
import { Species } from "./enums/species";
import { Type } from "./type";
import * as Utils from "../utils";
import beautify from "json-beautify";
import { TrainerType } from "./enums/trainer-type";
import { TimeOfDay } from "./enums/time-of-day";
import { Biome } from "./enums/biome";
import { Biome, Species, TimeOfDay, TrainerType } from "#enums";
import {pokemonEvolutions, SpeciesFormEvolution} from "./pokemon-evolutions";
import i18next from "i18next";

View File

@ -1,14 +1,12 @@
import * as Utils from "../utils";
import { Challenges } from "./enums/challenges";
import { Challenges, TrainerType, Species } from "#enums";
import i18next from "#app/plugins/i18n.js";
import { GameData } from "#app/system/game-data.js";
import PokemonSpecies, { getPokemonSpecies, speciesStarters } from "./pokemon-species";
import Pokemon from "#app/field/pokemon.js";
import { BattleType, FixedBattleConfig } from "#app/battle.js";
import { TrainerType } from "./enums/trainer-type";
import Trainer, { TrainerVariant } from "#app/field/trainer.js";
import { GameMode } from "#app/game-mode.js";
import { Species } from "./enums/species";
import { Type } from "./type";
/**

View File

@ -2,9 +2,8 @@ import BattleScene from "../battle-scene";
import { PlayerPokemon } from "../field/pokemon";
import { Starter } from "../ui/starter-select-ui-handler";
import * as Utils from "../utils";
import { Species } from "./enums/species";
import PokemonSpecies, { PokemonSpeciesForm, getPokemonSpecies, getPokemonSpeciesForm, speciesStarters } from "./pokemon-species";
import { PartyMemberStrength } from "./enums/party-member-strength";
import { PartyMemberStrength, Species } from "#enums";
export interface DailyRunConfig {
seed: integer;

View File

@ -1,6 +1,5 @@
import {trainerConfigs} from "./trainer-config";
import {TrainerType} from "./enums/trainer-type";
import {BattleSpec} from "../enums/battle-spec";
import {BattleSpec, TrainerType} from "#enums";
export interface TrainerTypeMessages {
encounter?: string | string[],

View File

@ -1,5 +1,4 @@
import { Moves } from "./enums/moves";
import { Species } from "./enums/species";
import { Moves, Species } from "#enums";
import { allMoves } from "./move";
import * as Utils from "../utils";

View File

@ -1,7 +1,6 @@
import BattleScene from "../battle-scene";
import { Species } from "./enums/species";
import PokemonSpecies, { getPokemonSpecies, speciesStarters } from "./pokemon-species";
import { EggTier } from "./enums/egg-type";
import { EggTier, Species } from "#enums";
import i18next from "../plugins/i18n";
export const EGG_SEED = 1073741824;

View File

@ -1,9 +1,7 @@
import { Moves } from "./enums/moves";
import { ChargeAnim, MoveChargeAnim, initMoveAnim, loadMoveAnimAssets } from "./battle-anims";
import { BattleEndPhase, MovePhase, NewBattlePhase, PartyStatusCurePhase, PokemonHealPhase, StatChangePhase, SwitchSummonPhase } from "../phases";
import { BattleStat, getBattleStatName } from "./battle-stat";
import { EncoreTag } from "./battler-tags";
import { BattlerTagType } from "./enums/battler-tag-type";
import { getPokemonMessage } from "../messages";
import Pokemon, { AttackMoveResult, EnemyPokemon, HitResult, MoveResult, PlayerPokemon, PokemonMove, TurnMove } from "../field/pokemon";
import { StatusEffect, getStatusEffectHealText, isNonVolatileStatusEffect, getNonVolatileStatusEffects} from "./status-effect";
@ -11,19 +9,16 @@ import { Type } from "./type";
import * as Utils from "../utils";
import { WeatherType } from "./weather";
import { ArenaTagSide, ArenaTrapTag } from "./arena-tag";
import { ArenaTagType } from "./enums/arena-tag-type";
import { UnswappableAbilityAbAttr, UncopiableAbilityAbAttr, UnsuppressableAbilityAbAttr, BlockRecoilDamageAttr, BlockOneHitKOAbAttr, IgnoreContactAbAttr, MaxMultiHitAbAttr, applyAbAttrs, BlockNonDirectDamageAbAttr, applyPreSwitchOutAbAttrs, PreSwitchOutAbAttr, applyPostDefendAbAttrs, PostDefendContactApplyStatusEffectAbAttr, MoveAbilityBypassAbAttr, ReverseDrainAbAttr, FieldPreventExplosiveMovesAbAttr, ForceSwitchOutImmunityAbAttr, BlockItemTheftAbAttr, applyPostAttackAbAttrs, ConfusionOnStatusEffectAbAttr, HealFromBerryUseAbAttr } from "./ability";
import { Abilities } from "./enums/abilities";
import { Abilities, ArenaTagType, BattlerTagType, Biome, Moves, Species } from "#enums";
import { allAbilities } from "./ability";
import { PokemonHeldItemModifier, BerryModifier, PreserveBerryModifier } from "../modifier/modifier";
import { BattlerIndex } from "../battle";
import { Stat } from "./pokemon-stat";
import { TerrainType } from "./terrain";
import { SpeciesFormChangeActiveTrigger } from "./pokemon-forms";
import { Species } from "./enums/species";
import { ModifierPoolType } from "#app/modifier/modifier-type";
import { Command } from "../ui/command-ui-handler";
import { Biome } from "./enums/biome";
import i18next, { Localizable } from "../plugins/i18n";
import { getBerryEffectFunc } from "./berry";
@ -1797,7 +1792,7 @@ export class RemoveHeldItemAttr extends MoveEffectAttr {
}
/**
* Attribute that causes targets of the move to eat a berry. If chosenBerry is not overriden, a random berry will be picked from the target's inventory.
* Attribute that causes targets of the move to eat a berry. If chosenBerry is not overridden, a random berry will be picked from the target's inventory.
*/
export class EatBerryAttr extends MoveEffectAttr {
protected chosenBerry: BerryModifier;

View File

@ -1,7 +1,7 @@
import { Stat, getStatName } from "./pokemon-stat";
import * as Utils from "../utils";
import { TextStyle, getBBCodeFrag } from "../ui/text";
import { UiTheme } from "#app/enums/ui-theme";
import { UiTheme } from "#enums";
import i18next from "i18next";
export enum Nature {

View File

@ -1,15 +1,12 @@
import { Gender } from "./gender";
import { Moves } from "./enums/moves";
import { PokeballType } from "./pokeball";
import Pokemon from "../field/pokemon";
import { Stat } from "./pokemon-stat";
import { Species } from "./enums/species";
import { Type } from "./type";
import * as Utils from "../utils";
import { SpeciesFormKey } from "./pokemon-species";
import { WeatherType } from "./weather";
import { Biome } from "./enums/biome";
import { TimeOfDay } from "./enums/time-of-day";
import { Biome, Moves, Species, TimeOfDay } from "#enums";
import { Nature } from "./nature";
export enum SpeciesWildEvolutionDelay {

View File

@ -1,12 +1,9 @@
import { TimeOfDay } from "./enums/time-of-day";
import { PokemonFormChangeItemModifier } from "../modifier/modifier";
import Pokemon from "../field/pokemon";
import { Moves } from "./enums/moves";
import { SpeciesFormKey } from "./pokemon-species";
import { Species } from "./enums/species";
import { StatusEffect } from "./status-effect";
import { MoveCategory, allMoves } from "./move";
import { Abilities } from "./enums/abilities";
import { Abilities, Moves, Species, TimeOfDay } from "#enums";
export enum FormChangeItem {
NONE,

View File

@ -1,5 +1,4 @@
import { Moves } from "./enums/moves";
import { Species } from "./enums/species";
import { Moves, Species } from "#enums";
export type LevelMoves = ([integer, Moves])[];

View File

@ -1,17 +1,15 @@
import { Abilities } from "./enums/abilities";
import { Abilities, PartyMemberStrength, Species } from "#enums";
import BattleScene, { AnySound } from "../battle-scene";
import { Variant, variantColorCache } from "./variant";
import { variantData } from "./variant";
import { GrowthRate } from "./exp";
import { SpeciesWildEvolutionDelay, pokemonEvolutions, pokemonPrevolutions } from "./pokemon-evolutions";
import { Species } from "./enums/species";
import { Type } from "./type";
import { LevelMoves, pokemonFormLevelMoves, pokemonFormLevelMoves as pokemonSpeciesFormLevelMoves, pokemonSpeciesLevelMoves } from "./pokemon-level-moves";
import { uncatchableSpecies } from "./biomes";
import * as Utils from "../utils";
import { StarterMoveset } from "../system/game-data";
import { speciesEggMoves } from "./egg-moves";
import { PartyMemberStrength } from "./enums/party-member-strength";
import { GameMode } from "../game-mode";
import { QuantizerCelebi, argbFromRgba, rgbaFromArgb } from "@material/material-color-utilities";
import { VariantSet } from "./variant";

View File

@ -1,6 +1,5 @@
import { ModifierTier } from "../modifier/modifier-tier";
import { Moves } from "./enums/moves";
import { Species } from "./enums/species";
import { Moves, Species } from "#enums";
interface TmSpecies {
[key: integer]: Array<Species | Array<Species | string>>

View File

@ -2,18 +2,15 @@ import BattleScene, {startingWave} from "../battle-scene";
import {ModifierTypeFunc, modifierTypes} from "../modifier/modifier-type";
import {EnemyPokemon} from "../field/pokemon";
import * as Utils from "../utils";
import {TrainerType} from "./enums/trainer-type";
import {Moves} from "./enums/moves";
import {PokeballType} from "./pokeball";
import {pokemonEvolutions, pokemonPrevolutions} from "./pokemon-evolutions";
import PokemonSpecies, {getPokemonSpecies, PokemonSpeciesFilter} from "./pokemon-species";
import {Species} from "./enums/species";
import {tmSpecies} from "./tms";
import {Type} from "./type";
import {doubleBattleDialogue} from "./dialogue";
import {PersistentModifier} from "../modifier/modifier";
import {TrainerVariant} from "../field/trainer";
import {PartyMemberStrength} from "./enums/party-member-strength";
import {Moves, PartyMemberStrength, Species, TrainerType} from "#enums";
import {getIsInitialized, initI18n} from "#app/plugins/i18n";
import i18next from "i18next";

View File

@ -1,4 +1,4 @@
import { TrainerType } from "./enums/trainer-type";
import { TrainerType } from "#enums";
import * as Utils from "../utils";
class TrainerNameConfig {

View File

@ -1,4 +1,4 @@
import { Biome } from "./enums/biome";
import { Biome } from "#enums";
import { getPokemonMessage, getPokemonNameWithAffix } from "../messages";
import Pokemon from "../field/pokemon";
import { Type } from "./type";

View File

@ -5,12 +5,11 @@ import * as Utils from "./utils";
import { Mode } from "./ui/ui";
import { EGG_SEED, Egg, GachaType, getLegendaryGachaSpeciesForTimestamp } from "./data/egg";
import EggHatchSceneHandler from "./ui/egg-hatch-scene-handler";
import { Species } from "./data/enums/species";
import { PlayerPokemon } from "./field/pokemon";
import { getPokemonSpecies, speciesStarters } from "./data/pokemon-species";
import { achvs } from "./system/achv";
import { pokemonPrevolutions } from "./data/pokemon-evolutions";
import { EggTier } from "./data/enums/egg-type";
import { EggTier, Species } from "#enums";
import PokemonInfoContainer from "./ui/pokemon-info-container";
import EggCounterContainer from "./ui/egg-counter-container";
import { EggCountChangedEvent } from "./events/egg";

View File

@ -0,0 +1,9 @@
/**
* Determines the selected battle style.
* - 'Switch' - The option to switch the active pokemon at the start of a battle will be displayed.
* - 'Set' - The option to switch the active pokemon at the start of a battle will not display.
*/
export enum BattleStyle {
SWITCH,
SET
}

25
src/enums/index.ts Normal file
View File

@ -0,0 +1,25 @@
export * from "./abilities";
export * from "./arena-tag-type";
export * from "./battle-spec";
export * from "./battle-style";
export * from "./battler-tag-type";
export * from "./berry-type";
export * from "./biome";
export * from "./buttons";
export * from "./challenges";
export * from "./devices";
export * from "./ease-type";
export * from "./egg-type";
export * from "./exp-notification";
export * from "./game-data-type";
export * from "./money-format";
export * from "./moves";
export * from "./money-format";
export * from "./party-member-strength";
export * from "./passive";
export * from "./player-gender";
export * from "./species";
export * from "./time-of-day";
export * from "./trainer-type";
export * from "./ui-theme";

View File

@ -1,5 +1,5 @@
import { ArenaTagSide } from "#app/data/arena-tag.js";
import { ArenaTagType } from "#app/data/enums/arena-tag-type.js";
import { ArenaTagType } from "#enums";
import { TerrainType } from "#app/data/terrain.js";
import { WeatherType } from "#app/data/weather.js";

View File

@ -1,25 +1,20 @@
import BattleScene from "../battle-scene";
import { BiomePoolTier, PokemonPools, BiomeTierTrainerPools, biomePokemonPools, biomeTrainerPools } from "../data/biomes";
import { Biome } from "../data/enums/biome";
import { ArenaTagType, Biome, Moves, Species, TimeOfDay, TrainerType } from "#enums";
import * as Utils from "../utils";
import PokemonSpecies, { getPokemonSpecies } from "../data/pokemon-species";
import { Species } from "../data/enums/species";
import { Weather, WeatherType, getTerrainClearMessage, getTerrainStartMessage, getWeatherClearMessage, getWeatherStartMessage } from "../data/weather";
import { CommonAnimPhase } from "../phases";
import { CommonAnim } from "../data/battle-anims";
import { Type } from "../data/type";
import Move from "../data/move";
import { ArenaTag, ArenaTagSide, getArenaTag } from "../data/arena-tag";
import { ArenaTagType } from "../data/enums/arena-tag-type";
import { TrainerType } from "../data/enums/trainer-type";
import { BattlerIndex } from "../battle";
import { Moves } from "../data/enums/moves";
import { TimeOfDay } from "../data/enums/time-of-day";
import { Terrain, TerrainType } from "../data/terrain";
import { PostTerrainChangeAbAttr, PostWeatherChangeAbAttr, applyPostTerrainChangeAbAttrs, applyPostWeatherChangeAbAttrs } from "../data/ability";
import Pokemon from "./pokemon";
import * as Overrides from "../overrides";
import { WeatherChangedEvent, TerrainChangedEvent, TagAddedEvent, TagRemovedEvent } from "./events/arena";
import { WeatherChangedEvent, TerrainChangedEvent, TagAddedEvent, TagRemovedEvent } from "../events/arena";
export class Arena {
public scene: BattleScene;

View File

@ -3,7 +3,6 @@ import BattleScene, { AnySound } from "../battle-scene";
import { Variant, VariantSet, variantColorCache } from "#app/data/variant";
import { variantData } from "#app/data/variant";
import BattleInfo, { PlayerBattleInfo, EnemyBattleInfo } from "../ui/battle-info";
import { Moves } from "../data/enums/moves";
import Move, { HighCritAttr, HitsTagAttr, applyMoveAttrs, FixedDamageAttr, VariableAtkAttr, VariablePowerAttr, allMoves, MoveCategory, TypelessAttr, CritOnlyAttr, getMoveTargets, OneHitKOAttr, MultiHitAttr, VariableMoveTypeAttr, StatusMoveTypeImmunityAttr, MoveTarget, VariableDefAttr, AttackMove, ModifiedDamageAttr, VariableMoveTypeMultiplierAttr, IgnoreOpponentStatChangesAttr, SacrificialAttr, VariableMoveCategoryAttr, CounterDamageAttr, StatChangeAttr, RechargeAttr, ChargeAttr, IgnoreWeatherTypeDebuffAttr, BypassBurnDamageReductionAttr, SacrificialAttrOnHit, MoveFlags } from "../data/move";
import { default as PokemonSpecies, PokemonSpeciesForm, SpeciesFormKey, getFusedSpeciesName, getPokemonSpecies, getPokemonSpeciesForm, getStarterValueFriendshipCap, speciesStarters, starterPassiveAbilities } from "../data/pokemon-species";
import * as Utils from "../utils";
@ -20,18 +19,13 @@ import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "../data/tms";
import { DamagePhase, FaintPhase, LearnMovePhase, ObtainStatusEffectPhase, StatChangePhase, SwitchSummonPhase, ToggleDoublePositionPhase } from "../phases";
import { BattleStat } from "../data/battle-stat";
import { BattlerTag, BattlerTagLapseType, EncoreTag, HelpingHandTag, HighestStatBoostTag, TypeBoostTag, getBattlerTag } from "../data/battler-tags";
import { BattlerTagType } from "../data/enums/battler-tag-type";
import { Species } from "../data/enums/species";
import { WeatherType } from "../data/weather";
import { TempBattleStat } from "../data/temp-battle-stat";
import { ArenaTagSide, WeakenMoveScreenTag, WeakenMoveTypeTag } from "../data/arena-tag";
import { ArenaTagType } from "../data/enums/arena-tag-type";
import { Biome } from "../data/enums/biome";
import { Abilities, ArenaTagType, Moves, BattlerTagType, Species, Biome, BattleSpec, BerryType } from "#enums";
import { Ability, AbAttr, BattleStatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, FieldVariableMovePowerAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, MoveTypeChangeAttr, PreApplyBattlerTagAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, VariableMovePowerAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldBattleStatMultiplierAbAttrs, FieldMultiplyBattleStatAbAttr } from "../data/ability";
import { Abilities } from "#app/data/enums/abilities";
import PokemonData from "../system/pokemon-data";
import { BattlerIndex } from "../battle";
import { BattleSpec } from "../enums/battle-spec";
import { Mode } from "../ui/ui";
import PartyUiHandler, { PartyOption, PartyUiMode } from "../ui/party-ui-handler";
import SoundFade from "phaser3-rex-plugins/plugins/soundfade";
@ -44,7 +38,6 @@ import { SpeciesFormChange, SpeciesFormChangeActiveTrigger, SpeciesFormChangeMov
import { TerrainType } from "../data/terrain";
import { TrainerSlot } from "../data/trainer-config";
import * as Overrides from "../overrides";
import { BerryType } from "../data/enums/berry-type";
import i18next from "../plugins/i18n";
import { speciesEggMoves } from "../data/egg-moves";
import { ModifierTier } from "../modifier/modifier-tier";
@ -1004,7 +997,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* @param {boolean} passive If true, check if passive can be applied instead of non-passive
* @returns {Ability} The passive ability of the pokemon
*/
canApplyAbility(passive: boolean = false, forceBypass: boolean = false): boolean {
canApplyAbility(passive: boolean = false): boolean {
if (passive && !this.hasPassive()) {
return false;
}
@ -1032,7 +1025,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return false;
}
}
return (this.hp || ability.isBypassFaint || forceBypass) && !ability.conditions.find(condition => !condition(this));
return (this.hp || ability.isBypassFaint) && !ability.conditions.find(condition => !condition(this));
}
/**
@ -3811,6 +3804,7 @@ export class PokemonSummonData {
public disabledTurns: integer = 0;
public tags: BattlerTag[] = [];
public abilitySuppressed: boolean = false;
public abilitiesApplied: Abilities[] = [];
public speciesForm: PokemonSpeciesForm;
public fusionSpeciesForm: PokemonSpeciesForm;
@ -3819,7 +3813,8 @@ export class PokemonSummonData {
public fusionGender: Gender;
public stats: integer[];
public moveset: PokemonMove[];
public types: Type[];
// If not initialized this value will not be populated from save data.
public types: Type[] = null;
}
export class PokemonBattleData {
@ -3831,7 +3826,9 @@ export class PokemonBattleData {
}
export class PokemonBattleSummonData {
/** The number of turns the pokemon has passed since entering the battle */
public turnCount: integer = 1;
/** The list of moves the pokemon has used since entering the battle */
public moveHistory: TurnMove[] = [];
}

View File

@ -11,8 +11,7 @@ import {
trainerPartyTemplates,
signatureSpecies
} from "../data/trainer-config";
import {PartyMemberStrength} from "../data/enums/party-member-strength";
import {TrainerType} from "../data/enums/trainer-type";
import {PartyMemberStrength, Species, TrainerType} from "#enums";
import {EnemyPokemon} from "./pokemon";
import * as Utils from "../utils";
import {PersistentModifier} from "../modifier/modifier";
@ -20,7 +19,6 @@ import {trainerNamePools} from "../data/trainer-names";
import {ArenaTagSide, ArenaTrapTag} from "#app/data/arena-tag";
import {getIsInitialized, initI18n} from "#app/plugins/i18n";
import i18next from "i18next";
import {Species} from "#app/data/enums/species";
export enum TrainerVariant {
DEFAULT,

View File

@ -7,7 +7,7 @@ import { EndEvolutionPhase, EvolutionPhase } from "./evolution-phase";
import Pokemon, { EnemyPokemon, PlayerPokemon } from "./field/pokemon";
import { Mode } from "./ui/ui";
import PartyUiHandler from "./ui/party-ui-handler";
import { BattleSpec } from "./enums/battle-spec";
import { BattleSpec } from "#enums";
import { BattlePhase, MovePhase, PokemonHealPhase } from "./phases";
import { getTypeRgb } from "./data/type";

View File

@ -2,8 +2,7 @@ import i18next from "i18next";
import { classicFixedBattles, FixedBattleConfig, FixedBattleConfigs } from "./battle";
import BattleScene from "./battle-scene";
import { allChallenges, applyChallenges, Challenge, ChallengeType, copyChallenge } from "./data/challenge";
import { Biome } from "./data/enums/biome";
import { Species } from "./data/enums/species";
import { Biome, Species } from "#enums";
import PokemonSpecies, { allSpecies } from "./data/pokemon-species";
import { Arena } from "./field/arena";
import * as Overrides from "./overrides";

View File

@ -6,12 +6,11 @@ import pad_unlicensedSNES from "./configs/inputs/pad_unlicensedSNES";
import pad_xbox360 from "./configs/inputs/pad_xbox360";
import pad_dualshock from "./configs/inputs/pad_dualshock";
import pad_procon from "./configs/inputs/pad_procon";
import {Button} from "./enums/buttons";
import {Mode} from "./ui/ui";
import SettingsGamepadUiHandler from "./ui/settings/settings-gamepad-ui-handler";
import SettingsKeyboardUiHandler from "./ui/settings/settings-keyboard-ui-handler";
import cfg_keyboard_qwerty from "./configs/inputs/cfg_keyboard_qwerty";
import {Device} from "#app/enums/devices";
import {Button, Device} from "#enums";
import {
assign,
getButtonWithKeycode,

View File

@ -1,6 +1,5 @@
import { GachaType } from "./data/egg";
import { Biome } from "./data/enums/biome";
import { TrainerType } from "./data/enums/trainer-type";
import { Biome, TrainerType } from "#enums";
import { trainerConfigs } from "./data/trainer-config";
import { getBiomeHasProps } from "./field/arena";
import CacheBustedLoaderPlugin from "./plugins/cache-busted-loader-plugin";

View File

@ -2931,7 +2931,7 @@ export const move: MoveTranslationEntries = {
},
"bouncyBubble": {
name: "Blubbsauger",
effect: "Evoli greift mit Wasserblasen an. Evolis KP werden um die Hälfte des vom Wasser angerichteten Schadens geheilt."
effect: "Der Anwender greift mit Wasserblasen an. Seine KP werden in Höhe des vom Wasser angerichteten Schadens geheilt."
},
"buzzyBuzz": {
name: "Knisterladung",

View File

@ -2931,7 +2931,7 @@ export const move: MoveTranslationEntries = {
},
"bouncyBubble": {
name: "Bouncy Bubble",
effect: "The user attacks by shooting water bubbles at the target. It then absorbs water and restores its HP by half the damage taken by the target."
effect: "The user attacks by shooting water bubbles at the target. It then absorbs water and restores its HP by the damage taken by the target."
},
"buzzyBuzz": {
name: "Buzzy Buzz",

View File

@ -2931,7 +2931,7 @@ export const move: MoveTranslationEntries = {
},
bouncyBubble: {
name: "Bouncy Bubble",
effect: "O usuário ataca atirando bolhas de água no alvo. Em seguida, absorve água e restaura seu HP pela metade do dano causado ao alvo."
effect: "O usuário ataca atirando bolhas de água no alvo. Em seguida, absorve água e restaura seu HP pelo mesmo valor de dano causado ao alvo."
},
buzzyBuzz: {
name: "Buzzy Buzz",

View File

@ -1,4 +1,4 @@
import { BattleSpec } from "./enums/battle-spec";
import { BattleSpec } from "#enums";
import Pokemon from "./field/pokemon";
import i18next from "./plugins/i18n";

View File

@ -1,7 +1,6 @@
import * as Modifiers from "./modifier";
import { AttackMove, allMoves } from "../data/move";
import { Moves } from "../data/enums/moves";
import { Abilities } from "../data/enums/abilities";
import { Abilities, BattlerTagType, BerryType, Moves } from "#enums";
import { PokeballType, getPokeballCatchMultiplier, getPokeballName } from "../data/pokeball";
import Pokemon, { EnemyPokemon, PlayerPokemon, PokemonMove } from "../field/pokemon";
import { EvolutionItem, pokemonEvolutions } from "../data/pokemon-evolutions";
@ -12,7 +11,6 @@ import PartyUiHandler, { PokemonMoveSelectFilter, PokemonSelectFilter } from "..
import * as Utils from "../utils";
import { TempBattleStat, getTempBattleStatBoosterItemName, getTempBattleStatName } from "../data/temp-battle-stat";
import { getBerryEffectDescription, getBerryName } from "../data/berry";
import { BerryType } from "../data/enums/berry-type";
import { Unlockables } from "../system/unlockables";
import { StatusEffect, getStatusEffectDescriptor } from "../data/status-effect";
import { SpeciesFormKey } from "../data/pokemon-species";
@ -23,7 +21,6 @@ import { ModifierTier } from "./modifier-tier";
import { Nature, getNatureName, getNatureStatMultiplier } from "#app/data/nature";
import i18next from "#app/plugins/i18n";
import { getModifierTierTextTint } from "#app/ui/text";
import { BattlerTagType } from "#app/data/enums/battler-tag-type.js";
import * as Overrides from "../overrides";
import { MoneyMultiplierModifier } from "./modifier";
@ -1343,13 +1340,13 @@ const modifierPool: ModifierPool = {
new WeightedModifierType(modifierTypes.RARE_EVOLUTION_ITEM, (party: Pokemon[]) => Math.min(Math.ceil(party[0].scene.currentBattle.waveIndex / 15) * 4, 32), 32),
new WeightedModifierType(modifierTypes.AMULET_COIN, 3),
new WeightedModifierType(modifierTypes.TOXIC_ORB, (party: Pokemon[]) => {
const checkedAbilities = [Abilities.QUICK_FEET, Abilities.GUTS, Abilities.MARVEL_SCALE, Abilities.TOXIC_BOOST, Abilities.POISON_HEAL];
const checkedAbilities = [Abilities.QUICK_FEET, Abilities.GUTS, Abilities.MARVEL_SCALE, Abilities.TOXIC_BOOST, Abilities.POISON_HEAL, Abilities.MAGIC_GUARD];
const checkedMoves = [Moves.FACADE, Moves.TRICK, Moves.FLING, Moves.SWITCHEROO, Moves.PSYCHO_SHIFT];
// If a party member doesn't already have one of these two orbs and has one of the above moves or abilities, the orb can appear
return party.some(p => !p.getHeldItems().some(i => i instanceof Modifiers.TurnStatusEffectModifier) && (checkedAbilities.some(a => p.hasAbility(a, false, true)) || p.getMoveset(true).some(m => checkedMoves.includes(m.moveId)))) ? 10 : 0;
}, 10),
new WeightedModifierType(modifierTypes.FLAME_ORB, (party: Pokemon[]) => {
const checkedAbilities = [Abilities.QUICK_FEET, Abilities.GUTS, Abilities.MARVEL_SCALE, Abilities.FLARE_BOOST];
const checkedAbilities = [Abilities.QUICK_FEET, Abilities.GUTS, Abilities.MARVEL_SCALE, Abilities.FLARE_BOOST, Abilities.MAGIC_GUARD];
const checkedMoves = [Moves.FACADE, Moves.TRICK, Moves.FLING, Moves.SWITCHEROO, Moves.PSYCHO_SHIFT];
// If a party member doesn't already have one of these two orbs and has one of the above moves or abilities, the orb can appear
return party.some(p => !p.getHeldItems().some(i => i instanceof Modifiers.TurnStatusEffectModifier) && (checkedAbilities.some(a => p.hasAbility(a, false, true)) || p.getMoveset(true).some(m => checkedMoves.includes(m.moveId)))) ? 10 : 0;

View File

@ -13,13 +13,12 @@ import { getPokemonMessage } from "../messages";
import * as Utils from "../utils";
import { TempBattleStat } from "../data/temp-battle-stat";
import { getBerryEffectFunc, getBerryPredicate } from "../data/berry";
import { BerryType } from "../data/enums/berry-type";
import { BattlerTagType, BerryType } from "#enums";
import { StatusEffect, getStatusEffectHealText } from "../data/status-effect";
import { achvs } from "../system/achv";
import { VoucherType } from "../system/voucher";
import { FormChangeItem, SpeciesFormChangeItemTrigger } from "../data/pokemon-forms";
import { Nature } from "#app/data/nature";
import { BattlerTagType } from "#app/data/enums/battler-tag-type";
import * as Overrides from "../overrides";
import { ModifierType, modifierTypes } from "./modifier-type";
import { Command } from "#app/ui/command-ui-handler.js";

View File

@ -1,20 +1,16 @@
import { Species } from "./data/enums/species";
import { Abilities } from "./data/enums/abilities";
import { Biome } from "./data/enums/biome";
import { Moves } from "./data/enums/moves";
import { Species, Abilities, Biome, Moves, BerryType, TimeOfDay } from "#enums";
import { WeatherType } from "./data/weather";
import { Variant } from "./data/variant";
import { BerryType } from "./data/enums/berry-type";
import { TempBattleStat } from "./data/temp-battle-stat";
import { Nature } from "./data/nature";
import { Type } from "./data/type";
import { Stat } from "./data/pokemon-stat";
import { PokeballCounts } from "./battle-scene";
import { PokeballType } from "./data/pokeball";
import {TimeOfDay} from "#app/data/enums/time-of-day";
import { Gender } from "./data/gender";
import { StatusEffect } from "./data/status-effect";
import { modifierTypes } from "./modifier/modifier-type";
import { allSpecies } from "./data/pokemon-species"; // eslint-disable-line @typescript-eslint/no-unused-vars
/**
* Overrides for testing different in game situations
@ -53,8 +49,18 @@ export const POKEBALL_OVERRIDE: { active: boolean, pokeballs: PokeballCounts } =
* PLAYER OVERRIDES
*/
// forms can be found in pokemon-species.ts
export const STARTER_FORM_OVERRIDE: integer = 0;
/**
* Set the form index of any starter in the party whose `speciesId` is inside this override
* @see {@link allSpecies} in `src/data/pokemon-species.ts` for form indexes
* @example
* ```
* const STARTER_FORM_OVERRIDES = {
* [Species.DARMANITAN]: 1
* }
* ```
*/
export const STARTER_FORM_OVERRIDES: Partial<Record<Species, number>> = {};
// default 5 or 20 for Daily
export const STARTING_LEVEL_OVERRIDE: integer = 0;
/**

View File

@ -1,7 +1,7 @@
import BattleScene, { bypassLogin } from "./battle-scene";
import { default as Pokemon, PlayerPokemon, EnemyPokemon, PokemonMove, MoveResult, DamageResult, FieldPosition, HitResult, TurnMove } from "./field/pokemon";
import * as Utils from "./utils";
import { Moves } from "./data/enums/moves";
import { Abilities, ArenaTagType, BattleSpec, BattleStyle, BattlerTagType, Biome, ExpNotification, Moves, PlayerGender, Species, TrainerType } from "#enums";
import { allMoves, applyMoveAttrs, BypassSleepAttr, ChargeAttr, applyFilteredMoveAttrs, HitsTagAttr, MissEffectAttr, MoveAttr, MoveEffectAttr, MoveFlags, MultiHitAttr, OverrideMoveEffectAttr, VariableAccuracyAttr, MoveTarget, getMoveTargets, MoveTargetSet, MoveEffectTrigger, CopyMoveAttr, AttackMove, SelfStatusMove, PreMoveMessageAttr, HealStatusEffectAttr, IgnoreOpponentStatChangesAttr, NoEffectAttr, BypassRedirectAttr, FixedDamageAttr, PostVictoryStatChangeAttr, OneHitKOAccuracyAttr, ForceSwitchOutAttr, VariableTargetAttr, IncrementMovePriorityAttr } from "./data/move";
import { Mode } from "./ui/ui";
import { Command } from "./ui/command-ui-handler";
@ -17,34 +17,27 @@ import { EvolutionPhase } from "./evolution-phase";
import { Phase } from "./phase";
import { BattleStat, getBattleStatLevelChangeDescription, getBattleStatName } from "./data/battle-stat";
import { biomeLinks, getBiomeName } from "./data/biomes";
import { Biome } from "./data/enums/biome";
import { ModifierTier } from "./modifier/modifier-tier";
import { FusePokemonModifierType, ModifierPoolType, ModifierType, ModifierTypeFunc, ModifierTypeOption, PokemonModifierType, PokemonMoveModifierType, PokemonPpRestoreModifierType, PokemonPpUpModifierType, RememberMoveModifierType, TmModifierType, getDailyRunStarterModifiers, getEnemyBuffModifierForWave, getModifierType, getPlayerModifierTypeOptions, getPlayerShopModifierTypeOptionsForWave, modifierTypes, regenerateModifierPoolThresholds } from "./modifier/modifier-type";
import SoundFade from "phaser3-rex-plugins/plugins/soundfade";
import { BattlerTagLapseType, EncoreTag, HideSpriteTag as HiddenTag, ProtectedTag, TrappedTag } from "./data/battler-tags";
import { BattlerTagType } from "./data/enums/battler-tag-type";
import { getPokemonMessage, getPokemonNameWithAffix } from "./messages";
import { Starter } from "./ui/starter-select-ui-handler";
import { Gender } from "./data/gender";
import { Weather, WeatherType, getRandomWeatherType, getTerrainBlockMessage, getWeatherDamageMessage, getWeatherLapseMessage } from "./data/weather";
import { TempBattleStat } from "./data/temp-battle-stat";
import { ArenaTagSide, ArenaTrapTag, MistTag, TrickRoomTag } from "./data/arena-tag";
import { ArenaTagType } from "./data/enums/arena-tag-type";
import { CheckTrappedAbAttr, IgnoreOpponentStatChangesAbAttr, IgnoreOpponentEvasionAbAttr, 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, BattleStatMultiplierAbAttr, applyBattleStatMultiplierAbAttrs, IncrementMovePriorityAbAttr, applyPostVictoryAbAttrs, PostVictoryAbAttr, BlockNonDirectDamageAbAttr as BlockNonDirectDamageAbAttr, applyPostKnockOutAbAttrs, PostKnockOutAbAttr, PostBiomeChangeAbAttr, applyPostFaintAbAttrs, PostFaintAbAttr, IncreasePpAbAttr, PostStatChangeAbAttr, applyPostStatChangeAbAttrs, AlwaysHitAbAttr, PreventBerryUseAbAttr, StatChangeCopyAbAttr, applyPostMoveUsedAbAttrs, PostMoveUsedAbAttr, MaxMultiHitAbAttr, HealFromBerryUseAbAttr } from "./data/ability";
import { CheckTrappedAbAttr, IgnoreOpponentStatChangesAbAttr, IgnoreOpponentEvasionAbAttr, 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, BattleStatMultiplierAbAttr, applyBattleStatMultiplierAbAttrs, IncrementMovePriorityAbAttr, applyPostVictoryAbAttrs, PostVictoryAbAttr, BlockNonDirectDamageAbAttr as BlockNonDirectDamageAbAttr, applyPostKnockOutAbAttrs, PostKnockOutAbAttr, PostBiomeChangeAbAttr, applyPostFaintAbAttrs, PostFaintAbAttr, IncreasePpAbAttr, PostStatChangeAbAttr, applyPostStatChangeAbAttrs, AlwaysHitAbAttr, PreventBerryUseAbAttr, StatChangeCopyAbAttr, PokemonTypeChangeAbAttr, applyPreAttackAbAttrs, applyPostMoveUsedAbAttrs, PostMoveUsedAbAttr, MaxMultiHitAbAttr, HealFromBerryUseAbAttr } from "./data/ability";
import { Unlockables, getUnlockableName } from "./system/unlockables";
import { getBiomeKey } from "./field/arena";
import { BattleType, BattlerIndex, TurnCommand } from "./battle";
import { BattleSpec } from "./enums/battle-spec";
import { Species } from "./data/enums/species";
import { ChallengeAchv, HealAchv, LevelAchv, achvs } from "./system/achv";
import { TrainerSlot, trainerConfigs } from "./data/trainer-config";
import { TrainerType } from "./data/enums/trainer-type";
import { EggHatchPhase } from "./egg-hatch-phase";
import { Egg } from "./data/egg";
import { vouchers } from "./system/voucher";
import { loggedInUser, updateUserInfo } from "./account";
import { SessionSaveData } from "./system/game-data";
import { PlayerGender } from "./data/enums/player-gender";
import { addPokeballCaptureStars, addPokeballOpenParticles } from "./field/anims";
import { SpeciesFormChangeActiveTrigger, SpeciesFormChangeManualTrigger, SpeciesFormChangeMoveLearnedTrigger, SpeciesFormChangePostMoveTrigger, SpeciesFormChangePreMoveTrigger } from "./data/pokemon-forms";
import { battleSpecDialogue, getCharVariantFromDialogue, miscDialogue } from "./data/dialogue";
@ -58,12 +51,10 @@ import { fetchDailyRunSeed, getDailyRunStarters } from "./data/daily-run";
import { GameMode, GameModes, getGameMode } from "./game-mode";
import PokemonSpecies, { getPokemonSpecies, speciesStarters } from "./data/pokemon-species";
import i18next from "./plugins/i18n";
import { Abilities } from "./data/enums/abilities";
import * as Overrides from "./overrides";
import { TextStyle, addTextObject } from "./ui/text";
import { Type } from "./data/type";
import { BerryUsedEvent, EncounterPhaseEvent, MoveUsedEvent, TurnEndEvent, TurnInitEvent } from "./events/battle-scene";
import { ExpNotification } from "./enums/exp-notification";
export class LoginPhase extends Phase {
@ -555,6 +546,10 @@ export class SelectStarterPhase extends Phase {
});
}
/**
* Initialize starters before starting the first battle
* @param starters {@linkcode Pokemon} with which to start the first battle
*/
initBattle(starters: Starter[]) {
const party = this.scene.getParty();
const loadPokemonAssets: Promise<void>[] = [];
@ -564,9 +559,13 @@ export class SelectStarterPhase extends Phase {
}
const starterProps = this.scene.gameData.getSpeciesDexAttrProps(starter.species, starter.dexAttr);
let starterFormIndex = Math.min(starterProps.formIndex, Math.max(starter.species.forms.length - 1, 0));
if (!i && Overrides.STARTER_SPECIES_OVERRIDE) {
starterFormIndex = Overrides.STARTER_FORM_OVERRIDE;
if (
starter.species.speciesId in Overrides.STARTER_FORM_OVERRIDES &&
starter.species.forms[Overrides.STARTER_FORM_OVERRIDES[starter.species.speciesId]]
) {
starterFormIndex = Overrides.STARTER_FORM_OVERRIDES[starter.species.speciesId];
}
let starterGender = starter.species.malePercent !== null
? !starterProps.female ? Gender.MALE : Gender.FEMALE
: Gender.GENDERLESS;
@ -1025,13 +1024,19 @@ export class EncounterPhase extends BattlePhase {
if (this.scene.currentBattle.battleType !== BattleType.TRAINER) {
enemyField.map(p => this.scene.pushConditionalPhase(new PostSummonPhase(this.scene, p.getBattlerIndex()), () => {
// is the player party initialized ?
const a = !!this.scene.getParty()?.length;
// if there is not a player party, we can't continue
if (!this.scene.getParty()?.length) {
return false;
}
// how many player pokemon are on the field ?
const amountOnTheField = this.scene.getParty().filter(p => p.isOnField()).length;
const pokemonsOnFieldCount = this.scene.getParty().filter(p => p.isOnField()).length;
// if it's a 2vs1, there will never be a 2nd pokemon on our field even
const requiredPokemonsOnField = Math.min(this.scene.getParty().filter((p) => !p.isFainted()).length, 2);
// if it's a double, there should be 2, otherwise 1
const b = this.scene.currentBattle.double ? amountOnTheField === 2 : amountOnTheField === 1;
return a && b;
if (this.scene.currentBattle.double) {
return pokemonsOnFieldCount === requiredPokemonsOnField;
}
return pokemonsOnFieldCount === 1;
}));
const ivScannerModifier = this.scene.findModifier(m => m instanceof IvScannerModifier);
if (ivScannerModifier) {
@ -1733,7 +1738,7 @@ export class CheckSwitchPhase extends BattlePhase {
const pokemon = this.scene.getPlayerField()[this.fieldIndex];
if (this.scene.battleStyle === 1) {
if (this.scene.battleStyle === BattleStyle.SET) {
super.end();
return;
}
@ -2677,6 +2682,16 @@ export class MovePhase extends BattlePhase {
failedText = getTerrainBlockMessage(targets[0], this.scene.arena.terrain.terrainType);
}
}
/**
* Trigger pokemon type change before playing the move animation
* Will still change the user's type when using Roar, Whirlwind, Trick-or-Treat, and Forest's Curse,
* regardless of whether the move successfully executes or not.
*/
if (success || [Moves.ROAR, Moves.WHIRLWIND, Moves.TRICK_OR_TREAT, Moves.FORESTS_CURSE].includes(this.move.moveId)) {
applyPreAttackAbAttrs(PokemonTypeChangeAbAttr, this.pokemon, null, this.move.getMove());
}
if (success) {
this.scene.unshiftPhase(this.getEffectPhase());
} else {

View File

@ -3,7 +3,7 @@ import BattleScene from "../battle-scene";
import { TurnHeldItemTransferModifier } from "../modifier/modifier";
import i18next from "../plugins/i18n";
import * as Utils from "../utils";
import { PlayerGender } from "#app/data/enums/player-gender";
import { PlayerGender } from "#enums";
import { ParseKeys } from "i18next";
import { Challenge, SingleGenerationChallenge, SingleTypeChallenge } from "#app/data/challenge.js";

View File

@ -1,17 +1,20 @@
import { Arena } from "../field/arena";
import { ArenaTag } from "../data/arena-tag";
import { Biome } from "../data/enums/biome";
import { Biome } from "#enums";
import { Weather } from "../data/weather";
import { Terrain } from "#app/data/terrain.js";
export default class ArenaData {
public biome: Biome;
public weather: Weather;
public terrain: Terrain;
public tags: ArenaTag[];
constructor(source: Arena | any) {
const sourceArena = source instanceof Arena ? source as Arena : null;
this.biome = sourceArena ? sourceArena.biomeType : source.biome;
this.weather = sourceArena ? sourceArena.weather : source.weather ? new Weather(source.weather.weatherType, source.weather.turnsLeft) : undefined;
this.terrain = sourceArena ? sourceArena.terrain : source.terrain ? new Terrain(source.terrain.terrainType, source.terrain.turnsLeft) : undefined;
this.tags = sourceArena ? sourceArena.tags : [];
}
}

View File

@ -2,7 +2,7 @@ import BattleScene, { PokeballCounts, bypassLogin } from "../battle-scene";
import Pokemon, { EnemyPokemon, PlayerPokemon } from "../field/pokemon";
import { pokemonEvolutions, pokemonPrevolutions } from "../data/pokemon-evolutions";
import PokemonSpecies, { allSpecies, getPokemonSpecies, noStarterFormKeys, speciesStarters } from "../data/pokemon-species";
import { Species, defaultStarterSpecies } from "../data/enums/species";
import { Species, defaultStarterSpecies, Moves, Device, PlayerGender, GameDataType } from "#enums";
import * as Utils from "../utils";
import * as Overrides from "../overrides";
import PokemonData from "./pokemon-data";
@ -24,7 +24,6 @@ import { clientSessionId, loggedInUser, updateUserInfo } from "../account";
import { Nature } from "../data/nature";
import { GameStats } from "./game-stats";
import { Tutorial } from "../tutorial";
import { Moves } from "../data/enums/moves";
import { speciesEggMoves } from "../data/egg-moves";
import { allMoves } from "../data/move";
import { TrainerVariant } from "../field/trainer";
@ -32,12 +31,9 @@ import { OutdatedPhase, ReloadSessionPhase } from "#app/phases";
import { Variant, variantData } from "#app/data/variant";
import {setSettingGamepad, SettingGamepad, settingGamepadDefaults} from "./settings/settings-gamepad";
import {setSettingKeyboard, SettingKeyboard} from "#app/system/settings/settings-keyboard";
import { TerrainChangedEvent, WeatherChangedEvent } from "#app/field/events/arena";
import { Device } from "#app/enums/devices.js";
import { TerrainChangedEvent, WeatherChangedEvent } from "#app/events/arena.js";
import { EnemyAttackStatusEffectChanceModifier } from "../modifier/modifier";
import { StatusEffect } from "#app/data/status-effect.js";
import { PlayerGender } from "#app/data/enums/player-gender";
import { GameDataType } from "#app/data/enums/game-data-type";
import ChallengeData from "./challenge-data";
const saveKey = "x0i2O7WRiANTqPmZ"; // Temporary; secure encryption is not yet necessary
@ -829,7 +825,7 @@ export class GameData {
loadSession(scene: BattleScene, slotId: integer, sessionData?: SessionSaveData): Promise<boolean> {
return new Promise(async (resolve, reject) => {
try {
const initSessionFromData = async sessionData => {
const initSessionFromData = async (sessionData: SessionSaveData) => {
console.debug(sessionData);
scene.gameMode = getGameMode(sessionData.gameMode || GameModes.CLASSIC);
@ -1213,9 +1209,11 @@ export class GameData {
reader.onload = (_ => {
return e => {
let dataName: string;
let dataStr = AES.decrypt(e.target.result.toString(), saveKey).toString(enc.Utf8);
let valid = false;
try {
dataName = GameDataType[dataType].toLowerCase();
switch (dataType) {
case GameDataType.SYSTEM:
dataStr = this.convertSystemDataStr(dataStr);
@ -1235,28 +1233,12 @@ export class GameData {
console.error(ex);
}
let dataName: string;
switch (dataType) {
case GameDataType.SYSTEM:
dataName = "save";
break;
case GameDataType.SESSION:
dataName = "session";
break;
case GameDataType.SETTINGS:
dataName = "settings";
break;
case GameDataType.TUTORIALS:
dataName = "tutorials";
break;
}
const displayError = (error: string) => this.scene.ui.showText(error, null, () => this.scene.ui.showText(null, 0), Utils.fixedInt(1500));
if (!valid) {
return this.scene.ui.showText(`Your ${dataName} data could not be loaded. It may be corrupted.`, null, () => this.scene.ui.showText(null, 0), Utils.fixedInt(1500));
}
this.scene.ui.revertMode();
this.scene.ui.showText(`Your ${dataName} data will be overridden and the page will reload. Proceed?`, null, () => {
this.scene.ui.setOverlayMode(Mode.CONFIRM, () => {
localStorage.setItem(dataKey, encrypt(dataStr, bypassLogin));

View File

@ -1,15 +1,13 @@
import { BattleType } from "../battle";
import BattleScene from "../battle-scene";
import { Biome } from "../data/enums/biome";
import { Biome, Species, Moves } from "#enums";
import { Gender } from "../data/gender";
import { Nature } from "../data/nature";
import { PokeballType } from "../data/pokeball";
import { getPokemonSpecies } from "../data/pokemon-species";
import { Species } from "../data/enums/species";
import { Status } from "../data/status-effect";
import Pokemon, { EnemyPokemon, PokemonMove, PokemonSummonData } from "../field/pokemon";
import { TrainerSlot } from "../data/trainer-config";
import { Moves } from "../data/enums/moves";
import { Variant } from "#app/data/variant";
import { loadBattlerTag } from "../data/battler-tags";
@ -54,7 +52,7 @@ export default class PokemonData {
public summonData: PokemonSummonData;
constructor(source: Pokemon | any, forHistory: boolean = false) {
const sourcePokemon = source instanceof Pokemon ? source as Pokemon : null;
const sourcePokemon = source instanceof Pokemon ? source : null;
this.id = source.id;
this.player = sourcePokemon ? sourcePokemon.isPlayer() : source.player;
this.species = sourcePokemon ? sourcePokemon.species.speciesId : source.species;
@ -121,6 +119,7 @@ export default class PokemonData {
this.summonData.disabledMove = source.summonData.disabledMove;
this.summonData.disabledTurns = source.summonData.disabledTurns;
this.summonData.abilitySuppressed = source.summonData.abilitySuppressed;
this.summonData.abilitiesApplied = source.summonData.abilitiesApplied;
this.summonData.ability = source.summonData.ability;
this.summonData.moveset = source.summonData.moveset?.map(m => PokemonMove.loadMove(m));

View File

@ -2,7 +2,7 @@ import BattleScene from "../../battle-scene";
import SettingsGamepadUiHandler from "../../ui/settings/settings-gamepad-ui-handler";
import {Mode} from "../../ui/ui";
import {truncateString} from "../../utils";
import {Button} from "../../enums/buttons";
import {Button} from "#enums";
import {SettingKeyboard} from "#app/system/settings/settings-keyboard";
export enum SettingGamepad {

View File

@ -1,4 +1,4 @@
import {Button} from "#app/enums/buttons";
import {Button} from "#enums";
import BattleScene from "#app/battle-scene";
import {Mode} from "#app/ui/ui";
import SettingsKeyboardUiHandler from "#app/ui/settings/settings-keyboard-ui-handler";

View File

@ -3,11 +3,9 @@ import i18next from "i18next";
import BattleScene from "../../battle-scene";
import { hasTouchscreen } from "../../touch-controls";
import { updateWindowType } from "../../ui/ui-theme";
import { PlayerGender } from "#app/data/enums/player-gender";
import { PlayerGender, MoneyFormat, EaseType } from "#enums";
import { CandyUpgradeNotificationChangedEvent } from "../../events/battle-scene";
import { MoneyFormat } from "../../enums/money-format";
import SettingsUiHandler from "#app/ui/settings/settings-ui-handler";
import { EaseType } from "#app/ui/enums/ease-type.js";
const MUTE = "Mute";
const VOLUME_OPTIONS = new Array(11).fill(null).map((_, i) => i ? (i * 10).toString() : MUTE);

View File

@ -1,5 +1,5 @@
import BattleScene from "../battle-scene";
import { TrainerType } from "../data/enums/trainer-type";
import { TrainerType } from "#enums";
import Trainer, { TrainerVariant } from "../field/trainer";
export default class TrainerData {

View File

@ -1,8 +1,7 @@
import BattleScene from "../battle-scene";
import { TrainerType } from "../data/enums/trainer-type";
import i18next from "../plugins/i18n";
import { Achv, AchvTier, achvs, getAchievementDescription } from "./achv";
import { PlayerGender } from "#app/data/enums/player-gender";
import { PlayerGender, TrainerType } from "#enums";
export enum VoucherType {
REGULAR,

View File

@ -0,0 +1,67 @@
import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest";
import GameManager from "#test/utils/gameManager";
import { getMovePosition } from "#test/utils/gameManagerUtils";
import * as Overrides from "#app/overrides";
import { Moves } from "#enums";
import { Abilities } from "#enums";
import { Species } from "#enums";
import { Status, StatusEffect } from "#app/data/status-effect.js";
import { TurnEndPhase } from "#app/phases.js";
import { QuietFormChangePhase } from "#app/form-change-phase.js";
const TIMEOUT = 20 * 1000;
describe("Abilities - BATTLE BOND", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
const moveToUse = Moves.SPLASH;
vi.spyOn(Overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(Overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.BATTLE_BOND);
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE]);
});
test(
"check if fainted pokemon switches to base form on arena reset",
async () => {
const baseForm = 1,
ashForm = 2;
vi.spyOn(Overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(4);
vi.spyOn(Overrides, "STARTER_FORM_OVERRIDES", "get").mockReturnValue({
[Species.GRENINJA]: ashForm,
});
await game.startBattle([Species.MAGIKARP, Species.GRENINJA]);
const greninja = game.scene.getParty().find((p) => p.species.speciesId === Species.GRENINJA);
expect(greninja).not.toBe(undefined);
expect(greninja.formIndex).toBe(ashForm);
greninja.hp = 0;
greninja.status = new Status(StatusEffect.FAINT);
expect(greninja.isFainted()).toBe(true);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.doKillOpponents();
await game.phaseInterceptor.to(TurnEndPhase);
game.doSelectModifier();
await game.phaseInterceptor.to(QuietFormChangePhase);
expect(greninja.formIndex).toBe(baseForm);
},
TIMEOUT
);
});

View File

@ -0,0 +1,67 @@
import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest";
import GameManager from "#test/utils/gameManager";
import { getMovePosition } from "#test/utils/gameManagerUtils";
import * as Overrides from "#app/overrides";
import { Moves } from "#enums";
import { Abilities } from "#enums";
import { Species } from "#enums";
import { Status, StatusEffect } from "#app/data/status-effect.js";
import { TurnEndPhase } from "#app/phases.js";
import { QuietFormChangePhase } from "#app/form-change-phase.js";
const TIMEOUT = 20 * 1000;
describe("Abilities - DISGUISE", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
const moveToUse = Moves.SPLASH;
vi.spyOn(Overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(Overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.DISGUISE);
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE]);
});
test(
"check if fainted pokemon switched to base form on arena reset",
async () => {
const baseForm = 0,
bustedForm = 1;
vi.spyOn(Overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(4);
vi.spyOn(Overrides, "STARTER_FORM_OVERRIDES", "get").mockReturnValue({
[Species.MIMIKYU]: bustedForm,
});
await game.startBattle([Species.MAGIKARP, Species.MIMIKYU]);
const mimikyu = game.scene.getParty().find((p) => p.species.speciesId === Species.MIMIKYU);
expect(mimikyu).not.toBe(undefined);
expect(mimikyu.formIndex).toBe(bustedForm);
mimikyu.hp = 0;
mimikyu.status = new Status(StatusEffect.FAINT);
expect(mimikyu.isFainted()).toBe(true);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.doKillOpponents();
await game.phaseInterceptor.to(TurnEndPhase);
game.doSelectModifier();
await game.phaseInterceptor.to(QuietFormChangePhase);
expect(mimikyu.formIndex).toBe(baseForm);
},
TIMEOUT
);
});

View File

@ -2,18 +2,18 @@ import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest
import Phaser from "phaser";
import GameManager from "#app/test/utils/gameManager";
import * as overrides from "#app/overrides";
import {Abilities} from "#app/data/enums/abilities";
import {Species} from "#app/data/enums/species";
import {Abilities, Species, Moves} from "#enums";
import {
CommandPhase, DamagePhase,
EnemyCommandPhase,
CommandPhase, DamagePhase, EncounterPhase,
EnemyCommandPhase, SelectStarterPhase,
TurnInitPhase,
} from "#app/phases";
import {Mode} from "#app/ui/ui";
import {BattleStat} from "#app/data/battle-stat";
import {Moves} from "#app/data/enums/moves";
import {getMovePosition} from "#app/test/utils/gameManagerUtils";
import {generateStarter, getMovePosition} from "#app/test/utils/gameManagerUtils";
import {Command} from "#app/ui/command-ui-handler";
import {Status, StatusEffect} from "#app/data/status-effect";
import {GameModes, getGameMode} from "#app/game-mode";
describe("Abilities - Intimidate", () => {
@ -337,5 +337,53 @@ describe("Abilities - Intimidate", () => {
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-1);
battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
}, 200000);
}, 20000);
it("double - wild vs only 1 on player side", async() => {
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(false);
vi.spyOn(overrides, "DOUBLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(3);
await game.runToSummon([
Species.MIGHTYENA,
]);
await game.phaseInterceptor.to(CommandPhase, false);
const battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
const battleStatsOpponent2 = game.scene.currentBattle.enemyParty[1].summonData.battleStats;
expect(battleStatsOpponent2[BattleStat.ATK]).toBe(-1);
const battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-2);
}, 20000);
it("double - wild vs only 1 alive on player side", async() => {
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(false);
vi.spyOn(overrides, "DOUBLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(3);
await game.runToTitle();
game.onNextPrompt("TitlePhase", Mode.TITLE, () => {
game.scene.gameMode = getGameMode(GameModes.CLASSIC);
const starters = generateStarter(game.scene, [
Species.MIGHTYENA,
Species.POOCHYENA,
]);
const selectStarterPhase = new SelectStarterPhase(game.scene);
game.scene.pushPhase(new EncounterPhase(game.scene, false));
selectStarterPhase.initBattle(starters);
game.scene.getParty()[1].hp = 0;
game.scene.getParty()[1].status = new Status(StatusEffect.FAINT);
});
await game.phaseInterceptor.run(EncounterPhase);
await game.phaseInterceptor.to(CommandPhase, false);
const battleStatsOpponent = game.scene.currentBattle.enemyParty[0].summonData.battleStats;
expect(battleStatsOpponent[BattleStat.ATK]).toBe(-1);
const battleStatsOpponent2 = game.scene.currentBattle.enemyParty[1].summonData.battleStats;
expect(battleStatsOpponent2[BattleStat.ATK]).toBe(-1);
const battleStatsPokemon = game.scene.getParty()[0].summonData.battleStats;
expect(battleStatsPokemon[BattleStat.ATK]).toBe(-2);
}, 20000);
});

View File

@ -2,8 +2,7 @@ import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest
import Phaser from "phaser";
import GameManager from "#app/test/utils/gameManager";
import * as overrides from "#app/overrides";
import {Abilities} from "#app/data/enums/abilities";
import {Species} from "#app/data/enums/species";
import {Abilities, Species} from "#enums";
import {
CommandPhase,
} from "#app/phases";

View File

@ -0,0 +1,364 @@
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest";
import GameManager from "../utils/gameManager";
import * as Overrides from "#app/overrides";
import { Species } from "#enums";
import { Abilities } from "#enums";
import { Moves } from "#enums";
import { getMovePosition } from "../utils/gameManagerUtils";
import { MoveEffectPhase, TurnEndPhase } from "#app/phases.js";
import { allMoves } from "#app/data/move.js";
import { BattlerTagType } from "#enums";
import { Weather, WeatherType } from "#app/data/weather.js";
import { Type } from "#app/data/type.js";
import { Biome } from "#enums";
import { PlayerPokemon } from "#app/field/pokemon.js";
const TIMEOUT = 20 * 1000;
describe("Abilities - Protean", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
vi.spyOn(Overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(Overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.LIBERO);
vi.spyOn(Overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(100);
vi.spyOn(Overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.RATTATA);
vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.ENDURE, Moves.ENDURE, Moves.ENDURE, Moves.ENDURE]);
});
test(
"ability applies and changes a pokemon's type",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH]);
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.phaseInterceptor.to(TurnEndPhase);
testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.SPLASH);
},
TIMEOUT,
);
test(
"ability applies only once per switch in",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH, Moves.AGILITY]);
await game.startBattle([Species.MAGIKARP, Species.BULBASAUR]);
let leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.phaseInterceptor.to(TurnEndPhase);
testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.SPLASH);
game.doAttack(getMovePosition(game.scene, 0, Moves.AGILITY));
await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.summonData.abilitiesApplied.filter((a) => a === Abilities.LIBERO)).toHaveLength(1);
const leadPokemonType = Type[leadPokemon.getTypes()[0]];
const moveType = Type[allMoves[Moves.AGILITY].defaultType];
expect(leadPokemonType).not.toBe(moveType);
await game.toNextTurn();
game.doSwitchPokemon(1);
await game.toNextTurn();
game.doSwitchPokemon(1);
await game.toNextTurn();
leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.phaseInterceptor.to(TurnEndPhase);
testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.SPLASH);
},
TIMEOUT,
);
test(
"ability applies correctly even if the pokemon's move has a variable type",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.WEATHER_BALL]);
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
game.scene.arena.weather = new Weather(WeatherType.SUNNY);
game.doAttack(getMovePosition(game.scene, 0, Moves.WEATHER_BALL));
await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.summonData.abilitiesApplied).toContain(Abilities.LIBERO);
expect(leadPokemon.getTypes()).toHaveLength(1);
const leadPokemonType = Type[leadPokemon.getTypes()[0]],
moveType = Type[Type.FIRE];
expect(leadPokemonType).toBe(moveType);
},
TIMEOUT,
);
test(
"ability applies correctly even if the type has changed by another ability",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE]);
vi.spyOn(Overrides, "PASSIVE_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.REFRIGERATE);
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE));
await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.summonData.abilitiesApplied).toContain(Abilities.LIBERO);
expect(leadPokemon.getTypes()).toHaveLength(1);
const leadPokemonType = Type[leadPokemon.getTypes()[0]],
moveType = Type[Type.ICE];
expect(leadPokemonType).toBe(moveType);
},
TIMEOUT,
);
test(
"ability applies correctly even if the pokemon's move calls another move",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.NATURE_POWER]);
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
game.scene.arena.biomeType = Biome.MOUNTAIN;
game.doAttack(getMovePosition(game.scene, 0, Moves.NATURE_POWER));
await game.phaseInterceptor.to(TurnEndPhase);
testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.AIR_SLASH);
},
TIMEOUT,
);
test(
"ability applies correctly even if the pokemon's move is delayed / charging",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.DIG]);
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.DIG));
await game.phaseInterceptor.to(TurnEndPhase);
testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.DIG);
},
TIMEOUT,
);
test(
"ability applies correctly even if the pokemon's move misses",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE]);
vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH, Moves.SPLASH, Moves.SPLASH, Moves.SPLASH]);
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE));
await game.phaseInterceptor.to(MoveEffectPhase, false);
vi.spyOn(game.scene.getCurrentPhase() as MoveEffectPhase, "hitCheck").mockReturnValueOnce(false);
await game.phaseInterceptor.to(TurnEndPhase);
const enemyPokemon = game.scene.getEnemyPokemon();
expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp());
testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.TACKLE);
},
TIMEOUT,
);
test(
"ability applies correctly even if the pokemon's move is protected against",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE]);
vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.PROTECT, Moves.PROTECT, Moves.PROTECT, Moves.PROTECT]);
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE));
await game.phaseInterceptor.to(TurnEndPhase);
testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.TACKLE);
},
TIMEOUT,
);
test(
"ability applies correctly even if the pokemon's move fails because of type immunity",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE]);
vi.spyOn(Overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.GASTLY);
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE));
await game.phaseInterceptor.to(TurnEndPhase);
testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.TACKLE);
},
TIMEOUT,
);
test(
"ability is not applied if pokemon's type is the same as the move's type",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH]);
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
leadPokemon.summonData.types = [allMoves[Moves.SPLASH].defaultType];
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.summonData.abilitiesApplied).not.toContain(Abilities.LIBERO);
},
TIMEOUT,
);
test(
"ability is not applied if pokemon is terastallized",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH]);
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
vi.spyOn(leadPokemon, "isTerastallized").mockReturnValue(true);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.summonData.abilitiesApplied).not.toContain(Abilities.LIBERO);
},
TIMEOUT,
);
test(
"ability is not applied if pokemon uses struggle",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.STRUGGLE]);
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.STRUGGLE));
await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.summonData.abilitiesApplied).not.toContain(Abilities.LIBERO);
},
TIMEOUT,
);
test(
"ability is not applied if the pokemon's move fails",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.BURN_UP]);
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.BURN_UP));
await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.summonData.abilitiesApplied).not.toContain(Abilities.LIBERO);
},
TIMEOUT,
);
test(
"ability applies correctly even if the pokemon's Trick-or-Treat fails",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TRICK_OR_TREAT]);
vi.spyOn(Overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.GASTLY);
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.TRICK_OR_TREAT));
await game.phaseInterceptor.to(TurnEndPhase);
testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.TRICK_OR_TREAT);
},
TIMEOUT,
);
test(
"ability applies correctly and the pokemon curses itself",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.CURSE]);
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.CURSE));
await game.phaseInterceptor.to(TurnEndPhase);
testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.CURSE);
expect(leadPokemon.getTag(BattlerTagType.CURSED)).not.toBe(undefined);
},
TIMEOUT,
);
});
function testPokemonTypeMatchesDefaultMoveType(pokemon: PlayerPokemon, move: Moves) {
expect(pokemon.summonData.abilitiesApplied).toContain(Abilities.LIBERO);
expect(pokemon.getTypes()).toHaveLength(1);
const pokemonType = Type[pokemon.getTypes()[0]],
moveType = Type[allMoves[move].defaultType];
expect(pokemonType).toBe(moveType);
}

View File

@ -2,8 +2,7 @@ import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest
import Phaser from "phaser";
import GameManager from "#app/test/utils/gameManager";
import * as overrides from "#app/overrides";
import {Abilities} from "#app/data/enums/abilities";
import {Species} from "#app/data/enums/species";
import {Abilities, Moves, Species} from "#enums";
import {
CommandPhase,
EnemyCommandPhase,
@ -11,7 +10,6 @@ import {
} from "#app/phases";
import {Mode} from "#app/ui/ui";
import {Stat} from "#app/data/pokemon-stat";
import {Moves} from "#app/data/enums/moves";
import {getMovePosition} from "#app/test/utils/gameManagerUtils";
import {Command} from "#app/ui/command-ui-handler";
import {BattleStat} from "#app/data/battle-stat";

View File

@ -0,0 +1,67 @@
import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest";
import GameManager from "#test/utils/gameManager";
import { getMovePosition } from "#test/utils/gameManagerUtils";
import * as Overrides from "#app/overrides";
import { Moves } from "#enums";
import { Abilities } from "#enums";
import { Species } from "#enums";
import { Status, StatusEffect } from "#app/data/status-effect.js";
import { TurnEndPhase } from "#app/phases.js";
import { QuietFormChangePhase } from "#app/form-change-phase.js";
const TIMEOUT = 20 * 1000;
describe("Abilities - POWER CONSTRUCT", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
const moveToUse = Moves.SPLASH;
vi.spyOn(Overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(Overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.POWER_CONSTRUCT);
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE]);
});
test(
"check if fainted pokemon switches to base form on arena reset",
async () => {
const baseForm = 2,
completeForm = 4;
vi.spyOn(Overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(4);
vi.spyOn(Overrides, "STARTER_FORM_OVERRIDES", "get").mockReturnValue({
[Species.ZYGARDE]: completeForm,
});
await game.startBattle([Species.MAGIKARP, Species.ZYGARDE]);
const zygarde = game.scene.getParty().find((p) => p.species.speciesId === Species.ZYGARDE);
expect(zygarde).not.toBe(undefined);
expect(zygarde.formIndex).toBe(completeForm);
zygarde.hp = 0;
zygarde.status = new Status(StatusEffect.FAINT);
expect(zygarde.isFainted()).toBe(true);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.doKillOpponents();
await game.phaseInterceptor.to(TurnEndPhase);
game.doSelectModifier();
await game.phaseInterceptor.to(QuietFormChangePhase);
expect(zygarde.formIndex).toBe(baseForm);
},
TIMEOUT
);
});

View File

@ -0,0 +1,364 @@
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest";
import GameManager from "../utils/gameManager";
import * as Overrides from "#app/overrides";
import { Species } from "#enums";
import { Abilities } from "#enums";
import { Moves } from "#enums";
import { getMovePosition } from "../utils/gameManagerUtils";
import { MoveEffectPhase, TurnEndPhase } from "#app/phases.js";
import { allMoves } from "#app/data/move.js";
import { BattlerTagType } from "#enums";
import { Weather, WeatherType } from "#app/data/weather.js";
import { Type } from "#app/data/type.js";
import { Biome } from "#enums";
import { PlayerPokemon } from "#app/field/pokemon.js";
const TIMEOUT = 20 * 1000;
describe("Abilities - Protean", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
vi.spyOn(Overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(Overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.PROTEAN);
vi.spyOn(Overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(100);
vi.spyOn(Overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.RATTATA);
vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.ENDURE, Moves.ENDURE, Moves.ENDURE, Moves.ENDURE]);
});
test(
"ability applies and changes a pokemon's type",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH]);
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.phaseInterceptor.to(TurnEndPhase);
testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.SPLASH);
},
TIMEOUT,
);
test(
"ability applies only once per switch in",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH, Moves.AGILITY]);
await game.startBattle([Species.MAGIKARP, Species.BULBASAUR]);
let leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.phaseInterceptor.to(TurnEndPhase);
testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.SPLASH);
game.doAttack(getMovePosition(game.scene, 0, Moves.AGILITY));
await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.summonData.abilitiesApplied.filter((a) => a === Abilities.PROTEAN)).toHaveLength(1);
const leadPokemonType = Type[leadPokemon.getTypes()[0]];
const moveType = Type[allMoves[Moves.AGILITY].defaultType];
expect(leadPokemonType).not.toBe(moveType);
await game.toNextTurn();
game.doSwitchPokemon(1);
await game.toNextTurn();
game.doSwitchPokemon(1);
await game.toNextTurn();
leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.phaseInterceptor.to(TurnEndPhase);
testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.SPLASH);
},
TIMEOUT,
);
test(
"ability applies correctly even if the pokemon's move has a variable type",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.WEATHER_BALL]);
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
game.scene.arena.weather = new Weather(WeatherType.SUNNY);
game.doAttack(getMovePosition(game.scene, 0, Moves.WEATHER_BALL));
await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.summonData.abilitiesApplied).toContain(Abilities.PROTEAN);
expect(leadPokemon.getTypes()).toHaveLength(1);
const leadPokemonType = Type[leadPokemon.getTypes()[0]],
moveType = Type[Type.FIRE];
expect(leadPokemonType).toBe(moveType);
},
TIMEOUT,
);
test(
"ability applies correctly even if the type has changed by another ability",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE]);
vi.spyOn(Overrides, "PASSIVE_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.REFRIGERATE);
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE));
await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.summonData.abilitiesApplied).toContain(Abilities.PROTEAN);
expect(leadPokemon.getTypes()).toHaveLength(1);
const leadPokemonType = Type[leadPokemon.getTypes()[0]],
moveType = Type[Type.ICE];
expect(leadPokemonType).toBe(moveType);
},
TIMEOUT,
);
test(
"ability applies correctly even if the pokemon's move calls another move",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.NATURE_POWER]);
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
game.scene.arena.biomeType = Biome.MOUNTAIN;
game.doAttack(getMovePosition(game.scene, 0, Moves.NATURE_POWER));
await game.phaseInterceptor.to(TurnEndPhase);
testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.AIR_SLASH);
},
TIMEOUT,
);
test(
"ability applies correctly even if the pokemon's move is delayed / charging",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.DIG]);
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.DIG));
await game.phaseInterceptor.to(TurnEndPhase);
testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.DIG);
},
TIMEOUT,
);
test(
"ability applies correctly even if the pokemon's move misses",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE]);
vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH, Moves.SPLASH, Moves.SPLASH, Moves.SPLASH]);
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE));
await game.phaseInterceptor.to(MoveEffectPhase, false);
vi.spyOn(game.scene.getCurrentPhase() as MoveEffectPhase, "hitCheck").mockReturnValueOnce(false);
await game.phaseInterceptor.to(TurnEndPhase);
const enemyPokemon = game.scene.getEnemyPokemon();
expect(enemyPokemon.hp).toBe(enemyPokemon.getMaxHp());
testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.TACKLE);
},
TIMEOUT,
);
test(
"ability applies correctly even if the pokemon's move is protected against",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE]);
vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.PROTECT, Moves.PROTECT, Moves.PROTECT, Moves.PROTECT]);
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE));
await game.phaseInterceptor.to(TurnEndPhase);
testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.TACKLE);
},
TIMEOUT,
);
test(
"ability applies correctly even if the pokemon's move fails because of type immunity",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE]);
vi.spyOn(Overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.GASTLY);
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.TACKLE));
await game.phaseInterceptor.to(TurnEndPhase);
testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.TACKLE);
},
TIMEOUT,
);
test(
"ability is not applied if pokemon's type is the same as the move's type",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH]);
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
leadPokemon.summonData.types = [allMoves[Moves.SPLASH].defaultType];
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.summonData.abilitiesApplied).not.toContain(Abilities.PROTEAN);
},
TIMEOUT,
);
test(
"ability is not applied if pokemon is terastallized",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH]);
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
vi.spyOn(leadPokemon, "isTerastallized").mockReturnValue(true);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.summonData.abilitiesApplied).not.toContain(Abilities.PROTEAN);
},
TIMEOUT,
);
test(
"ability is not applied if pokemon uses struggle",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.STRUGGLE]);
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.STRUGGLE));
await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.summonData.abilitiesApplied).not.toContain(Abilities.PROTEAN);
},
TIMEOUT,
);
test(
"ability is not applied if the pokemon's move fails",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.BURN_UP]);
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.BURN_UP));
await game.phaseInterceptor.to(TurnEndPhase);
expect(leadPokemon.summonData.abilitiesApplied).not.toContain(Abilities.PROTEAN);
},
TIMEOUT,
);
test(
"ability applies correctly even if the pokemon's Trick-or-Treat fails",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TRICK_OR_TREAT]);
vi.spyOn(Overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.GASTLY);
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.TRICK_OR_TREAT));
await game.phaseInterceptor.to(TurnEndPhase);
testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.TRICK_OR_TREAT);
},
TIMEOUT,
);
test(
"ability applies correctly and the pokemon curses itself",
async () => {
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.CURSE]);
await game.startBattle([Species.MAGIKARP]);
const leadPokemon = game.scene.getPlayerPokemon();
expect(leadPokemon).not.toBe(undefined);
game.doAttack(getMovePosition(game.scene, 0, Moves.CURSE));
await game.phaseInterceptor.to(TurnEndPhase);
testPokemonTypeMatchesDefaultMoveType(leadPokemon, Moves.CURSE);
expect(leadPokemon.getTag(BattlerTagType.CURSED)).not.toBe(undefined);
},
TIMEOUT,
);
});
function testPokemonTypeMatchesDefaultMoveType(pokemon: PlayerPokemon, move: Moves) {
expect(pokemon.summonData.abilitiesApplied).toContain(Abilities.PROTEAN);
expect(pokemon.getTypes()).toHaveLength(1);
const pokemonType = Type[pokemon.getTypes()[0]],
moveType = Type[allMoves[move].defaultType];
expect(pokemonType).toBe(moveType);
}

View File

@ -2,20 +2,18 @@ import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest
import Phaser from "phaser";
import GameManager from "#app/test/utils/gameManager";
import * as overrides from "#app/overrides";
import {Species} from "#app/data/enums/species";
import {
CommandPhase,
EnemyCommandPhase, TurnEndPhase,
EnemyCommandPhase, MoveEndPhase, TurnEndPhase,
} from "#app/phases";
import {Mode} from "#app/ui/ui";
import {Moves} from "#app/data/enums/moves";
import {getMovePosition} from "#app/test/utils/gameManagerUtils";
import {Command} from "#app/ui/command-ui-handler";
import { Abilities } from "#app/data/enums/abilities.js";
import { Abilities, BattlerTagType, Moves, Species } from "#enums";
import { BattleStat } from "#app/data/battle-stat.js";
import { TerrainType } from "#app/data/terrain.js";
// See also: ArenaTypeAbAttr
// See also: TypeImmunityAbAttr
describe("Abilities - Sap Sipper", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
@ -113,4 +111,28 @@ describe("Abilities - Sap Sipper", () => {
expect(game.scene.arena.terrain.terrainType).toBe(TerrainType.GRASSY);
expect(game.scene.getEnemyParty()[0].summonData.battleStats[BattleStat.ATK]).toBe(0);
});
it("do not activate against status moves that target the user", async() => {
const moveToUse = Moves.SPIKY_SHIELD;
const ability = Abilities.SAP_SIPPER;
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(ability);
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH, Moves.NONE, Moves.NONE, Moves.NONE]);
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.RATTATA);
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.NONE);
await game.startBattle();
game.doAttack(getMovePosition(game.scene, 0, moveToUse));
await game.phaseInterceptor.to(MoveEndPhase);
expect(game.scene.getParty()[0].getTag(BattlerTagType.SPIKY_SHIELD)).toBeDefined();
await game.phaseInterceptor.to(TurnEndPhase);
expect(game.scene.getParty()[0].summonData.battleStats[BattleStat.ATK]).toBe(0);
expect(game.phaseInterceptor.log).not.toContain("ShowAbilityPhase");
});
});

View File

@ -0,0 +1,67 @@
import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest";
import GameManager from "#test/utils/gameManager";
import { getMovePosition } from "#test/utils/gameManagerUtils";
import * as Overrides from "#app/overrides";
import { Moves } from "#enums";
import { Abilities } from "#enums";
import { Species } from "#enums";
import { Status, StatusEffect } from "#app/data/status-effect.js";
import { TurnEndPhase } from "#app/phases.js";
import { QuietFormChangePhase } from "#app/form-change-phase.js";
const TIMEOUT = 20 * 1000;
describe("Abilities - SCHOOLING", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
const moveToUse = Moves.SPLASH;
vi.spyOn(Overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(Overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.SCHOOLING);
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE]);
});
test(
"check if fainted pokemon switches to base form on arena reset",
async () => {
const soloForm = 0,
schoolForm = 1;
vi.spyOn(Overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(4);
vi.spyOn(Overrides, "STARTER_FORM_OVERRIDES", "get").mockReturnValue({
[Species.WISHIWASHI]: schoolForm,
});
await game.startBattle([Species.MAGIKARP, Species.WISHIWASHI]);
const wishiwashi = game.scene.getParty().find((p) => p.species.speciesId === Species.WISHIWASHI);
expect(wishiwashi).not.toBe(undefined);
expect(wishiwashi.formIndex).toBe(schoolForm);
wishiwashi.hp = 0;
wishiwashi.status = new Status(StatusEffect.FAINT);
expect(wishiwashi.isFainted()).toBe(true);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.doKillOpponents();
await game.phaseInterceptor.to(TurnEndPhase);
game.doSelectModifier();
await game.phaseInterceptor.to(QuietFormChangePhase);
expect(wishiwashi.formIndex).toBe(soloForm);
},
TIMEOUT
);
});

View File

@ -0,0 +1,67 @@
import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest";
import GameManager from "#test/utils/gameManager";
import { getMovePosition } from "#test/utils/gameManagerUtils";
import * as Overrides from "#app/overrides";
import { Moves } from "#enums";
import { Abilities } from "#enums";
import { Species } from "#enums";
import { Status, StatusEffect } from "#app/data/status-effect.js";
import { TurnEndPhase } from "#app/phases.js";
import { QuietFormChangePhase } from "#app/form-change-phase.js";
const TIMEOUT = 20 * 1000;
describe("Abilities - SHIELDS DOWN", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
const moveToUse = Moves.SPLASH;
vi.spyOn(Overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(Overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.SHIELDS_DOWN);
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE]);
});
test(
"check if fainted pokemon switched to base form on arena reset",
async () => {
const meteorForm = 0,
coreForm = 7;
vi.spyOn(Overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(4);
vi.spyOn(Overrides, "STARTER_FORM_OVERRIDES", "get").mockReturnValue({
[Species.MINIOR]: coreForm,
});
await game.startBattle([Species.MAGIKARP, Species.MINIOR]);
const minior = game.scene.getParty().find((p) => p.species.speciesId === Species.MINIOR);
expect(minior).not.toBe(undefined);
expect(minior.formIndex).toBe(coreForm);
minior.hp = 0;
minior.status = new Status(StatusEffect.FAINT);
expect(minior.isFainted()).toBe(true);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.doKillOpponents();
await game.phaseInterceptor.to(TurnEndPhase);
game.doSelectModifier();
await game.phaseInterceptor.to(QuietFormChangePhase);
expect(minior.formIndex).toBe(meteorForm);
},
TIMEOUT
);
});

View File

@ -0,0 +1,53 @@
import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest";
import Phaser from "phaser";
import GameManager from "#app/test/utils/gameManager";
import * as overrides from "#app/overrides";
import {
TurnEndPhase,
} from "#app/phases";
import {getMovePosition} from "#app/test/utils/gameManagerUtils";
import { Abilities, BattlerTagType, Moves, Species } from "#enums";
import { BattleStat } from "#app/data/battle-stat.js";
// See also: TypeImmunityAbAttr
describe("Abilities - Volt Absorb", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "NEVER_CRIT_OVERRIDE", "get").mockReturnValue(true);
});
it("does not activate when CHARGE is used", async () => {
const moveToUse = Moves.CHARGE;
const ability = Abilities.VOLT_ABSORB;
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(ability);
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH, Moves.NONE, Moves.NONE, Moves.NONE]);
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.DUSKULL);
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.NONE);
await game.startBattle();
game.doAttack(getMovePosition(game.scene, 0, moveToUse));
await game.phaseInterceptor.to(TurnEndPhase);
expect(game.scene.getParty()[0].summonData.battleStats[BattleStat.SPDEF]).toBe(1);
expect(game.scene.getParty()[0].getTag(BattlerTagType.CHARGED)).toBeDefined();
expect(game.phaseInterceptor.log).not.toContain("ShowAbilityPhase");
});
});

View File

@ -1,9 +1,7 @@
import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest";
import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest";
import Phaser from "phaser";
import GameManager from "#app/test/utils/gameManager";
import * as overrides from "#app/overrides";
import {Abilities} from "#app/data/enums/abilities";
import {Species} from "#app/data/enums/species";
import * as Overrides from "#app/overrides";
import {
CommandPhase,
DamagePhase,
@ -12,18 +10,21 @@ import {
PostSummonPhase,
SwitchPhase,
SwitchSummonPhase,
TurnEndPhase, TurnInitPhase,
TurnEndPhase,
TurnInitPhase,
TurnStartPhase,
} from "#app/phases";
import {Mode} from "#app/ui/ui";
import {Stat} from "#app/data/pokemon-stat";
import {Moves} from "#app/data/enums/moves";
import {getMovePosition} from "#app/test/utils/gameManagerUtils";
import {Command} from "#app/ui/command-ui-handler";
import {QuietFormChangePhase} from "#app/form-change-phase";
import { Mode } from "#app/ui/ui";
import { Stat } from "#app/data/pokemon-stat";
import { Abilities, Moves, Species } from "#enums";
import { getMovePosition } from "#app/test/utils/gameManagerUtils";
import { Command } from "#app/ui/command-ui-handler";
import { QuietFormChangePhase } from "#app/form-change-phase";
import { Status, StatusEffect } from "#app/data/status-effect.js";
const TIMEOUT = 20 * 1000;
describe("Abilities - Zen mode", () => {
describe("Abilities - ZEN MODE", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
@ -40,103 +41,139 @@ describe("Abilities - Zen mode", () => {
beforeEach(() => {
game = new GameManager(phaserGame);
const moveToUse = Moves.SPLASH;
vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.RATTATA);
vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.HYDRATION);
vi.spyOn(overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.ZEN_MODE);
vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(100);
vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE,Moves.TACKLE,Moves.TACKLE,Moves.TACKLE]);
vi.spyOn(Overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(Overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.RATTATA);
vi.spyOn(Overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.HYDRATION);
vi.spyOn(Overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.ZEN_MODE);
vi.spyOn(Overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(100);
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE]);
});
it("ZEN MODE - not enough damage to change form", async() => {
const moveToUse = Moves.SPLASH;
await game.startBattle([
Species.DARMANITAN,
]);
game.scene.getParty()[0].stats[Stat.SPD] = 1;
game.scene.getParty()[0].stats[Stat.HP] = 100;
game.scene.getParty()[0].hp = 100;
expect(game.scene.getParty()[0].formIndex).toBe(0);
test(
"not enough damage to change form",
async () => {
const moveToUse = Moves.SPLASH;
await game.startBattle([Species.DARMANITAN]);
game.scene.getParty()[0].stats[Stat.SPD] = 1;
game.scene.getParty()[0].stats[Stat.HP] = 100;
game.scene.getParty()[0].hp = 100;
expect(game.scene.getParty()[0].formIndex).toBe(0);
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
});
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
const movePosition = getMovePosition(game.scene, 0, moveToUse);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(DamagePhase, false);
// await game.phaseInterceptor.runFrom(DamagePhase).to(DamagePhase, false);
const damagePhase = game.scene.getCurrentPhase() as DamagePhase;
damagePhase.updateAmount(40);
await game.phaseInterceptor.runFrom(DamagePhase).to(TurnEndPhase, false);
expect(game.scene.getParty()[0].hp).toBeLessThan(100);
expect(game.scene.getParty()[0].formIndex).toBe(0);
}, 20000);
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
});
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
const movePosition = getMovePosition(game.scene, 0, moveToUse);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(DamagePhase, false);
// await game.phaseInterceptor.runFrom(DamagePhase).to(DamagePhase, false);
const damagePhase = game.scene.getCurrentPhase() as DamagePhase;
damagePhase.updateAmount(40);
await game.phaseInterceptor.runFrom(DamagePhase).to(TurnEndPhase, false);
expect(game.scene.getParty()[0].hp).toBeLessThan(100);
expect(game.scene.getParty()[0].formIndex).toBe(0);
},
TIMEOUT
);
it("ZEN MODE - enough damage to change form", async() => {
const moveToUse = Moves.SPLASH;
await game.startBattle([
Species.DARMANITAN,
]);
game.scene.getParty()[0].stats[Stat.SPD] = 1;
game.scene.getParty()[0].stats[Stat.HP] = 1000;
game.scene.getParty()[0].hp = 100;
expect(game.scene.getParty()[0].formIndex).toBe(0);
test(
"enough damage to change form",
async () => {
const moveToUse = Moves.SPLASH;
await game.startBattle([Species.DARMANITAN]);
game.scene.getParty()[0].stats[Stat.SPD] = 1;
game.scene.getParty()[0].stats[Stat.HP] = 1000;
game.scene.getParty()[0].hp = 100;
expect(game.scene.getParty()[0].formIndex).toBe(0);
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
});
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
const movePosition = getMovePosition(game.scene, 0, moveToUse);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(QuietFormChangePhase);
await game.phaseInterceptor.to(TurnInitPhase, false);
expect(game.scene.getParty()[0].hp).not.toBe(100);
expect(game.scene.getParty()[0].formIndex).not.toBe(0);
}, 20000);
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
});
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
const movePosition = getMovePosition(game.scene, 0, moveToUse);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(QuietFormChangePhase);
await game.phaseInterceptor.to(TurnInitPhase, false);
expect(game.scene.getParty()[0].hp).not.toBe(100);
expect(game.scene.getParty()[0].formIndex).not.toBe(0);
},
TIMEOUT
);
it("ZEN MODE - kill pokemon while on zen mode", async() => {
const moveToUse = Moves.SPLASH;
await game.startBattle([
Species.DARMANITAN,
Species.CHARIZARD,
]);
game.scene.getParty()[0].stats[Stat.SPD] = 1;
game.scene.getParty()[0].stats[Stat.HP] = 1000;
game.scene.getParty()[0].hp = 100;
expect(game.scene.getParty()[0].formIndex).toBe(0);
test(
"kill pokemon while on zen mode",
async () => {
const moveToUse = Moves.SPLASH;
await game.startBattle([Species.DARMANITAN, Species.CHARIZARD]);
game.scene.getParty()[0].stats[Stat.SPD] = 1;
game.scene.getParty()[0].stats[Stat.HP] = 1000;
game.scene.getParty()[0].hp = 100;
expect(game.scene.getParty()[0].formIndex).toBe(0);
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
});
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
const movePosition = getMovePosition(game.scene, 0, moveToUse);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(DamagePhase, false);
// await game.phaseInterceptor.runFrom(DamagePhase).to(DamagePhase, false);
const damagePhase = game.scene.getCurrentPhase() as DamagePhase;
damagePhase.updateAmount(80);
await game.phaseInterceptor.runFrom(DamagePhase).to(QuietFormChangePhase);
expect(game.scene.getParty()[0].hp).not.toBe(100);
expect(game.scene.getParty()[0].formIndex).not.toBe(0);
await game.killPokemon(game.scene.getParty()[0]);
expect(game.scene.getParty()[0].isFainted()).toBe(true);
await game.phaseInterceptor.run(MessagePhase);
await game.phaseInterceptor.run(EnemyCommandPhase);
await game.phaseInterceptor.run(TurnStartPhase);
game.onNextPrompt("SwitchPhase", Mode.PARTY, () => {
game.scene.unshiftPhase(new SwitchSummonPhase(game.scene, 0, 1, false, false));
game.scene.ui.setMode(Mode.MESSAGE);
});
game.onNextPrompt("SwitchPhase", Mode.MESSAGE, () => {
game.endPhase();
});
await game.phaseInterceptor.run(SwitchPhase);
await game.phaseInterceptor.to(PostSummonPhase);
expect(game.scene.getParty()[1].formIndex).toBe(1);
}, 20000);
game.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
game.scene.ui.setMode(Mode.FIGHT, (game.scene.getCurrentPhase() as CommandPhase).getFieldIndex());
});
game.onNextPrompt("CommandPhase", Mode.FIGHT, () => {
const movePosition = getMovePosition(game.scene, 0, moveToUse);
(game.scene.getCurrentPhase() as CommandPhase).handleCommand(Command.FIGHT, movePosition, false);
});
await game.phaseInterceptor.runFrom(EnemyCommandPhase).to(DamagePhase, false);
// await game.phaseInterceptor.runFrom(DamagePhase).to(DamagePhase, false);
const damagePhase = game.scene.getCurrentPhase() as DamagePhase;
damagePhase.updateAmount(80);
await game.phaseInterceptor.runFrom(DamagePhase).to(QuietFormChangePhase);
expect(game.scene.getParty()[0].hp).not.toBe(100);
expect(game.scene.getParty()[0].formIndex).not.toBe(0);
await game.killPokemon(game.scene.getParty()[0]);
expect(game.scene.getParty()[0].isFainted()).toBe(true);
await game.phaseInterceptor.run(MessagePhase);
await game.phaseInterceptor.run(EnemyCommandPhase);
await game.phaseInterceptor.run(TurnStartPhase);
game.onNextPrompt("SwitchPhase", Mode.PARTY, () => {
game.scene.unshiftPhase(new SwitchSummonPhase(game.scene, 0, 1, false, false));
game.scene.ui.setMode(Mode.MESSAGE);
});
game.onNextPrompt("SwitchPhase", Mode.MESSAGE, () => {
game.endPhase();
});
await game.phaseInterceptor.run(SwitchPhase);
await game.phaseInterceptor.to(PostSummonPhase);
expect(game.scene.getParty()[1].formIndex).toBe(1);
},
TIMEOUT
);
test(
"check if fainted pokemon switches to base form on arena reset",
async () => {
const baseForm = 0,
zenForm = 1;
vi.spyOn(Overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(4);
vi.spyOn(Overrides, "STARTER_FORM_OVERRIDES", "get").mockReturnValue({
[Species.DARMANITAN]: zenForm,
});
await game.startBattle([Species.MAGIKARP, Species.DARMANITAN]);
const darmanitan = game.scene.getParty().find((p) => p.species.speciesId === Species.DARMANITAN);
expect(darmanitan).not.toBe(undefined);
expect(darmanitan.formIndex).toBe(zenForm);
darmanitan.hp = 0;
darmanitan.status = new Status(StatusEffect.FAINT);
expect(darmanitan.isFainted()).toBe(true);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.doKillOpponents();
await game.phaseInterceptor.to(TurnEndPhase);
game.doSelectModifier();
await game.phaseInterceptor.to(QuietFormChangePhase);
expect(darmanitan.formIndex).toBe(baseForm);
},
TIMEOUT
);
});

View File

@ -0,0 +1,67 @@
import { afterEach, beforeAll, beforeEach, describe, expect, test, vi } from "vitest";
import GameManager from "#test/utils/gameManager";
import { getMovePosition } from "#test/utils/gameManagerUtils";
import * as Overrides from "#app/overrides";
import { Moves } from "#enums";
import { Abilities } from "#enums";
import { Species } from "#enums";
import { Status, StatusEffect } from "#app/data/status-effect.js";
import { TurnEndPhase } from "#app/phases.js";
import { QuietFormChangePhase } from "#app/form-change-phase.js";
const TIMEOUT = 20 * 1000;
describe("Abilities - ZERO TO HERO", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
const moveToUse = Moves.SPLASH;
vi.spyOn(Overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true);
vi.spyOn(Overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.ZERO_TO_HERO);
vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([moveToUse]);
vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.TACKLE, Moves.TACKLE, Moves.TACKLE, Moves.TACKLE]);
});
test(
"check if fainted pokemon switches to base form on arena reset",
async () => {
const baseForm = 0,
heroForm = 1;
vi.spyOn(Overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(4);
vi.spyOn(Overrides, "STARTER_FORM_OVERRIDES", "get").mockReturnValue({
[Species.PALAFIN]: heroForm,
});
await game.startBattle([Species.MAGIKARP, Species.PALAFIN]);
const palafin = game.scene.getParty().find((p) => p.species.speciesId === Species.PALAFIN);
expect(palafin).not.toBe(undefined);
expect(palafin.formIndex).toBe(heroForm);
palafin.hp = 0;
palafin.status = new Status(StatusEffect.FAINT);
expect(palafin.isFainted()).toBe(true);
game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH));
await game.doKillOpponents();
await game.phaseInterceptor.to(TurnEndPhase);
game.doSelectModifier();
await game.phaseInterceptor.to(QuietFormChangePhase);
expect(palafin.formIndex).toBe(baseForm);
},
TIMEOUT
);
});

View File

@ -2,19 +2,16 @@ import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest
import Phaser from "phaser";
import GameManager from "#app/test/utils/gameManager";
import * as overrides from "#app/overrides";
import {Abilities} from "#app/data/enums/abilities";
import {Species} from "#app/data/enums/species";
import {Abilities, Species, Moves, Button} from "#enums";
import {
CommandPhase, EnemyCommandPhase, SelectTargetPhase,
TurnStartPhase
} from "#app/phases";
import {Mode} from "#app/ui/ui";
import {getMovePosition} from "#app/test/utils/gameManagerUtils";
import {Moves} from "#app/data/enums/moves";
import {Command} from "#app/ui/command-ui-handler";
import {Stat} from "#app/data/pokemon-stat";
import TargetSelectUiHandler from "#app/ui/target-select-ui-handler";
import {Button} from "#app/enums/buttons";
describe("Battle order", () => {

View File

@ -2,7 +2,7 @@ import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest
import {generateStarter, getMovePosition,} from "#app/test/utils/gameManagerUtils";
import {Mode} from "#app/ui/ui";
import {GameModes} from "#app/game-mode";
import {Species} from "#app/data/enums/species";
import {Species, Moves, PlayerGender, Abilities} from "#enums";
import * as overrides from "../../overrides";
import {Command} from "#app/ui/command-ui-handler";
import {
@ -17,13 +17,10 @@ import {
TitlePhase,
TurnInitPhase, VictoryPhase,
} from "#app/phases";
import {Moves} from "#app/data/enums/moves";
import GameManager from "#app/test/utils/gameManager";
import Phaser from "phaser";
import {allSpecies} from "#app/data/pokemon-species";
import {PlayerGender} from "#app/data/enums/player-gender";
import { getGameMode } from "#app/game-mode.js";
import {Abilities} from "#app/data/enums/abilities";
describe("Test Battle Phase", () => {
let phaserGame: Phaser.Game;

View File

@ -2,9 +2,7 @@ import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest
import GameManager from "#app/test/utils/gameManager";
import Phaser from "phaser";
import * as overrides from "#app/overrides";
import {Species} from "#app/data/enums/species";
import {Moves} from "#app/data/enums/moves";
import {Abilities} from "#app/data/enums/abilities";
import {Species, Moves, Abilities} from "#enums";
describe("Test Battle Phase", () => {
let phaserGame: Phaser.Game;

View File

@ -1,14 +1,12 @@
import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest";
import {Mode} from "#app/ui/ui";
import {Species} from "#app/data/enums/species";
import {Species, Moves, Abilities} from "#enums";
import * as overrides from "../../overrides";
import {
CommandPhase,
} from "#app/phases";
import {Moves} from "#app/data/enums/moves";
import GameManager from "#app/test/utils/gameManager";
import Phaser from "phaser";
import {Abilities} from "#app/data/enums/abilities";
describe("Test Battle Phase", () => {
let phaserGame: Phaser.Game;

View File

@ -1,7 +1,7 @@
import {beforeAll, describe, expect, it} from "vitest";
import BattleScene from "../../battle-scene";
import { getLegendaryGachaSpeciesForTimestamp } from "#app/data/egg.js";
import { Species } from "#app/data/enums/species.js";
import { Species } from "#enums";
import Phaser from "phaser";
describe("getLegendaryGachaSpeciesForTimestamp", () => {

View File

@ -5,7 +5,7 @@ async function importModule() {
try {
initStatsKeys();
const { PokemonMove } = await import("#app/field/pokemon");
const { Species } = await import("#app/data/enums/species");
const { Species } = await import("#enums");
return {
PokemonMove,
Species,

View File

@ -2,8 +2,7 @@ import {afterEach, beforeAll, beforeEach, describe, expect, it, vi} from "vitest
import Phaser from "phaser";
import GameManager from "#app/test/utils/gameManager";
import * as overrides from "#app/overrides";
import {Abilities} from "#app/data/enums/abilities";
import {Species} from "#app/data/enums/species";
import {Abilities, Moves, Species} from "#enums";
import {
CommandPhase,
EnemyCommandPhase,
@ -11,7 +10,6 @@ import {
TurnEndPhase,
} from "#app/phases";
import {Mode} from "#app/ui/ui";
import {Moves} from "#app/data/enums/moves";
import {getMovePosition} from "#app/test/utils/gameManagerUtils";
import {Command} from "#app/ui/command-ui-handler";
import {StatusEffect} from "#app/data/status-effect";

View File

@ -1,7 +1,7 @@
import {afterEach, beforeAll, describe, expect, it} from "vitest";
import Phaser from "phaser";
import GameManager from "#app/test/utils/gameManager";
import {Species} from "#app/data/enums/species";
import {Species} from "#enums";
import i18next from "i18next";
import {initI18n} from "#app/plugins/i18n";

Some files were not shown because too many files have changed in this diff Show More