From 870bff0e6e2de1537a021d739d26543bf3ac8d6b Mon Sep 17 00:00:00 2001 From: Dean <69436131+emdeann@users.noreply.github.com> Date: Tue, 25 Feb 2025 15:38:17 -0800 Subject: [PATCH 1/7] [Bug][Ability] Fix Neutralizing Gas interactions #5381 * Add neutralizing gas arena tag * Add PostSummonAddArenaTagAbAttr * Add PreLeaveField attr for neutralizing gas * Remove old SuppressFieldAbilitiesAttr * Fix ability suppression logic * Add basic tests * Reapply onGain abilities when neutralizing gas ends * Untodo ability remval test * Add more tests * Modify gastro acid to call onLose attributes when suppressing * Properly cancel abilities on switch in * Add documentation * Remove partial designation * Update locale key usage * Reactivate the last source's other abilities when it becomes the last remaining * Fix loop vars * Add access modifiers for SuppressAbilitiesTag * Minor changes * Move test to reflect new location * Remove promises from abattr function signatures --- src/data/ability.ts | 78 ++++++++++---- src/data/arena-tag.ts | 67 +++++++++++- src/data/move.ts | 7 +- src/enums/arena-tag-type.ts | 1 + src/field/arena.ts | 2 +- src/field/pokemon.ts | 33 +++--- test/abilities/neutralizing_gas.test.ts | 138 ++++++++++++++++++++++++ 7 files changed, 285 insertions(+), 41 deletions(-) create mode 100644 test/abilities/neutralizing_gas.test.ts diff --git a/src/data/ability.ts b/src/data/ability.ts index 8de0c68a8e7..5f19af8cea4 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -12,7 +12,7 @@ import { getNonVolatileStatusEffects, getStatusEffectDescriptor, getStatusEffect import { Gender } from "./gender"; import type Move from "./move"; import { AttackMove, MoveCategory, MoveFlags, MoveTarget, FlinchAttr, OneHitKOAttr, HitHealAttr, allMoves, StatusMove, SelfStatusMove, VariablePowerAttr, applyMoveAttrs, VariableMoveTypeAttr, RandomMovesetMoveAttr, RandomMoveAttr, NaturePowerAttr, CopyMoveAttr, NeutralDamageAgainstFlyingTypeMultiplierAttr, FixedDamageAttr } from "./move"; -import type { ArenaTrapTag } from "./arena-tag"; +import type { ArenaTrapTag, SuppressAbilitiesTag } from "./arena-tag"; import { ArenaTagSide } from "./arena-tag"; import { BerryModifier, HitHealModifier, PokemonHeldItemModifier } from "../modifier/modifier"; import { TerrainType } from "./terrain"; @@ -2197,6 +2197,34 @@ export class PostSummonRemoveArenaTagAbAttr extends PostSummonAbAttr { } } +/** + * Generic class to add an arena tag upon switching in + */ +export class PostSummonAddArenaTagAbAttr extends PostSummonAbAttr { + private readonly tagType: ArenaTagType; + private readonly turnCount: number; + private readonly side?: ArenaTagSide; + private readonly quiet?: boolean; + private sourceId: number; + + + constructor(tagType: ArenaTagType, turnCount: number, side?: ArenaTagSide, quiet?: boolean) { + super(false); + this.tagType = tagType; + this.turnCount = turnCount; + this.side = side; + this.quiet = quiet; + } + + public override applyPostSummon(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + this.sourceId = pokemon.id; + if (!simulated) { + globalScene.arena.addTag(this.tagType, this.turnCount, undefined, this.sourceId, this.side, this.quiet); + } + return true; + } +} + export class PostSummonMessageAbAttr extends PostSummonAbAttr { private messageFunc: (pokemon: Pokemon) => string; @@ -2941,6 +2969,26 @@ export class PreLeaveFieldClearWeatherAbAttr extends PreLeaveFieldAbAttr { } } +/** + * Updates the active {@linkcode SuppressAbilitiesTag} when a pokemon with {@linkcode Abilities.NEUTRALIZING_GAS} leaves the field + */ +export class PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr extends PreLeaveFieldAbAttr { + constructor() { + super(false); + } + + public override applyPreLeaveField(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { + if (!simulated) { + const suppressTag = globalScene.arena.getTag(ArenaTagType.NEUTRALIZING_GAS) as SuppressAbilitiesTag; + if (suppressTag) { + suppressTag.onSourceLeave(globalScene.arena); + return true; + } + } + return simulated; + } +} + export class PreStatStageChangeAbAttr extends AbAttr { applyPreStatStageChange( pokemon: Pokemon | null, @@ -4692,21 +4740,6 @@ export class MoveAbilityBypassAbAttr extends AbAttr { } } -export class SuppressFieldAbilitiesAbAttr extends AbAttr { - constructor() { - super(false); - } - - apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder, args: any[]): boolean { - const ability = (args[0] as Ability); - if (!ability.hasAttr(UnsuppressableAbilityAbAttr) && !ability.hasAttr(SuppressFieldAbilitiesAbAttr)) { - cancelled.value = true; - return true; - } - return false; - } -} - export class AlwaysHitAbAttr extends AbAttr { } /** Attribute for abilities that allow moves that make contact to ignore protection (i.e. Unseen Fist) */ @@ -5946,10 +5979,10 @@ export function applyOnGainAbAttrs(pokemon: Pokemon, passive: boolean = false, s } /** - * Clears primal weather during the turn if {@linkcode pokemon}'s ability corresponds to one + * Clears primal weather/neutralizing gas during the turn if {@linkcode pokemon}'s ability corresponds to one */ -export function applyOnLoseClearWeatherAbAttrs(pokemon: Pokemon, passive: boolean = false, simulated: boolean = false, ...args: any[]): void { - applySingleAbAttrs(pokemon, passive, PreLeaveFieldClearWeatherAbAttr, (attr, passive) => attr.applyPreLeaveField(pokemon, passive, simulated, [ ...args, true ]), args, true, simulated); +export function applyOnLoseAbAttrs(pokemon: Pokemon, passive: boolean = false, simulated: boolean = false, ...args: any[]): void { + applySingleAbAttrs(pokemon, passive, PreLeaveFieldAbAttr, (attr, passive) => attr.applyPreLeaveField(pokemon, passive, simulated, [ ...args, true ]), args, true, simulated); } function queueShowAbility(pokemon: Pokemon, passive: boolean): void { globalScene.unshiftPhase(new ShowAbilityPhase(pokemon.id, passive)); @@ -6838,12 +6871,11 @@ export function initAbilities() { new Ability(Abilities.GORILLA_TACTICS, 8) .attr(GorillaTacticsAbAttr), new Ability(Abilities.NEUTRALIZING_GAS, 8) - .attr(SuppressFieldAbilitiesAbAttr) + .attr(PostSummonAddArenaTagAbAttr, ArenaTagType.NEUTRALIZING_GAS, 0) + .attr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr) .attr(UncopiableAbilityAbAttr) .attr(UnswappableAbilityAbAttr) - .attr(NoTransformAbilityAbAttr) - .attr(PostSummonMessageAbAttr, (pokemon: Pokemon) => i18next.t("abilityTriggers:postSummonNeutralizingGas", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })) - .partial(), // A bunch of weird interactions with other abilities being suppressed then unsuppressed + .attr(NoTransformAbilityAbAttr), new Ability(Abilities.PASTEL_VEIL, 8) .attr(PostSummonUserFieldRemoveStatusEffectAbAttr, StatusEffect.POISON, StatusEffect.TOXIC) .attr(UserFieldStatusEffectImmunityAbAttr, StatusEffect.POISON, StatusEffect.TOXIC) diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index 2fa4593fd6c..d034ccf83b8 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -8,7 +8,7 @@ import type Pokemon from "#app/field/pokemon"; import { HitResult, PokemonMove } from "#app/field/pokemon"; import { StatusEffect } from "#enums/status-effect"; import type { BattlerIndex } from "#app/battle"; -import { BlockNonDirectDamageAbAttr, InfiltratorAbAttr, ProtectStatAbAttr, applyAbAttrs } from "#app/data/ability"; +import { BlockNonDirectDamageAbAttr, InfiltratorAbAttr, PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr, ProtectStatAbAttr, applyAbAttrs, applyOnGainAbAttrs, applyOnLoseAbAttrs } from "#app/data/ability"; import { Stat } from "#enums/stat"; import { CommonAnim, CommonBattleAnim } from "#app/data/battle-anims"; import i18next from "i18next"; @@ -1221,6 +1221,69 @@ export class FairyLockTag extends ArenaTag { } +/** + * Arena tag class for {@link https://bulbapedia.bulbagarden.net/wiki/Neutralizing_Gas_(Ability) Neutralizing Gas} + * + * Keeps track of the number of pokemon on the field with Neutralizing Gas - If it drops to zero, the effect is ended and abilities are reactivated + * + * Additionally ends onLose abilities when it is activated + */ +export class SuppressAbilitiesTag extends ArenaTag { + private sourceCount: number; + + constructor(sourceId: number) { + super(ArenaTagType.NEUTRALIZING_GAS, 0, undefined, sourceId); + this.sourceCount = 1; + } + + public override onAdd(arena: Arena): void { + const pokemon = this.getSourcePokemon(); + if (pokemon) { + globalScene.queueMessage(i18next.t("arenaTag:neutralizingGasOnAdd", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) })); + + for (const fieldPokemon of globalScene.getField()) { + if (fieldPokemon && fieldPokemon.id !== pokemon.id) { + [ true, false ].forEach((passive) => applyOnLoseAbAttrs(fieldPokemon, passive)); + } + } + } + } + + public override onOverlap(arena: Arena): void { + this.sourceCount++; + } + + public onSourceLeave(arena: Arena): void { + this.sourceCount--; + if (this.sourceCount <= 0) { + arena.removeTag(ArenaTagType.NEUTRALIZING_GAS); + } else if (this.sourceCount === 1) { + // With 1 source left, that pokemon's other abilities should reactivate + // This may be confusing for players but would be the most accurate gameplay-wise + // Could have a custom message that plays when a specific pokemon's NG ends? This entire thing exists due to passives after all + const setter = globalScene.getField().filter((p) => p && p.hasAbilityWithAttr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr, false))[0]; + applyOnGainAbAttrs(setter, setter.getAbility().hasAttr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr)); + } + } + + public override onRemove(arena: Arena, quiet: boolean = false) { + if (!quiet) { + globalScene.queueMessage(i18next.t("arenaTag:neutralizingGasOnRemove")); + } + + for (const pokemon of globalScene.getField()) { + // There is only one pokemon with this attr on the field on removal, so its abilities are already active + if (pokemon && !pokemon.hasAbilityWithAttr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr, false)) { + [ true, false ].forEach((passive) => applyOnGainAbAttrs(pokemon, passive)); + } + } + } + + public shouldApplyToSelf(): boolean { + return this.sourceCount > 1; + } +} + // TODO: swap `sourceMove` and `sourceId` and make `sourceMove` an optional parameter export function getArenaTag(tagType: ArenaTagType, turnCount: number, sourceMove: Moves | undefined, sourceId: number, targetIndex?: BattlerIndex, side: ArenaTagSide = ArenaTagSide.BOTH): ArenaTag | null { switch (tagType) { @@ -1281,6 +1344,8 @@ export function getArenaTag(tagType: ArenaTagType, turnCount: number, sourceMove return new GrassWaterPledgeTag(sourceId, side); case ArenaTagType.FAIRY_LOCK: return new FairyLockTag(turnCount, sourceId); + case ArenaTagType.NEUTRALIZING_GAS: + return new SuppressAbilitiesTag(sourceId); default: return null; } diff --git a/src/data/move.ts b/src/data/move.ts index d9ceb0f3dce..26b182ec5db 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -7811,11 +7811,12 @@ export class SuppressAbilitiesAttr extends MoveEffectAttr { return false; } - target.summonData.abilitySuppressed = true; - globalScene.arena.triggerWeatherBasedFormChangesToNormal(); - globalScene.queueMessage(i18next.t("moveTriggers:suppressAbilities", { pokemonName: getPokemonNameWithAffix(target) })); + target.suppressAbility(); + + globalScene.arena.triggerWeatherBasedFormChangesToNormal(); + return true; } diff --git a/src/enums/arena-tag-type.ts b/src/enums/arena-tag-type.ts index 1c62ccb14a6..4180aa00ef5 100644 --- a/src/enums/arena-tag-type.ts +++ b/src/enums/arena-tag-type.ts @@ -29,4 +29,5 @@ export enum ArenaTagType { WATER_FIRE_PLEDGE = "WATER_FIRE_PLEDGE", GRASS_WATER_PLEDGE = "GRASS_WATER_PLEDGE", FAIRY_LOCK = "FAIRY_LOCK", + NEUTRALIZING_GAS = "NEUTRALIZING_GAS" } diff --git a/src/field/arena.ts b/src/field/arena.ts index 60ee4b5b03c..752eef81596 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -588,8 +588,8 @@ export class Arena { // creates a new tag object const newTag = getArenaTag(tagType, turnCount || 0, sourceMove, sourceId, targetIndex, side); if (newTag) { - this.tags.push(newTag); newTag.onAdd(this, quiet); + this.tags.push(newTag); const { layers = 0, maxLayers = 0 } = newTag instanceof ArenaTrapTag ? newTag : {}; diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 17917ad9b40..bc3b9b1403f 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -63,8 +63,9 @@ import { reverseCompatibleTms, tmSpecies, tmPoolTiers } from "#app/data/balance/ import { BattlerTag, BattlerTagLapseType, EncoreTag, GroundedTag, HighestStatBoostTag, SubstituteTag, TypeImmuneTag, getBattlerTag, SemiInvulnerableTag, TypeBoostTag, MoveRestrictionBattlerTag, ExposedTag, DragonCheerTag, CritBoostTag, TrappedTag, TarShotTag, AutotomizedTag, PowerTrickTag } from "../data/battler-tags"; import { WeatherType } from "#enums/weather-type"; import { ArenaTagSide, NoCritTag, WeakenMoveScreenTag } from "#app/data/arena-tag"; +import type { SuppressAbilitiesTag } 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, applyOnGainAbAttrs, PreLeaveFieldAbAttr, applyPreLeaveFieldAbAttrs, applyOnLoseClearWeatherAbAttrs } 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, 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, applyOnGainAbAttrs, PreLeaveFieldAbAttr, applyPreLeaveFieldAbAttrs, applyOnLoseAbAttrs, PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr } from "#app/data/ability"; import type PokemonData from "#app/system/pokemon-data"; import { BattlerIndex } from "#app/battle"; import { Mode } from "#app/ui/ui"; @@ -1487,7 +1488,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * @param ability New Ability */ public setTempAbility(ability: Ability, passive: boolean = false): void { - applyOnLoseClearWeatherAbAttrs(this, passive); + applyOnLoseAbAttrs(this, passive); if (passive) { this.summonData.passiveAbility = ability.id; } else { @@ -1496,6 +1497,14 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { applyOnGainAbAttrs(this, passive); } + /** + * Suppresses an ability and calls its onlose attributes + */ + public suppressAbility() { + this.summonData.abilitySuppressed = true; + [ true, false ].forEach((passive) => applyOnLoseAbAttrs(this, passive)); + } + /** * Checks if a pokemon has a passive either from: * - bought with starter candy @@ -1553,17 +1562,15 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (this.summonData?.abilitySuppressed && !ability.hasAttr(UnsuppressableAbilityAbAttr)) { return false; } - if (this.isOnField() && !ability.hasAttr(SuppressFieldAbilitiesAbAttr)) { - const suppressed = new Utils.BooleanHolder(false); - globalScene.getField(true).filter(p => p !== this).map(p => { - if (p.getAbility().hasAttr(SuppressFieldAbilitiesAbAttr) && p.canApplyAbility()) { - p.getAbility().getAttrs(SuppressFieldAbilitiesAbAttr).map(a => a.apply(this, false, false, suppressed, [ ability ])); - } - if (p.getPassiveAbility().hasAttr(SuppressFieldAbilitiesAbAttr) && p.canApplyAbility(true)) { - p.getPassiveAbility().getAttrs(SuppressFieldAbilitiesAbAttr).map(a => a.apply(this, true, false, suppressed, [ ability ])); - } - }); - if (suppressed.value) { + const suppressAbilitiesTag = arena.getTag(ArenaTagType.NEUTRALIZING_GAS) as SuppressAbilitiesTag; + if (this.isOnField() && suppressAbilitiesTag) { + const thisAbilitySuppressing = ability.hasAttr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr); + const hasSuppressingAbility = this.hasAbilityWithAttr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr, false); + // Neutralizing gas is up - suppress abilities unless they are unsuppressable or this pokemon is responsible for the gas + // (Balance decided that the other ability of a neutralizing gas pokemon should not be neutralized) + // If the ability itself is neutralizing gas, don't suppress it (handled through arena tag) + const unsuppressable = ability.hasAttr(UnsuppressableAbilityAbAttr) || thisAbilitySuppressing || (hasSuppressingAbility && !suppressAbilitiesTag.shouldApplyToSelf()); + if (!unsuppressable) { return false; } } diff --git a/test/abilities/neutralizing_gas.test.ts b/test/abilities/neutralizing_gas.test.ts new file mode 100644 index 00000000000..8b9c374f1cc --- /dev/null +++ b/test/abilities/neutralizing_gas.test.ts @@ -0,0 +1,138 @@ +import { BattlerIndex } from "#app/battle"; +import { Abilities } from "#enums/abilities"; +import { ArenaTagType } from "#enums/arena-tag-type"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import { Stat } from "#enums/stat"; +import GameManager from "#test/testUtils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Abilities - Neutralizing Gas", () => { + 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 ]) + .ability(Abilities.NEUTRALIZING_GAS) + .battleType("single") + .disableCrits() + .enemySpecies(Species.MAGIKARP) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset(Moves.SPLASH); + }); + + it("should prevent other abilities from activating", async () => { + game.override.enemyAbility(Abilities.INTIMIDATE); + await game.classicMode.startBattle([ Species.FEEBAS ]); + + game.move.select(Moves.SPLASH); + await game.phaseInterceptor.to("TurnEndPhase"); + + // Intimidate is suppressed, so the attack stat should not be lowered + expect(game.scene.getPlayerPokemon()?.getStatStage(Stat.ATK)).toBe(0); + }); + + it("should allow the user's passive to activate", async () => { + game.override.passiveAbility(Abilities.INTREPID_SWORD); + await game.classicMode.startBattle([ Species.FEEBAS ]); + + game.move.select(Moves.SPLASH); + await game.phaseInterceptor.to("TurnEndPhase"); + + expect(game.scene.getPlayerPokemon()?.getStatStage(Stat.ATK)).toBe(1); + }); + + it.todo("should activate before other abilities", async () => { + game.override.enemySpecies(Species.ACCELGOR) + .enemyLevel(100) + .enemyAbility(Abilities.INTIMIDATE); + + await game.classicMode.startBattle([ Species.FEEBAS ]); + + game.move.select(Moves.SPLASH); + await game.phaseInterceptor.to("TurnEndPhase"); + + // Intimidate is suppressed even when the user's speed is lower + expect(game.scene.getPlayerPokemon()?.getStatStage(Stat.ATK)).toBe(0); + }); + + it("should activate other abilities when removed", async () => { + game.override.enemyAbility(Abilities.INTREPID_SWORD) + .enemyPassiveAbility(Abilities.DAUNTLESS_SHIELD) + .enemyMoveset(Moves.ENTRAINMENT); + + await game.classicMode.startBattle([ Species.FEEBAS ]); + + const enemyPokemon = game.scene.getEnemyPokemon(); + expect(enemyPokemon?.getStatStage(Stat.ATK)).toBe(0); + expect(enemyPokemon?.getStatStage(Stat.DEF)).toBe(0); + + game.move.select(Moves.SPLASH); + await game.phaseInterceptor.to("BerryPhase"); + // Enemy removes user's ability, so both abilities are activated + expect(enemyPokemon?.getStatStage(Stat.ATK)).toBe(1); + expect(enemyPokemon?.getStatStage(Stat.DEF)).toBe(1); + }); + + it("should not activate the user's other ability when removed", async () => { + game.override.passiveAbility(Abilities.INTIMIDATE) + .enemyMoveset(Moves.ENTRAINMENT); + + await game.classicMode.startBattle([ Species.FEEBAS ]); + // Neutralising gas user's passive is still active + const enemyPokemon = game.scene.getEnemyPokemon(); + expect(enemyPokemon?.getStatStage(Stat.ATK)).toBe(-1); + + game.move.select(Moves.SPLASH); + await game.phaseInterceptor.to("BerryPhase"); + // Intimidate did not reactivate after neutralizing gas was removed + expect(enemyPokemon?.getStatStage(Stat.ATK)).toBe(-1); + }); + + it("should only deactivate when all setters are off the field", async () => { + game.override.enemyMoveset([ Moves.ENTRAINMENT, Moves.SPLASH ]) + .battleType("double"); + + await game.classicMode.startBattle([ Species.ACCELGOR, Species.ACCELGOR ]); + game.move.select(Moves.SPLASH, 0); + game.move.select(Moves.SPLASH, 1); + await game.forceEnemyMove(Moves.ENTRAINMENT, BattlerIndex.PLAYER); + await game.forceEnemyMove(Moves.SPLASH); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2 ]); + await game.phaseInterceptor.to("BerryPhase"); + expect(game.scene.arena.getTag(ArenaTagType.NEUTRALIZING_GAS)).toBeDefined(); // Now one neut gas user is left + + game.move.select(Moves.SPLASH, 0); + game.move.select(Moves.SPLASH, 1); + await game.forceEnemyMove(Moves.ENTRAINMENT, BattlerIndex.PLAYER_2); + await game.forceEnemyMove(Moves.SPLASH); + await game.setTurnOrder([ BattlerIndex.PLAYER, BattlerIndex.PLAYER_2, BattlerIndex.ENEMY, BattlerIndex.ENEMY_2 ]); + await game.phaseInterceptor.to("BerryPhase"); + expect(game.scene.arena.getTag(ArenaTagType.NEUTRALIZING_GAS)).toBeUndefined(); // No neut gas users are left + }); + + it("should deactivate when suppressed by gastro acid", async () => { + game.override.enemyMoveset(Moves.GASTRO_ACID); + + await game.classicMode.startBattle([ Species.FEEBAS ]); + + game.move.select(Moves.SPLASH); + await game.phaseInterceptor.to("BerryPhase"); + + expect(game.scene.arena.getTag(ArenaTagType.NEUTRALIZING_GAS)).toBeUndefined(); + }); + +}); From 2cc0ac90f88ac6cc1e79a439a4cdf4392d1a650a Mon Sep 17 00:00:00 2001 From: Dean <69436131+emdeann@users.noreply.github.com> Date: Tue, 25 Feb 2025 17:22:32 -0800 Subject: [PATCH 2/7] [Bug] Fix canApplyAbility check #5413 --- src/data/ability.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index 5f19af8cea4..dcbaa4026f4 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -5152,6 +5152,10 @@ function applySingleAbAttrs( showAbilityInstant: boolean = false, messages: string[] = [] ) { + if (!pokemon?.canApplyAbility(passive) || (passive && (pokemon.getPassiveAbility().id === pokemon.getAbility().id))) { + return; + } + const ability = passive ? pokemon.getPassiveAbility() : pokemon.getAbility(); if (gainedMidTurn && ability.getAttrs(attrType).some(attr => attr instanceof PostSummonAbAttr && !attr.shouldActivateOnGain())) { return; @@ -5445,12 +5449,10 @@ function applyAbAttrsInternal( gainedMidTurn: boolean = false ) { for (const passive of [ false, true ]) { - if (!pokemon?.canApplyAbility(passive) || (passive && (pokemon.getPassiveAbility().id === pokemon.getAbility().id))) { - continue; + if (pokemon) { + applySingleAbAttrs(pokemon, passive, attrType, applyFunc, args, gainedMidTurn, simulated, showAbilityInstant, messages); + globalScene.clearPhaseQueueSplice(); } - - applySingleAbAttrs(pokemon, passive, attrType, applyFunc, args, gainedMidTurn, simulated, showAbilityInstant, messages); - globalScene.clearPhaseQueueSplice(); } } From aa3f92463344c1c1aff3081e4095095dba8394bb Mon Sep 17 00:00:00 2001 From: Sirz Benjie <142067137+SirzBenjie@users.noreply.github.com> Date: Tue, 25 Feb 2025 21:05:18 -0600 Subject: [PATCH 3/7] Revert "[Bug] Fix canApplyAbility check #5413" (#5417) This reverts commit 2cc0ac90f88ac6cc1e79a439a4cdf4392d1a650a. --- src/data/ability.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index dcbaa4026f4..5f19af8cea4 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -5152,10 +5152,6 @@ function applySingleAbAttrs( showAbilityInstant: boolean = false, messages: string[] = [] ) { - if (!pokemon?.canApplyAbility(passive) || (passive && (pokemon.getPassiveAbility().id === pokemon.getAbility().id))) { - return; - } - const ability = passive ? pokemon.getPassiveAbility() : pokemon.getAbility(); if (gainedMidTurn && ability.getAttrs(attrType).some(attr => attr instanceof PostSummonAbAttr && !attr.shouldActivateOnGain())) { return; @@ -5449,10 +5445,12 @@ function applyAbAttrsInternal( gainedMidTurn: boolean = false ) { for (const passive of [ false, true ]) { - if (pokemon) { - applySingleAbAttrs(pokemon, passive, attrType, applyFunc, args, gainedMidTurn, simulated, showAbilityInstant, messages); - globalScene.clearPhaseQueueSplice(); + if (!pokemon?.canApplyAbility(passive) || (passive && (pokemon.getPassiveAbility().id === pokemon.getAbility().id))) { + continue; } + + applySingleAbAttrs(pokemon, passive, attrType, applyFunc, args, gainedMidTurn, simulated, showAbilityInstant, messages); + globalScene.clearPhaseQueueSplice(); } } From ca5e578954f9907cc75044d368eb5d2ead0a42f3 Mon Sep 17 00:00:00 2001 From: Xavion3 Date: Wed, 26 Feb 2025 14:27:20 +1100 Subject: [PATCH 4/7] [Bug] Fix AI func save/loads. #5414 Co-authored-by: damocleas --- src/battle-scene.ts | 2 -- src/phases/encounter-phase.ts | 3 +++ 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 962b9c8ca91..8205c1fcebc 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -3448,8 +3448,6 @@ export default class BattleScene extends SceneBase { for (const modifier of modifiers) { this.addEnemyModifier(modifier, true, true); } - - this.currentBattle.trainer.genAI(party); } party.forEach((enemyPokemon: EnemyPokemon, i: number) => { diff --git a/src/phases/encounter-phase.ts b/src/phases/encounter-phase.ts index 9485e77e5aa..ea8e43faeeb 100644 --- a/src/phases/encounter-phase.ts +++ b/src/phases/encounter-phase.ts @@ -253,7 +253,10 @@ export class EncounterPhase extends BattlePhase { globalScene.getEnemyField().forEach(enemy => { overrideHeldItems(enemy, false); }); + } + if (battle.battleType === BattleType.TRAINER) { + globalScene.currentBattle.trainer!.genAI(globalScene.getEnemyParty()); } globalScene.ui.setMode(Mode.MESSAGE).then(() => { From 92ee9d06caf8f68c24e85acc5c5dde81c31ce57a Mon Sep 17 00:00:00 2001 From: damocleas Date: Tue, 25 Feb 2025 23:02:04 -0500 Subject: [PATCH 5/7] [Balance] February 2025 Passive/Egg Move/Cost Changes 2 #5416 * Update egg-moves.ts * Update starters.ts cost changes * Update passives.ts * Update mystery-encounter-utils.test.ts --- src/data/balance/egg-moves.ts | 34 +++++++++---------- src/data/balance/passives.ts | 6 ++-- src/data/balance/starters.ts | 4 +-- .../mystery-encounter-utils.test.ts | 6 ++-- 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/src/data/balance/egg-moves.ts b/src/data/balance/egg-moves.ts index 35ec6f934a4..ae61acbd32e 100644 --- a/src/data/balance/egg-moves.ts +++ b/src/data/balance/egg-moves.ts @@ -46,7 +46,7 @@ export const speciesEggMoves = { [Species.DROWZEE]: [ Moves.BADDY_BAD, Moves.STRENGTH_SAP, Moves.LUMINA_CRASH, Moves.DARK_VOID ], [Species.KRABBY]: [ Moves.DIRE_CLAW, Moves.DRAGON_HAMMER, Moves.IVY_CUDGEL, Moves.JET_PUNCH ], [Species.VOLTORB]: [ Moves.NASTY_PLOT, Moves.FUSION_FLARE, Moves.FROST_BREATH, Moves.ELECTRO_DRIFT ], - [Species.EXEGGCUTE]: [ Moves.FICKLE_BEAM, Moves.APPLE_ACID, Moves.TRICK_ROOM, Moves.LUMINA_CRASH ], + [Species.EXEGGCUTE]: [ Moves.FICKLE_BEAM, Moves.APPLE_ACID, Moves.HEAT_WAVE, Moves.LUMINA_CRASH ], [Species.CUBONE]: [ Moves.HEAD_SMASH, Moves.WOOD_HAMMER, Moves.SHADOW_SNEAK, Moves.BITTER_BLADE ], [Species.LICKITUNG]: [ Moves.CRUSH_GRIP, Moves.FIRE_LASH, Moves.SLACK_OFF, Moves.MAGICAL_TORQUE ], [Species.KOFFING]: [ Moves.SCALD, Moves.RECOVER, Moves.BODY_PRESS, Moves.MALIGNANT_CHAIN ], @@ -59,7 +59,7 @@ export const speciesEggMoves = { [Species.SCYTHER]: [ Moves.MIGHTY_CLEAVE, Moves.GEAR_GRIND, Moves.STORM_THROW, Moves.BITTER_BLADE ], [Species.PINSIR]: [ Moves.HEADLONG_RUSH, Moves.LEECH_LIFE, Moves.CRUSH_GRIP, Moves.EXTREME_SPEED ], [Species.TAUROS]: [ Moves.SWORDS_DANCE, Moves.FIRE_LASH, Moves.WICKED_TORQUE, Moves.COLLISION_COURSE ], - [Species.MAGIKARP]: [ Moves.FLIP_TURN, Moves.ICE_SPINNER, Moves.DRAGON_ASCENT, Moves.SURGING_STRIKES ], + [Species.MAGIKARP]: [ Moves.FLIP_TURN, Moves.ICE_SPINNER, Moves.KNOCK_OFF, Moves.DRAGON_ASCENT ], [Species.LAPRAS]: [ Moves.RECOVER, Moves.FREEZE_DRY, Moves.SCALD, Moves.SHELL_SMASH ], [Species.DITTO]: [ Moves.MIMIC, Moves.SKETCH, Moves.METRONOME, Moves.IMPRISON ], [Species.EEVEE]: [ Moves.WISH, Moves.NO_RETREAT, Moves.ZIPPY_ZAP, Moves.BOOMBURST ], @@ -76,7 +76,7 @@ export const speciesEggMoves = { [Species.CHIKORITA]: [ Moves.SAPPY_SEED, Moves.STONE_AXE, Moves.DRAGON_DANCE, Moves.SPORE ], [Species.CYNDAQUIL]: [ Moves.NASTY_PLOT, Moves.EARTH_POWER, Moves.FIERY_DANCE, Moves.ELECTRO_DRIFT ], - [Species.TOTODILE]: [ Moves.THUNDER_PUNCH, Moves.DRAGON_DANCE, Moves.TRIPLE_AXEL, Moves.SURGING_STRIKES ], + [Species.TOTODILE]: [ Moves.THUNDER_PUNCH, Moves.DRAGON_DANCE, Moves.PLAY_ROUGH, Moves.SURGING_STRIKES ], [Species.SENTRET]: [ Moves.TIDY_UP, Moves.FAKE_OUT, Moves.NUZZLE, Moves.EXTREME_SPEED ], [Species.HOOTHOOT]: [ Moves.CALM_MIND, Moves.ESPER_WING, Moves.AEROBLAST, Moves.BOOMBURST ], [Species.LEDYBA]: [ Moves.POLLEN_PUFF, Moves.MAT_BLOCK, Moves.PARTING_SHOT, Moves.SPORE ], @@ -159,7 +159,7 @@ export const speciesEggMoves = { [Species.VOLBEAT]: [ Moves.BATON_PASS, Moves.STICKY_WEB, Moves.DECORATE, Moves.VICTORY_DANCE ], [Species.ILLUMISE]: [ Moves.PARTING_SHOT, Moves.GLITZY_GLOW, Moves.POWDER, Moves.QUIVER_DANCE ], [Species.GULPIN]: [ Moves.MALIGNANT_CHAIN, Moves.EARTH_POWER, Moves.CALM_MIND, Moves.STRENGTH_SAP ], - [Species.CARVANHA]: [ Moves.THUNDER_FANG, Moves.SWORDS_DANCE, Moves.OBSTRUCT, Moves.SURGING_STRIKES ], + [Species.CARVANHA]: [ Moves.THUNDER_FANG, Moves.GUNK_SHOT, Moves.OBSTRUCT, Moves.SURGING_STRIKES ], [Species.WAILMER]: [ Moves.TAKE_HEART, Moves.COMEUPPANCE, Moves.SLACK_OFF, Moves.STEAM_ERUPTION ], [Species.NUMEL]: [ Moves.TRICK_ROOM, Moves.ENERGY_BALL, Moves.SLACK_OFF, Moves.BLUE_FLARE ], [Species.TORKOAL]: [ Moves.MORNING_SUN, Moves.BURNING_BULWARK, Moves.BODY_PRESS, Moves.HYDRO_STEAM ], @@ -235,7 +235,7 @@ export const speciesEggMoves = { [Species.RIOLU]: [ Moves.THUNDEROUS_KICK, Moves.TACHYON_CUTTER, Moves.TRIPLE_AXEL, Moves.SUNSTEEL_STRIKE ], [Species.HIPPOPOTAS]: [ Moves.SHORE_UP, Moves.STONE_AXE, Moves.BULK_UP, Moves.SALT_CURE ], [Species.SKORUPI]: [ Moves.COIL, Moves.DIRE_CLAW, Moves.CRABHAMMER, Moves.WICKED_BLOW ], - [Species.CROAGUNK]: [ Moves.DIRE_CLAW, Moves.ICE_SPINNER, Moves.THUNDEROUS_KICK, Moves.VICTORY_DANCE ], + [Species.CROAGUNK]: [ Moves.DIRE_CLAW, Moves.TRIPLE_AXEL, Moves.THUNDEROUS_KICK, Moves.VICTORY_DANCE ], [Species.CARNIVINE]: [ Moves.STRENGTH_SAP, Moves.FIRE_LASH, Moves.COIL, Moves.SAPPY_SEED ], [Species.FINNEON]: [ Moves.QUIVER_DANCE, Moves.SPLISHY_SPLASH, Moves.FREEZE_DRY, Moves.OBLIVION_WING ], [Species.MANTYKE]: [ Moves.SPLISHY_SPLASH, Moves.FREEZY_FROST, Moves.NASTY_PLOT, Moves.OBLIVION_WING ], @@ -267,7 +267,7 @@ export const speciesEggMoves = { [Species.PANSEAR]: [ Moves.NASTY_PLOT, Moves.HYDRO_STEAM, Moves.EARTH_POWER, Moves.ERUPTION ], [Species.PANPOUR]: [ Moves.NASTY_PLOT, Moves.ENERGY_BALL, Moves.EARTH_POWER, Moves.WATER_SPOUT ], [Species.MUNNA]: [ Moves.COSMIC_POWER, Moves.AURA_SPHERE, Moves.LUNAR_BLESSING, Moves.MYSTICAL_POWER ], - [Species.PIDOVE]: [ Moves.GUNK_SHOT, Moves.TIDY_UP, Moves.FLOATY_FALL, Moves.TRIPLE_ARROWS ], + [Species.PIDOVE]: [ Moves.SLASH, Moves.TIDY_UP, Moves.FLOATY_FALL, Moves.TRIPLE_ARROWS ], [Species.BLITZLE]: [ Moves.HORN_LEECH, Moves.SWORDS_DANCE, Moves.FLARE_BLITZ, Moves.BOLT_STRIKE ], [Species.ROGGENROLA]: [ Moves.BODY_PRESS, Moves.CURSE, Moves.SHORE_UP, Moves.DIAMOND_STORM ], [Species.WOOBAT]: [ Moves.ESPER_WING, Moves.STORED_POWER, Moves.MYSTICAL_FIRE, Moves.OBLIVION_WING ], @@ -282,7 +282,7 @@ export const speciesEggMoves = { [Species.COTTONEE]: [ Moves.POLLEN_PUFF, Moves.PARTING_SHOT, Moves.SLEEP_POWDER, Moves.SEED_FLARE ], [Species.PETILIL]: [ Moves.THUNDEROUS_KICK, Moves.SPARKLING_ARIA, Moves.FIERY_DANCE, Moves.FLOWER_TRICK ], [Species.BASCULIN]: [ Moves.LAST_RESPECTS, Moves.CLOSE_COMBAT, Moves.SPLISHY_SPLASH, Moves.NO_RETREAT ], - [Species.SANDILE]: [ Moves.DIRE_CLAW, Moves.HEADLONG_RUSH, Moves.FIRE_LASH, Moves.WICKED_BLOW ], + [Species.SANDILE]: [ Moves.DIRE_CLAW, Moves.SUCKER_PUNCH, Moves.FIRE_LASH, Moves.HEADLONG_RUSH ], [Species.DARUMAKA]: [ Moves.DRAIN_PUNCH, Moves.ZIPPY_ZAP, Moves.HEADLONG_RUSH, Moves.PYRO_BALL ], [Species.MARACTUS]: [ Moves.EARTH_POWER, Moves.QUIVER_DANCE, Moves.FIERY_DANCE, Moves.SEED_FLARE ], [Species.DWEBBLE]: [ Moves.CRABHAMMER, Moves.STONE_AXE, Moves.LEECH_LIFE, Moves.MIGHTY_CLEAVE ], @@ -327,8 +327,8 @@ export const speciesEggMoves = { [Species.DEINO]: [ Moves.FIERY_WRATH, Moves.ESPER_WING, Moves.SLUDGE_BOMB, Moves.FICKLE_BEAM ], [Species.LARVESTA]: [ Moves.THUNDERBOLT, Moves.DAZZLING_GLEAM, Moves.EARTH_POWER, Moves.HYDRO_STEAM ], [Species.COBALION]: [ Moves.BEHEMOTH_BLADE, Moves.MIGHTY_CLEAVE, Moves.CEASELESS_EDGE, Moves.VICTORY_DANCE ], - [Species.TERRAKION]: [ Moves.MIGHTY_CLEAVE, Moves.HEADLONG_RUSH, Moves.CEASELESS_EDGE, Moves.VICTORY_DANCE ], - [Species.VIRIZION]: [ Moves.SAPPY_SEED, Moves.PSYBLADE, Moves.CEASELESS_EDGE, Moves.VICTORY_DANCE ], + [Species.TERRAKION]: [ Moves.MIGHTY_CLEAVE, Moves.HEADLONG_RUSH, Moves.KNOCK_OFF, Moves.VICTORY_DANCE ], + [Species.VIRIZION]: [ Moves.SAPPY_SEED, Moves.PSYBLADE, Moves.STONE_AXE, Moves.VICTORY_DANCE ], [Species.TORNADUS]: [ Moves.SANDSEAR_STORM, Moves.PARTING_SHOT, Moves.SPLISHY_SPLASH, Moves.OBLIVION_WING ], [Species.THUNDURUS]: [ Moves.SANDSEAR_STORM, Moves.HURRICANE, Moves.FROST_BREATH, Moves.ELECTRO_SHOT ], [Species.RESHIRAM]: [ Moves.ENERGY_BALL, Moves.TAKE_HEART, Moves.FICKLE_BEAM, Moves.ERUPTION ], @@ -342,7 +342,7 @@ export const speciesEggMoves = { [Species.CHESPIN]: [ Moves.COMBAT_TORQUE, Moves.SYNTHESIS, Moves.CEASELESS_EDGE, Moves.SAPPY_SEED ], [Species.FENNEKIN]: [ Moves.TWIN_BEAM, Moves.FIERY_DANCE, Moves.THUNDERBOLT, Moves.SPARKLY_SWIRL ], [Species.FROAKIE]: [ Moves.MOONBLAST, Moves.SHELL_SIDE_ARM, Moves.FIERY_WRATH, Moves.STEAM_ERUPTION ], - [Species.BUNNELBY]: [ Moves.DRAIN_PUNCH, Moves.TIDY_UP, Moves.FACADE, Moves.EXTREME_SPEED ], + [Species.BUNNELBY]: [ Moves.DRAIN_PUNCH, Moves.TIDY_UP, Moves.LANDS_WRATH, Moves.EXTREME_SPEED ], [Species.FLETCHLING]: [ Moves.DRILL_RUN, Moves.BURNING_BULWARK, Moves.HEAD_SMASH, Moves.VOLT_TACKLE ], [Species.SCATTERBUG]: [ Moves.FOCUS_BLAST, Moves.AFTER_YOU, Moves.DECORATE, Moves.BLIZZARD ], [Species.LITLEO]: [ Moves.EARTH_POWER, Moves.NASTY_PLOT, Moves.BURNING_BULWARK, Moves.BLUE_FLARE ], @@ -372,7 +372,7 @@ export const speciesEggMoves = { [Species.NOIBAT]: [ Moves.AEROBLAST, Moves.OVERDRIVE, Moves.NASTY_PLOT, Moves.CLANGING_SCALES ], [Species.XERNEAS]: [ Moves.EARTH_POWER, Moves.SPRINGTIDE_STORM, Moves.STRENGTH_SAP, Moves.TAIL_GLOW ], [Species.YVELTAL]: [ Moves.SLUDGE_WAVE, Moves.POWER_TRIP, Moves.FIERY_WRATH, Moves.CLANGOROUS_SOUL ], - [Species.ZYGARDE]: [ Moves.DRAGON_DARTS, Moves.HEAL_ORDER, Moves.CLANGOROUS_SOUL, Moves.DOUBLE_IRON_BASH ], + [Species.ZYGARDE]: [ Moves.DRAGON_DARTS, Moves.V_CREATE, Moves.CLANGOROUS_SOUL, Moves.HEAL_ORDER ], [Species.DIANCIE]: [ Moves.MAGICAL_TORQUE, Moves.FIERY_DANCE, Moves.SHORE_UP, Moves.GEOMANCY ], [Species.HOOPA]: [ Moves.PHOTON_GEYSER, Moves.SECRET_SWORD, Moves.FIERY_WRATH, Moves.SHELL_SMASH ], [Species.VOLCANION]: [ Moves.HYDRO_STEAM, Moves.CALM_MIND, Moves.SEARING_SHOT, Moves.THUNDERCLAP ], @@ -415,7 +415,7 @@ export const speciesEggMoves = { [Species.JANGMO_O]: [ Moves.BODY_PRESS, Moves.SHELL_SIDE_ARM, Moves.SECRET_SWORD, Moves.GLAIVE_RUSH ], [Species.TAPU_KOKO]: [ Moves.MAGICAL_TORQUE, Moves.TRIPLE_AXEL, Moves.SWORDS_DANCE, Moves.BOLT_STRIKE ], [Species.TAPU_LELE]: [ Moves.MOONLIGHT, Moves.NASTY_PLOT, Moves.HEAT_WAVE, Moves.EXPANDING_FORCE ], - [Species.TAPU_BULU]: [ Moves.SAPPY_SEED, Moves.DRAIN_PUNCH, Moves.MAGICAL_TORQUE, Moves.VICTORY_DANCE ], + [Species.TAPU_BULU]: [ Moves.SAPPY_SEED, Moves.LANDS_WRATH, Moves.MAGICAL_TORQUE, Moves.VICTORY_DANCE ], [Species.TAPU_FINI]: [ Moves.SPRINGTIDE_STORM, Moves.EARTH_POWER, Moves.RECOVER, Moves.QUIVER_DANCE ], [Species.COSMOG]: [ Moves.PHOTON_GEYSER, Moves.PRECIPICE_BLADES, Moves.SACRED_FIRE, Moves.ASTRAL_BARRAGE ], [Species.NIHILEGO]: [ Moves.STRENGTH_SAP, Moves.MALIGNANT_CHAIN, Moves.EARTH_POWER, Moves.QUIVER_DANCE ], @@ -505,7 +505,7 @@ export const speciesEggMoves = { [Species.HISUI_VOLTORB]: [ Moves.FROST_BREATH, Moves.NASTY_PLOT, Moves.APPLE_ACID, Moves.ELECTRO_DRIFT ], [Species.HISUI_QWILFISH]: [ Moves.CEASELESS_EDGE, Moves.KNOCK_OFF, Moves.RECOVER, Moves.FISHIOUS_REND ], [Species.HISUI_SNEASEL]: [ Moves.DRAIN_PUNCH, Moves.KNOCK_OFF, Moves.PSYCHIC_FANGS, Moves.TRIPLE_AXEL ], - [Species.HISUI_ZORUA]: [ Moves.MOONBLAST, Moves.HYPER_VOICE, Moves.PARTING_SHOT, Moves.BLOOD_MOON ], + [Species.HISUI_ZORUA]: [ Moves.MOONBLAST, Moves.SECRET_SWORD, Moves.PARTING_SHOT, Moves.BLOOD_MOON ], [Species.SPRIGATITO]: [ Moves.FIRE_LASH, Moves.TRIPLE_AXEL, Moves.SUCKER_PUNCH, Moves.WICKED_BLOW ], [Species.FUECOCO]: [ Moves.ALLURING_VOICE, Moves.SLACK_OFF, Moves.OVERDRIVE, Moves.MOONGEIST_BEAM ], @@ -538,7 +538,7 @@ export const speciesEggMoves = { [Species.CYCLIZAR]: [ Moves.PARTING_SHOT, Moves.FIRE_LASH, Moves.MAGICAL_TORQUE, Moves.GLAIVE_RUSH ], [Species.ORTHWORM]: [ Moves.SIZZLY_SLIDE, Moves.COIL, Moves.BODY_PRESS, Moves.SHORE_UP ], [Species.GLIMMET]: [ Moves.CALM_MIND, Moves.GIGA_DRAIN, Moves.FIERY_DANCE, Moves.MALIGNANT_CHAIN ], - [Species.GREAVARD]: [ Moves.SHADOW_BONE, Moves.YAWN, Moves.SHORE_UP, Moves.COLLISION_COURSE ], + [Species.GREAVARD]: [ Moves.SHADOW_BONE, Moves.SIZZLY_SLIDE, Moves.SHORE_UP, Moves.COLLISION_COURSE ], [Species.FLAMIGO]: [ Moves.THUNDEROUS_KICK, Moves.TRIPLE_AXEL, Moves.FLOATY_FALL, Moves.VICTORY_DANCE ], [Species.CETODDLE]: [ Moves.ZING_ZAP, Moves.HIGH_HORSEPOWER, Moves.SLACK_OFF, Moves.DRAGON_DANCE ], [Species.VELUZA]: [ Moves.PSYBLADE, Moves.LEAF_BLADE, Moves.CEASELESS_EDGE, Moves.BITTER_BLADE ], @@ -550,7 +550,7 @@ export const speciesEggMoves = { [Species.FLUTTER_MANE]: [ Moves.MOONLIGHT, Moves.NASTY_PLOT, Moves.EARTH_POWER, Moves.MOONGEIST_BEAM ], [Species.SLITHER_WING]: [ Moves.MIGHTY_CLEAVE, Moves.THUNDEROUS_KICK, Moves.FIRE_LASH, Moves.VICTORY_DANCE ], [Species.SANDY_SHOCKS]: [ Moves.MORNING_SUN, Moves.ICE_BEAM, Moves.NASTY_PLOT, Moves.THUNDERCLAP ], - [Species.IRON_TREADS]: [ Moves.FUSION_BOLT, Moves.BULK_UP, Moves.SHORE_UP, Moves.SUNSTEEL_STRIKE ], + [Species.IRON_TREADS]: [ Moves.FUSION_BOLT, Moves.SHIFT_GEAR, Moves.SHORE_UP, Moves.SUNSTEEL_STRIKE ], [Species.IRON_BUNDLE]: [ Moves.EARTH_POWER, Moves.SPLISHY_SPLASH, Moves.VOLT_SWITCH, Moves.NASTY_PLOT ], [Species.IRON_HANDS]: [ Moves.DRAIN_PUNCH, Moves.BULK_UP, Moves.PLASMA_FISTS, Moves.ICE_HAMMER ], [Species.IRON_JUGULIS]: [ Moves.FIERY_WRATH, Moves.ROOST, Moves.NASTY_PLOT, Moves.OBLIVION_WING ], @@ -562,7 +562,7 @@ export const speciesEggMoves = { [Species.CHIEN_PAO]: [ Moves.KNOCK_OFF, Moves.PARTING_SHOT, Moves.TRIPLE_AXEL, Moves.BITTER_BLADE ], [Species.TING_LU]: [ Moves.SHORE_UP, Moves.CEASELESS_EDGE, Moves.SAPPY_SEED, Moves.PRECIPICE_BLADES ], [Species.CHI_YU]: [ Moves.FIERY_WRATH, Moves.HYDRO_STEAM, Moves.MORNING_SUN, Moves.BLUE_FLARE ], - [Species.ROARING_MOON]: [ Moves.FIRE_LASH, Moves.DRAGON_HAMMER, Moves.SUCKER_PUNCH, Moves.WICKED_BLOW ], + [Species.ROARING_MOON]: [ Moves.FIRE_LASH, Moves.DRAGON_HAMMER, Moves.METEOR_MASH, Moves.DRAGON_ASCENT ], [Species.IRON_VALIANT]: [ Moves.PLASMA_FISTS, Moves.NO_RETREAT, Moves.SECRET_SWORD, Moves.MAGICAL_TORQUE ], [Species.KORAIDON]: [ Moves.SUNSTEEL_STRIKE, Moves.SOLAR_BLADE, Moves.DRAGON_DARTS, Moves.BITTER_BLADE ], [Species.MIRAIDON]: [ Moves.ICE_BEAM, Moves.CLANGOROUS_SOUL, Moves.CORE_ENFORCER, Moves.RISING_VOLTAGE ], @@ -577,7 +577,7 @@ export const speciesEggMoves = { [Species.RAGING_BOLT]: [ Moves.NASTY_PLOT, Moves.FLAMETHROWER, Moves.MORNING_SUN, Moves.ELECTRO_DRIFT ], [Species.IRON_BOULDER]: [ Moves.PSYBLADE, Moves.KOWTOW_CLEAVE, Moves.STONE_AXE, Moves.BITTER_BLADE ], [Species.IRON_CROWN]: [ Moves.NASTY_PLOT, Moves.SECRET_SWORD, Moves.PSYSTRIKE, Moves.ELECTRO_DRIFT ], - [Species.TERAPAGOS]: [ Moves.MOONBLAST, Moves.RECOVER, Moves.ICE_BEAM, Moves.SHELL_SMASH ], + [Species.TERAPAGOS]: [ Moves.MOONBLAST, Moves.NASTY_PLOT, Moves.ASTRAL_BARRAGE, Moves.RECOVER ], [Species.PECHARUNT]: [ Moves.TAKE_HEART, Moves.BODY_PRESS, Moves.SAPPY_SEED, Moves.ASTRAL_BARRAGE ], [Species.PALDEA_TAUROS]: [ Moves.NO_RETREAT, Moves.BLAZING_TORQUE, Moves.AQUA_STEP, Moves.THUNDEROUS_KICK ], [Species.PALDEA_WOOPER]: [ Moves.STONE_AXE, Moves.RECOVER, Moves.BANEFUL_BUNKER, Moves.BARB_BARRAGE ], diff --git a/src/data/balance/passives.ts b/src/data/balance/passives.ts index df347909d49..c613be0137b 100644 --- a/src/data/balance/passives.ts +++ b/src/data/balance/passives.ts @@ -555,7 +555,7 @@ export const starterPassiveAbilities: StarterPassiveAbilities = { [Species.FLUTTER_MANE]: { 0: Abilities.DAZZLING }, [Species.SLITHER_WING]: { 0: Abilities.SCRAPPY }, [Species.SANDY_SHOCKS]: { 0: Abilities.ELECTRIC_SURGE }, - [Species.IRON_TREADS]: { 0: Abilities.STEELY_SPIRIT }, + [Species.IRON_TREADS]: { 0: Abilities.DAUNTLESS_SHIELD }, [Species.IRON_BUNDLE]: { 0: Abilities.SNOW_WARNING }, [Species.IRON_HANDS]: { 0: Abilities.IRON_FIST }, [Species.IRON_JUGULIS]: { 0: Abilities.LIGHTNING_ROD }, @@ -567,7 +567,7 @@ export const starterPassiveAbilities: StarterPassiveAbilities = { [Species.CHIEN_PAO]: { 0: Abilities.INTIMIDATE }, [Species.TING_LU]: { 0: Abilities.STAMINA }, [Species.CHI_YU]: { 0: Abilities.BERSERK }, - [Species.ROARING_MOON]: { 0: Abilities.TOUGH_CLAWS }, + [Species.ROARING_MOON]: { 0: Abilities.INTIMIDATE }, [Species.IRON_VALIANT]: { 0: Abilities.NEUROFORCE }, [Species.KORAIDON]: { 0: Abilities.OPPORTUNIST }, [Species.MIRAIDON]: { 0: Abilities.OPPORTUNIST }, @@ -582,7 +582,7 @@ export const starterPassiveAbilities: StarterPassiveAbilities = { [Species.RAGING_BOLT]: { 0: Abilities.BEAST_BOOST }, [Species.IRON_BOULDER]: { 0: Abilities.SHARPNESS }, [Species.IRON_CROWN]: { 0: Abilities.SHARPNESS }, - [Species.TERAPAGOS]: { 0: Abilities.SOUL_HEART }, + [Species.TERAPAGOS]: { 0: Abilities.SHIELD_DUST }, [Species.PECHARUNT]: { 0: Abilities.TOXIC_CHAIN }, [Species.PALDEA_TAUROS]: { 0: Abilities.ADAPTABILITY }, [Species.PALDEA_WOOPER]: { 0: Abilities.THICK_FAT }, diff --git a/src/data/balance/starters.ts b/src/data/balance/starters.ts index dcf0766d005..10263f895b3 100644 --- a/src/data/balance/starters.ts +++ b/src/data/balance/starters.ts @@ -290,7 +290,7 @@ export const speciesStarterCosts = { [Species.SHAYMIN]: 6, [Species.ARCEUS]: 9, - [Species.VICTINI]: 7, + [Species.VICTINI]: 6, [Species.SNIVY]: 3, [Species.TEPIG]: 3, [Species.OSHAWOTT]: 3, @@ -611,7 +611,7 @@ export const speciesStarterCosts = { [Species.RAGING_BOLT]: 7, [Species.IRON_BOULDER]: 7, [Species.IRON_CROWN]: 7, - [Species.TERAPAGOS]: 8, + [Species.TERAPAGOS]: 9, [Species.PECHARUNT]: 6, [Species.PALDEA_TAUROS]: 5, [Species.PALDEA_WOOPER]: 3, diff --git a/test/mystery-encounter/mystery-encounter-utils.test.ts b/test/mystery-encounter/mystery-encounter-utils.test.ts index dacfb5cc96e..6b467b9fa81 100644 --- a/test/mystery-encounter/mystery-encounter-utils.test.ts +++ b/test/mystery-encounter/mystery-encounter-utils.test.ts @@ -223,14 +223,14 @@ describe("Mystery Encounter Utils", () => { }); it("excludes species from search", () => { - // Only 9 tiers are: Koraidon, Miraidon, Arceus, Rayquaza, Kyogre, Groudon, Zacian - const result = getRandomSpeciesByStarterCost(9, [ Species.KORAIDON, Species.MIRAIDON, Species.ARCEUS, Species.RAYQUAZA, Species.KYOGRE, Species.GROUDON ]); + // Only 9 tiers are: Kyogre, Groudon, Rayquaza, Arceus, Zacian, Koraidon, Miraidon, Terapagos + const result = getRandomSpeciesByStarterCost(9, [ Species.KYOGRE, Species.GROUDON, Species.RAYQUAZA, Species.ARCEUS, Species.KORAIDON, Species.MIRAIDON, Species.TERAPAGOS ]); const pokeSpecies = getPokemonSpecies(result); expect(pokeSpecies.speciesId).toBe(Species.ZACIAN); }); it("gets species of specified types", () => { - // Only 9 tiers are: Koraidon, Miraidon, Arceus, Rayquaza, Kyogre, Groudon, Zacian + // Only 9 tiers are: Kyogre, Groudon, Rayquaza, Arceus, Zacian, Koraidon, Miraidon, Terapagos const result = getRandomSpeciesByStarterCost(9, undefined, [ Type.GROUND ]); const pokeSpecies = getPokemonSpecies(result); expect(pokeSpecies.speciesId).toBe(Species.GROUDON); From b9a853ed2e61c7dddfb786e2b7249e81ca481422 Mon Sep 17 00:00:00 2001 From: Dean <69436131+emdeann@users.noreply.github.com> Date: Tue, 25 Feb 2025 21:48:44 -0800 Subject: [PATCH 6/7] [Bug] Fix canApplyAbility check but for real this time (#5418) * Move canApplyAbility check * Fix suppressed abilities not activated properly * Move canApplyAbility to applySingleAbAttrs --------- Co-authored-by: damocleas --- src/data/ability.ts | 12 +++++++----- src/data/arena-tag.ts | 7 +++++++ src/field/pokemon.ts | 4 ++-- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/data/ability.ts b/src/data/ability.ts index 5f19af8cea4..dcbaa4026f4 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -5152,6 +5152,10 @@ function applySingleAbAttrs( showAbilityInstant: boolean = false, messages: string[] = [] ) { + if (!pokemon?.canApplyAbility(passive) || (passive && (pokemon.getPassiveAbility().id === pokemon.getAbility().id))) { + return; + } + const ability = passive ? pokemon.getPassiveAbility() : pokemon.getAbility(); if (gainedMidTurn && ability.getAttrs(attrType).some(attr => attr instanceof PostSummonAbAttr && !attr.shouldActivateOnGain())) { return; @@ -5445,12 +5449,10 @@ function applyAbAttrsInternal( gainedMidTurn: boolean = false ) { for (const passive of [ false, true ]) { - if (!pokemon?.canApplyAbility(passive) || (passive && (pokemon.getPassiveAbility().id === pokemon.getAbility().id))) { - continue; + if (pokemon) { + applySingleAbAttrs(pokemon, passive, attrType, applyFunc, args, gainedMidTurn, simulated, showAbilityInstant, messages); + globalScene.clearPhaseQueueSplice(); } - - applySingleAbAttrs(pokemon, passive, attrType, applyFunc, args, gainedMidTurn, simulated, showAbilityInstant, messages); - globalScene.clearPhaseQueueSplice(); } } diff --git a/src/data/arena-tag.ts b/src/data/arena-tag.ts index d034ccf83b8..580ede9596c 100644 --- a/src/data/arena-tag.ts +++ b/src/data/arena-tag.ts @@ -1230,10 +1230,12 @@ export class FairyLockTag extends ArenaTag { */ export class SuppressAbilitiesTag extends ArenaTag { private sourceCount: number; + private beingRemoved: boolean; constructor(sourceId: number) { super(ArenaTagType.NEUTRALIZING_GAS, 0, undefined, sourceId); this.sourceCount = 1; + this.beingRemoved = false; } public override onAdd(arena: Arena): void { @@ -1267,6 +1269,7 @@ export class SuppressAbilitiesTag extends ArenaTag { } public override onRemove(arena: Arena, quiet: boolean = false) { + this.beingRemoved = true; if (!quiet) { globalScene.queueMessage(i18next.t("arenaTag:neutralizingGasOnRemove")); } @@ -1282,6 +1285,10 @@ export class SuppressAbilitiesTag extends ArenaTag { public shouldApplyToSelf(): boolean { return this.sourceCount > 1; } + + public isBeingRemoved() { + return this.beingRemoved; + } } // TODO: swap `sourceMove` and `sourceId` and make `sourceMove` an optional parameter diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index bc3b9b1403f..f0486a8f111 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -1501,8 +1501,8 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { * Suppresses an ability and calls its onlose attributes */ public suppressAbility() { - this.summonData.abilitySuppressed = true; [ true, false ].forEach((passive) => applyOnLoseAbAttrs(this, passive)); + this.summonData.abilitySuppressed = true; } /** @@ -1563,7 +1563,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { return false; } const suppressAbilitiesTag = arena.getTag(ArenaTagType.NEUTRALIZING_GAS) as SuppressAbilitiesTag; - if (this.isOnField() && suppressAbilitiesTag) { + if (this.isOnField() && suppressAbilitiesTag && !suppressAbilitiesTag.isBeingRemoved()) { const thisAbilitySuppressing = ability.hasAttr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr); const hasSuppressingAbility = this.hasAbilityWithAttr(PreLeaveFieldRemoveSuppressAbilitiesSourceAbAttr, false); // Neutralizing gas is up - suppress abilities unless they are unsuppressable or this pokemon is responsible for the gas From 6e8a4b287c228d6978ce67a461929105c18d0327 Mon Sep 17 00:00:00 2001 From: Wlowscha <54003515+Wlowscha@users.noreply.github.com> Date: Wed, 26 Feb 2025 07:05:39 +0100 Subject: [PATCH 7/7] [UI/UX] Pressing B on filter bar resets filters (#5404) * Make dropdown update selection when reset to default * Function to resect selection in filter bar * Pressing B on filter now closes it or resets instead of going to top of starters * Filter changes to starter select screen * Method to get column at a given index in filter bar * Specific reset behavior for caught filter in starter select menu * Apply suggestions from code review Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> * Update src/ui/starter-select-ui-handler.ts Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> --------- Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com> Co-authored-by: damocleas Co-authored-by: Madmadness65 <59298170+Madmadness65@users.noreply.github.com> --- src/ui/dropdown.ts | 2 ++ src/ui/filter-bar.ts | 14 ++++++++++++++ src/ui/pokedex-ui-handler.ts | 13 ++++--------- src/ui/starter-select-ui-handler.ts | 29 ++++++++++++++++++----------- 4 files changed, 38 insertions(+), 20 deletions(-) diff --git a/src/ui/dropdown.ts b/src/ui/dropdown.ts index 718058c7f99..ec433a35733 100644 --- a/src/ui/dropdown.ts +++ b/src/ui/dropdown.ts @@ -629,6 +629,8 @@ export class DropDown extends Phaser.GameObjects.Container { } } } + + this.onChange(); } } diff --git a/src/ui/filter-bar.ts b/src/ui/filter-bar.ts index 1eba81247d4..8ef910f954a 100644 --- a/src/ui/filter-bar.ts +++ b/src/ui/filter-bar.ts @@ -86,6 +86,15 @@ export class FilterBar extends Phaser.GameObjects.Container { return this.dropDowns[this.columns.indexOf(col)]; } + /** + * Get the DropDownColumn associated to a given index + * @param index the index of the column to retrieve + * @returns the associated DropDownColumn if it exists, undefined otherwise + */ + public getColumn(index: number): DropDownColumn { + return this.columns[index]; + } + /** * Highlight the labels of the FilterBar if the filters are different from their default values */ @@ -185,6 +194,11 @@ export class FilterBar extends Phaser.GameObjects.Container { return this.getFilter(col).getVals(); } + public resetSelection(col: DropDownColumn): void { + this.dropDowns[col].resetToDefault(); + this.updateFilterLabels(); + } + setValsToDefault(): void { for (const dropDown of this.dropDowns) { dropDown.resetToDefault(); diff --git a/src/ui/pokedex-ui-handler.ts b/src/ui/pokedex-ui-handler.ts index b3655d80fa1..a18f138e4f7 100644 --- a/src/ui/pokedex-ui-handler.ts +++ b/src/ui/pokedex-ui-handler.ts @@ -898,16 +898,11 @@ export default class PokedexUiHandler extends MessageUiHandler { if (this.filterMode && this.filterBar.openDropDown) { // CANCEL with a filter menu open > close it this.filterBar.toggleDropDown(this.filterBarCursor); - - // if there are possible pokemon go the first one of the list - if (numberOfStarters > 0) { - this.setFilterMode(false); - this.scrollCursor = 0; - this.updateScroll(); - this.setCursor(0); - } success = true; - + } else if (this.filterMode && !this.filterBar.getFilter(this.filterBarCursor).hasDefaultValues()) { + this.filterBar.resetSelection(this.filterBarCursor); + this.updateStarters(); + success = true; } else if (this.filterTextMode && !(this.filterText.getValue(this.filterTextCursor) === this.filterText.defaultText)) { this.filterText.resetSelection(this.filterTextCursor); success = true; diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 80a2baf7f55..771554f18de 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -1080,10 +1080,18 @@ export default class StarterSelectUiHandler extends MessageUiHandler { /** * Set the selections for all filters to their default starting value */ - resetFilters() : void { + public resetFilters(): void { + this.filterBar.setValsToDefault(); + this.resetCaughtDropdown(); + } + + /** + * Set default value for the caught dropdown, which only shows caught mons + */ + public resetCaughtDropdown(): void { const caughtDropDown: DropDown = this.filterBar.getFilter(DropDownColumn.CAUGHT); - this.filterBar.setValsToDefault(); + caughtDropDown.resetToDefault(); // initial setting, in caught filter, select the options excluding the uncaught option for (let i = 0; i < caughtDropDown.options.length; i++) { @@ -1323,16 +1331,15 @@ export default class StarterSelectUiHandler extends MessageUiHandler { if (this.filterMode && this.filterBar.openDropDown) { // CANCEL with a filter menu open > close it this.filterBar.toggleDropDown(this.filterBarCursor); - - // if there are possible starters go the first one of the list - if (numberOfStarters > 0) { - this.setFilterMode(false); - this.scrollCursor = 0; - this.updateScroll(); - this.setCursor(0); - } success = true; - + } else if (this.filterMode && !this.filterBar.getFilter(this.filterBar.getColumn(this.filterBarCursor)).hasDefaultValues()) { + if (this.filterBar.getColumn(this.filterBarCursor) === DropDownColumn.CAUGHT) { + this.resetCaughtDropdown(); + } else { + this.filterBar.resetSelection(this.filterBarCursor); + } + this.updateStarters(); + success = true; } else if (this.statsMode) { this.toggleStatsMode(false); success = true;