Merge branch 'beta' into moving-daily-run

This commit is contained in:
damocleas 2025-02-07 22:43:21 -05:00 committed by GitHub
commit 47ace3426e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 325 additions and 160 deletions

View File

@ -343,34 +343,39 @@ In addition to the lists below, please check [the PokéRogue wiki](https://wiki.
# 💻 Development
## Server Owner/Administrator
## Server Developers
- pancakes aka patapancakes
## Senior Developers
- Walker
- NightKev
- Moka
- Temp aka Tempo-anon
- Madmadness65
## Developers
## Current and former Development Team members
- bennybroseph
- Brain Frog
- CodeTappert
- Dakurei
- flx-sta
- innerthunder
- frutescens
- Greenlamp
- ImperialSympathizer
- innerthunder
- KimJeongSun
- Madmadness65
- Moka
- Navori
- NightKev
- Opaquer
- OrangeRed
- Sam aka Flashfyre (initial developer, started PokéRogue)
- sirzento
- SN34KZ
- Swain aka torranx
## Junior Developers
- KimJeongSun
- ImperialSympathizer
- Temp aka Tempo-anon
- Walker
- Xavion
## Bug/Issue Managers
- Snailman
- Daleks
- Lily
- PigeonBar
- Snailman
## Other Code Contributors
- Admiral-Billy
@ -378,10 +383,7 @@ In addition to the lists below, please check [the PokéRogue wiki](https://wiki.
- arColm
- Arxalc
- AsdarDevelops
- bennybroseph
- Brain Frog
- Corrade
- Dakurei
- DustinLin
- ElizaAlex
- EmberCM
@ -391,7 +393,6 @@ In addition to the lists below, please check [the PokéRogue wiki](https://wiki.
- francktrouillez
- FredeX
- geeilhan
- Greenlamp
- happinyz
- hayuna
- InfernoVulpix
@ -411,7 +412,6 @@ In addition to the lists below, please check [the PokéRogue wiki](https://wiki.
- Neverblade
- NxKarim
- okimin
- OrangeRed
- PigeonBar
- PrabbyDD
- prateau
@ -421,10 +421,8 @@ In addition to the lists below, please check [the PokéRogue wiki](https://wiki.
- RedstonewolfX
- ReneGV
- rnicar245
- Sam aka Flashfyre (initial developer, started PokéRogue)
- schmidtc1
- shayebeadling
- sirzento
- snoozbuster
- sodaMelon
- td76099

Binary file not shown.

View File

@ -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),

View File

@ -2871,7 +2871,7 @@ export class SyrupBombTag extends BattlerTag {
/**
* Telekinesis raises the target into the air for three turns and causes all moves used against the target (aside from OHKO moves) to hit the target unless the target is in a semi-invulnerable state from Fly/Dig.
* The first effect is provided by {@linkcode FloatingTag}, the accuracy-bypass effect is provided by TelekinesisTag
* The effects of Telekinesis can be baton passed to a teammate. Unlike the mainline games, Telekinesis can be baton-passed to Mega Gengar.
* The effects of Telekinesis can be baton passed to a teammate.
* @see {@link https://bulbapedia.bulbagarden.net/wiki/Telekinesis_(move) | Moves.TELEKINESIS}
*/
export class TelekinesisTag extends BattlerTag {

View File

@ -10429,9 +10429,8 @@ export function initMoves() {
new AttackMove(Moves.PIKA_PAPOW, Type.ELECTRIC, MoveCategory.SPECIAL, -1, -1, 20, -1, 0, 7)
.attr(FriendshipPowerAttr),
new AttackMove(Moves.BOUNCY_BUBBLE, Type.WATER, MoveCategory.SPECIAL, 60, 100, 20, -1, 0, 7)
.attr(HitHealAttr) // Custom
.triageMove()
.target(MoveTarget.ALL_NEAR_ENEMIES),
.attr(HitHealAttr, 1)
.triageMove(),
new AttackMove(Moves.BUZZY_BUZZ, Type.ELECTRIC, MoveCategory.SPECIAL, 60, 100, 20, 100, 0, 7)
.attr(StatusEffectAttr, StatusEffect.PARALYSIS),
new AttackMove(Moves.SIZZLY_SLIDE, Type.FIRE, MoveCategory.PHYSICAL, 60, 100, 20, 100, 0, 7)

View File

@ -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);

View File

@ -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);

View File

@ -168,6 +168,7 @@ async function doBiomeTransitionDialogueAndBattleInit() {
// Show dialogue and transition biome
await showEncounterText(`${namespace}:transport`);
await Promise.all([ animateBiomeChange(newBiome), transitionMysteryEncounterIntroVisuals() ]);
globalScene.updateBiomeWaveText();
globalScene.playBgm();
await showEncounterText(`${namespace}:attacked`);

View File

@ -575,7 +575,7 @@ function onGameOver() {
ease: "Sine.easeIn",
scale: 0.5,
onComplete: () => {
globalScene.field.remove(pokemon, true);
pokemon.leaveField(true, true, true);
}
});
}

View File

@ -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;

View File

@ -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();

View File

@ -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;
}
@ -3226,7 +3234,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
}
for (const tag of source.summonData.tags) {
if (!tag.isBatonPassable) {
if (!tag.isBatonPassable || (tag.tagType === BattlerTagType.TELEKINESIS && this.species.speciesId === Species.GENGAR && this.getFormKey() === "mega")) {
continue;
}
@ -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 {

View File

@ -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> = [];

View File

@ -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);

View File

@ -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();
}
});

View File

@ -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);
}
});
}

View 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);
});
});

View File

@ -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 () => {

View File

@ -7,6 +7,7 @@ import { MoveResult } from "#app/field/pokemon";
import GameManager from "#test/utils/gameManager";
import Phaser from "phaser";
import { afterEach, beforeAll, beforeEach, describe, it, expect, vi } from "vitest";
import { BattlerIndex } from "#app/battle";
describe("Moves - Telekinesis", () => {
let phaserGame: Phaser.Game;
@ -121,4 +122,17 @@ describe("Moves - Telekinesis", () => {
expect(enemyOpponent.getTag(BattlerTagType.FLOATING)).toBeUndefined();
expect(playerPokemon.getLastXMoves()[0].result).toBe(MoveResult.SUCCESS);
});
it("should not be baton passed onto a mega gengar", async () => {
game.override.moveset([ Moves.BATON_PASS ])
.enemyMoveset([ Moves.TELEKINESIS ])
.starterForms({ [Species.GENGAR]: 1 });
await game.classicMode.startBattle([ Species.MAGIKARP, Species.GENGAR ]);
game.move.select(Moves.BATON_PASS);
game.doSelectPartyPokemon(1);
await game.setTurnOrder([ BattlerIndex.ENEMY, BattlerIndex.PLAYER ]);
await game.phaseInterceptor.to("BerryPhase");
expect(game.scene.getPlayerPokemon()!.getTag(BattlerTagType.TELEKINESIS)).toBeUndefined();
});
});

View File

@ -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.

View File

@ -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