mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-08-28 18:29:36 +02:00
Merge branch 'beta' into refactor/tests/manual-timeout-set
This commit is contained in:
commit
c1fa7499a8
62
src/data/ability.ts
Executable file → Normal file
62
src/data/ability.ts
Executable file → Normal file
@ -1798,6 +1798,61 @@ export class PostDefendStealHeldItemAbAttr extends PostDefendAbAttr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for defining all {@linkcode Ability} Attributes after a status effect has been set.
|
||||||
|
* @see {@linkcode applyPostSetStatus()}.
|
||||||
|
*/
|
||||||
|
export class PostSetStatusAbAttr extends AbAttr {
|
||||||
|
/**
|
||||||
|
* Does nothing after a status condition is set.
|
||||||
|
* @param pokemon {@linkcode Pokemon} that status condition was set on.
|
||||||
|
* @param sourcePokemon {@linkcode Pokemon} that that set the status condition. Is `null` if status was not set by a Pokemon.
|
||||||
|
* @param passive Whether this ability is a passive.
|
||||||
|
* @param effect {@linkcode StatusEffect} that was set.
|
||||||
|
* @param args Set of unique arguments needed by this attribute.
|
||||||
|
* @returns `true` if application of the ability succeeds.
|
||||||
|
*/
|
||||||
|
applyPostSetStatus(pokemon: Pokemon, sourcePokemon: Pokemon | null = null, passive: boolean, effect: StatusEffect, simulated: boolean, args: any[]) : boolean | Promise<boolean> {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If another Pokemon burns, paralyzes, poisons, or badly poisons this Pokemon,
|
||||||
|
* that Pokemon receives the same non-volatile status condition as part of this
|
||||||
|
* ability attribute. For Synchronize ability.
|
||||||
|
*/
|
||||||
|
export class SynchronizeStatusAbAttr extends PostSetStatusAbAttr {
|
||||||
|
/**
|
||||||
|
* If the `StatusEffect` that was set is Burn, Paralysis, Poison, or Toxic, and the status
|
||||||
|
* was set by a source Pokemon, set the source Pokemon's status to the same `StatusEffect`.
|
||||||
|
* @param pokemon {@linkcode Pokemon} that status condition was set on.
|
||||||
|
* @param sourcePokemon {@linkcode Pokemon} that that set the status condition. Is null if status was not set by a Pokemon.
|
||||||
|
* @param passive Whether this ability is a passive.
|
||||||
|
* @param effect {@linkcode StatusEffect} that was set.
|
||||||
|
* @param args Set of unique arguments needed by this attribute.
|
||||||
|
* @returns `true` if application of the ability succeeds.
|
||||||
|
*/
|
||||||
|
override applyPostSetStatus(pokemon: Pokemon, sourcePokemon: Pokemon | null = null, passive: boolean, effect: StatusEffect, simulated: boolean, args: any[]): boolean {
|
||||||
|
/** Synchronizable statuses */
|
||||||
|
const syncStatuses = new Set<StatusEffect>([
|
||||||
|
StatusEffect.BURN,
|
||||||
|
StatusEffect.PARALYSIS,
|
||||||
|
StatusEffect.POISON,
|
||||||
|
StatusEffect.TOXIC
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (sourcePokemon && syncStatuses.has(effect)) {
|
||||||
|
if (!simulated) {
|
||||||
|
sourcePokemon.trySetStatus(effect, true, pokemon);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class PostVictoryAbAttr extends AbAttr {
|
export class PostVictoryAbAttr extends AbAttr {
|
||||||
applyPostVictory(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise<boolean> {
|
applyPostVictory(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise<boolean> {
|
||||||
return false;
|
return false;
|
||||||
@ -4677,6 +4732,10 @@ export function applyStatMultiplierAbAttrs(attrType: Constructor<StatMultiplierA
|
|||||||
pokemon: Pokemon, stat: BattleStat, statValue: Utils.NumberHolder, simulated: boolean = false, ...args: any[]): Promise<void> {
|
pokemon: Pokemon, stat: BattleStat, statValue: Utils.NumberHolder, simulated: boolean = false, ...args: any[]): Promise<void> {
|
||||||
return applyAbAttrsInternal<StatMultiplierAbAttr>(attrType, pokemon, (attr, passive) => attr.applyStatStage(pokemon, passive, simulated, stat, statValue, args), args);
|
return applyAbAttrsInternal<StatMultiplierAbAttr>(attrType, pokemon, (attr, passive) => attr.applyStatStage(pokemon, passive, simulated, stat, statValue, args), args);
|
||||||
}
|
}
|
||||||
|
export function applyPostSetStatusAbAttrs(attrType: Constructor<PostSetStatusAbAttr>,
|
||||||
|
pokemon: Pokemon, effect: StatusEffect, sourcePokemon?: Pokemon | null, simulated: boolean = false, ...args: any[]): Promise<void> {
|
||||||
|
return applyAbAttrsInternal<PostSetStatusAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPostSetStatus(pokemon, sourcePokemon, passive, effect, simulated, args), args, false, simulated);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies a field Stat multiplier attribute
|
* Applies a field Stat multiplier attribute
|
||||||
@ -4907,7 +4966,8 @@ export function initAbilities() {
|
|||||||
.attr(EffectSporeAbAttr),
|
.attr(EffectSporeAbAttr),
|
||||||
new Ability(Abilities.SYNCHRONIZE, 3)
|
new Ability(Abilities.SYNCHRONIZE, 3)
|
||||||
.attr(SyncEncounterNatureAbAttr)
|
.attr(SyncEncounterNatureAbAttr)
|
||||||
.unimplemented(),
|
.attr(SynchronizeStatusAbAttr)
|
||||||
|
.partial(), // interaction with psycho shift needs work, keeping to old Gen interaction for now
|
||||||
new Ability(Abilities.CLEAR_BODY, 3)
|
new Ability(Abilities.CLEAR_BODY, 3)
|
||||||
.attr(ProtectStatAbAttr)
|
.attr(ProtectStatAbAttr)
|
||||||
.ignorable(),
|
.ignorable(),
|
||||||
|
@ -650,7 +650,7 @@ export default class Move implements Localizable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies each {@linkcode MoveCondition} of this move to the params
|
* Applies each {@linkcode MoveCondition} function of this move to the params, determines if the move can be used prior to calling each attribute's apply()
|
||||||
* @param user {@linkcode Pokemon} to apply conditions to
|
* @param user {@linkcode Pokemon} to apply conditions to
|
||||||
* @param target {@linkcode Pokemon} to apply conditions to
|
* @param target {@linkcode Pokemon} to apply conditions to
|
||||||
* @param move {@linkcode Move} to apply conditions to
|
* @param move {@linkcode Move} to apply conditions to
|
||||||
@ -2091,21 +2091,20 @@ export class PsychoShiftEffectAttr extends MoveEffectAttr {
|
|||||||
|
|
||||||
if (target.status) {
|
if (target.status) {
|
||||||
return false;
|
return false;
|
||||||
}
|
} else {
|
||||||
//@ts-ignore - how can target.status.effect be checked when we return `false` before when it's defined?
|
const canSetStatus = target.canSetStatus(statusToApply, true, false, user);
|
||||||
if (!target.status || (target.status.effect === statusToApply && move.chance < 0)) { // TODO: resolve ts-ignore
|
|
||||||
const statusAfflictResult = target.trySetStatus(statusToApply, true, user);
|
if (canSetStatus) {
|
||||||
if (statusAfflictResult) {
|
|
||||||
if (user.status) {
|
if (user.status) {
|
||||||
user.scene.queueMessage(getStatusEffectHealText(user.status.effect, getPokemonNameWithAffix(user)));
|
user.scene.queueMessage(getStatusEffectHealText(user.status.effect, getPokemonNameWithAffix(user)));
|
||||||
}
|
}
|
||||||
user.resetStatus();
|
user.resetStatus();
|
||||||
user.updateInfo();
|
user.updateInfo();
|
||||||
|
target.trySetStatus(statusToApply, true, user);
|
||||||
}
|
}
|
||||||
return statusAfflictResult;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return canSetStatus;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
getTargetBenefitScore(user: Pokemon, target: Pokemon, move: Move): number {
|
||||||
@ -5296,6 +5295,21 @@ export class ForceSwitchOutAttr extends MoveEffectAttr {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export class ChillyReceptionAttr extends ForceSwitchOutAttr {
|
||||||
|
|
||||||
|
// using inherited constructor
|
||||||
|
|
||||||
|
apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): Promise<boolean> {
|
||||||
|
user.scene.arena.trySetWeather(WeatherType.SNOW, true);
|
||||||
|
return super.apply(user, target, move, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
getCondition(): MoveConditionFunc {
|
||||||
|
// chilly reception move will go through if the weather is change-able to snow, or the user can switch out, else move will fail
|
||||||
|
return (user, target, move) => user.scene.arena.trySetWeather(WeatherType.SNOW, true) || super.getSwitchOutCondition()(user, target, move);
|
||||||
|
}
|
||||||
|
}
|
||||||
export class RemoveTypeAttr extends MoveEffectAttr {
|
export class RemoveTypeAttr extends MoveEffectAttr {
|
||||||
|
|
||||||
private removedType: Type;
|
private removedType: Type;
|
||||||
@ -9485,10 +9499,9 @@ export function initMoves() {
|
|||||||
.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(),
|
.unimplemented(),
|
||||||
new StatusMove(Moves.CHILLY_RECEPTION, Type.ICE, -1, 10, -1, 0, 9)
|
new SelfStatusMove(Moves.CHILLY_RECEPTION, Type.ICE, -1, 10, -1, 0, 9)
|
||||||
.attr(WeatherChangeAttr, WeatherType.SNOW)
|
.attr(PreMoveMessageAttr, (user, move) => i18next.t("moveTriggers:chillyReception", {pokemonName: getPokemonNameWithAffix(user)}))
|
||||||
.attr(ForceSwitchOutAttr, true, false)
|
.attr(ChillyReceptionAttr, true, false),
|
||||||
.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)
|
||||||
.attr(RemoveArenaTrapAttr, true)
|
.attr(RemoveArenaTrapAttr, true)
|
||||||
|
@ -33,6 +33,7 @@ export class Arena {
|
|||||||
public tags: ArenaTag[];
|
public tags: ArenaTag[];
|
||||||
public bgm: string;
|
public bgm: string;
|
||||||
public ignoreAbilities: boolean;
|
public ignoreAbilities: boolean;
|
||||||
|
public ignoringEffectSource: BattlerIndex | null;
|
||||||
|
|
||||||
private lastTimeOfDay: TimeOfDay;
|
private lastTimeOfDay: TimeOfDay;
|
||||||
|
|
||||||
@ -569,8 +570,9 @@ export class Arena {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setIgnoreAbilities(ignoreAbilities: boolean = true): void {
|
setIgnoreAbilities(ignoreAbilities: boolean, ignoringEffectSource: BattlerIndex | null = null): void {
|
||||||
this.ignoreAbilities = ignoreAbilities;
|
this.ignoreAbilities = ignoreAbilities;
|
||||||
|
this.ignoringEffectSource = ignoreAbilities ? ignoringEffectSource : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -20,7 +20,7 @@ import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "../data/tms";
|
|||||||
import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, SubstituteTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, MoveRestrictionBattlerTag, ExposedTag, DragonCheerTag, CritBoostTag, TrappedTag, TarShotTag } from "../data/battler-tags";
|
import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, SubstituteTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, MoveRestrictionBattlerTag, ExposedTag, DragonCheerTag, CritBoostTag, TrappedTag, TarShotTag } from "../data/battler-tags";
|
||||||
import { WeatherType } from "../data/weather";
|
import { WeatherType } from "../data/weather";
|
||||||
import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "../data/arena-tag";
|
import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "../data/arena-tag";
|
||||||
import { Ability, AbAttr, StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldStatMultiplierAbAttrs, FieldMultiplyStatAbAttr, AddSecondStrikeAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, CheckTrappedAbAttr } from "../data/ability";
|
import { Ability, AbAttr, StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, ReduceStatusEffectDurationAbAttr, StabBoostAbAttr, StatusEffectImmunityAbAttr, TypeImmunityAbAttr, WeightMultiplierAbAttr, allAbilities, applyAbAttrs, applyStatMultiplierAbAttrs, applyPreApplyBattlerTagAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreSetStatusAbAttrs, UnsuppressableAbilityAbAttr, SuppressFieldAbilitiesAbAttr, NoFusionAbilityAbAttr, MultCritAbAttr, IgnoreTypeImmunityAbAttr, DamageBoostAbAttr, IgnoreTypeStatusEffectImmunityAbAttr, ConditionalCritAbAttr, applyFieldStatMultiplierAbAttrs, FieldMultiplyStatAbAttr, AddSecondStrikeAbAttr, UserFieldStatusEffectImmunityAbAttr, UserFieldBattlerTagImmunityAbAttr, BattlerTagImmunityAbAttr, MoveTypeChangeAbAttr, FullHpResistTypeAbAttr, applyCheckTrappedAbAttrs, CheckTrappedAbAttr, PostSetStatusAbAttr, applyPostSetStatusAbAttrs } from "../data/ability";
|
||||||
import PokemonData from "../system/pokemon-data";
|
import PokemonData from "../system/pokemon-data";
|
||||||
import { BattlerIndex } from "../battle";
|
import { BattlerIndex } from "../battle";
|
||||||
import { Mode } from "../ui/ui";
|
import { Mode } from "../ui/ui";
|
||||||
@ -1364,7 +1364,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
if (this.isFusion() && ability.hasAttr(NoFusionAbilityAbAttr)) {
|
if (this.isFusion() && ability.hasAttr(NoFusionAbilityAbAttr)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (this.scene?.arena.ignoreAbilities && ability.isIgnorable) {
|
const arena = this.scene?.arena;
|
||||||
|
if (arena.ignoreAbilities && arena.ignoringEffectSource !== this.getBattlerIndex() && ability.isIgnorable) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (this.summonData?.abilitySuppressed && !ability.hasAttr(UnsuppressableAbilityAbAttr)) {
|
if (this.summonData?.abilitySuppressed && !ability.hasAttr(UnsuppressableAbilityAbAttr)) {
|
||||||
@ -3365,7 +3366,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (asPhase) {
|
if (asPhase) {
|
||||||
this.scene.unshiftPhase(new ObtainStatusEffectPhase(this.scene, this.getBattlerIndex(), effect, cureTurn, sourceText!, sourcePokemon!)); // TODO: are these bangs correct?
|
this.scene.unshiftPhase(new ObtainStatusEffectPhase(this.scene, this.getBattlerIndex(), effect, cureTurn, sourceText, sourcePokemon));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3399,6 +3400,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
|||||||
|
|
||||||
if (effect !== StatusEffect.FAINT) {
|
if (effect !== StatusEffect.FAINT) {
|
||||||
this.scene.triggerPokemonFormChange(this, SpeciesFormChangeStatusEffectTrigger, true);
|
this.scene.triggerPokemonFormChange(this, SpeciesFormChangeStatusEffectTrigger, true);
|
||||||
|
applyPostSetStatusAbAttrs(PostSetStatusAbAttr, this, effect, sourcePokemon);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -65,6 +65,7 @@
|
|||||||
"suppressAbilities": "Die Fähigkeit von {{pokemonName}} wirkt nicht mehr!",
|
"suppressAbilities": "Die Fähigkeit von {{pokemonName}} wirkt nicht mehr!",
|
||||||
"revivalBlessing": "{{pokemonName}} ist wieder fit und kampfbereit!",
|
"revivalBlessing": "{{pokemonName}} ist wieder fit und kampfbereit!",
|
||||||
"swapArenaTags": "{{pokemonName}} hat die Effekte, die auf den beiden Seiten des Kampffeldes wirken, miteinander getauscht!",
|
"swapArenaTags": "{{pokemonName}} hat die Effekte, die auf den beiden Seiten des Kampffeldes wirken, miteinander getauscht!",
|
||||||
|
"chillyReception": "{{pokemonName}} erzählt einen schlechten Witz, der nicht besonders gut ankommt...",
|
||||||
"exposedMove": "{{pokemonName}} erkennt {{targetPokemonName}}!",
|
"exposedMove": "{{pokemonName}} erkennt {{targetPokemonName}}!",
|
||||||
"safeguard": "{{targetName}} wird durch Bodyguard geschützt!",
|
"safeguard": "{{targetName}} wird durch Bodyguard geschützt!",
|
||||||
"afterYou": "{{targetName}} lässt sich auf Galanterie ein!"
|
"afterYou": "{{targetName}} lässt sich auf Galanterie ein!"
|
||||||
|
@ -66,6 +66,7 @@
|
|||||||
"suppressAbilities": "{{pokemonName}}'s ability\nwas suppressed!",
|
"suppressAbilities": "{{pokemonName}}'s ability\nwas suppressed!",
|
||||||
"revivalBlessing": "{{pokemonName}} was revived!",
|
"revivalBlessing": "{{pokemonName}} was revived!",
|
||||||
"swapArenaTags": "{{pokemonName}} swapped the battle effects affecting each side of the field!",
|
"swapArenaTags": "{{pokemonName}} swapped the battle effects affecting each side of the field!",
|
||||||
|
"chillyReception": "{{pokemonName}} is preparing to tell a chillingly bad joke!",
|
||||||
"exposedMove": "{{pokemonName}} identified\n{{targetPokemonName}}!",
|
"exposedMove": "{{pokemonName}} identified\n{{targetPokemonName}}!",
|
||||||
"safeguard": "{{targetName}} is protected by Safeguard!",
|
"safeguard": "{{targetName}} is protected by Safeguard!",
|
||||||
"substituteOnOverlap": "{{pokemonName}} already\nhas a substitute!",
|
"substituteOnOverlap": "{{pokemonName}} already\nhas a substitute!",
|
||||||
|
@ -66,6 +66,7 @@
|
|||||||
"suppressAbilities": "Le talent de {{pokemonName}}\na été rendu inactif !",
|
"suppressAbilities": "Le talent de {{pokemonName}}\na été rendu inactif !",
|
||||||
"revivalBlessing": "{{pokemonName}} a repris connaissance\net est prêt à se battre de nouveau !",
|
"revivalBlessing": "{{pokemonName}} a repris connaissance\net est prêt à se battre de nouveau !",
|
||||||
"swapArenaTags": "Les effets affectant chaque côté du terrain\nont été échangés par {{pokemonName}} !",
|
"swapArenaTags": "Les effets affectant chaque côté du terrain\nont été échangés par {{pokemonName}} !",
|
||||||
|
"chillyReception": "{{pokemonName}} s’apprête\nà faire un mauvais jeu de mots…",
|
||||||
"exposedMove": "{{targetPokemonName}} est identifié\npar {{pokemonName}} !",
|
"exposedMove": "{{targetPokemonName}} est identifié\npar {{pokemonName}} !",
|
||||||
"safeguard": "{{targetName}} est protégé\npar la capacité Rune Protect !",
|
"safeguard": "{{targetName}} est protégé\npar la capacité Rune Protect !",
|
||||||
"substituteOnOverlap": "{{pokemonName}} a déjà\nun clone !",
|
"substituteOnOverlap": "{{pokemonName}} a déjà\nun clone !",
|
||||||
|
@ -66,6 +66,7 @@
|
|||||||
"revivalBlessing": "{{pokemonName}} torna in forze!",
|
"revivalBlessing": "{{pokemonName}} torna in forze!",
|
||||||
"swapArenaTags": "{{pokemonName}} ha invertito gli effetti attivi\nnelle due metà del campo!",
|
"swapArenaTags": "{{pokemonName}} ha invertito gli effetti attivi\nnelle due metà del campo!",
|
||||||
"exposedMove": "{{pokemonName}} ha identificato\n{{targetPokemonName}}!",
|
"exposedMove": "{{pokemonName}} ha identificato\n{{targetPokemonName}}!",
|
||||||
|
"chillyReception": "{{pokemonName}} sta per fare una battuta!",
|
||||||
"safeguard": "Salvaguardia protegge {{targetName}}!",
|
"safeguard": "Salvaguardia protegge {{targetName}}!",
|
||||||
"afterYou": "{{pokemonName}} approfitta della cortesia!"
|
"afterYou": "{{pokemonName}} approfitta della cortesia!"
|
||||||
}
|
}
|
||||||
|
@ -64,6 +64,8 @@
|
|||||||
"copyType": "{{pokemonName}}は {{targetPokemonName}}と\n同じタイプに なった!",
|
"copyType": "{{pokemonName}}は {{targetPokemonName}}と\n同じタイプに なった!",
|
||||||
"suppressAbilities": "{{pokemonName}}の 特性が 効かなくなった!",
|
"suppressAbilities": "{{pokemonName}}の 特性が 効かなくなった!",
|
||||||
"revivalBlessing": "{{pokemonName}}は\n復活して 戦えるようになった!",
|
"revivalBlessing": "{{pokemonName}}は\n復活して 戦えるようになった!",
|
||||||
|
"swapArenaTags": "{{pokemonName}}は\nお互いの 場の効果を 入れ替えた!",
|
||||||
|
"chillyReception": "{{pokemonName}}は\n寒い ギャグを かました!",
|
||||||
"swapArenaTags": "{{pokemonName}}は\nお互いの 場の 効果を 入れ替えた!",
|
"swapArenaTags": "{{pokemonName}}は\nお互いの 場の 効果を 入れ替えた!",
|
||||||
"exposedMove": "{{pokemonName}}は {{targetPokemonName}}の\n正体を 見破った!",
|
"exposedMove": "{{pokemonName}}は {{targetPokemonName}}の\n正体を 見破った!",
|
||||||
"afterYou": "{{pokemonName}}は\nお言葉に 甘えることにした!"
|
"afterYou": "{{pokemonName}}は\nお言葉に 甘えることにした!"
|
||||||
|
@ -66,6 +66,7 @@
|
|||||||
"suppressAbilities": "{{pokemonName}}의\n특성이 효과를 발휘하지 못하게 되었다!",
|
"suppressAbilities": "{{pokemonName}}의\n특성이 효과를 발휘하지 못하게 되었다!",
|
||||||
"revivalBlessing": "{{pokemonName}}[[는]]\n정신을 차려 싸울 수 있게 되었다!",
|
"revivalBlessing": "{{pokemonName}}[[는]]\n정신을 차려 싸울 수 있게 되었다!",
|
||||||
"swapArenaTags": "{{pokemonName}}[[는]]\n서로의 필드 효과를 교체했다!",
|
"swapArenaTags": "{{pokemonName}}[[는]]\n서로의 필드 효과를 교체했다!",
|
||||||
|
"chillyReception": "{{pokemonName}}[[는]] 썰렁한 개그를 선보였다!",
|
||||||
"exposedMove": "{{pokemonName}}[[는]]\n{{targetPokemonName}}의 정체를 꿰뚫어 보았다!",
|
"exposedMove": "{{pokemonName}}[[는]]\n{{targetPokemonName}}의 정체를 꿰뚫어 보았다!",
|
||||||
"safeguard": "{{targetName}}[[는]] 신비의 베일이 지켜 주고 있다!",
|
"safeguard": "{{targetName}}[[는]] 신비의 베일이 지켜 주고 있다!",
|
||||||
"afterYou": "{{pokemonName}}[[는]]\n배려를 받아들이기로 했다!"
|
"afterYou": "{{pokemonName}}[[는]]\n배려를 받아들이기로 했다!"
|
||||||
|
@ -61,6 +61,7 @@
|
|||||||
"suppressAbilities": "A habilidade de {{pokemonName}}\nfoi suprimida!",
|
"suppressAbilities": "A habilidade de {{pokemonName}}\nfoi suprimida!",
|
||||||
"revivalBlessing": "{{pokemonName}} foi reanimado!",
|
"revivalBlessing": "{{pokemonName}} foi reanimado!",
|
||||||
"swapArenaTags": "{{pokemonName}} trocou os efeitos de batalha que afetam cada lado do campo!",
|
"swapArenaTags": "{{pokemonName}} trocou os efeitos de batalha que afetam cada lado do campo!",
|
||||||
|
"chillyReception": "{{pokemonName}} está prestes a contar uma piada gelada!",
|
||||||
"exposedMove": "{{pokemonName}} identificou\n{{targetPokemonName}}!",
|
"exposedMove": "{{pokemonName}} identificou\n{{targetPokemonName}}!",
|
||||||
"safeguard": "{{targetName}} está protegido por Safeguard!",
|
"safeguard": "{{targetName}} está protegido por Safeguard!",
|
||||||
"afterYou": "{{pokemonName}} aceitou a gentil oferta!"
|
"afterYou": "{{pokemonName}} aceitou a gentil oferta!"
|
||||||
|
@ -65,6 +65,7 @@
|
|||||||
"suppressAbilities": "{{pokemonName}}的特性\n变得无效了!",
|
"suppressAbilities": "{{pokemonName}}的特性\n变得无效了!",
|
||||||
"revivalBlessing": "{{pokemonName}}复活了!",
|
"revivalBlessing": "{{pokemonName}}复活了!",
|
||||||
"swapArenaTags": "{{pokemonName}}\n交换了双方的场地效果!",
|
"swapArenaTags": "{{pokemonName}}\n交换了双方的场地效果!",
|
||||||
|
"chillyReception": "{{pokemonName}}\n说出了冷笑话!",
|
||||||
"exposedMove": "{{pokemonName}}识破了\n{{targetPokemonName}}的原型!",
|
"exposedMove": "{{pokemonName}}识破了\n{{targetPokemonName}}的原型!",
|
||||||
"safeguard": "{{targetName}}\n正受到神秘之幕的保护!",
|
"safeguard": "{{targetName}}\n正受到神秘之幕的保护!",
|
||||||
"afterYou": "{{pokemonName}}\n接受了对手的好意!"
|
"afterYou": "{{pokemonName}}\n接受了对手的好意!"
|
||||||
|
@ -65,6 +65,7 @@
|
|||||||
"suppressAbilities": "{{pokemonName}}的特性\n變得無效了!",
|
"suppressAbilities": "{{pokemonName}}的特性\n變得無效了!",
|
||||||
"revivalBlessing": "{{pokemonName}}復活了!",
|
"revivalBlessing": "{{pokemonName}}復活了!",
|
||||||
"swapArenaTags": "{{pokemonName}}\n交換了雙方的場地效果!",
|
"swapArenaTags": "{{pokemonName}}\n交換了雙方的場地效果!",
|
||||||
|
"chillyReception": "{{pokemonName}}\n說了冷笑話!",
|
||||||
"exposedMove": "{{pokemonName}}識破了\n{{targetPokemonName}}的原形!",
|
"exposedMove": "{{pokemonName}}識破了\n{{targetPokemonName}}的原形!",
|
||||||
"safeguard": "{{targetName}}\n正受到神秘之幕的保護!",
|
"safeguard": "{{targetName}}\n正受到神秘之幕的保護!",
|
||||||
"afterYou": "{{pokemonName}}\n接受了對手的好意!"
|
"afterYou": "{{pokemonName}}\n接受了對手的好意!"
|
||||||
|
@ -74,7 +74,7 @@ export class MovePhase extends BattlePhase {
|
|||||||
|
|
||||||
if (!this.followUp) {
|
if (!this.followUp) {
|
||||||
if (this.move.getMove().checkFlag(MoveFlags.IGNORE_ABILITIES, this.pokemon, null)) {
|
if (this.move.getMove().checkFlag(MoveFlags.IGNORE_ABILITIES, this.pokemon, null)) {
|
||||||
this.scene.arena.setIgnoreAbilities();
|
this.scene.arena.setIgnoreAbilities(true, this.pokemon.getBattlerIndex());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.pokemon.turnData.hitsLeft = 0; // TODO: is `0` correct?
|
this.pokemon.turnData.hitsLeft = 0; // TODO: is `0` correct?
|
||||||
|
@ -9,26 +9,26 @@ import { PokemonPhase } from "./pokemon-phase";
|
|||||||
import { PostTurnStatusEffectPhase } from "./post-turn-status-effect-phase";
|
import { PostTurnStatusEffectPhase } from "./post-turn-status-effect-phase";
|
||||||
|
|
||||||
export class ObtainStatusEffectPhase extends PokemonPhase {
|
export class ObtainStatusEffectPhase extends PokemonPhase {
|
||||||
private statusEffect: StatusEffect | undefined;
|
private statusEffect?: StatusEffect | undefined;
|
||||||
private cureTurn: integer | null;
|
private cureTurn?: integer | null;
|
||||||
private sourceText: string | null;
|
private sourceText?: string | null;
|
||||||
private sourcePokemon: Pokemon | null;
|
private sourcePokemon?: Pokemon | null;
|
||||||
|
|
||||||
constructor(scene: BattleScene, battlerIndex: BattlerIndex, statusEffect?: StatusEffect, cureTurn?: integer | null, sourceText?: string, sourcePokemon?: Pokemon) {
|
constructor(scene: BattleScene, battlerIndex: BattlerIndex, statusEffect?: StatusEffect, cureTurn?: integer | null, sourceText?: string | null, sourcePokemon?: Pokemon | null) {
|
||||||
super(scene, battlerIndex);
|
super(scene, battlerIndex);
|
||||||
|
|
||||||
this.statusEffect = statusEffect;
|
this.statusEffect = statusEffect;
|
||||||
this.cureTurn = cureTurn!; // TODO: is this bang correct?
|
this.cureTurn = cureTurn;
|
||||||
this.sourceText = sourceText!; // TODO: is this bang correct?
|
this.sourceText = sourceText;
|
||||||
this.sourcePokemon = sourcePokemon!; // For tracking which Pokemon caused the status effect // TODO: is this bang correct?
|
this.sourcePokemon = sourcePokemon; // For tracking which Pokemon caused the status effect
|
||||||
}
|
}
|
||||||
|
|
||||||
start() {
|
start() {
|
||||||
const pokemon = this.getPokemon();
|
const pokemon = this.getPokemon();
|
||||||
if (!pokemon?.status) {
|
if (pokemon && !pokemon.status) {
|
||||||
if (pokemon?.trySetStatus(this.statusEffect, false, this.sourcePokemon)) {
|
if (pokemon.trySetStatus(this.statusEffect, false, this.sourcePokemon)) {
|
||||||
if (this.cureTurn) {
|
if (this.cureTurn) {
|
||||||
pokemon.status!.cureTurn = this.cureTurn; // TODO: is this bang correct?
|
pokemon.status!.cureTurn = this.cureTurn; // TODO: is this bang correct?
|
||||||
}
|
}
|
||||||
pokemon.updateInfo(true);
|
pokemon.updateInfo(true);
|
||||||
new CommonBattleAnim(CommonAnim.POISON + (this.statusEffect! - 1), pokemon).play(this.scene, false, () => {
|
new CommonBattleAnim(CommonAnim.POISON + (this.statusEffect! - 1), pokemon).play(this.scene, false, () => {
|
||||||
@ -40,8 +40,8 @@ export class ObtainStatusEffectPhase extends PokemonPhase {
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else if (pokemon.status.effect === this.statusEffect) {
|
} else if (pokemon.status?.effect === this.statusEffect) {
|
||||||
this.scene.queueMessage(getStatusEffectOverlapText(this.statusEffect, getPokemonNameWithAffix(pokemon)));
|
this.scene.queueMessage(getStatusEffectOverlapText(this.statusEffect ?? StatusEffect.NONE, getPokemonNameWithAffix(pokemon)));
|
||||||
}
|
}
|
||||||
this.end();
|
this.end();
|
||||||
}
|
}
|
||||||
|
109
src/test/abilities/synchronize.test.ts
Normal file
109
src/test/abilities/synchronize.test.ts
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
import { StatusEffect } from "#app/data/status-effect";
|
||||||
|
import GameManager from "#app/test/utils/gameManager";
|
||||||
|
import { Abilities } from "#enums/abilities";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
describe("Abilities - Synchronize", () => {
|
||||||
|
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
|
||||||
|
.battleType("single")
|
||||||
|
.startingLevel(100)
|
||||||
|
.enemySpecies(Species.MAGIKARP)
|
||||||
|
.enemyAbility(Abilities.SYNCHRONIZE)
|
||||||
|
.moveset([Moves.SPLASH, Moves.THUNDER_WAVE, Moves.SPORE, Moves.PSYCHO_SHIFT])
|
||||||
|
.ability(Abilities.NO_GUARD);
|
||||||
|
}, 20000);
|
||||||
|
|
||||||
|
it("does not trigger when no status is applied by opponent Pokemon", async () => {
|
||||||
|
await game.classicMode.startBattle([Species.FEEBAS]);
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(game.scene.getParty()[0].status).toBeUndefined();
|
||||||
|
expect(game.phaseInterceptor.log).not.toContain("ShowAbilityPhase");
|
||||||
|
}, 20000);
|
||||||
|
|
||||||
|
it("sets the status of the source pokemon to Paralysis when paralyzed by it", async () => {
|
||||||
|
await game.classicMode.startBattle([Species.FEEBAS]);
|
||||||
|
|
||||||
|
game.move.select(Moves.THUNDER_WAVE);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(game.scene.getParty()[0].status?.effect).toBe(StatusEffect.PARALYSIS);
|
||||||
|
expect(game.scene.getEnemyParty()[0].status?.effect).toBe(StatusEffect.PARALYSIS);
|
||||||
|
expect(game.phaseInterceptor.log).toContain("ShowAbilityPhase");
|
||||||
|
}, 20000);
|
||||||
|
|
||||||
|
it("does not trigger on Sleep", async () => {
|
||||||
|
await game.classicMode.startBattle();
|
||||||
|
|
||||||
|
game.move.select(Moves.SPORE);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(game.scene.getParty()[0].status?.effect).toBeUndefined();
|
||||||
|
expect(game.scene.getEnemyParty()[0].status?.effect).toBe(StatusEffect.SLEEP);
|
||||||
|
expect(game.phaseInterceptor.log).not.toContain("ShowAbilityPhase");
|
||||||
|
}, 20000);
|
||||||
|
|
||||||
|
it("does not trigger when Pokemon is statused by Toxic Spikes", async () => {
|
||||||
|
game.override
|
||||||
|
.ability(Abilities.SYNCHRONIZE)
|
||||||
|
.enemyAbility(Abilities.BALL_FETCH)
|
||||||
|
.enemyMoveset(Array(4).fill(Moves.TOXIC_SPIKES));
|
||||||
|
|
||||||
|
await game.classicMode.startBattle([Species.FEEBAS, Species.MILOTIC]);
|
||||||
|
|
||||||
|
game.move.select(Moves.SPLASH);
|
||||||
|
await game.toNextTurn();
|
||||||
|
|
||||||
|
game.doSwitchPokemon(1);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(game.scene.getParty()[0].status?.effect).toBe(StatusEffect.POISON);
|
||||||
|
expect(game.scene.getEnemyParty()[0].status?.effect).toBeUndefined();
|
||||||
|
expect(game.phaseInterceptor.log).not.toContain("ShowAbilityPhase");
|
||||||
|
}, 20000);
|
||||||
|
|
||||||
|
it("shows ability even if it fails to set the status of the opponent Pokemon", async () => {
|
||||||
|
await game.classicMode.startBattle([Species.PIKACHU]);
|
||||||
|
|
||||||
|
game.move.select(Moves.THUNDER_WAVE);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(game.scene.getParty()[0].status?.effect).toBeUndefined();
|
||||||
|
expect(game.scene.getEnemyParty()[0].status?.effect).toBe(StatusEffect.PARALYSIS);
|
||||||
|
expect(game.phaseInterceptor.log).toContain("ShowAbilityPhase");
|
||||||
|
}, 20000);
|
||||||
|
|
||||||
|
it("should activate with Psycho Shift after the move clears the status", async () => {
|
||||||
|
game.override.statusEffect(StatusEffect.PARALYSIS);
|
||||||
|
await game.classicMode.startBattle();
|
||||||
|
|
||||||
|
game.move.select(Moves.PSYCHO_SHIFT);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase");
|
||||||
|
|
||||||
|
expect(game.scene.getParty()[0].status?.effect).toBe(StatusEffect.PARALYSIS); // keeping old gen < V impl for now since it's buggy otherwise
|
||||||
|
expect(game.scene.getEnemyParty()[0].status?.effect).toBe(StatusEffect.PARALYSIS);
|
||||||
|
expect(game.phaseInterceptor.log).toContain("ShowAbilityPhase");
|
||||||
|
}, 20000);
|
||||||
|
});
|
71
src/test/moves/chilly_reception.test.ts
Normal file
71
src/test/moves/chilly_reception.test.ts
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import { Abilities } from "#app/enums/abilities";
|
||||||
|
import { Moves } from "#enums/moves";
|
||||||
|
import { Species } from "#enums/species";
|
||||||
|
import { WeatherType } from "#enums/weather-type";
|
||||||
|
import GameManager from "#test/utils/gameManager";
|
||||||
|
import Phaser from "phaser";
|
||||||
|
import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
const TIMEOUT = 20 * 1000;
|
||||||
|
|
||||||
|
describe("Moves - Chilly Reception", () => {
|
||||||
|
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.battleType("single")
|
||||||
|
.moveset([Moves.CHILLY_RECEPTION, Moves.SNOWSCAPE])
|
||||||
|
.enemyMoveset(Array(4).fill(Moves.SPLASH))
|
||||||
|
.enemyAbility(Abilities.NONE)
|
||||||
|
.ability(Abilities.NONE);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should still change the weather if user can't switch out", async () => {
|
||||||
|
await game.classicMode.startBattle([Species.SLOWKING]);
|
||||||
|
|
||||||
|
game.move.select(Moves.CHILLY_RECEPTION);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to("BerryPhase", false);
|
||||||
|
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SNOW);
|
||||||
|
}, TIMEOUT);
|
||||||
|
|
||||||
|
it("should switch out even if it's snowing", async () => {
|
||||||
|
await game.classicMode.startBattle([Species.SLOWKING, Species.MEOWTH]);
|
||||||
|
// first turn set up snow with snowscape, try chilly reception on second turn
|
||||||
|
game.move.select(Moves.SNOWSCAPE);
|
||||||
|
await game.phaseInterceptor.to("BerryPhase", false);
|
||||||
|
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SNOW);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to("TurnInitPhase", false);
|
||||||
|
game.move.select(Moves.CHILLY_RECEPTION);
|
||||||
|
game.doSelectPartyPokemon(1);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to("BerryPhase", false);
|
||||||
|
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SNOW);
|
||||||
|
expect(game.scene.getPlayerField()[0].species.speciesId).toBe(Species.MEOWTH);
|
||||||
|
}, TIMEOUT);
|
||||||
|
|
||||||
|
it("happy case - switch out and weather changes", async () => {
|
||||||
|
|
||||||
|
await game.classicMode.startBattle([Species.SLOWKING, Species.MEOWTH]);
|
||||||
|
|
||||||
|
game.move.select(Moves.CHILLY_RECEPTION);
|
||||||
|
game.doSelectPartyPokemon(1);
|
||||||
|
|
||||||
|
await game.phaseInterceptor.to("BerryPhase", false);
|
||||||
|
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.SNOW);
|
||||||
|
expect(game.scene.getPlayerField()[0].species.speciesId).toBe(Species.MEOWTH);
|
||||||
|
}, TIMEOUT);
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user