mirror of
https://github.com/pagefaultgames/pokerogue.git
synced 2025-07-27 18:52:19 +02:00
Merge branch 'beta' into moving-daily-run
This commit is contained in:
commit
47ace3426e
42
CREDITS.md
42
CREDITS.md
@ -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.
@ -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),
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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`);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
@ -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 () => {
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -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…
Reference in New Issue
Block a user