Merge branch 'beta' into fix_roar_whirlwind_failing_5010

This commit is contained in:
Sophia :) 2025-04-23 23:30:36 +01:00 committed by GitHub
commit 8f9f5874c8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
615 changed files with 5022 additions and 3799 deletions

View File

@ -15,7 +15,7 @@ jobs:
submodules: 'recursive'
- uses: actions/setup-node@v4
with:
node-version: "20"
node-version-file: '.nvmrc'
- name: Install dependencies
run: npm ci
- name: Build

View File

@ -18,7 +18,7 @@ jobs:
submodules: 'recursive'
- uses: actions/setup-node@v4
with:
node-version: "20"
node-version-file: '.nvmrc'
- name: Install dependencies
run: npm ci
- name: Build

View File

@ -24,7 +24,7 @@ jobs:
steps:
- name: Checkout repository for Typedoc
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
submodules: 'recursive'
path: pokerogue_docs
@ -34,14 +34,14 @@ jobs:
sudo apt update
sudo apt install -y git openssh-client
- name: Setup Node 20.13.1
uses: actions/setup-node@v1
- name: Setup Node 22.14.1
uses: actions/setup-node@v4
with:
node-version: 20
node-version-file: "pokerogue_docs/.nvmrc"
- name: Checkout repository for Github Pages
if: github.event_name == 'push'
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
path: pokerogue_gh
ref: gh-pages

View File

@ -29,6 +29,7 @@ jobs:
uses: actions/setup-node@v4 # Use the setup-node action version 4
with:
node-version-file: '.nvmrc'
cache: 'npm'
- name: Install Node.js dependencies # Step to install Node.js dependencies
run: npm ci # Use 'npm ci' to install dependencies

View File

@ -19,13 +19,14 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Check out Git repository
uses: actions/checkout@v4
uses: actions/checkout@v4.2.2
with:
submodules: 'recursive'
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20
node-version-file: '.nvmrc'
cache: 'npm'
- name: Install Node.js dependencies
run: npm ci
- name: Run tests

2
.nvmrc
View File

@ -1 +1 @@
v20.13.1
v22.14.0

View File

@ -12,7 +12,7 @@ If you have the motivation and experience with Typescript/Javascript (or are wil
#### Prerequisites
- node: 20.13.1
- node: 22.14.0
- npm: [how to install](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)
#### Running Locally

View File

@ -31,7 +31,7 @@
"src/overrides.ts",
// TODO: these files are too big and complex, ignore them until their respective refactors
"src/data/moves/move.ts",
"src/data/ability.ts",
"src/data/abilities/ability.ts",
"src/field/pokemon.ts",
// this file is just too big:

17
package-lock.json generated
View File

@ -27,7 +27,7 @@
"@hpcc-js/wasm": "^2.22.4",
"@stylistic/eslint-plugin-ts": "^4.1.0",
"@types/jsdom": "^21.1.7",
"@types/node": "^20.12.13",
"@types/node": "^22.13.14",
"@typescript-eslint/eslint-plugin": "^8.28.0",
"@typescript-eslint/parser": "^8.28.0",
"@vitest/coverage-istanbul": "^3.0.9",
@ -2582,12 +2582,13 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "20.14.11",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.11.tgz",
"integrity": "sha512-kprQpL8MMeszbz6ojB5/tU8PLN4kesnN8Gjzw349rDlNgsSzg90lAVj3llK99Dh7JON+t9AuscPPFW6mPbTnSA==",
"version": "22.13.14",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.14.tgz",
"integrity": "sha512-Zs/Ollc1SJ8nKUAgc7ivOEdIBM8JAKgrqqUYi2J997JuKO7/tpQC+WCetQ1sypiKCQWHdvdg9wBNpUPEWZae7w==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~5.26.4"
"undici-types": "~6.20.0"
}
},
"node_modules/@types/statuses": {
@ -7312,9 +7313,9 @@
"dev": true
},
"node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"version": "6.20.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
"dev": true,
"license": "MIT"
},

View File

@ -33,7 +33,7 @@
"@hpcc-js/wasm": "^2.22.4",
"@stylistic/eslint-plugin-ts": "^4.1.0",
"@types/jsdom": "^21.1.7",
"@types/node": "^20.12.13",
"@types/node": "^22.13.14",
"@typescript-eslint/eslint-plugin": "^8.28.0",
"@typescript-eslint/parser": "^8.28.0",
"@vitest/coverage-istanbul": "^3.0.9",
@ -67,6 +67,6 @@
"phaser3-rex-plugins": "^1.80.14"
},
"engines": {
"node": ">=20.0.0"
"node": ">=22.0.0"
}
}

View File

@ -0,0 +1,11 @@
import type { AbAttr } from "#app/data/abilities/ab-attrs/ab-attr";
import type Move from "#app/data/moves/move";
import type Pokemon from "#app/field/pokemon";
import type { BattleStat } from "#enums/stat";
export type AbAttrApplyFunc<TAttr extends AbAttr> = (attr: TAttr, passive: boolean) => void;
export type AbAttrSuccessFunc<TAttr extends AbAttr> = (attr: TAttr, passive: boolean) => boolean;
export type AbAttrCondition = (pokemon: Pokemon) => boolean;
export type PokemonAttackCondition = (user: Pokemon | null, target: Pokemon | null, move: Move) => boolean;
export type PokemonDefendCondition = (target: Pokemon, user: Pokemon, move: Move) => boolean;
export type PokemonStatStageChangeCondition = (target: Pokemon, statsChanged: BattleStat[], stages: number) => boolean;

View File

@ -1,7 +1,7 @@
import { pokerogueApi } from "#app/plugins/api/pokerogue-api";
import type { UserInfo } from "#app/@types/UserInfo";
import { bypassLogin } from "#app/battle-scene";
import { randomString } from "#app/utils";
import { bypassLogin } from "./global-vars/bypass-login";
import { randomString } from "#app/utils/common";
export let loggedInUser: UserInfo | null = null;
// This is a random string that is used to identify the client session - unique per session (tab or window) so that the game will only save on the one that the server is expecting

View File

