Implement switch moves

This commit is contained in:
Flashfyre 2023-10-31 14:09:33 -04:00
parent 7d75bffab4
commit ca4f9297a8
5 changed files with 224 additions and 74 deletions

View File

@ -643,6 +643,7 @@ export class SummonPhase extends PartyMemberPokemonPhase {
pokemon.showInfo();
pokemon.playAnim();
pokemon.setVisible(true);
pokemon.getSprite().setVisible(true);
pokemon.setScale(0.5);
pokemon.tint(getPokeballTintColor(pokemon.pokeball));
pokemon.untint(250, 'Sine.easeIn');
@ -738,7 +739,8 @@ export class SwitchSummonPhase extends SummonPhase {
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);
if (batonPassModifier)
this.scene.tryTransferHeldItemModifier(batonPassModifier, switchedPokemon, false, false);
}
}
if (switchedPokemon) {
@ -1537,6 +1539,8 @@ class MoveEffectPhase extends PokemonPhase {
return;
}
const applyAttrs: Promise<void>[] = [];
// Move animation only needs one target
new MoveAnim(this.move.getMove().id as Moves, user, this.getTarget()?.getBattlerIndex()).play(this.scene, () => {
for (let target of targets) {
@ -1556,39 +1560,42 @@ class MoveEffectPhase extends PokemonPhase {
const hitResult = !isProtected ? target.apply(user, this.move) : HitResult.NO_EFFECT;
applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.PRE_APPLY,
user, target, this.move.getMove());
if (hitResult !== HitResult.FAIL) {
const chargeEffect = !!this.move.getMove().getAttrs(ChargeAttr).find(ca => (ca as ChargeAttr).chargeEffect);
// Charge attribute with charge effect takes all effect attributes and applies them to charge stage, so ignore them if this is present
if (!chargeEffect)
applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.POST_APPLY
&& (attr as MoveEffectAttr).selfTarget, user, target, this.move.getMove());
if (hitResult !== HitResult.NO_EFFECT) {
applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.POST_APPLY
&& !(attr as MoveEffectAttr).selfTarget, user, target, this.move.getMove());
if (hitResult < HitResult.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);
applyAttrs.push(new Promise(resolve => {
applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.PRE_APPLY,
user, target, this.move.getMove()).then(() => {
if (hitResult !== HitResult.FAIL) {
const chargeEffect = !!this.move.getMove().getAttrs(ChargeAttr).find(ca => (ca as ChargeAttr).chargeEffect);
// Charge attribute with charge effect takes all effect attributes and applies them to charge stage, so ignore them if this is present
Utils.executeIf(!chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.POST_APPLY
&& (attr as MoveEffectAttr).selfTarget, user, target, this.move.getMove())).then(() => {
if (hitResult !== HitResult.NO_EFFECT) {
applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.POST_APPLY
&& !(attr as MoveEffectAttr).selfTarget, user, target, this.move.getMove()).then(() => {
if (hitResult < HitResult.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);
}
Utils.executeIf(!isProtected && !chargeEffect, () => applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.HIT,
user, target, this.move.getMove()).then(() => {
if (!target.isFainted()) {
applyPostDefendAbAttrs(PostDefendAbAttr, target, user, this.move, hitResult);
if (!user.isPlayer() && this.move instanceof AttackMove)
user.scene.applyModifiers(EnemyAttackStatusEffectChanceModifier, false, target);
}
if (this.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT))
this.scene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target.getFieldIndex());
})
).then(() => resolve());
});
}
});
}
if (!isProtected && !chargeEffect) {
applyFilteredMoveAttrs((attr: MoveAttr) => attr instanceof MoveEffectAttr && (attr as MoveEffectAttr).trigger === MoveEffectTrigger.HIT,
user, target, this.move.getMove());
if (!target.isFainted()) {
applyPostDefendAbAttrs(PostDefendAbAttr, target, user, this.move, hitResult);
if (!user.isPlayer() && this.move instanceof AttackMove)
user.scene.applyModifiers(EnemyAttackStatusEffectChanceModifier, false, target);
}
if (this.move.getMove().hasFlag(MoveFlags.MAKES_CONTACT))
this.scene.applyModifiers(ContactHeldItemTransferChanceModifier, this.player, user, target.getFieldIndex());
}
}
}
});
}));
}
this.end();
Promise.allSettled(applyAttrs).then(() => this.end());
});
});
}
@ -2801,14 +2808,17 @@ export class AttemptRunPhase extends PokemonPhase {
targets: [ this.scene.arenaEnemy, enemyField ].flat(),
alpha: 0,
duration: 250,
ease: 'Sine.easeIn'
ease: 'Sine.easeIn',
onComplete: () => enemyField.forEach(enemyPokemon => enemyPokemon.destroy())
});
enemyField.forEach(enemyPokemon => {
enemyPokemon.hideInfo();
enemyPokemon.hideInfo().then(() => enemyPokemon.destroy());
enemyPokemon.hp = 0;
});
this.scene.clearEnemyHeldItemModifiers();
this.scene.pushPhase(new BattleEndPhase(this.scene));
this.scene.pushPhase(new NewBattlePhase(this.scene));
} else

