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/data/move.ts b/src/data/move.ts index 26b182ec5db..18f4b220911 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -1866,11 +1866,14 @@ export class HalfSacrificialAttr extends MoveEffectAttr { export class AddSubstituteAttr extends MoveEffectAttr { /** The ratio of the user's max HP that is required to apply this effect */ private hpCost: number; + /** Whether the damage taken should be rounded up (Shed Tail rounds up) */ + private roundUp: boolean; - constructor(hpCost: number = 0.25) { + constructor(hpCost: number, roundUp: boolean) { super(true); this.hpCost = hpCost; + this.roundUp = roundUp; } /** @@ -1886,7 +1889,8 @@ export class AddSubstituteAttr extends MoveEffectAttr { return false; } - user.damageAndUpdate(Math.floor(user.getMaxHp() * this.hpCost), HitResult.OTHER, false, true, true); + const damageTaken = this.roundUp ? Math.ceil(user.getMaxHp() * this.hpCost) : Math.floor(user.getMaxHp() * this.hpCost); + user.damageAndUpdate(damageTaken, HitResult.OTHER, false, true, true); user.addTag(BattlerTagType.SUBSTITUTE, 0, move.id, user.id); return true; } @@ -1899,7 +1903,7 @@ export class AddSubstituteAttr extends MoveEffectAttr { } getCondition(): MoveConditionFunc { - return (user, target, move) => !user.getTag(SubstituteTag) && user.hp > Math.floor(user.getMaxHp() * this.hpCost) && user.getMaxHp() > 1; + return (user, target, move) => !user.getTag(SubstituteTag) && user.hp > (this.roundUp ? Math.ceil(user.getMaxHp() * this.hpCost) : Math.floor(user.getMaxHp() * this.hpCost)) && user.getMaxHp() > 1; } /** @@ -9036,7 +9040,7 @@ export function initMoves() { .attr(HighCritAttr) .slicingMove(), new SelfStatusMove(Moves.SUBSTITUTE, Type.NORMAL, -1, 10, -1, 0, 1) - .attr(AddSubstituteAttr), + .attr(AddSubstituteAttr, 0.25, false), new AttackMove(Moves.STRUGGLE, Type.NORMAL, MoveCategory.PHYSICAL, 50, -1, 1, -1, 0, 1) .attr(RecoilAttr, true, 0.25, true) .attr(TypelessAttr) @@ -11382,7 +11386,7 @@ export function initMoves() { .attr(MovePowerMultiplierAttr, (user, target, move) => target.getAttackTypeEffectiveness(move.type, user) >= 2 ? 5461 / 4096 : 1) .makesContact(), new SelfStatusMove(Moves.SHED_TAIL, Type.NORMAL, -1, 10, -1, 0, 9) - .attr(AddSubstituteAttr, 0.5) + .attr(AddSubstituteAttr, 0.5, true) .attr(ForceSwitchOutAttr, true, SwitchType.SHED_TAIL) .condition(failIfLastInPartyCondition), new SelfStatusMove(Moves.CHILLY_RECEPTION, Type.ICE, -1, 10, -1, 0, 9) 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 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; diff --git a/test/moves/shed_tail.test.ts b/test/moves/shed_tail.test.ts index 37f046ba2fa..be746aacd78 100644 --- a/test/moves/shed_tail.test.ts +++ b/test/moves/shed_tail.test.ts @@ -46,11 +46,8 @@ describe("Moves - Shed Tail", () => { expect(feebas).not.toBe(magikarp); expect(feebas.hp).toBe(feebas.getMaxHp()); - // Note: Shed Tail's HP cost is currently not accurate to mainline, as it - // should cost ceil(maxHP / 2) instead of max(floor(maxHp / 2), 1). The current - // implementation is consistent with Substitute's HP cost logic, but that's not - // the case in mainline for some reason :regiDespair:. - expect(magikarp.hp).toBe(Math.ceil(magikarp.getMaxHp() / 2)); + // Note: Altered the test to be consistent with the correct HP cost :yipeevee_static: + expect(magikarp.hp).toBe(Math.floor(magikarp.getMaxHp() / 2)); expect(substituteTag).toBeDefined(); expect(substituteTag?.hp).toBe(Math.floor(magikarp.getMaxHp() / 4)); });