mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-08-26 09:19:31 +02:00
Implement Shed Tail
This commit is contained in:
parent
1c87532e64
commit
bf7b8c0fba
158
src/data/move.ts
158
src/data/move.ts
@ -37,6 +37,7 @@ import { SwitchSummonPhase } from "#app/phases/switch-summon-phase";
|
|||||||
import { SpeciesFormChangeRevertWeatherFormTrigger } from "./pokemon-forms";
|
import { SpeciesFormChangeRevertWeatherFormTrigger } from "./pokemon-forms";
|
||||||
import { GameMode } from "#app/game-mode";
|
import { GameMode } from "#app/game-mode";
|
||||||
import { applyChallenges, ChallengeType } from "./challenge";
|
import { applyChallenges, ChallengeType } from "./challenge";
|
||||||
|
import { SwitchType } from "#app/enums/switch-type";
|
||||||
|
|
||||||
export enum MoveCategory {
|
export enum MoveCategory {
|
||||||
PHYSICAL,
|
PHYSICAL,
|
||||||
@ -1476,8 +1477,13 @@ export class HalfSacrificialAttr extends MoveEffectAttr {
|
|||||||
* @see {@linkcode apply}
|
* @see {@linkcode apply}
|
||||||
*/
|
*/
|
||||||
export class AddSubstituteAttr extends MoveEffectAttr {
|
export class AddSubstituteAttr extends MoveEffectAttr {
|
||||||
constructor() {
|
/** The ratio of the user's max HP that is required to apply this effect */
|
||||||
|
private hpCost: number;
|
||||||
|
|
||||||
|
constructor(hpCost: number = 0.25) {
|
||||||
super(true);
|
super(true);
|
||||||
|
|
||||||
|
this.hpCost = hpCost;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1493,8 +1499,7 @@ export class AddSubstituteAttr extends MoveEffectAttr {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const hpCost = Math.floor(user.getMaxHp() / 4);
|
user.damageAndUpdate(Math.floor(user.getMaxHp() * this.hpCost), HitResult.OTHER, false, true, true);
|
||||||
user.damageAndUpdate(hpCost, HitResult.OTHER, false, true, true);
|
|
||||||
user.addTag(BattlerTagType.SUBSTITUTE, 0, move.id, user.id);
|
user.addTag(BattlerTagType.SUBSTITUTE, 0, move.id, user.id);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -1507,7 +1512,7 @@ export class AddSubstituteAttr extends MoveEffectAttr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getCondition(): MoveConditionFunc {
|
getCondition(): MoveConditionFunc {
|
||||||
return (user, target, move) => !user.getTag(SubstituteTag) && user.hp > Math.floor(user.getMaxHp() / 4) && user.getMaxHp() > 1;
|
return (user, target, move) => !user.getTag(SubstituteTag) && user.hp > Math.floor(user.getMaxHp() * this.hpCost) && user.getMaxHp() > 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
getFailedText(user: Pokemon, target: Pokemon, move: Move, cancelled: Utils.BooleanHolder): string | null {
|
getFailedText(user: Pokemon, target: Pokemon, move: Move, cancelled: Utils.BooleanHolder): string | null {
|
||||||
@ -1521,6 +1526,66 @@ export class AddSubstituteAttr extends MoveEffectAttr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attribute implementing the effects of {@link https://bulbapedia.bulbagarden.net/wiki/Shed_Tail_(move) | Shed Tail}.
|
||||||
|
* This is essentially a wrapper for two internal attributes: {@linkcode AddSubstituteAttr} which handles creating
|
||||||
|
* a Substitute for the user, and {@linkcode ForceSwitchOutAttr} which forces the user to switch out and transfers the
|
||||||
|
* Substitute to the switched in Pokemon.
|
||||||
|
*/
|
||||||
|
export class ShedTailAttr extends MoveEffectAttr {
|
||||||
|
private addSubstituteAttr: AddSubstituteAttr;
|
||||||
|
private forceSwitchOutAttr: ForceSwitchOutAttr;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(true);
|
||||||
|
|
||||||
|
this.addSubstituteAttr = new AddSubstituteAttr(0.5);
|
||||||
|
this.forceSwitchOutAttr = new ForceSwitchOutAttr(true, SwitchType.SHED_TAIL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Has the user put in a substitute, prompt a switch, and transfer the substitute
|
||||||
|
* to the switched in Pokemon.
|
||||||
|
* @param user The {@linkcode Pokemon} using this move
|
||||||
|
* @param target n/a
|
||||||
|
* @param move The move being used (i.e. Shed Tail)
|
||||||
|
* @param args n/a
|
||||||
|
* @returns a `Promise` that resolves `true` if all effects apply successfully; `false` otherwise
|
||||||
|
*/
|
||||||
|
override apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||||
|
if (this.addSubstituteAttr.apply(user, target, move, args)) {
|
||||||
|
return this.forceSwitchOutAttr.apply(user, target, move, args);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The conditions determining whether the move fails.
|
||||||
|
* @returns A function that returns `true` if the move will not fail.
|
||||||
|
*/
|
||||||
|
override getCondition(): MoveConditionFunc {
|
||||||
|
return this.addSubstituteAttr.getCondition() && this.forceSwitchOutAttr.getCondition();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the text to show when the move fails. Identical to the fail messages for Substitute
|
||||||
|
* @param user The {@linkcode Pokemon} using the move
|
||||||
|
* @param target n/a
|
||||||
|
* @param move n/a
|
||||||
|
* @param cancelled n/a
|
||||||
|
* @returns The string to display upon the move failing. May vary with different fail conditions.
|
||||||
|
*/
|
||||||
|
override getFailedText(user: Pokemon, target: Pokemon, move: Move, cancelled: Utils.BooleanHolder): string | null {
|
||||||
|
return this.addSubstituteAttr.getFailedText(user, target, move, cancelled);
|
||||||
|
}
|
||||||
|
|
||||||
|
override getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): integer {
|
||||||
|
return this.addSubstituteAttr.getUserBenefitScore(user, target, move)
|
||||||
|
+ this.forceSwitchOutAttr.getUserBenefitScore(user, target, move);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export enum MultiHitType {
|
export enum MultiHitType {
|
||||||
_2,
|
_2,
|
||||||
_2_TO_5,
|
_2_TO_5,
|
||||||
@ -5152,9 +5217,9 @@ export class RevivalBlessingAttr extends MoveEffectAttr {
|
|||||||
if (user.scene.currentBattle.double && user.scene.getEnemyParty().length > 1) {
|
if (user.scene.currentBattle.double && user.scene.getEnemyParty().length > 1) {
|
||||||
const allyPokemon = user.getAlly();
|
const allyPokemon = user.getAlly();
|
||||||
if (slotIndex<=1) {
|
if (slotIndex<=1) {
|
||||||
user.scene.unshiftPhase(new SwitchSummonPhase(user.scene, pokemon.getFieldIndex(), slotIndex, false, false, false));
|
user.scene.unshiftPhase(new SwitchSummonPhase(user.scene, SwitchType.SWITCH, pokemon.getFieldIndex(), slotIndex, false, false));
|
||||||
} else if (allyPokemon.isFainted()) {
|
} else if (allyPokemon.isFainted()) {
|
||||||
user.scene.unshiftPhase(new SwitchSummonPhase(user.scene, allyPokemon.getFieldIndex(), slotIndex, false, false, false));
|
user.scene.unshiftPhase(new SwitchSummonPhase(user.scene, SwitchType.SWITCH, allyPokemon.getFieldIndex(), slotIndex, false, false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resolve(true);
|
resolve(true);
|
||||||
@ -5176,47 +5241,45 @@ export class RevivalBlessingAttr extends MoveEffectAttr {
|
|||||||
|
|
||||||
export class ForceSwitchOutAttr extends MoveEffectAttr {
|
export class ForceSwitchOutAttr extends MoveEffectAttr {
|
||||||
private user: boolean;
|
private user: boolean;
|
||||||
private batonPass: boolean;
|
private switchType: SwitchType;
|
||||||
|
|
||||||
constructor(user?: boolean, batonPass?: boolean) {
|
constructor(user: boolean = false, switchType: SwitchType = SwitchType.SWITCH) {
|
||||||
super(false, MoveEffectTrigger.POST_APPLY, false, true);
|
super(false, MoveEffectTrigger.POST_APPLY, false, true);
|
||||||
this.user = !!user;
|
this.user = user;
|
||||||
this.batonPass = !!batonPass;
|
this.switchType = switchType;
|
||||||
}
|
}
|
||||||
|
|
||||||
isBatonPass() {
|
isBatonPass() {
|
||||||
return this.batonPass;
|
return this.switchType === SwitchType.BATON_PASS;
|
||||||
}
|
}
|
||||||
|
|
||||||
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean {
|
||||||
return new Promise(resolve => {
|
// Check if the move category is not STATUS or if the switch out condition is not met
|
||||||
|
if (!this.getSwitchOutCondition()(user, target, move)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if the move category is not STATUS or if the switch out condition is not met
|
/**
|
||||||
if (!this.getSwitchOutCondition()(user, target, move)) {
|
* Move the switch out logic inside the conditional block
|
||||||
return resolve(false);
|
* This ensures that the switch out only happens when the conditions are met
|
||||||
}
|
*/
|
||||||
|
|
||||||
// Move the switch out logic inside the conditional block
|
|
||||||
// This ensures that the switch out only happens when the conditions are met
|
|
||||||
const switchOutTarget = this.user ? user : target;
|
const switchOutTarget = this.user ? user : target;
|
||||||
if (switchOutTarget instanceof PlayerPokemon) {
|
if (switchOutTarget instanceof PlayerPokemon) {
|
||||||
switchOutTarget.leaveField(!this.batonPass);
|
switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH);
|
||||||
|
|
||||||
if (switchOutTarget.hp > 0) {
|
if (switchOutTarget.hp > 0) {
|
||||||
user.scene.prependToPhase(new SwitchPhase(user.scene, switchOutTarget.getFieldIndex(), true, true), MoveEndPhase);
|
user.scene.prependToPhase(new SwitchPhase(user.scene, this.switchType, switchOutTarget.getFieldIndex(), true, true), MoveEndPhase);
|
||||||
resolve(true);
|
return true;
|
||||||
} else {
|
}
|
||||||
resolve(false);
|
return false;
|
||||||
}
|
|
||||||
return;
|
|
||||||
} else if (user.scene.currentBattle.battleType !== BattleType.WILD) {
|
} else if (user.scene.currentBattle.battleType !== BattleType.WILD) {
|
||||||
// Switch out logic for trainer battles
|
// Switch out logic for trainer battles
|
||||||
switchOutTarget.leaveField(!this.batonPass);
|
switchOutTarget.leaveField(this.switchType === SwitchType.SWITCH);
|
||||||
|
|
||||||
if (switchOutTarget.hp > 0) {
|
if (switchOutTarget.hp > 0) {
|
||||||
// for opponent switching out
|
// for opponent switching out
|
||||||
user.scene.prependToPhase(new SwitchSummonPhase(user.scene, switchOutTarget.getFieldIndex(), (user.scene.currentBattle.trainer ? user.scene.currentBattle.trainer.getNextSummonIndex((switchOutTarget as EnemyPokemon).trainerSlot) : 0), false, this.batonPass, false), MoveEndPhase);
|
user.scene.prependToPhase(new SwitchSummonPhase(user.scene, this.switchType, switchOutTarget.getFieldIndex(), (user.scene.currentBattle.trainer ? user.scene.currentBattle.trainer.getNextSummonIndex((switchOutTarget as EnemyPokemon).trainerSlot) : 0), false, false), MoveEndPhase);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Switch out logic for everything else (eg: WILD battles)
|
// Switch out logic for everything else (eg: WILD battles)
|
||||||
switchOutTarget.leaveField(false);
|
switchOutTarget.leaveField(false);
|
||||||
@ -5224,11 +5287,11 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
|
|||||||
if (switchOutTarget.hp) {
|
if (switchOutTarget.hp) {
|
||||||
user.scene.queueMessage(i18next.t("moveTriggers:fled", {pokemonName: getPokemonNameWithAffix(switchOutTarget)}), null, true, 500);
|
user.scene.queueMessage(i18next.t("moveTriggers:fled", {pokemonName: getPokemonNameWithAffix(switchOutTarget)}), null, true, 500);
|
||||||
|
|
||||||
// in double battles redirect potential moves off fled pokemon
|
// in double battles redirect potential moves off fled pokemon
|
||||||
if (switchOutTarget.scene.currentBattle.double) {
|
if (switchOutTarget.scene.currentBattle.double) {
|
||||||
const allyPokemon = switchOutTarget.getAlly();
|
const allyPokemon = switchOutTarget.getAlly();
|
||||||
switchOutTarget.scene.redirectPokemonMoves(switchOutTarget, allyPokemon);
|
switchOutTarget.scene.redirectPokemonMoves(switchOutTarget, allyPokemon);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!switchOutTarget.getAlly()?.isActive(true)) {
|
if (!switchOutTarget.getAlly()?.isActive(true)) {
|
||||||
@ -5241,8 +5304,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(true);
|
return true;
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getCondition(): MoveConditionFunc {
|
getCondition(): MoveConditionFunc {
|
||||||
@ -5268,8 +5330,8 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!player && !user.scene.currentBattle.battleType) {
|
if (!player && user.scene.currentBattle.battleType === BattleType.WILD) {
|
||||||
if (this.batonPass) {
|
if (this.isBatonPass()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
// Don't allow wild opponents to flee on the boss stage since it can ruin a run early on
|
// Don't allow wild opponents to flee on the boss stage since it can ruin a run early on
|
||||||
@ -5288,7 +5350,7 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
|
|||||||
return -20;
|
return -20;
|
||||||
}
|
}
|
||||||
let ret = this.user ? Math.floor((1 - user.getHpRatio()) * 20) : super.getUserBenefitScore(user, target, move);
|
let ret = this.user ? Math.floor((1 - user.getHpRatio()) * 20) : super.getUserBenefitScore(user, target, move);
|
||||||
if (this.user && this.batonPass) {
|
if (this.user && this.isBatonPass()) {
|
||||||
const statStageTotal = user.getStatStages().reduce((s: integer, total: integer) => total += s, 0);
|
const statStageTotal = user.getStatStages().reduce((s: integer, total: integer) => total += s, 0);
|
||||||
ret = ret / 2 + (Phaser.Tweens.Builders.GetEaseFunction("Sine.easeOut")(Math.min(Math.abs(statStageTotal), 10) / 10) * (statStageTotal >= 0 ? 10 : -10));
|
ret = ret / 2 + (Phaser.Tweens.Builders.GetEaseFunction("Sine.easeOut")(Math.min(Math.abs(statStageTotal), 10) / 10) * (statStageTotal >= 0 ? 10 : -10));
|
||||||
}
|
}
|
||||||
@ -7376,7 +7438,7 @@ export function initMoves() {
|
|||||||
new AttackMove(Moves.DRAGON_BREATH, Type.DRAGON, MoveCategory.SPECIAL, 60, 100, 20, 30, 0, 2)
|
new AttackMove(Moves.DRAGON_BREATH, Type.DRAGON, MoveCategory.SPECIAL, 60, 100, 20, 30, 0, 2)
|
||||||
.attr(StatusEffectAttr, StatusEffect.PARALYSIS),
|
.attr(StatusEffectAttr, StatusEffect.PARALYSIS),
|
||||||
new SelfStatusMove(Moves.BATON_PASS, Type.NORMAL, -1, 40, -1, 0, 2)
|
new SelfStatusMove(Moves.BATON_PASS, Type.NORMAL, -1, 40, -1, 0, 2)
|
||||||
.attr(ForceSwitchOutAttr, true, true)
|
.attr(ForceSwitchOutAttr, true, SwitchType.BATON_PASS)
|
||||||
.hidesUser(),
|
.hidesUser(),
|
||||||
new StatusMove(Moves.ENCORE, Type.NORMAL, 100, 5, -1, 0, 2)
|
new StatusMove(Moves.ENCORE, Type.NORMAL, 100, 5, -1, 0, 2)
|
||||||
.attr(AddBattlerTagAttr, BattlerTagType.ENCORE, false, true)
|
.attr(AddBattlerTagAttr, BattlerTagType.ENCORE, false, true)
|
||||||
@ -7801,7 +7863,7 @@ export function initMoves() {
|
|||||||
.makesContact(false)
|
.makesContact(false)
|
||||||
.target(MoveTarget.ATTACKER),
|
.target(MoveTarget.ATTACKER),
|
||||||
new AttackMove(Moves.U_TURN, Type.BUG, MoveCategory.PHYSICAL, 70, 100, 20, -1, 0, 4)
|
new AttackMove(Moves.U_TURN, Type.BUG, MoveCategory.PHYSICAL, 70, 100, 20, -1, 0, 4)
|
||||||
.attr(ForceSwitchOutAttr, true, false),
|
.attr(ForceSwitchOutAttr, true),
|
||||||
new AttackMove(Moves.CLOSE_COMBAT, Type.FIGHTING, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 4)
|
new AttackMove(Moves.CLOSE_COMBAT, Type.FIGHTING, MoveCategory.PHYSICAL, 120, 100, 5, -1, 0, 4)
|
||||||
.attr(StatStageChangeAttr, [ Stat.DEF, Stat.SPDEF ], -1, true),
|
.attr(StatStageChangeAttr, [ Stat.DEF, Stat.SPDEF ], -1, true),
|
||||||
new AttackMove(Moves.PAYBACK, Type.DARK, MoveCategory.PHYSICAL, 50, 100, 10, -1, 0, 4)
|
new AttackMove(Moves.PAYBACK, Type.DARK, MoveCategory.PHYSICAL, 50, 100, 10, -1, 0, 4)
|
||||||
@ -8234,7 +8296,7 @@ export function initMoves() {
|
|||||||
new AttackMove(Moves.GRASS_PLEDGE, Type.GRASS, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 5)
|
new AttackMove(Moves.GRASS_PLEDGE, Type.GRASS, MoveCategory.SPECIAL, 80, 100, 10, -1, 0, 5)
|
||||||
.partial(),
|
.partial(),
|
||||||
new AttackMove(Moves.VOLT_SWITCH, Type.ELECTRIC, MoveCategory.SPECIAL, 70, 100, 20, -1, 0, 5)
|
new AttackMove(Moves.VOLT_SWITCH, Type.ELECTRIC, MoveCategory.SPECIAL, 70, 100, 20, -1, 0, 5)
|
||||||
.attr(ForceSwitchOutAttr, true, false),
|
.attr(ForceSwitchOutAttr, true),
|
||||||
new AttackMove(Moves.STRUGGLE_BUG, Type.BUG, MoveCategory.SPECIAL, 50, 100, 20, 100, 0, 5)
|
new AttackMove(Moves.STRUGGLE_BUG, Type.BUG, MoveCategory.SPECIAL, 50, 100, 20, 100, 0, 5)
|
||||||
.attr(StatStageChangeAttr, [ Stat.SPATK ], -1)
|
.attr(StatStageChangeAttr, [ Stat.SPATK ], -1)
|
||||||
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
||||||
@ -8402,7 +8464,7 @@ export function initMoves() {
|
|||||||
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
.target(MoveTarget.ALL_NEAR_ENEMIES),
|
||||||
new StatusMove(Moves.PARTING_SHOT, Type.DARK, 100, 20, -1, 0, 6)
|
new StatusMove(Moves.PARTING_SHOT, Type.DARK, 100, 20, -1, 0, 6)
|
||||||
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], -1, false, null, true, true, MoveEffectTrigger.PRE_APPLY)
|
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPATK ], -1, false, null, true, true, MoveEffectTrigger.PRE_APPLY)
|
||||||
.attr(ForceSwitchOutAttr, true, false)
|
.attr(ForceSwitchOutAttr, true)
|
||||||
.soundBased(),
|
.soundBased(),
|
||||||
new StatusMove(Moves.TOPSY_TURVY, Type.DARK, -1, 20, -1, 0, 6)
|
new StatusMove(Moves.TOPSY_TURVY, Type.DARK, -1, 20, -1, 0, 6)
|
||||||
.attr(InvertStatsAttr),
|
.attr(InvertStatsAttr),
|
||||||
@ -9158,7 +9220,7 @@ export function initMoves() {
|
|||||||
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF ], 1)
|
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.DEF ], 1)
|
||||||
.target(MoveTarget.NEAR_ALLY),
|
.target(MoveTarget.NEAR_ALLY),
|
||||||
new AttackMove(Moves.FLIP_TURN, Type.WATER, MoveCategory.PHYSICAL, 60, 100, 20, -1, 0, 8)
|
new AttackMove(Moves.FLIP_TURN, Type.WATER, MoveCategory.PHYSICAL, 60, 100, 20, -1, 0, 8)
|
||||||
.attr(ForceSwitchOutAttr, true, false),
|
.attr(ForceSwitchOutAttr, true),
|
||||||
new AttackMove(Moves.TRIPLE_AXEL, Type.ICE, MoveCategory.PHYSICAL, 20, 90, 10, -1, 0, 8)
|
new AttackMove(Moves.TRIPLE_AXEL, Type.ICE, MoveCategory.PHYSICAL, 20, 90, 10, -1, 0, 8)
|
||||||
.attr(MultiHitAttr, MultiHitType._3)
|
.attr(MultiHitAttr, MultiHitType._3)
|
||||||
.attr(MultiHitPowerIncrementAttr, 3)
|
.attr(MultiHitPowerIncrementAttr, 3)
|
||||||
@ -9485,10 +9547,10 @@ export function initMoves() {
|
|||||||
.attr(MovePowerMultiplierAttr, (user, target, move) => target.getAttackTypeEffectiveness(move.type, user) >= 2 ? 5461/4096 : 1)
|
.attr(MovePowerMultiplierAttr, (user, target, move) => target.getAttackTypeEffectiveness(move.type, user) >= 2 ? 5461/4096 : 1)
|
||||||
.makesContact(),
|
.makesContact(),
|
||||||
new SelfStatusMove(Moves.SHED_TAIL, Type.NORMAL, -1, 10, -1, 0, 9)
|
new SelfStatusMove(Moves.SHED_TAIL, Type.NORMAL, -1, 10, -1, 0, 9)
|
||||||
.unimplemented(),
|
.attr(ShedTailAttr),
|
||||||
new StatusMove(Moves.CHILLY_RECEPTION, Type.ICE, -1, 10, -1, 0, 9)
|
new StatusMove(Moves.CHILLY_RECEPTION, Type.ICE, -1, 10, -1, 0, 9)
|
||||||
.attr(WeatherChangeAttr, WeatherType.SNOW)
|
.attr(WeatherChangeAttr, WeatherType.SNOW)
|
||||||
.attr(ForceSwitchOutAttr, true, false)
|
.attr(ForceSwitchOutAttr, true)
|
||||||
.target(MoveTarget.BOTH_SIDES),
|
.target(MoveTarget.BOTH_SIDES),
|
||||||
new SelfStatusMove(Moves.TIDY_UP, Type.NORMAL, -1, 10, -1, 0, 9)
|
new SelfStatusMove(Moves.TIDY_UP, Type.NORMAL, -1, 10, -1, 0, 9)
|
||||||
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPD ], 1, true, null, true, true)
|
.attr(StatStageChangeAttr, [ Stat.ATK, Stat.SPD ], 1, true, null, true, true)
|
||||||
|
12
src/enums/switch-type.ts
Normal file
12
src/enums/switch-type.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
/**
|
||||||
|
* Indicates the type of switch functionality that a {@linkcode SwitchPhase}
|
||||||
|
* or {@linkcode SwitchSummonPhase} will carry out.
|
||||||
|
*/
|
||||||
|
export enum SwitchType {
|
||||||
|
/** Basic switchout where the Pokemon to switch in is selected */
|
||||||
|
SWITCH,
|
||||||
|
/** Transfers stat stages and other effects from the returning Pokemon to the switched in Pokemon */
|
||||||
|
BATON_PASS,
|
||||||
|
/** Transfers the returning Pokemon's Substitute to the switched in Pokemon */
|
||||||
|
SHED_TAIL
|
||||||
|
}
|
@ -61,6 +61,7 @@ import { Challenges } from "#enums/challenges";
|
|||||||
import { PokemonAnimType } from "#app/enums/pokemon-anim-type";
|
import { PokemonAnimType } from "#app/enums/pokemon-anim-type";
|
||||||
import { PLAYER_PARTY_MAX_SIZE } from "#app/constants";
|
import { PLAYER_PARTY_MAX_SIZE } from "#app/constants";
|
||||||
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data";
|
import { MysteryEncounterPokemonData } from "#app/data/mystery-encounters/mystery-encounter-pokemon-data";
|
||||||
|
import { SwitchType } from "#app/enums/switch-type";
|
||||||
|
|
||||||
export enum FieldPosition {
|
export enum FieldPosition {
|
||||||
CENTER,
|
CENTER,
|
||||||
@ -3989,16 +3990,17 @@ export class PlayerPokemon extends Pokemon {
|
|||||||
/**
|
/**
|
||||||
* Causes this mon to leave the field (via {@linkcode leaveField}) and then
|
* Causes this mon to leave the field (via {@linkcode leaveField}) and then
|
||||||
* opens the party switcher UI to switch a new mon in
|
* opens the party switcher UI to switch a new mon in
|
||||||
* @param batonPass Indicates if this switch was caused by a baton pass (and
|
* @param switchType the {@linkcode SwitchType} for this switch-out. If this is
|
||||||
* thus should maintain active mon effects)
|
* `BATON_PASS` or `SHED_TAIL`, this Pokemon's effects are not cleared upon leaving
|
||||||
|
* the field.
|
||||||
*/
|
*/
|
||||||
switchOut(batonPass: boolean): Promise<void> {
|
switchOut(switchType: SwitchType = SwitchType.SWITCH): Promise<void> {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
this.leaveField(!batonPass);
|
this.leaveField(switchType === SwitchType.SWITCH);
|
||||||
|
|
||||||
this.scene.ui.setMode(Mode.PARTY, PartyUiMode.FAINT_SWITCH, this.getFieldIndex(), (slotIndex: integer, option: PartyOption) => {
|
this.scene.ui.setMode(Mode.PARTY, PartyUiMode.FAINT_SWITCH, this.getFieldIndex(), (slotIndex: integer, option: PartyOption) => {
|
||||||
if (slotIndex >= this.scene.currentBattle.getBattlerCount() && slotIndex < 6) {
|
if (slotIndex >= this.scene.currentBattle.getBattlerCount() && slotIndex < 6) {
|
||||||
this.scene.prependToPhase(new SwitchSummonPhase(this.scene, this.getFieldIndex(), slotIndex, false, batonPass), MoveEndPhase);
|
this.scene.prependToPhase(new SwitchSummonPhase(this.scene, switchType, this.getFieldIndex(), slotIndex, false), MoveEndPhase);
|
||||||
}
|
}
|
||||||
this.scene.ui.setMode(Mode.MESSAGE).then(resolve);
|
this.scene.ui.setMode(Mode.MESSAGE).then(resolve);
|
||||||
}, PartyUiHandler.FilterNonFainted);
|
}, PartyUiHandler.FilterNonFainted);
|
||||||
@ -4060,11 +4062,11 @@ export class PlayerPokemon extends Pokemon {
|
|||||||
const allyPokemon = this.getAlly();
|
const allyPokemon = this.getAlly();
|
||||||
if (slotIndex<=1) {
|
if (slotIndex<=1) {
|
||||||
// Revived ally pokemon
|
// Revived ally pokemon
|
||||||
this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, pokemon.getFieldIndex(), slotIndex, false, false, true));
|
this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, SwitchType.SWITCH, pokemon.getFieldIndex(), slotIndex, false, true));
|
||||||
this.scene.unshiftPhase(new ToggleDoublePositionPhase(this.scene, true));
|
this.scene.unshiftPhase(new ToggleDoublePositionPhase(this.scene, true));
|
||||||
} else if (allyPokemon.isFainted()) {
|
} else if (allyPokemon.isFainted()) {
|
||||||
// Revived party pokemon, and ally pokemon is fainted
|
// Revived party pokemon, and ally pokemon is fainted
|
||||||
this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, allyPokemon.getFieldIndex(), slotIndex, false, false, true));
|
this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, SwitchType.SWITCH, allyPokemon.getFieldIndex(), slotIndex, false, true));
|
||||||
this.scene.unshiftPhase(new ToggleDoublePositionPhase(this.scene, true));
|
this.scene.unshiftPhase(new ToggleDoublePositionPhase(this.scene, true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import { BattlePhase } from "./battle-phase";
|
|||||||
import { PostSummonPhase } from "./post-summon-phase";
|
import { PostSummonPhase } from "./post-summon-phase";
|
||||||
import { SummonMissingPhase } from "./summon-missing-phase";
|
import { SummonMissingPhase } from "./summon-missing-phase";
|
||||||
import { SwitchPhase } from "./switch-phase";
|
import { SwitchPhase } from "./switch-phase";
|
||||||
|
import { SwitchType } from "#app/enums/switch-type";
|
||||||
|
|
||||||
export class CheckSwitchPhase extends BattlePhase {
|
export class CheckSwitchPhase extends BattlePhase {
|
||||||
protected fieldIndex: integer;
|
protected fieldIndex: integer;
|
||||||
@ -50,7 +51,7 @@ export class CheckSwitchPhase extends BattlePhase {
|
|||||||
this.scene.ui.setMode(Mode.CONFIRM, () => {
|
this.scene.ui.setMode(Mode.CONFIRM, () => {
|
||||||
this.scene.ui.setMode(Mode.MESSAGE);
|
this.scene.ui.setMode(Mode.MESSAGE);
|
||||||
this.scene.tryRemovePhase(p => p instanceof PostSummonPhase && p.player && p.fieldIndex === this.fieldIndex);
|
this.scene.tryRemovePhase(p => p instanceof PostSummonPhase && p.player && p.fieldIndex === this.fieldIndex);
|
||||||
this.scene.unshiftPhase(new SwitchPhase(this.scene, this.fieldIndex, false, true));
|
this.scene.unshiftPhase(new SwitchPhase(this.scene, SwitchType.SWITCH, this.fieldIndex, false, true));
|
||||||
this.end();
|
this.end();
|
||||||
}, () => {
|
}, () => {
|
||||||
this.scene.ui.setMode(Mode.MESSAGE);
|
this.scene.ui.setMode(Mode.MESSAGE);
|
||||||
|
@ -18,6 +18,7 @@ import { GameOverPhase } from "./game-over-phase";
|
|||||||
import { SwitchPhase } from "./switch-phase";
|
import { SwitchPhase } from "./switch-phase";
|
||||||
import { VictoryPhase } from "./victory-phase";
|
import { VictoryPhase } from "./victory-phase";
|
||||||
import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms";
|
import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms";
|
||||||
|
import { SwitchType } from "#app/enums/switch-type";
|
||||||
|
|
||||||
export class FaintPhase extends PokemonPhase {
|
export class FaintPhase extends PokemonPhase {
|
||||||
private preventEndure: boolean;
|
private preventEndure: boolean;
|
||||||
@ -106,14 +107,14 @@ export class FaintPhase extends PokemonPhase {
|
|||||||
* If previous conditions weren't met, and the player has at least 1 legal Pokemon off the field,
|
* If previous conditions weren't met, and the player has at least 1 legal Pokemon off the field,
|
||||||
* push a phase that prompts the player to summon a Pokemon from their party.
|
* push a phase that prompts the player to summon a Pokemon from their party.
|
||||||
*/
|
*/
|
||||||
this.scene.pushPhase(new SwitchPhase(this.scene, this.fieldIndex, true, false));
|
this.scene.pushPhase(new SwitchPhase(this.scene, SwitchType.SWITCH, this.fieldIndex, true, false));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.scene.unshiftPhase(new VictoryPhase(this.scene, this.battlerIndex));
|
this.scene.unshiftPhase(new VictoryPhase(this.scene, this.battlerIndex));
|
||||||
if ([BattleType.TRAINER, BattleType.MYSTERY_ENCOUNTER].includes(this.scene.currentBattle.battleType)) {
|
if ([BattleType.TRAINER, BattleType.MYSTERY_ENCOUNTER].includes(this.scene.currentBattle.battleType)) {
|
||||||
const hasReservePartyMember = !!this.scene.getEnemyParty().filter(p => p.isActive() && !p.isOnField() && p.trainerSlot === (pokemon as EnemyPokemon).trainerSlot).length;
|
const hasReservePartyMember = !!this.scene.getEnemyParty().filter(p => p.isActive() && !p.isOnField() && p.trainerSlot === (pokemon as EnemyPokemon).trainerSlot).length;
|
||||||
if (hasReservePartyMember) {
|
if (hasReservePartyMember) {
|
||||||
this.scene.pushPhase(new SwitchSummonPhase(this.scene, this.fieldIndex, -1, false, false, false));
|
this.scene.pushPhase(new SwitchSummonPhase(this.scene, SwitchType.SWITCH, this.fieldIndex, -1, false, false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ import { NewBattlePhase } from "#app/phases/new-battle-phase";
|
|||||||
import { GameOverPhase } from "#app/phases/game-over-phase";
|
import { GameOverPhase } from "#app/phases/game-over-phase";
|
||||||
import { SwitchPhase } from "#app/phases/switch-phase";
|
import { SwitchPhase } from "#app/phases/switch-phase";
|
||||||
import { SeenEncounterData } from "#app/data/mystery-encounters/mystery-encounter-save-data";
|
import { SeenEncounterData } from "#app/data/mystery-encounters/mystery-encounter-save-data";
|
||||||
|
import { SwitchType } from "#app/enums/switch-type";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will handle (in order):
|
* Will handle (in order):
|
||||||
@ -241,7 +242,7 @@ export class MysteryEncounterBattleStartCleanupPhase extends Phase {
|
|||||||
const playerField = this.scene.getPlayerField();
|
const playerField = this.scene.getPlayerField();
|
||||||
playerField.forEach((pokemon, i) => {
|
playerField.forEach((pokemon, i) => {
|
||||||
if (!pokemon.isAllowedInBattle() && legalPlayerPartyPokemon.length > i) {
|
if (!pokemon.isAllowedInBattle() && legalPlayerPartyPokemon.length > i) {
|
||||||
this.scene.unshiftPhase(new SwitchPhase(this.scene, i, true, false));
|
this.scene.unshiftPhase(new SwitchPhase(this.scene, SwitchType.SWITCH, i, true, false));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import BattleScene from "#app/battle-scene";
|
import BattleScene from "#app/battle-scene";
|
||||||
import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms";
|
import { SpeciesFormChangeActiveTrigger } from "#app/data/pokemon-forms";
|
||||||
|
import { SwitchType } from "#app/enums/switch-type";
|
||||||
import { SwitchSummonPhase } from "./switch-summon-phase";
|
import { SwitchSummonPhase } from "./switch-summon-phase";
|
||||||
|
|
||||||
export class ReturnPhase extends SwitchSummonPhase {
|
export class ReturnPhase extends SwitchSummonPhase {
|
||||||
constructor(scene: BattleScene, fieldIndex: integer) {
|
constructor(scene: BattleScene, fieldIndex: integer) {
|
||||||
super(scene, fieldIndex, -1, true, false);
|
super(scene, SwitchType.SWITCH, fieldIndex, -1, true, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
switchAndSummon(): void {
|
switchAndSummon(): void {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import BattleScene from "#app/battle-scene";
|
import BattleScene from "#app/battle-scene";
|
||||||
|
import { SwitchType } from "#app/enums/switch-type";
|
||||||
import PartyUiHandler, { PartyUiMode, PartyOption } from "#app/ui/party-ui-handler";
|
import PartyUiHandler, { PartyUiMode, PartyOption } from "#app/ui/party-ui-handler";
|
||||||
import { Mode } from "#app/ui/ui";
|
import { Mode } from "#app/ui/ui";
|
||||||
import { BattlePhase } from "./battle-phase";
|
import { BattlePhase } from "./battle-phase";
|
||||||
@ -10,21 +11,24 @@ import { SwitchSummonPhase } from "./switch-summon-phase";
|
|||||||
*/
|
*/
|
||||||
export class SwitchPhase extends BattlePhase {
|
export class SwitchPhase extends BattlePhase {
|
||||||
protected fieldIndex: integer;
|
protected fieldIndex: integer;
|
||||||
|
private switchType: SwitchType;
|
||||||
private isModal: boolean;
|
private isModal: boolean;
|
||||||
private doReturn: boolean;
|
private doReturn: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new SwitchPhase
|
* Creates a new SwitchPhase
|
||||||
* @param scene {@linkcode BattleScene} Current battle scene
|
* @param scene {@linkcode BattleScene} Current battle scene
|
||||||
|
* @param switchType The type of switch logic this phase implements
|
||||||
* @param fieldIndex Field index to switch out
|
* @param fieldIndex Field index to switch out
|
||||||
* @param isModal Indicates if the switch should be forced (true) or is
|
* @param isModal Indicates if the switch should be forced (true) or is
|
||||||
* optional (false).
|
* optional (false).
|
||||||
* @param doReturn Indicates if the party member on the field should be
|
* @param doReturn Indicates if the party member on the field should be
|
||||||
* recalled to ball or has already left the field. Passed to {@linkcode SwitchSummonPhase}.
|
* recalled to ball or has already left the field. Passed to {@linkcode SwitchSummonPhase}.
|
||||||
*/
|
*/
|
||||||
constructor(scene: BattleScene, fieldIndex: integer, isModal: boolean, doReturn: boolean) {
|
constructor(scene: BattleScene, switchType: SwitchType, fieldIndex: integer, isModal: boolean, doReturn: boolean) {
|
||||||
super(scene);
|
super(scene);
|
||||||
|
|
||||||
|
this.switchType = switchType;
|
||||||
this.fieldIndex = fieldIndex;
|
this.fieldIndex = fieldIndex;
|
||||||
this.isModal = isModal;
|
this.isModal = isModal;
|
||||||
this.doReturn = doReturn;
|
this.doReturn = doReturn;
|
||||||
@ -38,11 +42,13 @@ export class SwitchPhase extends BattlePhase {
|
|||||||
return super.end();
|
return super.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip if the fainted party member has been revived already. doReturn is
|
/**
|
||||||
// only passed as `false` from FaintPhase (as opposed to other usages such
|
* Skip if the fainted party member has been revived already. doReturn is
|
||||||
// as ForceSwitchOutAttr or CheckSwitchPhase), so we only want to check this
|
* only passed as `false` from FaintPhase (as opposed to other usages such
|
||||||
// if the mon should have already been returned but is still alive and well
|
* as ForceSwitchOutAttr or CheckSwitchPhase), so we only want to check this
|
||||||
// on the field. see also; battle.test.ts
|
* if the mon should have already been returned but is still alive and well
|
||||||
|
* on the field. see also; battle.test.ts
|
||||||
|
*/
|
||||||
if (this.isModal && !this.doReturn && !this.scene.getParty()[this.fieldIndex].isFainted()) {
|
if (this.isModal && !this.doReturn && !this.scene.getParty()[this.fieldIndex].isFainted()) {
|
||||||
return super.end();
|
return super.end();
|
||||||
}
|
}
|
||||||
@ -57,7 +63,8 @@ export class SwitchPhase extends BattlePhase {
|
|||||||
|
|
||||||
this.scene.ui.setMode(Mode.PARTY, this.isModal ? PartyUiMode.FAINT_SWITCH : PartyUiMode.POST_BATTLE_SWITCH, fieldIndex, (slotIndex: integer, option: PartyOption) => {
|
this.scene.ui.setMode(Mode.PARTY, this.isModal ? PartyUiMode.FAINT_SWITCH : PartyUiMode.POST_BATTLE_SWITCH, fieldIndex, (slotIndex: integer, option: PartyOption) => {
|
||||||
if (slotIndex >= this.scene.currentBattle.getBattlerCount() && slotIndex < 6) {
|
if (slotIndex >= this.scene.currentBattle.getBattlerCount() && slotIndex < 6) {
|
||||||
this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, fieldIndex, slotIndex, this.doReturn, option === PartyOption.PASS_BATON));
|
const switchType = (option === PartyOption.PASS_BATON) ? SwitchType.BATON_PASS : this.switchType;
|
||||||
|
this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, switchType, fieldIndex, slotIndex, this.doReturn));
|
||||||
}
|
}
|
||||||
this.scene.ui.setMode(Mode.MESSAGE).then(() => super.end());
|
this.scene.ui.setMode(Mode.MESSAGE).then(() => super.end());
|
||||||
}, PartyUiHandler.FilterNonFainted);
|
}, PartyUiHandler.FilterNonFainted);
|
||||||
|
@ -12,29 +12,31 @@ import i18next from "i18next";
|
|||||||
import { PostSummonPhase } from "./post-summon-phase";
|
import { PostSummonPhase } from "./post-summon-phase";
|
||||||
import { SummonPhase } from "./summon-phase";
|
import { SummonPhase } from "./summon-phase";
|
||||||
import { SubstituteTag } from "#app/data/battler-tags";
|
import { SubstituteTag } from "#app/data/battler-tags";
|
||||||
|
import { SwitchType } from "#app/enums/switch-type";
|
||||||
|
|
||||||
export class SwitchSummonPhase extends SummonPhase {
|
export class SwitchSummonPhase extends SummonPhase {
|
||||||
|
private switchType: SwitchType;
|
||||||
private slotIndex: integer;
|
private slotIndex: integer;
|
||||||
private doReturn: boolean;
|
private doReturn: boolean;
|
||||||
private batonPass: boolean;
|
|
||||||
|
|
||||||
private lastPokemon: Pokemon;
|
private lastPokemon: Pokemon;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor for creating a new SwitchSummonPhase
|
* Constructor for creating a new SwitchSummonPhase
|
||||||
* @param scene {@linkcode BattleScene} the scene the phase is associated with
|
* @param scene {@linkcode BattleScene} the scene the phase is associated with
|
||||||
|
* @param switchType the type of switch behavior
|
||||||
* @param fieldIndex integer representing position on the battle field
|
* @param fieldIndex integer representing position on the battle field
|
||||||
* @param slotIndex integer for the index of pokemon (in party of 6) to switch into
|
* @param slotIndex integer for the index of pokemon (in party of 6) to switch into
|
||||||
* @param doReturn boolean whether to render "comeback" dialogue
|
* @param doReturn boolean whether to render "comeback" dialogue
|
||||||
* @param batonPass boolean if the switch is from baton pass
|
* @param batonPass boolean if the switch is from baton pass
|
||||||
* @param player boolean if the switch is from the player
|
* @param player boolean if the switch is from the player
|
||||||
*/
|
*/
|
||||||
constructor(scene: BattleScene, fieldIndex: integer, slotIndex: integer, doReturn: boolean, batonPass: boolean, player?: boolean) {
|
constructor(scene: BattleScene, switchType: SwitchType, fieldIndex: integer, slotIndex: integer, doReturn: boolean, player?: boolean) {
|
||||||
super(scene, fieldIndex, player !== undefined ? player : true);
|
super(scene, fieldIndex, player !== undefined ? player : true);
|
||||||
|
|
||||||
|
this.switchType = switchType;
|
||||||
this.slotIndex = slotIndex;
|
this.slotIndex = slotIndex;
|
||||||
this.doReturn = doReturn;
|
this.doReturn = doReturn;
|
||||||
this.batonPass = batonPass;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
start(): void {
|
start(): void {
|
||||||
@ -64,7 +66,7 @@ export class SwitchSummonPhase extends SummonPhase {
|
|||||||
|
|
||||||
const pokemon = this.getPokemon();
|
const pokemon = this.getPokemon();
|
||||||
|
|
||||||
if (!this.batonPass) {
|
if (this.switchType === SwitchType.SWITCH) {
|
||||||
(this.player ? this.scene.getEnemyField() : this.scene.getPlayerField()).forEach(enemyPokemon => enemyPokemon.removeTagsBySourceId(pokemon.id));
|
(this.player ? this.scene.getEnemyField() : this.scene.getPlayerField()).forEach(enemyPokemon => enemyPokemon.removeTagsBySourceId(pokemon.id));
|
||||||
const substitute = pokemon.getTag(SubstituteTag);
|
const substitute = pokemon.getTag(SubstituteTag);
|
||||||
if (substitute) {
|
if (substitute) {
|
||||||
@ -94,7 +96,7 @@ export class SwitchSummonPhase extends SummonPhase {
|
|||||||
ease: "Sine.easeIn",
|
ease: "Sine.easeIn",
|
||||||
scale: 0.5,
|
scale: 0.5,
|
||||||
onComplete: () => {
|
onComplete: () => {
|
||||||
pokemon.leaveField(!this.batonPass, false);
|
pokemon.leaveField(this.switchType === SwitchType.SWITCH, false);
|
||||||
this.scene.time.delayedCall(750, () => this.switchAndSummon());
|
this.scene.time.delayedCall(750, () => this.switchAndSummon());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -105,7 +107,7 @@ export class SwitchSummonPhase extends SummonPhase {
|
|||||||
const switchedInPokemon = party[this.slotIndex];
|
const switchedInPokemon = party[this.slotIndex];
|
||||||
this.lastPokemon = this.getPokemon();
|
this.lastPokemon = this.getPokemon();
|
||||||
applyPreSwitchOutAbAttrs(PreSwitchOutAbAttr, this.lastPokemon);
|
applyPreSwitchOutAbAttrs(PreSwitchOutAbAttr, this.lastPokemon);
|
||||||
if (this.batonPass && switchedInPokemon) {
|
if (this.switchType === SwitchType.BATON_PASS && switchedInPokemon) {
|
||||||
(this.player ? this.scene.getEnemyField() : this.scene.getPlayerField()).forEach(enemyPokemon => enemyPokemon.transferTagsBySourceId(this.lastPokemon.id, switchedInPokemon.id));
|
(this.player ? this.scene.getEnemyField() : this.scene.getPlayerField()).forEach(enemyPokemon => enemyPokemon.transferTagsBySourceId(this.lastPokemon.id, switchedInPokemon.id));
|
||||||
if (!this.scene.findModifier(m => m instanceof SwitchEffectTransferModifier && (m as SwitchEffectTransferModifier).pokemonId === switchedInPokemon.id)) {
|
if (!this.scene.findModifier(m => m instanceof SwitchEffectTransferModifier && (m as SwitchEffectTransferModifier).pokemonId === switchedInPokemon.id)) {
|
||||||
const batonPassModifier = this.scene.findModifier(m => m instanceof SwitchEffectTransferModifier
|
const batonPassModifier = this.scene.findModifier(m => m instanceof SwitchEffectTransferModifier
|
||||||
@ -130,7 +132,7 @@ export class SwitchSummonPhase extends SummonPhase {
|
|||||||
* If this switch is passing a Substitute, make the switched Pokemon match the returned Pokemon's state as it left.
|
* If this switch is passing a Substitute, make the switched Pokemon match the returned Pokemon's state as it left.
|
||||||
* Otherwise, clear any persisting tags on the returned Pokemon.
|
* Otherwise, clear any persisting tags on the returned Pokemon.
|
||||||
*/
|
*/
|
||||||
if (this.batonPass) {
|
if (this.switchType === SwitchType.BATON_PASS || this.switchType === SwitchType.SHED_TAIL) {
|
||||||
const substitute = this.lastPokemon.getTag(SubstituteTag);
|
const substitute = this.lastPokemon.getTag(SubstituteTag);
|
||||||
if (substitute) {
|
if (substitute) {
|
||||||
switchedInPokemon.x += this.lastPokemon.getSubstituteOffset()[0];
|
switchedInPokemon.x += this.lastPokemon.getSubstituteOffset()[0];
|
||||||
@ -174,8 +176,13 @@ export class SwitchSummonPhase extends SummonPhase {
|
|||||||
pokemon.battleSummonData.turnCount--;
|
pokemon.battleSummonData.turnCount--;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.batonPass && pokemon) {
|
if (this.switchType === SwitchType.BATON_PASS && pokemon) {
|
||||||
pokemon.transferSummon(this.lastPokemon);
|
pokemon.transferSummon(this.lastPokemon);
|
||||||
|
} else if (this.switchType === SwitchType.SHED_TAIL && pokemon) {
|
||||||
|
const subTag = this.lastPokemon.getTag(SubstituteTag);
|
||||||
|
if (subTag) {
|
||||||
|
pokemon.summonData.tags.push(subTag);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.lastPokemon?.resetSummonData();
|
this.lastPokemon?.resetSummonData();
|
||||||
|
@ -32,7 +32,7 @@ export class TurnInitPhase extends FieldPhase {
|
|||||||
this.scene.unshiftPhase(new GameOverPhase(this.scene));
|
this.scene.unshiftPhase(new GameOverPhase(this.scene));
|
||||||
} else if (allowedPokemon.length >= this.scene.currentBattle.getBattlerCount() || (this.scene.currentBattle.double && !allowedPokemon[0].isActive(true))) {
|
} else if (allowedPokemon.length >= this.scene.currentBattle.getBattlerCount() || (this.scene.currentBattle.double && !allowedPokemon[0].isActive(true))) {
|
||||||
// If there is at least one pokemon in the back that is legal to switch in, force a switch.
|
// If there is at least one pokemon in the back that is legal to switch in, force a switch.
|
||||||
p.switchOut(false);
|
p.switchOut();
|
||||||
} else {
|
} else {
|
||||||
// If there are no pokemon in the back but we're not game overing, just hide the pokemon.
|
// If there are no pokemon in the back but we're not game overing, just hide the pokemon.
|
||||||
// This should only happen in double battles.
|
// This should only happen in double battles.
|
||||||
|
@ -19,6 +19,7 @@ import { TurnEndPhase } from "./turn-end-phase";
|
|||||||
import { WeatherEffectPhase } from "./weather-effect-phase";
|
import { WeatherEffectPhase } from "./weather-effect-phase";
|
||||||
import { BattlerIndex } from "#app/battle";
|
import { BattlerIndex } from "#app/battle";
|
||||||
import { TrickRoomTag } from "#app/data/arena-tag";
|
import { TrickRoomTag } from "#app/data/arena-tag";
|
||||||
|
import { SwitchType } from "#app/enums/switch-type";
|
||||||
|
|
||||||
export class TurnStartPhase extends FieldPhase {
|
export class TurnStartPhase extends FieldPhase {
|
||||||
constructor(scene: BattleScene) {
|
constructor(scene: BattleScene) {
|
||||||
@ -179,7 +180,8 @@ export class TurnStartPhase extends FieldPhase {
|
|||||||
this.scene.unshiftPhase(new AttemptCapturePhase(this.scene, turnCommand.targets![0] % 2, turnCommand.cursor!));//TODO: is the bang correct here?
|
this.scene.unshiftPhase(new AttemptCapturePhase(this.scene, turnCommand.targets![0] % 2, turnCommand.cursor!));//TODO: is the bang correct here?
|
||||||
break;
|
break;
|
||||||
case Command.POKEMON:
|
case Command.POKEMON:
|
||||||
this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, pokemon.getFieldIndex(), turnCommand.cursor!, true, turnCommand.args![0] as boolean, pokemon.isPlayer()));//TODO: is the bang correct here?
|
const switchType = turnCommand.args![0] ? SwitchType.BATON_PASS : SwitchType.SWITCH; // TODO: is the bang correct here?
|
||||||
|
this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, switchType, pokemon.getFieldIndex(), turnCommand.cursor!, true, pokemon.isPlayer()));
|
||||||
break;
|
break;
|
||||||
case Command.RUN:
|
case Command.RUN:
|
||||||
let runningPokemon = pokemon;
|
let runningPokemon = pokemon;
|
||||||
|
@ -18,6 +18,7 @@ import GameManager from "#test/utils/gameManager";
|
|||||||
import Phaser from "phaser";
|
import Phaser from "phaser";
|
||||||
import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest";
|
import { afterEach, beforeAll, beforeEach, describe, expect, test } from "vitest";
|
||||||
import { Status, StatusEffect } from "#app/data/status-effect";
|
import { Status, StatusEffect } from "#app/data/status-effect";
|
||||||
|
import { SwitchType } from "#app/enums/switch-type";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -113,7 +114,7 @@ describe("Abilities - ZEN MODE", () => {
|
|||||||
await game.phaseInterceptor.run(EnemyCommandPhase);
|
await game.phaseInterceptor.run(EnemyCommandPhase);
|
||||||
await game.phaseInterceptor.run(TurnStartPhase);
|
await game.phaseInterceptor.run(TurnStartPhase);
|
||||||
game.onNextPrompt("SwitchPhase", Mode.PARTY, () => {
|
game.onNextPrompt("SwitchPhase", Mode.PARTY, () => {
|
||||||
game.scene.unshiftPhase(new SwitchSummonPhase(game.scene, 0, 1, false, false));
|
game.scene.unshiftPhase(new SwitchSummonPhase(game.scene, SwitchType.SWITCH, 0, 1, false));
|
||||||
game.scene.ui.setMode(Mode.MESSAGE);
|
game.scene.ui.setMode(Mode.MESSAGE);
|
||||||
});
|
});
|
||||||
game.onNextPrompt("SwitchPhase", Mode.MESSAGE, () => {
|
game.onNextPrompt("SwitchPhase", Mode.MESSAGE, () => {
|
||||||
|
56
src/test/moves/shed_tail.test.ts
Normal file
56
src/test/moves/shed_tail.test.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { SubstituteTag } from "#app/data/battler-tags";
|
||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import GameManager from "#test/utils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, it, expect } from "vitest";
|
||||||
|
|
||||||
|
describe("Moves - Shed Tail", () => {
|
||||||
|
let phaserGame: Phaser.Game;
|
||||||
|
let game: GameManager;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
phaserGame = new Phaser.Game({
|
||||||
|
type: Phaser.HEADLESS,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
game.phaseInterceptor.restoreOg();
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
game = new GameManager(phaserGame);
|
||||||
|
game.override
|
||||||
|
.moveset([Moves.SHED_TAIL])
|
||||||
|
.battleType("single")
|
||||||
|
.enemySpecies(Species.SNORLAX)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.enemyMoveset(Moves.SPLASH);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("transfers a Substitute doll to the switched in Pokemon", async () => {
|
||||||
|
await game.classicMode.startBattle([Species.MAGIKARP, Species.FEEBAS]);
|
||||||
|
|
||||||
|
const magikarp = game.scene.getPlayerPokemon()!;
|
||||||
|
|
||||||
|
game.move.select(Moves.SHED_TAIL);
|
||||||
|
game.doSelectPartyPokemon(1);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to("TurnEndPhase", false);
|
||||||
|
|
||||||
|
const feebas = game.scene.getPlayerPokemon()!;
|
||||||
|
const substituteTag = feebas.getTag(SubstituteTag);
|
||||||
|
|
||||||
|
expect(feebas).not.toBe(magikarp);
|
||||||
|
expect(feebas.hp).toBe(feebas.getMaxHp());
|
||||||
|
// Note: Shed Tail's HP cost is currently not accurate to mainline, as it
|
||||||
|
// should cost ceil(maxHP / 2) instead of max(floor(maxHp / 2), 1). The current
|
||||||
|
// implementation is consistent with Substitute's HP cost logic, but that's not
|
||||||
|
// the case in mainline for some reason :regiDespair:.
|
||||||
|
expect(magikarp.hp).toBe(Math.ceil(magikarp.getMaxHp() / 2));
|
||||||
|
expect(substituteTag).toBeDefined();
|
||||||
|
expect(substituteTag?.hp).toBe(Math.floor(magikarp.getMaxHp() / 4));
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user