@ -18,7 +18,7 @@ import {
isNullOrUndefined,
BooleanHolder,
type Constructor,
} from "#app/utils";
} from "#app/utils/common";
import type { Modifier, ModifierPredicate, TurnHeldItemTransferModifier } from "./modifier/modifier";
import {
ConsumableModifier,
@ -67,7 +67,6 @@ import {
} from "#app/modifier/modifier-type";
import AbilityBar from "#app/ui/ability-bar";
import {
allAbilities,
applyAbAttrs,
applyPostBattleInitAbAttrs,
applyPostItemLostAbAttrs,
@ -75,9 +74,11 @@ import {
DoubleBattleChanceAbAttr,
PostBattleInitAbAttr,
PostItemLostAbAttr,
} from "#app/data/ability";
} from "#app/data/abilities/ability";
import { allAbilities } from "./data/data-lists";
import type { FixedBattleConfig } from "#app/battle";
import Battle, { BattleType } from "#app/battle";
import Battle from "#app/battle";
import { BattleType } from "#enums/battle-type";
import type { GameMode } from "#app/game-mode";
import { GameModes, getGameMode } from "#app/game-mode";
import FieldSpritePipeline from "#app/pipelines/field-sprite";
@ -150,7 +151,6 @@ import { NextEncounterPhase } from "#app/phases/next-encounter-phase";
import { PokemonAnimPhase } from "#app/phases/pokemon-anim-phase";
import { QuietFormChangePhase } from "#app/phases/quiet-form-change-phase";
import { ReturnPhase } from "#app/phases/return-phase";
import { SelectBiomePhase } from "#app/phases/select-biome-phase";
import { ShowTrainerPhase } from "#app/phases/show-trainer-phase";
import { SummonPhase } from "#app/phases/summon-phase";
import { SwitchPhase } from "#app/phases/switch-phase";
@ -184,8 +184,8 @@ import { HideAbilityPhase } from "#app/phases/hide-ability-phase";
import { expSpriteKeys } from "./sprites/sprite-keys";
import { hasExpSprite } from "./sprites/sprite-utils";
import { timedEventManager } from "./global-event-manager";
export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1";
import { starterColors } from "./global-vars/starter-colors";
import { startingWave } from "./starting-wave";
const DEBUG_RNG = false;
@ -193,13 +193,6 @@ const OPP_IVS_OVERRIDE_VALIDATED: number[] = (
Array.isArray(Overrides.OPP_IVS_OVERRIDE) ? Overrides.OPP_IVS_OVERRIDE : new Array(6).fill(Overrides.OPP_IVS_OVERRIDE)
).map(iv => (Number.isNaN(iv) || iv === null || iv > 31 ? -1 : iv));
export const startingWave = Overrides.STARTING_WAVE_OVERRIDE || 1;
export let starterColors: StarterColors;
interface StarterColors {
[key: string]: [string, string];
}
export interface PokeballCounts {
[pb: string]: number;
}
@ -809,11 +802,11 @@ export default class BattleScene extends SceneBase {
}
async initStarterColors(): Promise<void> {
if (starterColors) {
if (Object.keys(starterColors).length > 0) {
// already initialized
return;
}
const sc = await this.cachedFetch("./starter-colors.json").then(res => res.json());
starterColors = {};
for (const key of Object.keys(sc)) {
starterColors[key] = sc[key];
}
@ -1304,6 +1297,16 @@ export default class BattleScene extends SceneBase {
return Math.max(doubleChance.value, 1);
}
isNewBiome(currentBattle = this.currentBattle) {
const isWaveIndexMultipleOfTen = !(currentBattle.waveIndex % 10);
const isEndlessOrDaily = this.gameMode.hasShortBiomes || this.gameMode.isDaily;
const isEndlessFifthWave = this.gameMode.hasShortBiomes && currentBattle.waveIndex % 5 === 0;
const isWaveIndexMultipleOfFiftyMinusOne = currentBattle.waveIndex % 50 === 49;
const isNewBiome =
isWaveIndexMultipleOfTen || isEndlessFifthWave || (isEndlessOrDaily && isWaveIndexMultipleOfFiftyMinusOne);
return isNewBiome;
}
// TODO: ...this never actually returns `null`, right?
newBattle(
waveIndex?: number,
@ -1338,22 +1341,27 @@ export default class BattleScene extends SceneBase {
} else {
if (
!this.gameMode.hasTrainers ||
Overrides.BATTLE_TYPE_OVERRIDE === BattleType.WILD ||
(Overrides.DISABLE_STANDARD_TRAINERS_OVERRIDE && isNullOrUndefined(trainerData))
) {
newBattleType = BattleType.WILD;
} else if (battleType === undefined) {
newBattleType = this.gameMode.isWaveTrainer(newWaveIndex, this.arena) ? BattleType.TRAINER : BattleType.WILD;
} else {
newBattleType = battleType;
newBattleType =
Overrides.BATTLE_TYPE_OVERRIDE ??
battleType ??
(this.gameMode.isWaveTrainer(newWaveIndex, this.arena) ? BattleType.TRAINER : BattleType.WILD);
}
if (newBattleType === BattleType.TRAINER) {
const trainerType = this.arena.randomTrainerType(newWaveIndex);
const trainerType =
Overrides.RANDOM_TRAINER_OVERRIDE?.trainerType ?? this.arena.randomTrainerType(newWaveIndex);
let doubleTrainer = false;
if (trainerConfigs[trainerType].doubleOnly) {
doubleTrainer = true;
} else if (trainerConfigs[trainerType].hasDouble) {
doubleTrainer = !randSeedInt(this.getDoubleBattleChance(newWaveIndex, playerField));
doubleTrainer =
Overrides.RANDOM_TRAINER_OVERRIDE?.alwaysDouble ||
!randSeedInt(this.getDoubleBattleChance(newWaveIndex, playerField));
// Add a check that special trainers can't be double except for tate and liza - they should use the normal double chance
if (
trainerConfigs[trainerType].trainerTypeDouble &&
@ -1373,7 +1381,10 @@ export default class BattleScene extends SceneBase {
// Check for mystery encounter
// Can only occur in place of a standard (non-boss) wild battle, waves 10-180
if (this.isWaveMysteryEncounter(newBattleType, newWaveIndex) || newBattleType === BattleType.MYSTERY_ENCOUNTER) {
if (
!Overrides.BATTLE_TYPE_OVERRIDE &&
(this.isWaveMysteryEncounter(newBattleType, newWaveIndex) || newBattleType === BattleType.MYSTERY_ENCOUNTER)
) {
newBattleType = BattleType.MYSTERY_ENCOUNTER;
// Reset to base spawn weight
this.mysteryEncounterSaveData.encounterSpawnChance = BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT;
@ -1383,9 +1394,9 @@ export default class BattleScene extends SceneBase {
if (double === undefined && newWaveIndex > 1) {
if (newBattleType === BattleType.WILD && !this.gameMode.isWaveFinal(newWaveIndex)) {
newDouble = !randSeedInt(this.getDoubleBattleChance(newWaveIndex, playerField));
} else if (newBattleType === BattleType.TRAINER) {
newDouble = newTrainer?.variant === TrainerVariant.DOUBLE;
}
} else if (double === undefined && newBattleType === BattleType.TRAINER) {
newDouble = newTrainer?.variant === TrainerVariant.DOUBLE;
} else if (!battleConfig) {
newDouble = !!double;
}
@ -1395,10 +1406,10 @@ export default class BattleScene extends SceneBase {
newDouble = false;
}
if (!isNullOrUndefined(Overrides.BATTLE_TYPE_OVERRIDE)) {
if (!isNullOrUndefined(Overrides.BATTLE_STYLE_OVERRIDE)) {
let doubleOverrideForWave: "single" | "double" | null = null;
switch (Overrides.BATTLE_TYPE_OVERRIDE) {
switch (Overrides.BATTLE_STYLE_OVERRIDE) {
case "double":
doubleOverrideForWave = "double";
break;
@ -1418,7 +1429,7 @@ export default class BattleScene extends SceneBase {
}
/**
* Override battles into single only if not fighting with trainers.
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/1948 | GitHub Issue #1948}
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/1948 GitHub Issue #1948}
*/
if (newBattleType !== BattleType.TRAINER && doubleOverrideForWave === "single") {
newDouble = false;
@ -1459,12 +1470,7 @@ export default class BattleScene extends SceneBase {
}
if (!waveIndex && lastBattle) {
const isWaveIndexMultipleOfTen = !(lastBattle.waveIndex % 10);
const isEndlessOrDaily = this.gameMode.hasShortBiomes || this.gameMode.isDaily;
const isEndlessFifthWave = this.gameMode.hasShortBiomes && lastBattle.waveIndex % 5 === 0;
const isWaveIndexMultipleOfFiftyMinusOne = lastBattle.waveIndex % 50 === 49;
const isNewBiome =
isWaveIndexMultipleOfTen || isEndlessFifthWave || (isEndlessOrDaily && isWaveIndexMultipleOfFiftyMinusOne);
const isNewBiome = this.isNewBiome(lastBattle);
const resetArenaState =
isNewBiome ||
[BattleType.TRAINER, BattleType.MYSTERY_ENCOUNTER].includes(this.currentBattle.battleType) ||
@ -1513,7 +1519,6 @@ export default class BattleScene extends SceneBase {
if (!this.gameMode.hasRandomBiomes && !isNewBiome) {
this.pushPhase(new NextEncounterPhase());
} else {
this.pushPhase(new SelectBiomePhase());
this.pushPhase(new NewBiomeEncounterPhase());
const newMaxExpLevel = this.getMaxExpLevel();

View File

@ -8,7 +8,7 @@ import {
shiftCharCodes,
randSeedItem,
randInt,
} from "#app/utils";
} from "#app/utils/common";
import Trainer, { TrainerVariant } from "./field/trainer";
import type { GameMode } from "./game-mode";
import { MoneyMultiplierModifier, PokemonHeldItemModifier } from "./modifier/modifier";
@ -30,36 +30,8 @@ import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
import type { CustomModifierSettings } from "#app/modifier/modifier-type";
import { ModifierTier } from "#app/modifier/modifier-tier";
import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
export enum ClassicFixedBossWaves {
TOWN_YOUNGSTER = 5,
RIVAL_1 = 8,
RIVAL_2 = 25,
EVIL_GRUNT_1 = 35,
RIVAL_3 = 55,
EVIL_GRUNT_2 = 62,
EVIL_GRUNT_3 = 64,
EVIL_ADMIN_1 = 66,
RIVAL_4 = 95,
EVIL_GRUNT_4 = 112,
EVIL_ADMIN_2 = 114,
EVIL_BOSS_1 = 115,
RIVAL_5 = 145,
EVIL_BOSS_2 = 165,
ELITE_FOUR_1 = 182,
ELITE_FOUR_2 = 184,
ELITE_FOUR_3 = 186,
ELITE_FOUR_4 = 188,
CHAMPION = 190,
RIVAL_6 = 195,
}
export enum BattleType {
WILD,
TRAINER,
CLEAR,
MYSTERY_ENCOUNTER,
}
import { BattleType } from "#enums/battle-type";
import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves";
export enum BattlerIndex {
ATTACKER = -1,

View File

@ -9,3 +9,8 @@ export const SESSION_ID_COOKIE_NAME: string = "pokerogue_sessionId";
/** Max value for an integer attribute in {@linkcode SystemSaveData} */
export const MAX_INT_ATTR_VALUE = 0x80000000;
/** The min and max waves for mystery encounters to spawn in classic mode */
export const CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES: [number, number] = [10, 180] as const;
/** The min and max waves for mystery encounters to spawn in challenge mode */
export const CHALLENGE_MODE_MYSTERY_ENCOUNTER_WAVES: [number, number] = [10, 180] as const;

View File

@ -0,0 +1,54 @@
import type { AbAttrCondition } from "#app/@types/ability-types";
import type Pokemon from "#app/field/pokemon";
import type { BooleanHolder } from "#app/utils/common";
export abstract class AbAttr {
public showAbility: boolean;
private extraCondition: AbAttrCondition;
constructor(showAbility = true) {
this.showAbility = showAbility;
}
/**
* Applies ability effects without checking conditions
* @param _pokemon - The pokemon to apply this ability to
* @param _passive - Whether or not the ability is a passive
* @param _simulated - Whether the call is simulated
* @param _args - Extra args passed to the function. Handled by child classes.
* @see {@linkcode canApply}
*/
apply(
_pokemon: Pokemon,
_passive: boolean,
_simulated: boolean,
_cancelled: BooleanHolder | null,
_args: any[],
): void {}
getTriggerMessage(_pokemon: Pokemon, _abilityName: string, ..._args: any[]): string | null {
return null;
}
getCondition(): AbAttrCondition | null {
return this.extraCondition || null;
}
addCondition(condition: AbAttrCondition): AbAttr {
this.extraCondition = condition;
return this;
}
/**
* Returns a boolean describing whether the ability can be applied under current conditions
* @param _pokemon - The pokemon to apply this ability to
* @param _passive - Whether or not the ability is a passive
* @param _simulated - Whether the call is simulated
* @param _args - Extra args passed to the function. Handled by child classes.
* @returns `true` if the ability can be applied, `false` otherwise
* @see {@linkcode apply}
*/
canApply(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _args: any[]): boolean {
return true;
}
}

View File

@ -0,0 +1,137 @@
import { Abilities } from "#enums/abilities";
import type { AbAttrCondition } from "#app/@types/ability-types";
import type { AbAttr } from "#app/data/abilities/ab-attrs/ab-attr";
import i18next from "i18next";
import type { Localizable } from "#app/interfaces/locales";
import type { Constructor } from "#app/utils/common";
export class Ability implements Localizable {
public id: Abilities;
private nameAppend: string;
public name: string;
public description: string;
public generation: number;
public isBypassFaint: boolean;
public isIgnorable: boolean;
public isSuppressable = true;
public isCopiable = true;
public isReplaceable = true;
public attrs: AbAttr[];
public conditions: AbAttrCondition[];
constructor(id: Abilities, generation: number) {
this.id = id;
this.nameAppend = "";
this.generation = generation;
this.attrs = [];
this.conditions = [];
this.isSuppressable = true;
this.isCopiable = true;
this.isReplaceable = true;
this.localize();
}
public get isSwappable(): boolean {
return this.isCopiable && this.isReplaceable;
}
localize(): void {
const i18nKey = Abilities[this.id]
.split("_")
.filter(f => f)
.map((f, i) => (i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()))
.join("") as string;
this.name = this.id ? `${i18next.t(`ability:${i18nKey}.name`) as string}${this.nameAppend}` : "";
this.description = this.id ? (i18next.t(`ability:${i18nKey}.description`) as string) : "";
}
/**
* Get all ability attributes that match `attrType`
* @param attrType any attribute that extends {@linkcode AbAttr}
* @returns Array of attributes that match `attrType`, Empty Array if none match.
*/
getAttrs<T extends AbAttr>(attrType: Constructor<T>): T[] {
return this.attrs.filter((a): a is T => a instanceof attrType);
}
/**
* Check if an ability has an attribute that matches `attrType`
* @param attrType any attribute that extends {@linkcode AbAttr}
* @returns true if the ability has attribute `attrType`
*/
hasAttr<T extends AbAttr>(attrType: Constructor<T>): boolean {
return this.attrs.some(attr => attr instanceof attrType);
}
attr<T extends Constructor<AbAttr>>(AttrType: T, ...args: ConstructorParameters<T>): Ability {
const attr = new AttrType(...args);
this.attrs.push(attr);
return this;
}
conditionalAttr<T extends Constructor<AbAttr>>(
condition: AbAttrCondition,
AttrType: T,
...args: ConstructorParameters<T>
): Ability {
const attr = new AttrType(...args);
attr.addCondition(condition);
this.attrs.push(attr);
return this;
}
bypassFaint(): Ability {
this.isBypassFaint = true;
return this;
}
ignorable(): Ability {
this.isIgnorable = true;
return this;
}
unsuppressable(): Ability {
this.isSuppressable = false;
return this;
}
uncopiable(): Ability {
this.isCopiable = false;
return this;
}
unreplaceable(): Ability {
this.isReplaceable = false;
return this;
}
condition(condition: AbAttrCondition): Ability {
this.conditions.push(condition);
return this;
}
partial(): this {
this.nameAppend += " (P)";
return this;
}
unimplemented(): this {
this.nameAppend += " (N)";
return this;
}
/**
* Internal flag used for developers to document edge cases. When using this, please be sure to document the edge case.
* @returns the ability
*/
edgeCase(): this {
return this;
}
}

View File

@ -1,228 +1,78 @@
import type { EnemyPokemon, PokemonMove } from "../field/pokemon";
import type Pokemon from "../field/pokemon";
import { HitResult, MoveResult, PlayerPokemon } from "../field/pokemon";
import { PokemonType } from "#enums/pokemon-type";
import { BooleanHolder, NumberHolder, toDmgValue, isNullOrUndefined, randSeedItem, randSeedInt, type Constructor } from "#app/utils";
import { getPokemonNameWithAffix } from "../messages";
import type { Weather } from "#app/data/weather";
import type { BattlerTag } from "./battler-tags";
import { BattlerTagLapseType, GroundedTag } from "./battler-tags";
import { HitResult, MoveResult, PlayerPokemon } from "#app/field/pokemon";
import { BooleanHolder, NumberHolder, toDmgValue, isNullOrUndefined, randSeedItem, randSeedInt, type Constructor } from "#app/utils/common";
import { getPokemonNameWithAffix } from "#app/messages";
import { BattlerTagLapseType, GroundedTag } from "#app/data/battler-tags";
import { getNonVolatileStatusEffects, getStatusEffectDescriptor, getStatusEffectHealText } from "#app/data/status-effect";
import { Gender } from "./gender";
import type Move from "./moves/move";
import { AttackMove, FlinchAttr, OneHitKOAttr, HitHealAttr, allMoves, StatusMove, SelfStatusMove, VariablePowerAttr, applyMoveAttrs, VariableMoveTypeAttr, RandomMovesetMoveAttr, RandomMoveAttr, NaturePowerAttr, CopyMoveAttr, NeutralDamageAgainstFlyingTypeMultiplierAttr, FixedDamageAttr } from "./moves/move";
import { MoveFlags } from "#enums/MoveFlags";
import { MoveTarget } from "#enums/MoveTarget";
import { MoveCategory } from "#enums/MoveCategory";
import type { ArenaTrapTag, SuppressAbilitiesTag } from "./arena-tag";
import { ArenaTagSide } from "./arena-tag";
import { BerryModifier, HitHealModifier, PokemonHeldItemModifier } from "../modifier/modifier";
import { TerrainType } from "./terrain";
import { SpeciesFormChangeAbilityTrigger, SpeciesFormChangeRevertWeatherFormTrigger, SpeciesFormChangeWeatherTrigger } from "./pokemon-forms";
import { Gender } from "#app/data/gender";
import {
AttackMove,
FlinchAttr,
OneHitKOAttr,
HitHealAttr,
allMoves,
StatusMove,
SelfStatusMove,
VariablePowerAttr,
applyMoveAttrs,
VariableMoveTypeAttr,
RandomMovesetMoveAttr,
RandomMoveAttr,
NaturePowerAttr,
CopyMoveAttr,
NeutralDamageAgainstFlyingTypeMultiplierAttr,
FixedDamageAttr,
} from "#app/data/moves/move";
import { ArenaTagSide } from "#app/data/arena-tag";
import { BerryModifier, HitHealModifier, PokemonHeldItemModifier } from "#app/modifier/modifier";
import { TerrainType } from "#app/data/terrain";
import { SpeciesFormChangeAbilityTrigger, SpeciesFormChangeRevertWeatherFormTrigger, SpeciesFormChangeWeatherTrigger } from "#app/data/pokemon-forms";
import i18next from "i18next";
import type { Localizable } from "#app/interfaces/locales";
import { Command } from "../ui/command-ui-handler";
import { Command } from "#app/ui/command-ui-handler";
import { BerryModifierType } from "#app/modifier/modifier-type";
import { getPokeballName } from "./pokeball";
import type { BattlerIndex } from "#app/battle";
import { BattleType } from "#app/battle";
import { Abilities } from "#enums/abilities";
import { ArenaTagType } from "#enums/arena-tag-type";
import { BattlerTagType } from "#enums/battler-tag-type";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import { Stat, type BattleStat, type EffectiveStat, BATTLE_STATS, EFFECTIVE_STATS, getStatKey } from "#app/enums/stat";
import { getPokeballName } from "#app/data/pokeball";
import { BattleType } from "#enums/battle-type";
import { MovePhase } from "#app/phases/move-phase";
import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { globalScene } from "#app/global-scene";
import { SwitchType } from "#app/enums/switch-type";
import { SwitchPhase } from "#app/phases/switch-phase";
import { SwitchSummonPhase } from "#app/phases/switch-summon-phase";
import { BattleEndPhase } from "#app/phases/battle-end-phase";
import { NewBattlePhase } from "#app/phases/new-battle-phase";
import { MoveEndPhase } from "#app/phases/move-end-phase";
import { PokemonTransformPhase } from "#app/phases/pokemon-transform-phase";
import { allAbilities } from "#app/data/data-lists";
import { AbAttr } from "#app/data/abilities/ab-attrs/ab-attr";
import { Ability } from "#app/data/abilities/ability-class";
import { TrainerVariant } from "#app/field/trainer";
// Enum imports
import { Stat, type BattleStat , BATTLE_STATS, EFFECTIVE_STATS, getStatKey, type EffectiveStat } from "#enums/stat";
import { PokemonType } from "#enums/pokemon-type";
import { PokemonAnimType } from "#enums/pokemon-anim-type";
import { StatusEffect } from "#enums/status-effect";
import { WeatherType } from "#enums/weather-type";
import { PokemonTransformPhase } from "#app/phases/pokemon-transform-phase";
import { Abilities } from "#enums/abilities";
import { ArenaTagType } from "#enums/arena-tag-type";
import { BattlerTagType } from "#enums/battler-tag-type";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import { SwitchType } from "#enums/switch-type";
import { MoveFlags } from "#enums/MoveFlags";
import { MoveTarget } from "#enums/MoveTarget";
import { MoveCategory } from "#enums/MoveCategory";
export class Ability implements Localizable {
public id: Abilities;
private nameAppend: string;
public name: string;
public description: string;
public generation: number;
public isBypassFaint: boolean;
public isIgnorable: boolean;
public isSuppressable = true;
public isCopiable = true;
public isReplaceable = true;
public attrs: AbAttr[];
public conditions: AbAttrCondition[];
constructor(id: Abilities, generation: number) {
this.id = id;
this.nameAppend = "";
this.generation = generation;
this.attrs = [];
this.conditions = [];
this.isSuppressable = true;
this.isCopiable = true;
this.isReplaceable = true;
this.localize();
}
public get isSwappable(): boolean {
return this.isCopiable && this.isReplaceable;
}
localize(): void {
const i18nKey = Abilities[this.id].split("_").filter(f => f).map((f, i) => i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()).join("") as string;
this.name = this.id ? `${i18next.t(`ability:${i18nKey}.name`) as string}${this.nameAppend}` : "";
this.description = this.id ? i18next.t(`ability:${i18nKey}.description`) as string : "";
}
/**
* Get all ability attributes that match `attrType`
* @param attrType any attribute that extends {@linkcode AbAttr}
* @returns Array of attributes that match `attrType`, Empty Array if none match.
*/
getAttrs<T extends AbAttr>(attrType: Constructor<T> ): T[] {
return this.attrs.filter((a): a is T => a instanceof attrType);
}
/**
* Check if an ability has an attribute that matches `attrType`
* @param attrType any attribute that extends {@linkcode AbAttr}
* @returns true if the ability has attribute `attrType`
*/
hasAttr<T extends AbAttr>(attrType: Constructor<T>): boolean {
return this.attrs.some((attr) => attr instanceof attrType);
}
attr<T extends Constructor<AbAttr>>(AttrType: T, ...args: ConstructorParameters<T>): Ability {
const attr = new AttrType(...args);
this.attrs.push(attr);
return this;
}
conditionalAttr<T extends Constructor<AbAttr>>(condition: AbAttrCondition, AttrType: T, ...args: ConstructorParameters<T>): Ability {
const attr = new AttrType(...args);
attr.addCondition(condition);
this.attrs.push(attr);
return this;
}
bypassFaint(): Ability {
this.isBypassFaint = true;
return this;
}
ignorable(): Ability {
this.isIgnorable = true;
return this;
}
unsuppressable(): Ability {
this.isSuppressable = false;
return this;
}
uncopiable(): Ability {
this.isCopiable = false;
return this;
}
unreplaceable(): Ability {
this.isReplaceable = false;
return this;
}
condition(condition: AbAttrCondition): Ability {
this.conditions.push(condition);
return this;
}
partial(): this {
this.nameAppend += " (P)";
return this;
}
unimplemented(): this {
this.nameAppend += " (N)";
return this;
}
/**
* Internal flag used for developers to document edge cases. When using this, please be sure to document the edge case.
* @returns the ability
*/
edgeCase(): this {
return this;
}
}
type AbAttrApplyFunc<TAttr extends AbAttr> = (attr: TAttr, passive: boolean) => void;
type AbAttrSuccessFunc<TAttr extends AbAttr> = (attr: TAttr, passive: boolean) => boolean;
type AbAttrCondition = (pokemon: Pokemon) => boolean;
// TODO: Can this be improved?
type PokemonAttackCondition = (user: Pokemon | null, target: Pokemon | null, move: Move) => boolean;
type PokemonDefendCondition = (target: Pokemon, user: Pokemon, move: Move) => boolean;
type PokemonStatStageChangeCondition = (target: Pokemon, statsChanged: BattleStat[], stages: number) => boolean;
export abstract class AbAttr {
public showAbility: boolean;
private extraCondition: AbAttrCondition;
constructor(showAbility = true) {
this.showAbility = showAbility;
}
/**
* Applies ability effects without checking conditions
* @param pokemon - The pokemon to apply this ability to
* @param passive - Whether or not the ability is a passive
* @param simulated - Whether the call is simulated
* @param args - Extra args passed to the function. Handled by child classes.
* @see {@linkcode canApply}
*/
apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: BooleanHolder | null, args: any[]): void {}
getTriggerMessage(_pokemon: Pokemon, _abilityName: string, ..._args: any[]): string | null {
return null;
}
getCondition(): AbAttrCondition | null {
return this.extraCondition || null;
}
addCondition(condition: AbAttrCondition): AbAttr {
this.extraCondition = condition;
return this;
}
/**
* Returns a boolean describing whether the ability can be applied under current conditions
* @param pokemon - The pokemon to apply this ability to
* @param passive - Whether or not the ability is a passive
* @param simulated - Whether the call is simulated
* @param args - Extra args passed to the function. Handled by child classes.
* @returns `true` if the ability can be applied, `false` otherwise
* @see {@linkcode apply}
*/
canApply(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean {
return true;
}
}
// Type imports
import type { EnemyPokemon, PokemonMove } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon";
import type { Weather } from "#app/data/weather";
import type { BattlerTag } from "#app/data/battler-tags";
import type { AbAttrCondition, PokemonDefendCondition, PokemonStatStageChangeCondition, PokemonAttackCondition, AbAttrApplyFunc, AbAttrSuccessFunc } from "#app/@types/ability-types";
import type { BattlerIndex } from "#app/battle";
import type Move from "#app/data/moves/move";
import type { ArenaTrapTag, SuppressAbilitiesTag } from "#app/data/arena-tag";
import { SelectBiomePhase } from "#app/phases/select-biome-phase";
export class BlockRecoilDamageAttr extends AbAttr {
constructor() {
@ -233,7 +83,7 @@ export class BlockRecoilDamageAttr extends AbAttr {
cancelled.value = true;
}
getTriggerMessage(pokemon: Pokemon, abilityName: string, ...args: any[]) {
getTriggerMessage(pokemon: Pokemon, abilityName: string, ..._args: any[]) {
return i18next.t("abilityTriggers:blockRecoilDamage", { pokemonName: getPokemonNameWithAffix(pokemon), abilityName: abilityName });
}
}
@ -804,8 +654,8 @@ export class MoveImmunityStatStageChangeAbAttr extends MoveImmunityAbAttr {
*/
export class ReverseDrainAbAttr extends PostDefendAbAttr {
override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean {
return move.hasAttr(HitHealAttr) && !move.hitsSubstitute(attacker, pokemon);
override canApplyPostDefend(_pokemon: Pokemon, _passive: boolean, _simulated: boolean, _attacker: Pokemon, move: Move, _hitResult: HitResult | null, args: any[]): boolean {
return move.hasAttr(HitHealAttr);
}
/**
@ -844,7 +694,7 @@ export class PostDefendStatStageChangeAbAttr extends PostDefendAbAttr {
}
override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean {
return this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon);
return this.condition(pokemon, attacker, move);
}
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void {
@ -885,7 +735,7 @@ export class PostDefendHpGatedStatStageChangeAbAttr extends PostDefendAbAttr {
const hpGateFlat: number = Math.ceil(pokemon.getMaxHp() * this.hpGate);
const lastAttackReceived = pokemon.turnData.attacksReceived[pokemon.turnData.attacksReceived.length - 1];
const damageReceived = lastAttackReceived?.damage || 0;
return this.condition(pokemon, attacker, move) && (pokemon.hp <= hpGateFlat && (pokemon.hp + damageReceived) > hpGateFlat) && !move.hitsSubstitute(attacker, pokemon);
return this.condition(pokemon, attacker, move) && (pokemon.hp <= hpGateFlat && (pokemon.hp + damageReceived) > hpGateFlat);
}
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void {
@ -908,7 +758,7 @@ export class PostDefendApplyArenaTrapTagAbAttr extends PostDefendAbAttr {
override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean {
const tag = globalScene.arena.getTag(this.tagType) as ArenaTrapTag;
return (this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon))
return (this.condition(pokemon, attacker, move))
&& (!globalScene.arena.getTag(this.tagType) || tag.layers < tag.maxLayers);
}
@ -930,7 +780,7 @@ export class PostDefendApplyBattlerTagAbAttr extends PostDefendAbAttr {
}
override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean {
return this.condition(pokemon, attacker, move) && !move.hitsSubstitute(attacker, pokemon);
return this.condition(pokemon, attacker, move);
}
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void {
@ -947,7 +797,7 @@ export class PostDefendTypeChangeAbAttr extends PostDefendAbAttr {
override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
this.type = attacker.getMoveType(move);
const pokemonTypes = pokemon.getTypes(true);
return hitResult < HitResult.NO_EFFECT && !move.hitsSubstitute(attacker, pokemon) && (simulated || pokemonTypes.length !== 1 || pokemonTypes[0] !== this.type);
return hitResult < HitResult.NO_EFFECT && (simulated || pokemonTypes.length !== 1 || pokemonTypes[0] !== this.type);
}
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, _args: any[]): void {
@ -974,7 +824,7 @@ export class PostDefendTerrainChangeAbAttr extends PostDefendAbAttr {
}
override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, args: any[]): boolean {
return hitResult < HitResult.NO_EFFECT && !move.hitsSubstitute(attacker, pokemon) && globalScene.arena.canSetTerrain(this.terrainType);
return hitResult < HitResult.NO_EFFECT && globalScene.arena.canSetTerrain(this.terrainType);
}
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult, _args: any[]): void {
@ -998,7 +848,7 @@ export class PostDefendContactApplyStatusEffectAbAttr extends PostDefendAbAttr {
override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean {
const effect = this.effects.length === 1 ? this.effects[0] : this.effects[pokemon.randSeedInt(this.effects.length)];
return move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon}) && !attacker.status
&& (this.chance === -1 || pokemon.randSeedInt(100) < this.chance) && !move.hitsSubstitute(attacker, pokemon)
&& (this.chance === -1 || pokemon.randSeedInt(100) < this.chance)
&& attacker.canSetStatus(effect, true, false, pokemon);
}
@ -1038,7 +888,7 @@ export class PostDefendContactApplyTagChanceAbAttr extends PostDefendAbAttr {
override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean {
return move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon}) && pokemon.randSeedInt(100) < this.chance
&& !move.hitsSubstitute(attacker, pokemon) && attacker.canAddTag(this.tagType);
&& attacker.canAddTag(this.tagType);
}
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void {
@ -1059,10 +909,6 @@ export class PostDefendCritStatStageChangeAbAttr extends PostDefendAbAttr {
this.stages = stages;
}
override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean {
return !move.hitsSubstitute(attacker, pokemon);
}
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void {
if (!simulated) {
globalScene.unshiftPhase(new StatStageChangePhase(pokemon.getBattlerIndex(), true, [ this.stat ], this.stages));
@ -1085,7 +931,7 @@ export class PostDefendContactDamageAbAttr extends PostDefendAbAttr {
override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean {
return !simulated && move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon})
&& !attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr) && !move.hitsSubstitute(attacker, pokemon);
&& !attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr);
}
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void {
@ -1144,7 +990,7 @@ export class PostDefendWeatherChangeAbAttr extends PostDefendAbAttr {
}
override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean {
return (!(this.condition && !this.condition(pokemon, attacker, move) || move.hitsSubstitute(attacker, pokemon))
return (!(this.condition && !this.condition(pokemon, attacker, move))
&& !globalScene.arena.weather?.isImmutable() && globalScene.arena.canSetWeather(this.weatherType));
}
@ -1162,7 +1008,7 @@ export class PostDefendAbilitySwapAbAttr extends PostDefendAbAttr {
override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean {
return move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon})
&& attacker.getAbility().isSwappable && !move.hitsSubstitute(attacker, pokemon);
&& attacker.getAbility().isSwappable;
}
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, args: any[]): void {
@ -1188,10 +1034,10 @@ export class PostDefendAbilityGiveAbAttr extends PostDefendAbAttr {
override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean {
return move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon}) && attacker.getAbility().isSuppressable
&& !attacker.getAbility().hasAttr(PostDefendAbilityGiveAbAttr) && !move.hitsSubstitute(attacker, pokemon);
&& !attacker.getAbility().hasAttr(PostDefendAbilityGiveAbAttr);
}
override applyPostDefend(pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void {
override applyPostDefend(_pokemon: Pokemon, _passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, _hitResult: HitResult, _args: any[]): void {
if (!simulated) {
attacker.setTempAbility(allAbilities[this.ability]);
}
@ -1217,7 +1063,7 @@ export class PostDefendMoveDisableAbAttr extends PostDefendAbAttr {
}
override canApplyPostDefend(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean {
return attacker.getTag(BattlerTagType.DISABLED) === null && !move.hitsSubstitute(attacker, pokemon)
return attacker.getTag(BattlerTagType.DISABLED) === null
&& move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon}) && (this.chance === -1 || pokemon.randSeedInt(100) < this.chance);
}
@ -1921,7 +1767,6 @@ export class PostAttackApplyStatusEffectAbAttr extends PostAttackAbAttr {
override canApplyPostAttack(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker: Pokemon, move: Move, hitResult: HitResult | null, args: any[]): boolean {
if (
super.canApplyPostAttack(pokemon, passive, simulated, attacker, move, hitResult, args)
&& !(pokemon !== attacker && move.hitsSubstitute(attacker, pokemon))
&& (simulated || !attacker.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && pokemon !== attacker
&& (!this.contactRequired || move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user: attacker, target: pokemon})) && pokemon.randSeedInt(100) < this.chance && !pokemon.status)
) {
@ -1988,8 +1833,7 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr {
if (
!simulated &&
hitResult < HitResult.NO_EFFECT &&
(!this.condition || this.condition(pokemon, attacker, move)) &&
!move.hitsSubstitute(attacker, pokemon)
(!this.condition || this.condition(pokemon, attacker, move))
) {
const heldItems = this.getTargetHeldItems(attacker).filter((i) => i.isTransferable);
if (heldItems.length) {
@ -2370,18 +2214,6 @@ export class PostSummonMessageAbAttr extends PostSummonAbAttr {
}
}
/**
* Removes illusions when a Pokemon is summoned.
*/
export class PostSummonRemoveIllusionAbAttr extends PostSummonAbAttr {
applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean {
for (const pokemon of globalScene.getField(true)) {
pokemon.breakIllusion();
}
return true;
}
}
export class PostSummonUnnamedMessageAbAttr extends PostSummonAbAttr {
//Attr doesn't force pokemon name on the message
private message: string;
@ -4186,7 +4018,9 @@ export class PostTurnResetStatusAbAttr extends PostTurnAbAttr {
} else {
this.target = pokemon;
}
return !isNullOrUndefined(this.target?.status);
const effect = this.target?.status?.effect;
return !!effect && effect !== StatusEffect.FAINT;
}
override applyPostTurn(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): void {
@ -5224,6 +5058,8 @@ export class PostSummonStatStageChangeOnArenaAbAttr extends PostSummonStatStageC
/**
* Takes no damage from the first hit of a damaging move.
* This is used in the Disguise and Ice Face abilities.
*
* Does not apply to a user's substitute
* @extends ReceivedMoveDamageMultiplierAbAttr
*/
export class FormBlockDamageAbAttr extends ReceivedMoveDamageMultiplierAbAttr {
@ -5326,7 +5162,14 @@ export class IllusionPreSummonAbAttr extends PreSummonAbAttr {
}
}
export class IllusionBreakAbAttr extends PostDefendAbAttr {
export class IllusionBreakAbAttr extends AbAttr {
override apply(pokemon: Pokemon, _passive: boolean, _simulated: boolean, _cancelled: BooleanHolder | null, _args: any[]): void {
pokemon.breakIllusion();
pokemon.summonData.illusionBroken = true;
}
}
export class PostDefendIllusionBreakAbAttr extends PostDefendAbAttr {
/**
* Destroy the illusion upon taking damage
*
@ -5641,6 +5484,11 @@ class ForceSwitchOutHelper {
if (switchOutTarget.hp) {
globalScene.pushPhase(new BattleEndPhase(false));
if (globalScene.gameMode.hasRandomBiomes || globalScene.isNewBiome()) {
globalScene.pushPhase(new SelectBiomePhase());
}
globalScene.pushPhase(new NewBattlePhase());
}
}
@ -5677,8 +5525,8 @@ class ForceSwitchOutHelper {
const party = player ? globalScene.getPlayerParty() : globalScene.getEnemyParty();
return (!player && globalScene.currentBattle.battleType === BattleType.WILD)
|| party.filter(p => p.isAllowedInBattle()
&& (player || (p as EnemyPokemon).trainerSlot === (switchOutTarget as EnemyPokemon).trainerSlot)).length > globalScene.currentBattle.getBattlerCount();
|| party.filter(p => p.isAllowedInBattle() && !p.isOnField()
&& (player || (p as EnemyPokemon).trainerSlot === (switchOutTarget as EnemyPokemon).trainerSlot)).length > 0;
}
/**
@ -6418,7 +6266,7 @@ export function applyOnGainAbAttrs(
}
/**
* Clears primal weather/neutralizing gas during the turn if {@linkcode pokemon}'s ability corresponds to one
* Applies ability attributes which activate when the ability is lost or suppressed (i.e. primal weather)
*/
export function applyOnLoseAbAttrs(pokemon: Pokemon, passive = false, simulated = false, ...args: any[]): void {
applySingleAbAttrs<PreLeaveFieldAbAttr>(
@ -6430,6 +6278,17 @@ export function applyOnLoseAbAttrs(pokemon: Pokemon, passive = false, simulated
args,
true,
simulated);
applySingleAbAttrs<IllusionBreakAbAttr>(
pokemon,
passive,
IllusionBreakAbAttr,
(attr, passive) => attr.apply(pokemon, passive, simulated, null, args),
(attr, passive) => attr.canApply(pokemon, passive, simulated, args),
args,
true,
simulated
)
}
/**
@ -6453,10 +6312,9 @@ function getPokemonWithWeatherBasedForms() {
);
}
export const allAbilities = [ new Ability(Abilities.NONE, 3) ];
export function initAbilities() {
allAbilities.push(
new Ability(Abilities.NONE, 3),
new Ability(Abilities.STENCH, 3)
.attr(PostAttackApplyBattlerTagAbAttr, false, (user, target, move) => !move.hasAttr(FlinchAttr) && !move.hitsSubstitute(user, target) ? 10 : 0, BattlerTagType.FLINCHED),
new Ability(Abilities.DRIZZLE, 3)
@ -6853,11 +6711,11 @@ export function initAbilities() {
new Ability(Abilities.BAD_DREAMS, 4)
.attr(PostTurnHurtIfSleepingAbAttr),
new Ability(Abilities.PICKPOCKET, 5)
.attr(PostDefendStealHeldItemAbAttr, (target, user, move) => move.hasFlag(MoveFlags.MAKES_CONTACT))
.attr(PostDefendStealHeldItemAbAttr, (target, user, move) => move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user, target}))
.condition(getSheerForceHitDisableAbCondition()),
new Ability(Abilities.SHEER_FORCE, 5)
.attr(MovePowerBoostAbAttr, (user, target, move) => move.chance >= 1, 5461 / 4096)
.attr(MoveEffectChanceMultiplierAbAttr, 0), // Should disable life orb, eject button, red card, kee/maranga berry if they get implemented
.attr(MovePowerBoostAbAttr, (user, target, move) => move.chance >= 1, 1.3)
.attr(MoveEffectChanceMultiplierAbAttr, 0), // This attribute does not seem to function - Should disable life orb, eject button, red card, kee/maranga berry if they get implemented
new Ability(Abilities.CONTRARY, 5)
.attr(StatStageChangeMultiplierAbAttr, -1)
.ignorable(),
@ -6930,11 +6788,12 @@ export function initAbilities() {
return isNullOrUndefined(movePhase);
}, 1.3),
new Ability(Abilities.ILLUSION, 5)
//The pokemon generate an illusion if it's available
// The Pokemon generate an illusion if it's available
.attr(IllusionPreSummonAbAttr, false)
//The pokemon loses his illusion when he is damaged by a move
.attr(IllusionBreakAbAttr, true)
//Illusion is available again after a battle
.attr(IllusionBreakAbAttr)
// The Pokemon loses its illusion when damaged by a move
.attr(PostDefendIllusionBreakAbAttr, true)
// Illusion is available again after a battle
.conditionalAttr((pokemon) => pokemon.isAllowedInBattle(), IllusionPostBattleAbAttr, false)
.uncopiable()
.bypassFaint(),
@ -7203,7 +7062,7 @@ export function initAbilities() {
new Ability(Abilities.BATTERY, 7)
.attr(AllyMoveCategoryPowerBoostAbAttr, [ MoveCategory.SPECIAL ], 1.3),
new Ability(Abilities.FLUFFY, 7)
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => move.hasFlag(MoveFlags.MAKES_CONTACT), 0.5)
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user, target}), 0.5)
.attr(ReceivedMoveDamageMultiplierAbAttr, (target, user, move) => user.getMoveType(move) === PokemonType.FIRE, 2)
.ignorable(),
new Ability(Abilities.DAZZLING, 7)
@ -7212,7 +7071,7 @@ export function initAbilities() {
new Ability(Abilities.SOUL_HEART, 7)
.attr(PostKnockOutStatStageChangeAbAttr, Stat.SPATK, 1),
new Ability(Abilities.TANGLING_HAIR, 7)
.attr(PostDefendStatStageChangeAbAttr, (target, user, move) => move.hasFlag(MoveFlags.MAKES_CONTACT), Stat.SPD, -1, false),
.attr(PostDefendStatStageChangeAbAttr, (target, user, move) => move.doesFlagEffectApply({flag: MoveFlags.MAKES_CONTACT, user, target}), Stat.SPD, -1, false),
new Ability(Abilities.RECEIVER, 7)
.attr(CopyFaintedAllyAbilityAbAttr)
.uncopiable(),
@ -7348,8 +7207,6 @@ export function initAbilities() {
.attr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr)
.uncopiable()
.attr(NoTransformAbilityAbAttr)
.attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonNeutralizingGas", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }))
.attr(PostSummonRemoveIllusionAbAttr)
.bypassFaint(),
new Ability(Abilities.PASTEL_VEIL, 8)
.attr(PostSummonUserFieldRemoveStatusEffectAbAttr, StatusEffect.POISON, StatusEffect.TOXIC)
@ -7370,7 +7227,7 @@ export function initAbilities() {
new Ability(Abilities.CURIOUS_MEDICINE, 8)
.attr(PostSummonClearAllyStatStagesAbAttr),
new Ability(Abilities.TRANSISTOR, 8)
.attr(MoveTypePowerBoostAbAttr, PokemonType.ELECTRIC),
.attr(MoveTypePowerBoostAbAttr, PokemonType.ELECTRIC, 1.3),
new Ability(Abilities.DRAGONS_MAW, 8)
.attr(MoveTypePowerBoostAbAttr, PokemonType.DRAGON),
new Ability(Abilities.CHILLING_NEIGH, 8)

View File

@ -1,13 +1,13 @@
import { globalScene } from "#app/global-scene";
import type { Arena } from "#app/field/arena";
import { PokemonType } from "#enums/pokemon-type";
import { BooleanHolder, NumberHolder, toDmgValue } from "#app/utils";
import { BooleanHolder, NumberHolder, toDmgValue } from "#app/utils/common";
import { allMoves } from "#app/data/moves/move";
import { MoveTarget } from "#enums/MoveTarget";
import { MoveCategory } from "#enums/MoveCategory";
import { getPokemonNameWithAffix } from "#app/messages";
import type Pokemon from "#app/field/pokemon";
import { HitResult, PokemonMove } from "#app/field/pokemon";
import { HitResult } from "#app/field/pokemon";
import { StatusEffect } from "#enums/status-effect";
import type { BattlerIndex } from "#app/battle";
import {
@ -18,7 +18,7 @@ import {
applyAbAttrs,
applyOnGainAbAttrs,
applyOnLoseAbAttrs,
} from "#app/data/ability";
} from "#app/data/abilities/ability";
import { Stat } from "#enums/stat";
import { CommonAnim, CommonBattleAnim } from "#app/data/battle-anims";
import i18next from "i18next";
@ -335,7 +335,7 @@ export class ConditionalProtectTag extends ArenaTag {
* @param arena the {@linkcode Arena} containing this tag
* @param simulated `true` if the tag is applied quietly; `false` otherwise.
* @param isProtected a {@linkcode BooleanHolder} used to flag if the move is protected against
* @param attacker the attacking {@linkcode Pokemon}
* @param _attacker the attacking {@linkcode Pokemon}
* @param defender the defending {@linkcode Pokemon}
* @param moveId the {@linkcode Moves | identifier} for the move being used
* @param ignoresProtectBypass a {@linkcode BooleanHolder} used to flag if a protection effect supercedes effects that ignore protection
@ -345,7 +345,7 @@ export class ConditionalProtectTag extends ArenaTag {
arena: Arena,
simulated: boolean,
isProtected: BooleanHolder,
attacker: Pokemon,
_attacker: Pokemon,
defender: Pokemon,
moveId: Moves,
ignoresProtectBypass: BooleanHolder,
@ -354,8 +354,6 @@ export class ConditionalProtectTag extends ArenaTag {
if (!isProtected.value) {
isProtected.value = true;
if (!simulated) {
attacker.stopMultiHit(defender);
new CommonBattleAnim(CommonAnim.PROTECT, defender).play();
globalScene.queueMessage(
i18next.t("arenaTag:conditionalProtectApply", {
@ -899,7 +897,7 @@ export class DelayedAttackTag extends ArenaTag {
if (!ret) {
globalScene.unshiftPhase(
new MoveEffectPhase(this.sourceId!, [this.targetIndex], new PokemonMove(this.sourceMove!, 0, 0, true)),
new MoveEffectPhase(this.sourceId!, [this.targetIndex], allMoves[this.sourceMove!], false, true),
); // TODO: are those bangs correct?
}

View File

@ -1,5 +1,5 @@
import { PokemonType } from "#enums/pokemon-type";
import { randSeedInt, getEnumValues } from "#app/utils";
import { randSeedInt, getEnumValues } from "#app/utils/common";
import type { SpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions";
import { pokemonEvolutions } from "#app/data/balance/pokemon-evolutions";
import i18next from "i18next";

View File

@ -1,5 +1,5 @@
import { allMoves } from "#app/data/moves/move";
import { getEnumKeys, getEnumValues } from "#app/utils";
import { getEnumKeys, getEnumValues } from "#app/utils/common";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";

View File

@ -3,7 +3,7 @@ import { Gender } from "#app/data/gender";
import { PokeballType } from "#enums/pokeball";
import type Pokemon from "#app/field/pokemon";
import { PokemonType } from "#enums/pokemon-type";
import { randSeedInt } from "#app/utils";
import { randSeedInt } from "#app/utils/common";
import { WeatherType } from "#enums/weather-type";
import { Nature } from "#enums/nature";
import { Biome } from "#enums/biome";
@ -14,6 +14,7 @@ import { DamageMoneyRewardModifier, ExtraModifierModifier, MoneyMultiplierModifi
import { SpeciesFormKey } from "#enums/species-form-key";
import { speciesStarterCosts } from "./starters";
import i18next from "i18next";
import { initI18n } from "#app/plugins/i18n";
export enum SpeciesWildEvolutionDelay {
@ -95,6 +96,9 @@ export class SpeciesFormEvolution {
public description = "";
constructor(speciesId: Species, preFormKey: string | null, evoFormKey: string | null, level: number, item: EvolutionItem | null, condition: SpeciesEvolutionCondition | null, wildDelay?: SpeciesWildEvolutionDelay) {
if (!i18next.isInitialized) {
initI18n();
}
this.speciesId = speciesId;
this.preFormKey = preFormKey;
this.evoFormKey = evoFormKey;

View File

@ -11,87 +11,87 @@ export type SignatureSpecies = {
*/
export const signatureSpecies: SignatureSpecies = {
// Gym Leaders- Kanto
BROCK: [Species.GEODUDE, Species.ONIX],
MISTY: [Species.STARYU, Species.PSYDUCK],
LT_SURGE: [Species.VOLTORB, Species.PIKACHU, Species.ELECTABUZZ],
BROCK: [Species.ONIX, Species.GEODUDE, [Species.OMANYTE, Species.KABUTO], Species.AERODACTYL],
MISTY: [Species.STARYU, Species.PSYDUCK, Species.WOOPER, Species.LAPRAS],
LT_SURGE: [Species.PICHU, Species.VOLTORB, Species.ELEKID, Species.JOLTEON],
ERIKA: [Species.ODDISH, Species.BELLSPROUT, Species.TANGELA, Species.HOPPIP],
JANINE: [Species.VENONAT, Species.SPINARAK, Species.ZUBAT],
SABRINA: [Species.ABRA, Species.MR_MIME, Species.ESPEON],
BLAINE: [Species.GROWLITHE, Species.PONYTA, Species.MAGMAR],
GIOVANNI: [Species.SANDILE, Species.MURKROW, Species.NIDORAN_M, Species.NIDORAN_F],
JANINE: [Species.VENONAT, Species.SPINARAK, Species.ZUBAT, Species.KOFFING],
SABRINA: [Species.ABRA, Species.MR_MIME, Species.SMOOCHUM, Species.ESPEON],
BLAINE: [Species.GROWLITHE, Species.PONYTA, Species.MAGBY, Species.VULPIX],
GIOVANNI: [Species.RHYHORN, Species.MEOWTH, [Species.NIDORAN_F, Species.NIDORAN_M], Species.DIGLETT], // Tera Ground Meowth
// Gym Leaders- Johto
FALKNER: [Species.PIDGEY, Species.HOOTHOOT, Species.DODUO],
BUGSY: [Species.SCYTHER, Species.HERACROSS, Species.SHUCKLE, Species.PINSIR],
WHITNEY: [Species.JIGGLYPUFF, Species.MILTANK, Species.AIPOM, Species.GIRAFARIG],
MORTY: [Species.GASTLY, Species.MISDREAVUS, Species.SABLEYE],
CHUCK: [Species.POLIWRATH, Species.MANKEY],
JASMINE: [Species.MAGNEMITE, Species.STEELIX],
PRYCE: [Species.SEEL, Species.SWINUB],
CLAIR: [Species.DRATINI, Species.HORSEA, Species.GYARADOS],
FALKNER: [Species.PIDGEY, Species.HOOTHOOT, Species.NATU, Species.MURKROW],
BUGSY: [Species.SCYTHER, Species.SHUCKLE, Species.YANMA, [Species.PINSIR, Species.HERACROSS]],
WHITNEY: [Species.MILTANK, Species.AIPOM, Species.IGGLYBUFF, [Species.GIRAFARIG, Species.STANTLER]],
MORTY: [Species.GASTLY, Species.MISDREAVUS, Species.DUSKULL, Species.SABLEYE],
CHUCK: [Species.POLIWRATH, Species.MANKEY, Species.TYROGUE, Species.MACHOP],
JASMINE: [Species.STEELIX, Species.MAGNEMITE, Species.PINECO, Species.SKARMORY],
PRYCE: [Species.SWINUB, Species.SEEL, Species.SHELLDER, Species.SNEASEL],
CLAIR: [Species.HORSEA, Species.DRATINI, Species.MAGIKARP, Species.DRUDDIGON], // Tera Dragon Magikarp
// Gym Leaders- Hoenn
ROXANNE: [Species.GEODUDE, Species.NOSEPASS],
BRAWLY: [Species.MACHOP, Species.MAKUHITA],
WATTSON: [Species.MAGNEMITE, Species.VOLTORB, Species.ELECTRIKE],
FLANNERY: [Species.SLUGMA, Species.TORKOAL, Species.NUMEL],
NORMAN: [Species.SLAKOTH, Species.SPINDA, Species.ZIGZAGOON, Species.KECLEON],
ROXANNE: [Species.NOSEPASS, Species.GEODUDE, [Species.LILEEP, Species.ANORITH], Species.ARON],
BRAWLY: [Species.MAKUHITA, Species.MACHOP, Species.MEDITITE, Species.SHROOMISH],
WATTSON: [Species.ELECTRIKE, Species.VOLTORB, Species.MAGNEMITE, [Species.PLUSLE, Species.MINUN]],
FLANNERY: [Species.TORKOAL, Species.SLUGMA, Species.NUMEL, Species.HOUNDOUR],
NORMAN: [Species.SLAKOTH, Species.KECLEON, Species.WHISMUR, Species.ZANGOOSE],
WINONA: [Species.SWABLU, Species.WINGULL, Species.TROPIUS, Species.SKARMORY],
TATE: [Species.SOLROCK, Species.NATU, Species.CHIMECHO, Species.GALLADE],
LIZA: [Species.LUNATONE, Species.SPOINK, Species.BALTOY, Species.GARDEVOIR],
JUAN: [Species.HORSEA, Species.BARBOACH, Species.SPHEAL, Species.RELICANTH],
TATE: [Species.SOLROCK, Species.NATU, Species.CHINGLING, Species.GALLADE],
LIZA: [Species.LUNATONE, Species.BALTOY, Species.SPOINK, Species.GARDEVOIR],
JUAN: [Species.HORSEA, Species.SPHEAL, Species.BARBOACH, Species.CORPHISH],
// Gym Leaders- Sinnoh
ROARK: [Species.CRANIDOS, Species.LARVITAR, Species.GEODUDE],
GARDENIA: [Species.ROSELIA, Species.TANGELA, Species.TURTWIG],
MAYLENE: [Species.LUCARIO, Species.MEDITITE, Species.CHIMCHAR],
ROARK: [Species.CRANIDOS, Species.GEODUDE, Species.NOSEPASS, Species.LARVITAR],
GARDENIA: [Species.BUDEW, Species.CHERUBI, Species.TURTWIG, Species.LEAFEON],
MAYLENE: [Species.RIOLU, Species.MEDITITE, Species.CHIMCHAR, Species.CROAGUNK],
CRASHER_WAKE: [Species.BUIZEL, Species.WOOPER, Species.PIPLUP, Species.MAGIKARP],
FANTINA: [Species.MISDREAVUS, Species.DRIFLOON, Species.SPIRITOMB],
BYRON: [Species.SHIELDON, Species.BRONZOR, Species.AGGRON],
CANDICE: [Species.SNEASEL, Species.SNOVER, Species.SNORUNT],
VOLKNER: [Species.SHINX, Species.CHINCHOU, Species.ROTOM],
FANTINA: [Species.MISDREAVUS, Species.DRIFLOON, Species.DUSKULL, Species.SPIRITOMB],
BYRON: [Species.SHIELDON, Species.BRONZOR, Species.ARON, Species.SKARMORY],
CANDICE: [Species.FROSLASS, Species.SNOVER, Species.SNEASEL, Species.GLACEON],
VOLKNER: [Species.ELEKID, Species.SHINX, Species.CHINCHOU, Species.ROTOM],
// Gym Leaders- Unova
CILAN: [Species.PANSAGE, Species.FOONGUS, Species.PETILIL],
CHILI: [Species.PANSEAR, Species.DARUMAKA, Species.NUMEL],
CRESS: [Species.PANPOUR, Species.TYMPOLE, Species.SLOWPOKE],
CHEREN: [Species.LILLIPUP, Species.MINCCINO, Species.PIDOVE],
LENORA: [Species.PATRAT, Species.DEERLING, Species.AUDINO],
ROXIE: [Species.VENIPEDE, Species.TRUBBISH, Species.SKORUPI],
BURGH: [Species.SEWADDLE, Species.SHELMET, Species.KARRABLAST],
ELESA: [Species.EMOLGA, Species.BLITZLE, Species.JOLTIK],
CLAY: [Species.DRILBUR, Species.SANDILE, Species.GOLETT],
SKYLA: [Species.DUCKLETT, Species.WOOBAT, Species.RUFFLET],
BRYCEN: [Species.CRYOGONAL, Species.VANILLITE, Species.CUBCHOO],
DRAYDEN: [Species.DRUDDIGON, Species.AXEW, Species.DEINO],
MARLON: [Species.WAILMER, Species.FRILLISH, Species.TIRTOUGA],
CILAN: [Species.PANSAGE, Species.SNIVY, Species.MARACTUS, Species.FERROSEED],
CHILI: [Species.PANSEAR, Species.TEPIG, Species.HEATMOR, Species.DARUMAKA],
CRESS: [Species.PANPOUR, Species.OSHAWOTT, Species.BASCULIN, Species.TYMPOLE],
CHEREN: [Species.LILLIPUP, Species.MINCCINO, Species.PIDOVE, Species.BOUFFALANT],
LENORA: [Species.PATRAT, Species.DEERLING, Species.AUDINO, Species.BRAVIARY],
ROXIE: [Species.VENIPEDE, Species.KOFFING, Species.TRUBBISH, Species.TOXEL],
BURGH: [Species.SEWADDLE, Species.DWEBBLE, [Species.KARRABLAST, Species.SHELMET], Species.DURANT],
ELESA: [Species.BLITZLE, Species.EMOLGA, Species.JOLTIK, Species.TYNAMO],
CLAY: [Species.DRILBUR, Species.SANDILE, Species.TYMPOLE, Species.GOLETT],
SKYLA: [Species.DUCKLETT, Species.WOOBAT, [Species.RUFFLET, Species.VULLABY], Species.ARCHEN],
BRYCEN: [Species.CRYOGONAL, Species.VANILLITE, Species.CUBCHOO, Species.GALAR_DARUMAKA],
DRAYDEN: [Species.AXEW, Species.DRUDDIGON, Species.TRAPINCH, Species.DEINO],
MARLON: [Species.FRILLISH, Species.TIRTOUGA, Species.WAILMER, Species.MANTYKE],
// Gym Leaders- Kalos
VIOLA: [Species.SURSKIT, Species.SCATTERBUG],
GRANT: [Species.AMAURA, Species.TYRUNT],
KORRINA: [Species.HAWLUCHA, Species.LUCARIO, Species.MIENFOO],
RAMOS: [Species.SKIDDO, Species.HOPPIP, Species.BELLSPROUT],
CLEMONT: [Species.HELIOPTILE, Species.MAGNEMITE, Species.EMOLGA],
VALERIE: [Species.SYLVEON, Species.MAWILE, Species.MR_MIME],
OLYMPIA: [Species.ESPURR, Species.SIGILYPH, Species.SLOWKING],
WULFRIC: [Species.BERGMITE, Species.SNOVER, Species.CRYOGONAL],
VIOLA: [Species.SCATTERBUG, Species.SURSKIT, Species.CUTIEFLY, Species.BLIPBUG],
GRANT: [Species.TYRUNT, Species.AMAURA, Species.BINACLE, Species.DWEBBLE],
KORRINA: [Species.RIOLU, Species.MIENFOO, Species.HAWLUCHA, Species.PANCHAM],
RAMOS: [Species.SKIDDO, Species.HOPPIP, Species.BELLSPROUT, [Species.PHANTUMP, Species.PUMPKABOO]],
CLEMONT: [Species.HELIOPTILE, Species.MAGNEMITE, Species.DEDENNE, Species.ROTOM],
VALERIE: [Species.SYLVEON, Species.MAWILE, Species.MR_MIME, [Species.SPRITZEE, Species.SWIRLIX]],
OLYMPIA: [Species.ESPURR, Species.SIGILYPH, Species.INKAY, Species.SLOWKING],
WULFRIC: [Species.BERGMITE, Species.SNOVER, Species.CRYOGONAL, Species.SWINUB],
// Gym Leaders- Galar
MILO: [Species.GOSSIFLEUR, Species.APPLIN, Species.BOUNSWEET],
NESSA: [Species.CHEWTLE, Species.ARROKUDA, Species.WIMPOD],
KABU: [Species.SIZZLIPEDE, Species.VULPIX, Species.TORKOAL],
BEA: [Species.GALAR_FARFETCHD, Species.MACHOP, Species.CLOBBOPUS],
ALLISTER: [Species.GALAR_YAMASK, Species.GALAR_CORSOLA, Species.GASTLY],
OPAL: [Species.MILCERY, Species.TOGETIC, Species.GALAR_WEEZING],
BEDE: [Species.HATENNA, Species.GALAR_PONYTA, Species.GARDEVOIR],
GORDIE: [Species.ROLYCOLY, Species.STONJOURNER, Species.BINACLE],
MELONY: [Species.SNOM, Species.GALAR_DARUMAKA, Species.GALAR_MR_MIME],
PIERS: [Species.GALAR_ZIGZAGOON, Species.SCRAGGY, Species.INKAY],
MARNIE: [Species.IMPIDIMP, Species.PURRLOIN, Species.MORPEKO],
RAIHAN: [Species.DURALUDON, Species.TURTONATOR, Species.GOOMY],
MILO: [Species.GOSSIFLEUR, Species.SEEDOT, Species.APPLIN, Species.LOTAD],
NESSA: [Species.CHEWTLE, Species.WIMPOD, Species.ARROKUDA, Species.MAREANIE],
KABU: [Species.SIZZLIPEDE, Species.VULPIX, Species.GROWLITHE, Species.TORKOAL],
BEA: [Species.MACHOP, Species.GALAR_FARFETCHD, Species.CLOBBOPUS, Species.FALINKS],
ALLISTER: [Species.GASTLY, Species.GALAR_YAMASK, Species.GALAR_CORSOLA, Species.SINISTEA],
OPAL: [Species.MILCERY, Species.GALAR_WEEZING, Species.TOGEPI, Species.MAWILE],
BEDE: [Species.HATENNA, Species.GALAR_PONYTA, Species.GARDEVOIR, Species.SYLVEON],
GORDIE: [Species.ROLYCOLY, [Species.SHUCKLE, Species.BINACLE], Species.STONJOURNER, Species.LARVITAR],
MELONY: [Species.LAPRAS, Species.SNOM, Species.EISCUE, [Species.GALAR_MR_MIME, Species.GALAR_DARUMAKA]],
PIERS: [Species.GALAR_ZIGZAGOON, Species.SCRAGGY, Species.TOXEL, Species.INKAY], // Tera Dark Toxel
MARNIE: [Species.IMPIDIMP, Species.MORPEKO, Species.PURRLOIN, Species.CROAGUNK], // Tera Dark Croagunk
RAIHAN: [Species.DURALUDON, Species.TRAPINCH, Species.GOOMY, Species.TURTONATOR],
// Gym Leaders- Paldea; First slot is Tera
KATY: [Species.TEDDIURSA, Species.NYMBLE, Species.TAROUNTULA], // Tera Bug Teddiursa
BRASSIUS: [Species.SUDOWOODO, Species.BRAMBLIN, Species.SMOLIV], // Tera Grass Sudowoodo
IONO: [Species.MISDREAVUS, Species.TADBULB, Species.WATTREL], // Tera Ghost Misdreavus
KATY: [Species.TEDDIURSA, Species.NYMBLE, Species.TAROUNTULA, Species.RELLOR], // Tera Bug Teddiursa
BRASSIUS: [Species.BONSLY, Species.SMOLIV, Species.BRAMBLIN, Species.SUNKERN], // Tera Grass Bonsly
IONO: [Species.MISDREAVUS, Species.TADBULB, Species.WATTREL, Species.MAGNEMITE], // Tera Ghost Misdreavus
KOFU: [Species.CRABRAWLER, Species.VELUZA, Species.WIGLETT, Species.WINGULL], // Tera Water Crabrawler
LARRY: [Species.STARLY, Species.DUNSPARCE, Species.LECHONK, Species.KOMALA], // Tera Normal Starly
RYME: [Species.TOXEL, Species.GREAVARD, Species.SHUPPET, Species.MIMIKYU], // Tera Ghost Toxel
TULIP: [Species.FLABEBE, Species.FLITTLE, Species.RALTS, Species.GIRAFARIG], // Tera Psychic Flabebe
GRUSHA: [Species.SWABLU, Species.CETODDLE, Species.CUBCHOO, Species.ALOLA_VULPIX], // Tera Ice Swablu
GRUSHA: [Species.SWABLU, Species.CETODDLE, Species.SNOM, Species.CUBCHOO], // Tera Ice Swablu
// Elite Four- Kanto
LORELEI: [

View File

@ -2,11 +2,11 @@ import { globalScene } from "#app/global-scene";
import { AttackMove, BeakBlastHeaderAttr, DelayedAttackAttr, SelfStatusMove, allMoves } from "./moves/move";
import { MoveFlags } from "#enums/MoveFlags";
import type Pokemon from "../field/pokemon";
import { type nil, getFrameMs, getEnumKeys, getEnumValues, animationFileName } from "../utils";
import { type nil, getFrameMs, getEnumKeys, getEnumValues, animationFileName } from "../utils/common";
import type { BattlerIndex } from "../battle";
import { Moves } from "#enums/moves";
import { SubstituteTag } from "./battler-tags";
import { isNullOrUndefined } from "../utils";
import { isNullOrUndefined } from "../utils/common";
import Phaser from "phaser";
import { EncounterAnim } from "#enums/encounter-anims";

View File

@ -1,13 +1,13 @@
import { globalScene } from "#app/global-scene";
import {
allAbilities,
applyAbAttrs,
BlockNonDirectDamageAbAttr,
FlinchEffectAbAttr,
ProtectStatAbAttr,
ConditionalUserFieldProtectStatAbAttr,
ReverseDrainAbAttr,
} from "#app/data/ability";
} from "#app/data/abilities/ability";
import { allAbilities } from "./data-lists";
import { ChargeAnim, CommonAnim, CommonBattleAnim, MoveChargeAnim } from "#app/data/battle-anims";
import type Move from "#app/data/moves/move";
import {
@ -33,7 +33,7 @@ import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
import type { StatStageChangeCallback } from "#app/phases/stat-stage-change-phase";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import i18next from "#app/plugins/i18n";
import { BooleanHolder, getFrameMs, NumberHolder, toDmgValue } from "#app/utils";
import { BooleanHolder, getFrameMs, NumberHolder, toDmgValue } from "#app/utils/common";
import { Abilities } from "#enums/abilities";
import { BattlerTagType } from "#enums/battler-tag-type";
import { Moves } from "#enums/moves";
@ -42,7 +42,7 @@ import { Species } from "#enums/species";
import { EFFECTIVE_STATS, getStatKey, Stat, type BattleStat, type EffectiveStat } from "#enums/stat";
import { StatusEffect } from "#enums/status-effect";
import { WeatherType } from "#enums/weather-type";
import { isNullOrUndefined } from "#app/utils";
import { isNullOrUndefined } from "#app/utils/common";
export enum BattlerTagLapseType {
FAINT,
@ -52,6 +52,7 @@ export enum BattlerTagLapseType {
MOVE_EFFECT,
TURN_END,
HIT,
/** Tag lapses AFTER_HIT, applying its effects even if the user faints */
AFTER_HIT,
CUSTOM,
}
@ -498,7 +499,13 @@ export class BeakBlastChargingTag extends BattlerTag {
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
if (lapseType === BattlerTagLapseType.AFTER_HIT) {
const phaseData = getMoveEffectPhaseData(pokemon);
if (phaseData?.move.hasFlag(MoveFlags.MAKES_CONTACT)) {
if (
phaseData?.move.doesFlagEffectApply({
flag: MoveFlags.MAKES_CONTACT,
user: phaseData.attacker,
target: pokemon,
})
) {
phaseData.attacker.trySetStatus(StatusEffect.BURN, true, pokemon);
}
return true;
@ -1611,19 +1618,50 @@ export class ProtectedTag extends BattlerTag {
}
}
/** Base class for `BattlerTag`s that block damaging moves but not status moves */
export class DamageProtectedTag extends ProtectedTag {}
/** Class for `BattlerTag`s that apply some effect when hit by a contact move */
export class ContactProtectedTag extends ProtectedTag {
/**
* Function to call when a contact move hits the pokemon with this tag.
* @param _attacker - The pokemon using the contact move
* @param _user - The pokemon that is being attacked and has the tag
* @param _move - The move used by the attacker
*/
onContact(_attacker: Pokemon, _user: Pokemon) {}
/**
* Lapse the tag and apply `onContact` if the move makes contact and
* `lapseType` is custom, respecting the move's flags and the pokemon's
* abilities, and whether the lapseType is custom.
*
* @param pokemon - The pokemon with the tag
* @param lapseType - The type of lapse to apply. If this is not {@linkcode BattlerTagLapseType.CUSTOM CUSTOM}, no effect will be applied.
* @returns Whether the tag continues to exist after the lapse.
*/
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
const ret = super.lapse(pokemon, lapseType);
const moveData = getMoveEffectPhaseData(pokemon);
if (
lapseType === BattlerTagLapseType.CUSTOM &&
moveData &&
moveData.move.doesFlagEffectApply({ flag: MoveFlags.MAKES_CONTACT, user: moveData.attacker, target: pokemon })
) {
this.onContact(moveData.attacker, pokemon);
}
return ret;
}
}
/**
* `BattlerTag` class for moves that block damaging moves damage the enemy if the enemy's move makes contact
* Used by {@linkcode Moves.SPIKY_SHIELD}
*/
export class ContactDamageProtectedTag extends ProtectedTag {
export class ContactDamageProtectedTag extends ContactProtectedTag {
private damageRatio: number;
constructor(sourceMove: Moves, damageRatio: number) {
super(sourceMove, BattlerTagType.SPIKY_SHIELD);
this.damageRatio = damageRatio;
}
@ -1636,22 +1674,46 @@ export class ContactDamageProtectedTag extends ProtectedTag {
this.damageRatio = source.damageRatio;
}
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
const ret = super.lapse(pokemon, lapseType);
if (lapseType === BattlerTagLapseType.CUSTOM) {
const effectPhase = globalScene.getCurrentPhase();
if (effectPhase instanceof MoveEffectPhase && effectPhase.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) {
const attacker = effectPhase.getPokemon();
if (!attacker.hasAbilityWithAttr(BlockNonDirectDamageAbAttr)) {
attacker.damageAndUpdate(toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), {
result: HitResult.INDIRECT,
});
}
}
/**
* Damage the attacker by `this.damageRatio` of the target's max HP
* @param attacker - The pokemon using the contact move
* @param user - The pokemon that is being attacked and has the tag
*/
override onContact(attacker: Pokemon, user: Pokemon): void {
const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockNonDirectDamageAbAttr, user, cancelled);
if (!cancelled.value) {
attacker.damageAndUpdate(toDmgValue(attacker.getMaxHp() * (1 / this.damageRatio)), {
result: HitResult.INDIRECT,
});
}
}
}
return ret;
/** Base class for `BattlerTag`s that block damaging moves but not status moves */
export class DamageProtectedTag extends ContactProtectedTag {}
export class ContactSetStatusProtectedTag extends DamageProtectedTag {
/**
* @param sourceMove The move that caused the tag to be applied
* @param tagType The type of the tag
* @param statusEffect The status effect to apply to the attacker
*/
constructor(
sourceMove: Moves,
tagType: BattlerTagType,
private statusEffect: StatusEffect,
) {
super(sourceMove, tagType);
}
/**
* Set the status effect on the attacker
* @param attacker - The pokemon using the contact move
* @param user - The pokemon that is being attacked and has the tag
*/
override onContact(attacker: Pokemon, user: Pokemon): void {
attacker.trySetStatus(this.statusEffect, true, user);
}
}
@ -1674,68 +1736,19 @@ export class ContactStatStageChangeProtectedTag extends DamageProtectedTag {
* When given a battler tag or json representing one, load the data for it.
* @param {BattlerTag | any} source A battler tag
*/
loadTag(source: BattlerTag | any): void {
override loadTag(source: BattlerTag | any): void {
super.loadTag(source);
this.stat = source.stat;
this.levels = source.levels;
}
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
const ret = super.lapse(pokemon, lapseType);
if (lapseType === BattlerTagLapseType.CUSTOM) {
const effectPhase = globalScene.getCurrentPhase();
if (effectPhase instanceof MoveEffectPhase && effectPhase.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) {
const attacker = effectPhase.getPokemon();
globalScene.unshiftPhase(new StatStageChangePhase(attacker.getBattlerIndex(), false, [this.stat], this.levels));
}
}
return ret;
}
}
export class ContactPoisonProtectedTag extends ProtectedTag {
constructor(sourceMove: Moves) {
super(sourceMove, BattlerTagType.BANEFUL_BUNKER);
}
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
const ret = super.lapse(pokemon, lapseType);
if (lapseType === BattlerTagLapseType.CUSTOM) {
const effectPhase = globalScene.getCurrentPhase();
if (effectPhase instanceof MoveEffectPhase && effectPhase.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) {
const attacker = effectPhase.getPokemon();
attacker.trySetStatus(StatusEffect.POISON, true, pokemon);
}
}
return ret;
}
}
/**
* `BattlerTag` class for moves that block damaging moves and burn the enemy if the enemy's move makes contact
* Used by {@linkcode Moves.BURNING_BULWARK}
*/
export class ContactBurnProtectedTag extends DamageProtectedTag {
constructor(sourceMove: Moves) {
super(sourceMove, BattlerTagType.BURNING_BULWARK);
}
lapse(pokemon: Pokemon, lapseType: BattlerTagLapseType): boolean {
const ret = super.lapse(pokemon, lapseType);
if (lapseType === BattlerTagLapseType.CUSTOM) {
const effectPhase = globalScene.getCurrentPhase();
if (effectPhase instanceof MoveEffectPhase && effectPhase.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT)) {
const attacker = effectPhase.getPokemon();
attacker.trySetStatus(StatusEffect.BURN, true);
}
}
return ret;
/**
* Initiate the stat stage change on the attacker
* @param attacker - The pokemon using the contact move
* @param user - The pokemon that is being attacked and has the tag
*/
override onContact(attacker: Pokemon, _user: Pokemon): void {
globalScene.unshiftPhase(new StatStageChangePhase(attacker.getBattlerIndex(), false, [this.stat], this.levels));
}
}
@ -2624,7 +2637,7 @@ export class GulpMissileTag extends BattlerTag {
return false;
}
if (moveEffectPhase.move.getMove().hitsSubstitute(attacker, pokemon)) {
if (moveEffectPhase.move.hitsSubstitute(attacker, pokemon)) {
return true;
}
@ -2980,7 +2993,7 @@ export class SubstituteTag extends BattlerTag {
if (!attacker) {
return;
}
const move = moveEffectPhase.move.getMove();
const move = moveEffectPhase.move;
const firstHit = attacker.turnData.hitCount === attacker.turnData.hitsLeft;
if (firstHit && move.hitsSubstitute(attacker, pokemon)) {
@ -3518,9 +3531,9 @@ export function getBattlerTag(
case BattlerTagType.SILK_TRAP:
return new ContactStatStageChangeProtectedTag(sourceMove, tagType, Stat.SPD, -1);
case BattlerTagType.BANEFUL_BUNKER:
return new ContactPoisonProtectedTag(sourceMove);
return new ContactSetStatusProtectedTag(sourceMove, tagType, StatusEffect.POISON);
case BattlerTagType.BURNING_BULWARK:
return new ContactBurnProtectedTag(sourceMove);
return new ContactSetStatusProtectedTag(sourceMove, tagType, StatusEffect.BURN);
case BattlerTagType.ENDURING:
return new EnduringTag(tagType, BattlerTagLapseType.TURN_END, sourceMove);
case BattlerTagType.ENDURE_TOKEN:
@ -3668,7 +3681,7 @@ function getMoveEffectPhaseData(_pokemon: Pokemon): { phase: MoveEffectPhase; at
return {
phase: phase,
attacker: phase.getPokemon(),
move: phase.move.getMove(),
move: phase.move,
};
}
return null;

View File

@ -2,14 +2,14 @@ import { getPokemonNameWithAffix } from "../messages";
import type Pokemon from "../field/pokemon";
import { HitResult } from "../field/pokemon";
import { getStatusEffectHealText } from "./status-effect";
import { NumberHolder, toDmgValue, randSeedInt } from "#app/utils";
import { NumberHolder, toDmgValue, randSeedInt } from "#app/utils/common";
import {
DoubleBerryEffectAbAttr,
PostItemLostAbAttr,
ReduceBerryUseThresholdAbAttr,
applyAbAttrs,
applyPostItemLostAbAttrs,
} from "./ability";
} from "./abilities/ability";
import i18next from "i18next";
import { BattlerTagType } from "#enums/battler-tag-type";
import { BerryType } from "#enums/berry-type";

View File

@ -1,4 +1,4 @@
import { BooleanHolder, type NumberHolder, randSeedItem, deepCopy } from "#app/utils";
import { BooleanHolder, type NumberHolder, randSeedItem, deepCopy } from "#app/utils/common";
import i18next from "i18next";
import type { DexAttrProps, GameData } from "#app/system/game-data";
import { defaultStarterSpecies } from "#app/system/game-data";
@ -8,7 +8,9 @@ import { speciesStarterCosts } from "#app/data/balance/starters";
import type Pokemon from "#app/field/pokemon";
import { PokemonMove } from "#app/field/pokemon";
import type { FixedBattleConfig } from "#app/battle";
import { ClassicFixedBossWaves, BattleType, getRandomTrainerFunc } from "#app/battle";
import { getRandomTrainerFunc } from "#app/battle";
import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves";
import { BattleType } from "#enums/battle-type";
import Trainer, { TrainerVariant } from "#app/field/trainer";
import { PokemonType } from "#enums/pokemon-type";
import { Challenges } from "#enums/challenges";

View File

@ -1,6 +1,6 @@
import type { Abilities } from "#enums/abilities";
import type { PokemonType } from "#enums/pokemon-type";
import { isNullOrUndefined } from "#app/utils";
import { isNullOrUndefined } from "#app/utils/common";
import type { Nature } from "#enums/nature";
/**

View File

@ -3,7 +3,7 @@ import type { Species } from "#enums/species";
import { globalScene } from "#app/global-scene";
import { PlayerPokemon } from "#app/field/pokemon";
import type { Starter } from "#app/ui/starter-select-ui-handler";
import { randSeedGauss, randSeedInt, randSeedItem, getEnumValues } from "#app/utils";
import { randSeedGauss, randSeedInt, randSeedItem, getEnumValues } from "#app/utils/common";
import type { PokemonSpeciesForm } from "#app/data/pokemon-species";
import PokemonSpecies, { getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species";
import { speciesStarterCosts } from "#app/data/balance/starters";

3
src/data/data-lists.ts Normal file
View File

@ -0,0 +1,3 @@
import type { Ability } from "./abilities/ability-class";
export const allAbilities: Ability[] = [];

View File

@ -4,7 +4,7 @@ import type PokemonSpecies from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { speciesStarterCosts } from "#app/data/balance/starters";
import { VariantTier } from "#enums/variant-tier";
import { randInt, randomString, randSeedInt, getIvsFromId } from "#app/utils";
import { randInt, randomString, randSeedInt, getIvsFromId } from "#app/utils/common";
import Overrides from "#app/overrides";
import { pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions";
import type { PlayerPokemon } from "#app/field/pokemon";

View File

@ -0,0 +1,20 @@
import { MoveTarget } from "#enums/MoveTarget";
import type Move from "./move";
/**
* Return whether the move targets the field
*
* Examples include
* - Hazard moves like spikes
* - Weather moves like rain dance
* - User side moves like reflect and safeguard
*/
export function isFieldTargeted(move: Move): boolean {
switch (move.moveTarget) {
case MoveTarget.BOTH_SIDES:
case MoveTarget.USER_SIDE:
case MoveTarget.ENEMY_SIDE:
return true;
}
return false;
}

View File

@ -29,12 +29,11 @@ import {
} from "../status-effect";
import { getTypeDamageMultiplier } from "../type";
import { PokemonType } from "#enums/pokemon-type";
import { BooleanHolder, NumberHolder, isNullOrUndefined, toDmgValue, randSeedItem, randSeedInt, getEnumValues, toReadableString, type Constructor } from "#app/utils";
import { BooleanHolder, NumberHolder, isNullOrUndefined, toDmgValue, randSeedItem, randSeedInt, getEnumValues, toReadableString, type Constructor } from "#app/utils/common";
import { WeatherType } from "#enums/weather-type";
import type { ArenaTrapTag } from "../arena-tag";
import { ArenaTagSide, WeakenMoveTypeTag } from "../arena-tag";
import {
allAbilities,
AllyMoveCategoryPowerBoostAbAttr,
applyAbAttrs,
applyPostAttackAbAttrs,
@ -61,11 +60,13 @@ import {
MoveTypeChangeAbAttr,
PostDamageForceSwitchAbAttr,
PostItemLostAbAttr,
ReflectStatusMoveAbAttr,
ReverseDrainAbAttr,
UserFieldMoveTypePowerBoostAbAttr,
VariableMovePowerAbAttr,
WonderSkinAbAttr,
} from "../ability";
} from "../abilities/ability";
import { allAbilities } from "../data-lists";
import {
AttackTypeBoosterModifier,
BerryModifier,
@ -75,7 +76,7 @@ import {
PreserveBerryModifier,
} from "../../modifier/modifier";
import type { BattlerIndex } from "../../battle";
import { BattleType } from "../../battle";
import { BattleType } from "#enums/battle-type";
import { TerrainType } from "../terrain";
import { ModifierPoolType } from "#app/modifier/modifier-type";
import { Command } from "../../ui/command-ui-handler";
@ -121,6 +122,8 @@ import { MoveFlags } from "#enums/MoveFlags";
import { MoveEffectTrigger } from "#enums/MoveEffectTrigger";
import { MultiHitType } from "#enums/MultiHitType";
import { invalidAssistMoves, invalidCopycatMoves, invalidMetronomeMoves, invalidMirrorMoveMoves, invalidSleepTalkMoves } from "./invalid-moves";
import { TrainerVariant } from "#app/field/trainer";
import { SelectBiomePhase } from "#app/phases/select-biome-phase";
type MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => boolean;
type UserMoveConditionFunc = (user: Pokemon, move: Move) => boolean;
@ -664,6 +667,17 @@ export default class Move implements Localizable {
return true;
}
break;
case MoveFlags.REFLECTABLE:
// If the target is not semi-invulnerable and either has magic coat active or an unignored magic bounce ability
if (
target?.getTag(SemiInvulnerableTag) ||
!(target?.getTag(BattlerTagType.MAGIC_COAT) ||
(!this.doesFlagEffectApply({ flag: MoveFlags.IGNORE_ABILITIES, user, target }) &&
target?.hasAbilityWithAttr(ReflectStatusMoveAbAttr)))
) {
return false;
}
break;
}
return !!(this.flags & flag);
@ -1715,7 +1729,7 @@ export class SacrificialAttr extends MoveEffectAttr {
**/
export class SacrificialAttrOnHit extends MoveEffectAttr {
constructor() {
super(true, { trigger: MoveEffectTrigger.HIT });
super(true);
}
/**
@ -1954,6 +1968,14 @@ export class PartyStatusCureAttr extends MoveEffectAttr {
* @extends MoveEffectAttr
*/
export class FlameBurstAttr extends MoveEffectAttr {
constructor() {
/**
* This is self-targeted to bypass immunity to target-facing secondary
* effects when the target has an active Substitute doll.
* TODO: Find a more intuitive way to implement Substitute bypassing.
*/
super(true);
}
/**
* @param user - n/a
* @param target - The target Pokémon.
@ -2176,7 +2198,7 @@ export class HitHealAttr extends MoveEffectAttr {
private healStat: EffectiveStat | null;
constructor(healRatio?: number | null, healStat?: EffectiveStat) {
super(true, { trigger: MoveEffectTrigger.HIT });
super(true);
this.healRatio = healRatio ?? 0.5;
this.healStat = healStat ?? null;
@ -2425,7 +2447,7 @@ export class StatusEffectAttr extends MoveEffectAttr {
public overrideStatus: boolean = false;
constructor(effect: StatusEffect, selfTarget?: boolean, turnsRemaining?: number, overrideStatus: boolean = false) {
super(selfTarget, { trigger: MoveEffectTrigger.HIT });
super(selfTarget);
this.effect = effect;
this.turnsRemaining = turnsRemaining;
@ -2433,22 +2455,11 @@ export class StatusEffectAttr extends MoveEffectAttr {
}
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if (!this.selfTarget && move.hitsSubstitute(user, target)) {
return false;
}
const moveChance = this.getMoveChance(user, target, move, this.selfTarget, true);
const statusCheck = moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance;
if (statusCheck) {
const pokemon = this.selfTarget ? user : target;
if (pokemon.status && !this.overrideStatus) {
return false;
}
if (user !== target && target.isSafeguarded(user)) {
if (move.category === MoveCategory.STATUS) {
globalScene.queueMessage(i18next.t("moveTriggers:safeguard", { targetName: getPokemonNameWithAffix(target) }));
}
if (user !== target && move.category === MoveCategory.STATUS && !target.canSetStatus(this.effect, false, false, user, true)) {
return false;
}
if (((!pokemon.status || this.overrideStatus) || (pokemon.status.effect === this.effect && moveChance < 0))
@ -2494,7 +2505,7 @@ export class MultiStatusEffectAttr extends StatusEffectAttr {
export class PsychoShiftEffectAttr extends MoveEffectAttr {
constructor() {
super(false, { trigger: MoveEffectTrigger.HIT });
super(false);
}
/**
@ -2533,15 +2544,11 @@ export class StealHeldItemChanceAttr extends MoveEffectAttr {
private chance: number;
constructor(chance: number) {
super(false, { trigger: MoveEffectTrigger.HIT });
super(false);
this.chance = chance;
}
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if (move.hitsSubstitute(user, target)) {
return false;
}
const rand = Phaser.Math.RND.realInRange(0, 1);
if (rand >= this.chance) {
return false;
@ -2589,7 +2596,7 @@ export class RemoveHeldItemAttr extends MoveEffectAttr {
private berriesOnly: boolean;
constructor(berriesOnly: boolean) {
super(false, { trigger: MoveEffectTrigger.HIT });
super(false);
this.berriesOnly = berriesOnly;
}
@ -2599,17 +2606,13 @@ export class RemoveHeldItemAttr extends MoveEffectAttr {
* @param target Target {@linkcode Pokemon} that the moves applies to
* @param move {@linkcode Move} that is used
* @param args N/A
* @returns {boolean} True if an item was removed
* @returns True if an item was removed
*/
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if (!this.berriesOnly && target.isPlayer()) { // "Wild Pokemon cannot knock off Player Pokemon's held items" (See Bulbapedia)
return false;
}
if (move.hitsSubstitute(user, target)) {
return false;
}
const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockItemTheftAbAttr, target, cancelled); // Check for abilities that block item theft
@ -2663,8 +2666,8 @@ export class RemoveHeldItemAttr extends MoveEffectAttr {
*/
export class EatBerryAttr extends MoveEffectAttr {
protected chosenBerry: BerryModifier | undefined;
constructor() {
super(true, { trigger: MoveEffectTrigger.HIT });
constructor(selfTarget: boolean) {
super(selfTarget);
}
/**
* Causes the target to eat a berry.
@ -2679,17 +2682,20 @@ export class EatBerryAttr extends MoveEffectAttr {
return false;
}
const heldBerries = this.getTargetHeldBerries(target);
const pokemon = this.selfTarget ? user : target;
const heldBerries = this.getTargetHeldBerries(pokemon);
if (heldBerries.length <= 0) {
return false;
}
this.chosenBerry = heldBerries[user.randSeedInt(heldBerries.length)];
const preserve = new BooleanHolder(false);
globalScene.applyModifiers(PreserveBerryModifier, target.isPlayer(), target, preserve); // check for berry pouch preservation
// check for berry pouch preservation
globalScene.applyModifiers(PreserveBerryModifier, pokemon.isPlayer(), pokemon, preserve);
if (!preserve.value) {
this.reduceBerryModifier(target);
this.reduceBerryModifier(pokemon);
}
this.eatBerry(target);
this.eatBerry(pokemon);
return true;
}
@ -2717,20 +2723,17 @@ export class EatBerryAttr extends MoveEffectAttr {
*/
export class StealEatBerryAttr extends EatBerryAttr {
constructor() {
super();
super(false);
}
/**
* User steals a random berry from the target and then eats it.
* @param {Pokemon} user Pokemon that used the move and will eat the stolen berry
* @param {Pokemon} target Pokemon that will have its berry stolen
* @param {Move} move Move being used
* @param {any[]} args Unused
* @returns {boolean} true if the function succeeds
* @param user - Pokemon that used the move and will eat the stolen berry
* @param target - Pokemon that will have its berry stolen
* @param move - Move being used
* @param args Unused
* @returns true if the function succeeds
*/
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if (move.hitsSubstitute(user, target)) {
return false;
}
const cancelled = new BooleanHolder(false);
applyAbAttrs(BlockItemTheftAbAttr, target, cancelled); // check for abilities that block item theft
if (cancelled.value === true) {
@ -2781,10 +2784,6 @@ export class HealStatusEffectAttr extends MoveEffectAttr {
return false;
}
if (!this.selfTarget && move.hitsSubstitute(user, target)) {
return false;
}
// Special edge case for shield dust blocking Sparkling Aria curing burn
const moveTargets = getMoveTargets(user, move.id);
if (target.hasAbilityWithAttr(IgnoreMoveEffectsAbAttr) && move.id === Moves.SPARKLING_ARIA && moveTargets.targets.length === 1) {
@ -3161,15 +3160,7 @@ export class StatStageChangeAttr extends MoveEffectAttr {
private get showMessage () {
return this.options?.showMessage ?? true;
}
/**
* Indicates when the stat change should trigger
* @default MoveEffectTrigger.HIT
*/
public override get trigger () {
return this.options?.trigger ?? MoveEffectTrigger.HIT;
}
/**
* Attempts to change stats of the user or target (depending on value of selfTarget) if conditions are met
* @param user {@linkcode Pokemon} the user of the move
@ -3183,10 +3174,6 @@ export class StatStageChangeAttr extends MoveEffectAttr {
return false;
}
if (!this.selfTarget && move.hitsSubstitute(user, target)) {
return false;
}
const moveChance = this.getMoveChance(user, target, move, this.selfTarget, true);
if (moveChance < 0 || moveChance === 100 || user.randSeedInt(100) < moveChance) {
const stages = this.getLevels(user);
@ -3466,12 +3453,11 @@ export class CutHpStatStageBoostAttr extends StatStageChangeAttr {
/**
* Attribute implementing the stat boosting effect of {@link https://bulbapedia.bulbagarden.net/wiki/Order_Up_(move) | Order Up}.
* If the user has a Pokemon with {@link https://bulbapedia.bulbagarden.net/wiki/Commander_(Ability) | Commander} in their mouth,
* one of the user's stats are increased by 1 stage, depending on the "commanding" Pokemon's form. This effect does not respect
* effect chance, but Order Up itself may be boosted by Sheer Force.
* one of the user's stats are increased by 1 stage, depending on the "commanding" Pokemon's form.
*/
export class OrderUpStatBoostAttr extends MoveEffectAttr {
constructor() {
super(true, { trigger: MoveEffectTrigger.HIT });
super(true);
}
override apply(user: Pokemon, target: Pokemon, move: Move, args?: any[]): boolean {
@ -3548,17 +3534,15 @@ export class ResetStatsAttr extends MoveEffectAttr {
this.targetAllPokemon = targetAllPokemon;
}
override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
override apply(_user: Pokemon, target: Pokemon, _move: Move, _args: any[]): boolean {
if (this.targetAllPokemon) {
// Target all pokemon on the field when Freezy Frost or Haze are used
const activePokemon = globalScene.getField(true);
activePokemon.forEach((p) => this.resetStats(p));
globalScene.queueMessage(i18next.t("moveTriggers:statEliminated"));
} else { // Affects only the single target when Clear Smog is used
if (!move.hitsSubstitute(user, target)) {
this.resetStats(target);
globalScene.queueMessage(i18next.t("moveTriggers:resetStats", { pokemonName: getPokemonNameWithAffix(target) }));
}
this.resetStats(target);
globalScene.queueMessage(i18next.t("moveTriggers:resetStats", { pokemonName: getPokemonNameWithAffix(target) }));
}
return true;
}
@ -4217,7 +4201,8 @@ export class PresentPowerAttr extends VariablePowerAttr {
(args[0] as NumberHolder).value = 120;
} else if (80 < powerSeed && powerSeed <= 100) {
// If this move is multi-hit, disable all other hits
user.stopMultiHit();
user.turnData.hitCount = 1;
user.turnData.hitsLeft = 1;
globalScene.unshiftPhase(new PokemonHealPhase(target.getBattlerIndex(),
toDmgValue(target.getMaxHp() / 4), i18next.t("moveTriggers:regainedHealth", { pokemonName: getPokemonNameWithAffix(target) }), true));
}
@ -4811,8 +4796,8 @@ export class ShellSideArmCategoryAttr extends VariableMoveCategoryAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const category = (args[0] as NumberHolder);
const predictedPhysDmg = target.getBaseDamage(user, move, MoveCategory.PHYSICAL, true, true, true, true);
const predictedSpecDmg = target.getBaseDamage(user, move, MoveCategory.SPECIAL, true, true, true, true);
const predictedPhysDmg = target.getBaseDamage({source: user, move, moveCategory: MoveCategory.PHYSICAL, ignoreAbility: true, ignoreSourceAbility: true, ignoreAllyAbility: true, ignoreSourceAllyAbility: true, simulated: true});
const predictedSpecDmg = target.getBaseDamage({source: user, move, moveCategory: MoveCategory.SPECIAL, ignoreAbility: true, ignoreSourceAbility: true, ignoreAllyAbility: true, ignoreSourceAllyAbility: true, simulated: true});
if (predictedPhysDmg > predictedSpecDmg) {
category.value = MoveCategory.PHYSICAL;
@ -5371,7 +5356,7 @@ export class BypassRedirectAttr extends MoveAttr {
export class FrenzyAttr extends MoveEffectAttr {
constructor() {
super(true, { trigger: MoveEffectTrigger.HIT, lastHitOnly: true });
super(true, { lastHitOnly: true });
}
canApply(user: Pokemon, target: Pokemon, move: Move, args: any[]) {
@ -5443,22 +5428,20 @@ export class AddBattlerTagAttr extends MoveEffectAttr {
protected cancelOnFail: boolean;
private failOnOverlap: boolean;
constructor(tagType: BattlerTagType, selfTarget: boolean = false, failOnOverlap: boolean = false, turnCountMin: number = 0, turnCountMax?: number, lastHitOnly: boolean = false, cancelOnFail: boolean = false) {
constructor(tagType: BattlerTagType, selfTarget: boolean = false, failOnOverlap: boolean = false, turnCountMin: number = 0, turnCountMax?: number, lastHitOnly: boolean = false) {
super(selfTarget, { lastHitOnly: lastHitOnly });
this.tagType = tagType;
this.turnCountMin = turnCountMin;
this.turnCountMax = turnCountMax !== undefined ? turnCountMax : turnCountMin;
this.failOnOverlap = !!failOnOverlap;
this.cancelOnFail = cancelOnFail;
}
canApply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if (!super.canApply(user, target, move, args) || (this.cancelOnFail === true && user.getLastXMoves(1)[0]?.result === MoveResult.FAIL)) {
if (!super.canApply(user, target, move, args)) {
return false;
} else {
return true;
}
return true;
}
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
@ -5549,19 +5532,6 @@ export class LeechSeedAttr extends AddBattlerTagAttr {
constructor() {
super(BattlerTagType.SEEDED);
}
/**
* Adds a Seeding effect to the target if the target does not have an active Substitute.
* @param user the {@linkcode Pokemon} using the move
* @param target the {@linkcode Pokemon} targeted by the move
* @param move the {@linkcode Move} invoking this effect
* @param args n/a
* @returns `true` if the effect successfully applies; `false` otherwise
*/
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
return !move.hitsSubstitute(user, target)
&& super.apply(user, target, move, args);
}
}
/**
@ -5737,13 +5707,6 @@ export class FlinchAttr extends AddBattlerTagAttr {
constructor() {
super(BattlerTagType.FLINCHED, false);
}
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
if (!move.hitsSubstitute(user, target)) {
return super.apply(user, target, move, args);
}
return false;
}
}
export class ConfuseAttr extends AddBattlerTagAttr {
@ -5759,16 +5722,13 @@ export class ConfuseAttr extends AddBattlerTagAttr {
return false;
}
if (!move.hitsSubstitute(user, target)) {
return super.apply(user, target, move, args);
}
return false;
return super.apply(user, target, move, args);
}
}
export class RechargeAttr extends AddBattlerTagAttr {
constructor() {
super(BattlerTagType.RECHARGING, true, false, 1, 1, true, true);
super(BattlerTagType.RECHARGING, true, false, 1, 1, true);
}
}
@ -6151,7 +6111,7 @@ export class AddPledgeEffectAttr extends AddArenaTagAttr {
* @see {@linkcode apply}
*/
export class RevivalBlessingAttr extends MoveEffectAttr {
constructor(user?: boolean) {
constructor() {
super(true);
}
@ -6296,9 +6256,10 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
return false;
} else if (globalScene.currentBattle.battleType !== BattleType.WILD) { // Switch out logic for enemy trainers
// Find indices of off-field Pokemon that are eligible to be switched into
const isPartnerTrainer = globalScene.currentBattle.trainer?.isPartner();
const eligibleNewIndices: number[] = [];
globalScene.getEnemyParty().forEach((pokemon, index) => {
if (pokemon.isAllowedInBattle() && !pokemon.isOnField()) {
if (pokemon.isAllowedInBattle() && !pokemon.isOnField() && (!isPartnerTrainer || pokemon.trainerSlot === (switchOutTarget as EnemyPokemon).trainerSlot)) {
eligibleNewIndices.push(index);
}
});
@ -6348,15 +6309,6 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
}
}
if (globalScene.currentBattle.waveIndex % 10 === 0) {
return false;
}
// Don't allow wild mons to flee with U-turn et al.
if (this.selfSwitch && !user.isPlayer() && move.category !== MoveCategory.STATUS) {
return false;
}
const allyPokemon = switchOutTarget.getAlly();
if (switchOutTarget.hp > 0) {
@ -6369,13 +6321,17 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
}
}
if (!allyPokemon?.isActive(true)) {
globalScene.clearEnemyHeldItemModifiers();
// clear out enemy held item modifiers of the switch out target
globalScene.clearEnemyHeldItemModifiers(switchOutTarget);
if (switchOutTarget.hp) {
if (!allyPokemon?.isActive(true) && switchOutTarget.hp) {
globalScene.pushPhase(new BattleEndPhase(false));
if (globalScene.gameMode.hasRandomBiomes || globalScene.isNewBiome()) {
globalScene.pushPhase(new SelectBiomePhase());
}
globalScene.pushPhase(new NewBattlePhase());
}
}
}
@ -6394,6 +6350,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
}
}
getSwitchOutCondition(): MoveConditionFunc {
return (user, target, move) => {
const switchOutTarget = (this.selfSwitch ? user : target);
@ -6428,23 +6385,23 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
const blockedByAbility = new BooleanHolder(false);
applyAbAttrs(ForceSwitchOutImmunityAbAttr, target, blockedByAbility);
return !blockedByAbility.value;
if (blockedByAbility.value) {
return false;
}
}
if (!player && globalScene.currentBattle.battleType === BattleType.WILD) {
if (this.isBatonPass()) {
return false;
}
// Don't allow wild opponents to flee on the boss stage since it can ruin a run early on
if (globalScene.currentBattle.waveIndex % 10 === 0) {
return false;
}
// wild pokemon cannot switch out with baton pass.
return !this.isBatonPass()
&& globalScene.currentBattle.waveIndex % 10 !== 0
// Don't allow wild mons to flee with U-turn et al.
&& !(this.selfSwitch && MoveCategory.STATUS !== move.category);
}
const party = player ? globalScene.getPlayerParty() : globalScene.getEnemyParty();
return (!player && !globalScene.currentBattle.battleType)
|| party.filter(p => p.isAllowedInBattle()
&& (player || (p as EnemyPokemon).trainerSlot === (switchOutTarget as EnemyPokemon).trainerSlot)).length > globalScene.currentBattle.getBattlerCount();
return party.filter(p => p.isAllowedInBattle() && !p.isOnField()
&& (player || (p as EnemyPokemon).trainerSlot === (switchOutTarget as EnemyPokemon).trainerSlot)).length > 0;
};
}
@ -6669,7 +6626,7 @@ export class ChangeTypeAttr extends MoveEffectAttr {
private type: PokemonType;
constructor(type: PokemonType) {
super(false, { trigger: MoveEffectTrigger.HIT });
super(false);
this.type = type;
}
@ -6692,7 +6649,7 @@ export class AddTypeAttr extends MoveEffectAttr {
private type: PokemonType;
constructor(type: PokemonType) {
super(false, { trigger: MoveEffectTrigger.HIT });
super(false);
this.type = type;
}
@ -7388,7 +7345,7 @@ export class AbilityChangeAttr extends MoveEffectAttr {
public ability: Abilities;
constructor(ability: Abilities, selfTarget?: boolean) {
super(selfTarget, { trigger: MoveEffectTrigger.HIT });
super(selfTarget);
this.ability = ability;
}
@ -7419,7 +7376,7 @@ export class AbilityCopyAttr extends MoveEffectAttr {
public copyToPartner: boolean;
constructor(copyToPartner: boolean = false) {
super(false, { trigger: MoveEffectTrigger.HIT });
super(false);
this.copyToPartner = copyToPartner;
}
@ -7460,7 +7417,7 @@ export class AbilityGiveAttr extends MoveEffectAttr {
public copyToPartner: boolean;
constructor() {
super(false, { trigger: MoveEffectTrigger.HIT });
super(false);
}
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
@ -7739,7 +7696,7 @@ export class DiscourageFrequentUseAttr extends MoveAttr {
export class MoneyAttr extends MoveEffectAttr {
constructor() {
super(true, { trigger: MoveEffectTrigger.HIT, firstHitOnly: true });
super(true, {firstHitOnly: true });
}
apply(user: Pokemon, target: Pokemon, move: Move): boolean {
@ -7806,7 +7763,7 @@ export class StatusIfBoostedAttr extends MoveEffectAttr {
public effect: StatusEffect;
constructor(effect: StatusEffect) {
super(true, { trigger: MoveEffectTrigger.HIT });
super(true);
this.effect = effect;
}
@ -8202,7 +8159,7 @@ export type MoveTargetSet = {
export function getMoveTargets(user: Pokemon, move: Moves, replaceTarget?: MoveTarget): MoveTargetSet {
const variableTarget = new NumberHolder(0);
user.getOpponents().forEach(p => applyMoveAttrs(VariableTargetAttr, user, p, allMoves[move], variableTarget));
user.getOpponents(false).forEach(p => applyMoveAttrs(VariableTargetAttr, user, p, allMoves[move], variableTarget));
let moveTarget: MoveTarget | undefined;
if (allMoves[move].hasAttr(VariableTargetAttr)) {
@ -8214,7 +8171,7 @@ export function getMoveTargets(user: Pokemon, move: Moves, replaceTarget?: MoveT
} else if (move === undefined) {
moveTarget = MoveTarget.NEAR_ENEMY;
}
const opponents = user.getOpponents();
const opponents = user.getOpponents(false);
let set: Pokemon[] = [];
let multiple = false;
@ -9737,7 +9694,7 @@ export function initMoves() {
.ignoresProtect()
.target(MoveTarget.BOTH_SIDES)
.unimplemented(),
new AttackMove(Moves.SMACK_DOWN, PokemonType.ROCK, MoveCategory.PHYSICAL, 50, 100, 15, 100, 0, 5)
new AttackMove(Moves.SMACK_DOWN, PokemonType.ROCK, MoveCategory.PHYSICAL, 50, 100, 15, -1, 0, 5)
.attr(FallDownAttr)
.attr(AddBattlerTagAttr, BattlerTagType.INTERRUPTED)
.attr(RemoveBattlerTagAttr, [ BattlerTagType.FLYING, BattlerTagType.FLOATING, BattlerTagType.TELEKINESIS ])
@ -9904,7 +9861,7 @@ export function initMoves() {
.attr(MovePowerMultiplierAttr, (user, target, move) => globalScene.arena.getTerrainType() === TerrainType.GRASSY && target.isGrounded() ? 0.5 : 1)
.makesContact(false)
.target(MoveTarget.ALL_NEAR_OTHERS),
new AttackMove(Moves.FROST_BREATH, PokemonType.ICE, MoveCategory.SPECIAL, 60, 90, 10, 100, 0, 5)
new AttackMove(Moves.FROST_BREATH, PokemonType.ICE, MoveCategory.SPECIAL, 60, 90, 10, -1, 0, 5)
.attr(CritOnlyAttr),
new AttackMove(Moves.DRAGON_TAIL, PokemonType.DRAGON, MoveCategory.PHYSICAL, 60, 90, 10, -1, -6, 5)
.attr(ForceSwitchOutAttr, false, SwitchType.FORCE_SWITCH)
@ -10546,7 +10503,7 @@ export function initMoves() {
.attr(AddArenaTagAttr, ArenaTagType.LIGHT_SCREEN, 5, false, true),
new AttackMove(Moves.BADDY_BAD, PokemonType.DARK, MoveCategory.SPECIAL, 80, 95, 15, -1, 0, 7)
.attr(AddArenaTagAttr, ArenaTagType.REFLECT, 5, false, true),
new AttackMove(Moves.SAPPY_SEED, PokemonType.GRASS, MoveCategory.PHYSICAL, 100, 90, 10, 100, 0, 7)
new AttackMove(Moves.SAPPY_SEED, PokemonType.GRASS, MoveCategory.PHYSICAL, 100, 90, 10, -1, 0, 7)
.attr(LeechSeedAttr)
.makesContact(false),
new AttackMove(Moves.FREEZY_FROST, PokemonType.ICE, MoveCategory.SPECIAL, 100, 90, 10, -1, 0, 7)
@ -10585,7 +10542,7 @@ export function initMoves() {
.attr(JawLockAttr)
.bitingMove(),
new SelfStatusMove(Moves.STUFF_CHEEKS, PokemonType.NORMAL, -1, 10, -1, 0, 8)
.attr(EatBerryAttr)
.attr(EatBerryAttr, true)
.attr(StatStageChangeAttr, [ Stat.DEF ], 2, true)
.condition((user) => {
const userBerries = globalScene.findModifiers(m => m instanceof BerryModifier, user.isPlayer());
@ -10609,7 +10566,7 @@ export function initMoves() {
.makesContact(false)
.partial(), // smart targetting is unimplemented
new StatusMove(Moves.TEATIME, PokemonType.NORMAL, -1, 10, -1, 0, 8)
.attr(EatBerryAttr)
.attr(EatBerryAttr, false)
.target(MoveTarget.ALL),
new StatusMove(Moves.OCTOLOCK, PokemonType.FIGHTING, 100, 15, -1, 0, 8)
.condition(failIfGhostTypeCondition)
@ -10874,7 +10831,7 @@ export function initMoves() {
.attr(StatStageChangeAttr, [ Stat.SPD ], 1, true),
new AttackMove(Moves.BITTER_MALICE, PokemonType.GHOST, MoveCategory.SPECIAL, 75, 100, 10, 100, 0, 8)
.attr(StatStageChangeAttr, [ Stat.ATK ], -1),
new SelfStatusMove(Moves.SHELTER, PokemonType.STEEL, -1, 10, 100, 0, 8)
new SelfStatusMove(Moves.SHELTER, PokemonType.STEEL, -1, 10, -1, 0, 8)
.attr(StatStageChangeAttr, [ Stat.DEF ], 2, true),
new AttackMove(Moves.TRIPLE_ARROWS, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 90, 100, 10, 30, 0, 8)
.makesContact(false)
@ -11029,7 +10986,7 @@ export function initMoves() {
.makesContact(false),
new AttackMove(Moves.LUMINA_CRASH, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 80, 100, 10, 100, 0, 9)
.attr(StatStageChangeAttr, [ Stat.SPDEF ], -2),
new AttackMove(Moves.ORDER_UP, PokemonType.DRAGON, MoveCategory.PHYSICAL, 80, 100, 10, 100, 0, 9)
new AttackMove(Moves.ORDER_UP, PokemonType.DRAGON, MoveCategory.PHYSICAL, 80, 100, 10, -1, 0, 9)
.attr(OrderUpStatBoostAttr)
.makesContact(false),
new AttackMove(Moves.JET_PUNCH, PokemonType.WATER, MoveCategory.PHYSICAL, 60, 100, 15, -1, 1, 9)
@ -11083,7 +11040,7 @@ export function initMoves() {
.attr(CutHpStatStageBoostAttr, [ Stat.ATK, Stat.SPATK, Stat.SPD ], 2, 2),
new AttackMove(Moves.KOWTOW_CLEAVE, PokemonType.DARK, MoveCategory.PHYSICAL, 85, -1, 10, -1, 0, 9)
.slicingMove(),
new AttackMove(Moves.FLOWER_TRICK, PokemonType.GRASS, MoveCategory.PHYSICAL, 70, -1, 10, 100, 0, 9)
new AttackMove(Moves.FLOWER_TRICK, PokemonType.GRASS, MoveCategory.PHYSICAL, 70, -1, 10, -1, 0, 9)
.attr(CritOnlyAttr)
.makesContact(false),
new AttackMove(Moves.TORCH_SONG, PokemonType.FIRE, MoveCategory.SPECIAL, 80, 100, 10, 100, 0, 9)
@ -11202,7 +11159,7 @@ export function initMoves() {
.attr(StatusEffectAttr, StatusEffect.BURN)
.target(MoveTarget.ALL_NEAR_ENEMIES)
.triageMove(),
new AttackMove(Moves.SYRUP_BOMB, PokemonType.GRASS, MoveCategory.SPECIAL, 60, 85, 10, -1, 0, 9)
new AttackMove(Moves.SYRUP_BOMB, PokemonType.GRASS, MoveCategory.SPECIAL, 60, 85, 10, 100, 0, 9)
.attr(AddBattlerTagAttr, BattlerTagType.SYRUP_BOMB, false, false, 3)
.ballBombMove(),
new AttackMove(Moves.IVY_CUDGEL, PokemonType.GRASS, MoveCategory.PHYSICAL, 100, 100, 10, -1, 0, 9)
@ -11216,11 +11173,12 @@ export function initMoves() {
new AttackMove(Moves.TERA_STARSTORM, PokemonType.NORMAL, MoveCategory.SPECIAL, 120, 100, 5, -1, 0, 9)
.attr(TeraMoveCategoryAttr)
.attr(TeraStarstormTypeAttr)
.attr(VariableTargetAttr, (user, target, move) => user.hasSpecies(Species.TERAPAGOS) && user.isTerastallized ? MoveTarget.ALL_NEAR_ENEMIES : MoveTarget.NEAR_OTHER)
.attr(VariableTargetAttr, (user, target, move) => user.hasSpecies(Species.TERAPAGOS) && (user.isTerastallized || globalScene.currentBattle.preTurnCommands[user.getFieldIndex()]?.command === Command.TERA) ? MoveTarget.ALL_NEAR_ENEMIES : MoveTarget.NEAR_OTHER)
.partial(), /** Does not ignore abilities that affect stats, relevant in determining the move's category {@see TeraMoveCategoryAttr} */
new AttackMove(Moves.FICKLE_BEAM, PokemonType.DRAGON, MoveCategory.SPECIAL, 80, 100, 5, 30, 0, 9)
.attr(PreMoveMessageAttr, doublePowerChanceMessageFunc)
.attr(DoublePowerChanceAttr),
.attr(DoublePowerChanceAttr)
.edgeCase(), // Should not interact with Sheer Force
new SelfStatusMove(Moves.BURNING_BULWARK, PokemonType.FIRE, -1, 10, -1, 4, 9)
.attr(ProtectAttr, BattlerTagType.BURNING_BULWARK)
.condition(failIfLastCondition),
@ -11243,16 +11201,18 @@ export function initMoves() {
new StatusMove(Moves.DRAGON_CHEER, PokemonType.DRAGON, -1, 15, -1, 0, 9)
.attr(AddBattlerTagAttr, BattlerTagType.DRAGON_CHEER, false, true)
.target(MoveTarget.NEAR_ALLY),
new AttackMove(Moves.ALLURING_VOICE, PokemonType.FAIRY, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 9)
new AttackMove(Moves.ALLURING_VOICE, PokemonType.FAIRY, MoveCategory.SPECIAL, 80, 100, 10, 100, 0, 9)
.attr(AddBattlerTagIfBoostedAttr, BattlerTagType.CONFUSED)
.soundBased(),
new AttackMove(Moves.TEMPER_FLARE, PokemonType.FIRE, MoveCategory.PHYSICAL, 75, 100, 10, -1, 0, 9)
.attr(MovePowerMultiplierAttr, (user, target, move) => user.getLastXMoves(2)[1]?.result === MoveResult.MISS || user.getLastXMoves(2)[1]?.result === MoveResult.FAIL ? 2 : 1),
new AttackMove(Moves.SUPERCELL_SLAM, PokemonType.ELECTRIC, MoveCategory.PHYSICAL, 100, 95, 15, -1, 0, 9)
.attr(AlwaysHitMinimizeAttr)
.attr(HitsTagForDoubleDamageAttr, BattlerTagType.MINIMIZED)
.attr(MissEffectAttr, crashDamageFunc)
.attr(NoEffectAttr, crashDamageFunc)
.recklessMove(),
new AttackMove(Moves.PSYCHIC_NOISE, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 75, 100, 10, -1, 0, 9)
new AttackMove(Moves.PSYCHIC_NOISE, PokemonType.PSYCHIC, MoveCategory.SPECIAL, 75, 100, 10, 100, 0, 9)
.soundBased()
.attr(AddBattlerTagAttr, BattlerTagType.HEAL_BLOCK, false, false, 2),
new AttackMove(Moves.UPPER_HAND, PokemonType.FIGHTING, MoveCategory.PHYSICAL, 65, 100, 15, 100, 3, 9)

View File

@ -14,7 +14,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { TrainerType } from "#enums/trainer-type";
import { Species } from "#enums/species";
import { getSpriteKeysFromSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { randSeedInt } from "#app/utils";
import { randSeedInt } from "#app/utils/common";
import i18next from "i18next";
import type { IEggOptions } from "#app/data/egg";
import { EggSourceType } from "#enums/egg-source-types";
@ -22,7 +22,7 @@ import { EggTier } from "#enums/egg-type";
import { PartyHealPhase } from "#app/phases/party-heal-phase";
import { ModifierTier } from "#app/modifier/modifier-tier";
import { modifierTypes } from "#app/modifier/modifier-type";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounters/aTrainersTest";

View File

@ -24,7 +24,7 @@ import { BerryModifier, PokemonInstantReviveModifier } from "#app/modifier/modif
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { Moves } from "#enums/moves";
import { BattlerTagType } from "#enums/battler-tag-type";
import { randInt } from "#app/utils";
import { randInt } from "#app/utils/common";
import { BattlerIndex } from "#app/battle";
import {
applyModifierTypeToPlayerPokemon,
@ -37,7 +37,7 @@ import type HeldModifierConfig from "#app/interfaces/held-modifier-config";
import type { BerryType } from "#enums/berry-type";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { Stat } from "#enums/stat";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import i18next from "i18next";
/** the i18n namespace for this encounter */

View File

@ -23,7 +23,7 @@ import { speciesStarterCosts } from "#app/data/balance/starters";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import i18next from "i18next";
/** the i18n namespace for this encounter */

View File

@ -13,7 +13,7 @@ import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon";
import type { BerryModifierType, ModifierTypeOption } from "#app/modifier/modifier-type";
import { ModifierPoolType, modifierTypes, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type";
import { randSeedInt } from "#app/utils";
import { randSeedInt } from "#app/utils/common";
import { BattlerTagType } from "#enums/battler-tag-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { globalScene } from "#app/global-scene";
@ -36,7 +36,7 @@ import i18next from "#app/plugins/i18n";
import { BerryType } from "#enums/berry-type";
import { PERMANENT_STATS, Stat } from "#enums/stat";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounters/berriesAbound";

View File

@ -16,7 +16,7 @@ import { TrainerSlot } from "#enums/trainer-slot";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { PartyMemberStrength } from "#enums/party-member-strength";
import { globalScene } from "#app/global-scene";
import { isNullOrUndefined, randSeedInt, randSeedShuffle } from "#app/utils";
import { isNullOrUndefined, randSeedInt, randSeedShuffle } from "#app/utils/common";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
@ -52,7 +52,7 @@ import i18next from "i18next";
import MoveInfoOverlay from "#app/ui/move-info-overlay";
import { allMoves } from "#app/data/moves/move";
import { ModifierTier } from "#app/modifier/modifier-tier";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { getSpriteKeysFromSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
/** the i18n namespace for the encounter */

View File

@ -31,14 +31,14 @@ import {
import { PokemonType } from "#enums/pokemon-type";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { randSeedInt, randSeedShuffle } from "#app/utils";
import { randSeedInt, randSeedShuffle } from "#app/utils/common";
import { showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { Mode } from "#app/ui/ui";
import { UiMode } from "#enums/ui-mode";
import i18next from "i18next";
import type { OptionSelectConfig } from "#app/ui/abstact-option-select-ui-handler";
import type { PlayerPokemon } from "#app/field/pokemon";
import { PokemonMove } from "#app/field/pokemon";
import { Ability } from "#app/data/ability";
import { Ability } from "#app/data/abilities/ability-class";
import { BerryModifier } from "#app/modifier/modifier";
import { BerryType } from "#enums/berry-type";
import { BattlerIndex } from "#app/battle";
@ -46,7 +46,7 @@ import { Moves } from "#enums/moves";
import { EncounterBattleAnim } from "#app/data/battle-anims";
import { MoveCategory } from "#enums/MoveCategory";
import { CustomPokemonData } from "#app/data/custom-pokemon-data";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { EncounterAnim } from "#enums/encounter-anims";
import { Challenges } from "#enums/challenges";
@ -437,7 +437,7 @@ async function handleSwapAbility() {
await showEncounterDialogue(`${namespace}:option.1.apply_ability_dialogue`, `${namespace}:speaker`);
await showEncounterText(`${namespace}:option.1.apply_ability_message`);
globalScene.ui.setMode(Mode.MESSAGE).then(() => {
globalScene.ui.setMode(UiMode.MESSAGE).then(() => {
displayYesNoOptions(resolve);
});
});
@ -467,7 +467,7 @@ function displayYesNoOptions(resolve) {
maxOptions: 7,
yOffset: 0,
};
globalScene.ui.setModeWithoutClear(Mode.OPTION_SELECT, config, null, true);
globalScene.ui.setModeWithoutClear(UiMode.OPTION_SELECT, config, null, true);
}
function onYesAbilitySwap(resolve) {
@ -477,11 +477,11 @@ function onYesAbilitySwap(resolve) {
applyAbilityOverrideToPokemon(pokemon, encounter.misc.ability);
encounter.setDialogueToken("chosenPokemon", pokemon.getNameToRender());
globalScene.ui.setMode(Mode.MESSAGE).then(() => resolve(true));
globalScene.ui.setMode(UiMode.MESSAGE).then(() => resolve(true));
};
const onPokemonNotSelected = () => {
globalScene.ui.setMode(Mode.MESSAGE).then(() => {
globalScene.ui.setMode(UiMode.MESSAGE).then(() => {
displayYesNoOptions(resolve);
});
};

View File

@ -24,7 +24,7 @@ import { TrainerSlot } from "#enums/trainer-slot";
import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon";
import { EnemyPokemon, PokemonMove } from "#app/field/pokemon";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { modifierTypes } from "#app/modifier/modifier-type";
import { LearnMovePhase } from "#app/phases/learn-move-phase";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";

View File

@ -1,5 +1,5 @@
import type { PokemonType } from "#enums/pokemon-type";
import { isNullOrUndefined, randSeedInt } from "#app/utils";
import { isNullOrUndefined, randSeedInt } from "#app/utils/common";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
import { globalScene } from "#app/global-scene";
@ -19,7 +19,7 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
import type { PokemonHeldItemModifier } from "#app/modifier/modifier";
import { PokemonFormChangeItemModifier } from "#app/modifier/modifier";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { Challenges } from "#enums/challenges";
/** i18n namespace for encounter */

View File

@ -18,7 +18,7 @@ import { applyModifierTypeToPlayerPokemon } from "#app/data/mystery-encounters/u
import { getPokemonSpecies } from "#app/data/pokemon-species";
import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import type { PokemonHeldItemModifier, PokemonInstantReviveModifier } from "#app/modifier/modifier";
import {
BerryModifier,
@ -32,7 +32,7 @@ import { modifierTypes } from "#app/modifier/modifier-type";
import { ModifierRewardPhase } from "#app/phases/modifier-reward-phase";
import i18next from "#app/plugins/i18n";
import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import { randSeedItem } from "#app/utils";
import { randSeedItem } from "#app/utils/common";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";

View File

@ -4,13 +4,13 @@ import {
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import type { ModifierTypeFunc } from "#app/modifier/modifier-type";
import { modifierTypes } from "#app/modifier/modifier-type";
import { randSeedInt } from "#app/utils";
import { randSeedInt } from "#app/utils/common";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
/** i18n namespace for encounter */
const namespace = "mysteryEncounters/departmentStoreSale";

View File

@ -18,7 +18,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { Stat } from "#enums/stat";
import i18next from "i18next";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
/** i18n namespace for the encounter */
const namespace = "mysteryEncounters/fieldTrip";

View File

@ -30,7 +30,7 @@ import { PokemonMove } from "#app/field/pokemon";
import { Moves } from "#enums/moves";
import { EncounterBattleAnim } from "#app/data/battle-anims";
import { WeatherType } from "#enums/weather-type";
import { isNullOrUndefined, randSeedInt } from "#app/utils";
import { isNullOrUndefined, randSeedInt } from "#app/utils/common";
import { StatusEffect } from "#enums/status-effect";
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import {
@ -41,12 +41,12 @@ import {
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { EncounterAnim } from "#enums/encounter-anims";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { Abilities } from "#enums/abilities";
import { BattlerTagType } from "#enums/battler-tag-type";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { Stat } from "#enums/stat";
import { Ability } from "#app/data/ability";
import { Ability } from "#app/data/abilities/ability-class";
import { FIRE_RESISTANT_ABILITIES } from "#app/data/mystery-encounters/requirements/requirement-groups";
/** the i18n namespace for the encounter */

View File

@ -31,9 +31,9 @@ import {
import PokemonData from "#app/system/pokemon-data";
import { BattlerTagType } from "#enums/battler-tag-type";
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { randSeedInt } from "#app/utils";
import { randSeedInt } from "#app/utils/common";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounters/fightOrFlight";

View File

@ -30,7 +30,7 @@ import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms";
import { PostSummonPhase } from "#app/phases/post-summon-phase";
import { modifierTypes } from "#app/modifier/modifier-type";
import { Nature } from "#enums/nature";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { isPokemonValidForEncounterOptionSelection } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
/** the i18n namespace for the encounter */

View File

@ -23,7 +23,14 @@ import { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species";
import { getTypeRgb } from "#app/data/type";
import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/mystery-encounter-option";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { NumberHolder, isNullOrUndefined, randInt, randSeedInt, randSeedShuffle, randSeedItem } from "#app/utils";
import {
NumberHolder,
isNullOrUndefined,
randInt,
randSeedInt,
randSeedShuffle,
randSeedItem,
} from "#app/utils/common";
import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon";
import { EnemyPokemon, PokemonMove } from "#app/field/pokemon";
@ -41,7 +48,7 @@ import { Gender, getGenderSymbol } from "#app/data/gender";
import { getNatureName } from "#app/data/nature";
import { getPokeballAtlasKey, getPokeballTintColor } from "#app/data/pokeball";
import { getEncounterText, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { addPokemonDataToDexAndValidateAchievements } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import type { PokeballType } from "#enums/pokeball";
import { doShinySparkleAnim } from "#app/field/anims";

View File

@ -10,7 +10,7 @@ import { leaveEncounterWithoutBattle, setEncounterExp } from "../utils/encounter
import { applyDamageToPokemon } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { PokemonMove } from "#app/field/pokemon";
const OPTION_1_REQUIRED_MOVE = Moves.SURF;

View File

@ -12,11 +12,11 @@ import { modifierTypes } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { PartyMemberStrength } from "#enums/party-member-strength";
import { globalScene } from "#app/global-scene";
import { randSeedInt } from "#app/utils";
import { randSeedInt } from "#app/utils/common";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterBuilder } from "#app/data/mystery-encounters/mystery-encounter";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounters/mysteriousChallengers";

View File

@ -15,10 +15,10 @@ import {
koPlayerPokemon,
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { ModifierTier } from "#app/modifier/modifier-tier";
import { GameOverPhase } from "#app/phases/game-over-phase";
import { randSeedInt } from "#app/utils";
import { randSeedInt } from "#app/utils/common";
import { Moves } from "#enums/moves";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";

View File

@ -20,7 +20,7 @@ import { showEncounterDialogue, showEncounterText } from "#app/data/mystery-enco
import i18next from "i18next";
import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { isPokemonValidForEncounterOptionSelection } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
/** the i18n namespace for the encounter */

View File

@ -15,7 +15,7 @@ import { HiddenAbilityRateBoosterModifier, IvScannerModifier } from "#app/modifi
import type { EnemyPokemon } from "#app/field/pokemon";
import { PokeballType } from "#enums/pokeball";
import { PlayerGender } from "#enums/player-gender";
import { NumberHolder, randSeedInt } from "#app/utils";
import { NumberHolder, randSeedInt } from "#app/utils/common";
import type PokemonSpecies from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
@ -31,7 +31,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { ScanIvsPhase } from "#app/phases/scan-ivs-phase";
import { SummonPhase } from "#app/phases/summon-phase";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { NON_LEGEND_PARADOX_POKEMON } from "#app/data/balance/special-species-groups";
/** the i18n namespace for the encounter */

View File

@ -8,7 +8,7 @@ import {
import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon";
import { modifierTypes } from "#app/modifier/modifier-type";
import { randSeedInt } from "#app/utils";
import { randSeedInt } from "#app/utils/common";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { Species } from "#enums/species";
import { globalScene } from "#app/global-scene";
@ -26,7 +26,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import type { Nature } from "#enums/nature";
import { getNatureName } from "#app/data/nature";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import i18next from "i18next";
/** the i18n namespace for this encounter */

View File

@ -26,7 +26,7 @@ import { getPokemonSpecies } from "#app/data/pokemon-species";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { PartyHealPhase } from "#app/phases/party-heal-phase";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { BerryType } from "#enums/berry-type";
import { CustomPokemonData } from "#app/data/custom-pokemon-data";

View File

@ -7,7 +7,7 @@ import {
transitionMysteryEncounterIntroVisuals,
updatePlayerMoney,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { randSeedInt } from "#app/utils";
import { randSeedInt } from "#app/utils/common";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
@ -29,7 +29,7 @@ import { BattlerTagType } from "#enums/battler-tag-type";
import { getPokemonNameWithAffix } from "#app/messages";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { Stat } from "#enums/stat";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import {
getEncounterPokemonLevelForWave,
STANDARD_ENCOUNTER_BOOSTED_LEVEL_MODIFIER,

View File

@ -7,11 +7,11 @@ import {
import { trainerConfigs } from "#app/data/trainers/trainer-config";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { globalScene } from "#app/global-scene";
import { randSeedShuffle } from "#app/utils";
import { randSeedShuffle } from "#app/utils/common";
import type MysteryEncounter from "../mystery-encounter";
import { MysteryEncounterBuilder } from "../mystery-encounter";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { Biome } from "#enums/biome";
import { TrainerType } from "#enums/trainer-type";
import i18next from "i18next";

View File

@ -3,7 +3,7 @@ import {
transitionMysteryEncounterIntroVisuals,
updatePlayerMoney,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import { isNullOrUndefined, randSeedInt } from "#app/utils";
import { isNullOrUndefined, NumberHolder, randSeedInt, randSeedItem } from "#app/utils/common";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
@ -26,9 +26,10 @@ import { showEncounterDialogue } from "#app/data/mystery-encounters/utils/encoun
import PokemonData from "#app/system/pokemon-data";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { Abilities } from "#enums/abilities";
import { NON_LEGEND_PARADOX_POKEMON } from "#app/data/balance/special-species-groups";
import { NON_LEGEND_PARADOX_POKEMON, NON_LEGEND_ULTRA_BEASTS } from "#app/data/balance/special-species-groups";
import { timedEventManager } from "#app/global-event-manager";
/** the i18n namespace for this encounter */
const namespace = "mysteryEncounters/thePokemonSalesman";
@ -38,6 +39,9 @@ const MAX_POKEMON_PRICE_MULTIPLIER = 4;
/** Odds of shiny magikarp will be 1/value */
const SHINY_MAGIKARP_WEIGHT = 100;
/** Odds of event sale will be value/100 */
const EVENT_THRESHOLD = 50;
/**
* Pokemon Salesman encounter.
* @see {@link https://github.com/pagefaultgames/pokerogue/issues/3799 | GitHub Issue #3799}
@ -82,15 +86,46 @@ export const ThePokemonSalesmanEncounter: MysteryEncounter = MysteryEncounterBui
tries++;
}
const r = randSeedInt(SHINY_MAGIKARP_WEIGHT);
const validEventEncounters = timedEventManager
.getEventEncounters()
.filter(
s =>
!getPokemonSpecies(s.species).legendary &&
!getPokemonSpecies(s.species).subLegendary &&
!getPokemonSpecies(s.species).mythical &&
!NON_LEGEND_PARADOX_POKEMON.includes(s.species) &&
!NON_LEGEND_ULTRA_BEASTS.includes(s.species),
);
let pokemon: PlayerPokemon;
/**
* Mon is determined as follows:
* If you roll the 1% for Shiny Magikarp, you get Magikarp with a random variant
* If an event with more than 1 valid event encounter species is active, you have 20% chance to get one of those
* If the rolled species has no HA, and there are valid event encounters, you will get one of those
* If the rolled species has no HA and there are no valid event encounters, you will get Shiny Magikarp
* Mons rolled from the event encounter pool get 2 extra shiny rolls
*/
if (
randSeedInt(SHINY_MAGIKARP_WEIGHT) === 0 ||
isNullOrUndefined(species.abilityHidden) ||
species.abilityHidden === Abilities.NONE
r === 0 ||
((isNullOrUndefined(species.abilityHidden) || species.abilityHidden === Abilities.NONE) &&
(validEventEncounters.length === 0))
) {
// If no HA mon found or you roll 1%, give shiny Magikarp with random variant
// If you roll 1%, give shiny Magikarp with random variant
species = getPokemonSpecies(Species.MAGIKARP);
pokemon = new PlayerPokemon(species, 5, 2, species.formIndex, undefined, true);
pokemon = new PlayerPokemon(species, 5, 2, undefined, undefined, true);
} else if (
(validEventEncounters.length > 0 && (r <= EVENT_THRESHOLD ||
(isNullOrUndefined(species.abilityHidden) || species.abilityHidden === Abilities.NONE)))
) {
// If you roll 20%, give event encounter with 2 extra shiny rolls and its HA, if it has one
const enc = randSeedItem(validEventEncounters);
species = getPokemonSpecies(enc.species);
pokemon = new PlayerPokemon(species, 5, species.abilityHidden === Abilities.NONE ? undefined : 2, enc.formIndex);
pokemon.trySetShinySeed();
pokemon.trySetShinySeed();
} else {
pokemon = new PlayerPokemon(species, 5, 2, species.formIndex);
}

View File

@ -28,7 +28,7 @@ import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { CustomPokemonData } from "#app/data/custom-pokemon-data";
import { Stat } from "#enums/stat";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounters/theStrongStuff";

View File

@ -24,7 +24,7 @@ import { PokemonType } from "#enums/pokemon-type";
import { BerryType } from "#enums/berry-type";
import { Stat } from "#enums/stat";
import { SpeciesFormChangeAbilityTrigger } from "#app/data/pokemon-forms";
import { applyPostBattleInitAbAttrs, PostBattleInitAbAttr } from "#app/data/ability";
import { applyPostBattleInitAbAttrs, PostBattleInitAbAttr } from "#app/data/abilities/ability";
import { showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
import { PartyHealPhase } from "#app/phases/party-heal-phase";
@ -32,7 +32,7 @@ import { ShowTrainerPhase } from "#app/phases/show-trainer-phase";
import { ReturnPhase } from "#app/phases/return-phase";
import i18next from "i18next";
import { ModifierTier } from "#app/modifier/modifier-tier";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { BattlerTagType } from "#enums/battler-tag-type";
/** the i18n namespace for the encounter */

View File

@ -1,5 +1,5 @@
import type { Ability } from "#app/data/ability";
import { allAbilities } from "#app/data/ability";
import type { Ability } from "#app/data/abilities/ability-class";
import { allAbilities } from "#app/data/data-lists";
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import {
initBattleWithEnemyConfig,
@ -15,7 +15,7 @@ import type { PokemonHeldItemModifier } from "#app/modifier/modifier";
import { AbilityAttr } from "#app/system/game-data";
import PokemonData from "#app/system/pokemon-data";
import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import { isNullOrUndefined, randSeedShuffle } from "#app/utils";
import { isNullOrUndefined, randSeedShuffle } from "#app/utils/common";
import { BattlerTagType } from "#enums/battler-tag-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { globalScene } from "#app/global-scene";
@ -28,7 +28,7 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode
import type HeldModifierConfig from "#app/interfaces/held-modifier-config";
import i18next from "i18next";
import { getStatKey } from "#enums/stat";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { isPokemonValidForEncounterOptionSelection } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import type { Nature } from "#enums/nature";

View File

@ -26,8 +26,8 @@ import { getPokemonSpecies } from "#app/data/pokemon-species";
import { Moves } from "#enums/moves";
import { BattlerIndex } from "#app/battle";
import { PokemonMove } from "#app/field/pokemon";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { randSeedInt } from "#app/utils";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { randSeedInt } from "#app/utils/common";
/** the i18n namespace for this encounter */
const namespace = "mysteryEncounters/trashToTreasure";

View File

@ -27,7 +27,7 @@ import {
getSpriteKeysFromPokemon,
} from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import PokemonData from "#app/system/pokemon-data";
import { isNullOrUndefined, randSeedInt } from "#app/utils";
import { isNullOrUndefined, randSeedInt } from "#app/utils/common";
import type { Moves } from "#enums/moves";
import { BattlerIndex } from "#app/battle";
import { SelfStatusMove } from "#app/data/moves/move";
@ -37,7 +37,7 @@ import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encoun
import { BerryModifier } from "#app/modifier/modifier";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { Stat } from "#enums/stat";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/game-mode";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
/** the i18n namespace for the encounter */
const namespace = "mysteryEncounters/uncommonBreed";

View File

@ -17,7 +17,7 @@ import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode
import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon";
import { PokemonMove } from "#app/field/pokemon";
import { NumberHolder, isNullOrUndefined, randSeedInt, randSeedShuffle } from "#app/utils";
import { NumberHolder, isNullOrUndefined, randSeedInt, randSeedShuffle } from "#app/utils/common";
import type PokemonSpecies from "#app/data/pokemon-species";
import { allSpecies, getPokemonSpecies } from "#app/data/pokemon-species";
import type { PokemonHeldItemModifier } from "#app/modifier/modifier";

View File

@ -12,7 +12,7 @@ import {
} from "#app/data/mystery-encounters/mystery-encounter-requirements";
import type { CanLearnMoveRequirementOptions } from "./requirements/can-learn-move-requirement";
import { CanLearnMoveRequirement } from "./requirements/can-learn-move-requirement";
import { isNullOrUndefined, randSeedInt } from "#app/utils";
import { isNullOrUndefined, randSeedInt } from "#app/utils/common";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
// biome-ignore lint/suspicious/noConfusingVoidType: void unions in callbacks are OK

View File

@ -1,5 +1,5 @@
import { globalScene } from "#app/global-scene";
import { allAbilities } from "#app/data/ability";
import { allAbilities } from "../data-lists";
import { EvolutionItem, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions";
import { Nature } from "#enums/nature";
import { FormChangeItem, pokemonFormChanges, SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms";
@ -9,7 +9,7 @@ import { WeatherType } from "#enums/weather-type";
import type { PlayerPokemon } from "#app/field/pokemon";
import { AttackTypeBoosterModifier } from "#app/modifier/modifier";
import type { AttackTypeBoosterModifierType } from "#app/modifier/modifier-type";
import { isNullOrUndefined } from "#app/utils";
import { isNullOrUndefined } from "#app/utils/common";
import type { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import type { MysteryEncounterType } from "#enums/mystery-encounter-type";

View File

@ -1,6 +1,6 @@
import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { BASE_MYSTERY_ENCOUNTER_SPAWN_WEIGHT } from "#app/data/mystery-encounters/mystery-encounters";
import { isNullOrUndefined } from "#app/utils";
import { isNullOrUndefined } from "#app/utils/common";
import type { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
export class SeenEncounterData {

View File

@ -1,11 +1,11 @@
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import type { PlayerPokemon, PokemonMove } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon";
import { capitalizeFirstLetter, isNullOrUndefined } from "#app/utils";
import { capitalizeFirstLetter, isNullOrUndefined } from "#app/utils/common";
import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
import type { MysteryEncounterSpriteConfig } from "#app/field/mystery-encounter-intro";
import MysteryEncounterIntroVisuals from "#app/field/mystery-encounter-intro";
import { randSeedInt } from "#app/utils";
import { randSeedInt } from "#app/utils/common";
import type { StatusEffect } from "#enums/status-effect";
import type { OptionTextDisplay } from "./mystery-encounter-dialogue";
import type MysteryEncounterDialogue from "./mystery-encounter-dialogue";

View File

@ -1,7 +1,7 @@
import type { Moves } from "#app/enums/moves";
import type { PlayerPokemon } from "#app/field/pokemon";
import { PokemonMove } from "#app/field/pokemon";
import { isNullOrUndefined } from "#app/utils";
import { isNullOrUndefined } from "#app/utils/common";
import { EncounterPokemonRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { globalScene } from "#app/global-scene";

View File

@ -2,7 +2,7 @@ import { globalScene } from "#app/global-scene";
import type { TextStyle } from "#app/ui/text";
import { getTextWithColors } from "#app/ui/text";
import { UiTheme } from "#enums/ui-theme";
import { isNullOrUndefined } from "#app/utils";
import { isNullOrUndefined } from "#app/utils/common";
import i18next from "i18next";
/**

View File

@ -1,5 +1,6 @@
import type Battle from "#app/battle";
import { BattlerIndex, BattleType } from "#app/battle";
import { BattlerIndex } from "#app/battle";
import { BattleType } from "#enums/battle-type";
import { biomeLinks, BiomePoolTier } from "#app/data/balance/biomes";
import type MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option";
import {
@ -29,8 +30,8 @@ import type PokemonData from "#app/system/pokemon-data";
import type { OptionSelectConfig, OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import type { PartyOption, PokemonSelectFilter } from "#app/ui/party-ui-handler";
import { PartyUiMode } from "#app/ui/party-ui-handler";
import { Mode } from "#app/ui/ui";
import { isNullOrUndefined, randSeedInt, randomString, randSeedItem } from "#app/utils";
import { UiMode } from "#enums/ui-mode";
import { isNullOrUndefined, randSeedInt, randomString, randSeedItem } from "#app/utils/common";
import type { BattlerTagType } from "#enums/battler-tag-type";
import { Biome } from "#enums/biome";
import type { TrainerType } from "#enums/trainer-type";
@ -562,7 +563,7 @@ export function selectPokemonForOption(
// Open party screen to choose pokemon
globalScene.ui.setMode(
Mode.PARTY,
UiMode.PARTY,
PartyUiMode.SELECT,
-1,
(slotIndex: number, _option: PartyOption) => {
@ -580,7 +581,7 @@ export function selectPokemonForOption(
}
// There is a second option to choose after selecting the Pokemon
globalScene.ui.setMode(Mode.MESSAGE).then(() => {
globalScene.ui.setMode(UiMode.MESSAGE).then(() => {
const displayOptions = () => {
// Always appends a cancel option to bottom of options
const fullOptions = secondaryOptions
@ -622,7 +623,7 @@ export function selectPokemonForOption(
if (fullOptions[0].onHover) {
fullOptions[0].onHover();
}
globalScene.ui.setModeWithoutClear(Mode.OPTION_SELECT, config, null, true);
globalScene.ui.setModeWithoutClear(UiMode.OPTION_SELECT, config, null, true);
};
const textPromptKey =
@ -672,20 +673,20 @@ export function selectOptionThenPokemon(
const modeToSetOnExit = globalScene.ui.getMode();
const displayOptions = (config: OptionSelectConfig) => {
globalScene.ui.setMode(Mode.MESSAGE).then(() => {
globalScene.ui.setMode(UiMode.MESSAGE).then(() => {
if (!optionSelectPromptKey) {
// Do hover over the starting selection option
if (fullOptions[0].onHover) {
fullOptions[0].onHover();
}
globalScene.ui.setMode(Mode.OPTION_SELECT, config);
globalScene.ui.setMode(UiMode.OPTION_SELECT, config);
} else {
showEncounterText(optionSelectPromptKey).then(() => {
// Do hover over the starting selection option
if (fullOptions[0].onHover) {
fullOptions[0].onHover();
}
globalScene.ui.setMode(Mode.OPTION_SELECT, config);
globalScene.ui.setMode(UiMode.OPTION_SELECT, config);
});
}
});
@ -694,7 +695,7 @@ export function selectOptionThenPokemon(
const selectPokemonAfterOption = (selectedOptionIndex: number) => {
// Open party screen to choose a Pokemon
globalScene.ui.setMode(
Mode.PARTY,
UiMode.PARTY,
PartyUiMode.SELECT,
-1,
(slotIndex: number, _option: PartyOption) => {
@ -1074,8 +1075,8 @@ export function getRandomEncounterSpecies(level: number, isBoss = false, rerollH
ret.formIndex = formIndex;
}
//Reroll shiny for event encounters
if (isEventEncounter && !ret.shiny) {
//Reroll shiny or variant for event encounters
if (isEventEncounter) {
ret.trySetShinySeed();
}
//Reroll hidden ability

View File

@ -1,6 +1,6 @@
import { globalScene } from "#app/global-scene";
import i18next from "i18next";
import { isNullOrUndefined, randSeedInt } from "#app/utils";
import { isNullOrUndefined, randSeedInt } from "#app/utils/common";
import { PokemonHeldItemModifier } from "#app/modifier/modifier";
import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon";
@ -14,7 +14,7 @@ import { PlayerGender } from "#enums/player-gender";
import { addPokeballCaptureStars, addPokeballOpenParticles } from "#app/field/anims";
import { getStatusEffectCatchRateMultiplier } from "#app/data/status-effect";
import { achvs } from "#app/system/achv";
import { Mode } from "#app/ui/ui";
import { UiMode } from "#enums/ui-mode";
import type { PartyOption } from "#app/ui/party-ui-handler";
import { PartyUiMode } from "#app/ui/party-ui-handler";
import { Species } from "#enums/species";
@ -714,7 +714,7 @@ export async function catchPokemon(
() => {
globalScene.pokemonInfoContainer.makeRoomForConfirmUi(1, true);
globalScene.ui.setMode(
Mode.CONFIRM,
UiMode.CONFIRM,
() => {
const newPokemon = globalScene.addPlayerPokemon(
pokemon.species,
@ -729,12 +729,12 @@ export async function catchPokemon(
pokemon,
);
globalScene.ui.setMode(
Mode.SUMMARY,
UiMode.SUMMARY,
newPokemon,
0,
SummaryUiMode.DEFAULT,
() => {
globalScene.ui.setMode(Mode.MESSAGE).then(() => {
globalScene.ui.setMode(UiMode.MESSAGE).then(() => {
promptRelease();
});
},
@ -749,13 +749,13 @@ export async function catchPokemon(
female: pokemon.gender === Gender.FEMALE,
};
globalScene.ui.setOverlayMode(
Mode.POKEDEX_PAGE,
UiMode.POKEDEX_PAGE,
pokemon.species,
pokemon.formIndex,
attributes,
null,
() => {
globalScene.ui.setMode(Mode.MESSAGE).then(() => {
globalScene.ui.setMode(UiMode.MESSAGE).then(() => {
promptRelease();
});
},
@ -763,11 +763,11 @@ export async function catchPokemon(
},
() => {
globalScene.ui.setMode(
Mode.PARTY,
UiMode.PARTY,
PartyUiMode.RELEASE,
0,
(slotIndex: number, _option: PartyOption) => {
globalScene.ui.setMode(Mode.MESSAGE).then(() => {
globalScene.ui.setMode(UiMode.MESSAGE).then(() => {
if (slotIndex < 6) {
addToParty(slotIndex);
} else {
@ -778,7 +778,7 @@ export async function catchPokemon(
);
},
() => {
globalScene.ui.setMode(Mode.MESSAGE).then(() => {
globalScene.ui.setMode(UiMode.MESSAGE).then(() => {
removePokemon();
end();
});

View File

@ -1,5 +1,5 @@
import type { PlayerPokemon } from "#app/field/pokemon";
import { getFrameMs } from "#app/utils";
import { getFrameMs } from "#app/utils/common";
import { cos, sin } from "#app/field/anims";
import { getTypeRgb } from "#app/data/type";
import { globalScene } from "#app/global-scene";

View File

@ -1,4 +1,4 @@
import { toReadableString } from "#app/utils";
import { toReadableString } from "#app/utils/common";
import { TextStyle, getBBCodeFrag } from "../ui/text";
import { Nature } from "#enums/nature";
import { UiTheme } from "#enums/ui-theme";

View File

@ -1,6 +1,6 @@
import { globalScene } from "#app/global-scene";
import { CriticalCatchChanceBoosterModifier } from "#app/modifier/modifier";
import { NumberHolder } from "#app/utils";
import { NumberHolder } from "#app/utils/common";
import { PokeballType } from "#enums/pokeball";
import i18next from "i18next";

View File

@ -3,7 +3,7 @@ import type Pokemon from "../field/pokemon";
import { StatusEffect } from "#enums/status-effect";
import { allMoves } from "./moves/move";
import { MoveCategory } from "#enums/MoveCategory";
import type { Constructor, nil } from "#app/utils";
import type { Constructor, nil } from "#app/utils/common";
import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";

View File

@ -8,7 +8,7 @@ import type { AnySound } from "#app/battle-scene";
import { globalScene } from "#app/global-scene";
import type { GameMode } from "#app/game-mode";
import { DexAttr, type StarterMoveset } from "#app/system/game-data";
import { isNullOrUndefined, capitalizeString, randSeedInt, randSeedGauss, randSeedItem } from "#app/utils";
import { isNullOrUndefined, capitalizeString, randSeedInt, randSeedGauss, randSeedItem } from "#app/utils/common";
import { uncatchableSpecies } from "#app/data/balance/biomes";
import { speciesEggMoves } from "#app/data/balance/egg-moves";
import { GrowthRate } from "#app/data/exp";
@ -27,11 +27,12 @@ import {
} from "#app/data/balance/pokemon-level-moves";
import type { Stat } from "#enums/stat";
import type { Variant, VariantSet } from "#app/sprites/variant";
import { variantData } from "#app/sprites/variant";
import { populateVariantColorCache, variantColorCache, variantData } from "#app/sprites/variant";
import { speciesStarterCosts, POKERUS_STARTER_COUNT } from "#app/data/balance/starters";
import { SpeciesFormKey } from "#enums/species-form-key";
import { starterPassiveAbilities } from "#app/data/balance/passives";
import { loadPokemonVariantAssets } from "#app/sprites/pokemon-sprite";
import { hasExpSprite } from "#app/sprites/sprite-utils";
export enum Region {
NORMAL,
@ -388,8 +389,7 @@ export abstract class PokemonSpeciesForm {
return `${/_[1-3]$/.test(spriteId) ? "variant/" : ""}${spriteId}`;
}
/** Compute the sprite ID of the pokemon form. */
getSpriteId(female: boolean, formIndex?: number, shiny?: boolean, variant = 0, back?: boolean): string {
getBaseSpriteKey(female: boolean, formIndex?: number): string {
if (formIndex === undefined || this instanceof PokemonForm) {
formIndex = this.formIndex;
}
@ -400,7 +400,12 @@ export abstract class PokemonSpeciesForm {
female &&
![SpeciesFormKey.MEGA, SpeciesFormKey.GIGANTAMAX].includes(formSpriteKey as SpeciesFormKey);
const baseSpriteKey = `${showGenderDiffs ? "female__" : ""}${this.speciesId}${formSpriteKey ? `-${formSpriteKey}` : ""}`;
return `${showGenderDiffs ? "female__" : ""}${this.speciesId}${formSpriteKey ? `-${formSpriteKey}` : ""}`;
}
/** Compute the sprite ID of the pokemon form. */
getSpriteId(female: boolean, formIndex?: number, shiny?: boolean, variant = 0, back = false): string {
const baseSpriteKey = this.getBaseSpriteKey(female, formIndex);
let config = variantData;
`${back ? "back__" : ""}${baseSpriteKey}`.split("__").map(p => (config ? (config = config[p]) : null));
@ -589,6 +594,44 @@ export abstract class PokemonSpeciesForm {
return true;
}
/**
* Load the variant colors for the species into the variant color cache
*
* @param spriteKey - The sprite key to use
* @param female - Whether to load female instead of male
* @param back - Whether the back sprite is being loaded
*
*/
async loadVariantColors(
spriteKey: string,
female: boolean,
variant: Variant,
back = false,
formIndex?: number,
): Promise<void> {
let baseSpriteKey = this.getBaseSpriteKey(female, formIndex);
if (back) {
baseSpriteKey = "back__" + baseSpriteKey;
}
if (variantColorCache.hasOwnProperty(baseSpriteKey)) {
// Variant colors have already been loaded
return;
}
const variantInfo = variantData[this.getVariantDataIndex(formIndex)];
// Do nothing if there is no variant information or the variant does not have color replacements
if (!variantInfo || variantInfo[variant] !== 1) {
return;
}
await populateVariantColorCache(
"pkmn__" + baseSpriteKey,
globalScene.experimentalSprites && hasExpSprite(spriteKey),
baseSpriteKey.replace("__", "/"),
);
}
async loadAssets(
female: boolean,
formIndex?: number,
@ -597,10 +640,13 @@ export abstract class PokemonSpeciesForm {
startLoad = false,
back = false,
): Promise<void> {
// We need to populate the color cache for this species' variant
const spriteKey = this.getSpriteKey(female, formIndex, shiny, variant, back);
globalScene.loadPokemonAtlas(spriteKey, this.getSpriteAtlasPath(female, formIndex, shiny, variant, back));
globalScene.load.audio(this.getCryKey(formIndex), `audio/${this.getCryKey(formIndex)}.m4a`);
if (!isNullOrUndefined(variant)) {
await this.loadVariantColors(spriteKey, female, variant, back, formIndex);
}
return new Promise<void>(resolve => {
globalScene.load.once(Phaser.Loader.Events.COMPLETE, () => {
const originalWarn = console.warn;

View File

@ -1,4 +1,4 @@
import { randIntRange } from "#app/utils";
import { randIntRange } from "#app/utils/common";
import { StatusEffect } from "#enums/status-effect";
import type { ParseKeys } from "i18next";
import i18next from "i18next";

View File

@ -59,7 +59,7 @@ export class Terrain {
// Cancels move if the move has positive priority and targets a Pokemon grounded on the Psychic Terrain
return (
move.getPriority(user) > 0 &&
user.getOpponents().some(o => targets.includes(o.getBattlerIndex()) && o.isGrounded())
user.getOpponents(true).some(o => targets.includes(o.getBattlerIndex()) && o.isGrounded())
);
}
}

View File

@ -1,5 +1,5 @@
import { TrainerType } from "#enums/trainer-type";
import { toReadableString } from "#app/utils";
import { toReadableString } from "#app/utils/common";
class TrainerNameConfig {
public urls: string[];

View File

@ -1,6 +1,8 @@
import { startingWave } from "#app/battle-scene";
import { startingWave } from "#app/starting-wave";
import { globalScene } from "#app/global-scene";
import { PartyMemberStrength } from "#enums/party-member-strength";
import { GameModes } from "#app/game-mode";
import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves";
export class TrainerPartyTemplate {
public size: number;
@ -222,19 +224,18 @@ export const trainerPartyTemplates = {
*/
export function getEvilGruntPartyTemplate(): TrainerPartyTemplate {
const waveIndex = globalScene.currentBattle?.waveIndex;
if (waveIndex < 40) {
return trainerPartyTemplates.TWO_AVG;
switch (waveIndex) {
case ClassicFixedBossWaves.EVIL_GRUNT_1:
return trainerPartyTemplates.TWO_AVG;
case ClassicFixedBossWaves.EVIL_GRUNT_2:
return trainerPartyTemplates.THREE_AVG;
case ClassicFixedBossWaves.EVIL_GRUNT_3:
return trainerPartyTemplates.TWO_AVG_ONE_STRONG;
case ClassicFixedBossWaves.EVIL_ADMIN_1:
return trainerPartyTemplates.GYM_LEADER_4; // 3avg 1 strong 1 stronger
default:
return trainerPartyTemplates.GYM_LEADER_5; // 3 avg 2 strong 1 stronger
}
if (waveIndex < 63) {
return trainerPartyTemplates.THREE_AVG;
}
if (waveIndex < 65) {
return trainerPartyTemplates.TWO_AVG_ONE_STRONG;
}
if (waveIndex < 112) {
return trainerPartyTemplates.GYM_LEADER_4; // 3avg 1 strong 1 stronger
}
return trainerPartyTemplates.GYM_LEADER_5; // 3 avg 2 strong 1 stronger
}
export function getWavePartyTemplate(...templates: TrainerPartyTemplate[]) {
@ -245,11 +246,36 @@ export function getWavePartyTemplate(...templates: TrainerPartyTemplate[]) {
}
export function getGymLeaderPartyTemplate() {
return getWavePartyTemplate(
trainerPartyTemplates.GYM_LEADER_1,
trainerPartyTemplates.GYM_LEADER_2,
trainerPartyTemplates.GYM_LEADER_3,
trainerPartyTemplates.GYM_LEADER_4,
trainerPartyTemplates.GYM_LEADER_5,
);
const { currentBattle, gameMode } = globalScene;
switch (gameMode.modeId) {
case GameModes.DAILY:
if (currentBattle?.waveIndex <= 20) {
return trainerPartyTemplates.GYM_LEADER_2
}
return trainerPartyTemplates.GYM_LEADER_3;
case GameModes.CHALLENGE: // In the future, there may be a ChallengeType to call here. For now, use classic's.
case GameModes.CLASSIC:
if (currentBattle?.waveIndex <= 20) {
return trainerPartyTemplates.GYM_LEADER_1; // 1 avg 1 strong
}
else if (currentBattle?.waveIndex <= 30) {
return trainerPartyTemplates.GYM_LEADER_2; // 1 avg 1 strong 1 stronger
}
else if (currentBattle?.waveIndex <= 60) { // 50 and 60
return trainerPartyTemplates.GYM_LEADER_3; // 2 avg 1 strong 1 stronger
}
else if (currentBattle?.waveIndex <= 90) { // 80 and 90
return trainerPartyTemplates.GYM_LEADER_4; // 3 avg 1 strong 1 stronger
}
// 110+
return trainerPartyTemplates.GYM_LEADER_5; // 3 avg 2 strong 1 stronger
default:
return getWavePartyTemplate(
trainerPartyTemplates.GYM_LEADER_1,
trainerPartyTemplates.GYM_LEADER_2,
trainerPartyTemplates.GYM_LEADER_3,
trainerPartyTemplates.GYM_LEADER_4,
trainerPartyTemplates.GYM_LEADER_5,
);
}
}

View File

@ -1,7 +1,7 @@
import { globalScene } from "#app/global-scene";
import { modifierTypes } from "#app/modifier/modifier-type";
import { PokemonMove } from "#app/field/pokemon";
import { toReadableString, isNullOrUndefined, randSeedItem, randSeedInt } from "#app/utils";
import { toReadableString, isNullOrUndefined, randSeedItem, randSeedInt } from "#app/utils/common";
import { pokemonEvolutions, pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import { tmSpecies } from "#app/data/balance/tms";
@ -116,7 +116,6 @@ export class TrainerConfig {
public modifierRewardFuncs: ModifierTypeFunc[] = [];
public partyTemplates: TrainerPartyTemplate[];
public partyTemplateFunc: PartyTemplateFunc;
public eventRewardFuncs: ModifierTypeFunc[] = [];
public partyMemberFuncs: PartyMemberFuncs = {};
public speciesPools: TrainerTierPools;
public speciesFilter: PokemonSpeciesFilter;
@ -517,16 +516,6 @@ export class TrainerConfig {
// return ret;
// }
/**
* Sets eventRewardFuncs to the active event rewards for the specified wave
* @param wave Associated with {@linkcode getFixedBattleEventRewards}
* @returns this
*/
setEventModifierRewardFuncs(wave: number): TrainerConfig {
this.eventRewardFuncs = timedEventManager.getFixedBattleEventRewards(wave).map(r => modifierTypes[r]);
return this;
}
setModifierRewardFuncs(...modifierTypeFuncs: (() => ModifierTypeFunc)[]): TrainerConfig {
this.modifierRewardFuncs = modifierTypeFuncs.map(func => () => {
const modifierTypeFunc = func();
@ -1329,7 +1318,16 @@ export const trainerConfigs: TrainerConfigs = {
[TrainerPoolTier.RARE]: [Species.BELLOSSOM, Species.HITMONTOP, Species.MIME_JR, Species.ORICORIO],
[TrainerPoolTier.SUPER_RARE]: [Species.QUAXLY, Species.JANGMO_O],
}),
[TrainerType.DEPOT_AGENT]: new TrainerConfig(++t).setMoneyMultiplier(1.45).setEncounterBgm(TrainerType.CLERK),
[TrainerType.DEPOT_AGENT]: new TrainerConfig(++t)
.setMoneyMultiplier(1.45)
.setEncounterBgm(TrainerType.CLERK)
.setPartyTemplates(
trainerPartyTemplates.TWO_AVG,
trainerPartyTemplates.THREE_WEAK,
trainerPartyTemplates.THREE_AVG,
trainerPartyTemplates.FOUR_WEAK,
)
.setSpeciesFilter(s => s.isOfType(PokemonType.GROUND)),
[TrainerType.DOCTOR]: new TrainerConfig(++t)
.setHasGenders("Nurse", "lass")
.setHasDouble("Medical Team")
@ -1380,7 +1378,6 @@ export const trainerConfigs: TrainerConfigs = {
Species.CHINCHOU,
Species.CORSOLA,
Species.WAILMER,
Species.BARBOACH,
Species.CLAMPERL,
Species.LUVDISC,
Species.MANTYKE,
@ -2582,252 +2579,252 @@ export const trainerConfigs: TrainerConfigs = {
),
[TrainerType.BROCK]: new TrainerConfig((t = TrainerType.BROCK))
.initForGymLeader(signatureSpecies["BROCK"], true, PokemonType.ROCK)
.initForGymLeader(signatureSpecies["BROCK"], true, PokemonType.ROCK, false, -1)
.setBattleBgm("battle_kanto_gym")
.setMixedBattleBgm("battle_kanto_gym"),
[TrainerType.MISTY]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["MISTY"], false, PokemonType.WATER)
.initForGymLeader(signatureSpecies["MISTY"], false, PokemonType.WATER, false, -1)
.setBattleBgm("battle_kanto_gym")
.setMixedBattleBgm("battle_kanto_gym"),
[TrainerType.LT_SURGE]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["LT_SURGE"], true, PokemonType.ELECTRIC)
.initForGymLeader(signatureSpecies["LT_SURGE"], true, PokemonType.ELECTRIC, false, -1)
.setBattleBgm("battle_kanto_gym")
.setMixedBattleBgm("battle_kanto_gym"),
[TrainerType.ERIKA]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["ERIKA"], false, PokemonType.GRASS)
.initForGymLeader(signatureSpecies["ERIKA"], false, PokemonType.GRASS, false, -1)
.setBattleBgm("battle_kanto_gym")
.setMixedBattleBgm("battle_kanto_gym"),
[TrainerType.JANINE]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["JANINE"], false, PokemonType.POISON)
.initForGymLeader(signatureSpecies["JANINE"], false, PokemonType.POISON, false, -1)
.setBattleBgm("battle_kanto_gym")
.setMixedBattleBgm("battle_kanto_gym"),
[TrainerType.SABRINA]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["SABRINA"], false, PokemonType.PSYCHIC)
.initForGymLeader(signatureSpecies["SABRINA"], false, PokemonType.PSYCHIC, false, -1)
.setBattleBgm("battle_kanto_gym")
.setMixedBattleBgm("battle_kanto_gym"),
[TrainerType.BLAINE]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["BLAINE"], true, PokemonType.FIRE)
.initForGymLeader(signatureSpecies["BLAINE"], true, PokemonType.FIRE, false, -1)
.setBattleBgm("battle_kanto_gym")
.setMixedBattleBgm("battle_kanto_gym"),
[TrainerType.GIOVANNI]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["GIOVANNI"], true, PokemonType.DARK)
.initForGymLeader(signatureSpecies["GIOVANNI"], true, PokemonType.GROUND, false, -2)
.setBattleBgm("battle_kanto_gym")
.setMixedBattleBgm("battle_kanto_gym"),
[TrainerType.FALKNER]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["FALKNER"], true, PokemonType.FLYING)
.initForGymLeader(signatureSpecies["FALKNER"], true, PokemonType.FLYING, false, -1)
.setBattleBgm("battle_johto_gym")
.setMixedBattleBgm("battle_johto_gym"),
[TrainerType.BUGSY]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["BUGSY"], true, PokemonType.BUG)
.initForGymLeader(signatureSpecies["BUGSY"], true, PokemonType.BUG, false, -1)
.setBattleBgm("battle_johto_gym")
.setMixedBattleBgm("battle_johto_gym"),
[TrainerType.WHITNEY]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["WHITNEY"], false, PokemonType.NORMAL)
.initForGymLeader(signatureSpecies["WHITNEY"], false, PokemonType.NORMAL, false, -1)
.setBattleBgm("battle_johto_gym")
.setMixedBattleBgm("battle_johto_gym"),
[TrainerType.MORTY]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["MORTY"], true, PokemonType.GHOST)
.initForGymLeader(signatureSpecies["MORTY"], true, PokemonType.GHOST, false, -1)
.setBattleBgm("battle_johto_gym")
.setMixedBattleBgm("battle_johto_gym"),
[TrainerType.CHUCK]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["CHUCK"], true, PokemonType.FIGHTING)
.initForGymLeader(signatureSpecies["CHUCK"], true, PokemonType.FIGHTING, false, -1)
.setBattleBgm("battle_johto_gym")
.setMixedBattleBgm("battle_johto_gym"),
[TrainerType.JASMINE]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["JASMINE"], false, PokemonType.STEEL)
.initForGymLeader(signatureSpecies["JASMINE"], false, PokemonType.STEEL, false, -1)
.setBattleBgm("battle_johto_gym")
.setMixedBattleBgm("battle_johto_gym"),
[TrainerType.PRYCE]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["PRYCE"], true, PokemonType.ICE)
.initForGymLeader(signatureSpecies["PRYCE"], true, PokemonType.ICE, false, -1)
.setBattleBgm("battle_johto_gym")
.setMixedBattleBgm("battle_johto_gym"),
[TrainerType.CLAIR]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["CLAIR"], false, PokemonType.DRAGON)
.initForGymLeader(signatureSpecies["CLAIR"], false, PokemonType.DRAGON, false, -3)
.setBattleBgm("battle_johto_gym")
.setMixedBattleBgm("battle_johto_gym"),
[TrainerType.ROXANNE]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["ROXANNE"], false, PokemonType.ROCK)
.initForGymLeader(signatureSpecies["ROXANNE"], false, PokemonType.ROCK, false, -1)
.setBattleBgm("battle_hoenn_gym")
.setMixedBattleBgm("battle_hoenn_gym"),
[TrainerType.BRAWLY]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["BRAWLY"], true, PokemonType.FIGHTING)
.initForGymLeader(signatureSpecies["BRAWLY"], true, PokemonType.FIGHTING, false, -1)
.setBattleBgm("battle_hoenn_gym")
.setMixedBattleBgm("battle_hoenn_gym"),
[TrainerType.WATTSON]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["WATTSON"], true, PokemonType.ELECTRIC)
.initForGymLeader(signatureSpecies["WATTSON"], true, PokemonType.ELECTRIC, false, -1)
.setBattleBgm("battle_hoenn_gym")
.setMixedBattleBgm("battle_hoenn_gym"),
[TrainerType.FLANNERY]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["FLANNERY"], false, PokemonType.FIRE)
.initForGymLeader(signatureSpecies["FLANNERY"], false, PokemonType.FIRE, false, -1)
.setBattleBgm("battle_hoenn_gym")
.setMixedBattleBgm("battle_hoenn_gym"),
[TrainerType.NORMAN]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["NORMAN"], true, PokemonType.NORMAL)
.initForGymLeader(signatureSpecies["NORMAN"], true, PokemonType.NORMAL, false, -1)
.setBattleBgm("battle_hoenn_gym")
.setMixedBattleBgm("battle_hoenn_gym"),
[TrainerType.WINONA]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["WINONA"], false, PokemonType.FLYING)
.initForGymLeader(signatureSpecies["WINONA"], false, PokemonType.FLYING, false, -1)
.setBattleBgm("battle_hoenn_gym")
.setMixedBattleBgm("battle_hoenn_gym"),
[TrainerType.TATE]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["TATE"], true, PokemonType.PSYCHIC)
.initForGymLeader(signatureSpecies["TATE"], true, PokemonType.PSYCHIC, false, -1)
.setBattleBgm("battle_hoenn_gym")
.setMixedBattleBgm("battle_hoenn_gym")
.setHasDouble("tate_liza_double")
.setDoubleTrainerType(TrainerType.LIZA)
.setDoubleTitle("gym_leader_double"),
[TrainerType.LIZA]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["LIZA"], false, PokemonType.PSYCHIC)
.initForGymLeader(signatureSpecies["LIZA"], false, PokemonType.PSYCHIC, false, -1)
.setBattleBgm("battle_hoenn_gym")
.setMixedBattleBgm("battle_hoenn_gym")
.setHasDouble("liza_tate_double")
.setDoubleTrainerType(TrainerType.TATE)
.setDoubleTitle("gym_leader_double"),
[TrainerType.JUAN]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["JUAN"], true, PokemonType.WATER)
.initForGymLeader(signatureSpecies["JUAN"], true, PokemonType.WATER, false, -1)
.setBattleBgm("battle_hoenn_gym")
.setMixedBattleBgm("battle_hoenn_gym"),
[TrainerType.ROARK]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["ROARK"], true, PokemonType.ROCK)
.initForGymLeader(signatureSpecies["ROARK"], true, PokemonType.ROCK, false, -1)
.setBattleBgm("battle_sinnoh_gym")
.setMixedBattleBgm("battle_sinnoh_gym"),
[TrainerType.GARDENIA]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["GARDENIA"], false, PokemonType.GRASS)
.initForGymLeader(signatureSpecies["GARDENIA"], false, PokemonType.GRASS, false, -1)
.setBattleBgm("battle_sinnoh_gym")
.setMixedBattleBgm("battle_sinnoh_gym"),
[TrainerType.MAYLENE]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["MAYLENE"], false, PokemonType.FIGHTING)
.initForGymLeader(signatureSpecies["MAYLENE"], false, PokemonType.FIGHTING, false, -1)
.setBattleBgm("battle_sinnoh_gym")
.setMixedBattleBgm("battle_sinnoh_gym"),
[TrainerType.CRASHER_WAKE]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["CRASHER_WAKE"], true, PokemonType.WATER)
.initForGymLeader(signatureSpecies["CRASHER_WAKE"], true, PokemonType.WATER, false, -1)
.setBattleBgm("battle_sinnoh_gym")
.setMixedBattleBgm("battle_sinnoh_gym"),
[TrainerType.FANTINA]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["FANTINA"], false, PokemonType.GHOST)
.initForGymLeader(signatureSpecies["FANTINA"], false, PokemonType.GHOST, false, -1)
.setBattleBgm("battle_sinnoh_gym")
.setMixedBattleBgm("battle_sinnoh_gym"),
[TrainerType.BYRON]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["BYRON"], true, PokemonType.STEEL)
.initForGymLeader(signatureSpecies["BYRON"], true, PokemonType.STEEL, false, -1)
.setBattleBgm("battle_sinnoh_gym")
.setMixedBattleBgm("battle_sinnoh_gym"),
[TrainerType.CANDICE]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["CANDICE"], false, PokemonType.ICE)
.initForGymLeader(signatureSpecies["CANDICE"], false, PokemonType.ICE, false, -1)
.setBattleBgm("battle_sinnoh_gym")
.setMixedBattleBgm("battle_sinnoh_gym"),
[TrainerType.VOLKNER]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["VOLKNER"], true, PokemonType.ELECTRIC)
.initForGymLeader(signatureSpecies["VOLKNER"], true, PokemonType.ELECTRIC, false, -1)
.setBattleBgm("battle_sinnoh_gym")
.setMixedBattleBgm("battle_sinnoh_gym"),
[TrainerType.CILAN]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["CILAN"], true, PokemonType.GRASS)
.initForGymLeader(signatureSpecies["CILAN"], true, PokemonType.GRASS, false, -1)
.setMixedBattleBgm("battle_unova_gym"),
[TrainerType.CHILI]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["CHILI"], true, PokemonType.FIRE)
.initForGymLeader(signatureSpecies["CHILI"], true, PokemonType.FIRE, false, -1)
.setMixedBattleBgm("battle_unova_gym"),
[TrainerType.CRESS]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["CRESS"], true, PokemonType.WATER)
.initForGymLeader(signatureSpecies["CRESS"], true, PokemonType.WATER, false, -1)
.setMixedBattleBgm("battle_unova_gym"),
[TrainerType.CHEREN]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["CHEREN"], true, PokemonType.NORMAL)
.initForGymLeader(signatureSpecies["CHEREN"], true, PokemonType.NORMAL, false, -1)
.setMixedBattleBgm("battle_unova_gym"),
[TrainerType.LENORA]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["LENORA"], false, PokemonType.NORMAL)
.initForGymLeader(signatureSpecies["LENORA"], false, PokemonType.NORMAL, false, -1)
.setMixedBattleBgm("battle_unova_gym"),
[TrainerType.ROXIE]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["ROXIE"], false, PokemonType.POISON)
.initForGymLeader(signatureSpecies["ROXIE"], false, PokemonType.POISON, false, -1)
.setMixedBattleBgm("battle_unova_gym"),
[TrainerType.BURGH]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["BURGH"], true, PokemonType.BUG)
.initForGymLeader(signatureSpecies["BURGH"], true, PokemonType.BUG, false, -1)
.setMixedBattleBgm("battle_unova_gym"),
[TrainerType.ELESA]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["ELESA"], false, PokemonType.ELECTRIC)
.initForGymLeader(signatureSpecies["ELESA"], false, PokemonType.ELECTRIC, false, -1)
.setMixedBattleBgm("battle_unova_gym"),
[TrainerType.CLAY]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["CLAY"], true, PokemonType.GROUND)
.initForGymLeader(signatureSpecies["CLAY"], true, PokemonType.GROUND, false, -1)
.setMixedBattleBgm("battle_unova_gym"),
[TrainerType.SKYLA]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["SKYLA"], false, PokemonType.FLYING)
.initForGymLeader(signatureSpecies["SKYLA"], false, PokemonType.FLYING, false, -1)
.setMixedBattleBgm("battle_unova_gym"),
[TrainerType.BRYCEN]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["BRYCEN"], true, PokemonType.ICE)
.initForGymLeader(signatureSpecies["BRYCEN"], true, PokemonType.ICE, false, -1)
.setMixedBattleBgm("battle_unova_gym"),
[TrainerType.DRAYDEN]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["DRAYDEN"], true, PokemonType.DRAGON)
.initForGymLeader(signatureSpecies["DRAYDEN"], true, PokemonType.DRAGON, false, -1)
.setMixedBattleBgm("battle_unova_gym"),
[TrainerType.MARLON]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["MARLON"], true, PokemonType.WATER)
.initForGymLeader(signatureSpecies["MARLON"], true, PokemonType.WATER, false, -1)
.setMixedBattleBgm("battle_unova_gym"),
[TrainerType.VIOLA]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["VIOLA"], false, PokemonType.BUG)
.initForGymLeader(signatureSpecies["VIOLA"], false, PokemonType.BUG, false, -1)
.setMixedBattleBgm("battle_kalos_gym"),
[TrainerType.GRANT]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["GRANT"], true, PokemonType.ROCK)
.initForGymLeader(signatureSpecies["GRANT"], true, PokemonType.ROCK, false, -1)
.setMixedBattleBgm("battle_kalos_gym"),
[TrainerType.KORRINA]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["KORRINA"], false, PokemonType.FIGHTING)
.initForGymLeader(signatureSpecies["KORRINA"], false, PokemonType.FIGHTING, false, -1)
.setMixedBattleBgm("battle_kalos_gym"),
[TrainerType.RAMOS]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["RAMOS"], true, PokemonType.GRASS)
.initForGymLeader(signatureSpecies["RAMOS"], true, PokemonType.GRASS, false, -1)
.setMixedBattleBgm("battle_kalos_gym"),
[TrainerType.CLEMONT]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["CLEMONT"], true, PokemonType.ELECTRIC)
.initForGymLeader(signatureSpecies["CLEMONT"], true, PokemonType.ELECTRIC, false, -1)
.setMixedBattleBgm("battle_kalos_gym"),
[TrainerType.VALERIE]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["VALERIE"], false, PokemonType.FAIRY)
.initForGymLeader(signatureSpecies["VALERIE"], false, PokemonType.FAIRY, false, -1)
.setMixedBattleBgm("battle_kalos_gym"),
[TrainerType.OLYMPIA]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["OLYMPIA"], false, PokemonType.PSYCHIC)
.initForGymLeader(signatureSpecies["OLYMPIA"], false, PokemonType.PSYCHIC, false, -1)
.setMixedBattleBgm("battle_kalos_gym"),
[TrainerType.WULFRIC]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["WULFRIC"], true, PokemonType.ICE)
.initForGymLeader(signatureSpecies["WULFRIC"], true, PokemonType.ICE, false, -1)
.setMixedBattleBgm("battle_kalos_gym"),
[TrainerType.MILO]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["MILO"], true, PokemonType.GRASS)
.initForGymLeader(signatureSpecies["MILO"], true, PokemonType.GRASS, false, -1)
.setMixedBattleBgm("battle_galar_gym"),
[TrainerType.NESSA]: new TrainerConfig(++t)
.setName("Nessa")
.initForGymLeader(signatureSpecies["NESSA"], false, PokemonType.WATER)
.initForGymLeader(signatureSpecies["NESSA"], false, PokemonType.WATER, false, -1)
.setMixedBattleBgm("battle_galar_gym"),
[TrainerType.KABU]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["KABU"], true, PokemonType.FIRE)
.initForGymLeader(signatureSpecies["KABU"], true, PokemonType.FIRE, false, -1)
.setMixedBattleBgm("battle_galar_gym"),
[TrainerType.BEA]: new TrainerConfig(++t)
.setName("Bea")
.initForGymLeader(signatureSpecies["BEA"], false, PokemonType.FIGHTING)
.initForGymLeader(signatureSpecies["BEA"], false, PokemonType.FIGHTING, false, -1)
.setMixedBattleBgm("battle_galar_gym"),
[TrainerType.ALLISTER]: new TrainerConfig(++t)
.setName("Allister")
.initForGymLeader(signatureSpecies["ALLISTER"], true, PokemonType.GHOST)
.initForGymLeader(signatureSpecies["ALLISTER"], true, PokemonType.GHOST, false, -1)
.setMixedBattleBgm("battle_galar_gym"),
[TrainerType.OPAL]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["OPAL"], false, PokemonType.FAIRY)
.initForGymLeader(signatureSpecies["OPAL"], false, PokemonType.FAIRY, false, -1)
.setMixedBattleBgm("battle_galar_gym"),
[TrainerType.BEDE]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["BEDE"], true, PokemonType.FAIRY)
.initForGymLeader(signatureSpecies["BEDE"], true, PokemonType.FAIRY, false, -1)
.setMixedBattleBgm("battle_galar_gym"),
[TrainerType.GORDIE]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["GORDIE"], true, PokemonType.ROCK)
.initForGymLeader(signatureSpecies["GORDIE"], true, PokemonType.ROCK, false, -1)
.setMixedBattleBgm("battle_galar_gym"),
[TrainerType.MELONY]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["MELONY"], false, PokemonType.ICE)
.initForGymLeader(signatureSpecies["MELONY"], false, PokemonType.ICE, false, -1)
.setMixedBattleBgm("battle_galar_gym"),
[TrainerType.PIERS]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["PIERS"], true, PokemonType.DARK)
.initForGymLeader(signatureSpecies["PIERS"], true, PokemonType.DARK, false, -3)
.setHasDouble("piers_marnie_double")
.setDoubleTrainerType(TrainerType.MARNIE)
.setDoubleTitle("gym_leader_double")
.setMixedBattleBgm("battle_galar_gym"),
[TrainerType.MARNIE]: new TrainerConfig(++t)
.setName("Marnie")
.initForGymLeader(signatureSpecies["MARNIE"], false, PokemonType.DARK)
.initForGymLeader(signatureSpecies["MARNIE"], false, PokemonType.DARK, false, -4)
.setHasDouble("marnie_piers_double")
.setDoubleTrainerType(TrainerType.PIERS)
.setDoubleTitle("gym_leader_double")
.setMixedBattleBgm("battle_galar_gym"),
[TrainerType.RAIHAN]: new TrainerConfig(++t)
.setName("Raihan")
.initForGymLeader(signatureSpecies["RAIHAN"], true, PokemonType.DRAGON)
.initForGymLeader(signatureSpecies["RAIHAN"], true, PokemonType.DRAGON, false, -1)
.setMixedBattleBgm("battle_galar_gym"),
[TrainerType.KATY]: new TrainerConfig(++t)
.initForGymLeader(signatureSpecies["KATY"], false, PokemonType.BUG, true, -1)
@ -3692,7 +3689,6 @@ export const trainerConfigs: TrainerConfigs = {
() => modifierTypes.SUPER_EXP_CHARM,
() => modifierTypes.EXP_SHARE,
)
.setEventModifierRewardFuncs(8)
.setPartyMemberFunc(
0,
getRandomPartyMemberFunc(
@ -3760,7 +3756,6 @@ export const trainerConfigs: TrainerConfigs = {
.setMixedBattleBgm("battle_rival")
.setPartyTemplates(trainerPartyTemplates.RIVAL_2)
.setModifierRewardFuncs(() => modifierTypes.EXP_SHARE)
.setEventModifierRewardFuncs(25)
.setPartyMemberFunc(
0,
getRandomPartyMemberFunc(

View File

@ -5,8 +5,8 @@ import type Pokemon from "../field/pokemon";
import { PokemonType } from "#enums/pokemon-type";
import type Move from "./moves/move";
import { AttackMove } from "./moves/move";
import { randSeedInt } from "#app/utils";
import { SuppressWeatherEffectAbAttr } from "./ability";
import { randSeedInt } from "#app/utils/common";
import { SuppressWeatherEffectAbAttr } from "./abilities/ability";
import { TerrainType, getTerrainName } from "./terrain";
import i18next from "i18next";
import { globalScene } from "#app/global-scene";

View File

@ -1,7 +1,6 @@
export enum MoveEffectTrigger {
PRE_APPLY,
POST_APPLY,
HIT,
/** Triggers one time after all target effects have applied */
POST_TARGET
}

6
src/enums/battle-type.ts Normal file
View File

@ -0,0 +1,6 @@
export enum BattleType {
WILD,
TRAINER,
CLEAR,
MYSTERY_ENCOUNTER
}

View File

@ -0,0 +1,22 @@
export enum ClassicFixedBossWaves {
TOWN_YOUNGSTER = 5,
RIVAL_1 = 8,
RIVAL_2 = 25,
EVIL_GRUNT_1 = 35,
RIVAL_3 = 55,
EVIL_GRUNT_2 = 62,
EVIL_GRUNT_3 = 64,
EVIL_ADMIN_1 = 66,
RIVAL_4 = 95,
EVIL_GRUNT_4 = 112,
EVIL_ADMIN_2 = 114,
EVIL_BOSS_1 = 115,
RIVAL_5 = 145,
EVIL_BOSS_2 = 165,
ELITE_FOUR_1 = 182,
ELITE_FOUR_2 = 184,
ELITE_FOUR_3 = 186,
ELITE_FOUR_4 = 188,
CHAMPION = 190,
RIVAL_6 = 195
}

View File

@ -0,0 +1,23 @@
/** The result of a hit check calculation */
export const HitCheckResult = {
/** Hit checks haven't been evaluated yet in this pass */
PENDING: 0,
/** The move hits the target successfully */
HIT: 1,
/** The move has no effect on the target */
NO_EFFECT: 2,
/** The move has no effect on the target, but doesn't proc the default "no effect" message */
NO_EFFECT_NO_MESSAGE: 3,
/** The target protected itself against the move */
PROTECTED: 4,
/** The move missed the target */
MISS: 5,
/** The move is reflected by magic coat or magic bounce */
REFLECTED: 6,
/** The target is no longer on the field */
TARGET_NOT_ON_FIELD: 7,
/** The move failed unexpectedly */
ERROR: 8,
} as const;
export type HitCheckResult = typeof HitCheckResult[keyof typeof HitCheckResult];

47
src/enums/ui-mode.ts Normal file
View File

@ -0,0 +1,47 @@
export enum UiMode {
MESSAGE,
TITLE,
COMMAND,
FIGHT,
BALL,
TARGET_SELECT,
MODIFIER_SELECT,
SAVE_SLOT,
PARTY,
SUMMARY,
STARTER_SELECT,
EVOLUTION_SCENE,
EGG_HATCH_SCENE,
EGG_HATCH_SUMMARY,
CONFIRM,
OPTION_SELECT,
MENU,
MENU_OPTION_SELECT,
SETTINGS,
SETTINGS_DISPLAY,
SETTINGS_AUDIO,
SETTINGS_GAMEPAD,
GAMEPAD_BINDING,
SETTINGS_KEYBOARD,
KEYBOARD_BINDING,
ACHIEVEMENTS,
GAME_STATS,
EGG_LIST,
EGG_GACHA,
POKEDEX,
POKEDEX_SCAN,
POKEDEX_PAGE,
LOGIN_FORM,
REGISTRATION_FORM,
LOADING,
SESSION_RELOAD,
UNAVAILABLE,
CHALLENGE_SELECT,
RENAME_POKEMON,
RUN_HISTORY,
RUN_INFO,
TEST_DIALOGUE,
AUTO_COMPLETE,
ADMIN,
MYSTERY_ENCOUNTER
}

View File

@ -1,7 +1,7 @@
import { globalScene } from "#app/global-scene";
import { PokeballType } from "#enums/pokeball";
import type { Variant } from "#app/sprites/variant";
import { getFrameMs, randGauss } from "#app/utils";
import { getFrameMs, randGauss } from "#app/utils/common";
export function addPokeballOpenParticles(x: number, y: number, pokeballType: PokeballType): void {
switch (pokeballType) {

View File

@ -1,7 +1,7 @@
import { globalScene } from "#app/global-scene";
import type { BiomeTierTrainerPools, PokemonPools } from "#app/data/balance/biomes";
import { biomePokemonPools, BiomePoolTier, biomeTrainerPools } from "#app/data/balance/biomes";
import { randSeedInt, NumberHolder, isNullOrUndefined, type Constructor } from "#app/utils";
import { randSeedInt, NumberHolder, isNullOrUndefined, type Constructor } from "#app/utils/common";
import type PokemonSpecies from "#app/data/pokemon-species";
import { getPokemonSpecies } from "#app/data/pokemon-species";
import {
@ -26,7 +26,7 @@ import {
PostTerrainChangeAbAttr,
PostWeatherChangeAbAttr,
TerrainEventTypeChangeAbAttr,
} from "#app/data/ability";
} from "#app/data/abilities/ability";
import type Pokemon from "#app/field/pokemon";
import Overrides from "#app/overrides";
import { TagAddedEvent, TagRemovedEvent, TerrainChangedEvent, WeatherChangedEvent } from "#app/events/arena";

View File

@ -2,7 +2,7 @@ import { TextStyle, addTextObject } from "../ui/text";
import type { DamageResult } from "./pokemon";
import type Pokemon from "./pokemon";
import { HitResult } from "./pokemon";
import { formatStat, fixedInt } from "#app/utils";
import { formatStat, fixedInt } from "#app/utils/common";
import type { BattlerIndex } from "../battle";
import { globalScene } from "#app/global-scene";

View File

@ -2,7 +2,7 @@ import type { GameObjects } from "phaser";
import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
import type { Species } from "#enums/species";
import { isNullOrUndefined } from "#app/utils";
import { isNullOrUndefined } from "#app/utils/common";
import { getSpriteKeysFromSpecies } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import type { Variant } from "#app/sprites/variant";
import { doShinySparkleAnim } from "#app/field/anims";

View File

@ -1,6 +1,6 @@
import { globalScene } from "#app/global-scene";
import Pokemon from "./pokemon";
import { fixedInt, randInt } from "#app/utils";
import { fixedInt, randInt } from "#app/utils/common";
export default class PokemonSpriteSparkleHandler {
private sprites: Set<Phaser.GameObjects.Sprite>;

File diff suppressed because it is too large Load Diff

View File

@ -11,7 +11,7 @@ import { TrainerSlot } from "#enums/trainer-slot";
import { TrainerPoolTier } from "#enums/trainer-pool-tier";
import { TeraAIMode } from "#enums/tera-ai-mode";
import type { EnemyPokemon } from "#app/field/pokemon";
import { randSeedWeightedItem, randSeedItem, randSeedInt } from "#app/utils";
import { randSeedWeightedItem, randSeedItem, randSeedInt } from "#app/utils/common";
import type { PersistentModifier } from "#app/modifier/modifier";
import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag";
import { getIsInitialized, initI18n } from "#app/plugins/i18n";
@ -223,6 +223,13 @@ export default class Trainer extends Phaser.GameObjects.Container {
return this.config.doubleOnly || this.variant === TrainerVariant.DOUBLE;
}
/**
* Return whether the trainer is a duo, like Tate & Liza
*/
isPartner(): boolean {
return this.variant === TrainerVariant.DOUBLE;
}
getMixedBattleBgm(): string {
return this.config.mixedBattleBgm;
}

View File

@ -7,12 +7,13 @@ import type PokemonSpecies from "./data/pokemon-species";
import { allSpecies } from "./data/pokemon-species";
import type { Arena } from "./field/arena";
import Overrides from "#app/overrides";
import { randSeedInt, randSeedItem } from "#app/utils";
import { randSeedInt, randSeedItem } from "#app/utils/common";
import { Biome } from "#enums/biome";
import { Species } from "#enums/species";
import { Challenges } from "./enums/challenges";
import { globalScene } from "#app/global-scene";
import { getDailyStartingBiome } from "./data/daily-run";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES, CHALLENGE_MODE_MYSTERY_ENCOUNTER_WAVES } from "./constants";
export enum GameModes {
CLASSIC,
@ -36,10 +37,6 @@ interface GameModeConfig {
hasMysteryEncounters?: boolean;
}
// Describes min and max waves for MEs in specific game modes
export const CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES: [number, number] = [10, 180];
export const CHALLENGE_MODE_MYSTERY_ENCOUNTER_WAVES: [number, number] = [10, 180];
export class GameMode implements GameModeConfig {
public modeId: GameModes;
public isClassic: boolean;

View File

@ -0,0 +1 @@
export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1";

View File

@ -0,0 +1,4 @@
export const starterColors: StarterColors = {};
interface StarterColors {
[key: string]: [string, string];
}

View File

@ -1,11 +1,11 @@
import Phaser from "phaser";
import { deepCopy, getEnumValues } from "#app/utils";
import { deepCopy, getEnumValues } from "#app/utils/common";
import pad_generic from "./configs/inputs/pad_generic";
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 { Mode } from "./ui/ui";
import { UiMode } from "#enums/ui-mode";
import type SettingsGamepadUiHandler from "./ui/settings/settings-gamepad-ui-handler";
import type SettingsKeyboardUiHandler from "./ui/settings/settings-keyboard-ui-handler";
import cfg_keyboard_qwerty from "./configs/inputs/cfg_keyboard_qwerty";
@ -235,7 +235,7 @@ export class InputsController {
if (gamepadName) {
this.selectedDevice[Device.GAMEPAD] = gamepadName.toLowerCase();
}
const handler = globalScene.ui?.handlers[Mode.SETTINGS_GAMEPAD] as SettingsGamepadUiHandler;
const handler = globalScene.ui?.handlers[UiMode.SETTINGS_GAMEPAD] as SettingsGamepadUiHandler;
handler?.updateChosenGamepadDisplay();
}
@ -248,7 +248,7 @@ export class InputsController {
if (layoutKeyboard) {
this.selectedDevice[Device.KEYBOARD] = layoutKeyboard.toLowerCase();
}
const handler = globalScene.ui?.handlers[Mode.SETTINGS_KEYBOARD] as SettingsKeyboardUiHandler;
const handler = globalScene.ui?.handlers[UiMode.SETTINGS_KEYBOARD] as SettingsKeyboardUiHandler;
handler?.updateChosenKeyboardDisplay();
}
@ -296,7 +296,7 @@ export class InputsController {
globalScene.gameData?.saveMappingConfigs(gamepadID, this.configs[gamepadID]);
}
this.lastSource = "gamepad";
const handler = globalScene.ui?.handlers[Mode.SETTINGS_GAMEPAD] as SettingsGamepadUiHandler;
const handler = globalScene.ui?.handlers[UiMode.SETTINGS_GAMEPAD] as SettingsGamepadUiHandler;
handler?.updateChosenGamepadDisplay();
}
@ -406,7 +406,7 @@ export class InputsController {
this.lastSource = "gamepad";
if (
!this.selectedDevice[Device.GAMEPAD] ||
(globalScene.ui.getMode() !== Mode.GAMEPAD_BINDING &&
(globalScene.ui.getMode() !== UiMode.GAMEPAD_BINDING &&
this.selectedDevice[Device.GAMEPAD] !== pad.id.toLowerCase())
) {
this.setChosenGamepad(pad.id);

View File

@ -4,14 +4,14 @@ import CacheBustedLoaderPlugin from "#app/plugins/cache-busted-loader-plugin";
import { SceneBase } from "#app/scene-base";
import { WindowVariant, getWindowVariantSuffix } from "#app/ui/ui-theme";
import { isMobile } from "#app/touch-controls";
import { localPing, getEnumValues, hasAllLocalizedSprites, getEnumKeys } from "#app/utils";
import { localPing, getEnumValues, hasAllLocalizedSprites, getEnumKeys } from "#app/utils/common";
import { initPokemonPrevolutions, initPokemonStarters } from "#app/data/balance/pokemon-evolutions";
import { initBiomes } from "#app/data/balance/biomes";
import { initEggMoves } from "#app/data/balance/egg-moves";
import { initPokemonForms } from "#app/data/pokemon-forms";
import { initSpecies } from "#app/data/pokemon-species";
import { initMoves } from "#app/data/moves/move";
import { initAbilities } from "#app/data/ability";
import { initAbilities } from "#app/data/abilities/ability";
import { initAchievements } from "#app/system/achv";
import { initTrainerTypeDialogue } from "#app/data/dialogue";
import { initChallenges } from "#app/data/challenge";

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