View File

@ -1,6 +1,6 @@
//import { battleAnimRawData } from "./battle-anim-raw-data";
import BattleScene from "../battle-scene";
import { ChargeAttr, Moves, allMoves } from "./move";
import { ChargeAttr, MoveFlags, Moves, allMoves } from "./move";
import Pokemon from "../pokemon";
import * as Utils from "../utils";
import { BattlerIndex } from "../battle";
@ -560,6 +560,14 @@ export abstract class BattleAnim {
abstract isOppAnim(): boolean;
protected isHideUser(): boolean {
return false;
}
protected isHideTarget(): boolean {
return false;
}
private getGraphicFrameData(scene: BattleScene, frames: AnimFrame[]): Map<integer, Map<AnimFrameTarget, GraphicFrameData>> {
const ret: Map<integer, Map<AnimFrameTarget, GraphicFrameData>> = new Map([
[AnimFrameTarget.GRAPHIC, new Map<AnimFrameTarget, GraphicFrameData>() ],
@ -795,8 +803,10 @@ export abstract class BattleAnim {
targetSprite.setAlpha(1);
targetSprite.pipelineData['tone'] = [ 0.0, 0.0, 0.0, 0.0 ];
targetSprite.setAngle(0);
userSprite.setVisible(true);
targetSprite.setVisible(true);
if (!this.isHideUser())
userSprite.setVisible(true);
if (!this.isHideTarget() && (targetSprite !== userSprite || !this.isHideUser()))
targetSprite.setVisible(true);
for (let ms of Object.values(spriteCache).flat()) {
if (ms)
ms.destroy();
@ -858,6 +868,14 @@ export class MoveAnim extends BattleAnim {
isOppAnim(): boolean {
return !this.user.isPlayer() && Array.isArray(moveAnims.get(this.move));
}
protected isHideUser(): boolean {
return allMoves[this.move].hasFlag(MoveFlags.HIDE_USER);
}
protected isHideTarget(): boolean {
return allMoves[this.move].hasFlag(MoveFlags.HIDE_TARGET);
}
}
export class MoveChargeAnim extends MoveAnim {

View File

@ -1,5 +1,5 @@
import { ChargeAnim, MoveChargeAnim, initMoveAnim, loadMoveAnimAssets } from "./battle-anims";
import { DamagePhase, MovePhase, ObtainStatusEffectPhase, PokemonHealPhase, StatChangePhase } from "../battle-phases";
import { BattleEndPhase, DamagePhase, MovePhase, NewBattlePhase, ObtainStatusEffectPhase, PokemonHealPhase, StatChangePhase, SwitchSummonPhase } from "../battle-phases";
import { BattleStat } from "./battle-stat";
import { BattlerTagType } from "./battler-tag";
import { getPokemonMessage } from "../messages";
@ -45,7 +45,10 @@ export enum MoveTarget {
export enum MoveFlags {
MAKES_CONTACT = 1,
IGNORE_PROTECT = 2,
IGNORE_VIRTUAL = 4
IGNORE_VIRTUAL = 4,
SOUND_BASED = 8,
HIDE_USER = 16,
HIDE_TARGET = 32
}
type MoveCondition = (user: Pokemon, target: Pokemon, move: Move) => boolean;
@ -154,6 +157,21 @@ export default class Move {
return this;
}
soundBased(soundBased?: boolean): Move {
this.setFlag(MoveFlags.SOUND_BASED, soundBased);
return this;
}
hidesUser(hidesUser?: boolean): Move {
this.setFlag(MoveFlags.HIDE_USER, hidesUser);
return this;
}
hidesTarget(hidesTarget?: boolean): Move {
this.setFlag(MoveFlags.HIDE_TARGET, hidesTarget);
return this;
}
applyConditions(user: Pokemon, target: Pokemon, move: Move): boolean {
for (let condition of this.conditions) {
if (!condition(user, target, move))
@ -1975,6 +1993,56 @@ export class AddArenaTrapTagAttr extends AddArenaTagAttr {
}
}
export class ForceSwitchOutAttr extends MoveEffectAttr {
private user: boolean;
private batonPass: boolean;
constructor(user?: boolean, batonPass?: boolean) {
super(false, MoveEffectTrigger.HIT);
this.user = !!user;
this.batonPass = !!batonPass;
}
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
return new Promise(resolve => {
const switchOutTarget = this.user ? user : target;
if (switchOutTarget instanceof PlayerPokemon) {
(switchOutTarget as PlayerPokemon).switchOut(this.batonPass).then(() => resolve(true));
return;
} else if (user.scene.currentBattle.battleType) {
if ((switchOutTarget instanceof PlayerPokemon ? user.scene.getParty() : user.scene.getEnemyParty()).filter(p => !p.isFainted()).length > 1) {
switchOutTarget.resetTurnData();
switchOutTarget.resetSummonData();
switchOutTarget.hideInfo();
switchOutTarget.setVisible(false);
user.scene.unshiftPhase(new SwitchSummonPhase(user.scene, switchOutTarget.getFieldIndex(), user.scene.currentBattle.trainer.getNextSummonIndex(), false, this.batonPass, false));
} else
return resolve(false);
} else {
switchOutTarget.hideInfo().then(() => switchOutTarget.destroy());
switchOutTarget.hp = 0;
user.scene.queueMessage(getPokemonMessage(switchOutTarget, ' fled!'), null, true, 500);
if (!switchOutTarget.getAlly()?.isActive(true)) {
user.scene.clearEnemyHeldItemModifiers();
user.scene.pushPhase(new BattleEndPhase(user.scene));
user.scene.pushPhase(new NewBattlePhase(user.scene));
}
}
resolve(true);
});
}
getCondition(): MoveCondition {
return (user: Pokemon, target: Pokemon, move: Move) => !user.scene.currentBattle.battleType || ((this.user ? user : target) instanceof PlayerPokemon ? user.scene.getParty() : user.scene.getEnemyParty()).filter(p => !p.isFainted()).length === 1;
}
}
export class CopyTypeAttr extends MoveEffectAttr {
constructor() {
super(true);
@ -2256,53 +2324,53 @@ export function getMoveTargets(user: Pokemon, move: Moves): MoveTargetSet {
const moveTarget = move ? allMoves[move].moveTarget : move === undefined ? MoveTarget.NEAR_ENEMY : [];
const opponents = user.getOpponents();
let set: BattlerIndex[] = [];
let set: Pokemon[] = [];
let multiple = false;
switch (moveTarget) {
case MoveTarget.USER:
set = [ user.getBattlerIndex() ];
set = [ user];
break;
case MoveTarget.NEAR_OTHER:
case MoveTarget.OTHER:
case MoveTarget.ALL_NEAR_OTHERS:
case MoveTarget.ALL_OTHERS:
set = (opponents.concat([ user.getAlly() ])).map(p => p?.getBattlerIndex());
set = (opponents.concat([ user.getAlly() ]));
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.map(p => p.getBattlerIndex());
set = opponents;
multiple = moveTarget !== MoveTarget.NEAR_ENEMY;
break;
case MoveTarget.RANDOM_NEAR_ENEMY:
set = [ opponents[Utils.randInt(opponents.length)].getBattlerIndex() ];
set = [ opponents[Utils.randInt(opponents.length)] ];
break;
case MoveTarget.ATTACKER:
set = user.turnData.attacksReceived.length
? [ user.scene.getPokemonById(user.turnData.attacksReceived[0].sourceId).getBattlerIndex() ]
? [ user.scene.getPokemonById(user.turnData.attacksReceived[0].sourceId) ]
: [];
break;
case MoveTarget.NEAR_ALLY:
case MoveTarget.ALLY:
set = [ user.getAlly()?.getBattlerIndex() ];
set = [ user.getAlly() ];
break;
case MoveTarget.USER_OR_NEAR_ALLY:
case MoveTarget.USER_AND_ALLIES:
case MoveTarget.USER_SIDE:
set = [ user, user.getAlly() ].map(p => p?.getBattlerIndex());
set = [ user, user.getAlly() ];
multiple = moveTarget !== MoveTarget.USER_OR_NEAR_ALLY;
break;
case MoveTarget.ALL:
case MoveTarget.BOTH_SIDES:
set = [ user, user.getAlly() ].concat(user.getOpponents()).map(p => p?.getBattlerIndex());
set = [ user, user.getAlly() ].concat(user.getOpponents());
multiple = true;
break;
}
return { targets: set.filter(t => t !== undefined), multiple };
return { targets: set.filter(p => p?.isActive(true)).map(p => p.getBattlerIndex()).filter(t => t !== undefined), multiple };
}
export const allMoves: Move[] = [
@ -2344,7 +2412,9 @@ export function initMoves() {
.target(MoveTarget.OTHER),
new AttackMove(Moves.WING_ATTACK, "Wing Attack", Type.FLYING, MoveCategory.PHYSICAL, 60, 100, 35, -1, "", -1, 0, 1)
.target(MoveTarget.OTHER),
new StatusMove(Moves.WHIRLWIND, "Whirlwind (N)", Type.NORMAL, -1, 20, -1, "In battles, the opponent switches. In the wild, the Pokémon runs.", -1, -6, 1), // TODO
new StatusMove(Moves.WHIRLWIND, "Whirlwind", Type.NORMAL, -1, 20, -1, "In battles, the opponent switches. In the wild, the Pokémon runs.", -1, -6, 1)
.attr(ForceSwitchOutAttr)
.hidesTarget(),
new AttackMove(Moves.FLY, "Fly", Type.FLYING, MoveCategory.PHYSICAL, 90, 95, 15, 97, "Flies up on first turn, attacks on second turn.", -1, 0, 1)
.attr(ChargeAttr, ChargeAnim.FLY_CHARGING, 'flew\nup high!', BattlerTagType.FLYING)
.condition(failOnGravityCondition)
@ -2406,12 +2476,18 @@ export function initMoves() {
.attr(FlinchAttr),
new StatusMove(Moves.GROWL, "Growl", Type.NORMAL, 100, 40, -1, "Lowers opponent's Attack.", -1, 0, 1)
.attr(StatChangeAttr, BattleStat.ATK, -1)
.target(MoveTarget.ALL_NEAR_ENEMIES),
new StatusMove(Moves.ROAR, "Roar (N)", Type.NORMAL, -1, 20, -1, "In battles, the opponent switches. In the wild, the Pokémon runs.", -1, -6, 1),
.target(MoveTarget.ALL_NEAR_ENEMIES)
.soundBased(),
new StatusMove(Moves.ROAR, "Roar", Type.NORMAL, -1, 20, -1, "In battles, the opponent switches. In the wild, the Pokémon runs.", -1, -6, 1)
.attr(ForceSwitchOutAttr)
.soundBased()
.hidesTarget(),
new StatusMove(Moves.SING, "Sing", Type.NORMAL, 55, 15, -1, "Puts opponent to sleep.", -1, 0, 1)
.attr(StatusEffectAttr, StatusEffect.SLEEP),
.attr(StatusEffectAttr, StatusEffect.SLEEP)
.soundBased(),
new StatusMove(Moves.SUPERSONIC, "Supersonic", Type.NORMAL, 55, 20, -1, "Confuses opponent.", -1, 0, 1)
.attr(ConfuseAttr),
.attr(ConfuseAttr)
.soundBased(),
new AttackMove(Moves.SONIC_BOOM, "Sonic Boom", Type.NORMAL, MoveCategory.SPECIAL, -1, 90, 20, -1, "Always inflicts 20 HP.", -1, 0, 1)
.attr(FixedDamageAttr, 20),
new StatusMove(Moves.DISABLE, "Disable", Type.NORMAL, 100, 20, -1, "Opponent can't use its last attack for a few turns.", -1, 0, 1)
@ -2528,14 +2604,17 @@ export function initMoves() {
.attr(StatChangeAttr, BattleStat.SPD, 2, true),
new AttackMove(Moves.QUICK_ATTACK, "Quick Attack", Type.NORMAL, MoveCategory.PHYSICAL, 40, 100, 30, -1, "User attacks first.", -1, 1, 1),
new AttackMove(Moves.RAGE, "Rage (N)", Type.NORMAL, MoveCategory.PHYSICAL, 20, 100, 20, -1, "Raises user's Attack when hit.", -1, 0, 1), // TODO
new SelfStatusMove(Moves.TELEPORT, "Teleport (N)", Type.PSYCHIC, -1, 20, -1, "Allows user to flee wild battles.", -1, 0, 1),
new SelfStatusMove(Moves.TELEPORT, "Teleport", Type.PSYCHIC, -1, 20, -1, "Allows user to flee wild battles.", -1, -6, 1)
.attr(ForceSwitchOutAttr, true)
.hidesUser(),
new AttackMove(Moves.NIGHT_SHADE, "Night Shade", Type.GHOST, MoveCategory.SPECIAL, -1, 100, 15, 42, "Inflicts damage equal to user's level.", -1, 0, 1)
.attr(LevelDamageAttr),
new StatusMove(Moves.MIMIC, "Mimic", Type.NORMAL, -1, 10, -1, "Copies the opponent's last move.", -1, 0, 1)
.attr(MovesetCopyMoveAttr)
.ignoresVirtual(),
new StatusMove(Moves.SCREECH, "Screech", Type.NORMAL, 85, 40, -1, "Sharply lowers opponent's Defense.", -1, 0, 1)
.attr(StatChangeAttr, BattleStat.DEF, -2),
.attr(StatChangeAttr, BattleStat.DEF, -2)
.soundBased(),
new SelfStatusMove(Moves.DOUBLE_TEAM, "Double Team", Type.NORMAL, -1, 15, -1, "Raises user's Evasiveness.", -1, 0, 1)
.attr(StatChangeAttr, BattleStat.EVA, 1, true),
new SelfStatusMove(Moves.RECOVER, "Recover", Type.NORMAL, -1, 5, -1, "User recovers half its max HP.", -1, 0, 1)
@ -2714,7 +2793,8 @@ export function initMoves() {
new AttackMove(Moves.SNORE, "Snore", Type.NORMAL, MoveCategory.SPECIAL, 50, 100, 15, -1, "Can only be used if asleep. May cause flinching.", 30, 0, 2)
.attr(BypassSleepAttr)
.attr(FlinchAttr)
.condition((user: Pokemon, target: Pokemon, move: Move) => user.status?.effect === StatusEffect.SLEEP),
.condition((user: Pokemon, target: Pokemon, move: Move) => user.status?.effect === StatusEffect.SLEEP)
.soundBased(),
new StatusMove(Moves.CURSE, "Curse (N)", Type.GHOST, -1, 10, -1, "Ghosts lose 50% of max HP and curse the opponent; Non-Ghosts raise Attack, Defense and lower Speed.", -1, 0, 2)
.target(MoveTarget.USER),
new AttackMove(Moves.FLAIL, "Flail", Type.NORMAL, MoveCategory.PHYSICAL, -1, 100, 15, -1, "The lower the user's HP, the higher the power.", -1, 0, 2)
@ -2757,7 +2837,8 @@ export function initMoves() {
.ignoresProtect(),
new StatusMove(Moves.PERISH_SONG, "Perish Song (N)", Type.NORMAL, -1, 5, -1, "Any Pokémon in play when this attack is used faints in 3 turns.", -1, 0, 2)
.ignoresProtect()
.target(MoveTarget.ALL),
.target(MoveTarget.ALL)
.soundBased(),
new AttackMove(Moves.ICY_WIND, "Icy Wind", Type.ICE, MoveCategory.SPECIAL, 55, 95, 15, 34, "Lowers opponent's Speed.", 100, 0, 2)
.attr(StatChangeAttr, BattleStat.SPD, -1)
.target(MoveTarget.ALL_NEAR_ENEMIES),
@ -2805,7 +2886,8 @@ export function initMoves() {
.attr(RandomMovesetMoveAttr)
.condition((user: Pokemon, target: Pokemon, move: Move) => user.status?.effect === StatusEffect.SLEEP),
new SelfStatusMove(Moves.HEAL_BELL, "Heal Bell (N)", Type.NORMAL, -1, 5, -1, "Heals the user's party's status conditions.", -1, 0, 2)
.target(MoveTarget.USER_AND_ALLIES),
.target(MoveTarget.USER_AND_ALLIES)
.soundBased(),
new AttackMove(Moves.RETURN, "Return", Type.NORMAL, MoveCategory.PHYSICAL, -1, 100, 20, -1, "Power increases with higher Friendship.", -1, 0, 2)
.attr(WinCountPowerMoveAttr),
new AttackMove(Moves.PRESENT, "Present (N)", Type.NORMAL, MoveCategory.PHYSICAL, -1, 90, 15, -1, "Either deals damage or heals.", -1, 0, 2)
@ -2827,7 +2909,9 @@ export function initMoves() {
new AttackMove(Moves.MEGAHORN, "Megahorn", Type.BUG, MoveCategory.PHYSICAL, 120, 85, 10, -1, "", -1, 0, 2),
new AttackMove(Moves.DRAGON_BREATH, "Dragon Breath", Type.DRAGON, MoveCategory.SPECIAL, 60, 100, 20, -1, "May paralyze opponent.", 30, 0, 2)
.attr(StatusEffectAttr, StatusEffect.PARALYSIS),
new SelfStatusMove(Moves.BATON_PASS, "Baton Pass (N)", Type.NORMAL, -1, 40, 132, "User switches out and gives stat changes to the incoming Pokémon.", -1, 0, 2),
new SelfStatusMove(Moves.BATON_PASS, "Baton Pass", Type.NORMAL, -1, 40, 132, "User switches out and gives stat changes to the incoming Pokémon.", -1, 0, 2)
.attr(ForceSwitchOutAttr, true, true)
.hidesUser(),
new StatusMove(Moves.ENCORE, "Encore (N)", Type.NORMAL, 100, 5, 122, "Forces opponent to keep using its last move for 3 turns.", -1, 0, 2),
new AttackMove(Moves.PURSUIT, "Pursuit (N)", Type.DARK, MoveCategory.PHYSICAL, 40, 100, 20, -1, "Double power if the opponent is switching out.", -1, 0, 2),
new AttackMove(Moves.RAPID_SPIN, "Rapid Spin", Type.NORMAL, MoveCategory.PHYSICAL, 50, 100, 40, -1, "Raises user's Speed and removes entry hazards and trap move effects.", 100, 0, 2)
@ -2883,7 +2967,8 @@ export function initMoves() {
.condition((user: Pokemon, target: Pokemon, move: Move) => !user.getMoveHistory().length),
new AttackMove(Moves.UPROAR, "Uproar (N)", Type.NORMAL, MoveCategory.SPECIAL, 90, 100, 10, -1, "User attacks for 3 turns and prevents sleep.", -1, 0, 3)
.ignoresVirtual()
.target(MoveTarget.RANDOM_NEAR_ENEMY),
.target(MoveTarget.RANDOM_NEAR_ENEMY)
.soundBased(),
new SelfStatusMove(Moves.STOCKPILE, "Stockpile (N)", Type.NORMAL, -1, 20, -1, "Stores energy for use with Spit Up and Swallow.", -1, 0, 3),
new AttackMove(Moves.SPIT_UP, "Spit Up (N)", Type.NORMAL, MoveCategory.SPECIAL, -1, 100, 10, -1, "Power depends on how many times the user performed Stockpile.", -1, 0, 3),
new SelfStatusMove(Moves.SWALLOW, "Swallow (N)", Type.NORMAL, -1, 10, -1, "The more times the user has performed Stockpile, the more HP is recovered.", -1, 0, 3),
@ -2981,7 +3066,8 @@ export function initMoves() {
new SelfStatusMove(Moves.SLACK_OFF, "Slack Off", Type.NORMAL, -1, 5, -1, "User recovers half its max HP.", -1, 0, 3)
.attr(HealAttr),
new AttackMove(Moves.HYPER_VOICE, "Hyper Voice", Type.NORMAL, MoveCategory.SPECIAL, 90, 100, 10, 117, "", -1, 0, 3)
.target(MoveTarget.ALL_NEAR_ENEMIES),
.target(MoveTarget.ALL_NEAR_ENEMIES)
.soundBased(),
new AttackMove(Moves.POISON_FANG, "Poison Fang", Type.POISON, MoveCategory.PHYSICAL, 50, 100, 15, -1, "May badly poison opponent.", 50, 0, 3)
.attr(StatusEffectAttr, StatusEffect.TOXIC),
new AttackMove(Moves.CRUSH_CLAW, "Crush Claw", Type.NORMAL, MoveCategory.PHYSICAL, 75, 95, 10, -1, "May lower opponent's Defense.", 50, 0, 3)
@ -3011,9 +3097,11 @@ export function initMoves() {
new AttackMove(Moves.SILVER_WIND, "Silver Wind", Type.BUG, MoveCategory.SPECIAL, 60, 100, 5, -1, "May raise all stats of user at once.", 10, 0, 3)
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.SPD ], 1, true),
new StatusMove(Moves.METAL_SOUND, "Metal Sound", Type.STEEL, 85, 40, -1, "Sharply lowers opponent's Special Defense.", -1, 0, 3)
.attr(StatChangeAttr, BattleStat.SPDEF, -2),
.attr(StatChangeAttr, BattleStat.SPDEF, -2)
.soundBased(),
new StatusMove(Moves.GRASS_WHISTLE, "Grass Whistle", Type.GRASS, 55, 15, -1, "Puts opponent to sleep.", -1, 0, 3)
.attr(StatusEffectAttr, StatusEffect.SLEEP),
.attr(StatusEffectAttr, StatusEffect.SLEEP)
.soundBased(),
new StatusMove(Moves.TICKLE, "Tickle", Type.NORMAL, 100, 20, -1, "Lowers opponent's Attack and Defense.", -1, 0, 3)
.attr(StatChangeAttr, BattleStat.ATK, -1)
.attr(StatChangeAttr, BattleStat.DEF, -1),
@ -3051,7 +3139,8 @@ export function initMoves() {
.attr(AddBattlerTagAttr, BattlerTagType.TRAPPED, false, 1, true),
new SelfStatusMove(Moves.HOWL, "Howl", Type.NORMAL, -1, 40, -1, "Raises Attack of allies.", -1, 0, 3)
.attr(StatChangeAttr, BattleStat.ATK, 1, true)
.target(MoveTarget.USER_AND_ALLIES), // TODO
.target(MoveTarget.USER_AND_ALLIES)
.soundBased(), // TODO
new AttackMove(Moves.DRAGON_CLAW, "Dragon Claw", Type.DRAGON, MoveCategory.PHYSICAL, 80, 100, 15, 78, "", -1, 0, 3),
new AttackMove(Moves.FRENZY_PLANT, "Frenzy Plant", Type.GRASS, MoveCategory.SPECIAL, 150, 90, 5, 155, "User must recharge next turn.", -1, 0, 3)
.attr(AddBattlerTagAttr, BattlerTagType.RECHARGING, true),
@ -3191,7 +3280,8 @@ export function initMoves() {
.target(MoveTarget.OTHER),
new AttackMove(Moves.X_SCISSOR, "X-Scissor", Type.BUG, MoveCategory.PHYSICAL, 80, 100, 15, 105, "", -1, 0, 4),
new AttackMove(Moves.BUG_BUZZ, "Bug Buzz", Type.BUG, MoveCategory.SPECIAL, 90, 100, 10, 162, "May lower opponent's Special Defense.", 10, 0, 4)
.attr(StatChangeAttr, BattleStat.SPDEF, -1),
.attr(StatChangeAttr, BattleStat.SPDEF, -1)
.soundBased(),
new AttackMove(Moves.DRAGON_PULSE, "Dragon Pulse", Type.DRAGON, MoveCategory.SPECIAL, 85, 100, 10, 115, "", -1, 0, 4)
.target(MoveTarget.OTHER),
new AttackMove(Moves.DRAGON_RUSH, "Dragon Rush", Type.DRAGON, MoveCategory.PHYSICAL, 100, 75, 10, -1, "May cause flinching.", 20, 0, 4)
@ -3289,7 +3379,8 @@ export function initMoves() {
.makesContact(),
new AttackMove(Moves.CHATTER, "Chatter", Type.FLYING, MoveCategory.SPECIAL, 65, 100, 20, -1, "Confuses opponent.", 100, 0, 4)
.attr(ConfuseAttr)
.target(MoveTarget.OTHER),
.target(MoveTarget.OTHER)
.soundBased(),
new AttackMove(Moves.JUDGMENT, "Judgment (N)", Type.NORMAL, MoveCategory.SPECIAL, 100, 100, 10, -1, "Type depends on the Arceus Plate being held.", -1, 0, 4),
new AttackMove(Moves.BUG_BITE, "Bug Bite (N)", Type.BUG, MoveCategory.PHYSICAL, 60, 100, 20, -1, "Receives the effect from the opponent's held berry.", -1, 0, 4),
new AttackMove(Moves.CHARGE_BEAM, "Charge Beam", Type.ELECTRIC, MoveCategory.SPECIAL, 50, 90, 10, 23, "May raise user's Special Attack.", 70, 0, 4)
@ -3377,9 +3468,11 @@ export function initMoves() {
new StatusMove(Moves.ENTRAINMENT, "Entrainment (N)", Type.NORMAL, 100, 15, -1, "Makes target's ability same as user's.", -1, 0, 5),
new StatusMove(Moves.AFTER_YOU, "After You (N)", Type.NORMAL, -1, 15, -1, "Gives target priority in the next turn.", -1, 0, 5)
.ignoresProtect(),
new AttackMove(Moves.ROUND, "Round", Type.NORMAL, MoveCategory.SPECIAL, 60, 100, 15, -1, "Power increases if teammates use it in the same turn.", -1, 0, 5), // TODO
new AttackMove(Moves.ROUND, "Round", Type.NORMAL, MoveCategory.SPECIAL, 60, 100, 15, -1, "Power increases if teammates use it in the same turn.", -1, 0, 5)
.soundBased(), // TODO
new AttackMove(Moves.ECHOED_VOICE, "Echoed Voice", Type.NORMAL, MoveCategory.SPECIAL, 40, 100, 15, -1, "Power increases each turn.", -1, 0, 5)
.attr(ConsecutiveUseMultiBasePowerAttr, 5, false),
.attr(ConsecutiveUseMultiBasePowerAttr, 5, false)
.soundBased(),
new AttackMove(Moves.CHIP_AWAY, "Chip Away (N)", Type.NORMAL, MoveCategory.PHYSICAL, 70, 100, 20, -1, "Ignores opponent's stat changes.", -1, 0, 5),
new AttackMove(Moves.CLEAR_SMOG, "Clear Smog (N)", Type.POISON, MoveCategory.SPECIAL, 50, -1, 15, -1, "Removes all of the target's stat changes.", -1, 0, 5),
new AttackMove(Moves.STORED_POWER, "Stored Power (N)", Type.PSYCHIC, MoveCategory.SPECIAL, 20, 100, 10, 41, "Power increases when user's stats have been raised.", -1, 0, 5),
@ -3405,7 +3498,8 @@ export function initMoves() {
new SelfStatusMove(Moves.SHIFT_GEAR, "Shift Gear", Type.STEEL, -1, 10, -1, "Raises user's Attack and sharply raises Speed.", -1, 0, 5)
.attr(StatChangeAttr, BattleStat.ATK, 1, true)
.attr(StatChangeAttr, BattleStat.SPD, 2, true),
new AttackMove(Moves.CIRCLE_THROW, "Circle Throw (N)", Type.FIGHTING, MoveCategory.PHYSICAL, 60, 90, 10, -1, "In battles, the opponent switches. In the wild, the Pokémon runs.", -1, -6, 5),
new AttackMove(Moves.CIRCLE_THROW, "Circle Throw", Type.FIGHTING, MoveCategory.PHYSICAL, 60, 90, 10, -1, "In battles, the opponent switches. In the wild, the Pokémon runs.", -1, -6, 5)
.attr(ForceSwitchOutAttr),
new AttackMove(Moves.INCINERATE, "Incinerate (N)", Type.FIRE, MoveCategory.SPECIAL, 60, 100, 15, -1, "Destroys the target's held berry.", -1, 0, 5)
.target(MoveTarget.ALL_NEAR_ENEMIES),
new StatusMove(Moves.QUASH, "Quash (N)", Type.DARK, 100, 15, -1, "Makes the target act last this turn.", -1, 0, 5),
@ -3434,7 +3528,8 @@ export function initMoves() {
.target(MoveTarget.ALL_NEAR_OTHERS),
new AttackMove(Moves.FROST_BREATH, "Frost Breath", Type.ICE, MoveCategory.SPECIAL, 60, 90, 10, -1, "Always results in a critical hit.", 100, 0, 5)
.attr(CritOnlyAttr),
new AttackMove(Moves.DRAGON_TAIL, "Dragon Tail (N)", Type.DRAGON, MoveCategory.PHYSICAL, 60, 90, 10, 44, "In battles, the opponent switches. In the wild, the Pokémon runs.", -1, -6, 5),
new AttackMove(Moves.DRAGON_TAIL, "Dragon Tail", Type.DRAGON, MoveCategory.PHYSICAL, 60, 90, 10, 44, "In battles, the opponent switches. In the wild, the Pokémon runs.", -1, -6, 5)
.attr(ForceSwitchOutAttr),
new SelfStatusMove(Moves.WORK_UP, "Work Up", Type.NORMAL, -1, 30, -1, "Raises user's Attack and Special Attack.", -1, 0, 5)
.attr(StatChangeAttr, [ BattleStat.ATK, BattleStat.SPATK ], 1, true),
new AttackMove(Moves.ELECTROWEB, "Electroweb", Type.ELECTRIC, MoveCategory.SPECIAL, 55, 95, 15, -1, "Lowers opponent's Speed.", 100, 0, 5)
@ -3479,7 +3574,8 @@ export function initMoves() {
new AttackMove(Moves.TECHNO_BLAST, "Techno Blast (N)", Type.NORMAL, MoveCategory.SPECIAL, 120, 100, 5, -1, "Type depends on the Drive being held.", -1, 0, 5),
new AttackMove(Moves.RELIC_SONG, "Relic Song", Type.NORMAL, MoveCategory.SPECIAL, 75, 100, 10, -1, "May put the target to sleep.", 10, 0, 5)
.attr(StatusEffectAttr, StatusEffect.SLEEP)
.target(MoveTarget.ALL_NEAR_ENEMIES),
.target(MoveTarget.ALL_NEAR_ENEMIES)
.soundBased(),
new AttackMove(Moves.SECRET_SWORD, "Secret Sword (N)", Type.FIGHTING, MoveCategory.SPECIAL, 85, 100, 10, -1, "Inflicts damage based on the target's Defense, not Special Defense.", -1, 0, 5),
new AttackMove(Moves.GLACIATE, "Glaciate", Type.ICE, MoveCategory.SPECIAL, 65, 95, 10, -1, "Lowers opponent's Speed.", 100, 0, 5)
.attr(StatChangeAttr, BattleStat.SPD, -1)
@ -3499,7 +3595,8 @@ export function initMoves() {
.ignoresVirtual(),
new AttackMove(Moves.SNARL, "Snarl", Type.DARK, MoveCategory.SPECIAL, 55, 95, 15, 30, "Lowers opponent's Special Attack.", 100, 0, 5)
.attr(StatChangeAttr, BattleStat.SPATK, -1)
.target(MoveTarget.ALL_NEAR_ENEMIES),
.target(MoveTarget.ALL_NEAR_ENEMIES)
.soundBased(),
new AttackMove(Moves.ICICLE_CRASH, "Icicle Crash", Type.ICE, MoveCategory.PHYSICAL, 85, 90, 10, -1, "May cause flinching.", 30, 0, 5)
.attr(FlinchAttr)
.makesContact(false),

View File

@ -15,7 +15,7 @@ import { initMoveAnim, loadMoveAnimAssets } from './data/battle-anims';
import { Status, StatusEffect } from './data/status-effect';
import { tmSpecies } from './data/tms';
import { pokemonEvolutions, pokemonPrevolutions, SpeciesEvolution, SpeciesEvolutionCondition } from './data/pokemon-evolutions';
import { DamagePhase, FaintPhase } from './battle-phases';
import { DamagePhase, FaintPhase, SwitchSummonPhase } from './battle-phases';
import { BattleStat } from './data/battle-stat';
import { BattlerTag, BattlerTagLapseType, BattlerTagType, TypeBoostTag, getBattlerTag } from './data/battler-tag';
import { Species } from './data/species';
@ -26,6 +26,8 @@ import { Biome } from './data/biome';
import { Abilities, Ability, BattleStatMultiplierAbAttr, BlockCritAbAttr, NonSuperEffectiveImmunityAbAttr, PreApplyBattlerTagAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, VariableMovePowerAbAttr, abilities, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPostDefendAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs } from './data/ability';
import PokemonData from './system/pokemon-data';
import { BattlerIndex } from './battle';
import { Mode } from './ui/ui';
import PartyUiHandler, { PartyOption, PartyUiMode } from './ui/party-ui-handler';
export enum FieldPosition {
CENTER,
@ -184,6 +186,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
isOnField(): boolean {
if (!this.scene)
return false;
return this.scene.field.getIndex(this) > -1;
}
@ -192,6 +196,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
isActive(onField?: boolean): boolean {
if (!this.scene)
return false;
return !this.isFainted() && !!this.scene && (!onField || this.isOnField());
}
@ -1161,6 +1167,21 @@ export class PlayerPokemon extends Pokemon {
}
}
switchOut(batonPass: boolean): Promise<void> {
return new Promise(resolve => {
this.resetTurnData();
this.resetSummonData();
this.hideInfo();
this.setVisible(false);
this.scene.ui.setMode(Mode.PARTY, PartyUiMode.FAINT_SWITCH, this.getFieldIndex(), (slotIndex: integer, option: PartyOption) => {
if (slotIndex >= this.scene.currentBattle.getBattlerCount() && slotIndex < 6)
this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, this.getFieldIndex(), slotIndex, false, batonPass));
this.scene.ui.setMode(Mode.MESSAGE).then(() => resolve());
}, PartyUiHandler.FilterNonFainted);
});
}
evolve(evolution: SpeciesEvolution): Promise<void> {
return new Promise(resolve => {
this.handleSpecialEvolutions(evolution);

View File

@ -114,6 +114,10 @@ export function getEnumValues(enumType): integer[] {
return Object.values(enumType).filter(v => !isNaN(parseInt(v.toString()))).map(v => parseInt(v.toString()));
}
export function executeIf<T>(condition: boolean, promiseFunc: () => Promise<T>): Promise<T> {
return condition ? promiseFunc() : new Promise<T>(resolve => resolve(null));
}
export class BooleanHolder {
public value: boolean;