diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index 66d00d950d2..bec3e3f6543 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -396,7 +396,23 @@ export abstract class AbAttr { } } -export class BlockRecoilDamageAttr extends AbAttr { +/** + * Abstract class for ability attributes that simply cancel an interaction + * + * @remarks + * Abilities that have simple cancel interactions (e.g. {@linkcode BlockRecoilDamageAttr}) can extend this class to reuse the `canApply` and `apply` logic + */ +abstract class CancelInteractionAbAttr extends AbAttr { + override canApply({ cancelled }: AbAttrParamsWithCancel): boolean { + return !cancelled.value; + } + + override apply({ cancelled }: AbAttrParamsWithCancel): void { + cancelled.value = true; + } +} + +export class BlockRecoilDamageAttr extends CancelInteractionAbAttr { private declare readonly _: never; constructor() { super(false); @@ -592,11 +608,7 @@ export class PreDefendFullHpEndureAbAttr extends PreDefendAbAttr { } } -export class BlockItemTheftAbAttr extends AbAttr { - override apply({ cancelled }: AbAttrParamsWithCancel): void { - cancelled.value = true; - } - +export class BlockItemTheftAbAttr extends CancelInteractionAbAttr { getTriggerMessage({ pokemon }: AbAttrBaseParams, abilityName: string) { return i18next.t("abilityTriggers:blockItemTheft", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), @@ -869,8 +881,9 @@ export interface FieldPriorityMoveImmunityAbAttrParams extends AugmentMoveIntera } export class FieldPriorityMoveImmunityAbAttr extends PreDefendAbAttr { - override canApply({ move, opponent: attacker }: FieldPriorityMoveImmunityAbAttrParams): boolean { + override canApply({ move, opponent: attacker, cancelled }: FieldPriorityMoveImmunityAbAttrParams): boolean { return ( + !cancelled.value && !(move.moveTarget === MoveTarget.USER || move.moveTarget === MoveTarget.NEAR_ALLY) && move.getPriority(attacker) > 0 && !move.isMultiTarget() @@ -897,10 +910,8 @@ export class MoveImmunityAbAttr extends PreDefendAbAttr { this.immuneCondition = immuneCondition; } - override canApply({ pokemon, opponent: attacker, move }: MoveImmunityAbAttrParams): boolean { - // TODO: Investigate whether this method should be checking against `cancelled`, specifically - // if not checking this results in multiple flyouts showing when multiple abilities block the move. - return this.immuneCondition(pokemon, attacker, move); + override canApply({ pokemon, opponent: attacker, move, cancelled }: MoveImmunityAbAttrParams): boolean { + return !cancelled.value && this.immuneCondition(pokemon, attacker, move); } override apply({ cancelled }: MoveImmunityAbAttrParams): void { @@ -1591,12 +1602,7 @@ export interface FieldPreventExplosiveMovesAbAttrParams extends AbAttrBaseParams cancelled: BooleanHolder; } -export class FieldPreventExplosiveMovesAbAttr extends AbAttr { - // TODO: investigate whether we need to check against `cancelled` in a `canApply` method - override apply({ cancelled }: FieldPreventExplosiveMovesAbAttrParams): void { - cancelled.value = true; - } -} +export class FieldPreventExplosiveMovesAbAttr extends CancelInteractionAbAttr {} export interface FieldMultiplyStatAbAttrParams extends AbAttrBaseParams { /** The kind of stat that is being checked for modification */ @@ -2535,15 +2541,11 @@ export class IgnoreOpponentStatStagesAbAttr extends AbAttr { * Abilities with this attribute prevent the user from being affected by Intimidate. * @sealed */ -export class IntimidateImmunityAbAttr extends AbAttr { +export class IntimidateImmunityAbAttr extends CancelInteractionAbAttr { constructor() { super(false); } - override apply({ cancelled }: AbAttrParamsWithCancel): void { - cancelled.value = true; - } - getTriggerMessage({ pokemon }: AbAttrParamsWithCancel, abilityName: string, ..._args: any[]): string { return i18next.t("abilityTriggers:intimidateImmunity", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), @@ -3577,8 +3579,8 @@ export class ProtectStatAbAttr extends PreStatStageChangeAbAttr { this.protectedStat = protectedStat; } - override canApply({ stat }: PreStatStageChangeAbAttrParams): boolean { - return isNullOrUndefined(this.protectedStat) || stat === this.protectedStat; + override canApply({ stat, cancelled }: PreStatStageChangeAbAttrParams): boolean { + return !cancelled.value && (isNullOrUndefined(this.protectedStat) || stat === this.protectedStat); } /** @@ -3669,8 +3671,11 @@ export class PreSetStatusEffectImmunityAbAttr extends PreSetStatusAbAttr { this.immuneEffects = immuneEffects; } - override canApply({ effect }: PreSetStatusAbAttrParams): boolean { - return (this.immuneEffects.length === 0 && effect !== StatusEffect.FAINT) || this.immuneEffects.includes(effect); + override canApply({ effect, cancelled }: PreSetStatusAbAttrParams): boolean { + return ( + !cancelled.value && + ((this.immuneEffects.length === 0 && effect !== StatusEffect.FAINT) || this.immuneEffects.includes(effect)) + ); } /** @@ -3720,7 +3725,8 @@ export interface UserFieldStatusEffectImmunityAbAttrParams extends AbAttrBasePar /** * Provides immunity to status effects to the user's field. */ -export class UserFieldStatusEffectImmunityAbAttr extends AbAttr { +export class UserFieldStatusEffectImmunityAbAttr extends CancelInteractionAbAttr { + private declare readonly _: never; protected immuneEffects: StatusEffect[]; /** @@ -3740,12 +3746,8 @@ export class UserFieldStatusEffectImmunityAbAttr extends AbAttr { ); } - /** - * Set the `cancelled` value to true, indicating that the status effect is prevented. - */ - override apply({ cancelled }: UserFieldStatusEffectImmunityAbAttrParams): void { - cancelled.value = true; - } + // declare here to allow typescript to allow us to override `canApply` method without adjusting params + declare apply: (params: UserFieldStatusEffectImmunityAbAttrParams) => void; } /** @@ -3776,14 +3778,7 @@ export class ConditionalUserFieldStatusEffectImmunityAbAttr extends UserFieldSta * @returns Whether the ability can be applied to cancel the status effect. */ override canApply(params: UserFieldStatusEffectImmunityAbAttrParams): boolean { - return this.condition(params.target, params.source) && super.canApply(params); - } - - /** - * Set the `cancelled` value to true, indicating that the status effect is prevented. - */ - override apply({ cancelled }: UserFieldStatusEffectImmunityAbAttrParams): void { - cancelled.value = true; + return !params.cancelled.value && this.condition(params.target, params.source) && super.canApply(params); } } @@ -4019,20 +4014,16 @@ export class ConditionalCritAbAttr extends AbAttr { } } -export class BlockNonDirectDamageAbAttr extends AbAttr { +export class BlockNonDirectDamageAbAttr extends CancelInteractionAbAttr { constructor() { super(false); } - - override apply({ cancelled }: AbAttrParamsWithCancel): void { - cancelled.value = true; - } } /** * This attribute will block any status damage that you put in the parameter. */ -export class BlockStatusDamageAbAttr extends AbAttr { +export class BlockStatusDamageAbAttr extends CancelInteractionAbAttr { private effects: StatusEffect[]; /** @@ -4044,20 +4035,12 @@ export class BlockStatusDamageAbAttr extends AbAttr { this.effects = effects; } - override canApply({ pokemon }: AbAttrParamsWithCancel): boolean { - return !!pokemon.status?.effect && this.effects.includes(pokemon.status.effect); - } - - override apply({ cancelled }: AbAttrParamsWithCancel): void { - cancelled.value = true; + override canApply({ pokemon, cancelled }: AbAttrParamsWithCancel): boolean { + return !cancelled.value && !!pokemon.status?.effect && this.effects.includes(pokemon.status.effect); } } -export class BlockOneHitKOAbAttr extends AbAttr { - override apply({ cancelled }: AbAttrParamsWithCancel): void { - cancelled.value = true; - } -} +export class BlockOneHitKOAbAttr extends CancelInteractionAbAttr {} export interface ChangeMovePriorityAbAttrParams extends AbAttrBaseParams { /** The move being used */ @@ -4131,8 +4114,8 @@ export class BlockWeatherDamageAttr extends PreWeatherDamageAbAttr { this.weatherTypes = weatherTypes; } - override canApply({ weather }: PreWeatherEffectAbAttrParams): boolean { - if (!weather) { + override canApply({ weather, cancelled }: PreWeatherEffectAbAttrParams): boolean { + if (!weather || cancelled.value) { return false; } const weatherType = weather.weatherType; @@ -4153,8 +4136,8 @@ export class SuppressWeatherEffectAbAttr extends PreWeatherEffectAbAttr { this.affectsImmutable = affectsImmutable; } - override canApply({ weather }: PreWeatherEffectAbAttrParams): boolean { - if (!weather) { + override canApply({ weather, cancelled }: PreWeatherEffectAbAttrParams): boolean { + if (!weather || cancelled.value) { return false; } return this.affectsImmutable || weather.isImmutable(); @@ -5151,15 +5134,11 @@ export class StatStageChangeCopyAbAttr extends AbAttr { } } -export class BypassBurnDamageReductionAbAttr extends AbAttr { +export class BypassBurnDamageReductionAbAttr extends CancelInteractionAbAttr { private declare readonly _: never; constructor() { super(false); } - - override apply({ cancelled }: AbAttrParamsWithCancel): void { - cancelled.value = true; - } } export interface ReduceBurnDamageAbAttrParams extends AbAttrBaseParams { @@ -5199,14 +5178,7 @@ export class DoubleBerryEffectAbAttr extends AbAttr { * Attribute to prevent opposing berry use while on the field. * Used by {@linkcode AbilityId.UNNERVE}, {@linkcode AbilityId.AS_ONE_GLASTRIER} and {@linkcode AbilityId.AS_ONE_SPECTRIER} */ -export class PreventBerryUseAbAttr extends AbAttr { - /** - * Prevent use of opposing berries. - */ - override apply({ cancelled }: AbAttrParamsWithCancel): void { - cancelled.value = true; - } -} +export class PreventBerryUseAbAttr extends CancelInteractionAbAttr {} /** * A Pokemon with this ability heals by a percentage of their maximum hp after eating a berry @@ -5664,11 +5636,7 @@ export class IncreasePpAbAttr extends AbAttr { } /** @sealed */ -export class ForceSwitchOutImmunityAbAttr extends AbAttr { - override apply({ cancelled }: AbAttrParamsWithCancel): void { - cancelled.value = true; - } -} +export class ForceSwitchOutImmunityAbAttr extends CancelInteractionAbAttr {} export interface ReduceBerryUseThresholdAbAttrParams extends AbAttrBaseParams { /** Holds the hp ratio for the berry to proc, which may be modified by ability application */ @@ -5747,8 +5715,8 @@ export class MoveAbilityBypassAbAttr extends AbAttr { this.moveIgnoreFunc = moveIgnoreFunc || ((_pokemon, _move) => true); } - override canApply({ pokemon, move }: MoveAbilityBypassAbAttrParams): boolean { - return this.moveIgnoreFunc(pokemon, move); + override canApply({ pokemon, move, cancelled }: MoveAbilityBypassAbAttrParams): boolean { + return !cancelled.value && this.moveIgnoreFunc(pokemon, move); } override apply({ cancelled }: MoveAbilityBypassAbAttrParams): void { @@ -5842,8 +5810,8 @@ export class IgnoreTypeImmunityAbAttr extends AbAttr { this.allowedMoveTypes = allowedMoveTypes; } - override canApply({ moveType, defenderType }: IgnoreTypeImmunityAbAttrParams): boolean { - return this.defenderType === defenderType && this.allowedMoveTypes.includes(moveType); + override canApply({ moveType, defenderType, cancelled }: IgnoreTypeImmunityAbAttrParams): boolean { + return !cancelled.value && this.defenderType === defenderType && this.allowedMoveTypes.includes(moveType); } override apply({ cancelled }: IgnoreTypeImmunityAbAttrParams): void { diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 1c64b28fa75..097b7b9d68d 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -8140,9 +8140,12 @@ const failIfSingleBattle: MoveConditionFunc = (user, target, move) => globalScen const failIfDampCondition: MoveConditionFunc = (user, target, move) => { const cancelled = new BooleanHolder(false); - globalScene.getField(true).map(p=>applyAbAttrs("FieldPreventExplosiveMovesAbAttr", {pokemon: p, cancelled})); + // temporary workaround to prevent displaying the message during enemy command phase + // TODO: either move this, or make the move condition func have a `simulated` param + const simulated = globalScene.phaseManager.getCurrentPhase()?.is('EnemyCommandPhase'); + globalScene.getField(true).map(p=>applyAbAttrs("FieldPreventExplosiveMovesAbAttr", {pokemon: p, cancelled, simulated})); // Queue a message if an ability prevented usage of the move - if (cancelled.value) { + if (!simulated && cancelled.value) { globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:cannotUseMove", { pokemonName: getPokemonNameWithAffix(user), moveName: move.name })); } return !cancelled.value; diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index aac2ed55a72..86e63e49953 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -6590,6 +6590,7 @@ export class EnemyPokemon extends Pokemon { ignoreAllyAbility: !p.getAlly()?.waveData.abilityRevealed, ignoreSourceAllyAbility: false, isCritical, + simulated: true, }).damage >= p.hp ); }) diff --git a/src/touch-controls.ts b/src/touch-controls.ts index faee9ea232e..4d00027e4ef 100644 --- a/src/touch-controls.ts +++ b/src/touch-controls.ts @@ -61,12 +61,12 @@ export class TouchControl { * event, removes the keydown state, and removes the 'active' class from the node and the last touched element. */ bindKey(node: HTMLElement, key: string) { - node.addEventListener("touchstart", event => { + node.addEventListener("pointerdown", event => { event.preventDefault(); this.touchButtonDown(node, key); }); - node.addEventListener("touchend", event => { + node.addEventListener("pointerup", event => { event.preventDefault(); this.touchButtonUp(node, key, event.target?.["id"]); }); diff --git a/src/ui/settings/move-touch-controls-handler.ts b/src/ui/settings/move-touch-controls-handler.ts index 60572529c89..248ee76a850 100644 --- a/src/ui/settings/move-touch-controls-handler.ts +++ b/src/ui/settings/move-touch-controls-handler.ts @@ -9,9 +9,9 @@ export const TOUCH_CONTROL_POSITIONS_PORTRAIT = "touchControlPositionsPortrait"; type ControlPosition = { id: string; x: number; y: number }; type ConfigurationEventListeners = { - touchstart: EventListener[]; - touchmove: EventListener[]; - touchend: EventListener[]; + pointerdown: EventListener[]; + pointermove: EventListener[]; + pointerup: EventListener[]; }; type ToolbarRefs = { @@ -39,9 +39,9 @@ export class MoveTouchControlsHandler { * These are used to remove the event listeners when the configuration mode is disabled. */ private configurationEventListeners: ConfigurationEventListeners = { - touchstart: [], - touchmove: [], - touchend: [], + pointerdown: [], + pointermove: [], + pointerup: [], }; private overlay: Phaser.GameObjects.Container; @@ -165,34 +165,33 @@ export class MoveTouchControlsHandler { /** * Start dragging the given button. * @param controlGroup The button that is being dragged. - * @param touch The touch event that started the drag. + * @param event The pointer event that started the drag. */ private startDrag = (controlGroup: HTMLElement): void => { this.draggingElement = controlGroup; }; /** - * Drags the currently dragged element to the given touch position. - * @param touch The touch event that is currently happening. - * @param isLeft Whether the dragged element is a left button. + * Drags the currently dragged element to the given pointer position. + * @param event The pointer event that is currently happening. */ - private drag = (touch: Touch): void => { + private drag = (event: PointerEvent): void => { if (!this.draggingElement) { return; } const rect = this.draggingElement.getBoundingClientRect(); - // Map the touch position to the center of the dragged element. + // Map the pointer position to the center of the dragged element. const xOffset = this.isLeft(this.draggingElement) - ? touch.clientX - rect.width / 2 - : window.innerWidth - touch.clientX - rect.width / 2; - const yOffset = window.innerHeight - touch.clientY - rect.height / 2; + ? event.clientX - rect.width / 2 + : window.innerWidth - event.clientX - rect.width / 2; + const yOffset = window.innerHeight - event.clientY - rect.height / 2; this.setPosition(this.draggingElement, xOffset, yOffset); }; /** * Stops dragging the currently dragged element. */ - private stopDrag = () => { + private stopDrag = (): void => { this.draggingElement = null; }; @@ -303,19 +302,19 @@ export class MoveTouchControlsHandler { */ private createConfigurationEventListeners(controlGroups: HTMLDivElement[]): ConfigurationEventListeners { return { - touchstart: controlGroups.map((element: HTMLDivElement) => { + pointerdown: controlGroups.map((element: HTMLDivElement) => { const startDrag = () => this.startDrag(element); - element.addEventListener("touchstart", startDrag, { passive: true }); + element.addEventListener("pointerdown", startDrag, { passive: true }); return startDrag; }), - touchmove: controlGroups.map(() => { - const drag = event => this.drag(event.touches[0]); - window.addEventListener("touchmove", drag, { passive: true }); + pointermove: controlGroups.map(() => { + const drag = (event: PointerEvent) => this.drag(event); + window.addEventListener("pointermove", drag, { passive: true }); return drag; }), - touchend: controlGroups.map(() => { + pointerup: controlGroups.map(() => { const stopDrag = () => this.stopDrag(); - window.addEventListener("touchend", stopDrag, { passive: true }); + window.addEventListener("pointerup", stopDrag, { passive: true }); return stopDrag; }), }; @@ -373,12 +372,12 @@ export class MoveTouchControlsHandler { this.draggingElement = null; // Remove event listeners - const { touchstart, touchmove, touchend } = this.configurationEventListeners; + const { pointerdown, pointermove, pointerup } = this.configurationEventListeners; this.getControlGroupElements().forEach((element, index) => - element.removeEventListener("touchstart", touchstart[index]), + element.removeEventListener("pointerdown", pointerdown[index]), ); - touchmove.forEach(listener => window.removeEventListener("touchmove", listener)); - touchend.forEach(listener => window.removeEventListener("touchend", listener)); + pointermove.forEach(listener => window.removeEventListener("pointermove", listener)); + pointerup.forEach(listener => window.removeEventListener("pointerup", listener)); // Remove configuration toolbar const toolbar = document.querySelector("#touchControls #configToolbar"); diff --git a/test/test-utils/inputs-handler.ts b/test/test-utils/inputs-handler.ts index b8b3224c31d..b5638f3694c 100644 --- a/test/test-utils/inputs-handler.ts +++ b/test/test-utils/inputs-handler.ts @@ -111,7 +111,7 @@ class FakeMobile { if (!node) { return; } - const event = new Event("touchstart"); + const event = new Event("pointerdown"); node.dispatchEvent(event); } @@ -120,7 +120,7 @@ class FakeMobile { if (!node) { return; } - const event = new Event("touchend"); + const event = new Event("pointerup"); node.dispatchEvent(event); } }