Merge remote-tracking branch 'upstream/beta' into no-non-type-export

This commit is contained in:
Bertie690 2025-06-09 22:20:16 -04:00
commit a113c71791
313 changed files with 2679 additions and 2201 deletions

View File

@ -1,172 +0,0 @@
/**
* This script creates a test boilerplate file in the appropriate
* directory based on the type selected.
* @example npm run create-test
*/
import fs from "fs";
import inquirer from "inquirer";
import path from "path";
import { fileURLToPath } from "url";
// Get the directory name of the current module file
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const typeChoices = ["Move", "Ability", "Item", "Mystery Encounter"];
/**
* Prompts the user to select a type via list.
* @returns {Promise<{selectedOption: string}>} the selected type
*/
async function promptTestType() {
const typeAnswer = await inquirer.prompt([
{
type: "list",
name: "selectedOption",
message: "What type of test would you like to create:",
choices: [...typeChoices, "EXIT"],
},
]);
if (typeAnswer.selectedOption === "EXIT") {
console.log("Exiting...");
return process.exit();
}
if (!typeChoices.includes(typeAnswer.selectedOption)) {
console.error(`Please provide a valid type (${typeChoices.join(", ")})!`);
return await promptTestType();
}
return typeAnswer;
}
/**
* Prompts the user to provide a file name.
* @param {string} selectedType
* @returns {Promise<{userInput: string}>} the selected file name
*/
async function promptFileName(selectedType) {
const fileNameAnswer = await inquirer.prompt([
{
type: "input",
name: "userInput",
message: `Please provide the name of the ${selectedType}:`,
},
]);
if (!fileNameAnswer.userInput || fileNameAnswer.userInput.trim().length === 0) {
console.error("Please provide a valid file name!");
return await promptFileName(selectedType);
}
return fileNameAnswer;
}
/**
* Runs the interactive create-test "CLI"
* @returns {Promise<void>}
*/
async function runInteractive() {
const typeAnswer = await promptTestType();
const fileNameAnswer = await promptFileName(typeAnswer.selectedOption);
const type = typeAnswer.selectedOption.toLowerCase();
// Convert fileName from kebab-case or camelCase to snake_case
const fileName = fileNameAnswer.userInput
.replace(/-+/g, "_") // Convert kebab-case (dashes) to underscores
.replace(/([a-z])([A-Z])/g, "$1_$2") // Convert camelCase to snake_case
.replace(/\s+/g, "_") // Replace spaces with underscores
.toLowerCase(); // Ensure all lowercase
// Format the description for the test case
const formattedName = fileName.replace(/_/g, " ").replace(/\b\w/g, char => char.toUpperCase());
// Determine the directory based on the type
let dir;
let description;
switch (type) {
case "move":
dir = path.join(__dirname, "test", "moves");
description = `Moves - ${formattedName}`;
break;
case "ability":
dir = path.join(__dirname, "test", "abilities");
description = `Abilities - ${formattedName}`;
break;
case "item":
dir = path.join(__dirname, "test", "items");
description = `Items - ${formattedName}`;
break;
case "mystery encounter":
dir = path.join(__dirname, "test", "mystery-encounter", "encounters");
description = `Mystery Encounter - ${formattedName}`;
break;
default:
console.error(`Invalid type. Please use one of the following: ${typeChoices.join(", ")}.`);
process.exit(1);
}
// Define the content template
const content = `import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import GameManager from "#test/testUtils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
describe("${description}", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
game.override
.moveset([ Moves.SPLASH ])
.ability(Abilities.BALL_FETCH)
.battleType("single")
.disableCrits()
.enemySpecies(Species.MAGIKARP)
.enemyAbility(Abilities.BALL_FETCH)
.enemyMoveset(Moves.SPLASH);
});
it("should do X", async () => {
await game.classicMode.startBattle([ Species.FEEBAS ]);
game.move.select(Moves.SPLASH);
await game.phaseInterceptor.to("BerryPhase");
expect(true).toBe(true);
});
});
`;
// Ensure the directory exists
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
// Create the file with the given name
const filePath = path.join(dir, `${fileName}.test.ts`);
if (fs.existsSync(filePath)) {
console.error(`File "${fileName}.test.ts" already exists.`);
process.exit(1);
}
// Write the template content to the file
fs.writeFileSync(filePath, content, "utf8");
console.log(`File created at: ${filePath}`);
}
runInteractive();

View File

@ -13,6 +13,7 @@
"test:cov": "vitest run --coverage --no-isolate", "test:cov": "vitest run --coverage --no-isolate",
"test:watch": "vitest watch --coverage --no-isolate", "test:watch": "vitest watch --coverage --no-isolate",
"test:silent": "vitest run --silent --no-isolate", "test:silent": "vitest run --silent --no-isolate",
"test:create": "node scripts/create-test/create-test.js",
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",
"eslint": "eslint --fix .", "eslint": "eslint --fix .",
"eslint-ci": "eslint .", "eslint-ci": "eslint .",
@ -21,7 +22,6 @@
"docs": "typedoc", "docs": "typedoc",
"depcruise": "depcruise src", "depcruise": "depcruise src",
"depcruise:graph": "depcruise src --output-type dot | node dependency-graph.js > dependency-graph.svg", "depcruise:graph": "depcruise src --output-type dot | node dependency-graph.js > dependency-graph.svg",
"create-test": "node ./create-test-boilerplate.js",
"postinstall": "npx lefthook install && npx lefthook run post-merge", "postinstall": "npx lefthook install && npx lefthook run post-merge",
"update-version:patch": "npm version patch --force --no-git-tag-version", "update-version:patch": "npm version patch --force --no-git-tag-version",
"update-version:minor": "npm version minor --force --no-git-tag-version", "update-version:minor": "npm version minor --force --no-git-tag-version",

View File

@ -0,0 +1,147 @@
/**
* This script creates a test boilerplate file in the appropriate
* directory based on the type selected.
* @example npm run test:create
*/
import chalk from "chalk";
import inquirer from "inquirer";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
//#region Constants
const version = "2.0.1";
// Get the directory name of the current module file
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const projectRoot = path.join(__dirname, "..", "..");
const boilerplateFilePath = path.join(__dirname, "test-boilerplate.ts");
const choices = [
{ label: "Move", dir: "moves" },
{ label: "Ability", dir: "abilities" },
{ label: "Item", dir: "items" },
{ label: "Mystery Encounter", dir: "mystery-encounter/encounters" },
{ label: "Utils", dir: "utils" },
{ label: "UI", dir: "ui" },
];
//#endregion
//#region Functions
/**
* Get the path to a given folder in the test directory
* @param {...string} folders the subfolders to append to the base path
* @returns {string} the path to the requested folder
*/
function getTestFolderPath(...folders) {
return path.join(projectRoot, "test", ...folders);
}
/**
* Prompts the user to select a type via list.
* @returns {Promise<{selectedOption: {label: string, dir: string}}>} the selected type
*/
async function promptTestType() {
const typeAnswer = await inquirer.prompt([
{
type: "list",
name: "selectedOption",
message: "What type of test would you like to create:",
choices: [...choices.map(choice => ({ name: choice.label, value: choice })), "EXIT"],
},
]);
if (typeAnswer.selectedOption === "EXIT") {
console.log("Exiting...");
return process.exit();
}
if (!choices.some(choice => choice.dir === typeAnswer.selectedOption.dir)) {
console.error(`Please provide a valid type: (${choices.map(choice => choice.label).join(", ")})!`);
return await promptTestType();
}
return typeAnswer;
}
/**
* Prompts the user to provide a file name.
* @param {string} selectedType
* @returns {Promise<{userInput: string}>} the selected file name
*/
async function promptFileName(selectedType) {
/** @type {{userInput: string}} */
const fileNameAnswer = await inquirer.prompt([
{
type: "input",
name: "userInput",
message: `Please provide the name of the ${selectedType}:`,
},
]);
if (!fileNameAnswer.userInput || fileNameAnswer.userInput.trim().length === 0) {
console.error("Please provide a valid file name!");
return await promptFileName(selectedType);
}
return fileNameAnswer;
}
/**
* Runs the interactive test:create "CLI"
* @returns {Promise<void>}
*/
async function runInteractive() {
console.group(chalk.grey(`Create Test - v${version}\n`));
try {
const typeAnswer = await promptTestType();
const fileNameAnswer = await promptFileName(typeAnswer.selectedOption.label);
const type = typeAnswer.selectedOption;
// Convert fileName from snake_case or camelCase to kebab-case
const fileName = fileNameAnswer.userInput
.replace(/_+/g, "-") // Convert snake_case (underscore) to kebab-case (dashes)
.replace(/([a-z])([A-Z])/g, "$1-$2") // Convert camelCase to kebab-case
.replace(/\s+/g, "-") // Replace spaces with dashes
.toLowerCase(); // Ensure all lowercase
// Format the description for the test case
const formattedName = fileName.replace(/-/g, " ").replace(/\b\w/g, char => char.toUpperCase());
// Determine the directory based on the type
const dir = getTestFolderPath(type.dir);
const description = `${type.label} - ${formattedName}`;
// Define the content template
const content = fs.readFileSync(boilerplateFilePath, "utf8").replace("{{description}}", description);
// Ensure the directory exists
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
// Create the file with the given name
const filePath = path.join(dir, `${fileName}.test.ts`);
if (fs.existsSync(filePath)) {
console.error(chalk.red.bold(`\n✗ File "${fileName}.test.ts" already exists!\n`));
process.exit(1);
}
// Write the template content to the file
fs.writeFileSync(filePath, content, "utf8");
console.log(chalk.green.bold(`\n✔ File created at: test/${type.dir}/${fileName}.test.ts\n`));
console.groupEnd();
} catch (err) {
console.error(chalk.red("✗ Error: ", err.message));
}
}
//#endregion
//#region Run
runInteractive();
//#endregion

View File

@ -0,0 +1,43 @@
import { AbilityId } from "#enums/ability-id";
import { MoveId } from "#enums/move-id";
import { SpeciesId } from "#enums/species-id";
import GameManager from "#test/testUtils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
describe("{{description}}", () => {
let phaserGame: Phaser.Game;
let game: GameManager;
beforeAll(() => {
phaserGame = new Phaser.Game({
type: Phaser.HEADLESS,
});
});
afterEach(() => {
game.phaseInterceptor.restoreOg();
});
beforeEach(() => {
game = new GameManager(phaserGame);
game.override
.ability(AbilityId.BALL_FETCH)
.battleStyle("single")
.disableCrits()
.enemySpecies(SpeciesId.MAGIKARP)
.enemyAbility(AbilityId.BALL_FETCH)
.enemyMoveset(MoveId.SPLASH)
.startingLevel(100)
.enemyLevel(100);
});
it("should do XYZ", async () => {
await game.classicMode.startBattle([SpeciesId.FEEBAS]);
game.move.use(MoveId.SPLASH);
await game.toEndOfTurn();
expect(true).toBe(true);
});
});

56
src/@types/move-types.ts Normal file
View File

@ -0,0 +1,56 @@
import type {
AttackMove,
StatusMove,
SelfStatusMove,
ChargingAttackMove,
ChargingSelfStatusMove,
MoveAttrConstructorMap,
MoveAttr,
} from "#app/data/moves/move";
export type MoveAttrFilter = (attr: MoveAttr) => boolean;
export type * from "#app/data/moves/move";
/**
* Map of move subclass names to their respective classes.
* Does not include the ChargeMove subclasses. For that, use `ChargingMoveClassMap`.
*
* @privateremarks
* The `never` field (`declare private _: never`) in some classes is necessary
* to ensure typescript does not improperly narrow a failed `is` guard to `never`.
*
* For example, if we did not have the never, and wrote
* ```
* function Foo(move: Move) {
* if (move.is("AttackMove")) {
*
* } else if (move.is("StatusMove")) { // typescript errors on the `is`, saying that `move` is `never`
*
* }
* ```
*/
export type MoveClassMap = {
AttackMove: AttackMove;
StatusMove: StatusMove;
SelfStatusMove: SelfStatusMove;
};
/**
* Union type of all move subclass names
*/
export type MoveKindString = "AttackMove" | "StatusMove" | "SelfStatusMove";
/**
* Map of move attribute names to attribute instances.
*/
export type MoveAttrMap = {
[K in keyof MoveAttrConstructorMap]: InstanceType<MoveAttrConstructorMap[K]>;
};
/**
* Union type of all move attribute names as strings.
*/
export type MoveAttrString = keyof MoveAttrMap;
export type ChargingMove = ChargingAttackMove | ChargingSelfStatusMove;

View File

