mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-02-21 03:37:28 +00:00
[Refactor/Bug] Pokemon.leaveField()
, Fix Related Abilities (#5191)
* Added new AbAttr that triggers whenever a pokemon leaves the field * Use leaveField everywhere * Changing order for PreSwitchOutAbAttr * Don't clearEffects when catching in a mystery encounter * Attempts to make new overrides for testing * New options in overrides * Implemented tests for Desolate Land * Fixing instruct test to not read turnData of fainted mon * Removed post faint clear weather * Apply suggestions from code review Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Has_passive_ability override now turns off passives if set to "false", defaults to "null" * Updating overrides type definitions * Apply suggestions from code review Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Suggestions from review * Fixed strings in suggestions * Simplified function to throw balls in tests * Added tsdocs to overrideHelper.ts --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
This commit is contained in:
parent
60990deaf2
commit
6c4dedb73e
@ -2643,55 +2643,6 @@ export class PreSwitchOutResetStatusAbAttr extends PreSwitchOutAbAttr {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears Desolate Land/Primordial Sea/Delta Stream upon the Pokemon switching out.
|
||||
*/
|
||||
export class PreSwitchOutClearWeatherAbAttr extends PreSwitchOutAbAttr {
|
||||
|
||||
/**
|
||||
* @param pokemon The {@linkcode Pokemon} with the ability
|
||||
* @param passive N/A
|
||||
* @param args N/A
|
||||
* @returns {boolean} Returns true if the weather clears, otherwise false.
|
||||
*/
|
||||
applyPreSwitchOut(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise<boolean> {
|
||||
const weatherType = globalScene.arena.weather?.weatherType;
|
||||
let turnOffWeather = false;
|
||||
|
||||
// Clear weather only if user's ability matches the weather and no other pokemon has the ability.
|
||||
switch (weatherType) {
|
||||
case (WeatherType.HARSH_SUN):
|
||||
if (pokemon.hasAbility(Abilities.DESOLATE_LAND)
|
||||
&& globalScene.getField(true).filter(p => p !== pokemon).filter(p => p.hasAbility(Abilities.DESOLATE_LAND)).length === 0) {
|
||||
turnOffWeather = true;
|
||||
}
|
||||
break;
|
||||
case (WeatherType.HEAVY_RAIN):
|
||||
if (pokemon.hasAbility(Abilities.PRIMORDIAL_SEA)
|
||||
&& globalScene.getField(true).filter(p => p !== pokemon).filter(p => p.hasAbility(Abilities.PRIMORDIAL_SEA)).length === 0) {
|
||||
turnOffWeather = true;
|
||||
}
|
||||
break;
|
||||
case (WeatherType.STRONG_WINDS):
|
||||
if (pokemon.hasAbility(Abilities.DELTA_STREAM)
|
||||
&& globalScene.getField(true).filter(p => p !== pokemon).filter(p => p.hasAbility(Abilities.DELTA_STREAM)).length === 0) {
|
||||
turnOffWeather = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (simulated) {
|
||||
return turnOffWeather;
|
||||
}
|
||||
|
||||
if (turnOffWeather) {
|
||||
globalScene.arena.trySetWeather(WeatherType.NONE, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export class PreSwitchOutHealAbAttr extends PreSwitchOutAbAttr {
|
||||
applyPreSwitchOut(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise<boolean> {
|
||||
@ -2744,6 +2695,61 @@ export class PreSwitchOutFormChangeAbAttr extends PreSwitchOutAbAttr {
|
||||
|
||||
}
|
||||
|
||||
export class PreLeaveFieldAbAttr extends AbAttr {
|
||||
applyPreLeaveField(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise<boolean> {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears Desolate Land/Primordial Sea/Delta Stream upon the Pokemon switching out.
|
||||
*/
|
||||
export class PreLeaveFieldClearWeatherAbAttr extends PreLeaveFieldAbAttr {
|
||||
/**
|
||||
* @param pokemon The {@linkcode Pokemon} with the ability
|
||||
* @param passive N/A
|
||||
* @param args N/A
|
||||
* @returns Returns `true` if the weather clears, otherwise `false`.
|
||||
*/
|
||||
applyPreLeaveField(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean | Promise<boolean> {
|
||||
const weatherType = globalScene.arena.weather?.weatherType;
|
||||
let turnOffWeather = false;
|
||||
|
||||
// Clear weather only if user's ability matches the weather and no other pokemon has the ability.
|
||||
switch (weatherType) {
|
||||
case (WeatherType.HARSH_SUN):
|
||||
if (pokemon.hasAbility(Abilities.DESOLATE_LAND)
|
||||
&& globalScene.getField(true).filter(p => p !== pokemon).filter(p => p.hasAbility(Abilities.DESOLATE_LAND)).length === 0) {
|
||||
turnOffWeather = true;
|
||||
}
|
||||
break;
|
||||
case (WeatherType.HEAVY_RAIN):
|
||||
if (pokemon.hasAbility(Abilities.PRIMORDIAL_SEA)
|
||||
&& globalScene.getField(true).filter(p => p !== pokemon).filter(p => p.hasAbility(Abilities.PRIMORDIAL_SEA)).length === 0) {
|
||||
turnOffWeather = true;
|
||||
}
|
||||
break;
|
||||
case (WeatherType.STRONG_WINDS):
|
||||
if (pokemon.hasAbility(Abilities.DELTA_STREAM)
|
||||
&& globalScene.getField(true).filter(p => p !== pokemon).filter(p => p.hasAbility(Abilities.DELTA_STREAM)).length === 0) {
|
||||
turnOffWeather = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (simulated) {
|
||||
return turnOffWeather;
|
||||
}
|
||||
|
||||
if (turnOffWeather) {
|
||||
globalScene.arena.trySetWeather(WeatherType.NONE, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export class PreStatStageChangeAbAttr extends AbAttr {
|
||||
applyPreStatStageChange(pokemon: Pokemon | null, passive: boolean, simulated: boolean, stat: BattleStat, cancelled: Utils.BooleanHolder, args: any[]): boolean | Promise<boolean> {
|
||||
return false;
|
||||
@ -4171,59 +4177,6 @@ export class PostFaintUnsuppressedWeatherFormChangeAbAttr extends PostFaintAbAtt
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears Desolate Land/Primordial Sea/Delta Stream upon the Pokemon fainting
|
||||
*/
|
||||
export class PostFaintClearWeatherAbAttr extends PostFaintAbAttr {
|
||||
|
||||
/**
|
||||
* @param pokemon The {@linkcode Pokemon} with the ability
|
||||
* @param passive N/A
|
||||
* @param attacker N/A
|
||||
* @param move N/A
|
||||
* @param hitResult N/A
|
||||
* @param args N/A
|
||||
* @returns {boolean} Returns true if the weather clears, otherwise false.
|
||||
*/
|
||||
applyPostFaint(pokemon: Pokemon, passive: boolean, simulated: boolean, attacker?: Pokemon, move?: Move, hitResult?: HitResult, ...args: any[]): boolean {
|
||||
const weatherType = globalScene.arena.weather?.weatherType;
|
||||
let turnOffWeather = false;
|
||||
|
||||
// Clear weather only if user's ability matches the weather and no other pokemon has the ability.
|
||||
switch (weatherType) {
|
||||
case (WeatherType.HARSH_SUN):
|
||||
if (pokemon.hasAbility(Abilities.DESOLATE_LAND)
|
||||
&& globalScene.getField(true).filter(p => p.hasAbility(Abilities.DESOLATE_LAND)).length === 0) {
|
||||
turnOffWeather = true;
|
||||
}
|
||||
break;
|
||||
case (WeatherType.HEAVY_RAIN):
|
||||
if (pokemon.hasAbility(Abilities.PRIMORDIAL_SEA)
|
||||
&& globalScene.getField(true).filter(p => p.hasAbility(Abilities.PRIMORDIAL_SEA)).length === 0) {
|
||||
turnOffWeather = true;
|
||||
}
|
||||
break;
|
||||
case (WeatherType.STRONG_WINDS):
|
||||
if (pokemon.hasAbility(Abilities.DELTA_STREAM)
|
||||
&& globalScene.getField(true).filter(p => p.hasAbility(Abilities.DELTA_STREAM)).length === 0) {
|
||||
turnOffWeather = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (simulated) {
|
||||
return turnOffWeather;
|
||||
}
|
||||
|
||||
if (turnOffWeather) {
|
||||
globalScene.arena.trySetWeather(WeatherType.NONE, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export class PostFaintContactDamageAbAttr extends PostFaintAbAttr {
|
||||
private damageRatio: number;
|
||||
|
||||
@ -5229,6 +5182,11 @@ export function applyPreSwitchOutAbAttrs(attrType: Constructor<PreSwitchOutAbAtt
|
||||
return applyAbAttrsInternal<PreSwitchOutAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPreSwitchOut(pokemon, passive, simulated, args), args, true, simulated);
|
||||
}
|
||||
|
||||
export function applyPreLeaveFieldAbAttrs(attrType: Constructor<PreLeaveFieldAbAttr>,
|
||||
pokemon: Pokemon, simulated: boolean = false, ...args: any[]): Promise<void> {
|
||||
return applyAbAttrsInternal<PreLeaveFieldAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPreLeaveField(pokemon, passive, simulated, args), args, true, simulated);
|
||||
}
|
||||
|
||||
export function applyPreStatStageChangeAbAttrs(attrType: Constructor<PreStatStageChangeAbAttr>,
|
||||
pokemon: Pokemon | null, stat: BattleStat, cancelled: Utils.BooleanHolder, simulated: boolean = false, ...args: any[]): Promise<void> {
|
||||
return applyAbAttrsInternal<PreStatStageChangeAbAttr>(attrType, pokemon, (attr, passive) => attr.applyPreStatStageChange(pokemon, passive, simulated, stat, cancelled, args), args, false, simulated);
|
||||
@ -5912,20 +5870,17 @@ export function initAbilities() {
|
||||
new Ability(Abilities.PRIMORDIAL_SEA, 6)
|
||||
.attr(PostSummonWeatherChangeAbAttr, WeatherType.HEAVY_RAIN)
|
||||
.attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.HEAVY_RAIN)
|
||||
.attr(PreSwitchOutClearWeatherAbAttr)
|
||||
.attr(PostFaintClearWeatherAbAttr)
|
||||
.attr(PreLeaveFieldClearWeatherAbAttr)
|
||||
.bypassFaint(),
|
||||
new Ability(Abilities.DESOLATE_LAND, 6)
|
||||
.attr(PostSummonWeatherChangeAbAttr, WeatherType.HARSH_SUN)
|
||||
.attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.HARSH_SUN)
|
||||
.attr(PreSwitchOutClearWeatherAbAttr)
|
||||
.attr(PostFaintClearWeatherAbAttr)
|
||||
.attr(PreLeaveFieldClearWeatherAbAttr)
|
||||
.bypassFaint(),
|
||||
new Ability(Abilities.DELTA_STREAM, 6)
|
||||
.attr(PostSummonWeatherChangeAbAttr, WeatherType.STRONG_WINDS)
|
||||
.attr(PostBiomeChangeWeatherChangeAbAttr, WeatherType.STRONG_WINDS)
|
||||
.attr(PreSwitchOutClearWeatherAbAttr)
|
||||
.attr(PostFaintClearWeatherAbAttr)
|
||||
.attr(PreLeaveFieldClearWeatherAbAttr)
|
||||
.bypassFaint(),
|
||||
new Ability(Abilities.STAMINA, 7)
|
||||
.attr(PostDefendStatStageChangeAbAttr, (target, user, move) => move.category !== MoveCategory.STATUS, Stat.DEF, 1),
|
||||
|
@ -148,7 +148,7 @@ export const DancingLessonsEncounter: MysteryEncounter =
|
||||
|
||||
// Adds a real Pokemon sprite to the field (required for the animation)
|
||||
globalScene.getEnemyParty().forEach(enemyPokemon => {
|
||||
globalScene.field.remove(enemyPokemon, true);
|
||||
enemyPokemon.leaveField(true, true, true);
|
||||
});
|
||||
globalScene.currentBattle.enemyParty = [ oricorio ];
|
||||
globalScene.field.add(oricorio);
|
||||
|
@ -229,7 +229,7 @@ function handleLoseMinigame() {
|
||||
// End the battle
|
||||
if (wobbuffet) {
|
||||
wobbuffet.hideInfo();
|
||||
globalScene.field.remove(wobbuffet);
|
||||
wobbuffet.leaveField();
|
||||
}
|
||||
transitionMysteryEncounterIntroVisuals(true, true);
|
||||
globalScene.currentBattle.enemyParty = [];
|
||||
@ -278,7 +278,7 @@ function handleNextTurn() {
|
||||
|
||||
// End the battle
|
||||
wobbuffet.hideInfo();
|
||||
globalScene.field.remove(wobbuffet);
|
||||
wobbuffet.leaveField();
|
||||
globalScene.currentBattle.enemyParty = [];
|
||||
globalScene.currentBattle.mysteryEncounter!.doContinueEncounter = undefined;
|
||||
leaveEncounterWithoutBattle(isHealPhase);
|
||||
|
@ -575,7 +575,7 @@ function onGameOver() {
|
||||
ease: "Sine.easeIn",
|
||||
scale: 0.5,
|
||||
onComplete: () => {
|
||||
globalScene.field.remove(pokemon, true);
|
||||
pokemon.leaveField(true, true, true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -164,7 +164,7 @@ export async function initBattleWithEnemyConfig(partyConfig: EnemyPartyConfig):
|
||||
}
|
||||
|
||||
globalScene.getEnemyParty().forEach(enemyPokemon => {
|
||||
globalScene.field.remove(enemyPokemon, true);
|
||||
enemyPokemon.leaveField(true, true, true);
|
||||
});
|
||||
battle.enemyParty = [];
|
||||
battle.double = doubleBattle;
|
||||
@ -810,7 +810,7 @@ export function transitionMysteryEncounterIntroVisuals(hide: boolean = true, des
|
||||
globalScene.field.remove(introVisuals, true);
|
||||
|
||||
enemyPokemon.forEach(pokemon => {
|
||||
globalScene.field.remove(pokemon, true);
|
||||
pokemon.leaveField(true, true, true);
|
||||
});
|
||||
|
||||
globalScene.currentBattle.mysteryEncounter!.introVisuals = undefined;
|
||||
|
@ -592,7 +592,7 @@ export async function catchPokemon(pokemon: EnemyPokemon, pokeball: Phaser.GameO
|
||||
};
|
||||
const removePokemon = () => {
|
||||
if (pokemon) {
|
||||
globalScene.field.remove(pokemon, true);
|
||||
pokemon.leaveField(false, true, true);
|
||||
}
|
||||
};
|
||||
const addToParty = (slotIndex?: number) => {
|
||||
@ -695,7 +695,7 @@ export async function doPokemonFlee(pokemon: EnemyPokemon): Promise<void> {
|
||||
scale: pokemon.getSpriteScale(),
|
||||
onComplete: () => {
|
||||
pokemon.setVisible(false);
|
||||
globalScene.field.remove(pokemon, true);
|
||||
pokemon.leaveField(true, true, true);
|
||||
showEncounterText(i18next.t("battle:pokemonFled", { pokemonName: pokemon.getNameToRender() }), null, 600, false)
|
||||
.then(() => {
|
||||
resolve();
|
||||
@ -723,7 +723,7 @@ export function doPlayerFlee(pokemon: EnemyPokemon): Promise<void> {
|
||||
scale: pokemon.getSpriteScale(),
|
||||
onComplete: () => {
|
||||
pokemon.setVisible(false);
|
||||
globalScene.field.remove(pokemon, true);
|
||||
pokemon.leaveField(true, true, true);
|
||||
showEncounterText(i18next.t("battle:playerFled", { pokemonName: pokemon.getNameToRender() }), null, 600, false)
|
||||
.then(() => {
|
||||
resolve();
|
||||
|
@ -31,7 +31,7 @@ import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoo
|
||||
import { WeatherType } from "#enums/weather-type";
|
||||
import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag";
|
||||
import type { Ability, AbAttr } from "#app/data/ability";
|
||||
import { StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, 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, InfiltratorAbAttr, AlliedFieldDamageReductionAbAttr, PostDamageAbAttr, applyPostDamageAbAttrs, CommanderAbAttr, applyPostItemLostAbAttrs, PostItemLostAbAttr } from "#app/data/ability";
|
||||
import { StatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentStatStagesAbAttr, MoveImmunityAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, 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, InfiltratorAbAttr, AlliedFieldDamageReductionAbAttr, PostDamageAbAttr, applyPostDamageAbAttrs, CommanderAbAttr, applyPostItemLostAbAttrs, PostItemLostAbAttr, PreLeaveFieldAbAttr, applyPreLeaveFieldAbAttrs } from "#app/data/ability";
|
||||
import type PokemonData from "#app/system/pokemon-data";
|
||||
import { BattlerIndex } from "#app/battle";
|
||||
import { Mode } from "#app/ui/ui";
|
||||
@ -1422,8 +1422,16 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
*/
|
||||
public hasPassive(): boolean {
|
||||
// returns override if valid for current case
|
||||
if ((Overrides.PASSIVE_ABILITY_OVERRIDE !== Abilities.NONE && this.isPlayer())
|
||||
|| (Overrides.OPP_PASSIVE_ABILITY_OVERRIDE !== Abilities.NONE && !this.isPlayer())) {
|
||||
if (
|
||||
(Overrides.HAS_PASSIVE_ABILITY_OVERRIDE === false && this.isPlayer())
|
||||
|| (Overrides.OPP_HAS_PASSIVE_ABILITY_OVERRIDE === false && !this.isPlayer())
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
((Overrides.PASSIVE_ABILITY_OVERRIDE !== Abilities.NONE || Overrides.HAS_PASSIVE_ABILITY_OVERRIDE) && this.isPlayer())
|
||||
|| ((Overrides.OPP_PASSIVE_ABILITY_OVERRIDE !== Abilities.NONE || Overrides.OPP_HAS_PASSIVE_ABILITY_OVERRIDE) && !this.isPlayer())
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -4142,9 +4150,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
* @param hideInfo Indicates if this should also play the animation to hide the Pokemon's
|
||||
* info container.
|
||||
*/
|
||||
leaveField(clearEffects: boolean = true, hideInfo: boolean = true) {
|
||||
leaveField(clearEffects: boolean = true, hideInfo: boolean = true, destroy: boolean = false) {
|
||||
this.resetSprite();
|
||||
this.resetTurnData();
|
||||
globalScene.getField(true).filter(p => p !== this).forEach(p => p.removeTagsBySourceId(this.id));
|
||||
|
||||
if (clearEffects) {
|
||||
this.destroySubstitute();
|
||||
this.resetSummonData(); // this also calls `resetBattleSummonData`
|
||||
@ -4152,9 +4162,11 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
|
||||
if (hideInfo) {
|
||||
this.hideInfo();
|
||||
}
|
||||
globalScene.field.remove(this);
|
||||
// Trigger abilities that activate upon leaving the field
|
||||
applyPreLeaveFieldAbAttrs(PreLeaveFieldAbAttr, this);
|
||||
this.setSwitchOutStatus(true);
|
||||
globalScene.triggerPokemonFormChange(this, SpeciesFormChangeActiveTrigger, true);
|
||||
globalScene.field.remove(this, destroy);
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
|
@ -129,6 +129,7 @@ class DefaultOverrides {
|
||||
readonly STARTER_FUSION_SPECIES_OVERRIDE: Species | number = 0;
|
||||
readonly ABILITY_OVERRIDE: Abilities = Abilities.NONE;
|
||||
readonly PASSIVE_ABILITY_OVERRIDE: Abilities = Abilities.NONE;
|
||||
readonly HAS_PASSIVE_ABILITY_OVERRIDE: boolean | null = null;
|
||||
readonly STATUS_OVERRIDE: StatusEffect = StatusEffect.NONE;
|
||||
readonly GENDER_OVERRIDE: Gender | null = null;
|
||||
readonly MOVESET_OVERRIDE: Moves | Array<Moves> = [];
|
||||
@ -150,6 +151,7 @@ class DefaultOverrides {
|
||||
readonly OPP_LEVEL_OVERRIDE: number = 0;
|
||||
readonly OPP_ABILITY_OVERRIDE: Abilities = Abilities.NONE;
|
||||
readonly OPP_PASSIVE_ABILITY_OVERRIDE: Abilities = Abilities.NONE;
|
||||
readonly OPP_HAS_PASSIVE_ABILITY_OVERRIDE: boolean | null = null;
|
||||
readonly OPP_STATUS_OVERRIDE: StatusEffect = StatusEffect.NONE;
|
||||
readonly OPP_GENDER_OVERRIDE: Gender | null = null;
|
||||
readonly OPP_MOVESET_OVERRIDE: Moves | Array<Moves> = [];
|
||||
|
@ -241,11 +241,10 @@ export class AttemptCapturePhase extends PokemonPhase {
|
||||
};
|
||||
const removePokemon = () => {
|
||||
globalScene.addFaintedEnemyScore(pokemon);
|
||||
globalScene.getPlayerField().filter(p => p.isActive(true)).forEach(playerPokemon => playerPokemon.removeTagsBySourceId(pokemon.id));
|
||||
pokemon.hp = 0;
|
||||
pokemon.trySetStatus(StatusEffect.FAINT);
|
||||
globalScene.clearEnemyHeldItemModifiers();
|
||||
globalScene.field.remove(pokemon, true);
|
||||
pokemon.leaveField(true, true, true);
|
||||
};
|
||||
const addToParty = (slotIndex?: number) => {
|
||||
const newPokemon = pokemon.addToParty(this.pokeballType, slotIndex);
|
||||
|
@ -181,9 +181,7 @@ export class FaintPhase extends PokemonPhase {
|
||||
y: pokemon.y + 150,
|
||||
ease: "Sine.easeIn",
|
||||
onComplete: () => {
|
||||
pokemon.resetSprite();
|
||||
pokemon.lapseTags(BattlerTagLapseType.FAINT);
|
||||
globalScene.getField(true).filter(p => p !== pokemon).forEach(p => p.removeTagsBySourceId(pokemon.id));
|
||||
|
||||
pokemon.y -= 150;
|
||||
pokemon.trySetStatus(StatusEffect.FAINT);
|
||||
@ -193,7 +191,7 @@ export class FaintPhase extends PokemonPhase {
|
||||
globalScene.addFaintedEnemyScore(pokemon as EnemyPokemon);
|
||||
globalScene.currentBattle.addPostBattleLoot(pokemon as EnemyPokemon);
|
||||
}
|
||||
globalScene.field.remove(pokemon);
|
||||
pokemon.leaveField();
|
||||
this.end();
|
||||
}
|
||||
});
|
||||
|
@ -64,6 +64,7 @@ export class SwitchSummonPhase extends SummonPhase {
|
||||
|
||||
const pokemon = this.getPokemon();
|
||||
(this.player ? globalScene.getEnemyField() : globalScene.getPlayerField()).forEach(enemyPokemon => enemyPokemon.removeTagsBySourceId(pokemon.id));
|
||||
|
||||
if (this.switchType === SwitchType.SWITCH || this.switchType === SwitchType.INITIAL_SWITCH) {
|
||||
const substitute = pokemon.getTag(SubstituteTag);
|
||||
if (substitute) {
|
||||
@ -93,8 +94,8 @@ export class SwitchSummonPhase extends SummonPhase {
|
||||
ease: "Sine.easeIn",
|
||||
scale: 0.5,
|
||||
onComplete: () => {
|
||||
pokemon.leaveField(this.switchType === SwitchType.SWITCH, false);
|
||||
globalScene.time.delayedCall(750, () => this.switchAndSummon());
|
||||
pokemon.leaveField(this.switchType === SwitchType.SWITCH, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
139
src/test/abilities/desolate-land.test.ts
Normal file
139
src/test/abilities/desolate-land.test.ts
Normal file
@ -0,0 +1,139 @@
|
||||
import { PokeballType } from "#app/enums/pokeball";
|
||||
import { WeatherType } from "#app/enums/weather-type";
|
||||
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, vi } from "vitest";
|
||||
|
||||
describe("Abilities - Desolate Land", () => {
|
||||
let phaserGame: Phaser.Game;
|
||||
let game: GameManager;
|
||||
|
||||
beforeAll(() => {
|
||||
phaserGame = new Phaser.Game({
|
||||
type: Phaser.HEADLESS,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
game.phaseInterceptor.restoreOg();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
game = new GameManager(phaserGame);
|
||||
game.override
|
||||
.moveset(Moves.SPLASH)
|
||||
.hasPassiveAbility(true)
|
||||
.enemySpecies(Species.RALTS)
|
||||
.enemyAbility(Abilities.BALL_FETCH)
|
||||
.enemyMoveset(Moves.SPLASH);
|
||||
});
|
||||
|
||||
/**
|
||||
* This checks that the weather has changed after the Enemy Pokemon with {@linkcode Abilities.DESOLATE_LAND}
|
||||
* is forcefully moved out of the field from moves such as Roar {@linkcode Moves.ROAR}
|
||||
*/
|
||||
it("should lift only when all pokemon with this ability leave the field", async () => {
|
||||
game.override
|
||||
.battleType("double")
|
||||
.enemyMoveset([ Moves.SPLASH, Moves.ROAR ]);
|
||||
await game.classicMode.startBattle([ Species.MAGCARGO, Species.MAGCARGO, Species.MAGIKARP, Species.MAGIKARP ]);
|
||||
|
||||
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.HARSH_SUN);
|
||||
|
||||
vi.spyOn(game.scene, "randBattleSeedInt").mockImplementation((range, min: number = 0) => {
|
||||
return min;
|
||||
});
|
||||
|
||||
game.move.select(Moves.SPLASH, 0, 2);
|
||||
game.move.select(Moves.SPLASH, 1, 2);
|
||||
|
||||
await game.forceEnemyMove(Moves.ROAR, 0);
|
||||
await game.forceEnemyMove(Moves.SPLASH, 1);
|
||||
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
|
||||
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.HARSH_SUN);
|
||||
|
||||
await game.toNextTurn();
|
||||
|
||||
vi.spyOn(game.scene, "randBattleSeedInt").mockImplementation((range, min: number = 0) => {
|
||||
return min + 1;
|
||||
});
|
||||
|
||||
game.move.select(Moves.SPLASH, 0, 2);
|
||||
game.move.select(Moves.SPLASH, 1, 2);
|
||||
|
||||
await game.forceEnemyMove(Moves.ROAR, 1);
|
||||
await game.forceEnemyMove(Moves.SPLASH, 0);
|
||||
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
|
||||
expect(game.scene.arena.weather?.weatherType).not.toBe(WeatherType.HARSH_SUN);
|
||||
});
|
||||
|
||||
it("should lift when enemy faints", async () => {
|
||||
game.override
|
||||
.battleType("single")
|
||||
.moveset([ Moves.SHEER_COLD ])
|
||||
.ability(Abilities.NO_GUARD)
|
||||
.startingLevel(100)
|
||||
.enemyLevel(1)
|
||||
.enemyMoveset([ Moves.SPLASH ])
|
||||
.enemySpecies(Species.MAGCARGO)
|
||||
.enemyHasPassiveAbility(true);
|
||||
await game.classicMode.startBattle([ Species.MAGIKARP ]);
|
||||
|
||||
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.HARSH_SUN);
|
||||
|
||||
game.move.select(Moves.SHEER_COLD);
|
||||
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
|
||||
expect(game.scene.arena.weather?.weatherType).not.toBe(WeatherType.HARSH_SUN);
|
||||
});
|
||||
|
||||
it("should lift when pokemon returns upon switching from double to single battle", async () => {
|
||||
game.override
|
||||
.battleType("even-doubles")
|
||||
.enemyMoveset([ Moves.SPLASH, Moves.MEMENTO ])
|
||||
.startingWave(12);
|
||||
await game.classicMode.startBattle([ Species.MAGIKARP, Species.MAGCARGO ]);
|
||||
|
||||
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.HARSH_SUN);
|
||||
|
||||
game.move.select(Moves.SPLASH, 0, 2);
|
||||
game.move.select(Moves.SPLASH, 1, 2);
|
||||
await game.forceEnemyMove(Moves.MEMENTO, 0);
|
||||
await game.forceEnemyMove(Moves.MEMENTO, 1);
|
||||
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
|
||||
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.HARSH_SUN);
|
||||
|
||||
await game.toNextWave();
|
||||
|
||||
expect(game.scene.arena.weather?.weatherType).not.toBe(WeatherType.HARSH_SUN);
|
||||
});
|
||||
|
||||
it("should lift when enemy is captured", async () => {
|
||||
game.override
|
||||
.battleType("single")
|
||||
.enemyMoveset([ Moves.SPLASH ])
|
||||
.enemySpecies(Species.MAGCARGO)
|
||||
.enemyHasPassiveAbility(true);
|
||||
await game.classicMode.startBattle([ Species.MAGIKARP ]);
|
||||
|
||||
expect(game.scene.arena.weather?.weatherType).toBe(WeatherType.HARSH_SUN);
|
||||
|
||||
game.scene.pokeballCounts[PokeballType.MASTER_BALL] = 1;
|
||||
|
||||
game.doThrowPokeball(PokeballType.MASTER_BALL);
|
||||
|
||||
await game.phaseInterceptor.to("TurnEndPhase");
|
||||
|
||||
expect(game.scene.arena.weather?.weatherType).not.toBe(WeatherType.HARSH_SUN);
|
||||
});
|
||||
});
|
@ -221,7 +221,8 @@ describe("Moves - Instruct", () => {
|
||||
it("should allow for dancer copying of instructed dance move", async () => {
|
||||
game.override
|
||||
.battleType("double")
|
||||
.enemyMoveset([ Moves.INSTRUCT, Moves.SPLASH ]);
|
||||
.enemyMoveset([ Moves.INSTRUCT, Moves.SPLASH ])
|
||||
.enemyLevel(1000);
|
||||
await game.classicMode.startBattle([ Species.ORICORIO, Species.VOLCARONA ]);
|
||||
|
||||
const [ oricorio, volcarona ] = game.scene.getPlayerField();
|
||||
@ -236,11 +237,9 @@ describe("Moves - Instruct", () => {
|
||||
await game.phaseInterceptor.to("BerryPhase");
|
||||
|
||||
// fiery dance triggered dancer successfully for a total of 4 hits
|
||||
// Volcarona fiery dance has a _small_ chance to 3HKO a shuckle in worst case, so we add the hit count of both
|
||||
// foes to account for spillover
|
||||
// Enemy level is set to a high value so that it does not faint even after all 4 hits
|
||||
instructSuccess(volcarona, Moves.FIERY_DANCE);
|
||||
expect(game.scene.getEnemyField()[0].turnData.attacksReceived.length +
|
||||
game.scene.getEnemyField()[1].turnData.attacksReceived.length).toBe(4);
|
||||
expect(game.scene.getEnemyField()[0].turnData.attacksReceived.length).toBe(4);
|
||||
});
|
||||
|
||||
it("should not repeat move when switching out", async () => {
|
||||
|
@ -24,6 +24,7 @@ import { TurnInitPhase } from "#app/phases/turn-init-phase";
|
||||
import { TurnStartPhase } from "#app/phases/turn-start-phase";
|
||||
import ErrorInterceptor from "#app/test/utils/errorInterceptor";
|
||||
import type InputsHandler from "#app/test/utils/inputsHandler";
|
||||
import type BallUiHandler from "#app/ui/ball-ui-handler";
|
||||
import type BattleMessageUiHandler from "#app/ui/battle-message-ui-handler";
|
||||
import type CommandUiHandler from "#app/ui/command-ui-handler";
|
||||
import type ModifierSelectUiHandler from "#app/ui/modifier-select-ui-handler";
|
||||
@ -458,6 +459,24 @@ export default class GameManager {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the BALL option from the command menu, then press Action; in the BALL
|
||||
* menu, select a pokéball type and press Action again to throw it.
|
||||
* @param ballIndex the index of the pokeball to throw
|
||||
*/
|
||||
public doThrowPokeball(ballIndex: number) {
|
||||
this.onNextPrompt("CommandPhase", Mode.COMMAND, () => {
|
||||
(this.scene.ui.getHandler() as CommandUiHandler).setCursor(1);
|
||||
(this.scene.ui.getHandler() as CommandUiHandler).processInput(Button.ACTION);
|
||||
});
|
||||
|
||||
this.onNextPrompt("CommandPhase", Mode.BALL, () => {
|
||||
const ballHandler = this.scene.ui.getHandler() as BallUiHandler;
|
||||
ballHandler.setCursor(ballIndex);
|
||||
ballHandler.processInput(Button.ACTION); // select ball and throw
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Intercepts `TurnStartPhase` and mocks {@linkcode TurnStartPhase.getSpeedOrder}'s return value.
|
||||
* Used to manually modify Pokemon turn order.
|
||||
|
@ -181,6 +181,20 @@ export class OverridesHelper extends GameManagerHelper {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces the status of the player (pokemon) **passive** {@linkcode Abilities | ability}
|
||||
* @param hasPassiveAbility forces the passive to be active if `true`, inactive if `false`
|
||||
* @returns `this`
|
||||
*/
|
||||
public hasPassiveAbility(hasPassiveAbility: boolean | null): this {
|
||||
vi.spyOn(Overrides, "HAS_PASSIVE_ABILITY_OVERRIDE", "get").mockReturnValue(hasPassiveAbility);
|
||||
if (hasPassiveAbility === null) {
|
||||
this.log("Player Pokemon PASSIVE ability no longer force enabled or disabled!");
|
||||
} else {
|
||||
this.log(`Player Pokemon PASSIVE ability is force ${hasPassiveAbility ? "enabled" : "disabled"}!`);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* Override the player (pokemon) {@linkcode Moves | moves}set
|
||||
* @param moveset the {@linkcode Moves | moves}set to set
|
||||
@ -325,6 +339,21 @@ export class OverridesHelper extends GameManagerHelper {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces the status of the enemy (pokemon) **passive** {@linkcode Abilities | ability}
|
||||
* @param hasPassiveAbility forces the passive to be active if `true`, inactive if `false`
|
||||
* @returns `this`
|
||||
*/
|
||||
public enemyHasPassiveAbility(hasPassiveAbility: boolean | null): this {
|
||||
vi.spyOn(Overrides, "OPP_HAS_PASSIVE_ABILITY_OVERRIDE", "get").mockReturnValue(hasPassiveAbility);
|
||||
if (hasPassiveAbility === null) {
|
||||
this.log("Enemy Pokemon PASSIVE ability no longer force enabled or disabled!");
|
||||
} else {
|
||||
this.log(`Enemy Pokemon PASSIVE ability is force ${hasPassiveAbility ? "enabled" : "disabled"}!`);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the enemy (pokemon) {@linkcode Moves | moves}set
|
||||
* @param moveset the {@linkcode Moves | moves}set to set
|
||||
|
Loading…
x
Reference in New Issue
Block a user