pokerogue/src/battle-phases.ts

2161 lines
72 KiB
TypeScript
Raw Normal View History

import BattleScene, { startingLevel, startingWave } from "./battle-scene";
2023-04-15 06:32:16 +01:00
import { default as Pokemon, PlayerPokemon, EnemyPokemon, PokemonMove, MoveResult, DamageResult } from "./pokemon";
import * as Utils from './utils';
2023-04-30 05:51:33 +01:00
import { allMoves, applyMoveAttrs, BypassSleepAttr, ChargeAttr, applyFilteredMoveAttrs, HitsTagAttr, MissEffectAttr, MoveAttr, MoveCategory, MoveEffectAttr, MoveFlags, MoveHitEffectAttr, Moves, MultiHitAttr, OverrideMoveEffectAttr, VariableAccuracyAttr } from "./data/move";
2023-04-10 19:12:01 +01:00
import { Mode } from './ui/ui';
import { Command } from "./ui/command-ui-handler";
2023-04-20 20:46:05 +01:00
import { Stat } from "./data/pokemon-stat";
2023-04-29 00:26:41 +01:00
import { BerryModifier, ExpBalanceModifier, ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, FlinchChanceModifier, HealingBoosterModifier, HeldItemTransferModifier, HitHealModifier, MapModifier, MultipleParticipantExpBonusModifier, PokemonExpBoosterModifier, PokemonHeldItemModifier, SwitchEffectTransferModifier, TempBattleStatBoosterModifier, TurnHealModifier } from "./modifier/modifier";
2023-04-11 16:04:39 +01:00
import PartyUiHandler, { PartyOption, PartyUiMode } from "./ui/party-ui-handler";
import { doPokeballBounceAnim, getPokeballAtlasKey, getPokeballCatchMultiplier, getPokeballName, getPokeballTintColor, PokeballType } from "./data/pokeball";
2023-04-20 20:46:05 +01:00
import { CommonAnim, CommonBattleAnim, MoveAnim, initMoveAnim, loadMoveAnimAssets } from "./data/battle-anims";
import { StatusEffect, getStatusEffectActivationText, getStatusEffectCatchRateMultiplier, getStatusEffectHealText, getStatusEffectObtainText, getStatusEffectOverlapText } from "./data/status-effect";
2023-04-10 19:12:01 +01:00
import { SummaryUiMode } from "./ui/summary-ui-handler";
import EvolutionSceneHandler from "./ui/evolution-scene-handler";
import { EvolutionPhase } from "./evolution-phase";
import { BattlePhase } from "./battle-phase";
2023-04-20 20:46:05 +01:00
import { BattleStat, getBattleStatLevelChangeDescription, getBattleStatName } from "./data/battle-stat";
import { Biome, biomeLinks } from "./data/biome";
2023-04-21 20:45:48 +01:00
import { ModifierTypeOption, PokemonModifierType, PokemonMoveModifierType, getPlayerModifierTypeOptionsForWave, regenerateModifierPoolThresholds } from "./modifier/modifier-type";
2023-04-13 00:09:15 +01:00
import SoundFade from "phaser3-rex-plugins/plugins/soundfade";
2023-04-29 00:26:41 +01:00
import { BattlerTagLapseType, BattlerTagType, HideSpriteTag as HiddenTag, TrappedTag } from "./data/battler-tag";
2023-04-15 06:32:16 +01:00
import { getPokemonMessage } from "./messages";
import { Starter } from "./ui/starter-select-ui-handler";
2023-04-20 20:46:05 +01:00
import { Gender } from "./data/gender";
import { Weather, WeatherType, getRandomWeatherType, getWeatherDamageMessage, getWeatherLapseMessage } from "./data/weather";
import { TempBattleStat } from "./data/temp-battle-stat";
import { ArenaTrapTag, TrickRoomTag } from "./data/arena-tag";
import { PostWeatherLapseAbAttr, PreWeatherDamageAbAttr, ProtectStatAttr, SuppressWeatherEffectAbAttr, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreWeatherEffectAbAttrs } from "./data/ability";
2023-04-29 06:40:24 +01:00
import { Unlockables, getUnlockableName } from "./system/unlockables";
2023-04-10 19:12:01 +01:00
2023-04-28 20:03:42 +01:00
export class CheckLoadPhase extends BattlePhase {
private loaded: boolean;
constructor(scene: BattleScene) {
super(scene);
this.loaded = false;
}
start(): void {
if (!this.scene.gameData.hasSession()) {
this.end();
return;
}
this.scene.ui.showText('You currently have a session in progress.\nWould you like to continue where you left off?', null, () => {
this.scene.ui.setMode(Mode.CONFIRM, () => {
this.scene.ui.setMode(Mode.MESSAGE);
this.scene.gameData.loadSession(this.scene).then((success: boolean) => {
if (success) {
this.loaded = true;
this.scene.ui.showText('Session loaded successfully.', null, () => this.end());
} else
this.end();
}).catch(err => {
console.error(err);
this.scene.ui.showText('Your session data could not be loaded.\nIt may be corrupted. Please reload the page.', null);
});
}, () => {
this.scene.ui.setMode(Mode.MESSAGE);
this.scene.ui.clearText();
this.end();
})
});
}
end(): void {
if (!this.loaded) {
this.scene.arena.preloadBgm();
this.scene.pushPhase(new SelectStarterPhase(this.scene));
} else
this.scene.arena.playBgm();
this.scene.pushPhase(new EncounterPhase(this.scene, this.loaded));
this.scene.pushPhase(new SummonPhase(this.scene));
super.end();
}
}
2023-04-10 19:12:01 +01:00
export class SelectStarterPhase extends BattlePhase {
constructor(scene: BattleScene) {
super(scene);
}
start() {
super.start();
2023-04-13 02:44:12 +01:00
this.scene.sound.play('menu', { loop: true });
2023-04-13 00:09:15 +01:00
this.scene.ui.setMode(Mode.STARTER_SELECT, (starters: Starter[]) => {
2023-04-13 00:09:15 +01:00
const party = this.scene.getParty();
const loadPokemonAssets: Promise<void>[] = [];
for (let starter of starters) {
const starterGender = starter.species.malePercent !== null
? !starter.female ? Gender.MALE : Gender.FEMALE
: Gender.GENDERLESS;
2023-04-26 17:50:21 +01:00
const starterPokemon = new PlayerPokemon(this.scene, starter.species, startingLevel, starter.abilityIndex, starter.formIndex, starterGender, starter.shiny);
starterPokemon.setVisible(false);
party.push(starterPokemon);
loadPokemonAssets.push(starterPokemon.loadAssets());
2023-04-13 00:09:15 +01:00
}
Promise.all(loadPokemonAssets).then(() => {
this.scene.ui.clearText();
this.scene.ui.setMode(Mode.MESSAGE).then(() => {
SoundFade.fadeOut(this.scene.sound.get('menu'), 500, true);
this.scene.time.delayedCall(500, () => this.scene.arena.playBgm());
this.end();
});
});
});
2023-04-10 19:12:01 +01:00
}
}
export class EncounterPhase extends BattlePhase {
2023-04-28 20:03:42 +01:00
private loaded: boolean;
constructor(scene: BattleScene, loaded?: boolean) {
2023-04-10 19:12:01 +01:00
super(scene);
2023-04-28 20:03:42 +01:00
this.loaded = !!loaded;
2023-04-10 19:12:01 +01:00
}
start() {
super.start();
2023-04-21 00:44:56 +01:00
this.scene.updateWaveCountText();
2023-04-19 04:54:07 +01:00
2023-04-10 19:12:01 +01:00
const battle = this.scene.currentBattle;
2023-04-26 21:07:29 +01:00
const enemySpecies = this.scene.randomSpecies(battle.waveIndex, battle.enemyLevel, true);
2023-04-28 20:03:42 +01:00
if (!this.loaded)
battle.enemyPokemon = new EnemyPokemon(this.scene, enemySpecies, battle.enemyLevel);
2023-04-10 19:12:01 +01:00
const enemyPokemon = this.scene.getEnemyPokemon();
2023-04-11 04:15:06 +01:00
enemyPokemon.resetSummonData();
2023-04-10 19:12:01 +01:00
2023-04-18 06:32:26 +01:00
this.scene.gameData.setPokemonSeen(enemyPokemon);
2023-04-10 19:12:01 +01:00
console.log(enemyPokemon.species.name, enemyPokemon.species.speciesId, enemyPokemon.stats);
enemyPokemon.loadAssets().then(() => {
this.scene.field.add(enemyPokemon);
if (this.scene.getPlayerPokemon().visible)
this.scene.field.moveBelow(enemyPokemon, this.scene.getPlayerPokemon());
enemyPokemon.tint(0, 0.5);
2023-04-28 20:03:42 +01:00
if (!this.loaded) {
regenerateModifierPoolThresholds(this.scene.getEnemyParty(), false);
this.scene.generateEnemyModifiers();
}
2023-04-21 00:44:56 +01:00
2023-04-28 20:03:42 +01:00
this.scene.ui.setMode(Mode.MESSAGE).then(() => {
if (!this.loaded)
this.scene.gameData.saveSession(this.scene);
this.doEncounter();
});
2023-04-10 19:12:01 +01:00
});
}
doEncounter() {
2023-04-23 15:24:22 +01:00
if (startingWave > 10 && startingLevel < 100) {
for (let m = 0; m < Math.floor(startingWave / 10); m++)
2023-04-21 00:44:56 +01:00
this.scene.addModifier(getPlayerModifierTypeOptionsForWave((m + 1) * 10, 1, this.scene.getParty())[0].type.newModifier());
}
this.scene.arena.trySetWeather(getRandomWeatherType(this.scene.arena.biomeType), false);
2023-04-10 19:12:01 +01:00
const enemyPokemon = this.scene.getEnemyPokemon();
this.scene.tweens.add({
targets: [ this.scene.arenaEnemy, enemyPokemon, this.scene.arenaPlayer, this.scene.trainer ],
x: (_target, _key, value, targetIndex: integer) => targetIndex < 2 ? value + 300 : value - 300,
duration: 2000,
onComplete: () => {
enemyPokemon.untint(100, 'Sine.easeOut');
enemyPokemon.cry();
enemyPokemon.showInfo();
this.scene.ui.showText(`A wild ${enemyPokemon.name} appeared!`, null, () => this.end(), 1500);
}
});
}
end() {
if (this.scene.getEnemyPokemon().shiny)
this.scene.unshiftPhase(new ShinySparklePhase(this.scene, false));
this.scene.arena.applyTags(ArenaTrapTag, this.scene.getEnemyPokemon());
2023-04-21 19:05:16 +01:00
// TODO: Remove
2023-04-10 19:12:01 +01:00
//this.scene.unshiftPhase(new SelectModifierPhase(this.scene));
super.end();
}
}
export class NextEncounterPhase extends EncounterPhase {
constructor(scene: BattleScene) {
super(scene);
}
doEncounter(): void {
const enemyPokemon = this.scene.getEnemyPokemon();
this.scene.tweens.add({
targets: [ this.scene.arenaEnemy, this.scene.arenaNextEnemy, enemyPokemon ],
x: '+=300',
duration: 2000,
onComplete: () => {
this.scene.arenaEnemy.setX(this.scene.arenaNextEnemy.x);
this.scene.arenaNextEnemy.setX(this.scene.arenaNextEnemy.x - 300);
enemyPokemon.untint(100, 'Sine.easeOut');
enemyPokemon.cry();
enemyPokemon.showInfo();
this.scene.ui.showText(`A wild ${enemyPokemon.name} appeared!`, null, () => this.end(), 1500);
}
});
}
end() {
if (this.scene.getEnemyPokemon().shiny)
this.scene.unshiftPhase(new ShinySparklePhase(this.scene, false));
this.scene.unshiftPhase(new CheckSwitchPhase(this.scene));
super.end();
}
}
export class NewBiomeEncounterPhase extends NextEncounterPhase {
constructor(scene: BattleScene) {
super(scene);
}
doEncounter(): void {
this.scene.arena.trySetWeather(getRandomWeatherType(this.scene.arena.biomeType), false);
2023-04-10 19:12:01 +01:00
const enemyPokemon = this.scene.getEnemyPokemon();
this.scene.tweens.add({
targets: [ this.scene.arenaEnemy, enemyPokemon ],
x: (_target, _key, value, targetIndex: integer) => targetIndex < 2 ? value + 300 : value - 300,
duration: 2000,
onComplete: () => {
enemyPokemon.untint(100, 'Sine.easeOut');
enemyPokemon.cry();
enemyPokemon.showInfo();
this.scene.ui.showText(`A wild ${enemyPokemon.name} appeared!`, null, () => this.end(), 1500);
}
});
}
}
2023-04-12 05:37:56 +01:00
export class SelectBiomePhase extends BattlePhase {
2023-04-10 19:12:01 +01:00
constructor(scene: BattleScene) {
super(scene);
}
start() {
super.start();
this.scene.arena.fadeOutBgm(2000, true);
2023-04-10 19:12:01 +01:00
2023-04-12 05:37:56 +01:00
const currentBiome = this.scene.arena.biomeType;
const setNextBiome = (nextBiome: Biome) => {
this.scene.unshiftPhase(new SwitchBiomePhase(this.scene, nextBiome));
this.end();
};
if (this.scene.currentBattle.waveIndex === this.scene.finalWave - 9)
setNextBiome(Biome.END);
2023-04-27 00:19:39 +01:00
else if (Array.isArray(biomeLinks[currentBiome])) {
const biomes = biomeLinks[currentBiome] as Biome[];
if (this.scene.findModifier(m => m instanceof MapModifier)) {
this.scene.ui.setMode(Mode.BIOME_SELECT, currentBiome, (biomeIndex: integer) => {
this.scene.ui.setMode(Mode.MESSAGE);
setNextBiome(biomes[biomeIndex]);
});
} else
setNextBiome(biomes[Utils.randInt(biomes.length)]);
} else
setNextBiome(biomeLinks[currentBiome] as Biome);
2023-04-12 05:37:56 +01:00
}
}
export class SwitchBiomePhase extends BattlePhase {
private nextBiome: Biome;
constructor(scene: BattleScene, nextBiome: Biome) {
super(scene);
this.nextBiome = nextBiome;
}
start() {
super.start();
2023-04-10 19:12:01 +01:00
this.scene.tweens.add({
targets: this.scene.arenaEnemy,
x: '+=300',
duration: 2000,
onComplete: () => {
this.scene.arenaEnemy.setX(this.scene.arenaEnemy.x - 600);
this.scene.newArena(this.nextBiome);
2023-04-10 19:12:01 +01:00
const biomeKey = this.scene.arena.getBiomeKey();
const bgTexture = `${biomeKey}_bg`;
const playerTexture = `${biomeKey}_a`;
const enemyTexture = `${biomeKey}_b`;
this.scene.arenaBgTransition.setTexture(bgTexture)
this.scene.arenaBgTransition.setAlpha(0);
this.scene.arenaBgTransition.setVisible(true);
this.scene.arenaPlayerTransition.setTexture(playerTexture)
this.scene.arenaPlayerTransition.setAlpha(0);
this.scene.arenaPlayerTransition.setVisible(true);
this.scene.time.delayedCall(1000, () => this.scene.arena.playBgm());
this.scene.tweens.add({
targets: [ this.scene.arenaBgTransition, this.scene.arenaPlayerTransition ],
duration: 1000,
delay: 1000,
ease: 'Sine.easeInOut',
alpha: 1,
onComplete: () => {
this.scene.arenaBg.setTexture(bgTexture);
this.scene.arenaPlayer.setTexture(playerTexture);
this.scene.arenaEnemy.setTexture(enemyTexture);
this.scene.arenaNextEnemy.setTexture(enemyTexture);
this.scene.arenaBgTransition.setVisible(false);
this.scene.arenaPlayerTransition.setVisible(false);
this.end();
}
})
}
});
}
}
export class SummonPhase extends BattlePhase {
constructor(scene: BattleScene) {
super(scene);
}
start() {
super.start();
this.preSummon();
}
preSummon(): void {
this.scene.ui.showText(`Go! ${this.scene.getPlayerPokemon().name}!`);
this.scene.trainer.play('trainer_m_pb');
this.scene.tweens.add({
targets: this.scene.trainer,
x: -36,
duration: 1000
});
this.scene.time.delayedCall(750, () => this.summon());
}
summon(): void {
const pokeball = this.scene.add.sprite(36, 80, 'pb', 'pb');
pokeball.setVisible(false);
pokeball.setOrigin(0.5, 0.625);
this.scene.field.add(pokeball);
const playerPokemon = this.scene.getPlayerPokemon();
pokeball.setVisible(true);
this.scene.tweens.add({
targets: pokeball,
ease: 'Cubic.easeOut',
duration: 150,
x: 54,
y: 70,
onComplete: () => {
this.scene.tweens.add({
targets: pokeball,
duration: 500,
angle: 1440,
x: 100,
y: 132,
ease: 'Cubic.easeIn',
onComplete: () => {
this.scene.sound.play('pb_rel');
pokeball.destroy();
this.scene.add.existing(playerPokemon);
this.scene.field.add(playerPokemon);
playerPokemon.showInfo();
playerPokemon.playAnim();
playerPokemon.setVisible(true);
playerPokemon.setScale(0.5);
playerPokemon.tint(getPokeballTintColor(playerPokemon.pokeball));
playerPokemon.untint(250, 'Sine.easeIn');
this.scene.tweens.add({
targets: playerPokemon,
duration: 250,
ease: 'Sine.easeIn',
scale: 1,
onComplete: () => {
playerPokemon.cry();
playerPokemon.getSprite().clearTint();
playerPokemon.resetSummonData();
this.scene.time.delayedCall(1000, () => this.end());
this.scene.arena.applyTags(ArenaTrapTag, playerPokemon);
2023-04-10 19:12:01 +01:00
}
});
}
});
}
});
}
end() {
if (this.scene.getPlayerPokemon().shiny)
this.scene.unshiftPhase(new ShinySparklePhase(this.scene, true));
super.end();
}
}
export class SwitchSummonPhase extends SummonPhase {
private slotIndex: integer;
private doReturn: boolean;
2023-04-29 00:26:41 +01:00
private batonPass: boolean;
2023-04-10 19:12:01 +01:00
2023-04-29 00:26:41 +01:00
private lastPokemon: PlayerPokemon;
constructor(scene: BattleScene, slotIndex: integer, doReturn: boolean, batonPass: boolean) {
2023-04-10 19:12:01 +01:00
super(scene);
this.slotIndex = slotIndex;
this.doReturn = doReturn;
2023-04-29 00:26:41 +01:00
this.batonPass = batonPass;
2023-04-10 19:12:01 +01:00
}
start() {
super.start();
}
preSummon(): void {
if (!this.doReturn) {
this.switchAndSummon();
return;
}
const playerPokemon = this.scene.getPlayerPokemon();
2023-04-29 00:26:41 +01:00
if (!this.batonPass)
this.scene.getEnemyPokemon()?.removeTagsBySourceId(playerPokemon.id);
2023-04-23 03:14:53 +01:00
2023-04-10 19:12:01 +01:00
this.scene.ui.showText(`Come back, ${this.scene.getPlayerPokemon().name}!`);
this.scene.sound.play('pb_rel');
playerPokemon.hideInfo();
playerPokemon.tint(getPokeballTintColor(playerPokemon.pokeball), 1, 250, 'Sine.easeIn');
this.scene.tweens.add({
targets: playerPokemon,
duration: 250,
ease: 'Sine.easeIn',
scale: 0.5,
onComplete: () => {
playerPokemon.setVisible(false);
this.scene.field.remove(playerPokemon);
this.scene.time.delayedCall(750, () => this.switchAndSummon());
}
});
}
switchAndSummon() {
const party = this.scene.getParty();
const switchedPokemon = party[this.slotIndex];
2023-04-29 00:26:41 +01:00
this.lastPokemon = this.scene.getPlayerPokemon();
if (this.batonPass) {
this.scene.getEnemyPokemon()?.transferTagsBySourceId(this.lastPokemon.id, switchedPokemon.id);
if (!this.scene.findModifier(m => m instanceof SwitchEffectTransferModifier && (m as SwitchEffectTransferModifier).pokemonId === switchedPokemon.id)) {
const batonPassModifier = this.scene.findModifier(m => m instanceof SwitchEffectTransferModifier
&& (m as SwitchEffectTransferModifier).pokemonId === this.lastPokemon.id) as SwitchEffectTransferModifier;
this.scene.tryTransferHeldItemModifier(batonPassModifier, switchedPokemon, false, false);
}
}
party[this.slotIndex] = this.lastPokemon;
2023-04-10 19:12:01 +01:00
party[0] = switchedPokemon;
this.scene.ui.showText(`Go! ${switchedPokemon.name}!`);
this.summon();
}
2023-04-29 00:26:41 +01:00
end() {
if (this.batonPass)
this.scene.getPlayerPokemon().transferSummon(this.lastPokemon);
this.lastPokemon.resetSummonData();
super.end();
}
2023-04-10 19:12:01 +01:00
}
export class CheckSwitchPhase extends BattlePhase {
constructor(scene: BattleScene) {
super(scene)
}
start() {
super.start();
if (this.scene.field.getAll().indexOf(this.scene.getPlayerPokemon()) === -1) {
this.scene.unshiftPhase(new SummonMissingPhase(this.scene));
super.end();
return;
}
if (!this.scene.getParty().slice(1).filter(p => p.hp).length) {
super.end();
return;
}
2023-04-22 00:30:04 +01:00
if (this.scene.getPlayerPokemon().getTag(BattlerTagType.FRENZY)) {
2023-04-15 06:32:16 +01:00
super.end();
return;
}
2023-04-10 19:12:01 +01:00
this.scene.ui.showText('Will you switch\nPOKéMON?', null, () => {
this.scene.ui.setMode(Mode.CONFIRM, () => {
this.scene.ui.setMode(Mode.MESSAGE);
2023-04-10 19:12:01 +01:00
this.scene.unshiftPhase(new SwitchPhase(this.scene, false, true));
this.end();
}, () => {
this.scene.ui.setMode(Mode.MESSAGE);
this.end();
});
2023-04-10 19:12:01 +01:00
});
}
}
export class SummonMissingPhase extends SummonPhase {
constructor(scene: BattleScene) {
super(scene);
}
preSummon(): void {
this.scene.ui.showText(`Go! ${this.scene.getPlayerPokemon().name}!`);
this.scene.time.delayedCall(250, () => this.summon());
}
}
type PokemonFunc = (pokemon: Pokemon) => void;
export abstract class FieldPhase extends BattlePhase {
isPlayerDelayed(): boolean {
const playerPokemon = this.scene.getPlayerPokemon();
const enemyPokemon = this.scene.getEnemyPokemon();
const playerSpeed = playerPokemon?.getBattleStat(Stat.SPD) || 0;
const enemySpeed = enemyPokemon?.getBattleStat(Stat.SPD) || 0;
const speedDelayed = new Utils.BooleanHolder(playerSpeed < enemySpeed);
this.scene.arena.applyTags(TrickRoomTag, speedDelayed);
return speedDelayed.value || (playerSpeed === enemySpeed && Utils.randInt(2) === 1);
}
executeForBoth(func: PokemonFunc): void {
const playerPokemon = this.scene.getPlayerPokemon();
const enemyPokemon = this.scene.getEnemyPokemon();
const delayed = this.isPlayerDelayed();
if (!delayed && playerPokemon)
func(playerPokemon);
if (enemyPokemon)
func(enemyPokemon);
if (delayed && playerPokemon)
func(playerPokemon);
}
}
export class CommandPhase extends FieldPhase {
2023-04-10 19:12:01 +01:00
constructor(scene: BattleScene) {
super(scene)
}
start() {
super.start();
2023-04-13 17:16:36 +01:00
const playerPokemon = this.scene.getPlayerPokemon();
2023-04-10 19:12:01 +01:00
2023-04-13 17:16:36 +01:00
this.scene.currentBattle.addParticipant(playerPokemon);
playerPokemon.resetTurnData();
this.scene.getEnemyPokemon().resetTurnData();
2023-04-21 02:32:48 +01:00
const moveQueue = playerPokemon.getMoveQueue();
while (moveQueue.length && moveQueue[0]
2023-04-30 05:51:33 +01:00
&& moveQueue[0].move && (!playerPokemon.moveset.find(m => m.moveId === moveQueue[0].move)
2023-04-21 02:32:48 +01:00
|| !playerPokemon.moveset[playerPokemon.moveset.findIndex(m => m.moveId === moveQueue[0].move)].isUsable(moveQueue[0].ignorePP)))
moveQueue.shift();
if (moveQueue.length) {
const queuedMove = moveQueue[0];
if (!queuedMove.move)
this.handleCommand(Command.FIGHT, -1);
else {
const moveIndex = playerPokemon.moveset.findIndex(m => m.moveId === queuedMove.move);
if (playerPokemon.moveset[moveIndex].isUsable(queuedMove.ignorePP))
this.handleCommand(Command.FIGHT, moveIndex);
}
2023-04-19 23:19:55 +01:00
} else
2023-04-13 17:16:36 +01:00
this.scene.ui.setMode(Mode.COMMAND);
2023-04-10 19:12:01 +01:00
}
2023-04-29 00:26:41 +01:00
handleCommand(command: Command, cursor: integer, ...args: any[]): boolean {
2023-04-10 19:12:01 +01:00
const playerPokemon = this.scene.getPlayerPokemon();
const enemyPokemon = this.scene.getEnemyPokemon();
let success: boolean;
2023-04-12 00:08:03 +01:00
let isDelayed = (command: Command, playerMove: PokemonMove, enemyMove: PokemonMove) => {
switch (command) {
case Command.FIGHT:
if (playerMove && enemyMove) {
const playerMovePriority = playerMove.getMove().priority;
const enemyMovePriority = enemyMove.getMove().priority;
if (playerMovePriority !== enemyMovePriority)
return playerMovePriority < enemyMovePriority;
}
break;
case Command.BALL:
case Command.POKEMON:
return false;
case Command.RUN:
return true;
}
return this.isPlayerDelayed();
};
let playerMove: PokemonMove;
2023-04-10 19:12:01 +01:00
switch (command) {
case Command.FIGHT:
2023-04-21 02:32:48 +01:00
if (cursor == -1) {
this.scene.pushPhase(new PlayerMovePhase(this.scene, playerPokemon, new PokemonMove(Moves.NONE)));
success = true;
break;
}
2023-04-10 19:12:01 +01:00
if (playerPokemon.trySelectMove(cursor)) {
playerMove = playerPokemon.moveset[cursor];
const playerPhase = new PlayerMovePhase(this.scene, playerPokemon, playerMove);
2023-04-10 19:12:01 +01:00
this.scene.pushPhase(playerPhase);
success = true;
2023-04-19 23:19:55 +01:00
} else if (cursor < playerPokemon.moveset.length) {
const move = playerPokemon.moveset[cursor];
2023-04-20 03:51:46 +01:00
if (move.isDisabled()) {
this.scene.ui.setMode(Mode.MESSAGE);
this.scene.ui.showText(`${move.getName()} is disabled!`, null, () => {
this.scene.ui.clearText();
this.scene.ui.setMode(Mode.FIGHT);
}, null, true);
}
2023-04-10 19:12:01 +01:00
}
2023-04-19 23:19:55 +01:00
2023-04-10 19:12:01 +01:00
break;
case Command.BALL:
if (this.scene.arena.biomeType === Biome.END) {
2023-04-30 05:51:33 +01:00
this.scene.ui.setMode(Mode.COMMAND);
this.scene.ui.setMode(Mode.MESSAGE);
2023-04-30 05:51:33 +01:00
this.scene.ui.showText(`A strange force\nprevents using POKé BALLS.`, null, () => {
this.scene.ui.showText(null, 0);
this.scene.ui.setMode(Mode.COMMAND);
}, null, true);
} else if (cursor < 4) {
2023-04-10 19:12:01 +01:00
this.scene.unshiftPhase(new AttemptCapturePhase(this.scene, cursor as PokeballType));
success = true;
}
break;
case Command.POKEMON:
2023-04-29 00:26:41 +01:00
const trapTag = playerPokemon.findTag(t => t instanceof TrappedTag) as TrappedTag;
const batonPass = args[0] as boolean;
if (batonPass || !trapTag) {
this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, cursor, true, args[0] as boolean));
2023-04-23 03:14:53 +01:00
success = true;
} else
this.scene.ui.showText(`${this.scene.getPokemonById(trapTag.sourceId).name}'s ${trapTag.getMoveName()}\nprevents switching!`, null, () => {
this.scene.ui.showText(null, 0);
}, null, true);
2023-04-10 19:12:01 +01:00
break;
2023-04-14 04:04:51 +01:00
case Command.RUN:
2023-04-25 03:32:12 +01:00
//this.scene.unshiftPhase(new MoveAnimTestPhase(this.scene));
//success = true;
2023-04-14 04:04:51 +01:00
break;
2023-04-10 19:12:01 +01:00
}
2023-04-12 05:37:56 +01:00
2023-04-10 19:12:01 +01:00
if (success) {
if (this.scene.arena.weather)
this.scene.unshiftPhase(new WeatherEffectPhase(this.scene, this.scene.arena.weather));
2023-04-10 19:12:01 +01:00
const enemyMove = enemyPokemon.getNextMove();
const enemyPhase = new EnemyMovePhase(this.scene, enemyPokemon, enemyMove);
if (isDelayed(command, playerMove, enemyMove))
2023-04-10 19:12:01 +01:00
this.scene.unshiftPhase(enemyPhase);
else
this.scene.pushPhase(enemyPhase);
2023-04-12 00:08:03 +01:00
const statusEffectPhases: PostTurnStatusEffectPhase[] = [];
if (playerPokemon.status && playerPokemon.status.isPostTurn())
statusEffectPhases.push(new PostTurnStatusEffectPhase(this.scene, true));
if (enemyPokemon.status && enemyPokemon.status.isPostTurn()) {
const enemyStatusEffectPhase = new PostTurnStatusEffectPhase(this.scene, false);
if (isDelayed(command, playerMove, enemyMove))
2023-04-12 00:08:03 +01:00
statusEffectPhases.unshift(enemyStatusEffectPhase);
else
statusEffectPhases.push(enemyStatusEffectPhase);
}
for (let sef of statusEffectPhases)
this.scene.pushPhase(sef);
2023-04-13 17:16:36 +01:00
this.scene.pushPhase(new TurnEndPhase(this.scene));
2023-04-10 19:12:01 +01:00
this.end();
}
return success;
}
end() {
this.scene.ui.setMode(Mode.MESSAGE).then(() => super.end());
}
}
export class TurnEndPhase extends FieldPhase {
2023-04-13 17:16:36 +01:00
constructor(scene: BattleScene) {
super(scene);
}
start() {
super.start();
2023-04-22 00:30:04 +01:00
this.scene.currentBattle.incrementTurn();
2023-04-19 23:19:55 +01:00
const handlePokemon = (pokemon: Pokemon) => {
2023-04-21 05:10:45 +01:00
if (!pokemon || !pokemon.hp)
2023-04-19 23:19:55 +01:00
return;
2023-04-22 00:30:04 +01:00
pokemon.lapseTags(BattlerTagLapseType.TURN_END);
2023-04-19 23:19:55 +01:00
const disabledMoves = pokemon.moveset.filter(m => m.isDisabled());
for (let dm of disabledMoves) {
if (!--dm.disableTurns)
this.scene.pushPhase(new MessagePhase(this.scene, `${dm.getName()} is disabled\nno more!`));
}
2023-04-21 00:44:56 +01:00
const hasUsableBerry = !!this.scene.findModifier(m => m instanceof BerryModifier && m.shouldApply([ pokemon ]), pokemon.isPlayer());
2023-04-20 20:46:05 +01:00
if (hasUsableBerry)
this.scene.pushPhase(new BerryPhase(this.scene, pokemon.isPlayer()));
2023-04-21 01:15:16 +01:00
this.scene.applyModifiers(TurnHealModifier, pokemon.isPlayer(), pokemon);
2023-04-23 15:24:22 +01:00
this.scene.applyModifiers(HeldItemTransferModifier, pokemon.isPlayer(), pokemon);
2023-04-19 23:19:55 +01:00
pokemon.battleSummonData.turnCount++;
};
this.executeForBoth(handlePokemon);
2023-04-22 00:30:04 +01:00
this.scene.arena.lapseTags();
2023-04-13 17:16:36 +01:00
if (this.scene.arena.weather && !this.scene.arena.weather.lapse())
this.scene.arena.trySetWeather(WeatherType.NONE, false);
2023-04-13 17:16:36 +01:00
this.end();
}
}
export class BattleEndPhase extends BattlePhase {
constructor(scene: BattleScene) {
super(scene);
}
start() {
super.start();
2023-04-21 00:44:56 +01:00
this.scene.clearEnemyModifiers();
const tempBattleStatBoosterModifiers = this.scene.getModifiers(TempBattleStatBoosterModifier) as TempBattleStatBoosterModifier[];
for (let m of tempBattleStatBoosterModifiers) {
if (!m.lapse())
this.scene.removeModifier(m);
}
this.scene.updateModifiers().then(() => this.end());
}
}
export abstract class PokemonPhase extends FieldPhase {
2023-04-10 19:12:01 +01:00
protected player: boolean;
constructor(scene: BattleScene, player: boolean) {
super(scene);
this.player = player;
}
getPokemon() {
return this.player ? this.scene.getPlayerPokemon() : this.scene.getEnemyPokemon();
}
}
export abstract class PartyMemberPokemonPhase extends PokemonPhase {
protected partyMemberIndex: integer;
constructor(scene: BattleScene, partyMemberIndex: integer) {
super(scene, true);
this.partyMemberIndex = partyMemberIndex;
}
getPokemon() {
return this.scene.getParty()[this.partyMemberIndex];
}
}
2023-04-14 06:08:44 +01:00
export class CommonAnimPhase extends PokemonPhase {
private anim: CommonAnim;
constructor(scene: BattleScene, player: boolean, anim: CommonAnim) {
super(scene, player);
this.anim = anim;
}
start() {
2023-04-21 00:44:56 +01:00
new CommonBattleAnim(this.anim, this.getPokemon(), this.player ? this.scene.getEnemyPokemon() : this.scene.getPlayerPokemon()).play(this.scene, () => {
2023-04-14 06:08:44 +01:00
this.end();
});
}
}
2023-04-15 06:32:16 +01:00
export abstract class MovePhase extends BattlePhase {
2023-04-10 19:12:01 +01:00
protected pokemon: Pokemon;
protected move: PokemonMove;
2023-04-16 23:40:32 +01:00
protected followUp: boolean;
2023-04-12 00:08:03 +01:00
protected cancelled: boolean;
2023-04-10 19:12:01 +01:00
2023-04-16 23:40:32 +01:00
constructor(scene: BattleScene, pokemon: Pokemon, move: PokemonMove, followUp?: boolean) {
2023-04-10 19:12:01 +01:00
super(scene);
this.pokemon = pokemon;
this.move = move;
2023-04-16 23:40:32 +01:00
this.followUp = !!followUp;
2023-04-12 00:08:03 +01:00
this.cancelled = false;
2023-04-10 19:12:01 +01:00
}
abstract getEffectPhase(): MoveEffectPhase;
canMove(): boolean {
2023-04-19 23:19:55 +01:00
return !!this.pokemon.hp && this.move.isUsable();
2023-04-10 19:12:01 +01:00
}
2023-04-15 06:32:16 +01:00
cancel(): void {
this.cancelled = true;
}
2023-04-10 19:12:01 +01:00
start() {
super.start();
2023-04-19 23:19:55 +01:00
console.log(Moves[this.move.moveId]);
2023-04-16 23:40:32 +01:00
const target = this.pokemon.isPlayer() ? this.scene.getEnemyPokemon() : this.scene.getPlayerPokemon();
2023-04-19 04:54:07 +01:00
if (!this.followUp && this.canMove())
2023-04-22 00:30:04 +01:00
this.pokemon.lapseTags(BattlerTagLapseType.MOVE);
2023-04-15 06:32:16 +01:00
2023-04-12 00:08:03 +01:00
const doMove = () => {
2023-04-21 02:32:48 +01:00
const moveQueue = this.pokemon.getMoveQueue();
if (moveQueue.length && moveQueue[0].move === Moves.NONE) {
moveQueue.shift();
this.cancel();
}
2023-04-12 00:08:03 +01:00
if (this.cancelled) {
2023-04-25 06:32:48 +01:00
this.pokemon.pushMoveHistory({ move: Moves.NONE, result: MoveResult.FAILED });
2023-04-12 00:08:03 +01:00
this.end();
return;
}
2023-04-16 23:40:32 +01:00
2023-04-22 00:30:04 +01:00
this.scene.queueMessage(getPokemonMessage(this.pokemon, ` used\n${this.move.getName()}!`), 500);
2023-04-21 02:32:48 +01:00
if (!moveQueue.length || !moveQueue.shift().ignorePP)
2023-04-13 17:16:36 +01:00
this.move.ppUsed++;
2023-04-16 23:40:32 +01:00
let success = this.move.getMove().applyConditions(this.pokemon, target, this.move.getMove());
if (success && this.scene.arena.isMoveWeatherCancelled(this.move.getMove()))
success = false;
if (success)
this.scene.unshiftPhase(this.getEffectPhase());
else {
2023-04-25 06:32:48 +01:00
this.pokemon.pushMoveHistory({ move: this.move.moveId, result: MoveResult.FAILED, virtual: this.move.virtual });
2023-04-22 00:30:04 +01:00
this.scene.queueMessage('But it failed!');
}
2023-04-12 00:08:03 +01:00
this.end();
};
2023-04-10 19:12:01 +01:00
if (!this.canMove()) {
2023-04-19 23:19:55 +01:00
if (this.move.isDisabled())
2023-04-22 00:30:04 +01:00
this.scene.queueMessage(`${this.move.getName()} is disabled!`);
2023-04-10 19:12:01 +01:00
this.end();
return;
}
2023-04-12 00:08:03 +01:00
2023-04-16 23:40:32 +01:00
if (!this.followUp && this.pokemon.status && !this.pokemon.status.isPostTurn()) {
2023-04-12 00:08:03 +01:00
this.pokemon.status.incrementTurn();
let activated = false;
let healed = false;
2023-04-16 23:40:32 +01:00
2023-04-12 00:08:03 +01:00
switch (this.pokemon.status.effect) {
case StatusEffect.PARALYSIS:
if (Utils.randInt(4) === 0) {
activated = true;
this.cancelled = true;
}
break;
case StatusEffect.SLEEP:
2023-04-16 23:40:32 +01:00
applyMoveAttrs(BypassSleepAttr, this.pokemon, target, this.move.getMove());
2023-04-12 00:08:03 +01:00
healed = this.pokemon.status.turnCount === this.pokemon.status.cureTurn;
2023-04-22 00:30:04 +01:00
activated = !healed && !this.pokemon.getTag(BattlerTagType.BYPASS_SLEEP);
2023-04-12 00:08:03 +01:00
this.cancelled = activated;
break;
case StatusEffect.FREEZE:
healed = Utils.randInt(5) === 0;
activated = !healed;
this.cancelled = activated;
break;
}
if (activated) {
2023-04-22 00:30:04 +01:00
this.scene.queueMessage(getPokemonMessage(this.pokemon, getStatusEffectActivationText(this.pokemon.status.effect)));
2023-04-15 06:32:16 +01:00
this.scene.unshiftPhase(new CommonAnimPhase(this.scene, this.pokemon.isPlayer(), CommonAnim.POISON + (this.pokemon.status.effect - 1)));
doMove();
2023-04-12 00:08:03 +01:00
} else {
if (healed) {
2023-04-22 00:30:04 +01:00
this.scene.queueMessage(getPokemonMessage(this.pokemon, getStatusEffectHealText(this.pokemon.status.effect)));
2023-04-12 00:08:03 +01:00
this.pokemon.resetStatus();
2023-04-20 20:46:05 +01:00
this.pokemon.updateInfo();
2023-04-12 00:08:03 +01:00
}
doMove();
}
} else
doMove();
2023-04-10 19:12:01 +01:00
}
2023-04-16 23:40:32 +01:00
end() {
2023-04-18 20:07:10 +01:00
if (!this.followUp && this.canMove())
2023-04-16 23:40:32 +01:00
this.scene.unshiftPhase(new MoveEndPhase(this.scene, this.pokemon.isPlayer()));
super.end();
}
2023-04-10 19:12:01 +01:00
}
export class PlayerMovePhase extends MovePhase {
2023-04-16 23:40:32 +01:00
constructor(scene: BattleScene, pokemon: PlayerPokemon, move: PokemonMove, followUp?: boolean) {
super(scene, pokemon, move, followUp);
2023-04-10 19:12:01 +01:00
}
getEffectPhase(): MoveEffectPhase {
return new PlayerMoveEffectPhase(this.scene, this.move);
}
}
export class EnemyMovePhase extends MovePhase {
2023-04-16 23:40:32 +01:00
constructor(scene: BattleScene, pokemon: EnemyPokemon, move: PokemonMove, followUp?: boolean) {
super(scene, pokemon, move, followUp);
2023-04-10 19:12:01 +01:00
}
getEffectPhase(): MoveEffectPhase {
return new EnemyMoveEffectPhase(this.scene, this.move);
}
}
abstract class MoveEffectPhase extends PokemonPhase {
protected move: PokemonMove;
constructor(scene: BattleScene, player: boolean, move: PokemonMove) {
super(scene, player);
this.move = move;
}
start() {
super.start();
const user = this.getUserPokemon();
const target = this.getTargetPokemon();
2023-04-13 17:16:36 +01:00
const overridden = new Utils.BooleanHolder(false);
2023-04-10 21:17:25 +01:00
2023-04-16 05:29:55 +01:00
applyMoveAttrs(OverrideMoveEffectAttr, user, target, this.move.getMove(), overridden).then(() => {
2023-04-10 21:17:25 +01:00
2023-04-13 17:16:36 +01:00
if (overridden.value) {
2023-04-10 21:17:25 +01:00
this.end();
2023-04-13 17:16:36 +01:00
return;
2023-04-10 19:12:01 +01:00
}
2023-04-13 17:16:36 +01:00
2023-04-22 00:30:04 +01:00
user.lapseTags(BattlerTagLapseType.MOVE_EFFECT);
2023-04-13 17:16:36 +01:00
if (user.turnData.hitsLeft === undefined) {
const hitCount = new Utils.IntegerHolder(1);
2023-04-16 05:29:55 +01:00
applyMoveAttrs(MultiHitAttr, user, target, this.move.getMove(), hitCount);
2023-04-13 17:16:36 +01:00
user.turnData.hitCount = 0;
user.turnData.hitsLeft = user.turnData.hitsTotal = hitCount.value;
}
if (!this.hitCheck()) {
2023-04-22 00:30:04 +01:00
this.scene.queueMessage(getPokemonMessage(user, '\'s\nattack missed!'));
2023-04-25 06:32:48 +01:00
user.pushMoveHistory({ move: this.move.moveId, result: MoveResult.MISSED, virtual: this.move.virtual });
2023-04-16 05:29:55 +01:00
applyMoveAttrs(MissEffectAttr, user, target, this.move.getMove());
2023-04-13 17:16:36 +01:00
this.end();
return;
2023-04-10 19:12:01 +01:00
}
2023-04-18 17:30:47 +01:00
2023-04-22 00:30:04 +01:00
const isProtected = !this.move.getMove().hasFlag(MoveFlags.IGNORE_PROTECT) && target.lapseTag(BattlerTagType.PROTECTED);
2023-04-13 17:16:36 +01:00
new MoveAnim(this.move.getMove().id as Moves, user, target).play(this.scene, () => {
2023-04-18 17:30:47 +01:00
const result = !isProtected ? target.apply(user, this.move) : MoveResult.NO_EFFECT;
2023-04-15 06:32:16 +01:00
++user.turnData.hitCount;
2023-04-25 06:32:48 +01:00
user.pushMoveHistory({ move: this.move.moveId, result: result, virtual: this.move.virtual });
2023-04-23 15:24:22 +01:00
if (result !== MoveResult.NO_EFFECT && result !== MoveResult.FAILED) {
applyMoveAttrs(MoveEffectAttr, user, target, this.move.getMove());
2023-04-25 06:32:48 +01:00
if (result < MoveResult.NO_EFFECT) {
const flinched = new Utils.BooleanHolder(false);
user.scene.applyModifiers(FlinchChanceModifier, user.isPlayer(), user, flinched);
if (flinched.value)
target.addTag(BattlerTagType.FLINCHED, undefined, this.move.moveId, user.id);
}
2023-04-23 15:24:22 +01:00
// Charge attribute with charge effect takes all effect attributes and applies them to charge stage, so ignore them if this is present
2023-04-30 05:51:33 +01:00
if (!isProtected && !this.move.getMove().getAttrs(ChargeAttr).filter(ca => (ca as ChargeAttr).chargeEffect).length)
applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveHitEffectAttr && (!!target.hp || (attr as MoveHitEffectAttr).selfTarget), user, target, this.move.getMove());
2023-04-23 15:24:22 +01:00
}
2023-04-24 05:38:28 +01:00
this.end();
2023-04-13 17:16:36 +01:00
});
2023-04-10 19:12:01 +01:00
});
}
2023-04-10 21:17:25 +01:00
end() {
const user = this.getUserPokemon();
2023-04-14 04:04:51 +01:00
if (--user.turnData.hitsLeft >= 1 && this.getTargetPokemon().hp)
2023-04-10 21:17:25 +01:00
this.scene.unshiftPhase(this.getNewHitPhase());
2023-04-13 17:16:36 +01:00
else {
if (user.turnData.hitsTotal > 1)
2023-04-22 00:30:04 +01:00
this.scene.queueMessage(`Hit ${user.turnData.hitCount} time(s)!`);
2023-04-21 00:44:56 +01:00
this.scene.applyModifiers(HitHealModifier, this.player, user);
2023-04-13 17:16:36 +01:00
}
2023-04-10 21:17:25 +01:00
super.end();
}
hitCheck(): boolean {
2023-04-18 17:30:47 +01:00
if (this.move.getMove().selfTarget)
return true;
2023-04-13 17:16:36 +01:00
2023-04-15 06:32:16 +01:00
const hiddenTag = this.getTargetPokemon().getTag(HiddenTag);
2023-04-13 17:16:36 +01:00
if (hiddenTag) {
if (!this.move.getMove().getAttrs(HitsTagAttr).filter(hta => (hta as HitsTagAttr).tagType === hiddenTag.tagType).length)
return false;
}
2023-04-28 01:12:25 +01:00
if (this.getUserPokemon().getTag(BattlerTagType.IGNORE_ACCURACY))
return true;
const moveAccuracy = new Utils.NumberHolder(this.move.getMove().accuracy);
applyMoveAttrs(VariableAccuracyAttr, this.getUserPokemon(), this.getTargetPokemon(), this.move.getMove(), moveAccuracy);
if (moveAccuracy.value === -1)
return true;
2023-04-13 17:16:36 +01:00
2023-04-10 21:17:25 +01:00
if (this.move.getMove().category !== MoveCategory.STATUS) {
const userAccuracyLevel = new Utils.IntegerHolder(this.getUserPokemon().summonData.battleStats[BattleStat.ACC]);
const targetEvasionLevel = new Utils.IntegerHolder(this.getTargetPokemon().summonData.battleStats[BattleStat.EVA]);
2023-04-21 00:44:56 +01:00
this.scene.applyModifiers(TempBattleStatBoosterModifier, this.player, TempBattleStat.ACC, userAccuracyLevel);
2023-04-10 21:17:25 +01:00
const rand = Utils.randInt(100, 1);
let accuracyMultiplier = 1;
if (userAccuracyLevel.value !== targetEvasionLevel.value) {
accuracyMultiplier = userAccuracyLevel.value > targetEvasionLevel.value
? (3 + Math.min(userAccuracyLevel.value - targetEvasionLevel.value, 6)) / 3
: 3 / (3 + Math.min(targetEvasionLevel.value - userAccuracyLevel.value, 6));
2023-04-10 21:17:25 +01:00
}
return rand <= this.move.getMove().accuracy * accuracyMultiplier;
2023-04-10 21:17:25 +01:00
}
2023-04-28 20:03:42 +01:00
2023-04-10 21:17:25 +01:00
return true;
}
2023-04-10 19:12:01 +01:00
abstract getUserPokemon(): Pokemon;
abstract getTargetPokemon(): Pokemon;
2023-04-10 21:17:25 +01:00
abstract getNewHitPhase(): MoveEffectPhase;
2023-04-10 19:12:01 +01:00
}
export class PlayerMoveEffectPhase extends MoveEffectPhase {
constructor(scene: BattleScene, move: PokemonMove) {
super(scene, true, move);
}
getUserPokemon(): Pokemon {
return this.scene.getPlayerPokemon();
}
getTargetPokemon(): Pokemon {
/*if (this.move.getMove().category === MoveCategory.STATUS)
return this.getUserPokemon();*/
return this.scene.getEnemyPokemon();
}
2023-04-10 21:17:25 +01:00
getNewHitPhase() {
return new PlayerMoveEffectPhase(this.scene, this.move);
}
2023-04-10 19:12:01 +01:00
}
export class EnemyMoveEffectPhase extends MoveEffectPhase {
constructor(scene: BattleScene, move: PokemonMove) {
super(scene, false, move);
}
getUserPokemon(): Pokemon {
return this.scene.getEnemyPokemon();
}
getTargetPokemon(): Pokemon {
/*if (this.move.getMove().category === MoveCategory.STATUS)
return this.getUserPokemon();*/
return this.scene.getPlayerPokemon();
}
2023-04-10 21:17:25 +01:00
getNewHitPhase() {
return new EnemyMoveEffectPhase(this.scene, this.move);
}
2023-04-10 19:12:01 +01:00
}
2023-04-16 23:40:32 +01:00
export class MoveEndPhase extends PokemonPhase {
constructor(scene: BattleScene, player: boolean) {
super(scene, player);
}
start() {
super.start();
2023-04-22 00:30:04 +01:00
this.getPokemon().lapseTags(BattlerTagLapseType.AFTER_MOVE);
2023-04-16 23:40:32 +01:00
this.end();
}
}
2023-04-14 04:04:51 +01:00
export class MoveAnimTestPhase extends BattlePhase {
private moveQueue: Moves[];
constructor(scene: BattleScene, moveQueue?: Moves[]) {
super(scene);
2023-04-21 02:32:48 +01:00
this.moveQueue = moveQueue || Utils.getEnumValues(Moves).slice(1);
2023-04-14 04:04:51 +01:00
}
start() {
const moveQueue = this.moveQueue.slice(0);
this.playMoveAnim(moveQueue, true);
}
playMoveAnim(moveQueue: Moves[], player: boolean) {
const moveId = player ? moveQueue[0] : moveQueue.shift();
if (moveId === undefined) {
this.playMoveAnim(this.moveQueue.slice(0), true);
return;
2023-04-25 03:32:12 +01:00
} else if (player)
console.log(Moves[moveId]);
2023-04-14 04:04:51 +01:00
initMoveAnim(moveId).then(() => {
loadMoveAnimAssets(this.scene, [ moveId ], true)
.then(() => {
new MoveAnim(moveId, player ? this.scene.getPlayerPokemon() : this.scene.getEnemyPokemon(),
player ? this.scene.getEnemyPokemon() : this.scene.getPlayerPokemon()).play(this.scene, () => {
if (player)
this.playMoveAnim(moveQueue, false);
else
this.playMoveAnim(moveQueue, true);
});
});
});
}
}
export class ShowAbilityPhase extends PokemonPhase {
constructor(scene: BattleScene, player: boolean) {
super(scene, player);
}
start() {
this.scene.abilityBar.showAbility(this.getPokemon());
this.end();
}
}
2023-04-11 04:15:06 +01:00
export class StatChangePhase extends PokemonPhase {
private stats: BattleStat[];
2023-04-27 04:33:13 +01:00
private selfTarget: boolean;
2023-04-11 04:15:06 +01:00
private levels: integer;
2023-04-27 04:33:13 +01:00
constructor(scene: BattleScene, player: boolean, selfTarget: boolean, stats: BattleStat[], levels: integer) {
2023-04-11 04:15:06 +01:00
super(scene, player);
2023-04-15 06:32:16 +01:00
const allStats = Utils.getEnumValues(BattleStat);
this.selfTarget = selfTarget;
2023-04-15 06:32:16 +01:00
this.stats = stats.map(s => s !== BattleStat.RAND ? s : allStats[Utils.randInt(BattleStat.SPD + 1)]);
2023-04-11 04:15:06 +01:00
this.levels = levels;
}
start() {
const pokemon = this.getPokemon();
2023-04-27 04:33:13 +01:00
const filteredStats = this.stats.filter(stat => {
const cancelled = new Utils.BooleanHolder(false);
if (!this.selfTarget && this.levels < 0)
applyPreStatChangeAbAttrs(ProtectStatAttr, this.getPokemon(), stat, cancelled);
2023-04-27 04:33:13 +01:00
return !cancelled.value;
});
2023-04-11 04:15:06 +01:00
const battleStats = this.getPokemon().summonData.battleStats;
2023-04-27 04:33:13 +01:00
const relLevels = filteredStats.map(stat => (this.levels >= 1 ? Math.min(battleStats[stat] + this.levels, 6) : Math.max(battleStats[stat] + this.levels, -6)) - battleStats[stat]);
2023-04-11 04:15:06 +01:00
const end = () => {
2023-04-27 04:33:13 +01:00
const messages = this.getStatChangeMessages(filteredStats, relLevels);
2023-04-11 04:15:06 +01:00
for (let message of messages)
2023-04-22 00:30:04 +01:00
this.scene.queueMessage(message);
2023-04-11 04:15:06 +01:00
2023-04-27 04:33:13 +01:00
for (let stat of filteredStats)
2023-04-11 04:15:06 +01:00
pokemon.summonData.battleStats[stat] = Math.max(Math.min(pokemon.summonData.battleStats[stat] + this.levels, 6), -6);
this.end();
};
if (relLevels.filter(l => l).length) {
pokemon.enableMask();
const pokemonMaskSprite = pokemon.maskSprite;
2023-04-27 04:33:13 +01:00
const statSprite = this.scene.add.tileSprite((this.player ? 106 : 236) * 6, ((this.player ? 148 : 84) + (this.levels >= 1 ? 160 : 0)) * 6, 156, 316, 'battle_stats', filteredStats.length > 1 ? 'mix' : BattleStat[filteredStats[0]].toLowerCase());
2023-04-11 04:15:06 +01:00
statSprite.setAlpha(0);
statSprite.setScale(6);
statSprite.setOrigin(0.5, 1);
this.scene.sound.play(`stat_${this.levels >= 1 ? 'up' : 'down'}`);
statSprite.setMask(new Phaser.Display.Masks.BitmapMask(this.scene, pokemonMaskSprite));
this.scene.tweens.add({
targets: statSprite,
duration: 250,
alpha: 0.8375,
onComplete: () => {
this.scene.tweens.add({
targets: statSprite,
delay: 1000,
duration: 250,
alpha: 0
});
}
});
2023-04-11 14:41:11 +01:00
2023-04-11 04:15:06 +01:00
this.scene.tweens.add({
targets: statSprite,
duration: 1500,
y: `${this.levels >= 1 ? '-' : '+'}=${160 * 6}`
});
this.scene.time.delayedCall(1750, () => {
pokemon.disableMask();
end();
});
} else
end();
}
2023-04-27 04:33:13 +01:00
getStatChangeMessages(stats: BattleStat[], relLevels: integer[]): string[] {
2023-04-11 04:15:06 +01:00
const messages: string[] = [];
2023-04-27 04:33:13 +01:00
for (let s = 0; s < stats.length; s++)
messages.push(getPokemonMessage(this.getPokemon(), `'s ${getBattleStatName(stats[s])} ${getBattleStatLevelChangeDescription(Math.abs(relLevels[s]), this.levels >= 1)}!`));
2023-04-11 04:15:06 +01:00
return messages;
}
}
export class WeatherEffectPhase extends CommonAnimPhase {
private weather: Weather;
constructor(scene: BattleScene, weather: Weather) {
super(scene, true, CommonAnim.SUNNY + (weather.weatherType - 1));
this.weather = weather;
}
start() {
if (this.weather.isDamaging()) {
const cancelled = new Utils.BooleanHolder(false);
this.executeForBoth((pokemon: Pokemon) => applyPreWeatherEffectAbAttrs(SuppressWeatherEffectAbAttr, pokemon, this.weather, cancelled));
if (!cancelled.value) {
const inflictDamage = (pokemon: Pokemon) => {
const cancelled = new Utils.BooleanHolder(false);
applyPreWeatherEffectAbAttrs(PreWeatherDamageAbAttr, pokemon, this.weather, cancelled);
if (cancelled.value)
return;
this.scene.queueMessage(getWeatherDamageMessage(this.weather.weatherType, pokemon));
this.scene.unshiftPhase(new DamagePhase(this.scene, pokemon.isPlayer()));
pokemon.damage(Math.ceil(pokemon.getMaxHp() / 16));
};
this.executeForBoth((pokemon: Pokemon) => {
const immune = !pokemon || !!pokemon.getTypes().filter(t => this.weather.isTypeDamageImmune(t)).length;
if (!immune)
inflictDamage(pokemon);
});
}
}
this.scene.ui.showText(getWeatherLapseMessage(this.weather.weatherType), null, () => {
this.executeForBoth((pokemon: Pokemon) => applyPostWeatherLapseAbAttrs(PostWeatherLapseAbAttr, pokemon, this.weather));
super.start();
});
}
}
2023-04-10 19:12:01 +01:00
export class ObtainStatusEffectPhase extends PokemonPhase {
private statusEffect: StatusEffect;
2023-04-16 23:40:32 +01:00
private cureTurn: integer;
private sourceText: string;
2023-04-10 19:12:01 +01:00
constructor(scene: BattleScene, player: boolean, statusEffect: StatusEffect, cureTurn?: integer, sourceText?: string) {
2023-04-10 19:12:01 +01:00
super(scene, player);
2023-04-12 00:08:03 +01:00
this.statusEffect = statusEffect;
2023-04-16 23:40:32 +01:00
this.cureTurn = cureTurn;
this.sourceText = sourceText;
2023-04-10 19:12:01 +01:00
}
start() {
const pokemon = this.getPokemon();
2023-04-12 00:08:03 +01:00
if (!pokemon.status) {
if (pokemon.trySetStatus(this.statusEffect)) {
2023-04-16 23:40:32 +01:00
if (this.cureTurn)
pokemon.status.cureTurn = this.cureTurn;
2023-04-12 00:08:03 +01:00
pokemon.updateInfo(true);
new CommonBattleAnim(CommonAnim.POISON + (this.statusEffect - 1), pokemon).play(this.scene, () => {
this.scene.queueMessage(getPokemonMessage(pokemon, getStatusEffectObtainText(this.statusEffect, this.sourceText)));
2023-04-12 00:08:03 +01:00
if (pokemon.status.isPostTurn())
this.scene.pushPhase(new PostTurnStatusEffectPhase(this.scene, this.player));
this.end();
});
return;
}
} else if (pokemon.status.effect === this.statusEffect)
2023-04-22 00:30:04 +01:00
this.scene.queueMessage(getPokemonMessage(pokemon, getStatusEffectOverlapText(this.statusEffect)));
2023-04-12 00:08:03 +01:00
this.end();
}
}
export class PostTurnStatusEffectPhase extends PokemonPhase {
constructor(scene: BattleScene, player: boolean) {
super(scene, player);
}
start() {
const pokemon = this.getPokemon();
2023-04-19 04:54:07 +01:00
if (pokemon?.hp && pokemon.status && pokemon.status.isPostTurn()) {
2023-04-12 00:08:03 +01:00
pokemon.status.incrementTurn();
new CommonBattleAnim(CommonAnim.POISON + (pokemon.status.effect - 1), pokemon).play(this.scene, () => {
2023-04-22 00:30:04 +01:00
this.scene.queueMessage(getPokemonMessage(pokemon, getStatusEffectActivationText(pokemon.status.effect)));
2023-04-12 00:08:03 +01:00
switch (pokemon.status.effect) {
case StatusEffect.POISON:
case StatusEffect.BURN:
2023-04-16 23:40:32 +01:00
pokemon.damage(Math.max(pokemon.getMaxHp() >> 3, 1));
2023-04-12 00:08:03 +01:00
break;
case StatusEffect.TOXIC:
2023-04-16 23:40:32 +01:00
pokemon.damage(Math.max(Math.floor((pokemon.getMaxHp() / 16) * pokemon.status.turnCount), 1));
2023-04-12 00:08:03 +01:00
break;
}
pokemon.updateInfo().then(() => this.end());
});
2023-04-10 19:12:01 +01:00
} else
this.end();
}
}
export class MessagePhase extends BattlePhase {
private text: string;
2023-04-12 00:08:03 +01:00
private callbackDelay: integer;
2023-04-10 19:12:01 +01:00
private prompt: boolean;
2023-04-12 00:08:03 +01:00
constructor(scene: BattleScene, text: string, callbackDelay?: integer, prompt?: boolean) {
2023-04-10 19:12:01 +01:00
super(scene);
this.text = text;
2023-04-12 00:08:03 +01:00
this.callbackDelay = callbackDelay;
2023-04-10 19:12:01 +01:00
this.prompt = prompt;
}
start() {
super.start();
this.scene.ui.showText(this.text, null, () => this.end(), this.callbackDelay || (this.prompt ? 0 : 1500), this.prompt);
2023-04-10 19:12:01 +01:00
}
2023-04-27 06:14:15 +01:00
end() {
if (this.scene.abilityBar.shown)
this.scene.abilityBar.hide();
super.end();
}
2023-04-10 19:12:01 +01:00
}
2023-04-15 06:32:16 +01:00
export class DamagePhase extends PokemonPhase {
private damageResult: DamageResult;
constructor(scene: BattleScene, player: boolean, damageResult?: DamageResult) {
super(scene, player);
this.damageResult = damageResult || MoveResult.EFFECTIVE;
}
start() {
super.start();
switch (this.damageResult) {
case MoveResult.EFFECTIVE:
this.scene.sound.play('hit');
break;
case MoveResult.SUPER_EFFECTIVE:
this.scene.sound.play('hit_strong');
break;
case MoveResult.NOT_VERY_EFFECTIVE:
this.scene.sound.play('hit_weak');
break;
}
2023-04-18 17:30:47 +01:00
if (this.damageResult !== MoveResult.OTHER) {
const flashTimer = this.scene.time.addEvent({
delay: 100,
repeat: 5,
startAt: 200,
callback: () => {
this.getPokemon().getSprite().setVisible(flashTimer.repeatCount % 2 === 0);
if (!flashTimer.repeatCount)
this.getPokemon().updateInfo().then(() => this.end());
}
});
} else
this.getPokemon().updateInfo().then(() => this.end());
2023-04-15 06:32:16 +01:00
}
}
2023-04-10 19:12:01 +01:00
export class FaintPhase extends PokemonPhase {
constructor(scene: BattleScene, player: boolean) {
super(scene, player);
}
start() {
super.start();
2023-04-22 00:30:04 +01:00
this.scene.queueMessage(getPokemonMessage(this.getPokemon(), ' fainted!'), null, true);
2023-04-15 06:32:16 +01:00
if (this.player) {
this.scene.unshiftPhase(this.scene.getParty().filter(p => p.hp).length ? new SwitchPhase(this.scene, true, false) : new GameOverPhase(this.scene));
} else
2023-04-10 19:12:01 +01:00
this.scene.unshiftPhase(new VictoryPhase(this.scene));
const pokemon = this.getPokemon();
2023-04-14 04:04:51 +01:00
2023-04-22 00:30:04 +01:00
pokemon.lapseTags(BattlerTagLapseType.FAINT);
2023-04-23 03:14:53 +01:00
if (pokemon.isPlayer())
this.scene.getEnemyPokemon()?.removeTagsBySourceId(pokemon.id);
else
this.scene.getPlayerPokemon()?.removeTagsBySourceId(pokemon.id);
2023-04-13 17:16:36 +01:00
2023-04-10 19:12:01 +01:00
pokemon.faintCry(() => {
pokemon.hideInfo();
this.scene.sound.play('faint');
this.scene.tweens.add({
targets: pokemon,
duration: 500,
y: pokemon.y + 150,
ease: 'Sine.easeIn',
onComplete: () => {
pokemon.setVisible(false);
pokemon.y -= 150;
2023-04-16 23:40:32 +01:00
pokemon.trySetStatus(StatusEffect.FAINT);
2023-04-15 06:32:16 +01:00
if (pokemon.isPlayer())
2023-04-10 19:12:01 +01:00
this.scene.currentBattle.removeFaintedParticipant(pokemon as PlayerPokemon);
this.scene.field.remove(pokemon);
this.end();
}
});
});
}
}
export class VictoryPhase extends PokemonPhase {
constructor(scene: BattleScene) {
super(scene, true);
}
start() {
super.start();
const participantIds = this.scene.currentBattle.playerParticipantIds;
const party = this.scene.getParty();
const expShareModifier = this.scene.findModifier(m => m instanceof ExpShareModifier) as ExpShareModifier;
2023-04-20 03:51:46 +01:00
const expBalanceModifier = this.scene.findModifier(m => m instanceof ExpBalanceModifier) as ExpBalanceModifier;
const multipleParticipantExpBonusModifier = this.scene.findModifier(m => m instanceof MultipleParticipantExpBonusModifier) as MultipleParticipantExpBonusModifier;
const expValue = this.scene.getEnemyPokemon().getExpValue();
2023-04-20 03:51:46 +01:00
const expPartyMembers = party.filter(p => p.hp && p.level < 100);
const partyMemberExp = [];
for (let partyMember of expPartyMembers) {
const pId = partyMember.id;
2023-04-10 19:12:01 +01:00
const participated = participantIds.has(pId);
2023-04-21 03:26:38 +01:00
if (participated)
partyMember.winCount++;
else if (!expShareModifier) {
2023-04-20 03:51:46 +01:00
partyMemberExp.push(0);
2023-04-10 19:12:01 +01:00
continue;
2023-04-20 03:51:46 +01:00
}
let expMultiplier = 0;
if (participated)
expMultiplier += (1 / participantIds.size);
if (participantIds.size > 1 && multipleParticipantExpBonusModifier)
expMultiplier += (multipleParticipantExpBonusModifier.getStackCount() * 0.1);
2023-04-20 03:51:46 +01:00
if (expShareModifier)
expMultiplier += expShareModifier.getStackCount() * 0.1;
2023-04-20 03:51:46 +01:00
const pokemonExp = new Utils.NumberHolder(expValue * expMultiplier);
2023-04-21 00:44:56 +01:00
this.scene.applyModifiers(PokemonExpBoosterModifier, true, partyMember, pokemonExp);
2023-04-20 03:51:46 +01:00
partyMemberExp.push(Math.floor(pokemonExp.value));
}
if (expBalanceModifier) {
let totalLevel = 0;
let totalExp = 0;
expPartyMembers.forEach((expPartyMember, epm) => {
totalExp += partyMemberExp[epm];
totalLevel += expPartyMember.level;
});
const medianLevel = Math.floor(totalLevel / expPartyMembers.length);
const recipientExpPartyMemberIndexes = [];
expPartyMembers.forEach((expPartyMember, epm) => {
if (expPartyMember.level <= medianLevel)
recipientExpPartyMemberIndexes.push(epm);
});
const splitExp = Math.floor(totalExp / recipientExpPartyMemberIndexes.length);
expPartyMembers.forEach((_partyMember, pm) => {
partyMemberExp[pm] = recipientExpPartyMemberIndexes.indexOf(pm) > -1 ? splitExp : 0;
});
}
for (let pm = 0; pm < expPartyMembers.length; pm++) {
const exp = partyMemberExp[pm];
if (exp) {
const partyMemberIndex = party.indexOf(expPartyMembers[pm]);
this.scene.unshiftPhase(new ExpPhase(this.scene, partyMemberIndex, exp));
2023-04-10 19:12:01 +01:00
}
}
this.scene.pushPhase(new BattleEndPhase(this.scene));
if (this.scene.currentBattle.waveIndex < this.scene.finalWave) {
this.scene.pushPhase(new SelectModifierPhase(this.scene));
this.scene.newBattle();
} else
this.scene.pushPhase(new GameOverPhase(this.scene, true));
2023-04-10 19:12:01 +01:00
this.end();
}
}
export class GameOverPhase extends BattlePhase {
private victory: boolean;
constructor(scene: BattleScene, victory?: boolean) {
super(scene);
this.victory = !!victory;
}
start() {
super.start();
2023-04-28 20:03:42 +01:00
this.scene.gameData.clearSession();
this.scene.time.delayedCall(1000, () => {
const fadeDuration = this.victory ? 10000 : 5000;
this.scene.fadeOutBgm(fadeDuration, true);
this.scene.ui.fadeOut(fadeDuration).then(() => {
this.scene.clearPhaseQueue();
this.scene.ui.clearText();
this.scene.reset();
this.scene.newBattle();
this.end();
});
});
}
2023-04-29 06:40:24 +01:00
end(): void {
if (!this.scene.gameData.unlocks[Unlockables.MINI_BLACK_HOLE])
this.scene.unshiftPhase(new UnlockPhase(this.scene, Unlockables.MINI_BLACK_HOLE));
super.end();
}
}
export class UnlockPhase extends BattlePhase {
private unlockable: Unlockables;
constructor(scene: BattleScene, unlockable: Unlockables) {
super(scene);
this.unlockable = unlockable;
}
start(): void {
this.scene.time.delayedCall(2000, () => {
this.scene.gameData.unlocks[this.unlockable] = true;
this.scene.gameData.saveSystem();
this.scene.sound.play('level_up_fanfare');
this.scene.ui.setMode(Mode.MESSAGE);
this.scene.arenaBg.setVisible(false);
this.scene.ui.fadeIn(250).then(() => {
this.scene.ui.showText(`${getUnlockableName(this.unlockable)}\nhas been unlocked.`, null, () => {
this.scene.time.delayedCall(1500, () => this.scene.arenaBg.setVisible(true));
this.end();
}, null, true, 1500);
});
});
}
}
2023-04-10 19:12:01 +01:00
export class SwitchPhase extends BattlePhase {
private isModal: boolean;
private doReturn: boolean;
constructor(scene: BattleScene, isModal: boolean, doReturn: boolean) {
super(scene);
this.isModal = isModal;
this.doReturn = doReturn;
}
start() {
super.start();
2023-04-29 00:26:41 +01:00
this.scene.ui.setMode(Mode.PARTY, this.isModal ? PartyUiMode.FAINT_SWITCH : PartyUiMode.POST_BATTLE_SWITCH, (slotIndex: integer, option: PartyOption) => {
2023-04-10 19:12:01 +01:00
if (slotIndex && slotIndex < 6)
2023-04-29 00:26:41 +01:00
this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, slotIndex, this.doReturn, option === PartyOption.PASS_BATON));
2023-04-10 19:12:01 +01:00
this.scene.ui.setMode(Mode.MESSAGE).then(() => super.end());
}, PartyUiHandler.FilterNonFainted);
}
}
export class ExpPhase extends PartyMemberPokemonPhase {
private expValue: number;
constructor(scene: BattleScene, partyMemberIndex: integer, expValue: number) {
super(scene, partyMemberIndex);
this.expValue = expValue;
}
start() {
super.start();
const pokemon = this.getPokemon();
let exp = new Utils.NumberHolder(this.expValue);
2023-04-21 00:44:56 +01:00
this.scene.applyModifiers(ExpBoosterModifier, true, exp);
2023-04-10 19:12:01 +01:00
exp.value = Math.floor(exp.value);
this.scene.ui.showText(`${pokemon.name} gained\n${exp.value} EXP. Points!`, null, () => {
const lastLevel = pokemon.level;
let newLevel: integer;
pokemon.addExp(exp.value);
newLevel = pokemon.level;
if (newLevel > lastLevel)
this.scene.unshiftPhase(new LevelUpPhase(this.scene, this.partyMemberIndex, lastLevel, newLevel));
pokemon.updateInfo().then(() => this.end());
}, null, true);
}
}
export class LevelUpPhase extends PartyMemberPokemonPhase {
private lastLevel: integer;
private level: integer;
constructor(scene: BattleScene, partyMemberIndex: integer, lastLevel: integer, level: integer) {
super(scene, partyMemberIndex);
this.lastLevel = lastLevel;
this.level = level;
}
start() {
super.start();
const pokemon = this.getPokemon();
const prevStats = pokemon.stats.slice(0);
pokemon.calculateStats();
pokemon.updateInfo();
2023-04-18 06:32:26 +01:00
this.scene.playSoundWithoutBgm('level_up_fanfare', 1500);
this.scene.ui.showText(`${this.getPokemon().name} grew to\nLV. ${this.level}!`, null, () => this.scene.ui.getMessageHandler().promptLevelUpStats(this.partyMemberIndex, prevStats, false, () => this.end()), null, true);
if (this.level <= 100) {
const levelMoves = this.getPokemon().getLevelMoves(this.lastLevel + 1);
for (let lm of levelMoves)
this.scene.unshiftPhase(new LearnMovePhase(this.scene, this.partyMemberIndex, lm));
const evolution = pokemon.getEvolution();
if (evolution)
this.scene.unshiftPhase(new EvolutionPhase(this.scene, this.partyMemberIndex, evolution, this.lastLevel));
}
2023-04-10 19:12:01 +01:00
}
}
export class LearnMovePhase extends PartyMemberPokemonPhase {
private moveId: Moves;
constructor(scene: BattleScene, partyMemberIndex: integer, moveId: Moves) {
super(scene, partyMemberIndex);
this.moveId = moveId;
}
start() {
super.start();
const pokemon = this.getPokemon();
2023-04-21 02:32:48 +01:00
const move = allMoves[this.moveId];
2023-04-10 19:12:01 +01:00
const existingMoveIndex = pokemon.moveset.findIndex(m => m?.moveId === move.id);
if (existingMoveIndex > -1) {
this.end();
return;
}
const emptyMoveIndex = pokemon.moveset.length < 4
? pokemon.moveset.length
: pokemon.moveset.findIndex(m => m === null);
const messageMode = this.scene.ui.getHandler() instanceof EvolutionSceneHandler
? Mode.EVOLUTION_SCENE
: Mode.MESSAGE;
if (emptyMoveIndex > -1) {
pokemon.moveset[emptyMoveIndex] = new PokemonMove(this.moveId, 0, 0);
2023-04-12 00:08:03 +01:00
initMoveAnim(this.moveId).then(() => {
2023-04-10 19:12:01 +01:00
loadMoveAnimAssets(this.scene, [ this.moveId ], true)
.then(() => {
this.scene.ui.setMode(messageMode).then(() => {
2023-04-18 06:32:26 +01:00
this.scene.playSoundWithoutBgm('level_up_fanfare', 1500);
2023-04-10 19:12:01 +01:00
this.scene.ui.showText(`${pokemon.name} learned\n${Utils.toPokemonUpperCase(move.name)}!`, null, () => this.end(), messageMode === Mode.EVOLUTION_SCENE ? 1000 : null, true);
});
});
});
} else {
this.scene.ui.setMode(messageMode).then(() => {
this.scene.ui.showText(`${pokemon.name} wants to learn the\nmove ${move.name}.`, null, () => {
this.scene.ui.showText(`However, ${pokemon.name} already\nknows four moves.`, null, () => {
this.scene.ui.showText(`Should a move be deleted and\nreplaced with ${move.name}?`, null, () => {
const noHandler = () => {
this.scene.ui.setMode(messageMode).then(() => {
this.scene.ui.showText(`Stop trying to teach\n${move.name}?`, null, () => {
this.scene.ui.setModeWithoutClear(Mode.CONFIRM, () => {
this.scene.ui.setMode(messageMode);
this.scene.ui.showText(`${pokemon.name} did not learn the\nmove ${move.name}.`, null, () => this.end(), null, true);
}, () => {
this.scene.ui.setMode(messageMode);
this.scene.unshiftPhase(new LearnMovePhase(this.scene, this.partyMemberIndex, this.moveId));
this.end();
});
});
});
};
this.scene.ui.setModeWithoutClear(Mode.CONFIRM, () => {
this.scene.ui.setMode(messageMode);
this.scene.ui.showText('Which move should be forgotten?', null, () => {
this.scene.ui.setModeWithoutClear(Mode.SUMMARY, this.getPokemon(), SummaryUiMode.LEARN_MOVE, move, (moveIndex: integer) => {
if (moveIndex === 4) {
noHandler();
return;
}
this.scene.ui.setMode(messageMode).then(() => {
this.scene.ui.showText('@d{32}1, @d{15}2, and@d{15}… @d{15}… @d{15}… @d{15}@s{pb_bounce_1}Poof!', null, () => {
this.scene.ui.showText(`${pokemon.name} forgot how to\nuse ${pokemon.moveset[moveIndex].getName()}.`, null, () => {
this.scene.ui.showText('And…', null, () => {
pokemon.moveset[moveIndex] = null;
this.scene.unshiftPhase(new LearnMovePhase(this.scene, this.partyMemberIndex, this.moveId));
this.end();
}, null, true);
}, null, true);
}, null, true);
});
});
}, null, true);
}, noHandler);
});
}, null, true);
}, null, true);
});
}
}
}
2023-04-20 20:46:05 +01:00
export class BerryPhase extends CommonAnimPhase {
constructor(scene: BattleScene, player: boolean) {
super(scene, player, CommonAnim.USE_ITEM);
}
start() {
let berryModifier: BerryModifier;
2023-04-21 00:44:56 +01:00
if ((berryModifier = this.scene.applyModifier(BerryModifier, this.player, this.getPokemon()) as BerryModifier)) {
if (berryModifier.consumed) {
if (!--berryModifier.stackCount)
this.scene.removeModifier(berryModifier);
else
berryModifier.consumed = false;
this.scene.updateModifiers(this.player);
2023-04-20 20:46:05 +01:00
}
2023-04-21 00:44:56 +01:00
super.start();
return;
2023-04-20 20:46:05 +01:00
}
this.end();
}
}
2023-04-14 06:08:44 +01:00
export class PokemonHealPhase extends CommonAnimPhase {
private hpHealed: integer;
2023-04-16 05:29:55 +01:00
private message: string;
private showFullHpMessage: boolean;
private skipAnim: boolean;
2023-04-14 06:08:44 +01:00
2023-04-16 05:29:55 +01:00
constructor(scene: BattleScene, player: boolean, hpHealed: integer, message: string, showFullHpMessage: boolean, skipAnim?: boolean) {
2023-04-14 06:08:44 +01:00
super(scene, player, CommonAnim.HEALTH_UP);
this.hpHealed = hpHealed;
2023-04-16 05:29:55 +01:00
this.message = message;
this.showFullHpMessage = showFullHpMessage;
this.skipAnim = !!skipAnim;
}
start() {
2023-04-23 06:03:09 +01:00
if (!this.skipAnim && this.getPokemon().hp && this.getPokemon().getHpRatio() < 1)
2023-04-16 05:29:55 +01:00
super.start();
else
this.end();
2023-04-14 06:08:44 +01:00
}
end() {
const pokemon = this.getPokemon();
2023-04-23 06:03:09 +01:00
if (!this.getPokemon().hp) {
super.end();
return;
}
2023-04-14 06:08:44 +01:00
2023-04-16 05:29:55 +01:00
const fullHp = pokemon.getHpRatio() >= 1;
if (!fullHp) {
2023-04-20 20:46:05 +01:00
const hpRestoreMultiplier = new Utils.IntegerHolder(1);
2023-04-21 00:44:56 +01:00
this.scene.applyModifiers(HealingBoosterModifier, this.player, hpRestoreMultiplier);
2023-04-20 20:46:05 +01:00
pokemon.hp = Math.min(pokemon.hp + this.hpHealed * hpRestoreMultiplier.value, pokemon.getMaxHp());
2023-04-16 05:29:55 +01:00
pokemon.updateInfo().then(() => super.end());
} else if (this.showFullHpMessage)
this.message = getPokemonMessage(pokemon, `'s\nHP is full!`);
if (this.message)
2023-04-22 00:30:04 +01:00
this.scene.queueMessage(this.message);
2023-04-16 05:29:55 +01:00
if (fullHp)
super.end();
2023-04-14 06:08:44 +01:00
}
}
2023-04-10 19:12:01 +01:00
export class AttemptCapturePhase extends BattlePhase {
private pokeballType: PokeballType;
private pokeball: Phaser.GameObjects.Sprite;
private originalY: number;
constructor(scene: BattleScene, pokeballType: PokeballType) {
super(scene);
this.pokeballType = pokeballType;
}
start() {
super.start();
this.scene.pokeballCounts[this.pokeballType]--;
const pokemon = this.scene.getEnemyPokemon();
this.originalY = pokemon.y;
const _3m = 3 * pokemon.getMaxHp();
const _2h = 2 * pokemon.hp;
const catchRate = pokemon.species.catchRate;
const pokeballMultiplier = getPokeballCatchMultiplier(this.pokeballType);
const statusMultiplier = pokemon.status ? getStatusEffectCatchRateMultiplier(pokemon.status.effect) : 1;
2023-04-10 19:12:01 +01:00
const x = Math.round((((_3m - _2h) * catchRate * pokeballMultiplier) / _3m) * statusMultiplier);
const y = Math.round(65536 / Math.sqrt(Math.sqrt(255 / x)));
const pokeballAtlasKey = getPokeballAtlasKey(this.pokeballType);
this.pokeball = this.scene.add.sprite(16, 80, 'pb', pokeballAtlasKey);
this.pokeball.setOrigin(0.5, 0.625);
this.scene.field.add(this.pokeball);
this.scene.sound.play('pb_throw');
this.scene.time.delayedCall(300, () => {
this.scene.field.moveBelow(this.pokeball, pokemon);
});
this.scene.tweens.add({
targets: this.pokeball,
x: { value: 236, ease: 'Linear' },
y: { value: 16, ease: 'Cubic.easeOut' },
duration: 500,
onComplete: () => {
this.pokeball.setTexture('pb', `${pokeballAtlasKey}_opening`);
this.scene.time.delayedCall(17, () => this.pokeball.setTexture('pb', `${pokeballAtlasKey}_open`));
this.scene.sound.play('pb_rel');
pokemon.tint(getPokeballTintColor(this.pokeballType));
this.scene.tweens.add({
targets: pokemon,
duration: 250,
ease: 'Sine.easeIn',
scale: 0.25,
y: 20,
onComplete: () => {
this.pokeball.setTexture('pb', `${pokeballAtlasKey}_opening`);
pokemon.setVisible(false);
this.scene.sound.play('pb_catch');
this.scene.time.delayedCall(17, () => this.pokeball.setTexture('pb', `${pokeballAtlasKey}`));
2023-04-23 15:24:22 +01:00
const doShake = pokeballMultiplier > -1 ? () => {
2023-04-10 19:12:01 +01:00
let shakeCount = 0;
const pbX = this.pokeball.x;
const shakeCounter = this.scene.tweens.addCounter({
from: 0,
to: 1,
repeat: 4,
yoyo: true,
ease: 'Cubic.easeOut',
duration: 250,
repeatDelay: 500,
onUpdate: t => {
if (shakeCount && shakeCount < 4) {
const value = t.getValue();
const directionMultiplier = shakeCount % 2 === 1 ? 1 : -1;
this.pokeball.setX(pbX + value * 4 * directionMultiplier);
this.pokeball.setAngle(value * 27.5 * directionMultiplier);
}
},
onRepeat: () => {
2023-04-26 21:07:29 +01:00
if (!pokemon.species.isObtainable()) {
shakeCounter.stop();
this.failCatch(shakeCount);
} else if (shakeCount++ < 3) {
2023-04-10 19:12:01 +01:00
if (Utils.randInt(65536) < y)
this.scene.sound.play('pb_move');
else {
shakeCounter.stop();
2023-04-11 06:31:18 +01:00
this.failCatch(shakeCount);
2023-04-10 19:12:01 +01:00
}
} else
this.scene.sound.play('pb_lock')
},
2023-04-21 00:44:56 +01:00
onComplete: () => this.catch()
2023-04-10 19:12:01 +01:00
});
} : () => this.catch();
this.scene.time.delayedCall(250, () => doPokeballBounceAnim(this.scene, this.pokeball, 16, 72, 350, doShake));
}
});
}
});
}
2023-04-11 06:31:18 +01:00
failCatch(shakeCount: integer) {
2023-04-10 19:12:01 +01:00
const pokemon = this.scene.getEnemyPokemon();
this.scene.sound.play('pb_rel');
pokemon.setY(this.originalY);
pokemon.cry();
pokemon.tint(getPokeballTintColor(this.pokeballType));
pokemon.setVisible(true);
pokemon.untint(250, 'Sine.easeOut');
const pokeballAtlasKey = getPokeballAtlasKey(this.pokeballType);
this.pokeball.setTexture('pb', `${pokeballAtlasKey}_opening`);
this.scene.time.delayedCall(17, () => this.pokeball.setTexture('pb', `${pokeballAtlasKey}_open`));
this.scene.tweens.add({
targets: pokemon,
duration: 250,
ease: 'Sine.easeOut',
scale: 1
});
this.removePb();
this.end();
}
catch() {
const pokemon = this.scene.getEnemyPokemon();
this.scene.unshiftPhase(new VictoryPhase(this.scene));
this.scene.ui.showText(`${pokemon.name} was caught!`, null, () => {
2023-04-10 21:52:27 +01:00
const end = () => {
2023-04-10 19:12:01 +01:00
this.removePb();
this.end();
2023-04-10 21:52:27 +01:00
};
2023-04-11 06:31:18 +01:00
const addToParty = () => {
const newPokemon = pokemon.addToParty();
2023-04-21 00:44:56 +01:00
const modifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier, false);
Promise.all(modifiers.map(m => this.scene.addModifier(m))).then(() => {
2023-04-23 03:14:53 +01:00
this.scene.getPlayerPokemon().removeTagsBySourceId(pokemon.id);
2023-04-21 00:44:56 +01:00
pokemon.hp = 0;
this.scene.clearEnemyModifiers();
this.scene.field.remove(pokemon, true);
if (newPokemon)
newPokemon.loadAssets().then(end);
else
end();
});
2023-04-11 06:31:18 +01:00
};
Promise.all([ pokemon.hideInfo(), this.scene.gameData.setPokemonCaught(pokemon) ]).then(() => {
2023-04-18 06:32:26 +01:00
if (this.scene.getParty().length === 6) {
const promptRelease = () => {
this.scene.ui.showText(`Your party is full.\nRelease a POKéMON to make room for ${pokemon.name}?`, null, () => {
this.scene.ui.setMode(Mode.CONFIRM, () => {
this.scene.ui.setMode(Mode.PARTY, PartyUiMode.RELEASE, (slotIndex: integer, _option: PartyOption) => {
this.scene.ui.setMode(Mode.MESSAGE).then(() => {
if (slotIndex < 6)
addToParty();
else
promptRelease();
});
2023-04-11 06:31:18 +01:00
});
2023-04-18 06:32:26 +01:00
}, () => {
this.scene.ui.setMode(Mode.MESSAGE);
pokemon.hp = 0;
end();
2023-04-11 06:31:18 +01:00
});
});
2023-04-18 06:32:26 +01:00
};
promptRelease();
} else
addToParty();
});
2023-04-10 19:12:01 +01:00
}, 0, true);
}
removePb() {
this.scene.tweens.add({
targets: this.pokeball,
duration: 250,
delay: 250,
ease: 'Sine.easeIn',
alpha: 0,
onComplete: () => this.pokeball.destroy()
});
}
}
export class SelectModifierPhase extends BattlePhase {
constructor(scene: BattleScene) {
super(scene);
}
start() {
super.start();
const party = this.scene.getParty();
regenerateModifierPoolThresholds(party);
const modifierCount = new Utils.IntegerHolder(3);
2023-04-21 00:44:56 +01:00
this.scene.applyModifiers(ExtraModifierModifier, true, modifierCount);
const typeOptions: Array<ModifierTypeOption> = getPlayerModifierTypeOptionsForWave(this.scene.currentBattle.waveIndex - 1, modifierCount.value, party);
2023-04-10 19:12:01 +01:00
const modifierSelectCallback = (cursor: integer) => {
if (cursor < 0) {
this.scene.ui.setMode(Mode.MESSAGE);
super.end();
return;
2023-04-21 19:05:16 +01:00
} else if (cursor >= typeOptions.length) {
this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.MODIFIER_TRANSFER, (fromSlotIndex: integer, itemIndex: integer, toSlotIndex: integer) => {
if (toSlotIndex !== undefined && fromSlotIndex < 6 && toSlotIndex < 6 && fromSlotIndex !== toSlotIndex && itemIndex > -1) {
this.scene.ui.setMode(Mode.MODIFIER_SELECT).then(() => {
const itemModifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier
&& (m as PokemonHeldItemModifier).pokemonId === party[fromSlotIndex].id) as PokemonHeldItemModifier[];
const itemModifier = itemModifiers[itemIndex];
2023-04-23 15:24:22 +01:00
this.scene.tryTransferHeldItemModifier(itemModifier, party[toSlotIndex], true, true).then(success => {
if (success) {
this.scene.ui.clearText();
this.scene.ui.setMode(Mode.MESSAGE);
2023-04-23 15:24:22 +01:00
super.end();
} else
this.scene.ui.setMode(Mode.MODIFIER_SELECT, typeOptions, modifierSelectCallback);
});
2023-04-21 20:45:48 +01:00
});
2023-04-22 00:30:04 +01:00
} else
this.scene.ui.setMode(Mode.MODIFIER_SELECT, typeOptions, modifierSelectCallback);
2023-04-21 19:05:16 +01:00
}, PartyUiHandler.FilterItemMaxStacks);
return;
2023-04-10 19:12:01 +01:00
}
2023-04-12 17:56:37 +01:00
const modifierType = typeOptions[cursor].type;
2023-04-10 19:12:01 +01:00
if (modifierType instanceof PokemonModifierType) {
const pokemonModifierType = modifierType as PokemonModifierType;
2023-04-11 16:04:39 +01:00
const isMoveModifier = modifierType instanceof PokemonMoveModifierType;
this.scene.ui.setModeWithoutClear(Mode.PARTY, !isMoveModifier ? PartyUiMode.MODIFIER : PartyUiMode.MOVE_MODIFIER, (slotIndex: integer, option: PartyOption) => {
2023-04-10 19:12:01 +01:00
if (slotIndex < 6) {
this.scene.ui.setMode(Mode.MODIFIER_SELECT).then(() => {
const modifierType = typeOptions[cursor].type;
const modifier = !isMoveModifier
? modifierType.newModifier(party[slotIndex])
: modifierType.newModifier(party[slotIndex], option - PartyOption.MOVE_1);
this.scene.ui.clearText();
this.scene.ui.setMode(Mode.MESSAGE);
2023-04-23 15:24:22 +01:00
this.scene.addModifier(modifier, true).then(() => super.end());
});
2023-04-10 19:12:01 +01:00
} else
2023-04-12 17:56:37 +01:00
this.scene.ui.setMode(Mode.MODIFIER_SELECT, typeOptions, modifierSelectCallback);
2023-04-11 16:04:39 +01:00
}, pokemonModifierType.selectFilter, modifierType instanceof PokemonMoveModifierType ? (modifierType as PokemonMoveModifierType).moveSelectFilter : undefined);
2023-04-10 19:12:01 +01:00
} else {
2023-04-21 00:44:56 +01:00
this.scene.addModifier(typeOptions[cursor].type.newModifier(), true).then(() => super.end());
2023-04-10 19:12:01 +01:00
this.scene.ui.clearText();
this.scene.ui.setMode(Mode.MESSAGE);
}
};
2023-04-12 17:56:37 +01:00
this.scene.ui.setMode(Mode.MODIFIER_SELECT, typeOptions, modifierSelectCallback);
2023-04-10 19:12:01 +01:00
}
}
export class ShinySparklePhase extends PokemonPhase {
constructor(scene: BattleScene, player: boolean) {
super(scene, player);
}
start() {
super.start();
this.getPokemon().sparkle();
this.scene.time.delayedCall(1000, () => this.end());
}
}