@ -80,13 +80,15 @@ import type { FixedBattleConfig } from "#app/battle";
import Battle from "#app/battle"; import Battle from "#app/battle";
import { BattleType } from "#enums/battle-type"; import { BattleType } from "#enums/battle-type";
import type { GameMode } from "#app/game-mode"; import type { GameMode } from "#app/game-mode";
import { GameModes, getGameMode } from "#app/game-mode"; import { getGameMode } from "#app/game-mode";
import { GameModes } from "#enums/game-modes";
import FieldSpritePipeline from "#app/pipelines/field-sprite"; import FieldSpritePipeline from "#app/pipelines/field-sprite";
import SpritePipeline from "#app/pipelines/sprite"; import SpritePipeline from "#app/pipelines/sprite";
import PartyExpBar from "#app/ui/party-exp-bar"; import PartyExpBar from "#app/ui/party-exp-bar";
import type { TrainerSlot } from "./enums/trainer-slot"; import type { TrainerSlot } from "./enums/trainer-slot";
import { trainerConfigs } from "#app/data/trainers/trainer-config"; import { trainerConfigs } from "#app/data/trainers/trainer-config";
import Trainer, { TrainerVariant } from "#app/field/trainer"; import Trainer from "#app/field/trainer";
import { TrainerVariant } from "#enums/trainer-variant";
import type TrainerData from "#app/system/trainer-data"; import type TrainerData from "#app/system/trainer-data";
import SoundFade from "phaser3-rex-plugins/plugins/soundfade"; import SoundFade from "phaser3-rex-plugins/plugins/soundfade";
import { pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; import { pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions";
@ -101,13 +103,12 @@ import type UIPlugin from "phaser3-rex-plugins/templates/ui/ui-plugin";
import { addUiThemeOverrides } from "#app/ui/ui-theme"; import { addUiThemeOverrides } from "#app/ui/ui-theme";
import type PokemonData from "#app/system/pokemon-data"; import type PokemonData from "#app/system/pokemon-data";
import { Nature } from "#enums/nature"; import { Nature } from "#enums/nature";
import type { SpeciesFormChange, SpeciesFormChangeTrigger } from "#app/data/pokemon-forms"; import type { SpeciesFormChange } from "#app/data/pokemon-forms";
import { import type { SpeciesFormChangeTrigger } from "./data/pokemon-forms/form-change-triggers";
FormChangeItem, import { pokemonFormChanges } from "#app/data/pokemon-forms";
pokemonFormChanges, import { SpeciesFormChangeTimeOfDayTrigger } from "./data/pokemon-forms/form-change-triggers";
SpeciesFormChangeManualTrigger, import { SpeciesFormChangeManualTrigger } from "./data/pokemon-forms/form-change-triggers";
SpeciesFormChangeTimeOfDayTrigger, import { FormChangeItem } from "#enums/form-change-item";
} from "#app/data/pokemon-forms";
import { getTypeRgb } from "#app/data/type"; import { getTypeRgb } from "#app/data/type";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import PokemonSpriteSparkleHandler from "#app/field/pokemon-sprite-sparkle-handler"; import PokemonSpriteSparkleHandler from "#app/field/pokemon-sprite-sparkle-handler";

View File

@ -1,5 +1,5 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type { Command } from "./ui/command-ui-handler"; import type { Command } from "#enums/command";
import { import {
randomString, randomString,
getEnumValues, getEnumValues,
@ -10,7 +10,8 @@ import {
randInt, randInt,
randSeedFloat, randSeedFloat,
} from "#app/utils/common"; } from "#app/utils/common";
import Trainer, { TrainerVariant } from "./field/trainer"; import Trainer from "./field/trainer";
import { TrainerVariant } from "#enums/trainer-variant";
import type { GameMode } from "./game-mode"; import type { GameMode } from "./game-mode";
import { MoneyMultiplierModifier, PokemonHeldItemModifier } from "./modifier/modifier"; import { MoneyMultiplierModifier, PokemonHeldItemModifier } from "./modifier/modifier";
import type { PokeballType } from "#enums/pokeball"; import type { PokeballType } from "#enums/pokeball";
@ -33,14 +34,7 @@ import { ModifierTier } from "#app/modifier/modifier-tier";
import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { BattleType } from "#enums/battle-type"; import { BattleType } from "#enums/battle-type";
import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves"; import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves";
import { BattlerIndex } from "#enums/battler-index";
export enum BattlerIndex {
ATTACKER = -1,
PLAYER,
PLAYER_2,
ENEMY,
ENEMY_2,
}
export interface TurnCommand { export interface TurnCommand {
command: Command; command: Command;

View File

@ -1,3 +1,5 @@
import { SpeciesId } from "#enums/species-id";
/** The maximum size of the player's party */ /** The maximum size of the player's party */
export const PLAYER_PARTY_MAX_SIZE: number = 6; export const PLAYER_PARTY_MAX_SIZE: number = 6;
@ -17,3 +19,38 @@ export const CHALLENGE_MODE_MYSTERY_ENCOUNTER_WAVES: [number, number] = [10, 180
/** The raw percentage power boost for type boost items*/ /** The raw percentage power boost for type boost items*/
export const TYPE_BOOST_ITEM_BOOST_PERCENT = 20; export const TYPE_BOOST_ITEM_BOOST_PERCENT = 20;
/**
* The default species that a new player can choose from
*/
export const defaultStarterSpecies: SpeciesId[] = [
SpeciesId.BULBASAUR,
SpeciesId.CHARMANDER,
SpeciesId.SQUIRTLE,
SpeciesId.CHIKORITA,
SpeciesId.CYNDAQUIL,
SpeciesId.TOTODILE,
SpeciesId.TREECKO,
SpeciesId.TORCHIC,
SpeciesId.MUDKIP,
SpeciesId.TURTWIG,
SpeciesId.CHIMCHAR,
SpeciesId.PIPLUP,
SpeciesId.SNIVY,
SpeciesId.TEPIG,
SpeciesId.OSHAWOTT,
SpeciesId.CHESPIN,
SpeciesId.FENNEKIN,
SpeciesId.FROAKIE,
SpeciesId.ROWLET,
SpeciesId.LITTEN,
SpeciesId.POPPLIO,
SpeciesId.GROOKEY,
SpeciesId.SCORBUNNY,
SpeciesId.SOBBLE,
SpeciesId.SPRIGATITO,
SpeciesId.FUECOCO,
SpeciesId.QUAXLY,
];
export const saveKey = "x0i2O7WRiANTqPmZ"; // Temporary; secure encryption is not yet necessary

View File

@ -1,4 +1,5 @@
import { HitResult, MoveResult } from "#app/field/pokemon"; import { MoveResult } from "#enums/move-result";
import { HitResult } from "#enums/hit-result";
import { import {
BooleanHolder, BooleanHolder,
NumberHolder, NumberHolder,
@ -10,40 +11,26 @@ import {
randSeedFloat, randSeedFloat,
} from "#app/utils/common"; } from "#app/utils/common";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import { BattlerTagLapseType, GroundedTag } from "#app/data/battler-tags"; import { GroundedTag } from "#app/data/battler-tags";
import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
import { import {
getNonVolatileStatusEffects, getNonVolatileStatusEffects,
getStatusEffectDescriptor, getStatusEffectDescriptor,
getStatusEffectHealText, getStatusEffectHealText,
} from "#app/data/status-effect"; } from "#app/data/status-effect";
import { Gender } from "#app/data/gender"; import { Gender } from "#app/data/gender";
import { import { applyMoveAttrs } from "../moves/apply-attrs";
AttackMove,
FlinchAttr,
OneHitKOAttr,
HitHealAttr,
StatusMove,
SelfStatusMove,
VariablePowerAttr,
applyMoveAttrs,
RandomMovesetMoveAttr,
RandomMoveAttr,
NaturePowerAttr,
CopyMoveAttr,
NeutralDamageAgainstFlyingTypeMultiplierAttr,
FixedDamageAttr,
} from "#app/data/moves/move";
import { allMoves } from "../data-lists"; import { allMoves } from "../data-lists";
import { ArenaTagSide } from "#app/data/arena-tag"; import { ArenaTagSide } from "#enums/arena-tag-side";
import { BerryModifier, HitHealModifier, PokemonHeldItemModifier } from "#app/modifier/modifier"; import { BerryModifier, HitHealModifier, PokemonHeldItemModifier } from "#app/modifier/modifier";
import { TerrainType } from "#app/data/terrain"; import { TerrainType } from "#app/data/terrain";
import { import {
SpeciesFormChangeAbilityTrigger,
SpeciesFormChangeRevertWeatherFormTrigger, SpeciesFormChangeRevertWeatherFormTrigger,
SpeciesFormChangeWeatherTrigger, SpeciesFormChangeWeatherTrigger,
} from "#app/data/pokemon-forms"; } from "../pokemon-forms/form-change-triggers";
import { SpeciesFormChangeAbilityTrigger } from "../pokemon-forms/form-change-triggers";
import i18next from "i18next"; import i18next from "i18next";
import { Command } from "#app/ui/command-ui-handler"; import { Command } from "#enums/command";
import { BerryModifierType } from "#app/modifier/modifier-type"; import { BerryModifierType } from "#app/modifier/modifier-type";
import { getPokeballName } from "#app/data/pokeball"; import { getPokeballName } from "#app/data/pokeball";
import { BattleType } from "#enums/battle-type"; import { BattleType } from "#enums/battle-type";
@ -69,12 +56,13 @@ import { MoveFlags } from "#enums/MoveFlags";
import { MoveTarget } from "#enums/MoveTarget"; import { MoveTarget } from "#enums/MoveTarget";
import { MoveCategory } from "#enums/MoveCategory"; import { MoveCategory } from "#enums/MoveCategory";
import type { BerryType } from "#enums/berry-type"; import type { BerryType } from "#enums/berry-type";
import { CommonAnim } from "../battle-anims"; import { CommonAnim } from "#enums/move-anims-common";
import { getBerryEffectFunc } from "../berry"; import { getBerryEffectFunc } from "../berry";
import { BerryUsedEvent } from "#app/events/battle-scene"; import { BerryUsedEvent } from "#app/events/battle-scene";
// Type imports // Type imports
import type { EnemyPokemon, PokemonMove } from "#app/field/pokemon"; import type { EnemyPokemon } from "#app/field/pokemon";
import type { PokemonMove } from "../moves/pokemon-move";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import type { Weather } from "#app/data/weather"; import type { Weather } from "#app/data/weather";
import type { BattlerTag } from "#app/data/battler-tags"; import type { BattlerTag } from "#app/data/battler-tags";
@ -86,7 +74,7 @@ import type {
AbAttrApplyFunc, AbAttrApplyFunc,
AbAttrSuccessFunc, AbAttrSuccessFunc,
} from "#app/@types/ability-types"; } from "#app/@types/ability-types";
import type { BattlerIndex } from "#app/battle"; import type { BattlerIndex } from "#enums/battler-index";
import type Move from "#app/data/moves/move"; import type Move from "#app/data/moves/move";
import type { ArenaTrapTag, SuppressAbilitiesTag } from "#app/data/arena-tag"; import type { ArenaTrapTag, SuppressAbilitiesTag } from "#app/data/arena-tag";
import { noAbilityTypeOverrideMoves } from "../moves/invalid-moves"; import { noAbilityTypeOverrideMoves } from "../moves/invalid-moves";
@ -520,7 +508,7 @@ export class AttackTypeImmunityAbAttr extends TypeImmunityAbAttr {
): boolean { ): boolean {
return ( return (
move.category !== MoveCategory.STATUS && move.category !== MoveCategory.STATUS &&
!move.hasAttr(NeutralDamageAgainstFlyingTypeMultiplierAttr) && !move.hasAttr("NeutralDamageAgainstFlyingTypeMultiplierAttr") &&
super.canApplyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args) super.canApplyPreDefend(pokemon, passive, simulated, attacker, move, cancelled, args)
); );
} }
@ -693,7 +681,7 @@ export class NonSuperEffectiveImmunityAbAttr extends TypeImmunityAbAttr {
args.length > 0 args.length > 0
? (args[0] as NumberHolder).value ? (args[0] as NumberHolder).value
: pokemon.getAttackTypeEffectiveness(attacker.getMoveType(move), attacker, undefined, undefined, move); : pokemon.getAttackTypeEffectiveness(attacker.getMoveType(move), attacker, undefined, undefined, move);
return move instanceof AttackMove && modifierValue < 2; return move.is("AttackMove") && modifierValue < 2;
} }
override applyPreDefend( override applyPreDefend(
@ -735,7 +723,7 @@ export class FullHpResistTypeAbAttr extends PreDefendAbAttr {
const typeMultiplier = args[0]; const typeMultiplier = args[0];
return ( return (
typeMultiplier instanceof NumberHolder && typeMultiplier instanceof NumberHolder &&
!move?.hasAttr(FixedDamageAttr) && !move?.hasAttr("FixedDamageAttr") &&
pokemon.isFullHp() && pokemon.isFullHp() &&
typeMultiplier.value > 0.5 typeMultiplier.value > 0.5
); );
@ -980,7 +968,7 @@ export class ReverseDrainAbAttr extends PostDefendAbAttr {
_hitResult: HitResult | null, _hitResult: HitResult | null,
_args: any[], _args: any[],
): boolean { ): boolean {
return move.hasAttr(HitHealAttr); return move.hasAttr("HitHealAttr");
} }
/** /**
@ -2061,10 +2049,10 @@ export class PokemonTypeChangeAbAttr extends PreAttackAbAttr {
*/ */
!move.findAttr( !move.findAttr(
attr => attr =>
attr instanceof RandomMovesetMoveAttr || attr.is("RandomMovesetMoveAttr") ||
attr instanceof RandomMoveAttr || attr.is("RandomMoveAttr") ||
attr instanceof NaturePowerAttr || attr.is("NaturePowerAttr") ||
attr instanceof CopyMoveAttr, attr.is("CopyMoveAttr"),
) )
) { ) {
const moveType = pokemon.getMoveType(move); const moveType = pokemon.getMoveType(move);
@ -2539,17 +2527,11 @@ export class AllyStatMultiplierAbAttr extends AbAttr {
* @extends AbAttr * @extends AbAttr
*/ */
export class ExecutedMoveAbAttr extends AbAttr { export class ExecutedMoveAbAttr extends AbAttr {
canApplyExecutedMove( canApplyExecutedMove(_pokemon: Pokemon, _simulated: boolean): boolean {
_pokemon: Pokemon,
_simulated: boolean,
): boolean {
return true; return true;
} }
applyExecutedMove( applyExecutedMove(_pokemon: Pokemon, _simulated: boolean): void {}
_pokemon: Pokemon,
_simulated: boolean,
): void {}
} }
/** /**
@ -2557,7 +2539,7 @@ export class ExecutedMoveAbAttr extends AbAttr {
* @extends ExecutedMoveAbAttr * @extends ExecutedMoveAbAttr
*/ */
export class GorillaTacticsAbAttr extends ExecutedMoveAbAttr { export class GorillaTacticsAbAttr extends ExecutedMoveAbAttr {
constructor(showAbility: boolean = false) { constructor(showAbility = false) {
super(showAbility); super(showAbility);
} }
@ -4926,13 +4908,13 @@ function getAnticipationCondition(): AbAttrCondition {
} }
// the move's base type (not accounting for variable type changes) is super effective // the move's base type (not accounting for variable type changes) is super effective
if ( if (
move.getMove() instanceof AttackMove && move.getMove().is("AttackMove") &&
pokemon.getAttackTypeEffectiveness(move.getMove().type, opponent, true, undefined, move.getMove()) >= 2 pokemon.getAttackTypeEffectiveness(move.getMove().type, opponent, true, undefined, move.getMove()) >= 2
) { ) {
return true; return true;
} }
// move is a OHKO // move is a OHKO
if (move.getMove().hasAttr(OneHitKOAttr)) { if (move.getMove().hasAttr("OneHitKOAttr")) {
return true; return true;
} }
// edge case for hidden power, type is computed // edge case for hidden power, type is computed
@ -5001,9 +4983,9 @@ export class ForewarnAbAttr extends PostSummonAbAttr {
let movePower = 0; let movePower = 0;
for (const opponent of pokemon.getOpponents()) { for (const opponent of pokemon.getOpponents()) {
for (const move of opponent.moveset) { for (const move of opponent.moveset) {
if (move?.getMove() instanceof StatusMove) { if (move?.getMove().is("StatusMove")) {
movePower = 1; movePower = 1;
} else if (move?.getMove().hasAttr(OneHitKOAttr)) { } else if (move?.getMove().hasAttr("OneHitKOAttr")) {
movePower = 150; movePower = 150;
} else if ( } else if (
move?.getMove().id === MoveId.COUNTER || move?.getMove().id === MoveId.COUNTER ||
@ -5874,10 +5856,10 @@ export class PostDancingMoveAbAttr extends PostMoveUsedAbAttr {
dancer.turnData.extraTurns++; dancer.turnData.extraTurns++;
const phaseManager = globalScene.phaseManager; const phaseManager = globalScene.phaseManager;
// If the move is an AttackMove or a StatusMove the Dancer must replicate the move on the source of the Dance // If the move is an AttackMove or a StatusMove the Dancer must replicate the move on the source of the Dance
if (move.getMove() instanceof AttackMove || move.getMove() instanceof StatusMove) { if (move.getMove().is("AttackMove") || move.getMove().is("StatusMove")) {
const target = this.getTarget(dancer, source, targets); const target = this.getTarget(dancer, source, targets);
phaseManager.unshiftNew("MovePhase", dancer, target, move, true, true); phaseManager.unshiftNew("MovePhase", dancer, target, move, true, true);
} else if (move.getMove() instanceof SelfStatusMove) { } else if (move.getMove().is("SelfStatusMove")) {
// If the move is a SelfStatusMove (ie. Swords Dance) the Dancer should replicate it on itself // If the move is a SelfStatusMove (ie. Swords Dance) the Dancer should replicate it on itself
phaseManager.unshiftNew("MovePhase", dancer, [dancer.getBattlerIndex()], move, true, true); phaseManager.unshiftNew("MovePhase", dancer, [dancer.getBattlerIndex()], move, true, true);
} }
@ -7785,7 +7767,7 @@ export function applyPreAttackAbAttrs(
export function applyExecutedMoveAbAttrs( export function applyExecutedMoveAbAttrs(
attrType: Constructor<ExecutedMoveAbAttr>, attrType: Constructor<ExecutedMoveAbAttr>,
pokemon: Pokemon, pokemon: Pokemon,
simulated: boolean = false, simulated = false,
...args: any[] ...args: any[]
): void { ): void {
applyAbAttrsInternal<ExecutedMoveAbAttr>( applyAbAttrsInternal<ExecutedMoveAbAttr>(
@ -8207,7 +8189,7 @@ export function initAbilities() {
allAbilities.push( allAbilities.push(
new Ability(AbilityId.NONE, 3), new Ability(AbilityId.NONE, 3),
new Ability(AbilityId.STENCH, 3) new Ability(AbilityId.STENCH, 3)
.attr(PostAttackApplyBattlerTagAbAttr, false, (user, target, move) => !move.hasAttr(FlinchAttr) && !move.hitsSubstitute(user, target) ? 10 : 0, BattlerTagType.FLINCHED), .attr(PostAttackApplyBattlerTagAbAttr, false, (user, target, move) => !move.hasAttr("FlinchAttr") && !move.hitsSubstitute(user, target) ? 10 : 0, BattlerTagType.FLINCHED),
new Ability(AbilityId.DRIZZLE, 3) new Ability(AbilityId.DRIZZLE, 3)
.attr(PostSummonWeatherChangeAbAttr, WeatherType.RAIN) .attr(PostSummonWeatherChangeAbAttr, WeatherType.RAIN)
.attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.RAIN), .attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.RAIN),
@ -8514,7 +8496,7 @@ export function initAbilities() {
new Ability(AbilityId.TECHNICIAN, 4) new Ability(AbilityId.TECHNICIAN, 4)
.attr(MovePowerBoostAbAttr, (user, target, move) => { .attr(MovePowerBoostAbAttr, (user, target, move) => {
const power = new NumberHolder(move.power); const power = new NumberHolder(move.power);
applyMoveAttrs(VariablePowerAttr, user, target, move, power); applyMoveAttrs("VariablePowerAttr", user, target, move, power);
return power.value <= 60; return power.value <= 60;
}, 1.5), }, 1.5),
new Ability(AbilityId.LEAF_GUARD, 4) new Ability(AbilityId.LEAF_GUARD, 4)
@ -8635,7 +8617,7 @@ export function initAbilities() {
) )
.edgeCase(), // Cannot recover berries used up by fling or natural gift (unimplemented) .edgeCase(), // Cannot recover berries used up by fling or natural gift (unimplemented)
new Ability(AbilityId.TELEPATHY, 5) new Ability(AbilityId.TELEPATHY, 5)
.attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon.getAlly() === attacker && move instanceof AttackMove) .attr(MoveImmunityAbAttr, (pokemon, attacker, move) => pokemon.getAlly() === attacker && move.is("AttackMove"))
.ignorable(), .ignorable(),
new Ability(AbilityId.MOODY, 5) new Ability(AbilityId.MOODY, 5)
.attr(MoodyAbAttr), .attr(MoodyAbAttr),

View File

@ -7,9 +7,9 @@ import { MoveTarget } from "#enums/MoveTarget";
import { MoveCategory } from "#enums/MoveCategory"; import { MoveCategory } from "#enums/MoveCategory";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { HitResult } from "#app/field/pokemon"; import { HitResult } from "#enums/hit-result";
import { StatusEffect } from "#enums/status-effect"; import { StatusEffect } from "#enums/status-effect";
import type { BattlerIndex } from "#app/battle"; import type { BattlerIndex } from "#enums/battler-index";
import { import {
BlockNonDirectDamageAbAttr, BlockNonDirectDamageAbAttr,
InfiltratorAbAttr, InfiltratorAbAttr,
@ -20,18 +20,14 @@ import {
applyOnLoseAbAttrs, applyOnLoseAbAttrs,
} from "#app/data/abilities/ability"; } from "#app/data/abilities/ability";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import { CommonAnim, CommonBattleAnim } from "#app/data/battle-anims"; import { CommonBattleAnim } from "#app/data/battle-anims";
import { CommonAnim } from "#enums/move-anims-common";
import i18next from "i18next"; import i18next from "i18next";
import { AbilityId } from "#enums/ability-id"; import { AbilityId } from "#enums/ability-id";
import { ArenaTagType } from "#enums/arena-tag-type"; import { ArenaTagType } from "#enums/arena-tag-type";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import { ArenaTagSide } from "#enums/arena-tag-side";
export enum ArenaTagSide {
BOTH,
PLAYER,
ENEMY,
}
export abstract class ArenaTag { export abstract class ArenaTag {
constructor( constructor(

View File

@ -5988,6 +5988,7 @@ export const tmSpecies: TmSpecies = {
SpeciesId.FEZANDIPITI, SpeciesId.FEZANDIPITI,
SpeciesId.ARCHALUDON, SpeciesId.ARCHALUDON,
SpeciesId.IRON_CROWN, SpeciesId.IRON_CROWN,
SpeciesId.TERAPAGOS,
SpeciesId.ALOLA_RATICATE, SpeciesId.ALOLA_RATICATE,
SpeciesId.ALOLA_RAICHU, SpeciesId.ALOLA_RAICHU,
SpeciesId.ALOLA_SANDSLASH, SpeciesId.ALOLA_SANDSLASH,
@ -16248,6 +16249,7 @@ export const tmSpecies: TmSpecies = {
SpeciesId.CALYREX, SpeciesId.CALYREX,
SpeciesId.SANDY_SHOCKS, SpeciesId.SANDY_SHOCKS,
SpeciesId.IRON_JUGULIS, SpeciesId.IRON_JUGULIS,
SpeciesId.TERAPAGOS,
SpeciesId.ALOLA_DUGTRIO, SpeciesId.ALOLA_DUGTRIO,
SpeciesId.GALAR_SLOWPOKE, SpeciesId.GALAR_SLOWPOKE,
SpeciesId.GALAR_SLOWBRO, SpeciesId.GALAR_SLOWBRO,
@ -39466,6 +39468,8 @@ export const tmSpecies: TmSpecies = {
SpeciesId.FARFETCHD, SpeciesId.FARFETCHD,
SpeciesId.DODUO, SpeciesId.DODUO,
SpeciesId.DODRIO, SpeciesId.DODRIO,
SpeciesId.DEWGONG,
SpeciesId.GRIMER,
SpeciesId.MUK, SpeciesId.MUK,
SpeciesId.GASTLY, SpeciesId.GASTLY,
SpeciesId.HAUNTER, SpeciesId.HAUNTER,
@ -39477,6 +39481,7 @@ export const tmSpecies: TmSpecies = {
SpeciesId.CUBONE, SpeciesId.CUBONE,
SpeciesId.MAROWAK, SpeciesId.MAROWAK,
SpeciesId.HITMONLEE, SpeciesId.HITMONLEE,
SpeciesId.HITMONCHAN,
SpeciesId.LICKITUNG, SpeciesId.LICKITUNG,
SpeciesId.TANGELA, SpeciesId.TANGELA,
SpeciesId.GOLDEEN, SpeciesId.GOLDEEN,
@ -48806,6 +48811,7 @@ export const tmSpecies: TmSpecies = {
SpeciesId.GARGANACL, SpeciesId.GARGANACL,
SpeciesId.GLIMMET, SpeciesId.GLIMMET,
SpeciesId.GLIMMORA, SpeciesId.GLIMMORA,
SpeciesId.TERAPAGOS,
SpeciesId.ALOLA_GEODUDE, SpeciesId.ALOLA_GEODUDE,
SpeciesId.ALOLA_GRAVELER, SpeciesId.ALOLA_GRAVELER,
SpeciesId.ALOLA_GOLEM, SpeciesId.ALOLA_GOLEM,
@ -53077,6 +53083,7 @@ export const tmSpecies: TmSpecies = {
SpeciesId.MIRAIDON, SpeciesId.MIRAIDON,
SpeciesId.ARCHALUDON, SpeciesId.ARCHALUDON,
SpeciesId.IRON_CROWN, SpeciesId.IRON_CROWN,
SpeciesId.TERAPAGOS,
[ [
SpeciesId.WORMADAM, SpeciesId.WORMADAM,
"trash", "trash",

View File

@ -1,111 +1,15 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { AttackMove, BeakBlastHeaderAttr, DelayedAttackAttr, SelfStatusMove } from "./moves/move";
import { allMoves } from "./data-lists"; import { allMoves } from "./data-lists";
import { MoveFlags } from "#enums/MoveFlags"; import { MoveFlags } from "#enums/MoveFlags";
import type Pokemon from "../field/pokemon"; import type Pokemon from "../field/pokemon";
import { type nil, getFrameMs, getEnumKeys, getEnumValues, animationFileName } from "../utils/common"; import { type nil, getFrameMs, getEnumKeys, getEnumValues, animationFileName } from "../utils/common";
import type { BattlerIndex } from "../battle"; import type { BattlerIndex } from "#enums/battler-index";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import { SubstituteTag } from "./battler-tags"; import { SubstituteTag } from "./battler-tags";
import { isNullOrUndefined } from "../utils/common"; import { isNullOrUndefined } from "../utils/common";
import Phaser from "phaser"; import Phaser from "phaser";
import { EncounterAnim } from "#enums/encounter-anims"; import { EncounterAnim } from "#enums/encounter-anims";
import { AnimBlendType, AnimFrameTarget, AnimFocus, ChargeAnim, CommonAnim } from "#enums/move-anims-common";
export enum AnimFrameTarget {
USER,
TARGET,
GRAPHIC,
}
enum AnimFocus {
TARGET = 1,
USER,
USER_TARGET,
SCREEN,
}
enum AnimBlendType {
NORMAL,
ADD,
SUBTRACT,
}
export enum ChargeAnim {
FLY_CHARGING = 1000,
BOUNCE_CHARGING,
DIG_CHARGING,
FUTURE_SIGHT_CHARGING,
DIVE_CHARGING,
SOLAR_BEAM_CHARGING,
SHADOW_FORCE_CHARGING,
SKULL_BASH_CHARGING,
FREEZE_SHOCK_CHARGING,
SKY_DROP_CHARGING,
SKY_ATTACK_CHARGING,
ICE_BURN_CHARGING,
DOOM_DESIRE_CHARGING,
RAZOR_WIND_CHARGING,
PHANTOM_FORCE_CHARGING,
GEOMANCY_CHARGING,
SHADOW_BLADE_CHARGING,
SOLAR_BLADE_CHARGING,
BEAK_BLAST_CHARGING,
METEOR_BEAM_CHARGING,
ELECTRO_SHOT_CHARGING,
}
export enum CommonAnim {
USE_ITEM = 2000,
HEALTH_UP,
TERASTALLIZE,
POISON = 2010,
TOXIC,
PARALYSIS,
SLEEP,
FROZEN,
BURN,
CONFUSION,
ATTRACT,
BIND,
WRAP,
CURSE_NO_GHOST,
LEECH_SEED,
FIRE_SPIN,
PROTECT,
COVET,
WHIRLPOOL,
BIDE,
SAND_TOMB,
QUICK_GUARD,
WIDE_GUARD,
CURSE,
MAGMA_STORM,
CLAMP,
SNAP_TRAP,
THUNDER_CAGE,
INFESTATION,
ORDER_UP_CURLY,
ORDER_UP_DROOPY,
ORDER_UP_STRETCHY,
RAGING_BULL_FIRE,
RAGING_BULL_WATER,
SALT_CURE,
POWDER,
SUNNY = 2100,
RAIN,
SANDSTORM,
HAIL,
SNOW,
WIND,
HEAVY_RAIN,
HARSH_SUN,
STRONG_WINDS,
MISTY_TERRAIN = 2110,
ELECTRIC_TERRAIN,
GRASSY_TERRAIN,
PSYCHIC_TERRAIN,
LOCK_ON = 2120,
}
export class AnimConfig { export class AnimConfig {
public id: number; public id: number;
@ -531,7 +435,7 @@ export function initMoveAnim(move: MoveId): Promise<void> {
if (moveAnims.get(move) !== null) { if (moveAnims.get(move) !== null) {
const chargeAnimSource = allMoves[move].isChargingMove() const chargeAnimSource = allMoves[move].isChargingMove()
? allMoves[move] ? allMoves[move]
: (allMoves[move].getAttrs(DelayedAttackAttr)[0] ?? allMoves[move].getAttrs(BeakBlastHeaderAttr)[0]); : (allMoves[move].getAttrs("DelayedAttackAttr")[0] ?? allMoves[move].getAttrs("BeakBlastHeaderAttr")[0]);
if (chargeAnimSource && chargeAnims.get(chargeAnimSource.chargeAnim) === null) { if (chargeAnimSource && chargeAnims.get(chargeAnimSource.chargeAnim) === null) {
return; return;
} }
@ -542,12 +446,11 @@ export function initMoveAnim(move: MoveId): Promise<void> {
} }
} else { } else {
moveAnims.set(move, null); moveAnims.set(move, null);
const defaultMoveAnim = const defaultMoveAnim = allMoves[move].is("AttackMove")
allMoves[move] instanceof AttackMove ? MoveId.TACKLE
? MoveId.TACKLE : allMoves[move].is("SelfStatusMove")
: allMoves[move] instanceof SelfStatusMove ? MoveId.FOCUS_ENERGY
? MoveId.FOCUS_ENERGY : MoveId.TAIL_WHIP;
: MoveId.TAIL_WHIP;
const fetchAnimAndResolve = (move: MoveId) => { const fetchAnimAndResolve = (move: MoveId) => {
globalScene globalScene
@ -570,7 +473,7 @@ export function initMoveAnim(move: MoveId): Promise<void> {
} }
const chargeAnimSource = allMoves[move].isChargingMove() const chargeAnimSource = allMoves[move].isChargingMove()
? allMoves[move] ? allMoves[move]
: (allMoves[move].getAttrs(DelayedAttackAttr)[0] ?? allMoves[move].getAttrs(BeakBlastHeaderAttr)[0]); : (allMoves[move].getAttrs("DelayedAttackAttr")[0] ?? allMoves[move].getAttrs("BeakBlastHeaderAttr")[0]);
if (chargeAnimSource) { if (chargeAnimSource) {
initMoveChargeAnim(chargeAnimSource.chargeAnim).then(() => resolve()); initMoveChargeAnim(chargeAnimSource.chargeAnim).then(() => resolve());
} else { } else {
@ -703,7 +606,7 @@ export function loadMoveAnimAssets(moveIds: MoveId[], startLoad?: boolean): Prom
for (const moveId of moveIds) { for (const moveId of moveIds) {
const chargeAnimSource = allMoves[moveId].isChargingMove() const chargeAnimSource = allMoves[moveId].isChargingMove()
? allMoves[moveId] ? allMoves[moveId]
: (allMoves[moveId].getAttrs(DelayedAttackAttr)[0] ?? allMoves[moveId].getAttrs(BeakBlastHeaderAttr)[0]); : (allMoves[moveId].getAttrs("DelayedAttackAttr")[0] ?? allMoves[moveId].getAttrs("BeakBlastHeaderAttr")[0]);
if (chargeAnimSource) { if (chargeAnimSource) {
const moveChargeAnims = chargeAnims.get(chargeAnimSource.chargeAnim); const moveChargeAnims = chargeAnims.get(chargeAnimSource.chargeAnim);
moveAnimations.push(moveChargeAnims instanceof AnimConfig ? moveChargeAnims : moveChargeAnims![0]); // TODO: is the bang correct? moveAnimations.push(moveChargeAnims instanceof AnimConfig ? moveChargeAnims : moveChargeAnims![0]); // TODO: is the bang correct?

View File

@ -9,23 +9,20 @@ import {
ReverseDrainAbAttr, ReverseDrainAbAttr,
} from "#app/data/abilities/ability"; } from "#app/data/abilities/ability";
import { allAbilities } from "./data-lists"; import { allAbilities } from "./data-lists";
import { ChargeAnim, CommonAnim, CommonBattleAnim, MoveChargeAnim } from "#app/data/battle-anims"; import { CommonBattleAnim, MoveChargeAnim } from "#app/data/battle-anims";
import { ChargeAnim, CommonAnim } from "#enums/move-anims-common";
import type Move from "#app/data/moves/move"; import type Move from "#app/data/moves/move";
import { import { applyMoveAttrs } from "./moves/apply-attrs";
applyMoveAttrs,
ConsecutiveUseDoublePowerAttr,
HealOnAllyAttr,
StatusCategoryOnAllyAttr,
} from "#app/data/moves/move";
import { allMoves } from "./data-lists"; import { allMoves } from "./data-lists";
import { MoveFlags } from "#enums/MoveFlags"; import { MoveFlags } from "#enums/MoveFlags";
import { MoveCategory } from "#enums/MoveCategory"; import { MoveCategory } from "#enums/MoveCategory";
import { SpeciesFormChangeAbilityTrigger } from "#app/data/pokemon-forms"; import { SpeciesFormChangeAbilityTrigger } from "./pokemon-forms/form-change-triggers";
import { getStatusEffectHealText } from "#app/data/status-effect"; import { getStatusEffectHealText } from "#app/data/status-effect";
import { TerrainType } from "#app/data/terrain"; import { TerrainType } from "#app/data/terrain";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { HitResult, MoveResult } from "#app/field/pokemon"; import { MoveResult } from "#enums/move-result";
import { HitResult } from "#enums/hit-result";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import type { MoveEffectPhase } from "#app/phases/move-effect-phase"; import type { MoveEffectPhase } from "#app/phases/move-effect-phase";
import type { MovePhase } from "#app/phases/move-phase"; import type { MovePhase } from "#app/phases/move-phase";
@ -41,19 +38,7 @@ import { EFFECTIVE_STATS, getStatKey, Stat, type BattleStat, type EffectiveStat
import { StatusEffect } from "#enums/status-effect"; import { StatusEffect } from "#enums/status-effect";
import { WeatherType } from "#enums/weather-type"; import { WeatherType } from "#enums/weather-type";
import { isNullOrUndefined } from "#app/utils/common"; import { isNullOrUndefined } from "#app/utils/common";
import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
export enum BattlerTagLapseType {
FAINT,
MOVE,
PRE_MOVE,
AFTER_MOVE,
MOVE_EFFECT,
TURN_END,
HIT,
/** Tag lapses AFTER_HIT, applying its effects even if the user faints */
AFTER_HIT,
CUSTOM,
}
export class BattlerTag { export class BattlerTag {
public tagType: BattlerTagType; public tagType: BattlerTagType;
@ -2804,8 +2789,8 @@ export class HealBlockTag extends MoveRestrictionBattlerTag {
*/ */
override isMoveTargetRestricted(move: MoveId, user: Pokemon, target: Pokemon) { override isMoveTargetRestricted(move: MoveId, user: Pokemon, target: Pokemon) {
const moveCategory = new NumberHolder(allMoves[move].category); const moveCategory = new NumberHolder(allMoves[move].category);
applyMoveAttrs(StatusCategoryOnAllyAttr, user, target, allMoves[move], moveCategory); applyMoveAttrs("StatusCategoryOnAllyAttr", user, target, allMoves[move], moveCategory);
return allMoves[move].hasAttr(HealOnAllyAttr) && moveCategory.value === MoveCategory.STATUS; return allMoves[move].hasAttr("HealOnAllyAttr") && moveCategory.value === MoveCategory.STATUS;
} }
/** /**
@ -3141,7 +3126,7 @@ export class TormentTag extends MoveRestrictionBattlerTag {
// This checks for locking / momentum moves like Rollout and Hydro Cannon + if the user is under the influence of BattlerTagType.FRENZY // This checks for locking / momentum moves like Rollout and Hydro Cannon + if the user is under the influence of BattlerTagType.FRENZY
// Because Uproar's unique behavior is not implemented, it does not check for Uproar. Torment has been marked as partial in moves.ts // Because Uproar's unique behavior is not implemented, it does not check for Uproar. Torment has been marked as partial in moves.ts
const moveObj = allMoves[lastMove.move]; const moveObj = allMoves[lastMove.move];
const isUnaffected = moveObj.hasAttr(ConsecutiveUseDoublePowerAttr) || user.getTag(BattlerTagType.FRENZY); const isUnaffected = moveObj.hasAttr("ConsecutiveUseDoublePowerAttr") || user.getTag(BattlerTagType.FRENZY);
const validLastMoveResult = lastMove.result === MoveResult.SUCCESS || lastMove.result === MoveResult.MISS; const validLastMoveResult = lastMove.result === MoveResult.SUCCESS || lastMove.result === MoveResult.MISS;
return lastMove.move === move && validLastMoveResult && lastMove.move !== MoveId.STRUGGLE && !isUnaffected; return lastMove.move === move && validLastMoveResult && lastMove.move !== MoveId.STRUGGLE && !isUnaffected;
} }

View File

@ -1,6 +1,6 @@
import { getPokemonNameWithAffix } from "../messages"; import { getPokemonNameWithAffix } from "../messages";
import type Pokemon from "../field/pokemon"; import type Pokemon from "../field/pokemon";
import { HitResult } from "../field/pokemon"; import { HitResult } from "#enums/hit-result";
import { getStatusEffectHealText } from "./status-effect"; import { getStatusEffectHealText } from "./status-effect";
import { NumberHolder, toDmgValue, randSeedInt } from "#app/utils/common"; import { NumberHolder, toDmgValue, randSeedInt } from "#app/utils/common";
import { DoubleBerryEffectAbAttr, ReduceBerryUseThresholdAbAttr, applyAbAttrs } from "./abilities/ability"; import { DoubleBerryEffectAbAttr, ReduceBerryUseThresholdAbAttr, applyAbAttrs } from "./abilities/ability";

View File

@ -2,17 +2,18 @@ import { BooleanHolder, type NumberHolder, randSeedItem } from "#app/utils/commo
import { deepCopy } from "#app/utils/data"; import { deepCopy } from "#app/utils/data";
import i18next from "i18next"; import i18next from "i18next";
import type { DexAttrProps, GameData } from "#app/system/game-data"; import type { DexAttrProps, GameData } from "#app/system/game-data";
import { defaultStarterSpecies } from "#app/system/game-data"; import { defaultStarterSpecies } from "#app/constants";
import type PokemonSpecies from "#app/data/pokemon-species"; import type PokemonSpecies from "#app/data/pokemon-species";
import { getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species"; import { getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species";
import { speciesStarterCosts } from "#app/data/balance/starters"; import { speciesStarterCosts } from "#app/data/balance/starters";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { PokemonMove } from "#app/field/pokemon"; import { PokemonMove } from "./moves/pokemon-move";
import type { FixedBattleConfig } from "#app/battle"; import type { FixedBattleConfig } from "#app/battle";
import { getRandomTrainerFunc } from "#app/battle"; import { getRandomTrainerFunc } from "#app/battle";
import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves"; import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves";
import { BattleType } from "#enums/battle-type"; import { BattleType } from "#enums/battle-type";
import Trainer, { TrainerVariant } from "#app/field/trainer"; import Trainer from "#app/field/trainer";
import { TrainerVariant } from "#enums/trainer-variant";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import { Challenges } from "#enums/challenges"; import { Challenges } from "#enums/challenges";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
@ -24,93 +25,12 @@ import { ModifierTier } from "#app/modifier/modifier-tier";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { pokemonFormChanges } from "./pokemon-forms"; import { pokemonFormChanges } from "./pokemon-forms";
import { pokemonEvolutions } from "./balance/pokemon-evolutions"; import { pokemonEvolutions } from "./balance/pokemon-evolutions";
import { ChallengeType } from "#enums/challenge-type";
import type { MoveSourceType } from "#enums/move-source-type";
/** A constant for the default max cost of the starting party before a run */ /** A constant for the default max cost of the starting party before a run */
const DEFAULT_PARTY_MAX_COST = 10; const DEFAULT_PARTY_MAX_COST = 10;
/**
* An enum for all the challenge types. The parameter entries on these describe the
* parameters to use when calling the applyChallenges function.
*/
export enum ChallengeType {
/**
* Challenges which modify what starters you can choose
* @see {@link Challenge.applyStarterChoice}
*/
STARTER_CHOICE,
/**
* Challenges which modify how many starter points you have
* @see {@link Challenge.applyStarterPoints}
*/
STARTER_POINTS,
/**
* Challenges which modify how many starter points you have
* @see {@link Challenge.applyStarterPointCost}
*/
STARTER_COST,
/**
* Challenges which modify your starters in some way
* @see {@link Challenge.applyStarterModify}
*/
STARTER_MODIFY,
/**
* Challenges which limit which pokemon you can have in battle.
* @see {@link Challenge.applyPokemonInBattle}
*/
POKEMON_IN_BATTLE,
/**
* Adds or modifies the fixed battles in a run
* @see {@link Challenge.applyFixedBattle}
*/
FIXED_BATTLES,
/**
* Modifies the effectiveness of Type matchups in battle
* @see {@linkcode Challenge.applyTypeEffectiveness}
*/
TYPE_EFFECTIVENESS,
/**
* Modifies what level the AI pokemon are. UNIMPLEMENTED.
*/
AI_LEVEL,
/**
* Modifies how many move slots the AI has. UNIMPLEMENTED.
*/
AI_MOVE_SLOTS,
/**
* Modifies if a pokemon has its passive. UNIMPLEMENTED.
*/
PASSIVE_ACCESS,
/**
* Modifies the game mode settings in some way. UNIMPLEMENTED.
*/
GAME_MODE_MODIFY,
/**
* Modifies what level AI pokemon can access a move. UNIMPLEMENTED.
*/
MOVE_ACCESS,
/**
* Modifies what weight AI pokemon have when generating movesets. UNIMPLEMENTED.
*/
MOVE_WEIGHT,
/**
* Modifies what the pokemon stats for Flip Stat Mode.
*/
FLIP_STAT,
}
/**
* Used for challenge types that modify movesets, these denote the various sources of moves for pokemon.
*/
export enum MoveSourceType {
LEVEL_UP, // Currently unimplemented for move access
RELEARNER, // Relearner moves currently unimplemented
COMMON_TM,
GREAT_TM,
ULTRA_TM,
COMMON_EGG,
RARE_EGG,
}
/** /**
* A challenge object. Exists only to serve as a base class. * A challenge object. Exists only to serve as a base class.
*/ */

View File

@ -0,0 +1,58 @@
/**
* Module holding functions to apply move attributes.
* Must not import anything that is not a type.
*/
import type Pokemon from "#app/field/pokemon";
import type { default as Move, MoveAttr } from "./move";
import type { ChargingMove } from "#app/@types/move-types";
import type { MoveAttrFilter, MoveAttrString } from "#app/@types/move-types";
function applyMoveAttrsInternal(
attrFilter: MoveAttrFilter,
user: Pokemon | null,
target: Pokemon | null,
move: Move,
args: any[],
): void {
move.attrs.filter(attr => attrFilter(attr)).forEach(attr => attr.apply(user, target, move, args));
}
function applyMoveChargeAttrsInternal(
attrFilter: MoveAttrFilter,
user: Pokemon | null,
target: Pokemon | null,
move: ChargingMove,
args: any[],
): void {
move.chargeAttrs.filter(attr => attrFilter(attr)).forEach(attr => attr.apply(user, target, move, args));
}
export function applyMoveAttrs(
attrType: MoveAttrString,
user: Pokemon | null,
target: Pokemon | null,
move: Move,
...args: any[]
): void {
applyMoveAttrsInternal((attr: MoveAttr) => attr.is(attrType), user, target, move, args);
}
export function applyFilteredMoveAttrs(
attrFilter: MoveAttrFilter,
user: Pokemon,
target: Pokemon | null,
move: Move,
...args: any[]
): void {
applyMoveAttrsInternal(attrFilter, user, target, move, args);
}
export function applyMoveChargeAttrs(
attrType: MoveAttrString,
user: Pokemon | null,
target: Pokemon | null,
move: ChargingMove,
...args: any[]
): void {
applyMoveChargeAttrsInternal((attr: MoveAttr) => attr.is(attrType), user, target, move, args);
}

View File

@ -1,5 +1,14 @@
import { MoveTarget } from "#enums/MoveTarget"; import type Pokemon from "#app/field/pokemon";
import type { BattlerIndex } from "#enums/battler-index";
import type { MoveId } from "#enums/move-id";
import type { MoveTargetSet, UserMoveConditionFunc } from "./move";
import type Move from "./move"; import type Move from "./move";
import { NumberHolder, isNullOrUndefined } from "#app/utils/common";
import { MoveTarget } from "#enums/MoveTarget";
import { PokemonType } from "#enums/pokemon-type";
import { allMoves } from "#app/data/data-lists";
import { applyMoveAttrs } from "./apply-attrs";
import { BattlerTagType } from "#enums/battler-tag-type";
/** /**
* Return whether the move targets the field * Return whether the move targets the field
@ -18,3 +27,88 @@ export function isFieldTargeted(move: Move): boolean {
} }
return false; return false;
} }
export function getMoveTargets(user: Pokemon, move: MoveId, replaceTarget?: MoveTarget): MoveTargetSet {
const variableTarget = new NumberHolder(0);
user.getOpponents(false).forEach(p => applyMoveAttrs("VariableTargetAttr", user, p, allMoves[move], variableTarget));
let moveTarget: MoveTarget | undefined;
if (allMoves[move].hasAttr("VariableTargetAttr")) {
moveTarget = variableTarget.value;
} else if (replaceTarget !== undefined) {
moveTarget = replaceTarget;
} else if (move) {
moveTarget = allMoves[move].moveTarget;
} else if (move === undefined) {
moveTarget = MoveTarget.NEAR_ENEMY;
}
const opponents = user.getOpponents(false);
let set: Pokemon[] = [];
let multiple = false;
const ally: Pokemon | undefined = user.getAlly();
switch (moveTarget) {
case MoveTarget.USER:
case MoveTarget.PARTY:
set = [user];
break;
case MoveTarget.NEAR_OTHER:
case MoveTarget.OTHER:
case MoveTarget.ALL_NEAR_OTHERS:
case MoveTarget.ALL_OTHERS:
set = !isNullOrUndefined(ally) ? opponents.concat([ally]) : opponents;
multiple = moveTarget === MoveTarget.ALL_NEAR_OTHERS || moveTarget === MoveTarget.ALL_OTHERS;
break;
case MoveTarget.NEAR_ENEMY:
case MoveTarget.ALL_NEAR_ENEMIES:
case MoveTarget.ALL_ENEMIES:
case MoveTarget.ENEMY_SIDE:
set = opponents;
multiple = moveTarget !== MoveTarget.NEAR_ENEMY;
break;
case MoveTarget.RANDOM_NEAR_ENEMY:
set = [opponents[user.randBattleSeedInt(opponents.length)]];
break;
case MoveTarget.ATTACKER:
return { targets: [-1 as BattlerIndex], multiple: false };
case MoveTarget.NEAR_ALLY:
case MoveTarget.ALLY:
set = !isNullOrUndefined(ally) ? [ally] : [];
break;
case MoveTarget.USER_OR_NEAR_ALLY:
case MoveTarget.USER_AND_ALLIES:
case MoveTarget.USER_SIDE:
set = !isNullOrUndefined(ally) ? [user, ally] : [user];
multiple = moveTarget !== MoveTarget.USER_OR_NEAR_ALLY;
break;
case MoveTarget.ALL:
case MoveTarget.BOTH_SIDES:
set = (!isNullOrUndefined(ally) ? [user, ally] : [user]).concat(opponents);
multiple = true;
break;
case MoveTarget.CURSE:
{
const extraTargets = !isNullOrUndefined(ally) ? [ally] : [];
set = user.getTypes(true).includes(PokemonType.GHOST) ? opponents.concat(extraTargets) : [user];
}
break;
}
return {
targets: set
.filter(p => p?.isActive(true))
.map(p => p.getBattlerIndex())
.filter(t => t !== undefined),
multiple,
};
}
export const frenzyMissFunc: UserMoveConditionFunc = (user: Pokemon, move: Move) => {
while (user.getMoveQueue().length && user.getMoveQueue()[0].move === move.id) {
user.getMoveQueue().shift();
}
user.removeTag(BattlerTagType.FRENZY); // FRENZY tag should be disrupted on miss/no effect
return true;
};

View File

@ -1,4 +1,5 @@
import { ChargeAnim, MoveChargeAnim } from "../battle-anims"; import { MoveChargeAnim } from "../battle-anims";
import { ChargeAnim } from "#enums/move-anims-common";
import { import {
CommandedTag, CommandedTag,
EncoreTag, EncoreTag,
@ -14,14 +15,11 @@ import {
import { getPokemonNameWithAffix } from "../../messages"; import { getPokemonNameWithAffix } from "../../messages";
import type { AttackMoveResult, TurnMove } from "../../field/pokemon"; import type { AttackMoveResult, TurnMove } from "../../field/pokemon";
import type Pokemon from "../../field/pokemon"; import type Pokemon from "../../field/pokemon";
import { import type { EnemyPokemon } from "#app/field/pokemon";
EnemyPokemon, import { PokemonMove } from "./pokemon-move";
FieldPosition, import { MoveResult } from "#enums/move-result";
HitResult, import { HitResult } from "#enums/hit-result";
MoveResult, import { FieldPosition } from "#enums/field-position";
PlayerPokemon,
PokemonMove,
} from "../../field/pokemon";
import { import {
getNonVolatileStatusEffects, getNonVolatileStatusEffects,
getStatusEffectHealText, getStatusEffectHealText,
@ -32,7 +30,8 @@ import { PokemonType } from "#enums/pokemon-type";
import { BooleanHolder, NumberHolder, isNullOrUndefined, toDmgValue, randSeedItem, randSeedInt, getEnumValues, toReadableString, type Constructor, randSeedFloat } from "#app/utils/common"; import { BooleanHolder, NumberHolder, isNullOrUndefined, toDmgValue, randSeedItem, randSeedInt, getEnumValues, toReadableString, type Constructor, randSeedFloat } from "#app/utils/common";
import { WeatherType } from "#enums/weather-type"; import { WeatherType } from "#enums/weather-type";
import type { ArenaTrapTag } from "../arena-tag"; import type { ArenaTrapTag } from "../arena-tag";
import { ArenaTagSide, WeakenMoveTypeTag } from "../arena-tag"; import { WeakenMoveTypeTag } from "../arena-tag";
import { ArenaTagSide } from "#enums/arena-tag-side";
import { import {
AllyMoveCategoryPowerBoostAbAttr, AllyMoveCategoryPowerBoostAbAttr,
applyAbAttrs, applyAbAttrs,
@ -75,11 +74,11 @@ import {
PokemonMultiHitModifier, PokemonMultiHitModifier,
PreserveBerryModifier, PreserveBerryModifier,
} from "../../modifier/modifier"; } from "../../modifier/modifier";
import type { BattlerIndex } from "../../battle"; import type { BattlerIndex } from "#enums/battler-index";
import { BattleType } from "#enums/battle-type"; import { BattleType } from "#enums/battle-type";
import { TerrainType } from "../terrain"; import { TerrainType } from "../terrain";
import { ModifierPoolType } from "#app/modifier/modifier-type"; import { ModifierPoolType } from "#app/modifier/modifier-type";
import { Command } from "../../ui/command-ui-handler"; import { Command } from "#enums/command";
import i18next from "i18next"; import i18next from "i18next";
import type { Localizable } from "#app/@types/locales"; import type { Localizable } from "#app/@types/locales";
import { getBerryEffectFunc } from "../berry"; import { getBerryEffectFunc } from "../berry";
@ -105,9 +104,10 @@ import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase"; import { StatStageChangePhase } from "#app/phases/stat-stage-change-phase";
import { SwitchPhase } from "#app/phases/switch-phase"; import { SwitchPhase } from "#app/phases/switch-phase";
import { SwitchSummonPhase } from "#app/phases/switch-summon-phase"; import { SwitchSummonPhase } from "#app/phases/switch-summon-phase";
import { SpeciesFormChangeRevertWeatherFormTrigger } from "../pokemon-forms"; import { SpeciesFormChangeRevertWeatherFormTrigger } from "../pokemon-forms/form-change-triggers";
import type { GameMode } from "#app/game-mode"; import type { GameMode } from "#app/game-mode";
import { applyChallenges, ChallengeType } from "../challenge"; import { applyChallenges } from "../challenge";
import { ChallengeType } from "#enums/challenge-type";
import { SwitchType } from "#enums/switch-type"; import { SwitchType } from "#enums/switch-type";
import { StatusEffect } from "#enums/status-effect"; import { StatusEffect } from "#enums/status-effect";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
@ -123,11 +123,14 @@ import { MoveEffectTrigger } from "#enums/MoveEffectTrigger";
import { MultiHitType } from "#enums/MultiHitType"; import { MultiHitType } from "#enums/MultiHitType";
import { invalidAssistMoves, invalidCopycatMoves, invalidMetronomeMoves, invalidMirrorMoveMoves, invalidSleepTalkMoves } from "./invalid-moves"; import { invalidAssistMoves, invalidCopycatMoves, invalidMetronomeMoves, invalidMirrorMoveMoves, invalidSleepTalkMoves } from "./invalid-moves";
import { SelectBiomePhase } from "#app/phases/select-biome-phase"; import { SelectBiomePhase } from "#app/phases/select-biome-phase";
import { ChargingMove, MoveAttrMap, MoveAttrString, MoveKindString, MoveClassMap } from "#app/@types/move-types";
import { applyMoveAttrs } from "./apply-attrs";
import { frenzyMissFunc, getMoveTargets } from "./move-utils";
type MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => boolean; type MoveConditionFunc = (user: Pokemon, target: Pokemon, move: Move) => boolean;
type UserMoveConditionFunc = (user: Pokemon, move: Move) => boolean; export type UserMoveConditionFunc = (user: Pokemon, move: Move) => boolean;
export default class Move implements Localizable { export default abstract class Move implements Localizable {
public id: MoveId; public id: MoveId;
public name: string; public name: string;
private _type: PokemonType; private _type: PokemonType;
@ -147,6 +150,17 @@ export default class Move implements Localizable {
private flags: number = 0; private flags: number = 0;
private nameAppend: string = ""; private nameAppend: string = "";
/**
* Check if the move is of the given subclass without requiring `instanceof`.
*
* Does _not_ work for {@linkcode ChargingAttackMove} and {@linkcode ChargingSelfStatusMove} subclasses. For those,
* use {@linkcode isChargingMove} instead.
*
* @param moveKind - The string name of the move to check against
* @returns Whether this move is of the provided type.
*/
public abstract is<K extends MoveKindString>(moveKind: K): this is MoveClassMap[K];
constructor(id: MoveId, type: PokemonType, category: MoveCategory, defaultMoveTarget: MoveTarget, power: number, accuracy: number, pp: number, chance: number, priority: number, generation: number) { constructor(id: MoveId, type: PokemonType, category: MoveCategory, defaultMoveTarget: MoveTarget, power: number, accuracy: number, pp: number, chance: number, priority: number, generation: number) {
this.id = id; this.id = id;
this._type = type; this._type = type;
@ -188,8 +202,12 @@ export default class Move implements Localizable {
* @param attrType any attribute that extends {@linkcode MoveAttr} * @param attrType any attribute that extends {@linkcode MoveAttr}
* @returns Array of attributes that match `attrType`, Empty Array if none match. * @returns Array of attributes that match `attrType`, Empty Array if none match.
*/ */
getAttrs<T extends MoveAttr>(attrType: Constructor<T>): T[] { getAttrs<T extends MoveAttrString>(attrType: T): (MoveAttrMap[T])[] {
return this.attrs.filter((a): a is T => a instanceof attrType); const targetAttr = MoveAttrs[attrType];
if (!targetAttr) {
return [];
}
return this.attrs.filter((a): a is MoveAttrMap[T] => a instanceof targetAttr);
} }
/** /**
@ -197,8 +215,13 @@ export default class Move implements Localizable {
* @param attrType any attribute that extends {@linkcode MoveAttr} * @param attrType any attribute that extends {@linkcode MoveAttr}
* @returns true if the move has attribute `attrType` * @returns true if the move has attribute `attrType`
*/ */
hasAttr<T extends MoveAttr>(attrType: Constructor<T>): boolean { hasAttr(attrType: MoveAttrString): boolean {
return this.attrs.some((attr) => attr instanceof attrType); const targetAttr = MoveAttrs[attrType];
// Guard against invalid attrType
if (!targetAttr) {
return false;
}
return this.attrs.some((attr) => attr instanceof targetAttr);
} }
/** /**
@ -768,14 +791,14 @@ export default class Move implements Localizable {
calculateBattleAccuracy(user: Pokemon, target: Pokemon, simulated: boolean = false) { calculateBattleAccuracy(user: Pokemon, target: Pokemon, simulated: boolean = false) {
const moveAccuracy = new NumberHolder(this.accuracy); const moveAccuracy = new NumberHolder(this.accuracy);
applyMoveAttrs(VariableAccuracyAttr, user, target, this, moveAccuracy); applyMoveAttrs("VariableAccuracyAttr", user, target, this, moveAccuracy);
applyPreDefendAbAttrs(WonderSkinAbAttr, target, user, this, { value: false }, simulated, moveAccuracy); applyPreDefendAbAttrs(WonderSkinAbAttr, target, user, this, { value: false }, simulated, moveAccuracy);
if (moveAccuracy.value === -1) { if (moveAccuracy.value === -1) {
return moveAccuracy.value; return moveAccuracy.value;
} }
const isOhko = this.hasAttr(OneHitKOAccuracyAttr); const isOhko = this.hasAttr("OneHitKOAccuracyAttr");
if (!isOhko) { if (!isOhko) {
globalScene.applyModifiers(PokemonMoveAccuracyBoosterModifier, user.isPlayer(), user, moveAccuracy); globalScene.applyModifiers(PokemonMoveAccuracyBoosterModifier, user.isPlayer(), user, moveAccuracy);
@ -815,7 +838,7 @@ export default class Move implements Localizable {
applyPreAttackAbAttrs(MoveTypeChangeAbAttr, source, target, this, true, typeChangeHolder, typeChangeMovePowerMultiplier); applyPreAttackAbAttrs(MoveTypeChangeAbAttr, source, target, this, true, typeChangeHolder, typeChangeMovePowerMultiplier);
const sourceTeraType = source.getTeraType(); const sourceTeraType = source.getTeraType();
if (source.isTerastallized && sourceTeraType === this.type && power.value < 60 && this.priority <= 0 && !this.hasAttr(MultiHitAttr) && !globalScene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) { if (source.isTerastallized && sourceTeraType === this.type && power.value < 60 && this.priority <= 0 && !this.hasAttr("MultiHitAttr") && !globalScene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) {
power.value = 60; power.value = 60;
} }
@ -847,9 +870,9 @@ export default class Move implements Localizable {
power.value *= typeBoost.boostValue; power.value *= typeBoost.boostValue;
} }
applyMoveAttrs(VariablePowerAttr, source, target, this, power); applyMoveAttrs("VariablePowerAttr", source, target, this, power);
if (!this.hasAttr(TypelessAttr)) { if (!this.hasAttr("TypelessAttr")) {
globalScene.arena.applyTags(WeakenMoveTypeTag, simulated, typeChangeHolder.value, power); globalScene.arena.applyTags(WeakenMoveTypeTag, simulated, typeChangeHolder.value, power);
globalScene.applyModifiers(AttackTypeBoosterModifier, source.isPlayer(), source, typeChangeHolder.value, power); globalScene.applyModifiers(AttackTypeBoosterModifier, source.isPlayer(), source, typeChangeHolder.value, power);
} }
@ -864,7 +887,7 @@ export default class Move implements Localizable {
getPriority(user: Pokemon, simulated: boolean = true) { getPriority(user: Pokemon, simulated: boolean = true) {
const priority = new NumberHolder(this.priority); const priority = new NumberHolder(this.priority);
applyMoveAttrs(IncrementMovePriorityAttr, user, null, this, priority); applyMoveAttrs("IncrementMovePriorityAttr", user, null, this, priority);
applyAbAttrs(ChangeMovePriorityAbAttr, user, null, simulated, this, priority); applyAbAttrs(ChangeMovePriorityAbAttr, user, null, simulated, this, priority);
return priority.value; return priority.value;
@ -885,7 +908,7 @@ export default class Move implements Localizable {
} else if (this.id === MoveId.TRIPLE_KICK) { } else if (this.id === MoveId.TRIPLE_KICK) {
effectivePower = 47.07; effectivePower = 47.07;
} else { } else {
const multiHitAttr = this.getAttrs(MultiHitAttr)[0]; const multiHitAttr = this.getAttrs("MultiHitAttr")[0];
if (multiHitAttr) { if (multiHitAttr) {
effectivePower = multiHitAttr.calculateExpectedHitCount(this) * this.power; effectivePower = multiHitAttr.calculateExpectedHitCount(this) * this.power;
} else { } else {
@ -898,10 +921,10 @@ export default class Move implements Localizable {
// These are intentionally not else-if statements even though there are no // These are intentionally not else-if statements even though there are no
// pokemon moves that have more than one of these attributes. This allows // pokemon moves that have more than one of these attributes. This allows
// the function to future proof new moves / custom move behaviors. // the function to future proof new moves / custom move behaviors.
if (this.hasAttr(DelayedAttackAttr)) { if (this.hasAttr("DelayedAttackAttr")) {
numTurns += 2; numTurns += 2;
} }
if (this.hasAttr(RechargeAttr)) { if (this.hasAttr("RechargeAttr")) {
numTurns += 1; numTurns += 1;
} }
if (this.isChargingMove()) { if (this.isChargingMove()) {
@ -927,10 +950,10 @@ export default class Move implements Localizable {
const isMultiTarget = multiple && targets.length > 1; const isMultiTarget = multiple && targets.length > 1;
// ...cannot enhance multi-hit or sacrificial moves // ...cannot enhance multi-hit or sacrificial moves
const exceptAttrs: Constructor<MoveAttr>[] = [ const exceptAttrs: MoveAttrString[] = [
MultiHitAttr, "MultiHitAttr",
SacrificialAttr, "SacrificialAttr",
SacrificialAttrOnHit "SacrificialAttrOnHit"
]; ];
// ...and cannot enhance these specific moves // ...and cannot enhance these specific moves
@ -956,6 +979,13 @@ export default class Move implements Localizable {
} }
export class AttackMove extends Move { export class AttackMove extends Move {
/** This field does not exist at runtime and must not be used.
* Its sole purpose is to ensure that typescript is able to properly narrow when the `is` method is called.
*/
declare private _: never;
override is<K extends keyof MoveClassMap>(moveKind: K): this is MoveClassMap[K] {
return moveKind === "AttackMove";
}
constructor(id: MoveId, type: PokemonType, category: MoveCategory, power: number, accuracy: number, pp: number, chance: number, priority: number, generation: number) { constructor(id: MoveId, type: PokemonType, category: MoveCategory, power: number, accuracy: number, pp: number, chance: number, priority: number, generation: number) {
super(id, type, category, MoveTarget.NEAR_OTHER, power, accuracy, pp, chance, priority, generation); super(id, type, category, MoveTarget.NEAR_OTHER, power, accuracy, pp, chance, priority, generation);
@ -985,7 +1015,7 @@ export class AttackMove extends Move {
const [ thisStat, offStat ]: EffectiveStat[] = this.category === MoveCategory.PHYSICAL ? [ Stat.ATK, Stat.SPATK ] : [ Stat.SPATK, Stat.ATK ]; const [ thisStat, offStat ]: EffectiveStat[] = this.category === MoveCategory.PHYSICAL ? [ Stat.ATK, Stat.SPATK ] : [ Stat.SPATK, Stat.ATK ];
const statHolder = new NumberHolder(user.getEffectiveStat(thisStat, target)); const statHolder = new NumberHolder(user.getEffectiveStat(thisStat, target));
const offStatValue = user.getEffectiveStat(offStat, target); const offStatValue = user.getEffectiveStat(offStat, target);
applyMoveAttrs(VariableAtkAttr, user, target, move, statHolder); applyMoveAttrs("VariableAtkAttr", user, target, move, statHolder);
const statRatio = offStatValue / statHolder.value; const statRatio = offStatValue / statHolder.value;
if (statRatio <= 0.75) { if (statRatio <= 0.75) {
attackScore *= 2; attackScore *= 2;
@ -994,7 +1024,7 @@ export class AttackMove extends Move {
} }
const power = new NumberHolder(this.calculateEffectivePower()); const power = new NumberHolder(this.calculateEffectivePower());
applyMoveAttrs(VariablePowerAttr, user, target, move, power); applyMoveAttrs("VariablePowerAttr", user, target, move, power);
attackScore += Math.floor(power.value / 5); attackScore += Math.floor(power.value / 5);
@ -1003,20 +1033,42 @@ export class AttackMove extends Move {
} }
export class StatusMove extends Move { export class StatusMove extends Move {
/** This field does not exist at runtime and must not be used.
* Its sole purpose is to ensure that typescript is able to properly narrow when the `is` method is called.
*/
declare private _: never;
constructor(id: MoveId, type: PokemonType, accuracy: number, pp: number, chance: number, priority: number, generation: number) { constructor(id: MoveId, type: PokemonType, accuracy: number, pp: number, chance: number, priority: number, generation: number) {
super(id, type, MoveCategory.STATUS, MoveTarget.NEAR_OTHER, -1, accuracy, pp, chance, priority, generation); super(id, type, MoveCategory.STATUS, MoveTarget.NEAR_OTHER, -1, accuracy, pp, chance, priority, generation);
} }
override is<K extends MoveKindString>(moveKind: K): this is MoveClassMap[K] {
return moveKind === "StatusMove";
}
} }
export class SelfStatusMove extends Move { export class SelfStatusMove extends Move {
/** This field does not exist at runtime and must not be used.
* Its sole purpose is to ensure that typescript is able to properly narrow when the `is` method is called.
*/
declare private _: never;
constructor(id: MoveId, type: PokemonType, accuracy: number, pp: number, chance: number, priority: number, generation: number) { constructor(id: MoveId, type: PokemonType, accuracy: number, pp: number, chance: number, priority: number, generation: number) {
super(id, type, MoveCategory.STATUS, MoveTarget.USER, -1, accuracy, pp, chance, priority, generation); super(id, type, MoveCategory.STATUS, MoveTarget.USER, -1, accuracy, pp, chance, priority, generation);
} }
override is<K extends MoveKindString>(moveKind: K): this is MoveClassMap[K] {
return moveKind === "SelfStatusMove";
}
} }
type SubMove = new (...args: any[]) => Move; // TODO: Figure out how to improve the signature of this so that
// the `ChargeMove` function knows that the argument `Base` is a specific subclass of move that cannot
// be abstract.
// Right now, I only know how to do this by using the type conjunction (the & operators)
type SubMove = new (...args: any[]) => Move & {
is<K extends keyof MoveClassMap>(moveKind: K): this is MoveClassMap[K];
};
function ChargeMove<TBase extends SubMove>(Base: TBase) { function ChargeMove<TBase extends SubMove>(Base: TBase, nameAppend: string) {
return class extends Base { return class extends Base {
/** The animation to play during the move's charging phase */ /** The animation to play during the move's charging phase */
public readonly chargeAnim: ChargeAnim = ChargeAnim[`${MoveId[this.id]}_CHARGING`]; public readonly chargeAnim: ChargeAnim = ChargeAnim[`${MoveId[this.id]}_CHARGING`];
@ -1060,8 +1112,12 @@ function ChargeMove<TBase extends SubMove>(Base: TBase) {
* @returns Array of attributes that match `attrType`, or an empty array if * @returns Array of attributes that match `attrType`, or an empty array if
* no matches are found. * no matches are found.
*/ */
getChargeAttrs<T extends MoveAttr>(attrType: Constructor<T>): T[] { getChargeAttrs<T extends MoveAttrString>(attrType: T): (MoveAttrMap[T])[] {
return this.chargeAttrs.filter((attr): attr is T => attr instanceof attrType); const targetAttr = MoveAttrs[attrType];
if (!targetAttr) {
return [];
}
return this.chargeAttrs.filter((attr): attr is MoveAttrMap[T] => attr instanceof targetAttr);
} }
/** /**
@ -1069,8 +1125,12 @@ function ChargeMove<TBase extends SubMove>(Base: TBase) {
* @param attrType any attribute that extends {@linkcode MoveAttr} * @param attrType any attribute that extends {@linkcode MoveAttr}
* @returns `true` if a matching attribute is found; `false` otherwise * @returns `true` if a matching attribute is found; `false` otherwise
*/ */
hasChargeAttr<T extends MoveAttr>(attrType: Constructor<T>): boolean { hasChargeAttr<T extends MoveAttrString>(attrType: T): boolean {
return this.chargeAttrs.some((attr) => attr instanceof attrType); const targetAttr = MoveAttrs[attrType];
if (!targetAttr) {
return false;
}
return this.chargeAttrs.some((attr) => attr instanceof targetAttr);
} }
/** /**
@ -1088,10 +1148,8 @@ function ChargeMove<TBase extends SubMove>(Base: TBase) {
}; };
} }
export class ChargingAttackMove extends ChargeMove(AttackMove) {} export class ChargingAttackMove extends ChargeMove(AttackMove, "ChargingAttackMove") {}
export class ChargingSelfStatusMove extends ChargeMove(SelfStatusMove) {} export class ChargingSelfStatusMove extends ChargeMove(SelfStatusMove, "ChargingSelfStatusMove") {}
export type ChargingMove = ChargingAttackMove | ChargingSelfStatusMove;
/** /**
* Base class defining all {@linkcode Move} Attributes * Base class defining all {@linkcode Move} Attributes
@ -1102,6 +1160,22 @@ export abstract class MoveAttr {
/** Should this {@linkcode Move} target the user? */ /** Should this {@linkcode Move} target the user? */
public selfTarget: boolean; public selfTarget: boolean;
/**
* Return whether this attribute is of the given type.
*
* @remarks
* Used to avoid requring the caller to have imported the specific attribute type, avoiding circular dependencies.
* @param attr - The attribute to check against
* @returns Whether the attribute is an instance of the given type.
*/
public is<T extends MoveAttrString>(attr: T): this is MoveAttrMap[T] {
const targetAttr = MoveAttrs[attr];
if (!targetAttr) {
return false;
}
return this instanceof targetAttr;
}
constructor(selfTarget: boolean = false) { constructor(selfTarget: boolean = false) {
this.selfTarget = selfTarget; this.selfTarget = selfTarget;
} }
@ -1268,7 +1342,7 @@ export class MoveEffectAttr extends MoveAttr {
applyAbAttrs(MoveEffectChanceMultiplierAbAttr, user, null, !showAbility, moveChance, move); applyAbAttrs(MoveEffectChanceMultiplierAbAttr, user, null, !showAbility, moveChance, move);
if ((!move.hasAttr(FlinchAttr) || moveChance.value <= move.chance) && !move.hasAttr(SecretPowerAttr)) { if ((!move.hasAttr("FlinchAttr") || moveChance.value <= move.chance) && !move.hasAttr("SecretPowerAttr")) {
const userSide = user.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; const userSide = user.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
globalScene.arena.applyTagsForSide(ArenaTagType.WATER_FIRE_PLEDGE, userSide, false, moveChance); globalScene.arena.applyTagsForSide(ArenaTagType.WATER_FIRE_PLEDGE, userSide, false, moveChance);
} }
@ -2331,7 +2405,7 @@ export class MultiHitAttr extends MoveAttr {
*/ */
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
const hitType = new NumberHolder(this.intrinsicMultiHitType); const hitType = new NumberHolder(this.intrinsicMultiHitType);
applyMoveAttrs(ChangeMultiHitTypeAttr, user, target, move, hitType); applyMoveAttrs("ChangeMultiHitTypeAttr", user, target, move, hitType);
this.multiHitType = hitType.value; this.multiHitType = hitType.value;
(args[0] as NumberHolder).value = this.getHitCount(user, target); (args[0] as NumberHolder).value = this.getHitCount(user, target);
@ -3032,7 +3106,11 @@ export class WeatherInstantChargeAttr extends InstantChargeAttr {
} }
export class OverrideMoveEffectAttr extends MoveAttr { export class OverrideMoveEffectAttr extends MoveAttr {
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { /** This field does not exist at runtime and must not be used.
* Its sole purpose is to ensure that typescript is able to properly narrow when the `is` method is called.
*/
declare private _: never;
override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
return true; return true;
} }
} }
@ -3114,7 +3192,7 @@ export class AwaitCombinedPledgeAttr extends OverrideMoveEffectAttr {
const allyMovePhase = globalScene.phaseManager.findPhase<MovePhase>((phase) => phase.is("MovePhase") && phase.pokemon.isPlayer() === user.isPlayer()); const allyMovePhase = globalScene.phaseManager.findPhase<MovePhase>((phase) => phase.is("MovePhase") && phase.pokemon.isPlayer() === user.isPlayer());
if (allyMovePhase) { if (allyMovePhase) {
const allyMove = allyMovePhase.move.getMove(); const allyMove = allyMovePhase.move.getMove();
if (allyMove !== move && allyMove.hasAttr(AwaitCombinedPledgeAttr)) { if (allyMove !== move && allyMove.hasAttr("AwaitCombinedPledgeAttr")) {
[ user, allyMovePhase.pokemon ].forEach((p) => p.turnData.combiningPledge = move.id); [ user, allyMovePhase.pokemon ].forEach((p) => p.turnData.combiningPledge = move.id);
// "{userPokemonName} is waiting for {allyPokemonName}'s move..." // "{userPokemonName} is waiting for {allyPokemonName}'s move..."
@ -3235,22 +3313,22 @@ export class StatStageChangeAttr extends MoveEffectAttr {
switch (stat) { switch (stat) {
case Stat.ATK: case Stat.ATK:
if (this.selfTarget) { if (this.selfTarget) {
noEffect = !user.getMoveset().find(m => m instanceof AttackMove && m.category === MoveCategory.PHYSICAL); noEffect = !user.getMoveset().find(m => {const mv = m.getMove(); return mv.is("AttackMove") && mv.category === MoveCategory.PHYSICAL;} );
} }
break; break;
case Stat.DEF: case Stat.DEF:
if (!this.selfTarget) { if (!this.selfTarget) {
noEffect = !user.getMoveset().find(m => m instanceof AttackMove && m.category === MoveCategory.PHYSICAL); noEffect = !user.getMoveset().find(m => {const mv = m.getMove(); return mv.is("AttackMove") && mv.category === MoveCategory.PHYSICAL;} );
} }
break; break;
case Stat.SPATK: case Stat.SPATK:
if (this.selfTarget) { if (this.selfTarget) {
noEffect = !user.getMoveset().find(m => m instanceof AttackMove && m.category === MoveCategory.SPECIAL); noEffect = !user.getMoveset().find(m => {const mv = m.getMove(); return mv.is("AttackMove") && mv.category === MoveCategory.PHYSICAL;} );
} }
break; break;
case Stat.SPDEF: case Stat.SPDEF:
if (!this.selfTarget) { if (!this.selfTarget) {
noEffect = !user.getMoveset().find(m => m instanceof AttackMove && m.category === MoveCategory.SPECIAL); noEffect = !user.getMoveset().find(m => {const mv = m.getMove(); return mv.is("AttackMove") && mv.category === MoveCategory.PHYSICAL;} );
} }
break; break;
} }
@ -5410,7 +5488,7 @@ export class FrenzyAttr extends MoveEffectAttr {
new Array(turnCount).fill(null).map(() => user.getMoveQueue().push({ move: move.id, targets: [ target.getBattlerIndex() ], ignorePP: true })); new Array(turnCount).fill(null).map(() => user.getMoveQueue().push({ move: move.id, targets: [ target.getBattlerIndex() ], ignorePP: true }));
user.addTag(BattlerTagType.FRENZY, turnCount, move.id, user.id); user.addTag(BattlerTagType.FRENZY, turnCount, move.id, user.id);
} else { } else {
applyMoveAttrs(AddBattlerTagAttr, user, target, move, args); applyMoveAttrs("AddBattlerTagAttr", user, target, move, args);
user.lapseTag(BattlerTagType.FRENZY); // if FRENZY is already in effect (moveQueue.length > 0), lapse the tag user.lapseTag(BattlerTagType.FRENZY); // if FRENZY is already in effect (moveQueue.length > 0), lapse the tag
} }
@ -5418,15 +5496,6 @@ export class FrenzyAttr extends MoveEffectAttr {
} }
} }
export const frenzyMissFunc: UserMoveConditionFunc = (user: Pokemon, move: Move) => {
while (user.getMoveQueue().length && user.getMoveQueue()[0].move === move.id) {
user.getMoveQueue().shift();
}
user.removeTag(BattlerTagType.FRENZY); // FRENZY tag should be disrupted on miss/no effect
return true;
};
/** /**
* Attribute that grants {@link https://bulbapedia.bulbagarden.net/wiki/Semi-invulnerable_turn | semi-invulnerability} to the user during * Attribute that grants {@link https://bulbapedia.bulbagarden.net/wiki/Semi-invulnerable_turn | semi-invulnerability} to the user during
* the associated move's charging phase. Should only be used for {@linkcode ChargingMove | ChargingMoves} as a `chargeAttr`. * the associated move's charging phase. Should only be used for {@linkcode ChargingMove | ChargingMoves} as a `chargeAttr`.
@ -5781,7 +5850,7 @@ export class ProtectAttr extends AddBattlerTagAttr {
while (moveHistory.length) { while (moveHistory.length) {
turnMove = moveHistory.shift(); turnMove = moveHistory.shift();
if (!allMoves[turnMove?.move ?? MoveId.NONE].hasAttr(ProtectAttr) || turnMove?.result !== MoveResult.SUCCESS) { if (!allMoves[turnMove?.move ?? MoveId.NONE].hasAttr("ProtectAttr") || turnMove?.result !== MoveResult.SUCCESS) {
break; break;
} }
timesUsed++; timesUsed++;
@ -5912,7 +5981,7 @@ export class AddArenaTagAttr extends MoveEffectAttr {
} }
if ((move.chance < 0 || move.chance === 100 || user.randBattleSeedInt(100) < move.chance) && user.getLastXMoves(1)[0]?.result === MoveResult.SUCCESS) { if ((move.chance < 0 || move.chance === 100 || user.randBattleSeedInt(100) < move.chance) && user.getLastXMoves(1)[0]?.result === MoveResult.SUCCESS) {
const side = ((this.selfSideTarget ? user : target).isPlayer() !== (move.hasAttr(AddArenaTrapTagAttr) && target === user)) ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; const side = ((this.selfSideTarget ? user : target).isPlayer() !== (move.hasAttr("AddArenaTrapTagAttr") && target === user)) ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
globalScene.arena.addTag(this.tagType, this.turnCount, move.id, user.id, side); globalScene.arena.addTag(this.tagType, this.turnCount, move.id, user.id, side);
return true; return true;
} }
@ -6376,7 +6445,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
return (user, target, move) => { return (user, target, move) => {
const switchOutTarget = (this.selfSwitch ? user : target); const switchOutTarget = (this.selfSwitch ? user : target);
const player = switchOutTarget.isPlayer(); const player = switchOutTarget.isPlayer();
const forceSwitchAttr = move.getAttrs(ForceSwitchOutAttr).find(attr => attr.switchType === SwitchType.FORCE_SWITCH); const forceSwitchAttr = move.getAttrs("ForceSwitchOutAttr").find(attr => attr.switchType === SwitchType.FORCE_SWITCH);
if (!this.selfSwitch) { if (!this.selfSwitch) {
if (move.hitsSubstitute(user, target)) { if (move.hitsSubstitute(user, target)) {
@ -7945,58 +8014,6 @@ const attackedByItemMessageFunc = (user: Pokemon, target: Pokemon, move: Move) =
return message; return message;
}; };
export type MoveAttrFilter = (attr: MoveAttr) => boolean;
function applyMoveAttrsInternal(
attrFilter: MoveAttrFilter,
user: Pokemon | null,
target: Pokemon | null,
move: Move,
args: any[],
): void {
move.attrs.filter((attr) => attrFilter(attr)).forEach((attr) => attr.apply(user, target, move, args));
}
function applyMoveChargeAttrsInternal(
attrFilter: MoveAttrFilter,
user: Pokemon | null,
target: Pokemon | null,
move: ChargingMove,
args: any[],
): void {
move.chargeAttrs.filter((attr) => attrFilter(attr)).forEach((attr) => attr.apply(user, target, move, args));
}
export function applyMoveAttrs(
attrType: Constructor<MoveAttr>,
user: Pokemon | null,
target: Pokemon | null,
move: Move,
...args: any[]
): void {
applyMoveAttrsInternal((attr: MoveAttr) => attr instanceof attrType, user, target, move, args);
}
export function applyFilteredMoveAttrs(
attrFilter: MoveAttrFilter,
user: Pokemon,
target: Pokemon | null,
move: Move,
...args: any[]
): void {
applyMoveAttrsInternal(attrFilter, user, target, move, args);
}
export function applyMoveChargeAttrs(
attrType: Constructor<MoveAttr>,
user: Pokemon | null,
target: Pokemon | null,
move: ChargingMove,
...args: any[]
): void {
applyMoveChargeAttrsInternal((attr: MoveAttr) => attr instanceof attrType, user, target, move, args);
}
export class MoveCondition { export class MoveCondition {
protected func: MoveConditionFunc; protected func: MoveConditionFunc;
@ -8175,73 +8192,232 @@ export type MoveTargetSet = {
multiple: boolean; multiple: boolean;
}; };
export function getMoveTargets(user: Pokemon, move: MoveId, replaceTarget?: MoveTarget): MoveTargetSet { /**
const variableTarget = new NumberHolder(0); * Map of Move attributes to their respective classes. Used for instanceof checks.
user.getOpponents(false).forEach(p => applyMoveAttrs(VariableTargetAttr, user, p, allMoves[move], variableTarget)); */
const MoveAttrs = Object.freeze({
MoveEffectAttr,
MoveHeaderAttr,
MessageHeaderAttr,
AddBattlerTagAttr,
AddBattlerTagHeaderAttr,
BeakBlastHeaderAttr,
PreMoveMessageAttr,
PreUseInterruptAttr,
RespectAttackTypeImmunityAttr,
IgnoreOpponentStatStagesAttr,
HighCritAttr,
CritOnlyAttr,
FixedDamageAttr,
UserHpDamageAttr,
TargetHalfHpDamageAttr,
MatchHpAttr,
CounterDamageAttr,
LevelDamageAttr,
RandomLevelDamageAttr,
ModifiedDamageAttr,
SurviveDamageAttr,
SplashAttr,
CelebrateAttr,
RecoilAttr,
SacrificialAttr,
SacrificialAttrOnHit,
HalfSacrificialAttr,
AddSubstituteAttr,
HealAttr,
PartyStatusCureAttr,
FlameBurstAttr,
SacrificialFullRestoreAttr,
IgnoreWeatherTypeDebuffAttr,
WeatherHealAttr,
PlantHealAttr,
SandHealAttr,
BoostHealAttr,
HealOnAllyAttr,
HitHealAttr,
IncrementMovePriorityAttr,
MultiHitAttr,
ChangeMultiHitTypeAttr,
WaterShurikenMultiHitTypeAttr,
StatusEffectAttr,
MultiStatusEffectAttr,
PsychoShiftEffectAttr,
StealHeldItemChanceAttr,
RemoveHeldItemAttr,
EatBerryAttr,
StealEatBerryAttr,
HealStatusEffectAttr,
BypassSleepAttr,
BypassBurnDamageReductionAttr,
WeatherChangeAttr,
ClearWeatherAttr,
TerrainChangeAttr,
ClearTerrainAttr,
OneHitKOAttr,
InstantChargeAttr,
WeatherInstantChargeAttr,
OverrideMoveEffectAttr,
DelayedAttackAttr,
AwaitCombinedPledgeAttr,
StatStageChangeAttr,
SecretPowerAttr,
PostVictoryStatStageChangeAttr,
AcupressureStatStageChangeAttr,
GrowthStatStageChangeAttr,
CutHpStatStageBoostAttr,
OrderUpStatBoostAttr,
CopyStatsAttr,
InvertStatsAttr,
ResetStatsAttr,
SwapStatStagesAttr,
HpSplitAttr,
VariablePowerAttr,
LessPPMorePowerAttr,
MovePowerMultiplierAttr,
BeatUpAttr,
DoublePowerChanceAttr,
ConsecutiveUsePowerMultiplierAttr,
ConsecutiveUseDoublePowerAttr,
ConsecutiveUseMultiBasePowerAttr,
WeightPowerAttr,
ElectroBallPowerAttr,
GyroBallPowerAttr,
LowHpPowerAttr,
CompareWeightPowerAttr,
HpPowerAttr,
OpponentHighHpPowerAttr,
FirstAttackDoublePowerAttr,
TurnDamagedDoublePowerAttr,
MagnitudePowerAttr,
AntiSunlightPowerDecreaseAttr,
FriendshipPowerAttr,
RageFistPowerAttr,
PositiveStatStagePowerAttr,
PunishmentPowerAttr,
PresentPowerAttr,
WaterShurikenPowerAttr,
SpitUpPowerAttr,
SwallowHealAttr,
MultiHitPowerIncrementAttr,
LastMoveDoublePowerAttr,
CombinedPledgePowerAttr,
CombinedPledgeStabBoostAttr,
RoundPowerAttr,
CueNextRoundAttr,
StatChangeBeforeDmgCalcAttr,
SpectralThiefAttr,
VariableAtkAttr,
TargetAtkUserAtkAttr,
DefAtkAttr,
VariableDefAttr,
DefDefAttr,
VariableAccuracyAttr,
ThunderAccuracyAttr,
StormAccuracyAttr,
AlwaysHitMinimizeAttr,
ToxicAccuracyAttr,
BlizzardAccuracyAttr,
VariableMoveCategoryAttr,
PhotonGeyserCategoryAttr,
TeraMoveCategoryAttr,
TeraBlastPowerAttr,
StatusCategoryOnAllyAttr,
ShellSideArmCategoryAttr,
VariableMoveTypeAttr,
FormChangeItemTypeAttr,
TechnoBlastTypeAttr,
AuraWheelTypeAttr,
RagingBullTypeAttr,
IvyCudgelTypeAttr,
WeatherBallTypeAttr,
TerrainPulseTypeAttr,
HiddenPowerTypeAttr,
TeraBlastTypeAttr,
TeraStarstormTypeAttr,
MatchUserTypeAttr,
CombinedPledgeTypeAttr,
VariableMoveTypeMultiplierAttr,
NeutralDamageAgainstFlyingTypeMultiplierAttr,
IceNoEffectTypeAttr,
FlyingTypeMultiplierAttr,
VariableMoveTypeChartAttr,
FreezeDryAttr,
OneHitKOAccuracyAttr,
SheerColdAccuracyAttr,
MissEffectAttr,
NoEffectAttr,
TypelessAttr,
BypassRedirectAttr,
FrenzyAttr,
SemiInvulnerableAttr,
LeechSeedAttr,
FallDownAttr,
GulpMissileTagAttr,
JawLockAttr,
CurseAttr,
LapseBattlerTagAttr,
RemoveBattlerTagAttr,
FlinchAttr,
ConfuseAttr,
RechargeAttr,
TrapAttr,
ProtectAttr,
IgnoreAccuracyAttr,
FaintCountdownAttr,
RemoveAllSubstitutesAttr,
HitsTagAttr,
HitsTagForDoubleDamageAttr,
AddArenaTagAttr,
RemoveArenaTagsAttr,
AddArenaTrapTagAttr,
AddArenaTrapTagHitAttr,
RemoveArenaTrapAttr,
RemoveScreensAttr,
SwapArenaTagsAttr,
AddPledgeEffectAttr,
RevivalBlessingAttr,
ForceSwitchOutAttr,
ChillyReceptionAttr,
RemoveTypeAttr,
CopyTypeAttr,
CopyBiomeTypeAttr,
ChangeTypeAttr,
AddTypeAttr,
FirstMoveTypeAttr,
CallMoveAttr,
RandomMoveAttr,
RandomMovesetMoveAttr,
NaturePowerAttr,
CopyMoveAttr,
RepeatMoveAttr,
ReducePpMoveAttr,
AttackReducePpMoveAttr,
MovesetCopyMoveAttr,
SketchAttr,
AbilityChangeAttr,
AbilityCopyAttr,
AbilityGiveAttr,
SwitchAbilitiesAttr,
SuppressAbilitiesAttr,
TransformAttr,
SwapStatAttr,
ShiftStatAttr,
AverageStatsAttr,
MoneyAttr,
DestinyBondAttr,
AddBattlerTagIfBoostedAttr,
StatusIfBoostedAttr,
LastResortAttr,
VariableTargetAttr,
AfterYouAttr,
ForceLastAttr,
HitsSameTypeAttr,
ResistLastMoveTypeAttr,
ExposedMoveAttr,
});
let moveTarget: MoveTarget | undefined; /** Map of of move attribute names to their constructors */
if (allMoves[move].hasAttr(VariableTargetAttr)) { export type MoveAttrConstructorMap = typeof MoveAttrs;
moveTarget = variableTarget.value;
} else if (replaceTarget !== undefined) {
moveTarget = replaceTarget;
} else if (move) {
moveTarget = allMoves[move].moveTarget;
} else if (move === undefined) {
moveTarget = MoveTarget.NEAR_ENEMY;
}
const opponents = user.getOpponents(false);
let set: Pokemon[] = [];
let multiple = false;
const ally: Pokemon | undefined = user.getAlly();
switch (moveTarget) {
case MoveTarget.USER:
case MoveTarget.PARTY:
set = [ user ];
break;
case MoveTarget.NEAR_OTHER:
case MoveTarget.OTHER:
case MoveTarget.ALL_NEAR_OTHERS:
case MoveTarget.ALL_OTHERS:
set = !isNullOrUndefined(ally) ? (opponents.concat([ ally ])) : opponents;
multiple = moveTarget === MoveTarget.ALL_NEAR_OTHERS || moveTarget === MoveTarget.ALL_OTHERS;
break;
case MoveTarget.NEAR_ENEMY:
case MoveTarget.ALL_NEAR_ENEMIES:
case MoveTarget.ALL_ENEMIES:
case MoveTarget.ENEMY_SIDE:
set = opponents;
multiple = moveTarget !== MoveTarget.NEAR_ENEMY;
break;
case MoveTarget.RANDOM_NEAR_ENEMY:
set = [ opponents[user.randBattleSeedInt(opponents.length)] ];
break;
case MoveTarget.ATTACKER:
return { targets: [ -1 as BattlerIndex ], multiple: false };
case MoveTarget.NEAR_ALLY:
case MoveTarget.ALLY:
set = !isNullOrUndefined(ally) ? [ ally ] : [];
break;
case MoveTarget.USER_OR_NEAR_ALLY:
case MoveTarget.USER_AND_ALLIES:
case MoveTarget.USER_SIDE:
set = !isNullOrUndefined(ally) ? [ user, ally ] : [ user ];
multiple = moveTarget !== MoveTarget.USER_OR_NEAR_ALLY;
break;
case MoveTarget.ALL:
case MoveTarget.BOTH_SIDES:
set = (!isNullOrUndefined(ally) ? [ user, ally ] : [ user ]).concat(opponents);
multiple = true;
break;
case MoveTarget.CURSE:
const extraTargets = !isNullOrUndefined(ally) ? [ ally ] : [];
set = user.getTypes(true).includes(PokemonType.GHOST) ? (opponents.concat(extraTargets)) : [ user ];
break;
}
return { targets: set.filter(p => p?.isActive(true)).map(p => p.getBattlerIndex()).filter(t => t !== undefined), multiple };
}
export const selfStatLowerMoves: MoveId[] = []; export const selfStatLowerMoves: MoveId[] = [];
@ -11255,7 +11431,7 @@ export function initMoves() {
.attr(StatusEffectAttr, StatusEffect.TOXIC) .attr(StatusEffectAttr, StatusEffect.TOXIC)
); );
allMoves.map(m => { allMoves.map(m => {
if (m.getAttrs(StatStageChangeAttr).some(a => a.selfTarget && a.stages < 0)) { if (m.getAttrs("StatStageChangeAttr").some(a => a.selfTarget && a.stages < 0)) {
selfStatLowerMoves.push(m.id); selfStatLowerMoves.push(m.id);
} }
}); });

View File

@ -0,0 +1,93 @@
import type Pokemon from "#app/field/pokemon";
import { toDmgValue } from "#app/utils/common";
import type { MoveId } from "#enums/move-id";
import { allMoves } from "../data-lists";
import type Move from "./move";
/**
* Wrapper class for the {@linkcode Move} class for Pokemon to interact with.
* These are the moves assigned to a {@linkcode Pokemon} object.
* It links to {@linkcode Move} class via the move ID.
* Compared to {@linkcode Move}, this class also tracks things like
* PP Ups recieved, PP used, etc.
* @see {@linkcode isUsable} - checks if move is restricted, out of PP, or not implemented.
* @see {@linkcode getMove} - returns {@linkcode Move} object by looking it up via ID.
* @see {@linkcode usePp} - removes a point of PP from the move.
* @see {@linkcode getMovePp} - returns amount of PP a move currently has.
* @see {@linkcode getPpRatio} - returns the current PP amount / max PP amount.
* @see {@linkcode getName} - returns name of {@linkcode Move}.
**/
export class PokemonMove {
public moveId: MoveId;
public ppUsed: number;
public ppUp: number;
public virtual: boolean;
/**
* If defined and nonzero, overrides the maximum PP of the move (e.g., due to move being copied by Transform).
* This also nullifies all effects of `ppUp`.
*/
public maxPpOverride?: number;
constructor(moveId: MoveId, ppUsed = 0, ppUp = 0, virtual = false, maxPpOverride?: number) {
this.moveId = moveId;
this.ppUsed = ppUsed;
this.ppUp = ppUp;
this.virtual = virtual;
this.maxPpOverride = maxPpOverride;
}
/**
* Checks whether the move can be selected or performed by a Pokemon, without consideration for the move's targets.
* The move is unusable if it is out of PP, restricted by an effect, or unimplemented.
*
* @param pokemon - {@linkcode Pokemon} that would be using this move
* @param ignorePp - If `true`, skips the PP check
* @param ignoreRestrictionTags - If `true`, skips the check for move restriction tags (see {@link MoveRestrictionBattlerTag})
* @returns `true` if the move can be selected and used by the Pokemon, otherwise `false`.
*/
isUsable(pokemon: Pokemon, ignorePp = false, ignoreRestrictionTags = false): boolean {
if (this.moveId && !ignoreRestrictionTags && pokemon.isMoveRestricted(this.moveId, pokemon)) {
return false;
}
if (this.getMove().name.endsWith(" (N)")) {
return false;
}
return ignorePp || this.ppUsed < this.getMovePp() || this.getMove().pp === -1;
}
getMove(): Move {
return allMoves[this.moveId];
}
/**
* Sets {@link ppUsed} for this move and ensures the value does not exceed {@link getMovePp}
* @param count Amount of PP to use
*/
usePp(count = 1) {
this.ppUsed = Math.min(this.ppUsed + count, this.getMovePp());
}
getMovePp(): number {
return this.maxPpOverride || this.getMove().pp + this.ppUp * toDmgValue(this.getMove().pp / 5);
}
getPpRatio(): number {
return 1 - this.ppUsed / this.getMovePp();
}
getName(): string {
return this.getMove().name;
}
/**
* Copies an existing move or creates a valid {@linkcode PokemonMove} object from json representing one
* @param source The data for the move to copy; can be a {@linkcode PokemonMove} or JSON object representing one
* @returns A valid {@linkcode PokemonMove} object
*/
static loadMove(source: PokemonMove | any): PokemonMove {
return new PokemonMove(source.moveId, source.ppUsed, source.ppUp, source.virtual, source.maxPpOverride);
}
}

View File

@ -7,7 +7,8 @@ import {
transitionMysteryEncounterIntroVisuals, transitionMysteryEncounterIntroVisuals,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils"; } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { EnemyPokemon, PokemonMove } from "#app/field/pokemon"; import { EnemyPokemon } from "#app/field/pokemon";
import { PokemonMove } from "#app/data/moves/pokemon-move";
import type { BerryModifierType, PokemonHeldItemModifierType } from "#app/modifier/modifier-type"; import type { BerryModifierType, PokemonHeldItemModifierType } from "#app/modifier/modifier-type";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
@ -25,7 +26,7 @@ import { getPokemonSpecies } from "#app/data/pokemon-species";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { randInt } from "#app/utils/common"; import { randInt } from "#app/utils/common";
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#enums/battler-index";
import { import {
applyModifierTypeToPlayerPokemon, applyModifierTypeToPlayerPokemon,
catchPokemon, catchPokemon,

View File

@ -24,7 +24,7 @@ import { TrainerType } from "#enums/trainer-type";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { PokemonMove } from "#app/field/pokemon"; import { PokemonMove } from "#app/data/moves/pokemon-move";
import { getEncounterText, showEncounterDialogue } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { getEncounterText, showEncounterDialogue } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";

View File

@ -37,11 +37,11 @@ import { UiMode } from "#enums/ui-mode";
import i18next from "i18next"; import i18next from "i18next";
import type { OptionSelectConfig } from "#app/ui/abstact-option-select-ui-handler"; import type { OptionSelectConfig } from "#app/ui/abstact-option-select-ui-handler";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import { PokemonMove } from "#app/field/pokemon"; import { PokemonMove } from "#app/data/moves/pokemon-move";
import { Ability } from "#app/data/abilities/ability-class"; import { Ability } from "#app/data/abilities/ability-class";
import { BerryModifier } from "#app/modifier/modifier"; import { BerryModifier } from "#app/modifier/modifier";
import { BerryType } from "#enums/berry-type"; import { BerryType } from "#enums/berry-type";
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#enums/battler-index";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import { EncounterBattleAnim } from "#app/data/battle-anims"; import { EncounterBattleAnim } from "#app/data/battle-anims";
import { MoveCategory } from "#enums/MoveCategory"; import { MoveCategory } from "#enums/MoveCategory";

View File

@ -1,4 +1,4 @@
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#enums/battler-index";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { EncounterBattleAnim } from "#app/data/battle-anims"; import { EncounterBattleAnim } from "#app/data/battle-anims";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
@ -23,7 +23,8 @@ import { getPokemonSpecies } from "#app/data/pokemon-species";
import { TrainerSlot } from "#enums/trainer-slot"; import { TrainerSlot } from "#enums/trainer-slot";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { EnemyPokemon, PokemonMove } from "#app/field/pokemon"; import { EnemyPokemon } from "#app/field/pokemon";
import { PokemonMove } from "#app/data/moves/pokemon-move";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
import PokemonData from "#app/system/pokemon-data"; import PokemonData from "#app/system/pokemon-data";

View File

@ -7,7 +7,8 @@ import {
setEncounterExp, setEncounterExp,
setEncounterRewards, setEncounterRewards,
} from "#app/data/mystery-encounters/utils/encounter-phase-utils"; } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import type { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import type { PokemonMove } from "#app/data/moves/pokemon-move";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";

View File

@ -24,9 +24,9 @@ import { SpeciesId } from "#enums/species-id";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { Gender } from "#app/data/gender"; import { Gender } from "#app/data/gender";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#enums/battler-index";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { PokemonMove } from "#app/field/pokemon"; import { PokemonMove } from "#app/data/moves/pokemon-move";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import { EncounterBattleAnim } from "#app/data/battle-anims"; import { EncounterBattleAnim } from "#app/data/battle-anims";
import { WeatherType } from "#enums/weather-type"; import { WeatherType } from "#enums/weather-type";

View File

@ -13,7 +13,7 @@ import { MysteryEncounterOptionBuilder } from "#app/data/mystery-encounters/myst
import { TrainerSlot } from "#enums/trainer-slot"; import { TrainerSlot } from "#enums/trainer-slot";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { FieldPosition } from "#app/field/pokemon"; import { FieldPosition } from "#enums/field-position";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements"; import { MoneyRequirement } from "#app/data/mystery-encounters/mystery-encounter-requirements";
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
@ -25,7 +25,7 @@ import { getPokemonNameWithAffix } from "#app/messages";
import { PlayerGender } from "#enums/player-gender"; import { PlayerGender } from "#enums/player-gender";
import { getPokeballAtlasKey, getPokeballTintColor } from "#app/data/pokeball"; import { getPokeballAtlasKey, getPokeballTintColor } from "#app/data/pokeball";
import { addPokeballOpenParticles } from "#app/field/anims"; import { addPokeballOpenParticles } from "#app/field/anims";
import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms"; import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms/form-change-triggers";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
import { Nature } from "#enums/nature"; import { Nature } from "#enums/nature";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";

View File

@ -33,7 +33,8 @@ import {
} from "#app/utils/common"; } from "#app/utils/common";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { EnemyPokemon, PokemonMove } from "#app/field/pokemon"; import { EnemyPokemon } from "#app/field/pokemon";
import { PokemonMove } from "#app/data/moves/pokemon-move";
import type { PokemonHeldItemModifier } from "#app/modifier/modifier"; import type { PokemonHeldItemModifier } from "#app/modifier/modifier";
import { import {
HiddenAbilityRateBoosterModifier, HiddenAbilityRateBoosterModifier,

View File

@ -11,7 +11,7 @@ import { applyDamageToPokemon } from "#app/data/mystery-encounters/utils/encount
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { PokemonMove } from "#app/field/pokemon"; import { PokemonMove } from "#app/data/moves/pokemon-move";
const OPTION_1_REQUIRED_MOVE = MoveId.SURF; const OPTION_1_REQUIRED_MOVE = MoveId.SURF;
const OPTION_2_REQUIRED_MOVE = MoveId.FLY; const OPTION_2_REQUIRED_MOVE = MoveId.FLY;

View File

@ -21,8 +21,9 @@ import {
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { Nature } from "#enums/nature"; import { Nature } from "#enums/nature";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#enums/battler-index";
import { AiType, PokemonMove } from "#app/field/pokemon"; import { PokemonMove } from "#app/data/moves/pokemon-move";
import { AiType } from "#enums/ai-type";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";

View File

@ -17,11 +17,11 @@ import { getPokemonSpecies } from "#app/data/pokemon-species";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import { Nature } from "#enums/nature"; import { Nature } from "#enums/nature";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { PokemonMove } from "#app/field/pokemon"; import { PokemonMove } from "#app/data/moves/pokemon-move";
import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { queueEncounterMessage, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { modifyPlayerPokemonBST } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils"; import { modifyPlayerPokemonBST } from "#app/data/mystery-encounters/utils/encounter-pokemon-utils";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#enums/battler-index";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { BerryType } from "#enums/berry-type"; import { BerryType } from "#enums/berry-type";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";

View File

@ -23,7 +23,7 @@ import { Nature } from "#enums/nature";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import { BerryType } from "#enums/berry-type"; import { BerryType } from "#enums/berry-type";
import { Stat } from "#enums/stat"; import { Stat } from "#enums/stat";
import { SpeciesFormChangeAbilityTrigger } from "#app/data/pokemon-forms"; import { SpeciesFormChangeAbilityTrigger } from "#app/data/pokemon-forms/form-change-triggers";
import { applyPostBattleInitAbAttrs, PostBattleInitAbAttr } from "#app/data/abilities/ability"; import { applyPostBattleInitAbAttrs, PostBattleInitAbAttr } from "#app/data/abilities/ability";
import { showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { showEncounterDialogue, showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";

View File

@ -12,7 +12,7 @@ import { speciesStarterCosts } from "#app/data/balance/starters";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import type { PokemonHeldItemModifier } from "#app/modifier/modifier"; import type { PokemonHeldItemModifier } from "#app/modifier/modifier";
import { AbilityAttr } from "#app/system/game-data"; import { AbilityAttr } from "#enums/ability-attr";
import PokemonData from "#app/system/pokemon-data"; import PokemonData from "#app/system/pokemon-data";
import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler"; import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import { isNullOrUndefined, randSeedShuffle } from "#app/utils/common"; import { isNullOrUndefined, randSeedShuffle } from "#app/utils/common";

View File

@ -24,8 +24,8 @@ import i18next from "#app/plugins/i18n";
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#app/modifier/modifier-tier";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#enums/battler-index";
import { PokemonMove } from "#app/field/pokemon"; import { PokemonMove } from "#app/data/moves/pokemon-move";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES } from "#app/constants";
import { randSeedInt } from "#app/utils/common"; import { randSeedInt } from "#app/utils/common";

View File

@ -10,7 +10,7 @@ import {
import { CHARMING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups"; import { CHARMING_MOVES } from "#app/data/mystery-encounters/requirements/requirement-groups";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import type { EnemyPokemon } from "#app/field/pokemon"; import type { EnemyPokemon } from "#app/field/pokemon";
import { PokemonMove } from "#app/field/pokemon"; import { PokemonMove } from "#app/data/moves/pokemon-move";
import { MysteryEncounterType } from "#enums/mystery-encounter-type"; import { MysteryEncounterType } from "#enums/mystery-encounter-type";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter"; import type MysteryEncounter from "#app/data/mystery-encounters/mystery-encounter";
@ -29,8 +29,7 @@ import {
import PokemonData from "#app/system/pokemon-data"; import PokemonData from "#app/system/pokemon-data";
import { isNullOrUndefined, randSeedInt } from "#app/utils/common"; import { isNullOrUndefined, randSeedInt } from "#app/utils/common";
import type { MoveId } from "#enums/move-id"; import type { MoveId } from "#enums/move-id";
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#enums/battler-index";
import { SelfStatusMove } from "#app/data/moves/move";
import { PokeballType } from "#enums/pokeball"; import { PokeballType } from "#enums/pokeball";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { queueEncounterMessage } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
@ -175,7 +174,7 @@ export const UncommonBreedEncounter: MysteryEncounter = MysteryEncounterBuilder.
// Check what type of move the egg move is to determine target // Check what type of move the egg move is to determine target
const pokemonMove = new PokemonMove(eggMove); const pokemonMove = new PokemonMove(eggMove);
const move = pokemonMove.getMove(); const move = pokemonMove.getMove();
const target = move instanceof SelfStatusMove ? BattlerIndex.ENEMY : BattlerIndex.PLAYER; const target = move.is("SelfStatusMove") ? BattlerIndex.ENEMY : BattlerIndex.PLAYER;
encounter.startOfBattleEffects.push({ encounter.startOfBattleEffects.push({
sourceBattlerIndex: BattlerIndex.ENEMY, sourceBattlerIndex: BattlerIndex.ENEMY,

View File

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

View File

@ -2,7 +2,9 @@ import { globalScene } from "#app/global-scene";
import { allAbilities } from "../data-lists"; import { allAbilities } from "../data-lists";
import { EvolutionItem, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import { EvolutionItem, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions";
import { Nature } from "#enums/nature"; import { Nature } from "#enums/nature";
import { FormChangeItem, pokemonFormChanges, SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms"; import { pokemonFormChanges } from "#app/data/pokemon-forms";
import { SpeciesFormChangeItemTrigger } from "../pokemon-forms/form-change-triggers";
import { FormChangeItem } from "#enums/form-change-item";
import { StatusEffect } from "#enums/status-effect"; import { StatusEffect } from "#enums/status-effect";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import { WeatherType } from "#enums/weather-type"; import { WeatherType } from "#enums/weather-type";

View File

@ -1,5 +1,6 @@
import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils"; import type { EnemyPartyConfig } from "#app/data/mystery-encounters/utils/encounter-phase-utils";
import type { PlayerPokemon, PokemonMove } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import type { PokemonMove } from "../moves/pokemon-move";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { capitalizeFirstLetter, isNullOrUndefined } from "#app/utils/common"; import { capitalizeFirstLetter, isNullOrUndefined } from "#app/utils/common";
import type { MysteryEncounterType } from "#enums/mystery-encounter-type"; import type { MysteryEncounterType } from "#enums/mystery-encounter-type";
@ -20,11 +21,11 @@ import {
StatusEffectRequirement, StatusEffectRequirement,
WaveRangeRequirement, WaveRangeRequirement,
} from "./mystery-encounter-requirements"; } from "./mystery-encounter-requirements";
import type { BattlerIndex } from "#app/battle"; import type { BattlerIndex } from "#enums/battler-index";
import { MysteryEncounterTier } from "#enums/mystery-encounter-tier"; import { MysteryEncounterTier } from "#enums/mystery-encounter-tier";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode"; import { MysteryEncounterOptionMode } from "#enums/mystery-encounter-option-mode";
import type { GameModes } from "#app/game-mode"; import type { GameModes } from "#enums/game-modes";
import type { EncounterAnim } from "#enums/encounter-anims"; import type { EncounterAnim } from "#enums/encounter-anims";
import type { Challenges } from "#enums/challenges"; import type { Challenges } from "#enums/challenges";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";

View File

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

View File

@ -1,5 +1,5 @@
import type Battle from "#app/battle"; import type Battle from "#app/battle";
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#enums/battler-index";
import { BattleType } from "#enums/battle-type"; import { BattleType } from "#enums/battle-type";
import { biomeLinks, BiomePoolTier } from "#app/data/balance/biomes"; import { biomeLinks, BiomePoolTier } from "#app/data/balance/biomes";
import type MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option"; import type MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option";
@ -8,9 +8,12 @@ import {
WEIGHT_INCREMENT_ON_SPAWN_MISS, WEIGHT_INCREMENT_ON_SPAWN_MISS,
} from "#app/data/mystery-encounters/mystery-encounters"; } from "#app/data/mystery-encounters/mystery-encounters";
import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils"; import { showEncounterText } from "#app/data/mystery-encounters/utils/encounter-dialogue-utils";
import type { AiType, PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import type { AiType } from "#enums/ai-type";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { EnemyPokemon, FieldPosition, PokemonMove } from "#app/field/pokemon"; import { EnemyPokemon } from "#app/field/pokemon";
import { PokemonMove } from "#app/data/moves/pokemon-move";
import { FieldPosition } from "#enums/field-position";
import type { CustomModifierSettings, ModifierType } from "#app/modifier/modifier-type"; import type { CustomModifierSettings, ModifierType } from "#app/modifier/modifier-type";
import { import {
getPartyLuckValue, getPartyLuckValue,
@ -30,7 +33,8 @@ import type { BattlerTagType } from "#enums/battler-tag-type";
import { BiomeId } from "#enums/biome-id"; import { BiomeId } from "#enums/biome-id";
import type { TrainerType } from "#enums/trainer-type"; import type { TrainerType } from "#enums/trainer-type";
import i18next from "i18next"; import i18next from "i18next";
import Trainer, { TrainerVariant } from "#app/field/trainer"; import Trainer from "#app/field/trainer";
import { TrainerVariant } from "#enums/trainer-variant";
import type { Gender } from "#app/data/gender"; import type { Gender } from "#app/data/gender";
import type { Nature } from "#enums/nature"; import type { Nature } from "#enums/nature";
import type { MoveId } from "#enums/move-id"; import type { MoveId } from "#enums/move-id";

View File

@ -1,139 +1,30 @@
import { PokemonFormChangeItemModifier } from "../modifier/modifier";
import type Pokemon from "../field/pokemon"; import type Pokemon from "../field/pokemon";
import { StatusEffect } from "#enums/status-effect";
import { allMoves } from "./data-lists"; import { allMoves } from "./data-lists";
import { MoveCategory } from "#enums/MoveCategory"; import { MoveCategory } from "#enums/MoveCategory";
import type { Constructor, nil } from "#app/utils/common"; import type { Constructor, nil } from "#app/utils/common";
import { AbilityId } from "#enums/ability-id"; import { AbilityId } from "#enums/ability-id";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import type { TimeOfDay } from "#enums/time-of-day";
import { getPokemonNameWithAffix } from "#app/messages";
import i18next from "i18next";
import { WeatherType } from "#enums/weather-type"; import { WeatherType } from "#enums/weather-type";
import { Challenges } from "#app/enums/challenges";
import { SpeciesFormKey } from "#enums/species-form-key"; import { SpeciesFormKey } from "#enums/species-form-key";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { FormChangeItem } from "#enums/form-change-item";
export enum FormChangeItem { import {
NONE, MeloettaFormChangePostMoveTrigger,
SpeciesDefaultFormMatchTrigger,
ABOMASITE, SpeciesFormChangeAbilityTrigger,
ABSOLITE, SpeciesFormChangeActiveTrigger,
AERODACTYLITE, SpeciesFormChangeCompoundTrigger,
AGGRONITE, SpeciesFormChangeItemTrigger,
ALAKAZITE, SpeciesFormChangeLapseTeraTrigger,
ALTARIANITE, SpeciesFormChangeManualTrigger,
AMPHAROSITE, SpeciesFormChangeMoveLearnedTrigger,
AUDINITE, SpeciesFormChangePreMoveTrigger,
BANETTITE, SpeciesFormChangeRevertWeatherFormTrigger,
BEEDRILLITE, SpeciesFormChangeTeraTrigger,
BLASTOISINITE, type SpeciesFormChangeTrigger,
BLAZIKENITE, SpeciesFormChangeWeatherTrigger,
CAMERUPTITE, } from "./pokemon-forms/form-change-triggers";
CHARIZARDITE_X,
CHARIZARDITE_Y,
DIANCITE,
GALLADITE,
GARCHOMPITE,
GARDEVOIRITE,
GENGARITE,
GLALITITE,
GYARADOSITE,
HERACRONITE,
HOUNDOOMINITE,
KANGASKHANITE,
LATIASITE,
LATIOSITE,
LOPUNNITE,
LUCARIONITE,
MANECTITE,
MAWILITE,
MEDICHAMITE,
METAGROSSITE,
MEWTWONITE_X,
MEWTWONITE_Y,
PIDGEOTITE,
PINSIRITE,
RAYQUAZITE,
SABLENITE,
SALAMENCITE,
SCEPTILITE,
SCIZORITE,
SHARPEDONITE,
SLOWBRONITE,
STEELIXITE,
SWAMPERTITE,
TYRANITARITE,
VENUSAURITE,
BLUE_ORB = 50,
RED_ORB,
ADAMANT_CRYSTAL,
LUSTROUS_GLOBE,
GRISEOUS_CORE,
REVEAL_GLASS,
MAX_MUSHROOMS,
DARK_STONE,
LIGHT_STONE,
PRISON_BOTTLE,
RUSTED_SWORD,
RUSTED_SHIELD,
ICY_REINS_OF_UNITY,
SHADOW_REINS_OF_UNITY,
ULTRANECROZIUM_Z,
SHARP_METEORITE = 100,
HARD_METEORITE,
SMOOTH_METEORITE,
GRACIDEA,
SHOCK_DRIVE,
BURN_DRIVE,
CHILL_DRIVE,
DOUSE_DRIVE,
N_SOLARIZER,
N_LUNARIZER,
WELLSPRING_MASK,
HEARTHFLAME_MASK,
CORNERSTONE_MASK,
FIST_PLATE,
SKY_PLATE,
TOXIC_PLATE,
EARTH_PLATE,
STONE_PLATE,
INSECT_PLATE,
SPOOKY_PLATE,
IRON_PLATE,
FLAME_PLATE,
SPLASH_PLATE,
MEADOW_PLATE,
ZAP_PLATE,
MIND_PLATE,
ICICLE_PLATE,
DRACO_PLATE,
DREAD_PLATE,
PIXIE_PLATE,
BLANK_PLATE, // TODO: Find a potential use for this
LEGEND_PLATE, // TODO: Find a potential use for this
FIGHTING_MEMORY,
FLYING_MEMORY,
POISON_MEMORY,
GROUND_MEMORY,
ROCK_MEMORY,
BUG_MEMORY,
GHOST_MEMORY,
STEEL_MEMORY,
FIRE_MEMORY,
WATER_MEMORY,
GRASS_MEMORY,
ELECTRIC_MEMORY,
PSYCHIC_MEMORY,
ICE_MEMORY,
DRAGON_MEMORY,
DARK_MEMORY,
FAIRY_MEMORY,
NORMAL_MEMORY, // TODO: Find a potential use for this
}
export type SpeciesFormChangeConditionPredicate = (p: Pokemon) => boolean; export type SpeciesFormChangeConditionPredicate = (p: Pokemon) => boolean;
export type SpeciesFormChangeConditionEnforceFunc = (p: Pokemon) => void; export type SpeciesFormChangeConditionEnforceFunc = (p: Pokemon) => void;
@ -214,347 +105,6 @@ export class SpeciesFormChangeCondition {
} }
} }
export abstract class SpeciesFormChangeTrigger {
public description = "";
canChange(_pokemon: Pokemon): boolean {
return true;
}
hasTriggerType(triggerType: Constructor<SpeciesFormChangeTrigger>): boolean {
return this instanceof triggerType;
}
}
export class SpeciesFormChangeManualTrigger extends SpeciesFormChangeTrigger {}
export class SpeciesFormChangeAbilityTrigger extends SpeciesFormChangeTrigger {
public description: string = i18next.t("pokemonEvolutions:Forms.ability");
}
export class SpeciesFormChangeCompoundTrigger {
public description = "";
public triggers: SpeciesFormChangeTrigger[];
constructor(...triggers: SpeciesFormChangeTrigger[]) {
this.triggers = triggers;
this.description = this.triggers
.filter(trigger => trigger?.description?.length > 0)
.map(trigger => trigger.description)
.join(", ");
}
canChange(pokemon: Pokemon): boolean {
for (const trigger of this.triggers) {
if (!trigger.canChange(pokemon)) {
return false;
}
}
return true;
}
hasTriggerType(triggerType: Constructor<SpeciesFormChangeTrigger>): boolean {
return !!this.triggers.find(t => t.hasTriggerType(triggerType));
}
}
export class SpeciesFormChangeItemTrigger extends SpeciesFormChangeTrigger {
public item: FormChangeItem;
public active: boolean;
constructor(item: FormChangeItem, active = true) {
super();
this.item = item;
this.active = active;
this.description = this.active
? i18next.t("pokemonEvolutions:Forms.item", {
item: i18next.t(`modifierType:FormChangeItem.${FormChangeItem[this.item]}`),
})
: i18next.t("pokemonEvolutions:Forms.deactivateItem", {
item: i18next.t(`modifierType:FormChangeItem.${FormChangeItem[this.item]}`),
});
}
canChange(pokemon: Pokemon): boolean {
return !!globalScene.findModifier(
m =>
m instanceof PokemonFormChangeItemModifier &&
m.pokemonId === pokemon.id &&
m.formChangeItem === this.item &&
m.active === this.active,
);
}
}
export class SpeciesFormChangeTimeOfDayTrigger extends SpeciesFormChangeTrigger {
public timesOfDay: TimeOfDay[];
constructor(...timesOfDay: TimeOfDay[]) {
super();
this.timesOfDay = timesOfDay;
this.description = i18next.t("pokemonEvolutions:Forms.timeOfDay");
}
canChange(_pokemon: Pokemon): boolean {
return this.timesOfDay.indexOf(globalScene.arena.getTimeOfDay()) > -1;
}
}
export class SpeciesFormChangeActiveTrigger extends SpeciesFormChangeTrigger {
public active: boolean;
constructor(active = false) {
super();
this.active = active;
this.description = this.active
? i18next.t("pokemonEvolutions:Forms.enter")
: i18next.t("pokemonEvolutions:Forms.leave");
}
canChange(pokemon: Pokemon): boolean {
return pokemon.isActive(true) === this.active;
}
}
export class SpeciesFormChangeStatusEffectTrigger extends SpeciesFormChangeTrigger {
public statusEffects: StatusEffect[];
public invert: boolean;
constructor(statusEffects: StatusEffect | StatusEffect[], invert = false) {
super();
if (!Array.isArray(statusEffects)) {
statusEffects = [statusEffects];
}
this.statusEffects = statusEffects;
this.invert = invert;
this.description = i18next.t("pokemonEvolutions:Forms.statusEffect");
}
canChange(pokemon: Pokemon): boolean {
return this.statusEffects.indexOf(pokemon.status?.effect || StatusEffect.NONE) > -1 !== this.invert;
}
}
export class SpeciesFormChangeMoveLearnedTrigger extends SpeciesFormChangeTrigger {
public move: MoveId;
public known: boolean;
constructor(move: MoveId, known = true) {
super();
this.move = move;
this.known = known;
const moveKey = MoveId[this.move]
.split("_")
.filter(f => f)
.map((f, i) => (i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()))
.join("") as unknown as string;
this.description = known
? i18next.t("pokemonEvolutions:Forms.moveLearned", {
move: i18next.t(`move:${moveKey}.name`),
})
: i18next.t("pokemonEvolutions:Forms.moveForgotten", {
move: i18next.t(`move:${moveKey}.name`),
});
}
canChange(pokemon: Pokemon): boolean {
return !!pokemon.moveset.filter(m => m.moveId === this.move).length === this.known;
}
}
export abstract class SpeciesFormChangeMoveTrigger extends SpeciesFormChangeTrigger {
public movePredicate: (m: MoveId) => boolean;
public used: boolean;
constructor(move: MoveId | ((m: MoveId) => boolean), used = true) {
super();
this.movePredicate = typeof move === "function" ? move : (m: MoveId) => m === move;
this.used = used;
}
}
export class SpeciesFormChangePreMoveTrigger extends SpeciesFormChangeMoveTrigger {
description = i18next.t("pokemonEvolutions:Forms.preMove");
canChange(pokemon: Pokemon): boolean {
const command = globalScene.currentBattle.turnCommands[pokemon.getBattlerIndex()];
return !!command?.move && this.movePredicate(command.move.move) === this.used;
}
}
export class SpeciesFormChangePostMoveTrigger extends SpeciesFormChangeMoveTrigger {
description = i18next.t("pokemonEvolutions:Forms.postMove");
canChange(pokemon: Pokemon): boolean {
return (
pokemon.summonData && !!pokemon.getLastXMoves(1).filter(m => this.movePredicate(m.move)).length === this.used
);
}
}
export class MeloettaFormChangePostMoveTrigger extends SpeciesFormChangePostMoveTrigger {
override canChange(pokemon: Pokemon): boolean {
if (globalScene.gameMode.hasChallenge(Challenges.SINGLE_TYPE)) {
return false;
}
// Meloetta will not transform if it has the ability Sheer Force when using Relic Song
if (pokemon.hasAbility(AbilityId.SHEER_FORCE)) {
return false;
}
return super.canChange(pokemon);
}
}
export class SpeciesDefaultFormMatchTrigger extends SpeciesFormChangeTrigger {
private formKey: string;
constructor(formKey: string) {
super();
this.formKey = formKey;
this.description = "";
}
canChange(pokemon: Pokemon): boolean {
return (
this.formKey ===
pokemon.species.forms[globalScene.getSpeciesFormIndex(pokemon.species, pokemon.gender, pokemon.getNature(), true)]
.formKey
);
}
}
/**
* Class used for triggering form changes based on the user's Tera type.
* Used by Ogerpon and Terapagos.
* @extends SpeciesFormChangeTrigger
*/
export class SpeciesFormChangeTeraTrigger extends SpeciesFormChangeTrigger {
description = i18next.t("pokemonEvolutions:Forms.tera");
}
/**
* Class used for triggering form changes based on the user's lapsed Tera type.
* Used by Ogerpon and Terapagos.
* @extends SpeciesFormChangeTrigger
*/
export class SpeciesFormChangeLapseTeraTrigger extends SpeciesFormChangeTrigger {
description = i18next.t("pokemonEvolutions:Forms.teraLapse");
}
/**
* Class used for triggering form changes based on weather.
* Used by Castform and Cherrim.
* @extends SpeciesFormChangeTrigger
*/
export class SpeciesFormChangeWeatherTrigger extends SpeciesFormChangeTrigger {
/** The ability that triggers the form change */
public ability: AbilityId;
/** The list of weathers that trigger the form change */
public weathers: WeatherType[];
constructor(ability: AbilityId, weathers: WeatherType[]) {
super();
this.ability = ability;
this.weathers = weathers;
this.description = i18next.t("pokemonEvolutions:Forms.weather");
}
/**
* Checks if the Pokemon has the required ability and is in the correct weather while
* the weather or ability is also not suppressed.
* @param {Pokemon} pokemon the pokemon that is trying to do the form change
* @returns `true` if the Pokemon can change forms, `false` otherwise
*/
canChange(pokemon: Pokemon): boolean {
const currentWeather = globalScene.arena.weather?.weatherType ?? WeatherType.NONE;
const isWeatherSuppressed = globalScene.arena.weather?.isEffectSuppressed();
const isAbilitySuppressed = pokemon.summonData.abilitySuppressed;
return (
!isAbilitySuppressed &&
!isWeatherSuppressed &&
pokemon.hasAbility(this.ability) &&
this.weathers.includes(currentWeather)
);
}
}
/**
* Class used for reverting to the original form when the weather runs out
* or when the user loses the ability/is suppressed.
* Used by Castform and Cherrim.
* @extends SpeciesFormChangeTrigger
*/
export class SpeciesFormChangeRevertWeatherFormTrigger extends SpeciesFormChangeTrigger {
/** The ability that triggers the form change*/
public ability: AbilityId;
/** The list of weathers that will also trigger a form change to original form */
public weathers: WeatherType[];
constructor(ability: AbilityId, weathers: WeatherType[]) {
super();
this.ability = ability;
this.weathers = weathers;
this.description = i18next.t("pokemonEvolutions:Forms.weatherRevert");
}
/**
* Checks if the Pokemon has the required ability and the weather is one that will revert
* the Pokemon to its original form or the weather or ability is suppressed
* @param {Pokemon} pokemon the pokemon that is trying to do the form change
* @returns `true` if the Pokemon will revert to its original form, `false` otherwise
*/
canChange(pokemon: Pokemon): boolean {
if (pokemon.hasAbility(this.ability, false, true)) {
const currentWeather = globalScene.arena.weather?.weatherType ?? WeatherType.NONE;
const isWeatherSuppressed = globalScene.arena.weather?.isEffectSuppressed();
const isAbilitySuppressed = pokemon.summonData.abilitySuppressed;
const summonDataAbility = pokemon.summonData.ability;
const isAbilityChanged = summonDataAbility !== this.ability && summonDataAbility !== AbilityId.NONE;
if (this.weathers.includes(currentWeather) || isWeatherSuppressed || isAbilitySuppressed || isAbilityChanged) {
return true;
}
}
return false;
}
}
export function getSpeciesFormChangeMessage(pokemon: Pokemon, formChange: SpeciesFormChange, preName: string): string {
const isMega = formChange.formKey.indexOf(SpeciesFormKey.MEGA) > -1;
const isGmax = formChange.formKey.indexOf(SpeciesFormKey.GIGANTAMAX) > -1;
const isEmax = formChange.formKey.indexOf(SpeciesFormKey.ETERNAMAX) > -1;
const isRevert = !isMega && formChange.formKey === pokemon.species.forms[0].formKey;
if (isMega) {
return i18next.t("battlePokemonForm:megaChange", {
preName,
pokemonName: pokemon.name,
});
}
if (isGmax) {
return i18next.t("battlePokemonForm:gigantamaxChange", {
preName,
pokemonName: pokemon.name,
});
}
if (isEmax) {
return i18next.t("battlePokemonForm:eternamaxChange", {
preName,
pokemonName: pokemon.name,
});
}
if (isRevert) {
return i18next.t("battlePokemonForm:revertChange", {
pokemonName: getPokemonNameWithAffix(pokemon),
});
}
if (pokemon.getAbility().id === AbilityId.DISGUISE) {
return i18next.t("battlePokemonForm:disguiseChange");
}
return i18next.t("battlePokemonForm:formChange", { preName });
}
/** /**
* Gives a condition for form changing checking if a species is registered as caught in the player's dex data. * Gives a condition for form changing checking if a species is registered as caught in the player's dex data.
* Used for fusion forms such as Kyurem and Necrozma. * Used for fusion forms such as Kyurem and Necrozma.

View File

@ -0,0 +1,348 @@
import i18next from "i18next";
import type { Constructor } from "#app/utils/common";
import type { TimeOfDay } from "#enums/time-of-day";
import type Pokemon from "#app/field/pokemon";
import type { SpeciesFormChange } from "#app/data/pokemon-forms";
import type { PokemonFormChangeItemModifier } from "#app/modifier/modifier";
import { getPokemonNameWithAffix } from "#app/messages";
import { globalScene } from "#app/global-scene";
import { FormChangeItem } from "#enums/form-change-item";
import { AbilityId } from "#enums/ability-id";
import { Challenges } from "#enums/challenges";
import { MoveId } from "#enums/move-id";
import { SpeciesFormKey } from "#enums/species-form-key";
import { StatusEffect } from "#enums/status-effect";
import { WeatherType } from "#enums/weather-type";
export abstract class SpeciesFormChangeTrigger {
public description = "";
canChange(_pokemon: Pokemon): boolean {
return true;
}
hasTriggerType(triggerType: Constructor<SpeciesFormChangeTrigger>): boolean {
return this instanceof triggerType;
}
}
export class SpeciesFormChangeManualTrigger extends SpeciesFormChangeTrigger {}
export class SpeciesFormChangeAbilityTrigger extends SpeciesFormChangeTrigger {
public description: string = i18next.t("pokemonEvolutions:Forms.ability");
}
export class SpeciesFormChangeCompoundTrigger {
public description = "";
public triggers: SpeciesFormChangeTrigger[];
constructor(...triggers: SpeciesFormChangeTrigger[]) {
this.triggers = triggers;
this.description = this.triggers
.filter(trigger => trigger?.description?.length > 0)
.map(trigger => trigger.description)
.join(", ");
}
canChange(pokemon: Pokemon): boolean {
for (const trigger of this.triggers) {
if (!trigger.canChange(pokemon)) {
return false;
}
}
return true;
}
hasTriggerType(triggerType: Constructor<SpeciesFormChangeTrigger>): boolean {
return !!this.triggers.find(t => t.hasTriggerType(triggerType));
}
}
export class SpeciesFormChangeItemTrigger extends SpeciesFormChangeTrigger {
public item: FormChangeItem;
public active: boolean;
constructor(item: FormChangeItem, active = true) {
super();
this.item = item;
this.active = active;
this.description = this.active
? i18next.t("pokemonEvolutions:Forms.item", {
item: i18next.t(`modifierType:FormChangeItem.${FormChangeItem[this.item]}`),
})
: i18next.t("pokemonEvolutions:Forms.deactivateItem", {
item: i18next.t(`modifierType:FormChangeItem.${FormChangeItem[this.item]}`),
});
}
canChange(pokemon: Pokemon): boolean {
return !!globalScene.findModifier(r => {
// Assume that if m has the `formChangeItem` property, then it is a PokemonFormChangeItemModifier
const m = r as PokemonFormChangeItemModifier;
return (
"formChangeItem" in m &&
m.pokemonId === pokemon.id &&
m.formChangeItem === this.item &&
m.active === this.active
);
});
}
}
export class SpeciesFormChangeTimeOfDayTrigger extends SpeciesFormChangeTrigger {
public timesOfDay: TimeOfDay[];
constructor(...timesOfDay: TimeOfDay[]) {
super();
this.timesOfDay = timesOfDay;
this.description = i18next.t("pokemonEvolutions:Forms.timeOfDay");
}
canChange(_pokemon: Pokemon): boolean {
return this.timesOfDay.indexOf(globalScene.arena.getTimeOfDay()) > -1;
}
}
export class SpeciesFormChangeActiveTrigger extends SpeciesFormChangeTrigger {
public active: boolean;
constructor(active = false) {
super();
this.active = active;
this.description = this.active
? i18next.t("pokemonEvolutions:Forms.enter")
: i18next.t("pokemonEvolutions:Forms.leave");
}
canChange(pokemon: Pokemon): boolean {
return pokemon.isActive(true) === this.active;
}
}
export class SpeciesFormChangeStatusEffectTrigger extends SpeciesFormChangeTrigger {
public statusEffects: StatusEffect[];
public invert: boolean;
constructor(statusEffects: StatusEffect | StatusEffect[], invert = false) {
super();
if (!Array.isArray(statusEffects)) {
statusEffects = [statusEffects];
}
this.statusEffects = statusEffects;
this.invert = invert;
// this.description = i18next.t("pokemonEvolutions:Forms.statusEffect");
}
canChange(pokemon: Pokemon): boolean {
return this.statusEffects.indexOf(pokemon.status?.effect || StatusEffect.NONE) > -1 !== this.invert;
}
}
export class SpeciesFormChangeMoveLearnedTrigger extends SpeciesFormChangeTrigger {
public move: MoveId;
public known: boolean;
constructor(move: MoveId, known = true) {
super();
this.move = move;
this.known = known;
const moveKey = MoveId[this.move]
.split("_")
.filter(f => f)
.map((f, i) => (i ? `${f[0]}${f.slice(1).toLowerCase()}` : f.toLowerCase()))
.join("") as unknown as string;
this.description = known
? i18next.t("pokemonEvolutions:Forms.moveLearned", {
move: i18next.t(`move:${moveKey}.name`),
})
: i18next.t("pokemonEvolutions:Forms.moveForgotten", {
move: i18next.t(`move:${moveKey}.name`),
});
}
canChange(pokemon: Pokemon): boolean {
return !!pokemon.moveset.filter(m => m.moveId === this.move).length === this.known;
}
}
export abstract class SpeciesFormChangeMoveTrigger extends SpeciesFormChangeTrigger {
public movePredicate: (m: MoveId) => boolean;
public used: boolean;
constructor(move: MoveId | ((m: MoveId) => boolean), used = true) {
super();
this.movePredicate = typeof move === "function" ? move : (m: MoveId) => m === move;
this.used = used;
}
}
export class SpeciesFormChangePreMoveTrigger extends SpeciesFormChangeMoveTrigger {
description = i18next.t("pokemonEvolutions:Forms.preMove");
canChange(pokemon: Pokemon): boolean {
const command = globalScene.currentBattle.turnCommands[pokemon.getBattlerIndex()];
return !!command?.move && this.movePredicate(command.move.move) === this.used;
}
}
export class SpeciesFormChangePostMoveTrigger extends SpeciesFormChangeMoveTrigger {
description = i18next.t("pokemonEvolutions:Forms.postMove");
canChange(pokemon: Pokemon): boolean {
return (
pokemon.summonData && !!pokemon.getLastXMoves(1).filter(m => this.movePredicate(m.move)).length === this.used
);
}
}
export class MeloettaFormChangePostMoveTrigger extends SpeciesFormChangePostMoveTrigger {
override canChange(pokemon: Pokemon): boolean {
if (globalScene.gameMode.hasChallenge(Challenges.SINGLE_TYPE)) {
return false;
}
// Meloetta will not transform if it has the ability Sheer Force when using Relic Song
if (pokemon.hasAbility(AbilityId.SHEER_FORCE)) {
return false;
}
return super.canChange(pokemon);
}
}
export class SpeciesDefaultFormMatchTrigger extends SpeciesFormChangeTrigger {
private formKey: string;
constructor(formKey: string) {
super();
this.formKey = formKey;
this.description = "";
}
canChange(pokemon: Pokemon): boolean {
return (
this.formKey ===
pokemon.species.forms[globalScene.getSpeciesFormIndex(pokemon.species, pokemon.gender, pokemon.getNature(), true)]
.formKey
);
}
}
/**
* Class used for triggering form changes based on the user's Tera type.
* Used by Ogerpon and Terapagos.
*/
export class SpeciesFormChangeTeraTrigger extends SpeciesFormChangeTrigger {}
/**
* Class used for triggering form changes based on the user's lapsed Tera type.
* Used by Ogerpon and Terapagos.
*/
export class SpeciesFormChangeLapseTeraTrigger extends SpeciesFormChangeTrigger {}
/**
* Class used for triggering form changes based on weather.
* Used by Castform and Cherrim.
*/
export class SpeciesFormChangeWeatherTrigger extends SpeciesFormChangeTrigger {
/** The ability that triggers the form change */
public ability: AbilityId;
/** The list of weathers that trigger the form change */
public weathers: WeatherType[];
constructor(ability: AbilityId, weathers: WeatherType[]) {
super();
this.ability = ability;
this.weathers = weathers;
this.description = i18next.t("pokemonEvolutions:Forms.weather");
}
/**
* Checks if the Pokemon has the required ability and is in the correct weather while
* the weather or ability is also not suppressed.
* @param pokemon - The pokemon that is trying to do the form change
* @returns `true` if the Pokemon can change forms, `false` otherwise
*/
canChange(pokemon: Pokemon): boolean {
const currentWeather = globalScene.arena.weather?.weatherType ?? WeatherType.NONE;
const isWeatherSuppressed = globalScene.arena.weather?.isEffectSuppressed();
const isAbilitySuppressed = pokemon.summonData.abilitySuppressed;
return (
!isAbilitySuppressed &&
!isWeatherSuppressed &&
pokemon.hasAbility(this.ability) &&
this.weathers.includes(currentWeather)
);
}
}
/**
* Class used for reverting to the original form when the weather runs out
* or when the user loses the ability/is suppressed.
* Used by Castform and Cherrim.
*/
export class SpeciesFormChangeRevertWeatherFormTrigger extends SpeciesFormChangeTrigger {
/** The ability that triggers the form change*/
public ability: AbilityId;
/** The list of weathers that will also trigger a form change to original form */
public weathers: WeatherType[];
constructor(ability: AbilityId, weathers: WeatherType[]) {
super();
this.ability = ability;
this.weathers = weathers;
this.description = i18next.t("pokemonEvolutions:Forms.weatherRevert");
}
/**
* Checks if the Pokemon has the required ability and the weather is one that will revert
* the Pokemon to its original form or the weather or ability is suppressed
* @param {Pokemon} pokemon the pokemon that is trying to do the form change
* @returns `true` if the Pokemon will revert to its original form, `false` otherwise
*/
canChange(pokemon: Pokemon): boolean {
if (pokemon.hasAbility(this.ability, false, true)) {
const currentWeather = globalScene.arena.weather?.weatherType ?? WeatherType.NONE;
const isWeatherSuppressed = globalScene.arena.weather?.isEffectSuppressed();
const isAbilitySuppressed = pokemon.summonData.abilitySuppressed;
const summonDataAbility = pokemon.summonData.ability;
const isAbilityChanged = summonDataAbility !== this.ability && summonDataAbility !== AbilityId.NONE;
if (this.weathers.includes(currentWeather) || isWeatherSuppressed || isAbilitySuppressed || isAbilityChanged) {
return true;
}
}
return false;
}
}
export function getSpeciesFormChangeMessage(pokemon: Pokemon, formChange: SpeciesFormChange, preName: string): string {
const isMega = formChange.formKey.indexOf(SpeciesFormKey.MEGA) > -1;
const isGmax = formChange.formKey.indexOf(SpeciesFormKey.GIGANTAMAX) > -1;
const isEmax = formChange.formKey.indexOf(SpeciesFormKey.ETERNAMAX) > -1;
const isRevert = !isMega && formChange.formKey === pokemon.species.forms[0].formKey;
if (isMega) {
return i18next.t("battlePokemonForm:megaChange", {
preName,
pokemonName: pokemon.name,
});
}
if (isGmax) {
return i18next.t("battlePokemonForm:gigantamaxChange", {
preName,
pokemonName: pokemon.name,
});
}
if (isEmax) {
return i18next.t("battlePokemonForm:eternamaxChange", {
preName,
pokemonName: pokemon.name,
});
}
if (isRevert) {
return i18next.t("battlePokemonForm:revertChange", {
pokemonName: getPokemonNameWithAffix(pokemon),
});
}
if (pokemon.getAbility().id === AbilityId.DISGUISE) {
return i18next.t("battlePokemonForm:disguiseChange");
}
return i18next.t("battlePokemonForm:formChange", { preName });
}

View File

@ -7,7 +7,8 @@ import i18next from "i18next";
import type { AnySound } from "#app/battle-scene"; import type { AnySound } from "#app/battle-scene";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type { GameMode } from "#app/game-mode"; import type { GameMode } from "#app/game-mode";
import { DexAttr, type StarterMoveset } from "#app/system/game-data"; import type { StarterMoveset } from "#app/system/game-data";
import { DexAttr } from "#enums/dex-attr";
import { import {
isNullOrUndefined, isNullOrUndefined,
capitalizeString, capitalizeString,

View File

@ -1,8 +1,7 @@
import type Pokemon from "../field/pokemon"; import type Pokemon from "../field/pokemon";
import type Move from "./moves/move"; import type Move from "./moves/move";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import { ProtectAttr } from "./moves/move"; import type { BattlerIndex } from "#enums/battler-index";
import type { BattlerIndex } from "#app/battle";
import i18next from "i18next"; import i18next from "i18next";
export enum TerrainType { export enum TerrainType {
@ -55,7 +54,7 @@ export class Terrain {
isMoveTerrainCancelled(user: Pokemon, targets: BattlerIndex[], move: Move): boolean { isMoveTerrainCancelled(user: Pokemon, targets: BattlerIndex[], move: Move): boolean {
switch (this.terrainType) { switch (this.terrainType) {
case TerrainType.PSYCHIC: case TerrainType.PSYCHIC:
if (!move.hasAttr(ProtectAttr)) { if (!move.hasAttr("ProtectAttr")) {
// Cancels move if the move has positive priority and targets a Pokemon grounded on the Psychic Terrain // Cancels move if the move has positive priority and targets a Pokemon grounded on the Psychic Terrain
return ( return (
move.getPriority(user) > 0 && move.getPriority(user) > 0 &&

View File

@ -1,7 +1,7 @@
import { startingWave } from "#app/starting-wave"; import { startingWave } from "#app/starting-wave";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { PartyMemberStrength } from "#enums/party-member-strength"; import { PartyMemberStrength } from "#enums/party-member-strength";
import { GameModes } from "#app/game-mode"; import { GameModes } from "#enums/game-modes";
import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves"; import { ClassicFixedBossWaves } from "#enums/fixed-boss-waves";
export class TrainerPartyTemplate { export class TrainerPartyTemplate {

View File

@ -1,12 +1,12 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { modifierTypes } from "#app/modifier/modifier-type"; import { modifierTypes } from "#app/modifier/modifier-type";
import { PokemonMove } from "#app/field/pokemon"; import { PokemonMove } from "../moves/pokemon-move";
import { toReadableString, isNullOrUndefined, randSeedItem, randSeedInt, randSeedIntRange } from "#app/utils/common"; import { toReadableString, isNullOrUndefined, randSeedItem, randSeedInt, randSeedIntRange } from "#app/utils/common";
import { pokemonEvolutions, pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions"; import { pokemonEvolutions, pokemonPrevolutions } from "#app/data/balance/pokemon-evolutions";
import { getPokemonSpecies } from "#app/data/pokemon-species"; import { getPokemonSpecies } from "#app/data/pokemon-species";
import { tmSpecies } from "#app/data/balance/tms"; import { tmSpecies } from "#app/data/balance/tms";
import { doubleBattleDialogue } from "#app/data/dialogue"; import { doubleBattleDialogue } from "#app/data/dialogue";
import { TrainerVariant } from "#app/field/trainer"; import { TrainerVariant } from "#enums/trainer-variant";
import { getIsInitialized, initI18n } from "#app/plugins/i18n"; import { getIsInitialized, initI18n } from "#app/plugins/i18n";
import i18next from "i18next"; import i18next from "i18next";
import { Gender } from "#app/data/gender"; import { Gender } from "#app/data/gender";

View File

@ -4,7 +4,6 @@ import { getPokemonNameWithAffix } from "../messages";
import type Pokemon from "../field/pokemon"; import type Pokemon from "../field/pokemon";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import type Move from "./moves/move"; import type Move from "./moves/move";
import { AttackMove } from "./moves/move";
import { randSeedInt } from "#app/utils/common"; import { randSeedInt } from "#app/utils/common";
import { SuppressWeatherEffectAbAttr } from "./abilities/ability"; import { SuppressWeatherEffectAbAttr } from "./abilities/ability";
import { TerrainType, getTerrainName } from "./terrain"; import { TerrainType, getTerrainName } from "./terrain";
@ -95,9 +94,9 @@ export class Weather {
switch (this.weatherType) { switch (this.weatherType) {
case WeatherType.HARSH_SUN: case WeatherType.HARSH_SUN:
return move instanceof AttackMove && moveType === PokemonType.WATER; return move.is("AttackMove") && moveType === PokemonType.WATER;
case WeatherType.HEAVY_RAIN: case WeatherType.HEAVY_RAIN:
return move instanceof AttackMove && moveType === PokemonType.FIRE; return move.is("AttackMove") && moveType === PokemonType.FIRE;
} }
return false; return false;

11
src/enums/ability-attr.ts Normal file
View File

@ -0,0 +1,11 @@
/**
* Not to be confused with an Ability Attribute.
* This is an object literal storing the slot that an ability can occupy.
*/
export const AbilityAttr = Object.freeze({
ABILITY_1: 1,
ABILITY_2: 2,
ABILITY_HIDDEN: 4,
});
export type AbilityAttr = typeof AbilityAttr[keyof typeof AbilityAttr];

5
src/enums/ai-type.ts Normal file
View File

@ -0,0 +1,5 @@
export enum AiType {
RANDOM,
SMART_RANDOM,
SMART
}

View File

@ -0,0 +1,5 @@
export enum ArenaTagSide {
BOTH,
PLAYER,
ENEMY
}

View File

@ -0,0 +1,7 @@
export enum BattlerIndex {
ATTACKER = -1,
PLAYER,
PLAYER_2,
ENEMY,
ENEMY_2
}

View File

@ -0,0 +1,12 @@
export enum BattlerTagLapseType {
FAINT,
MOVE,
PRE_MOVE,
AFTER_MOVE,
MOVE_EFFECT,
TURN_END,
HIT,
/** Tag lapses AFTER_HIT, applying its effects even if the user faints */
AFTER_HIT,
CUSTOM
}

View File

@ -0,0 +1,69 @@
/**
* An enum for all the challenge types. The parameter entries on these describe the
* parameters to use when calling the applyChallenges function.
*/
export enum ChallengeType {
/**
* Challenges which modify what starters you can choose
* @see {@link Challenge.applyStarterChoice}
*/
STARTER_CHOICE,
/**
* Challenges which modify how many starter points you have
* @see {@link Challenge.applyStarterPoints}
*/
STARTER_POINTS,
/**
* Challenges which modify how many starter points you have
* @see {@link Challenge.applyStarterPointCost}
*/
STARTER_COST,
/**
* Challenges which modify your starters in some way
* @see {@link Challenge.applyStarterModify}
*/
STARTER_MODIFY,
/**
* Challenges which limit which pokemon you can have in battle.
* @see {@link Challenge.applyPokemonInBattle}
*/
POKEMON_IN_BATTLE,
/**
* Adds or modifies the fixed battles in a run
* @see {@link Challenge.applyFixedBattle}
*/
FIXED_BATTLES,
/**
* Modifies the effectiveness of Type matchups in battle
* @see {@linkcode Challenge.applyTypeEffectiveness}
*/
TYPE_EFFECTIVENESS,
/**
* Modifies what level the AI pokemon are. UNIMPLEMENTED.
*/
AI_LEVEL,
/**
* Modifies how many move slots the AI has. UNIMPLEMENTED.
*/
AI_MOVE_SLOTS,
/**
* Modifies if a pokemon has its passive. UNIMPLEMENTED.
*/
PASSIVE_ACCESS,
/**
* Modifies the game mode settings in some way. UNIMPLEMENTED.
*/
GAME_MODE_MODIFY,
/**
* Modifies what level AI pokemon can access a move. UNIMPLEMENTED.
*/
MOVE_ACCESS,
/**
* Modifies what weight AI pokemon have when generating movesets. UNIMPLEMENTED.
*/
MOVE_WEIGHT,
/**
* Modifies what the pokemon stats for Flip Stat Mode.
*/
FLIP_STAT
}

7
src/enums/command.ts Normal file
View File

@ -0,0 +1,7 @@
export enum Command {
FIGHT = 0,
BALL,
POKEMON,
RUN,
TERA
}

11
src/enums/dex-attr.ts Normal file
View File

@ -0,0 +1,11 @@
export const DexAttr = Object.freeze({
NON_SHINY: 1n,
SHINY: 2n,
MALE: 4n,
FEMALE: 8n,
DEFAULT_VARIANT: 16n,
VARIANT_2: 32n,
VARIANT_3: 64n,
DEFAULT_FORM: 128n,
});
export type DexAttr = typeof DexAttr[keyof typeof DexAttr];

View File

@ -0,0 +1,5 @@
export enum FieldPosition {
CENTER,
LEFT,
RIGHT
}

View File

@ -0,0 +1,119 @@
export enum FormChangeItem {
NONE,
ABOMASITE,
ABSOLITE,
AERODACTYLITE,
AGGRONITE,
ALAKAZITE,
ALTARIANITE,
AMPHAROSITE,
AUDINITE,
BANETTITE,
BEEDRILLITE,
BLASTOISINITE,
BLAZIKENITE,
CAMERUPTITE,
CHARIZARDITE_X,
CHARIZARDITE_Y,
DIANCITE,
GALLADITE,
GARCHOMPITE,
GARDEVOIRITE,
GENGARITE,
GLALITITE,
GYARADOSITE,
HERACRONITE,
HOUNDOOMINITE,
KANGASKHANITE,
LATIASITE,
LATIOSITE,
LOPUNNITE,
LUCARIONITE,
MANECTITE,
MAWILITE,
MEDICHAMITE,
METAGROSSITE,
MEWTWONITE_X,
MEWTWONITE_Y,
PIDGEOTITE,
PINSIRITE,
RAYQUAZITE,
SABLENITE,
SALAMENCITE,
SCEPTILITE,
SCIZORITE,
SHARPEDONITE,
SLOWBRONITE,
STEELIXITE,
SWAMPERTITE,
TYRANITARITE,
VENUSAURITE,
BLUE_ORB = 50,
RED_ORB,
ADAMANT_CRYSTAL,
LUSTROUS_GLOBE,
GRISEOUS_CORE,
REVEAL_GLASS,
MAX_MUSHROOMS,
DARK_STONE,
LIGHT_STONE,
PRISON_BOTTLE,
RUSTED_SWORD,
RUSTED_SHIELD,
ICY_REINS_OF_UNITY,
SHADOW_REINS_OF_UNITY,
ULTRANECROZIUM_Z,
SHARP_METEORITE = 100,
HARD_METEORITE,
SMOOTH_METEORITE,
GRACIDEA,
SHOCK_DRIVE,
BURN_DRIVE,
CHILL_DRIVE,
DOUSE_DRIVE,
N_SOLARIZER,
N_LUNARIZER,
WELLSPRING_MASK,
HEARTHFLAME_MASK,
CORNERSTONE_MASK,
FIST_PLATE,
SKY_PLATE,
TOXIC_PLATE,
EARTH_PLATE,
STONE_PLATE,
INSECT_PLATE,
SPOOKY_PLATE,
IRON_PLATE,
FLAME_PLATE,
SPLASH_PLATE,
MEADOW_PLATE,
ZAP_PLATE,
MIND_PLATE,
ICICLE_PLATE,
DRACO_PLATE,
DREAD_PLATE,
PIXIE_PLATE,
BLANK_PLATE,// TODO: Find a potential use for this
LEGEND_PLATE,// TODO: Find a potential use for this
FIGHTING_MEMORY,
FLYING_MEMORY,
POISON_MEMORY,
GROUND_MEMORY,
ROCK_MEMORY,
BUG_MEMORY,
GHOST_MEMORY,
STEEL_MEMORY,
FIRE_MEMORY,
WATER_MEMORY,
GRASS_MEMORY,
ELECTRIC_MEMORY,
PSYCHIC_MEMORY,
ICE_MEMORY,
DRAGON_MEMORY,
DARK_MEMORY,
FAIRY_MEMORY,
NORMAL_MEMORY
}

7
src/enums/game-modes.ts Normal file
View File

@ -0,0 +1,7 @@
export enum GameModes {
CLASSIC,
ENDLESS,
SPLICED_ENDLESS,
DAILY,
CHALLENGE
}

15
src/enums/hit-result.ts Normal file
View File

@ -0,0 +1,15 @@
export enum HitResult {
EFFECTIVE = 1,
SUPER_EFFECTIVE,
NOT_VERY_EFFECTIVE,
ONE_HIT_KO,
NO_EFFECT,
STATUS,
HEAL,
FAIL,
MISS,
INDIRECT,
IMMUNE,
CONFUSION,
INDIRECT_KO
}

View File

@ -0,0 +1,8 @@
export enum LearnMoveSituation {
MISC,
LEVEL_UP,
RELEARN,
EVOLUTION,
EVOLUTION_FUSED,// If fusionSpecies has Evolved
EVOLUTION_FUSED_BASE
}

View File

@ -0,0 +1,8 @@
export enum LearnMoveType {
/** For learning a move via level-up, evolution, or other non-item-based event */
LEARN_MOVE,
/** For learning a move via Memory Mushroom */
MEMORY,
/** For learning a move via TM */
TM
}

View File

@ -0,0 +1,95 @@
export enum AnimFrameTarget {
USER,
TARGET,
GRAPHIC
}
export enum AnimFocus {
TARGET = 1,
USER,
USER_TARGET,
SCREEN
}
export enum AnimBlendType {
NORMAL,
ADD,
SUBTRACT
}
export enum ChargeAnim {
FLY_CHARGING = 1000,
BOUNCE_CHARGING,
DIG_CHARGING,
FUTURE_SIGHT_CHARGING,
DIVE_CHARGING,
SOLAR_BEAM_CHARGING,
SHADOW_FORCE_CHARGING,
SKULL_BASH_CHARGING,
FREEZE_SHOCK_CHARGING,
SKY_DROP_CHARGING,
SKY_ATTACK_CHARGING,
ICE_BURN_CHARGING,
DOOM_DESIRE_CHARGING,
RAZOR_WIND_CHARGING,
PHANTOM_FORCE_CHARGING,
GEOMANCY_CHARGING,
SHADOW_BLADE_CHARGING,
SOLAR_BLADE_CHARGING,
BEAK_BLAST_CHARGING,
METEOR_BEAM_CHARGING,
ELECTRO_SHOT_CHARGING
}
export enum CommonAnim {
USE_ITEM = 2000,
HEALTH_UP,
TERASTALLIZE,
POISON = 2010,
TOXIC,
PARALYSIS,
SLEEP,
FROZEN,
BURN,
CONFUSION,
ATTRACT,
BIND,
WRAP,
CURSE_NO_GHOST,
LEECH_SEED,
FIRE_SPIN,
PROTECT,
COVET,
WHIRLPOOL,
BIDE,
SAND_TOMB,
QUICK_GUARD,
WIDE_GUARD,
CURSE,
MAGMA_STORM,
CLAMP,
SNAP_TRAP,
THUNDER_CAGE,
INFESTATION,
ORDER_UP_CURLY,
ORDER_UP_DROOPY,
ORDER_UP_STRETCHY,
RAGING_BULL_FIRE,
RAGING_BULL_WATER,
SALT_CURE,
POWDER,
SUNNY = 2100,
RAIN,
SANDSTORM,
HAIL,
SNOW,
WIND,
HEAVY_RAIN,
HARSH_SUN,
STRONG_WINDS,
MISTY_TERRAIN = 2110,
ELECTRIC_TERRAIN,
GRASSY_TERRAIN,
PSYCHIC_TERRAIN,
LOCK_ON = 2120
}

7
src/enums/move-result.ts Normal file
View File

@ -0,0 +1,7 @@
export enum MoveResult {
PENDING,
SUCCESS,
FAIL,
MISS,
OTHER
}

View File

@ -0,0 +1,12 @@
/**
* Used for challenge types that modify movesets, these denote the various sources of moves for pokemon.
*/
export enum MoveSourceType {
LEVEL_UP,// Currently unimplemented for move access
RELEARNER,// Relearner moves currently unimplemented
COMMON_TM,
GREAT_TM,
ULTRA_TM,
COMMON_EGG,
RARE_EGG
}

View File

@ -0,0 +1,5 @@
export enum TrainerVariant {
DEFAULT,
FEMALE,
DOUBLE
}

View File

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

View File

@ -12,12 +12,13 @@ import {
getLegendaryWeatherContinuesMessage, getLegendaryWeatherContinuesMessage,
Weather, Weather,
} from "#app/data/weather"; } from "#app/data/weather";
import { CommonAnim } from "#app/data/battle-anims"; import { CommonAnim } from "#enums/move-anims-common";
import type { PokemonType } from "#enums/pokemon-type"; import type { PokemonType } from "#enums/pokemon-type";
import type Move from "#app/data/moves/move"; import type Move from "#app/data/moves/move";
import type { ArenaTag } from "#app/data/arena-tag"; import type { ArenaTag } from "#app/data/arena-tag";
import { ArenaTagSide, ArenaTrapTag, getArenaTag } from "#app/data/arena-tag"; import { ArenaTrapTag, getArenaTag } from "#app/data/arena-tag";
import type { BattlerIndex } from "#app/battle"; import { ArenaTagSide } from "#enums/arena-tag-side";
import type { BattlerIndex } from "#enums/battler-index";
import { Terrain, TerrainType } from "#app/data/terrain"; import { Terrain, TerrainType } from "#app/data/terrain";
import { import {
applyAbAttrs, applyAbAttrs,
@ -37,7 +38,10 @@ import { SpeciesId } from "#enums/species-id";
import { TimeOfDay } from "#enums/time-of-day"; import { TimeOfDay } from "#enums/time-of-day";
import { TrainerType } from "#enums/trainer-type"; import { TrainerType } from "#enums/trainer-type";
import { AbilityId } from "#enums/ability-id"; import { AbilityId } from "#enums/ability-id";
import { SpeciesFormChangeRevertWeatherFormTrigger, SpeciesFormChangeWeatherTrigger } from "#app/data/pokemon-forms"; import {
SpeciesFormChangeRevertWeatherFormTrigger,
SpeciesFormChangeWeatherTrigger,
} from "#app/data/pokemon-forms/form-change-triggers";
import { WeatherType } from "#enums/weather-type"; import { WeatherType } from "#enums/weather-type";
import { FieldEffectModifier } from "#app/modifier/modifier"; import { FieldEffectModifier } from "#app/modifier/modifier";

View File

@ -1,9 +1,9 @@
import { TextStyle, addTextObject } from "../ui/text"; import { TextStyle, addTextObject } from "../ui/text";
import type { DamageResult } from "./pokemon"; import type { DamageResult } from "./pokemon";
import type Pokemon from "./pokemon"; import type Pokemon from "./pokemon";
import { HitResult } from "./pokemon"; import { HitResult } from "#enums/hit-result";
import { formatStat, fixedInt } from "#app/utils/common"; import { formatStat, fixedInt } from "#app/utils/common";
import type { BattlerIndex } from "../battle"; import type { BattlerIndex } from "#enums/battler-index";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
type TextAndShadowArr = [string | null, string | null]; type TextAndShadowArr = [string | null, string | null];

View File

@ -9,36 +9,8 @@ import BattleInfo from "#app/ui/battle-info/battle-info";
import { EnemyBattleInfo } from "#app/ui/battle-info/enemy-battle-info"; import { EnemyBattleInfo } from "#app/ui/battle-info/enemy-battle-info";
import { PlayerBattleInfo } from "#app/ui/battle-info/player-battle-info"; import { PlayerBattleInfo } from "#app/ui/battle-info/player-battle-info";
import type Move from "#app/data/moves/move"; import type Move from "#app/data/moves/move";
import { import { getMoveTargets } from "#app/data/moves/move-utils";
HighCritAttr, import { applyMoveAttrs } from "#app/data/moves/apply-attrs";
HitsTagAttr,
applyMoveAttrs,
FixedDamageAttr,
VariableAtkAttr,
TypelessAttr,
CritOnlyAttr,
getMoveTargets,
OneHitKOAttr,
VariableMoveTypeAttr,
VariableDefAttr,
AttackMove,
ModifiedDamageAttr,
VariableMoveTypeMultiplierAttr,
IgnoreOpponentStatStagesAttr,
SacrificialAttr,
VariableMoveCategoryAttr,
CounterDamageAttr,
StatStageChangeAttr,
RechargeAttr,
IgnoreWeatherTypeDebuffAttr,
BypassBurnDamageReductionAttr,
SacrificialAttrOnHit,
OneHitKOAccuracyAttr,
RespectAttackTypeImmunityAttr,
CombinedPledgeStabBoostAttr,
VariableMoveTypeChartAttr,
HpSplitAttr,
} from "#app/data/moves/move";
import { allMoves } from "#app/data/data-lists"; import { allMoves } from "#app/data/data-lists";
import { MoveTarget } from "#enums/MoveTarget"; import { MoveTarget } from "#enums/MoveTarget";
import { MoveCategory } from "#enums/MoveCategory"; import { MoveCategory } from "#enums/MoveCategory";
@ -116,7 +88,6 @@ import {
import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "#app/data/balance/tms"; import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "#app/data/balance/tms";
import { import {
BattlerTag, BattlerTag,
BattlerTagLapseType,
EncoreTag, EncoreTag,
GroundedTag, GroundedTag,
HighestStatBoostTag, HighestStatBoostTag,
@ -135,8 +106,10 @@ import {
loadBattlerTag, loadBattlerTag,
type GrudgeTag, type GrudgeTag,
} from "../data/battler-tags"; } from "../data/battler-tags";
import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
import { WeatherType } from "#enums/weather-type"; import { WeatherType } from "#enums/weather-type";
import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag"; import { NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag";
import { ArenaTagSide } from "#enums/arena-tag-side";
import type { SuppressAbilitiesTag } from "#app/data/arena-tag"; import type { SuppressAbilitiesTag } from "#app/data/arena-tag";
import type { Ability } from "#app/data/abilities/ability-class"; import type { Ability } from "#app/data/abilities/ability-class";
import type { AbAttr } from "#app/data/abilities/ab-attrs/ab-attr"; import type { AbAttr } from "#app/data/abilities/ab-attrs/ab-attr";
@ -195,7 +168,7 @@ import {
} from "#app/data/abilities/ability"; } from "#app/data/abilities/ability";
import { allAbilities } from "#app/data/data-lists"; import { allAbilities } from "#app/data/data-lists";
import type PokemonData from "#app/system/pokemon-data"; import type PokemonData from "#app/system/pokemon-data";
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#enums/battler-index";
import { UiMode } from "#enums/ui-mode"; import { UiMode } from "#enums/ui-mode";
import type { PartyOption } from "#app/ui/party-ui-handler"; import type { PartyOption } from "#app/ui/party-ui-handler";
import PartyUiHandler, { PartyUiMode } from "#app/ui/party-ui-handler"; import PartyUiHandler, { PartyUiMode } from "#app/ui/party-ui-handler";
@ -204,7 +177,7 @@ import type { LevelMoves } from "#app/data/balance/pokemon-level-moves";
import { EVOLVE_MOVE, RELEARN_MOVE } from "#app/data/balance/pokemon-level-moves"; import { EVOLVE_MOVE, RELEARN_MOVE } from "#app/data/balance/pokemon-level-moves";
import { achvs } from "#app/system/achv"; import { achvs } from "#app/system/achv";
import type { StarterDataEntry, StarterMoveset } from "#app/system/game-data"; import type { StarterDataEntry, StarterMoveset } from "#app/system/game-data";
import { DexAttr } from "#app/system/game-data"; import { DexAttr } from "#enums/dex-attr";
import { QuantizerCelebi, argbFromRgba, rgbaFromArgb } from "@material/material-color-utilities"; import { QuantizerCelebi, argbFromRgba, rgbaFromArgb } from "@material/material-color-utilities";
import { getNatureStatMultiplier } from "#app/data/nature"; import { getNatureStatMultiplier } from "#app/data/nature";
import type { SpeciesFormChange } from "#app/data/pokemon-forms"; import type { SpeciesFormChange } from "#app/data/pokemon-forms";
@ -213,14 +186,15 @@ import {
SpeciesFormChangeLapseTeraTrigger, SpeciesFormChangeLapseTeraTrigger,
SpeciesFormChangeMoveLearnedTrigger, SpeciesFormChangeMoveLearnedTrigger,
SpeciesFormChangePostMoveTrigger, SpeciesFormChangePostMoveTrigger,
} from "#app/data/pokemon-forms"; } from "#app/data/pokemon-forms/form-change-triggers";
import { TerrainType } from "#app/data/terrain"; import { TerrainType } from "#app/data/terrain";
import type { TrainerSlot } from "#enums/trainer-slot"; import type { TrainerSlot } from "#enums/trainer-slot";
import Overrides from "#app/overrides"; import Overrides from "#app/overrides";
import i18next from "i18next"; import i18next from "i18next";
import { speciesEggMoves } from "#app/data/balance/egg-moves"; import { speciesEggMoves } from "#app/data/balance/egg-moves";
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#app/modifier/modifier-tier";
import { applyChallenges, ChallengeType } from "#app/data/challenge"; import { applyChallenges } from "#app/data/challenge";
import { ChallengeType } from "#enums/challenge-type";
import { AbilityId } from "#enums/ability-id"; import { AbilityId } from "#enums/ability-id";
import { ArenaTagType } from "#enums/arena-tag-type"; import { ArenaTagType } from "#enums/arena-tag-type";
import { BattleSpec } from "#enums/battle-spec"; import { BattleSpec } from "#enums/battle-spec";
@ -249,21 +223,12 @@ import { doShinySparkleAnim } from "#app/field/anims";
import { MoveFlags } from "#enums/MoveFlags"; import { MoveFlags } from "#enums/MoveFlags";
import { timedEventManager } from "#app/global-event-manager"; import { timedEventManager } from "#app/global-event-manager";
import { loadMoveAnimations } from "#app/sprites/pokemon-asset-loader"; import { loadMoveAnimations } from "#app/sprites/pokemon-asset-loader";
import { FieldPosition } from "#enums/field-position";
export enum LearnMoveSituation { import { LearnMoveSituation } from "#enums/learn-move-situation";
MISC, import { HitResult } from "#enums/hit-result";
LEVEL_UP, import { AiType } from "#enums/ai-type";
RELEARN, import type { MoveResult } from "#enums/move-result";
EVOLUTION, import { PokemonMove } from "#app/data/moves/pokemon-move";
EVOLUTION_FUSED, // If fusionSpecies has Evolved
EVOLUTION_FUSED_BASE, // If fusion's base species has Evolved
}
export enum FieldPosition {
CENTER,
LEFT,
RIGHT,
}
/** Base typeclass for damage parameter methods, used for DRY */ /** Base typeclass for damage parameter methods, used for DRY */
type damageParams = { type damageParams = {
@ -1435,7 +1400,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
*/ */
getCritStage(source: Pokemon, move: Move): number { getCritStage(source: Pokemon, move: Move): number {
const critStage = new NumberHolder(0); const critStage = new NumberHolder(0);
applyMoveAttrs(HighCritAttr, source, this, move, critStage); applyMoveAttrs("HighCritAttr", source, this, move, critStage);
globalScene.applyModifiers(CritBoosterModifier, source.isPlayer(), source, critStage); globalScene.applyModifiers(CritBoosterModifier, source.isPlayer(), source, critStage);
globalScene.applyModifiers(TempCritBoosterModifier, source.isPlayer(), critStage); globalScene.applyModifiers(TempCritBoosterModifier, source.isPlayer(), critStage);
applyAbAttrs(BonusCritAbAttr, source, null, false, critStage); applyAbAttrs(BonusCritAbAttr, source, null, false, critStage);
@ -1461,7 +1426,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
*/ */
getMoveCategory(target: Pokemon, move: Move): MoveCategory { getMoveCategory(target: Pokemon, move: Move): MoveCategory {
const moveCategory = new NumberHolder(move.category); const moveCategory = new NumberHolder(move.category);
applyMoveAttrs(VariableMoveCategoryAttr, this, target, move, moveCategory); applyMoveAttrs("VariableMoveCategoryAttr", this, target, move, moveCategory);
return moveCategory.value; return moveCategory.value;
} }
@ -2356,7 +2321,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
public getMoveType(move: Move, simulated = true): PokemonType { public getMoveType(move: Move, simulated = true): PokemonType {
const moveTypeHolder = new NumberHolder(move.type); const moveTypeHolder = new NumberHolder(move.type);
applyMoveAttrs(VariableMoveTypeAttr, this, null, move, moveTypeHolder); applyMoveAttrs("VariableMoveTypeAttr", this, null, move, moveTypeHolder);
applyPreAttackAbAttrs(MoveTypeChangeAbAttr, this, null, move, simulated, moveTypeHolder); applyPreAttackAbAttrs(MoveTypeChangeAbAttr, this, null, move, simulated, moveTypeHolder);
// If the user is terastallized and the move is tera blast, or tera starstorm that is stellar type, // If the user is terastallized and the move is tera blast, or tera starstorm that is stellar type,
@ -2400,18 +2365,18 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
return this.turnData?.moveEffectiveness; return this.turnData?.moveEffectiveness;
} }
if (move.hasAttr(TypelessAttr)) { if (move.hasAttr("TypelessAttr")) {
return 1; return 1;
} }
const moveType = source.getMoveType(move); const moveType = source.getMoveType(move);
const typeMultiplier = new NumberHolder( const typeMultiplier = new NumberHolder(
move.category !== MoveCategory.STATUS || move.hasAttr(RespectAttackTypeImmunityAttr) move.category !== MoveCategory.STATUS || move.hasAttr("RespectAttackTypeImmunityAttr")
? this.getAttackTypeEffectiveness(moveType, source, false, simulated, move, useIllusion) ? this.getAttackTypeEffectiveness(moveType, source, false, simulated, move, useIllusion)
: 1, : 1,
); );
applyMoveAttrs(VariableMoveTypeMultiplierAttr, source, this, move, typeMultiplier); applyMoveAttrs("VariableMoveTypeMultiplierAttr", source, this, move, typeMultiplier);
if (this.getTypes(true, true).find(t => move.isTypeImmune(source, this, t))) { if (this.getTypes(true, true).find(t => move.isTypeImmune(source, this, t))) {
typeMultiplier.value = 0; typeMultiplier.value = 0;
} }
@ -2438,7 +2403,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const immuneTags = this.findTags(tag => tag instanceof TypeImmuneTag && tag.immuneType === moveType); const immuneTags = this.findTags(tag => tag instanceof TypeImmuneTag && tag.immuneType === moveType);
for (const tag of immuneTags) { for (const tag of immuneTags) {
if (move && !move.getAttrs(HitsTagAttr).some(attr => attr.tagType === tag.tagType)) { if (move && !move.getAttrs("HitsTagAttr").some(attr => attr.tagType === tag.tagType)) {
typeMultiplier.value = 0; typeMultiplier.value = 0;
break; break;
} }
@ -2494,7 +2459,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const multiplier = new NumberHolder(getTypeDamageMultiplier(moveType, defType)); const multiplier = new NumberHolder(getTypeDamageMultiplier(moveType, defType));
applyChallenges(ChallengeType.TYPE_EFFECTIVENESS, multiplier); applyChallenges(ChallengeType.TYPE_EFFECTIVENESS, multiplier);
if (move) { if (move) {
applyMoveAttrs(VariableMoveTypeChartAttr, null, this, move, multiplier, defType); applyMoveAttrs("VariableMoveTypeChartAttr", null, this, move, multiplier, defType);
} }
if (source) { if (source) {
const ignoreImmunity = new BooleanHolder(false); const ignoreImmunity = new BooleanHolder(false);
@ -3130,24 +3095,26 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
// Bosses never get self ko moves or Pain Split // Bosses never get self ko moves or Pain Split
if (this.isBoss()) { if (this.isBoss()) {
movePool = movePool.filter(m => !allMoves[m[0]].hasAttr(SacrificialAttr) && !allMoves[m[0]].hasAttr(HpSplitAttr)); movePool = movePool.filter(
m => !allMoves[m[0]].hasAttr("SacrificialAttr") && !allMoves[m[0]].hasAttr("HpSplitAttr"),
);
} }
// No one gets Memento or Final Gambit // No one gets Memento or Final Gambit
movePool = movePool.filter(m => !allMoves[m[0]].hasAttr(SacrificialAttrOnHit)); movePool = movePool.filter(m => !allMoves[m[0]].hasAttr("SacrificialAttrOnHit"));
if (this.hasTrainer()) { if (this.hasTrainer()) {
// Trainers never get OHKO moves // Trainers never get OHKO moves
movePool = movePool.filter(m => !allMoves[m[0]].hasAttr(OneHitKOAttr)); movePool = movePool.filter(m => !allMoves[m[0]].hasAttr("OneHitKOAttr"));
// Half the weight of self KO moves // Half the weight of self KO moves
movePool = movePool.map(m => [m[0], m[1] * (allMoves[m[0]].hasAttr(SacrificialAttr) ? 0.5 : 1)]); movePool = movePool.map(m => [m[0], m[1] * (allMoves[m[0]].hasAttr("SacrificialAttr") ? 0.5 : 1)]);
// Trainers get a weight bump to stat buffing moves // Trainers get a weight bump to stat buffing moves
movePool = movePool.map(m => [ movePool = movePool.map(m => [
m[0], m[0],
m[1] * (allMoves[m[0]].getAttrs(StatStageChangeAttr).some(a => a.stages > 1 && a.selfTarget) ? 1.25 : 1), m[1] * (allMoves[m[0]].getAttrs("StatStageChangeAttr").some(a => a.stages > 1 && a.selfTarget) ? 1.25 : 1),
]); ]);
// Trainers get a weight decrease to multiturn moves // Trainers get a weight decrease to multiturn moves
movePool = movePool.map(m => [ movePool = movePool.map(m => [
m[0], m[0],
m[1] * (!!allMoves[m[0]].isChargingMove() || !!allMoves[m[0]].hasAttr(RechargeAttr) ? 0.7 : 1), m[1] * (!!allMoves[m[0]].isChargingMove() || !!allMoves[m[0]].hasAttr("RechargeAttr") ? 0.7 : 1),
]); ]);
} }
@ -3214,7 +3181,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
!this.moveset.some( !this.moveset.some(
mo => mo =>
m[0] === mo.moveId || m[0] === mo.moveId ||
(allMoves[m[0]].hasAttr(SacrificialAttr) && mo.getMove().hasAttr(SacrificialAttr)), // Only one self-KO move allowed (allMoves[m[0]].hasAttr("SacrificialAttr") && mo.getMove().hasAttr("SacrificialAttr")), // Only one self-KO move allowed
), ),
) )
.map(m => { .map(m => {
@ -3243,7 +3210,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
!this.moveset.some( !this.moveset.some(
mo => mo =>
m[0] === mo.moveId || m[0] === mo.moveId ||
(allMoves[m[0]].hasAttr(SacrificialAttr) && mo.getMove().hasAttr(SacrificialAttr)), // Only one self-KO move allowed (allMoves[m[0]].hasAttr("SacrificialAttr") && mo.getMove().hasAttr("SacrificialAttr")), // Only one self-KO move allowed
), ),
); );
} }
@ -3451,7 +3418,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
applyAbAttrs(IgnoreOpponentStatStagesAbAttr, opponent, null, simulated, stat, ignoreStatStage); applyAbAttrs(IgnoreOpponentStatStagesAbAttr, opponent, null, simulated, stat, ignoreStatStage);
} }
if (move) { if (move) {
applyMoveAttrs(IgnoreOpponentStatStagesAttr, this, opponent, move, ignoreStatStage); applyMoveAttrs("IgnoreOpponentStatStagesAttr", this, opponent, move, ignoreStatStage);
} }
} }
@ -3476,7 +3443,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* @returns The calculated accuracy multiplier. * @returns The calculated accuracy multiplier.
*/ */
getAccuracyMultiplier(target: Pokemon, sourceMove: Move): number { getAccuracyMultiplier(target: Pokemon, sourceMove: Move): number {
const isOhko = sourceMove.hasAttr(OneHitKOAccuracyAttr); const isOhko = sourceMove.hasAttr("OneHitKOAccuracyAttr");
if (isOhko) { if (isOhko) {
return 1; return 1;
} }
@ -3489,7 +3456,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
applyAbAttrs(IgnoreOpponentStatStagesAbAttr, target, null, false, Stat.ACC, ignoreAccStatStage); applyAbAttrs(IgnoreOpponentStatStagesAbAttr, target, null, false, Stat.ACC, ignoreAccStatStage);
applyAbAttrs(IgnoreOpponentStatStagesAbAttr, this, null, false, Stat.EVA, ignoreEvaStatStage); applyAbAttrs(IgnoreOpponentStatStagesAbAttr, this, null, false, Stat.EVA, ignoreEvaStatStage);
applyMoveAttrs(IgnoreOpponentStatStagesAttr, this, target, sourceMove, ignoreEvaStatStage); applyMoveAttrs("IgnoreOpponentStatStagesAttr", this, target, sourceMove, ignoreEvaStatStage);
globalScene.applyModifiers(TempStatStageBoosterModifier, this.isPlayer(), Stat.ACC, userAccStage); globalScene.applyModifiers(TempStatStageBoosterModifier, this.isPlayer(), Stat.ACC, userAccStage);
@ -3572,7 +3539,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
simulated, simulated,
), ),
); );
applyMoveAttrs(VariableAtkAttr, source, this, move, sourceAtk); applyMoveAttrs("VariableAtkAttr", source, this, move, sourceAtk);
/** /**
* This Pokemon's defensive stat for the given move's category. * This Pokemon's defensive stat for the given move's category.
@ -3590,7 +3557,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
simulated, simulated,
), ),
); );
applyMoveAttrs(VariableDefAttr, source, this, move, targetDef); applyMoveAttrs("VariableDefAttr", source, this, move, targetDef);
/** /**
* The attack's base damage, as determined by the source's level, move power * The attack's base damage, as determined by the source's level, move power
@ -3617,7 +3584,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
*/ */
calculateStabMultiplier(source: Pokemon, move: Move, ignoreSourceAbility: boolean, simulated: boolean): number { calculateStabMultiplier(source: Pokemon, move: Move, ignoreSourceAbility: boolean, simulated: boolean): number {
// If the move has the Typeless attribute, it doesn't get STAB (e.g. struggle) // If the move has the Typeless attribute, it doesn't get STAB (e.g. struggle)
if (move.hasAttr(TypelessAttr)) { if (move.hasAttr("TypelessAttr")) {
return 1; return 1;
} }
const sourceTypes = source.getTypes(); const sourceTypes = source.getTypes();
@ -3629,7 +3596,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
stabMultiplier.value += 0.5; stabMultiplier.value += 0.5;
} }
applyMoveAttrs(CombinedPledgeStabBoostAttr, source, this, move, stabMultiplier); applyMoveAttrs("CombinedPledgeStabBoostAttr", source, this, move, stabMultiplier);
if (!ignoreSourceAbility) { if (!ignoreSourceAbility) {
applyAbAttrs(StabBoostAbAttr, source, null, simulated, stabMultiplier); applyAbAttrs(StabBoostAbAttr, source, null, simulated, stabMultiplier);
@ -3678,7 +3645,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
const variableCategory = new NumberHolder(move.category); const variableCategory = new NumberHolder(move.category);
applyMoveAttrs(VariableMoveCategoryAttr, source, this, move, variableCategory); applyMoveAttrs("VariableMoveCategoryAttr", source, this, move, variableCategory);
const moveCategory = variableCategory.value as MoveCategory; const moveCategory = variableCategory.value as MoveCategory;
/** The move's type after type-changing effects are applied */ /** The move's type after type-changing effects are applied */
@ -3703,7 +3670,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const arenaAttackTypeMultiplier = new NumberHolder( const arenaAttackTypeMultiplier = new NumberHolder(
globalScene.arena.getAttackTypeMultiplier(moveType, source.isGrounded()), globalScene.arena.getAttackTypeMultiplier(moveType, source.isGrounded()),
); );
applyMoveAttrs(IgnoreWeatherTypeDebuffAttr, source, this, move, arenaAttackTypeMultiplier); applyMoveAttrs("IgnoreWeatherTypeDebuffAttr", source, this, move, arenaAttackTypeMultiplier);
const isTypeImmune = typeMultiplier * arenaAttackTypeMultiplier.value === 0; const isTypeImmune = typeMultiplier * arenaAttackTypeMultiplier.value === 0;
@ -3717,7 +3684,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
// If the attack deals fixed damage, return a result with that much damage // If the attack deals fixed damage, return a result with that much damage
const fixedDamage = new NumberHolder(0); const fixedDamage = new NumberHolder(0);
applyMoveAttrs(FixedDamageAttr, source, this, move, fixedDamage); applyMoveAttrs("FixedDamageAttr", source, this, move, fixedDamage);
if (fixedDamage.value) { if (fixedDamage.value) {
const multiLensMultiplier = new NumberHolder(1); const multiLensMultiplier = new NumberHolder(1);
globalScene.applyModifiers( globalScene.applyModifiers(
@ -3739,7 +3706,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
// If the attack is a one-hit KO move, return a result with damage equal to this Pokemon's HP // If the attack is a one-hit KO move, return a result with damage equal to this Pokemon's HP
const isOneHitKo = new BooleanHolder(false); const isOneHitKo = new BooleanHolder(false);
applyMoveAttrs(OneHitKOAttr, source, this, move, isOneHitKo); applyMoveAttrs("OneHitKOAttr", source, this, move, isOneHitKo);
if (isOneHitKo.value) { if (isOneHitKo.value) {
return { return {
cancelled: false, cancelled: false,
@ -3816,7 +3783,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
isPhysical && isPhysical &&
source.status && source.status &&
source.status.effect === StatusEffect.BURN && source.status.effect === StatusEffect.BURN &&
!move.hasAttr(BypassBurnDamageReductionAttr) !move.hasAttr("BypassBurnDamageReductionAttr")
) { ) {
const burnDamageReductionCancelled = new BooleanHolder(false); const burnDamageReductionCancelled = new BooleanHolder(false);
if (!ignoreSourceAbility) { if (!ignoreSourceAbility) {
@ -3850,7 +3817,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
*/ */
const hitsTagMultiplier = new NumberHolder(1); const hitsTagMultiplier = new NumberHolder(1);
move move
.getAttrs(HitsTagAttr) .getAttrs("HitsTagAttr")
.filter(hta => hta.doubleDamage) .filter(hta => hta.doubleDamage)
.forEach(hta => { .forEach(hta => {
if (this.getTag(hta.tagType)) { if (this.getTag(hta.tagType)) {
@ -3907,7 +3874,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
// This attribute may modify damage arbitrarily, so be careful about changing its order of application. // This attribute may modify damage arbitrarily, so be careful about changing its order of application.
applyMoveAttrs(ModifiedDamageAttr, source, this, move, damage); applyMoveAttrs("ModifiedDamageAttr", source, this, move, damage);
if (this.isFullHp() && !ignoreAbility) { if (this.isFullHp() && !ignoreAbility) {
applyPreDefendAbAttrs(PreDefendFullHpEndureAbAttr, this, source, move, cancelled, false, damage); applyPreDefendAbAttrs(PreDefendFullHpEndureAbAttr, this, source, move, cancelled, false, damage);
@ -3943,7 +3910,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
getCriticalHitResult(source: Pokemon, move: Move, simulated = true): boolean { getCriticalHitResult(source: Pokemon, move: Move, simulated = true): boolean {
const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; const defendingSide = this.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY;
const noCritTag = globalScene.arena.getTagOnSide(NoCritTag, defendingSide); const noCritTag = globalScene.arena.getTagOnSide(NoCritTag, defendingSide);
if (noCritTag || Overrides.NEVER_CRIT_OVERRIDE || move.hasAttr(FixedDamageAttr)) { if (noCritTag || Overrides.NEVER_CRIT_OVERRIDE || move.hasAttr("FixedDamageAttr")) {
return false; return false;
} }
const isCritical = new BooleanHolder(false); const isCritical = new BooleanHolder(false);
@ -3951,7 +3918,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (source.getTag(BattlerTagType.ALWAYS_CRIT)) { if (source.getTag(BattlerTagType.ALWAYS_CRIT)) {
isCritical.value = true; isCritical.value = true;
} }
applyMoveAttrs(CritOnlyAttr, source, this, move, isCritical); applyMoveAttrs("CritOnlyAttr", source, this, move, isCritical);
applyAbAttrs(ConditionalCritAbAttr, source, null, simulated, isCritical, this, move); applyAbAttrs(ConditionalCritAbAttr, source, null, simulated, isCritical, this, move);
if (!isCritical.value) { if (!isCritical.value) {
const critChance = [24, 8, 2, 1][Math.max(0, Math.min(this.getCritStage(source, move), 3))]; const critChance = [24, 8, 2, 1][Math.max(0, Math.min(this.getCritStage(source, move), 3))];
@ -6272,7 +6239,7 @@ export class EnemyPokemon extends Pokemon {
.targets.map(ind => fieldPokemon[ind]) .targets.map(ind => fieldPokemon[ind])
.filter(p => this.isPlayer() !== p.isPlayer()); .filter(p => this.isPlayer() !== p.isPlayer());
// Only considers critical hits for crit-only moves or when this Pokemon is under the effect of Laser Focus // Only considers critical hits for crit-only moves or when this Pokemon is under the effect of Laser Focus
const isCritical = move.hasAttr(CritOnlyAttr) || !!this.getTag(BattlerTagType.ALWAYS_CRIT); const isCritical = move.hasAttr("CritOnlyAttr") || !!this.getTag(BattlerTagType.ALWAYS_CRIT);
return ( return (
move.category !== MoveCategory.STATUS && move.category !== MoveCategory.STATUS &&
@ -6341,7 +6308,7 @@ export class EnemyPokemon extends Pokemon {
![MoveId.SUCKER_PUNCH, MoveId.UPPER_HAND, MoveId.THUNDERCLAP].includes(move.id) ![MoveId.SUCKER_PUNCH, MoveId.UPPER_HAND, MoveId.THUNDERCLAP].includes(move.id)
) { ) {
targetScore = -20; targetScore = -20;
} else if (move instanceof AttackMove) { } else if (move.is("AttackMove")) {
/** /**
* Attack moves are given extra multipliers to their base benefit score based on * Attack moves are given extra multipliers to their base benefit score based on
* the move's type effectiveness against the target and whether the move is a STAB move. * the move's type effectiveness against the target and whether the move is a STAB move.
@ -6462,7 +6429,7 @@ export class EnemyPokemon extends Pokemon {
if (!sortedBenefitScores.length) { if (!sortedBenefitScores.length) {
// Set target to BattlerIndex.ATTACKER when using a counter move // Set target to BattlerIndex.ATTACKER when using a counter move
// This is the same as when the player does so // This is the same as when the player does so
if (move.hasAttr(CounterDamageAttr)) { if (move.hasAttr("CounterDamageAttr")) {
return [BattlerIndex.ATTACKER]; return [BattlerIndex.ATTACKER];
} }
@ -6945,36 +6912,6 @@ export class PokemonTurnData {
public berriesEaten: BerryType[] = []; public berriesEaten: BerryType[] = [];
} }
export enum AiType {
RANDOM,
SMART_RANDOM,
SMART,
}
export enum MoveResult {
PENDING,
SUCCESS,
FAIL,
MISS,
OTHER,
}
export enum HitResult {
EFFECTIVE = 1,
SUPER_EFFECTIVE,
NOT_VERY_EFFECTIVE,
ONE_HIT_KO,
NO_EFFECT,
STATUS,
HEAL,
FAIL,
MISS,
INDIRECT,
IMMUNE,
CONFUSION,
INDIRECT_KO,
}
export type DamageResult = export type DamageResult =
| HitResult.EFFECTIVE | HitResult.EFFECTIVE
| HitResult.SUPER_EFFECTIVE | HitResult.SUPER_EFFECTIVE
@ -6993,91 +6930,3 @@ export interface DamageCalculationResult {
/** The damage dealt by the move */ /** The damage dealt by the move */
damage: number; damage: number;
} }
/**
* Wrapper class for the {@linkcode Move} class for Pokemon to interact with.
* These are the moves assigned to a {@linkcode Pokemon} object.
* It links to {@linkcode Move} class via the move ID.
* Compared to {@linkcode Move}, this class also tracks things like
* PP Ups recieved, PP used, etc.
* @see {@linkcode isUsable} - checks if move is restricted, out of PP, or not implemented.
* @see {@linkcode getMove} - returns {@linkcode Move} object by looking it up via ID.
* @see {@linkcode usePp} - removes a point of PP from the move.
* @see {@linkcode getMovePp} - returns amount of PP a move currently has.
* @see {@linkcode getPpRatio} - returns the current PP amount / max PP amount.
* @see {@linkcode getName} - returns name of {@linkcode Move}.
**/
export class PokemonMove {
public moveId: MoveId;
public ppUsed: number;
public ppUp: number;
public virtual: boolean;
/**
* If defined and nonzero, overrides the maximum PP of the move (e.g., due to move being copied by Transform).
* This also nullifies all effects of `ppUp`.
*/
public maxPpOverride?: number;
constructor(moveId: MoveId, ppUsed = 0, ppUp = 0, virtual = false, maxPpOverride?: number) {
this.moveId = moveId;
this.ppUsed = ppUsed;
this.ppUp = ppUp;
this.virtual = virtual;
this.maxPpOverride = maxPpOverride;
}
/**
* Checks whether the move can be selected or performed by a Pokemon, without consideration for the move's targets.
* The move is unusable if it is out of PP, restricted by an effect, or unimplemented.
*
* @param pokemon - {@linkcode Pokemon} that would be using this move
* @param ignorePp - If `true`, skips the PP check
* @param ignoreRestrictionTags - If `true`, skips the check for move restriction tags (see {@link MoveRestrictionBattlerTag})
* @returns `true` if the move can be selected and used by the Pokemon, otherwise `false`.
*/
isUsable(pokemon: Pokemon, ignorePp = false, ignoreRestrictionTags = false): boolean {
if (this.moveId && !ignoreRestrictionTags && pokemon.isMoveRestricted(this.moveId, pokemon)) {
return false;
}
if (this.getMove().name.endsWith(" (N)")) {
return false;
}
return ignorePp || this.ppUsed < this.getMovePp() || this.getMove().pp === -1;
}
getMove(): Move {
return allMoves[this.moveId];
}
/**
* Sets {@link ppUsed} for this move and ensures the value does not exceed {@link getMovePp}
* @param count Amount of PP to use
*/
usePp(count = 1) {
this.ppUsed = Math.min(this.ppUsed + count, this.getMovePp());
}
getMovePp(): number {
return this.maxPpOverride || this.getMove().pp + this.ppUp * toDmgValue(this.getMove().pp / 5);
}
getPpRatio(): number {
return 1 - this.ppUsed / this.getMovePp();
}
getName(): string {
return this.getMove().name;
}
/**
* Copies an existing move or creates a valid {@linkcode PokemonMove} object from json representing one
* @param source The data for the move to copy; can be a {@linkcode PokemonMove} or JSON object representing one
* @returns A valid {@linkcode PokemonMove} object
*/
static loadMove(source: PokemonMove | any): PokemonMove {
return new PokemonMove(source.moveId, source.ppUsed, source.ppUp, source.virtual, source.maxPpOverride);
}
}

View File

@ -13,19 +13,15 @@ import { TeraAIMode } from "#enums/tera-ai-mode";
import type { EnemyPokemon } from "#app/field/pokemon"; import type { EnemyPokemon } from "#app/field/pokemon";
import { randSeedWeightedItem, randSeedItem, randSeedInt } from "#app/utils/common"; import { randSeedWeightedItem, randSeedItem, randSeedInt } from "#app/utils/common";
import type { PersistentModifier } from "#app/modifier/modifier"; import type { PersistentModifier } from "#app/modifier/modifier";
import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag"; import { ArenaTrapTag } from "#app/data/arena-tag";
import { ArenaTagSide } from "#enums/arena-tag-side";
import { getIsInitialized, initI18n } from "#app/plugins/i18n"; import { getIsInitialized, initI18n } from "#app/plugins/i18n";
import i18next from "i18next"; import i18next from "i18next";
import { PartyMemberStrength } from "#enums/party-member-strength"; import { PartyMemberStrength } from "#enums/party-member-strength";
import { SpeciesId } from "#enums/species-id"; import { SpeciesId } from "#enums/species-id";
import { TrainerType } from "#enums/trainer-type"; import { TrainerType } from "#enums/trainer-type";
import { signatureSpecies } from "#app/data/balance/signature-species"; import { signatureSpecies } from "#app/data/balance/signature-species";
import { TrainerVariant } from "#enums/trainer-variant";
export enum TrainerVariant {
DEFAULT,
FEMALE,
DOUBLE,
}
export default class Trainer extends Phaser.GameObjects.Container { export default class Trainer extends Phaser.GameObjects.Container {
public config: TrainerConfig; public config: TrainerConfig;

View File

@ -2,7 +2,8 @@ import i18next from "i18next";
import type { FixedBattleConfigs } from "./battle"; import type { FixedBattleConfigs } from "./battle";
import { classicFixedBattles, FixedBattleConfig } from "./battle"; import { classicFixedBattles, FixedBattleConfig } from "./battle";
import type { Challenge } from "./data/challenge"; import type { Challenge } from "./data/challenge";
import { allChallenges, applyChallenges, ChallengeType, copyChallenge } from "./data/challenge"; import { allChallenges, applyChallenges, copyChallenge } from "./data/challenge";
import { ChallengeType } from "#enums/challenge-type";
import type PokemonSpecies from "./data/pokemon-species"; import type PokemonSpecies from "./data/pokemon-species";
import { allSpecies } from "./data/pokemon-species"; import { allSpecies } from "./data/pokemon-species";
import type { Arena } from "./field/arena"; import type { Arena } from "./field/arena";
@ -14,14 +15,7 @@ import { Challenges } from "./enums/challenges";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { getDailyStartingBiome } from "./data/daily-run"; import { getDailyStartingBiome } from "./data/daily-run";
import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES, CHALLENGE_MODE_MYSTERY_ENCOUNTER_WAVES } from "./constants"; import { CLASSIC_MODE_MYSTERY_ENCOUNTER_WAVES, CHALLENGE_MODE_MYSTERY_ENCOUNTER_WAVES } from "./constants";
import { GameModes } from "#enums/game-modes";
export enum GameModes {
CLASSIC,
ENDLESS,
SPLICED_ENDLESS,
DAILY,
CHALLENGE,
}
interface GameModeConfig { interface GameModeConfig {
isClassic?: boolean; isClassic?: boolean;

View File

@ -2,19 +2,16 @@ import { globalScene } from "#app/global-scene";
import { EvolutionItem, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions"; import { EvolutionItem, pokemonEvolutions } from "#app/data/balance/pokemon-evolutions";
import { tmPoolTiers, tmSpecies } from "#app/data/balance/tms"; import { tmPoolTiers, tmSpecies } from "#app/data/balance/tms";
import { getBerryEffectDescription, getBerryName } from "#app/data/berry"; import { getBerryEffectDescription, getBerryName } from "#app/data/berry";
import { AttackMove } from "#app/data/moves/move";
import { allMoves } from "#app/data/data-lists"; import { allMoves } from "#app/data/data-lists";
import { getNatureName, getNatureStatMultiplier } from "#app/data/nature"; import { getNatureName, getNatureStatMultiplier } from "#app/data/nature";
import { getPokeballCatchMultiplier, getPokeballName, MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball"; import { getPokeballCatchMultiplier, getPokeballName, MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball";
import { import { pokemonFormChanges, SpeciesFormChangeCondition } from "#app/data/pokemon-forms";
FormChangeItem, import { SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms/form-change-triggers";
pokemonFormChanges, import { FormChangeItem } from "#enums/form-change-item";
SpeciesFormChangeCondition,
SpeciesFormChangeItemTrigger,
} from "#app/data/pokemon-forms";
import { getStatusEffectDescriptor } from "#app/data/status-effect"; import { getStatusEffectDescriptor } from "#app/data/status-effect";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import type { EnemyPokemon, PlayerPokemon, PokemonMove } from "#app/field/pokemon"; import type { EnemyPokemon, PlayerPokemon } from "#app/field/pokemon";
import type { PokemonMove } from "#app/data/moves/pokemon-move";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import { import {
@ -1339,7 +1336,7 @@ class AttackTypeBoosterModifierTypeGenerator extends ModifierTypeGenerator {
p p
.getMoveset() .getMoveset()
.map(m => m.getMove()) .map(m => m.getMove())
.filter(m => m instanceof AttackMove) .filter(m => m.is("AttackMove"))
.map(m => m.type), .map(m => m.type),
); );
if (!attackMoveTypes.length) { if (!attackMoveTypes.length) {

View File

@ -3,14 +3,15 @@ import { getBerryEffectFunc, getBerryPredicate } from "#app/data/berry";
import { getLevelTotalExp } from "#app/data/exp"; import { getLevelTotalExp } from "#app/data/exp";
import { allMoves } from "#app/data/data-lists"; import { allMoves } from "#app/data/data-lists";
import { MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball"; import { MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball";
import { type FormChangeItem, SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms"; import { SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms/form-change-triggers";
import type { FormChangeItem } from "#enums/form-change-item";
import { getStatusEffectHealText } from "#app/data/status-effect"; import { getStatusEffectHealText } from "#app/data/status-effect";
import Pokemon, { type PlayerPokemon } from "#app/field/pokemon"; import Pokemon, { type PlayerPokemon } from "#app/field/pokemon";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import Overrides from "#app/overrides"; import Overrides from "#app/overrides";
import { LearnMoveType } from "#app/phases/learn-move-phase"; import { LearnMoveType } from "#enums/learn-move-type";
import type { VoucherType } from "#app/system/voucher"; import type { VoucherType } from "#app/system/voucher";
import { Command } from "#app/ui/command-ui-handler"; import { Command } from "#enums/command";
import { addTextObject, TextStyle } from "#app/ui/text"; import { addTextObject, TextStyle } from "#app/ui/text";
import { BooleanHolder, hslToHex, isNullOrUndefined, NumberHolder, randSeedFloat, toDmgValue } from "#app/utils/common"; import { BooleanHolder, hslToHex, isNullOrUndefined, NumberHolder, randSeedFloat, toDmgValue } from "#app/utils/common";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";

View File

@ -1,7 +1,7 @@
import { type PokeballCounts } from "#app/battle-scene"; import { type PokeballCounts } from "#app/battle-scene";
import { EvolutionItem } from "#app/data/balance/pokemon-evolutions"; import { EvolutionItem } from "#app/data/balance/pokemon-evolutions";
import { Gender } from "#app/data/gender"; import { Gender } from "#app/data/gender";
import { FormChangeItem } from "#app/data/pokemon-forms"; import { FormChangeItem } from "#enums/form-change-item";
import { type ModifierOverride } from "#app/modifier/modifier-type"; import { type ModifierOverride } from "#app/modifier/modifier-type";
import { Variant } from "#app/sprites/variant"; import { Variant } from "#app/sprites/variant";
import { Unlockables } from "#app/system/unlockables"; import { Unlockables } from "#app/system/unlockables";

View File

@ -1,4 +1,4 @@
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#enums/battler-index";
import { PLAYER_PARTY_MAX_SIZE } from "#app/constants"; import { PLAYER_PARTY_MAX_SIZE } from "#app/constants";
import { SubstituteTag } from "#app/data/battler-tags"; import { SubstituteTag } from "#app/data/battler-tags";
import { import {

View File

@ -4,7 +4,7 @@ import {
HealFromBerryUseAbAttr, HealFromBerryUseAbAttr,
RepeatBerryNextTurnAbAttr, RepeatBerryNextTurnAbAttr,
} from "#app/data/abilities/ability"; } from "#app/data/abilities/ability";
import { CommonAnim } from "#app/data/battle-anims"; import { CommonAnim } from "#enums/move-anims-common";
import { BerryUsedEvent } from "#app/events/battle-scene"; import { BerryUsedEvent } from "#app/events/battle-scene";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import { BerryModifier } from "#app/modifier/modifier"; import { BerryModifier } from "#app/modifier/modifier";

View File

@ -1,5 +1,5 @@
import { Phase } from "#app/phase"; import { Phase } from "#app/phase";
import type { BattlerIndex } from "#app/battle"; import type { BattlerIndex } from "#enums/battler-index";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
export class CheckStatusEffectPhase extends Phase { export class CheckStatusEffectPhase extends Phase {

View File

@ -4,7 +4,7 @@ import { BattleType } from "#enums/battle-type";
import type { EncoreTag } from "#app/data/battler-tags"; import type { EncoreTag } from "#app/data/battler-tags";
import { TrappedTag } from "#app/data/battler-tags"; import { TrappedTag } from "#app/data/battler-tags";
import type { MoveTargetSet } from "#app/data/moves/move"; import type { MoveTargetSet } from "#app/data/moves/move";
import { getMoveTargets } from "#app/data/moves/move"; import { getMoveTargets } from "#app/data/moves/move-utils";
import { speciesStarterCosts } from "#app/data/balance/starters"; import { speciesStarterCosts } from "#app/data/balance/starters";
import { AbilityId } from "#enums/ability-id"; import { AbilityId } from "#enums/ability-id";
import { BattlerTagType } from "#app/enums/battler-tag-type"; import { BattlerTagType } from "#app/enums/battler-tag-type";
@ -12,15 +12,15 @@ import { BiomeId } from "#enums/biome-id";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import { PokeballType } from "#enums/pokeball"; import { PokeballType } from "#enums/pokeball";
import type { PlayerPokemon, TurnMove } from "#app/field/pokemon"; import type { PlayerPokemon, TurnMove } from "#app/field/pokemon";
import { FieldPosition } from "#app/field/pokemon"; import { FieldPosition } from "#enums/field-position";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import { Command } from "#app/ui/command-ui-handler"; import { Command } from "#enums/command";
import { UiMode } from "#enums/ui-mode"; import { UiMode } from "#enums/ui-mode";
import i18next from "i18next"; import i18next from "i18next";
import { FieldPhase } from "./field-phase"; import { FieldPhase } from "./field-phase";
import { MysteryEncounterMode } from "#enums/mystery-encounter-mode"; import { MysteryEncounterMode } from "#enums/mystery-encounter-mode";
import { isNullOrUndefined } from "#app/utils/common"; import { isNullOrUndefined } from "#app/utils/common";
import { ArenaTagSide } from "#app/data/arena-tag"; import { ArenaTagSide } from "#enums/arena-tag-side";
import { ArenaTagType } from "#app/enums/arena-tag-type"; import { ArenaTagType } from "#app/enums/arena-tag-type";
export class CommandPhase extends FieldPhase { export class CommandPhase extends FieldPhase {

View File

@ -1,6 +1,6 @@
import type { BattlerIndex } from "#app/battle"; import type { BattlerIndex } from "#enums/battler-index";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type { CommonAnim } from "#app/data/battle-anims"; import type { CommonAnim } from "#enums/move-anims-common";
import { CommonBattleAnim } from "#app/data/battle-anims"; import { CommonBattleAnim } from "#app/data/battle-anims";
import { PokemonPhase } from "./pokemon-phase"; import { PokemonPhase } from "./pokemon-phase";

View File

@ -1,7 +1,8 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type { BattlerIndex } from "#app/battle"; import type { BattlerIndex } from "#enums/battler-index";
import { BattleSpec } from "#enums/battle-spec"; import { BattleSpec } from "#enums/battle-spec";
import { type DamageResult, HitResult } from "#app/field/pokemon"; import type { DamageResult } from "#app/field/pokemon";
import { HitResult } from "#enums/hit-result";
import { fixedInt } from "#app/utils/common"; import { fixedInt } from "#app/utils/common";
import { PokemonPhase } from "#app/phases/pokemon-phase"; import { PokemonPhase } from "#app/phases/pokemon-phase";

View File

@ -1,4 +1,4 @@
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#enums/battler-index";
import { BattleType } from "#enums/battle-type"; import { BattleType } from "#enums/battle-type";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { PLAYER_PARTY_MAX_SIZE } from "#app/constants"; import { PLAYER_PARTY_MAX_SIZE } from "#app/constants";
@ -17,7 +17,7 @@ import { TrainerSlot } from "#enums/trainer-slot";
import { getRandomWeatherType } from "#app/data/weather"; import { getRandomWeatherType } from "#app/data/weather";
import { EncounterPhaseEvent } from "#app/events/battle-scene"; import { EncounterPhaseEvent } from "#app/events/battle-scene";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { FieldPosition } from "#app/field/pokemon"; import { FieldPosition } from "#enums/field-position";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import { BoostBugSpawnModifier, IvScannerModifier, TurnHeldItemTransferModifier } from "#app/modifier/modifier"; import { BoostBugSpawnModifier, IvScannerModifier, TurnHeldItemTransferModifier } from "#app/modifier/modifier";
import { ModifierPoolType, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type"; import { ModifierPoolType, regenerateModifierPoolThresholds } from "#app/modifier/modifier-type";

View File

@ -1,6 +1,6 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#enums/battler-index";
import { Command } from "#app/ui/command-ui-handler"; import { Command } from "#enums/command";
import { FieldPhase } from "./field-phase"; import { FieldPhase } from "./field-phase";
import { AbilityId } from "#enums/ability-id"; import { AbilityId } from "#enums/ability-id";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";

View File

@ -10,7 +10,7 @@ import { UiMode } from "#enums/ui-mode";
import { cos, sin } from "#app/field/anims"; import { cos, sin } from "#app/field/anims";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { LearnMoveSituation } from "#app/field/pokemon"; import { LearnMoveSituation } from "#enums/learn-move-situation";
import { getTypeRgb } from "#app/data/type"; import { getTypeRgb } from "#app/data/type";
import i18next from "i18next"; import i18next from "i18next";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";

View File

@ -1,4 +1,4 @@
import type { BattlerIndex } from "#app/battle"; import type { BattlerIndex } from "#enums/battler-index";
import { BattleType } from "#enums/battle-type"; import { BattleType } from "#enums/battle-type";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { import {
@ -9,16 +9,16 @@ import {
PostKnockOutAbAttr, PostKnockOutAbAttr,
PostVictoryAbAttr, PostVictoryAbAttr,
} from "#app/data/abilities/ability"; } from "#app/data/abilities/ability";
import { BattlerTagLapseType } from "#app/data/battler-tags"; import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
import { battleSpecDialogue } from "#app/data/dialogue"; import { battleSpecDialogue } from "#app/data/dialogue";
import { PostVictoryStatStageChangeAttr } from "#app/data/moves/move";
import { allMoves } from "#app/data/data-lists"; import { allMoves } from "#app/data/data-lists";
import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms"; import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms/form-change-triggers";
import { BattleSpec } from "#app/enums/battle-spec"; import { BattleSpec } from "#app/enums/battle-spec";
import { StatusEffect } from "#app/enums/status-effect"; import { StatusEffect } from "#app/enums/status-effect";
import type { EnemyPokemon } from "#app/field/pokemon"; import type { EnemyPokemon } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { HitResult, PokemonMove } from "#app/field/pokemon"; import { PokemonMove } from "#app/data/moves/pokemon-move";
import { HitResult } from "#enums/hit-result";
import type { PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import { PokemonInstantReviveModifier } from "#app/modifier/modifier"; import { PokemonInstantReviveModifier } from "#app/modifier/modifier";
@ -144,7 +144,7 @@ export class FaintPhase extends PokemonPhase {
if (defeatSource?.isOnField()) { if (defeatSource?.isOnField()) {
applyPostVictoryAbAttrs(PostVictoryAbAttr, defeatSource); applyPostVictoryAbAttrs(PostVictoryAbAttr, defeatSource);
const pvmove = allMoves[pokemon.turnData.attacksReceived[0].move]; const pvmove = allMoves[pokemon.turnData.attacksReceived[0].move];
const pvattrs = pvmove.getAttrs(PostVictoryStatStageChangeAttr); const pvattrs = pvmove.getAttrs("PostVictoryStatStageChangeAttr");
if (pvattrs.length) { if (pvattrs.length) {
for (const pvattr of pvattrs) { for (const pvattr of pvattrs) {
pvattr.applyPostVictory(defeatSource, defeatSource, pvmove); pvattr.applyPostVictory(defeatSource, defeatSource, pvmove);

View File

@ -2,7 +2,7 @@ import { globalScene } from "#app/global-scene";
import { fixedInt } from "#app/utils/common"; import { fixedInt } from "#app/utils/common";
import { achvs } from "../system/achv"; import { achvs } from "../system/achv";
import type { SpeciesFormChange } from "../data/pokemon-forms"; import type { SpeciesFormChange } from "../data/pokemon-forms";
import { getSpeciesFormChangeMessage } from "../data/pokemon-forms"; import { getSpeciesFormChangeMessage } from "#app/data/pokemon-forms/form-change-triggers";
import type { PlayerPokemon } from "../field/pokemon"; import type { PlayerPokemon } from "../field/pokemon";
import { UiMode } from "#enums/ui-mode"; import { UiMode } from "#enums/ui-mode";
import type PartyUiHandler from "../ui/party-ui-handler"; import type PartyUiHandler from "../ui/party-ui-handler";

View File

@ -2,7 +2,7 @@ import { globalScene } from "#app/global-scene";
import { initMoveAnim, loadMoveAnimAssets } from "#app/data/battle-anims"; import { initMoveAnim, loadMoveAnimAssets } from "#app/data/battle-anims";
import type Move from "#app/data/moves/move"; import type Move from "#app/data/moves/move";
import { allMoves } from "#app/data/data-lists"; import { allMoves } from "#app/data/data-lists";
import { SpeciesFormChangeMoveLearnedTrigger } from "#app/data/pokemon-forms"; import { SpeciesFormChangeMoveLearnedTrigger } from "#app/data/pokemon-forms/form-change-triggers";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import Overrides from "#app/overrides"; import Overrides from "#app/overrides";
@ -12,15 +12,7 @@ import { UiMode } from "#enums/ui-mode";
import i18next from "i18next"; import i18next from "i18next";
import { PlayerPartyMemberPokemonPhase } from "#app/phases/player-party-member-pokemon-phase"; import { PlayerPartyMemberPokemonPhase } from "#app/phases/player-party-member-pokemon-phase";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { LearnMoveType } from "#enums/learn-move-type";
export enum LearnMoveType {
/** For learning a move via level-up, evolution, or other non-item-based event */
LEARN_MOVE,
/** For learning a move via Memory Mushroom */
MEMORY,
/** For learning a move via TM */
TM,
}
export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { export class LearnMovePhase extends PlayerPartyMemberPokemonPhase {
public readonly phaseName = "LearnMovePhase"; public readonly phaseName = "LearnMovePhase";

View File

@ -1,10 +1,10 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type { BattlerIndex } from "#app/battle"; import type { BattlerIndex } from "#enums/battler-index";
import { MoveChargeAnim } from "#app/data/battle-anims"; import { MoveChargeAnim } from "#app/data/battle-anims";
import { applyMoveChargeAttrs, MoveEffectAttr, InstantChargeAttr } from "#app/data/moves/move"; import { applyMoveChargeAttrs } from "#app/data/moves/apply-attrs";
import type { PokemonMove } from "#app/field/pokemon"; import type { PokemonMove } from "#app/data/moves/pokemon-move";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { MoveResult } from "#app/field/pokemon"; import { MoveResult } from "#enums/move-result";
import { BooleanHolder } from "#app/utils/common"; import { BooleanHolder } from "#app/utils/common";
import { PokemonPhase } from "#app/phases/pokemon-phase"; import { PokemonPhase } from "#app/phases/pokemon-phase";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
@ -43,7 +43,7 @@ export class MoveChargePhase extends PokemonPhase {
new MoveChargeAnim(move.chargeAnim, move.id, user).play(false, () => { new MoveChargeAnim(move.chargeAnim, move.id, user).play(false, () => {
move.showChargeText(user, target); move.showChargeText(user, target);
applyMoveChargeAttrs(MoveEffectAttr, user, target, move); applyMoveChargeAttrs("MoveEffectAttr", user, target, move);
user.addTag(BattlerTagType.CHARGING, 1, move.id, user.id); user.addTag(BattlerTagType.CHARGING, 1, move.id, user.id);
this.end(); this.end();
}); });
@ -57,7 +57,7 @@ export class MoveChargePhase extends PokemonPhase {
if (move.isChargingMove()) { if (move.isChargingMove()) {
const instantCharge = new BooleanHolder(false); const instantCharge = new BooleanHolder(false);
applyMoveChargeAttrs(InstantChargeAttr, user, null, move, instantCharge); applyMoveChargeAttrs("InstantChargeAttr", user, null, move, instantCharge);
if (instantCharge.value) { if (instantCharge.value) {
// this MoveEndPhase will be duplicated by the queued MovePhase if not removed // this MoveEndPhase will be duplicated by the queued MovePhase if not removed

View File

@ -1,4 +1,4 @@
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#enums/battler-index";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { import {
AddSecondStrikeAbAttr, AddSecondStrikeAbAttr,
@ -16,43 +16,31 @@ import {
PostDefendAbAttr, PostDefendAbAttr,
ReflectStatusMoveAbAttr, ReflectStatusMoveAbAttr,
} from "#app/data/abilities/ability"; } from "#app/data/abilities/ability";
import { ArenaTagSide, ConditionalProtectTag } from "#app/data/arena-tag"; import { ConditionalProtectTag } from "#app/data/arena-tag";
import { ArenaTagSide } from "#enums/arena-tag-side";
import { MoveAnim } from "#app/data/battle-anims"; import { MoveAnim } from "#app/data/battle-anims";
import { import {
BattlerTagLapseType,
DamageProtectedTag, DamageProtectedTag,
ProtectedTag, ProtectedTag,
SemiInvulnerableTag, SemiInvulnerableTag,
SubstituteTag, SubstituteTag,
TypeBoostTag, TypeBoostTag,
} from "#app/data/battler-tags"; } from "#app/data/battler-tags";
import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
import type { MoveAttr } from "#app/data/moves/move"; import type { MoveAttr } from "#app/data/moves/move";
import { import { getMoveTargets } from "#app/data/moves/move-utils";
applyFilteredMoveAttrs, import { applyFilteredMoveAttrs, applyMoveAttrs } from "#app/data/moves/apply-attrs";
applyMoveAttrs,
AttackMove,
DelayedAttackAttr,
FlinchAttr,
getMoveTargets,
HitsTagAttr,
MissEffectAttr,
MoveEffectAttr,
MultiHitAttr,
NoEffectAttr,
OneHitKOAttr,
OverrideMoveEffectAttr,
StatChangeBeforeDmgCalcAttr,
ToxicAccuracyAttr,
} from "#app/data/moves/move";
import { MoveEffectTrigger } from "#enums/MoveEffectTrigger"; import { MoveEffectTrigger } from "#enums/MoveEffectTrigger";
import { MoveFlags } from "#enums/MoveFlags"; import { MoveFlags } from "#enums/MoveFlags";
import { MoveTarget } from "#enums/MoveTarget"; import { MoveTarget } from "#enums/MoveTarget";
import { MoveCategory } from "#enums/MoveCategory"; import { MoveCategory } from "#enums/MoveCategory";
import { SpeciesFormChangePostMoveTrigger } from "#app/data/pokemon-forms"; import { SpeciesFormChangePostMoveTrigger } from "#app/data/pokemon-forms/form-change-triggers";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import { type DamageResult, PokemonMove, type TurnMove } from "#app/field/pokemon"; import type { DamageResult, TurnMove } from "#app/field/pokemon";
import { PokemonMove } from "#app/data/moves/pokemon-move";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { HitResult, MoveResult } from "#app/field/pokemon"; import { MoveResult } from "#enums/move-result";
import { HitResult } from "#enums/hit-result";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import { import {
ContactHeldItemTransferChanceModifier, ContactHeldItemTransferChanceModifier,
@ -238,13 +226,13 @@ export class MoveEffectPhase extends PokemonPhase {
case HitCheckResult.NO_EFFECT_NO_MESSAGE: case HitCheckResult.NO_EFFECT_NO_MESSAGE:
case HitCheckResult.PROTECTED: case HitCheckResult.PROTECTED:
case HitCheckResult.TARGET_NOT_ON_FIELD: case HitCheckResult.TARGET_NOT_ON_FIELD:
applyMoveAttrs(NoEffectAttr, user, target, this.move); applyMoveAttrs("NoEffectAttr", user, target, this.move);
break; break;
case HitCheckResult.MISS: case HitCheckResult.MISS:
globalScene.phaseManager.queueMessage( globalScene.phaseManager.queueMessage(
i18next.t("battle:attackMissed", { pokemonNameWithAffix: getPokemonNameWithAffix(target) }), i18next.t("battle:attackMissed", { pokemonNameWithAffix: getPokemonNameWithAffix(target) }),
); );
applyMoveAttrs(MissEffectAttr, user, target, this.move); applyMoveAttrs("MissEffectAttr", user, target, this.move);
break; break;
case HitCheckResult.REFLECTED: case HitCheckResult.REFLECTED:
this.queueReflectedMove(user, target); this.queueReflectedMove(user, target);
@ -274,7 +262,7 @@ export class MoveEffectPhase extends PokemonPhase {
globalScene.currentBattle.lastPlayerInvolved = this.fieldIndex; globalScene.currentBattle.lastPlayerInvolved = this.fieldIndex;
} }
const isDelayedAttack = this.move.hasAttr(DelayedAttackAttr); const isDelayedAttack = this.move.hasAttr("DelayedAttackAttr");
/** If the user was somehow removed from the field and it's not a delayed attack, end this phase */ /** If the user was somehow removed from the field and it's not a delayed attack, end this phase */
if (!user.isOnField()) { if (!user.isOnField()) {
if (!isDelayedAttack) { if (!isDelayedAttack) {
@ -300,7 +288,7 @@ export class MoveEffectPhase extends PokemonPhase {
const move = this.move; const move = this.move;
// Assume single target for override // Assume single target for override
applyMoveAttrs(OverrideMoveEffectAttr, user, this.getFirstTarget() ?? null, move, overridden, this.virtual); applyMoveAttrs("OverrideMoveEffectAttr", user, this.getFirstTarget() ?? null, move, overridden, this.virtual);
// If other effects were overriden, stop this phase before they can be applied // If other effects were overriden, stop this phase before they can be applied
if (overridden.value) { if (overridden.value) {
@ -327,7 +315,7 @@ export class MoveEffectPhase extends PokemonPhase {
if (user.turnData.hitsLeft === -1) { if (user.turnData.hitsLeft === -1) {
const hitCount = new NumberHolder(1); const hitCount = new NumberHolder(1);
// Assume single target for multi hit // Assume single target for multi hit
applyMoveAttrs(MultiHitAttr, user, this.getFirstTarget() ?? null, move, hitCount); applyMoveAttrs("MultiHitAttr", user, this.getFirstTarget() ?? null, move, hitCount);
// If Parental Bond is applicable, add another hit // If Parental Bond is applicable, add another hit
applyPreAttackAbAttrs(AddSecondStrikeAbAttr, user, null, move, false, hitCount, null); applyPreAttackAbAttrs(AddSecondStrikeAbAttr, user, null, move, false, hitCount, null);
// If Multi-Lens is applicable, add hits equal to the number of held Multi-Lenses // If Multi-Lens is applicable, add hits equal to the number of held Multi-Lenses
@ -359,7 +347,7 @@ export class MoveEffectPhase extends PokemonPhase {
// Play the animation if the move was successful against any of its targets or it has a POST_TARGET effect (like self destruct) // Play the animation if the move was successful against any of its targets or it has a POST_TARGET effect (like self destruct)
if ( if (
this.moveHistoryEntry.result === MoveResult.SUCCESS || this.moveHistoryEntry.result === MoveResult.SUCCESS ||
move.getAttrs(MoveEffectAttr).some(attr => attr.trigger === MoveEffectTrigger.POST_TARGET) move.getAttrs("MoveEffectAttr").some(attr => attr.trigger === MoveEffectTrigger.POST_TARGET)
) { ) {
const firstTarget = this.getFirstTarget(); const firstTarget = this.getFirstTarget();
new MoveAnim( new MoveAnim(
@ -458,7 +446,7 @@ export class MoveEffectPhase extends PokemonPhase {
* @returns a function intended to be passed into a `then()` call. * @returns a function intended to be passed into a `then()` call.
*/ */
protected applyHeldItemFlinchCheck(user: Pokemon, target: Pokemon, dealsDamage: boolean): void { protected applyHeldItemFlinchCheck(user: Pokemon, target: Pokemon, dealsDamage: boolean): void {
if (this.move.hasAttr(FlinchAttr)) { if (this.move.hasAttr("FlinchAttr")) {
return; return;
} }
@ -600,7 +588,7 @@ export class MoveEffectPhase extends PokemonPhase {
const bypassAccuracy = const bypassAccuracy =
bypassAccAndInvuln || bypassAccAndInvuln ||
target.getTag(BattlerTagType.ALWAYS_GET_HIT) || target.getTag(BattlerTagType.ALWAYS_GET_HIT) ||
(target.getTag(BattlerTagType.TELEKINESIS) && !this.move.hasAttr(OneHitKOAttr)); (target.getTag(BattlerTagType.TELEKINESIS) && !this.move.hasAttr("OneHitKOAttr"));
if (moveAccuracy === -1 || bypassAccuracy) { if (moveAccuracy === -1 || bypassAccuracy) {
return [HitCheckResult.HIT, effectiveness]; return [HitCheckResult.HIT, effectiveness];
@ -641,7 +629,7 @@ export class MoveEffectPhase extends PokemonPhase {
if (user.hasAbilityWithAttr(AlwaysHitAbAttr) || target.hasAbilityWithAttr(AlwaysHitAbAttr)) { if (user.hasAbilityWithAttr(AlwaysHitAbAttr) || target.hasAbilityWithAttr(AlwaysHitAbAttr)) {
return true; return true;
} }
if (this.move.hasAttr(ToxicAccuracyAttr) && user.isOfType(PokemonType.POISON)) { if (this.move.hasAttr("ToxicAccuracyAttr") && user.isOfType(PokemonType.POISON)) {
return true; return true;
} }
// TODO: Fix lock on / mind reader check. // TODO: Fix lock on / mind reader check.
@ -666,7 +654,7 @@ export class MoveEffectPhase extends PokemonPhase {
return false; return false;
} }
const move = this.move; const move = this.move;
return move.getAttrs(HitsTagAttr).some(hta => hta.tagType === semiInvulnerableTag.tagType); return move.getAttrs("HitsTagAttr").some(hta => hta.tagType === semiInvulnerableTag.tagType);
} }
/** @returns The {@linkcode Pokemon} using this phase's invoked move */ /** @returns The {@linkcode Pokemon} using this phase's invoked move */
@ -757,7 +745,7 @@ export class MoveEffectPhase extends PokemonPhase {
): void { ): void {
applyFilteredMoveAttrs( applyFilteredMoveAttrs(
(attr: MoveAttr) => (attr: MoveAttr) =>
attr instanceof MoveEffectAttr && attr.is("MoveEffectAttr") &&
attr.trigger === triggerType && attr.trigger === triggerType &&
(isNullOrUndefined(selfTarget) || attr.selfTarget === selfTarget) && (isNullOrUndefined(selfTarget) || attr.selfTarget === selfTarget) &&
(!attr.firstHitOnly || this.firstHit) && (!attr.firstHitOnly || this.firstHit) &&
@ -820,7 +808,7 @@ export class MoveEffectPhase extends PokemonPhase {
* Apply stat changes from {@linkcode move} and gives it to {@linkcode source} * Apply stat changes from {@linkcode move} and gives it to {@linkcode source}
* before damage calculation * before damage calculation
*/ */
applyMoveAttrs(StatChangeBeforeDmgCalcAttr, user, target, this.move); applyMoveAttrs("StatChangeBeforeDmgCalcAttr", user, target, this.move);
const { result, damage: dmg } = target.getAttackDamage({ const { result, damage: dmg } = target.getAttackDamage({
source: user, source: user,
@ -998,12 +986,12 @@ export class MoveEffectPhase extends PokemonPhase {
applyPostAttackAbAttrs(PostAttackAbAttr, user, target, this.move, hitResult); applyPostAttackAbAttrs(PostAttackAbAttr, user, target, this.move, hitResult);
// We assume only enemy Pokemon are able to have the EnemyAttackStatusEffectChanceModifier from tokens // We assume only enemy Pokemon are able to have the EnemyAttackStatusEffectChanceModifier from tokens
if (!user.isPlayer() && this.move instanceof AttackMove) { if (!user.isPlayer() && this.move.is("AttackMove")) {
globalScene.applyShuffledModifiers(EnemyAttackStatusEffectChanceModifier, false, target); globalScene.applyShuffledModifiers(EnemyAttackStatusEffectChanceModifier, false, target);
} }
// Apply Grip Claw's chance to steal an item from the target // Apply Grip Claw's chance to steal an item from the target
if (this.move instanceof AttackMove) { if (this.move.is("AttackMove")) {
globalScene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target); globalScene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target);
} }
} }

View File

@ -1,7 +1,7 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { BattlerTagLapseType } from "#app/data/battler-tags"; import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
import { PokemonPhase } from "./pokemon-phase"; import { PokemonPhase } from "./pokemon-phase";
import type { BattlerIndex } from "#app/battle"; import type { BattlerIndex } from "#enums/battler-index";
import { applyPostSummonAbAttrs, PostSummonRemoveEffectAbAttr } from "#app/data/abilities/ability"; import { applyPostSummonAbAttrs, PostSummonRemoveEffectAbAttr } from "#app/data/abilities/ability";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";

View File

@ -1,5 +1,5 @@
import { applyMoveAttrs, MoveHeaderAttr } from "#app/data/moves/move"; import { applyMoveAttrs } from "#app/data/moves/apply-attrs";
import type { PokemonMove } from "#app/field/pokemon"; import type { PokemonMove } from "#app/data/moves/pokemon-move";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { BattlePhase } from "./battle-phase"; import { BattlePhase } from "./battle-phase";
@ -23,7 +23,7 @@ export class MoveHeaderPhase extends BattlePhase {
super.start(); super.start();
if (this.canMove()) { if (this.canMove()) {
applyMoveAttrs(MoveHeaderAttr, this.pokemon, null, this.move.getMove()); applyMoveAttrs("MoveHeaderAttr", this.pokemon, null, this.move.getMove());
} }
this.end(); this.end();
} }

View File

@ -1,4 +1,4 @@
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#enums/battler-index";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { import {
applyAbAttrs, applyAbAttrs,
@ -12,30 +12,21 @@ import {
ReduceStatusEffectDurationAbAttr, ReduceStatusEffectDurationAbAttr,
} from "#app/data/abilities/ability"; } from "#app/data/abilities/ability";
import type { DelayedAttackTag } from "#app/data/arena-tag"; import type { DelayedAttackTag } from "#app/data/arena-tag";
import { CommonAnim } from "#app/data/battle-anims"; import { CommonAnim } from "#enums/move-anims-common";
import { BattlerTagLapseType, CenterOfAttentionTag } from "#app/data/battler-tags"; import { CenterOfAttentionTag } from "#app/data/battler-tags";
import { import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
AddArenaTrapTagAttr, import type { HealStatusEffectAttr } from "#app/data/moves/move";
applyMoveAttrs, import { applyMoveAttrs } from "#app/data/moves/apply-attrs";
BypassRedirectAttr,
BypassSleepAttr,
CopyMoveAttr,
DelayedAttackAttr,
frenzyMissFunc,
HealStatusEffectAttr,
PreMoveMessageAttr,
PreUseInterruptAttr,
} from "#app/data/moves/move";
import { allMoves } from "#app/data/data-lists"; import { allMoves } from "#app/data/data-lists";
import { MoveFlags } from "#enums/MoveFlags"; import { MoveFlags } from "#enums/MoveFlags";
import { SpeciesFormChangePreMoveTrigger } from "#app/data/pokemon-forms"; import { SpeciesFormChangePreMoveTrigger } from "#app/data/pokemon-forms/form-change-triggers";
import { getStatusEffectActivationText, getStatusEffectHealText } from "#app/data/status-effect"; import { getStatusEffectActivationText, getStatusEffectHealText } from "#app/data/status-effect";
import { PokemonType } from "#enums/pokemon-type"; import { PokemonType } from "#enums/pokemon-type";
import { getTerrainBlockMessage, getWeatherBlockMessage } from "#app/data/weather"; import { getTerrainBlockMessage, getWeatherBlockMessage } from "#app/data/weather";
import { MoveUsedEvent } from "#app/events/battle-scene"; import { MoveUsedEvent } from "#app/events/battle-scene";
import type { PokemonMove } from "#app/field/pokemon"; import type { PokemonMove } from "#app/data/moves/pokemon-move";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { MoveResult } from "#app/field/pokemon"; import { MoveResult } from "#enums/move-result";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import Overrides from "#app/overrides"; import Overrides from "#app/overrides";
import { BattlePhase } from "#app/phases/battle-phase"; import { BattlePhase } from "#app/phases/battle-phase";
@ -46,6 +37,7 @@ import { BattlerTagType } from "#enums/battler-tag-type";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import { StatusEffect } from "#enums/status-effect"; import { StatusEffect } from "#enums/status-effect";
import i18next from "i18next"; import i18next from "i18next";
import { frenzyMissFunc } from "#app/data/moves/move-utils";
export class MovePhase extends BattlePhase { export class MovePhase extends BattlePhase {
public readonly phaseName = "MovePhase"; public readonly phaseName = "MovePhase";
@ -204,7 +196,7 @@ export class MovePhase extends BattlePhase {
const moveQueue = this.pokemon.getMoveQueue(); const moveQueue = this.pokemon.getMoveQueue();
if ( if (
(targets.length === 0 && !this.move.getMove().hasAttr(AddArenaTrapTagAttr)) || (targets.length === 0 && !this.move.getMove().hasAttr("AddArenaTrapTagAttr")) ||
(moveQueue.length && moveQueue[0].move === MoveId.NONE) (moveQueue.length && moveQueue[0].move === MoveId.NONE)
) { ) {
this.showMoveText(); this.showMoveText();
@ -233,7 +225,7 @@ export class MovePhase extends BattlePhase {
Overrides.STATUS_ACTIVATION_OVERRIDE !== false; Overrides.STATUS_ACTIVATION_OVERRIDE !== false;
break; break;
case StatusEffect.SLEEP: { case StatusEffect.SLEEP: {
applyMoveAttrs(BypassSleepAttr, this.pokemon, null, this.move.getMove()); applyMoveAttrs("BypassSleepAttr", this.pokemon, null, this.move.getMove());
const turnsRemaining = new NumberHolder(this.pokemon.status.sleepTurnsRemaining ?? 0); const turnsRemaining = new NumberHolder(this.pokemon.status.sleepTurnsRemaining ?? 0);
applyAbAttrs( applyAbAttrs(
ReduceStatusEffectDurationAbAttr, ReduceStatusEffectDurationAbAttr,
@ -253,7 +245,10 @@ export class MovePhase extends BattlePhase {
!!this.move !!this.move
.getMove() .getMove()
.findAttr( .findAttr(
attr => attr instanceof HealStatusEffectAttr && attr.selfTarget && attr.isOfEffect(StatusEffect.FREEZE), attr =>
attr.is("HealStatusEffectAttr") &&
attr.selfTarget &&
(attr as unknown as HealStatusEffectAttr).isOfEffect(StatusEffect.FREEZE),
) || ) ||
(!this.pokemon.randBattleSeedInt(5) && Overrides.STATUS_ACTIVATION_OVERRIDE !== true) || (!this.pokemon.randBattleSeedInt(5) && Overrides.STATUS_ACTIVATION_OVERRIDE !== true) ||
Overrides.STATUS_ACTIVATION_OVERRIDE === false; Overrides.STATUS_ACTIVATION_OVERRIDE === false;
@ -303,7 +298,7 @@ export class MovePhase extends BattlePhase {
// form changes happen even before we know that the move wll execute. // form changes happen even before we know that the move wll execute.
globalScene.triggerPokemonFormChange(this.pokemon, SpeciesFormChangePreMoveTrigger); globalScene.triggerPokemonFormChange(this.pokemon, SpeciesFormChangePreMoveTrigger);
const isDelayedAttack = this.move.getMove().hasAttr(DelayedAttackAttr); const isDelayedAttack = this.move.getMove().hasAttr("DelayedAttackAttr");
if (isDelayedAttack) { if (isDelayedAttack) {
// Check the player side arena if future sight is active // Check the player side arena if future sight is active
const futureSightTags = globalScene.arena.findTags(t => t.tagType === ArenaTagType.FUTURE_SIGHT); const futureSightTags = globalScene.arena.findTags(t => t.tagType === ArenaTagType.FUTURE_SIGHT);
@ -331,7 +326,7 @@ export class MovePhase extends BattlePhase {
let success = true; let success = true;
// Check if there are any attributes that can interrupt the move, overriding the fail message. // Check if there are any attributes that can interrupt the move, overriding the fail message.
for (const move of this.move.getMove().getAttrs(PreUseInterruptAttr)) { for (const move of this.move.getMove().getAttrs("PreUseInterruptAttr")) {
if (move.apply(this.pokemon, targets[0], this.move.getMove())) { if (move.apply(this.pokemon, targets[0], this.move.getMove())) {
success = false; success = false;
break; break;
@ -386,7 +381,7 @@ export class MovePhase extends BattlePhase {
} }
// Update the battle's "last move" pointer, unless we're currently mimicking a move. // Update the battle's "last move" pointer, unless we're currently mimicking a move.
if (!allMoves[this.move.moveId].hasAttr(CopyMoveAttr)) { if (!allMoves[this.move.moveId].hasAttr("CopyMoveAttr")) {
// The last move used is unaffected by moves that fail // The last move used is unaffected by moves that fail
if (success) { if (success) {
globalScene.currentBattle.lastMove = this.move.moveId; globalScene.currentBattle.lastMove = this.move.moveId;
@ -543,7 +538,7 @@ export class MovePhase extends BattlePhase {
}); });
if (currentTarget !== redirectTarget.value) { if (currentTarget !== redirectTarget.value) {
const bypassRedirectAttrs = this.move.getMove().getAttrs(BypassRedirectAttr); const bypassRedirectAttrs = this.move.getMove().getAttrs("BypassRedirectAttr");
bypassRedirectAttrs.forEach(attr => { bypassRedirectAttrs.forEach(attr => {
if (!attr.abilitiesOnly || redirectedByAbility) { if (!attr.abilitiesOnly || redirectedByAbility) {
redirectTarget.value = currentTarget; redirectTarget.value = currentTarget;
@ -664,7 +659,7 @@ export class MovePhase extends BattlePhase {
}), }),
500, 500,
); );
applyMoveAttrs(PreMoveMessageAttr, this.pokemon, this.pokemon.getOpponents(false)[0], this.move.getMove()); applyMoveAttrs("PreMoveMessageAttr", this.pokemon, this.pokemon.getOpponents(false)[0], this.move.getMove());
} }
public showFailedText(failedText: string = i18next.t("battle:attackFailed")): void { public showFailedText(failedText: string = i18next.t("battle:attackFailed")): void {

View File

@ -1,4 +1,4 @@
import { BattlerTagLapseType } from "#app/data/battler-tags"; import { BattlerTagLapseType } from "#enums/battler-tag-lapse-type";
import type { OptionPhaseCallback } from "#app/data/mystery-encounters/mystery-encounter-option"; import type { OptionPhaseCallback } from "#app/data/mystery-encounters/mystery-encounter-option";
import type MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option"; import type MysteryEncounterOption from "#app/data/mystery-encounters/mystery-encounter-option";
import { SeenEncounterData } from "#app/data/mystery-encounters/mystery-encounter-save-data"; import { SeenEncounterData } from "#app/data/mystery-encounters/mystery-encounter-save-data";

View File

@ -1,12 +1,13 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type { BattlerIndex } from "#app/battle"; import type { BattlerIndex } from "#enums/battler-index";
import { CommonBattleAnim, CommonAnim } from "#app/data/battle-anims"; import { CommonBattleAnim } from "#app/data/battle-anims";
import { CommonAnim } from "#enums/move-anims-common";
import { getStatusEffectObtainText, getStatusEffectOverlapText } from "#app/data/status-effect"; import { getStatusEffectObtainText, getStatusEffectOverlapText } from "#app/data/status-effect";
import { StatusEffect } from "#app/enums/status-effect"; import { StatusEffect } from "#app/enums/status-effect";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import { PokemonPhase } from "./pokemon-phase"; import { PokemonPhase } from "./pokemon-phase";
import { SpeciesFormChangeStatusEffectTrigger } from "#app/data/pokemon-forms"; import { SpeciesFormChangeStatusEffectTrigger } from "#app/data/pokemon-forms/form-change-triggers";
import { applyPostSetStatusAbAttrs, PostSetStatusAbAttr } from "#app/data/abilities/ability"; import { applyPostSetStatusAbAttrs, PostSetStatusAbAttr } from "#app/data/abilities/ability";
import { isNullOrUndefined } from "#app/utils/common"; import { isNullOrUndefined } from "#app/utils/common";

View File

@ -1,9 +1,9 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type { BattlerIndex } from "#app/battle"; import type { BattlerIndex } from "#enums/battler-index";
import { CommonAnim } from "#app/data/battle-anims"; import { CommonAnim } from "#enums/move-anims-common";
import { getStatusEffectHealText } from "#app/data/status-effect"; import { getStatusEffectHealText } from "#app/data/status-effect";
import { StatusEffect } from "#app/enums/status-effect"; import { StatusEffect } from "#app/enums/status-effect";
import { HitResult } from "#app/field/pokemon"; import { HitResult } from "#enums/hit-result";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import { HealingBoosterModifier } from "#app/modifier/modifier"; import { HealingBoosterModifier } from "#app/modifier/modifier";
import { HealAchv } from "#app/system/achv"; import { HealAchv } from "#app/system/achv";

View File

@ -1,5 +1,5 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { BattlerIndex } from "#app/battle"; import { BattlerIndex } from "#enums/battler-index";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { FieldPhase } from "./field-phase"; import { FieldPhase } from "./field-phase";

View File

@ -1,8 +1,8 @@
import type { BattlerIndex } from "#app/battle"; import type { BattlerIndex } from "#enums/battler-index";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { MoveId } from "#enums/move-id"; import { MoveId } from "#enums/move-id";
import { EFFECTIVE_STATS, BATTLE_STATS } from "#enums/stat"; import { EFFECTIVE_STATS, BATTLE_STATS } from "#enums/stat";
import { PokemonMove } from "#app/field/pokemon"; import { PokemonMove } from "#app/data/moves/pokemon-move";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { PokemonPhase } from "./pokemon-phase"; import { PokemonPhase } from "./pokemon-phase";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";

View File

@ -1,5 +1,5 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type { BattlerIndex } from "#app/battle"; import type { BattlerIndex } from "#enums/battler-index";
import { import {
applyAbAttrs, applyAbAttrs,
applyPostDamageAbAttrs, applyPostDamageAbAttrs,
@ -8,7 +8,8 @@ import {
PostDamageAbAttr, PostDamageAbAttr,
ReduceBurnDamageAbAttr, ReduceBurnDamageAbAttr,
} from "#app/data/abilities/ability"; } from "#app/data/abilities/ability";
import { CommonBattleAnim, CommonAnim } from "#app/data/battle-anims"; import { CommonBattleAnim } from "#app/data/battle-anims";
import { CommonAnim } from "#enums/move-anims-common";
import { getStatusEffectActivationText } from "#app/data/status-effect"; import { getStatusEffectActivationText } from "#app/data/status-effect";
import { BattleSpec } from "#app/enums/battle-spec"; import { BattleSpec } from "#app/enums/battle-spec";
import { StatusEffect } from "#app/enums/status-effect"; import { StatusEffect } from "#app/enums/status-effect";

View File

@ -1,7 +1,10 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { SemiInvulnerableTag } from "#app/data/battler-tags"; import { SemiInvulnerableTag } from "#app/data/battler-tags";
import type { SpeciesFormChange } from "#app/data/pokemon-forms"; import type { SpeciesFormChange } from "#app/data/pokemon-forms";
import { getSpeciesFormChangeMessage, SpeciesFormChangeTeraTrigger } from "#app/data/pokemon-forms"; import {
getSpeciesFormChangeMessage,
SpeciesFormChangeTeraTrigger,
} from "#app/data/pokemon-forms/form-change-triggers";
import { getTypeRgb } from "#app/data/type"; import { getTypeRgb } from "#app/data/type";
import { BattleSpec } from "#app/enums/battle-spec"; import { BattleSpec } from "#app/enums/battle-spec";
import { BattlerTagType } from "#app/enums/battler-tag-type"; import { BattlerTagType } from "#app/enums/battler-tag-type";

View File

@ -1,5 +1,5 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms"; import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms/form-change-triggers";
import { SwitchType } from "#enums/switch-type"; import { SwitchType } from "#enums/switch-type";
import { SwitchSummonPhase } from "./switch-summon-phase"; import { SwitchSummonPhase } from "./switch-summon-phase";

View File

@ -1,5 +1,5 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import type { BattlerIndex } from "#app/battle"; import type { BattlerIndex } from "#enums/battler-index";
import { PERMANENT_STATS, Stat } from "#app/enums/stat"; import { PERMANENT_STATS, Stat } from "#app/enums/stat";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import { getTextColor, TextStyle } from "#app/ui/text"; import { getTextColor, TextStyle } from "#app/ui/text";

View File

@ -31,6 +31,8 @@ import Overrides from "#app/overrides";
import type { CustomModifierSettings } from "#app/modifier/modifier-type"; import type { CustomModifierSettings } from "#app/modifier/modifier-type";
import { isNullOrUndefined, NumberHolder } from "#app/utils/common"; import { isNullOrUndefined, NumberHolder } from "#app/utils/common";
export type ModifierSelectCallback = (rowCursor: number, cursor: number) => boolean;
export class SelectModifierPhase extends BattlePhase { export class SelectModifierPhase extends BattlePhase {
public readonly phaseName = "SelectModifierPhase"; public readonly phaseName = "SelectModifierPhase";
private rerollCount: number; private rerollCount: number;
@ -57,6 +59,10 @@ export class SelectModifierPhase extends BattlePhase {
start() { start() {
super.start(); super.start();
if (!this.isPlayer()) {
return false;
}
if (!this.rerollCount && !this.isCopy) { if (!this.rerollCount && !this.isCopy) {
this.updateSeed(); this.updateSeed();
} else if (this.rerollCount) { } else if (this.rerollCount) {
@ -67,27 +73,9 @@ export class SelectModifierPhase extends BattlePhase {
if (!this.isCopy) { if (!this.isCopy) {
regenerateModifierPoolThresholds(party, this.getPoolType(), this.rerollCount); regenerateModifierPoolThresholds(party, this.getPoolType(), this.rerollCount);
} }
const modifierCount = new NumberHolder(3); const modifierCount = this.getModifierCount();
if (this.isPlayer()) {
globalScene.applyModifiers(ExtraModifierModifier, true, modifierCount);
globalScene.applyModifiers(TempExtraModifierModifier, true, modifierCount);
}
// If custom modifiers are specified, overrides default item count this.typeOptions = this.getModifierTypeOptions(modifierCount);
if (this.customModifierSettings) {
const newItemCount =
(this.customModifierSettings.guaranteedModifierTiers?.length || 0) +
(this.customModifierSettings.guaranteedModifierTypeOptions?.length || 0) +
(this.customModifierSettings.guaranteedModifierTypeFuncs?.length || 0);
if (this.customModifierSettings.fillRemaining) {
const originalCount = modifierCount.value;
modifierCount.value = originalCount > newItemCount ? originalCount : newItemCount;
} else {
modifierCount.value = newItemCount;
}
}
this.typeOptions = this.getModifierTypeOptions(modifierCount.value);
const modifierSelectCallback = (rowCursor: number, cursor: number) => { const modifierSelectCallback = (rowCursor: number, cursor: number) => {
if (rowCursor < 0 || cursor < 0) { if (rowCursor < 0 || cursor < 0) {
@ -99,258 +87,312 @@ export class SelectModifierPhase extends BattlePhase {
globalScene.ui.setMode(UiMode.MESSAGE); globalScene.ui.setMode(UiMode.MESSAGE);
super.end(); super.end();
}, },
() => () => this.resetModifierSelect(modifierSelectCallback),
globalScene.ui.setMode(
UiMode.MODIFIER_SELECT,
this.isPlayer(),
this.typeOptions,
modifierSelectCallback,
this.getRerollCost(globalScene.lockModifierTiers),
),
); );
}); });
return false; return false;
} }
let modifierType: ModifierType;
let cost: number;
const rerollCost = this.getRerollCost(globalScene.lockModifierTiers);
switch (rowCursor) { switch (rowCursor) {
// Execute one of the options from the bottom row
case 0: case 0:
switch (cursor) { switch (cursor) {
case 0: case 0:
if (rerollCost < 0 || globalScene.money < rerollCost) { return this.rerollModifiers();
globalScene.ui.playError();
return false;
}
globalScene.reroll = true;
globalScene.phaseManager.unshiftNew(
"SelectModifierPhase",
this.rerollCount + 1,
this.typeOptions.map(o => o.type?.tier).filter(t => t !== undefined) as ModifierTier[],
);
globalScene.ui.clearText();
globalScene.ui.setMode(UiMode.MESSAGE).then(() => super.end());
if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) {
globalScene.money -= rerollCost;
globalScene.updateMoneyText();
globalScene.animateMoneyChanged(false);
}
globalScene.playSound("se/buy");
break;
case 1: case 1:
globalScene.ui.setModeWithoutClear( return this.openModifierTransferScreen(modifierSelectCallback);
UiMode.PARTY, // Check the party, pass a callback to restore the modifier select screen.
PartyUiMode.MODIFIER_TRANSFER,
-1,
(fromSlotIndex: number, itemIndex: number, itemQuantity: number, toSlotIndex: number) => {
if (
toSlotIndex !== undefined &&
fromSlotIndex < 6 &&
toSlotIndex < 6 &&
fromSlotIndex !== toSlotIndex &&
itemIndex > -1
) {
const itemModifiers = globalScene.findModifiers(
m =>
m instanceof PokemonHeldItemModifier &&
m.isTransferable &&
m.pokemonId === party[fromSlotIndex].id,
) as PokemonHeldItemModifier[];
const itemModifier = itemModifiers[itemIndex];
globalScene.tryTransferHeldItemModifier(
itemModifier,
party[toSlotIndex],
true,
itemQuantity,
undefined,
undefined,
false,
);
} else {
globalScene.ui.setMode(
UiMode.MODIFIER_SELECT,
this.isPlayer(),
this.typeOptions,
modifierSelectCallback,
this.getRerollCost(globalScene.lockModifierTiers),
);
}
},
PartyUiHandler.FilterItemMaxStacks,
);
break;
case 2: case 2:
globalScene.ui.setModeWithoutClear(UiMode.PARTY, PartyUiMode.CHECK, -1, () => { globalScene.ui.setModeWithoutClear(UiMode.PARTY, PartyUiMode.CHECK, -1, () => {
globalScene.ui.setMode( this.resetModifierSelect(modifierSelectCallback);
UiMode.MODIFIER_SELECT,
this.isPlayer(),
this.typeOptions,
modifierSelectCallback,
this.getRerollCost(globalScene.lockModifierTiers),
);
}); });
break; return true;
case 3: case 3:
if (rerollCost < 0) { return this.toggleRerollLock();
// Reroll lock button is also disabled when reroll is disabled default:
globalScene.ui.playError();
return false;
}
globalScene.lockModifierTiers = !globalScene.lockModifierTiers;
const uiHandler = globalScene.ui.getHandler() as ModifierSelectUiHandler;
uiHandler.setRerollCost(this.getRerollCost(globalScene.lockModifierTiers));
uiHandler.updateLockRaritiesText();
uiHandler.updateRerollCostText();
return false; return false;
} }
return true; // Pick an option from the rewards
case 1: case 1:
if (this.typeOptions.length === 0) { return this.selectRewardModifierOption(cursor, modifierSelectCallback);
globalScene.ui.clearText(); // Pick an option from the shop
globalScene.ui.setMode(UiMode.MESSAGE); default: {
super.end(); return this.selectShopModifierOption(rowCursor, cursor, modifierSelectCallback);
return true;
}
if (this.typeOptions[cursor].type) {
modifierType = this.typeOptions[cursor].type;
}
break;
default:
const shopOptions = getPlayerShopModifierTypeOptionsForWave(
globalScene.currentBattle.waveIndex,
globalScene.getWaveMoneyAmount(1),
);
const shopOption =
shopOptions[
rowCursor > 2 || shopOptions.length <= SHOP_OPTIONS_ROW_LIMIT ? cursor : cursor + SHOP_OPTIONS_ROW_LIMIT
];
if (shopOption.type) {
modifierType = shopOption.type;
}
// Apply Black Sludge to healing item cost
const healingItemCost = new NumberHolder(shopOption.cost);
globalScene.applyModifier(HealShopCostModifier, true, healingItemCost);
cost = healingItemCost.value;
break;
}
if (cost! && globalScene.money < cost && !Overrides.WAIVE_ROLL_FEE_OVERRIDE) {
// TODO: is the bang on cost correct?
globalScene.ui.playError();
return false;
}
const applyModifier = (modifier: Modifier, playSound = false) => {
const result = globalScene.addModifier(modifier, false, playSound, undefined, undefined, cost);
// Queue a copy of this phase when applying a TM or Memory Mushroom.
// If the player selects either of these, then escapes out of consuming them,
// they are returned to a shop in the same state.
if (modifier.type instanceof RememberMoveModifierType || modifier.type instanceof TmModifierType) {
globalScene.phaseManager.unshiftPhase(this.copy());
} }
if (cost && !(modifier.type instanceof RememberMoveModifierType)) {
if (result) {
if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) {
globalScene.money -= cost;
globalScene.updateMoneyText();
globalScene.animateMoneyChanged(false);
}
globalScene.playSound("se/buy");
(globalScene.ui.getHandler() as ModifierSelectUiHandler).updateCostText();
} else {
globalScene.ui.playError();
}
} else {
globalScene.ui.clearText();
globalScene.ui.setMode(UiMode.MESSAGE);
super.end();
}
};
if (modifierType! instanceof PokemonModifierType) {
//TODO: is the bang correct?
if (modifierType instanceof FusePokemonModifierType) {
globalScene.ui.setModeWithoutClear(
UiMode.PARTY,
PartyUiMode.SPLICE,
-1,
(fromSlotIndex: number, spliceSlotIndex: number) => {
if (
spliceSlotIndex !== undefined &&
fromSlotIndex < 6 &&
spliceSlotIndex < 6 &&
fromSlotIndex !== spliceSlotIndex
) {
globalScene.ui.setMode(UiMode.MODIFIER_SELECT, this.isPlayer()).then(() => {
const modifier = modifierType.newModifier(party[fromSlotIndex], party[spliceSlotIndex])!; //TODO: is the bang correct?
applyModifier(modifier, true);
});
} else {
globalScene.ui.setMode(
UiMode.MODIFIER_SELECT,
this.isPlayer(),
this.typeOptions,
modifierSelectCallback,
this.getRerollCost(globalScene.lockModifierTiers),
);
}
},
modifierType.selectFilter,
);
} else {
const pokemonModifierType = modifierType as PokemonModifierType;
const isMoveModifier = modifierType instanceof PokemonMoveModifierType;
const isTmModifier = modifierType instanceof TmModifierType;
const isRememberMoveModifier = modifierType instanceof RememberMoveModifierType;
const isPpRestoreModifier =
modifierType instanceof PokemonPpRestoreModifierType || modifierType instanceof PokemonPpUpModifierType;
const partyUiMode = isMoveModifier
? PartyUiMode.MOVE_MODIFIER
: isTmModifier
? PartyUiMode.TM_MODIFIER
: isRememberMoveModifier
? PartyUiMode.REMEMBER_MOVE_MODIFIER
: PartyUiMode.MODIFIER;
const tmMoveId = isTmModifier ? (modifierType as TmModifierType).moveId : undefined;
globalScene.ui.setModeWithoutClear(
UiMode.PARTY,
partyUiMode,
-1,
(slotIndex: number, option: PartyOption) => {
if (slotIndex < 6) {
globalScene.ui.setMode(UiMode.MODIFIER_SELECT, this.isPlayer()).then(() => {
const modifier = !isMoveModifier
? !isRememberMoveModifier
? modifierType.newModifier(party[slotIndex])
: modifierType.newModifier(party[slotIndex], option as number)
: modifierType.newModifier(party[slotIndex], option - PartyOption.MOVE_1);
applyModifier(modifier!, true); // TODO: is the bang correct?
});
} else {
globalScene.ui.setMode(
UiMode.MODIFIER_SELECT,
this.isPlayer(),
this.typeOptions,
modifierSelectCallback,
this.getRerollCost(globalScene.lockModifierTiers),
);
}
},
pokemonModifierType.selectFilter,
modifierType instanceof PokemonMoveModifierType
? (modifierType as PokemonMoveModifierType).moveSelectFilter
: undefined,
tmMoveId,
isPpRestoreModifier,
);
}
} else {
applyModifier(modifierType!.newModifier()!); // TODO: is the bang correct?
} }
return !cost!; // TODO: is the bang correct?
}; };
this.resetModifierSelect(modifierSelectCallback);
}
// Pick a modifier from among the rewards and apply it
private selectRewardModifierOption(cursor: number, modifierSelectCallback: ModifierSelectCallback): boolean {
if (this.typeOptions.length === 0) {
globalScene.ui.clearText();
globalScene.ui.setMode(UiMode.MESSAGE);
super.end();
return true;
}
const modifierType = this.typeOptions[cursor].type;
return this.applyChosenModifier(modifierType, 0, modifierSelectCallback);
}
// Pick a modifier from the shop and apply it
private selectShopModifierOption(
rowCursor: number,
cursor: number,
modifierSelectCallback: ModifierSelectCallback,
): boolean {
const shopOptions = getPlayerShopModifierTypeOptionsForWave(
globalScene.currentBattle.waveIndex,
globalScene.getWaveMoneyAmount(1),
);
const shopOption =
shopOptions[
rowCursor > 2 || shopOptions.length <= SHOP_OPTIONS_ROW_LIMIT ? cursor : cursor + SHOP_OPTIONS_ROW_LIMIT
];
const modifierType = shopOption.type;
// Apply Black Sludge to healing item cost
const healingItemCost = new NumberHolder(shopOption.cost);
globalScene.applyModifier(HealShopCostModifier, true, healingItemCost);
const cost = healingItemCost.value;
if (globalScene.money < cost && !Overrides.WAIVE_ROLL_FEE_OVERRIDE) {
globalScene.ui.playError();
return false;
}
return this.applyChosenModifier(modifierType, cost, modifierSelectCallback);
}
// Apply a chosen modifier: do an effect or open the party menu
private applyChosenModifier(
modifierType: ModifierType,
cost: number,
modifierSelectCallback: ModifierSelectCallback,
): boolean {
if (modifierType instanceof PokemonModifierType) {
if (modifierType instanceof FusePokemonModifierType) {
this.openFusionMenu(modifierType, cost, modifierSelectCallback);
} else {
this.openModifierMenu(modifierType, cost, modifierSelectCallback);
}
} else {
this.applyModifier(modifierType.newModifier()!);
}
return !cost;
}
// Reroll rewards
private rerollModifiers() {
const rerollCost = this.getRerollCost(globalScene.lockModifierTiers);
if (rerollCost < 0 || globalScene.money < rerollCost) {
globalScene.ui.playError();
return false;
}
globalScene.reroll = true;
globalScene.phaseManager.unshiftNew(
"SelectModifierPhase",
this.rerollCount + 1,
this.typeOptions.map(o => o.type?.tier).filter(t => t !== undefined) as ModifierTier[],
);
globalScene.ui.clearText();
globalScene.ui.setMode(UiMode.MESSAGE).then(() => super.end());
if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) {
globalScene.money -= rerollCost;
globalScene.updateMoneyText();
globalScene.animateMoneyChanged(false);
}
globalScene.playSound("se/buy");
return true;
}
// Transfer modifiers among party pokemon
private openModifierTransferScreen(modifierSelectCallback: ModifierSelectCallback) {
const party = globalScene.getPlayerParty();
globalScene.ui.setModeWithoutClear(
UiMode.PARTY,
PartyUiMode.MODIFIER_TRANSFER,
-1,
(fromSlotIndex: number, itemIndex: number, itemQuantity: number, toSlotIndex: number) => {
if (
toSlotIndex !== undefined &&
fromSlotIndex < 6 &&
toSlotIndex < 6 &&
fromSlotIndex !== toSlotIndex &&
itemIndex > -1
) {
const itemModifiers = globalScene.findModifiers(
m => m instanceof PokemonHeldItemModifier && m.isTransferable && m.pokemonId === party[fromSlotIndex].id,
) as PokemonHeldItemModifier[];
const itemModifier = itemModifiers[itemIndex];
globalScene.tryTransferHeldItemModifier(
itemModifier,
party[toSlotIndex],
true,
itemQuantity,
undefined,
undefined,
false,
);
} else {
this.resetModifierSelect(modifierSelectCallback);
}
},
PartyUiHandler.FilterItemMaxStacks,
);
return true;
}
// Toggle reroll lock
private toggleRerollLock() {
const rerollCost = this.getRerollCost(globalScene.lockModifierTiers);
if (rerollCost < 0) {
// Reroll lock button is also disabled when reroll is disabled
globalScene.ui.playError();
return false;
}
globalScene.lockModifierTiers = !globalScene.lockModifierTiers;
const uiHandler = globalScene.ui.getHandler() as ModifierSelectUiHandler;
uiHandler.setRerollCost(this.getRerollCost(globalScene.lockModifierTiers));
uiHandler.updateLockRaritiesText();
uiHandler.updateRerollCostText();
return false;
}
// Applies the effects of the chosen modifier
private applyModifier(modifier: Modifier, cost = 0, playSound = false): void {
const result = globalScene.addModifier(modifier, false, playSound, undefined, undefined, cost);
// Queue a copy of this phase when applying a TM or Memory Mushroom.
// If the player selects either of these, then escapes out of consuming them,
// they are returned to a shop in the same state.
if (modifier.type instanceof RememberMoveModifierType || modifier.type instanceof TmModifierType) {
globalScene.phaseManager.unshiftPhase(this.copy());
}
if (cost && !(modifier.type instanceof RememberMoveModifierType)) {
if (result) {
if (!Overrides.WAIVE_ROLL_FEE_OVERRIDE) {
globalScene.money -= cost;
globalScene.updateMoneyText();
globalScene.animateMoneyChanged(false);
}
globalScene.playSound("se/buy");
(globalScene.ui.getHandler() as ModifierSelectUiHandler).updateCostText();
} else {
globalScene.ui.playError();
}
} else {
globalScene.ui.clearText();
globalScene.ui.setMode(UiMode.MESSAGE);
super.end();
}
}
// Opens the party menu specifically for fusions
private openFusionMenu(
modifierType: PokemonModifierType,
cost: number,
modifierSelectCallback: ModifierSelectCallback,
): void {
const party = globalScene.getPlayerParty();
globalScene.ui.setModeWithoutClear(
UiMode.PARTY,
PartyUiMode.SPLICE,
-1,
(fromSlotIndex: number, spliceSlotIndex: number) => {
if (
spliceSlotIndex !== undefined &&
fromSlotIndex < 6 &&
spliceSlotIndex < 6 &&
fromSlotIndex !== spliceSlotIndex
) {
globalScene.ui.setMode(UiMode.MODIFIER_SELECT, this.isPlayer()).then(() => {
const modifier = modifierType.newModifier(party[fromSlotIndex], party[spliceSlotIndex])!; //TODO: is the bang correct?
this.applyModifier(modifier, cost, true);
});
} else {
this.resetModifierSelect(modifierSelectCallback);
}
},
modifierType.selectFilter,
);
}
// Opens the party menu to apply one of various modifiers
private openModifierMenu(
modifierType: PokemonModifierType,
cost: number,
modifierSelectCallback: ModifierSelectCallback,
): void {
const party = globalScene.getPlayerParty();
const pokemonModifierType = modifierType as PokemonModifierType;
const isMoveModifier = modifierType instanceof PokemonMoveModifierType;
const isTmModifier = modifierType instanceof TmModifierType;
const isRememberMoveModifier = modifierType instanceof RememberMoveModifierType;
const isPpRestoreModifier =
modifierType instanceof PokemonPpRestoreModifierType || modifierType instanceof PokemonPpUpModifierType;
const partyUiMode = isMoveModifier
? PartyUiMode.MOVE_MODIFIER
: isTmModifier
? PartyUiMode.TM_MODIFIER
: isRememberMoveModifier
? PartyUiMode.REMEMBER_MOVE_MODIFIER
: PartyUiMode.MODIFIER;
const tmMoveId = isTmModifier ? (modifierType as TmModifierType).moveId : undefined;
globalScene.ui.setModeWithoutClear(
UiMode.PARTY,
partyUiMode,
-1,
(slotIndex: number, option: PartyOption) => {
if (slotIndex < 6) {
globalScene.ui.setMode(UiMode.MODIFIER_SELECT, this.isPlayer()).then(() => {
const modifier = !isMoveModifier
? !isRememberMoveModifier
? modifierType.newModifier(party[slotIndex])
: modifierType.newModifier(party[slotIndex], option as number)
: modifierType.newModifier(party[slotIndex], option - PartyOption.MOVE_1);
this.applyModifier(modifier!, cost, true); // TODO: is the bang correct?
});
} else {
this.resetModifierSelect(modifierSelectCallback);
}
},
pokemonModifierType.selectFilter,
modifierType instanceof PokemonMoveModifierType
? (modifierType as PokemonMoveModifierType).moveSelectFilter
: undefined,
tmMoveId,
isPpRestoreModifier,
);
}
// Function that determines how many reward slots are available
private getModifierCount(): number {
const modifierCountHolder = new NumberHolder(3);
globalScene.applyModifiers(ExtraModifierModifier, true, modifierCountHolder);
globalScene.applyModifiers(TempExtraModifierModifier, true, modifierCountHolder);
// If custom modifiers are specified, overrides default item count
if (this.customModifierSettings) {
const newItemCount =
(this.customModifierSettings.guaranteedModifierTiers?.length ?? 0) +
(this.customModifierSettings.guaranteedModifierTypeOptions?.length ?? 0) +
(this.customModifierSettings.guaranteedModifierTypeFuncs?.length ?? 0);
if (this.customModifierSettings.fillRemaining) {
const originalCount = modifierCountHolder.value;
modifierCountHolder.value = originalCount > newItemCount ? originalCount : newItemCount;
} else {
modifierCountHolder.value = newItemCount;
}
}
return modifierCountHolder.value;
}
// Function that resets the reward selection screen,
// e.g. after pressing cancel in the party ui or while learning a move
private resetModifierSelect(modifierSelectCallback: ModifierSelectCallback) {
globalScene.ui.setMode( globalScene.ui.setMode(
UiMode.MODIFIER_SELECT, UiMode.MODIFIER_SELECT,
this.isPlayer(), this.isPlayer(),

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