From c36b0e27448f3decc062ad36322c42aead334f29 Mon Sep 17 00:00:00 2001 From: Bertie690 Date: Fri, 25 Jul 2025 08:44:07 -0400 Subject: [PATCH] Started fixing the battle scene flyout --- src/data/abilities/ability.ts | 4 +- src/data/berry.ts | 2 + src/data/moves/move.ts | 10 +-- src/events/battle-scene.ts | 86 ++++++++++++----------- src/phases/berry-phase.ts | 2 - src/phases/move-phase.ts | 6 +- src/ui/battle-flyout.ts | 125 +++++++++++++++------------------- 7 files changed, 111 insertions(+), 124 deletions(-) diff --git a/src/data/abilities/ability.ts b/src/data/abilities/ability.ts index 62d6974d3a2..85f3b963a93 100644 --- a/src/data/abilities/ability.ts +++ b/src/data/abilities/ability.ts @@ -43,7 +43,7 @@ import { BATTLE_STATS, EFFECTIVE_STATS, getStatKey, Stat } from "#enums/stat"; import { StatusEffect } from "#enums/status-effect"; import { SwitchType } from "#enums/switch-type"; import { WeatherType } from "#enums/weather-type"; -import { BerryUsedEvent } from "#events/battle-scene"; +import { MovePPRestoredEvent } from "#events/battle-scene"; import type { EnemyPokemon, Pokemon } from "#field/pokemon"; import { BerryModifier, HitHealModifier, PokemonHeldItemModifier } from "#modifiers/modifier"; import { BerryModifierType } from "#modifiers/modifier-type"; @@ -4751,7 +4751,7 @@ export class CudChewConsumeBerryAbAttr extends AbAttr { for (const berryType of pokemon.summonData.berriesEatenLast) { getBerryEffectFunc(berryType)(pokemon); const bMod = new BerryModifier(new BerryModifierType(berryType), pokemon.id, berryType, 1); - globalScene.eventTarget.dispatchEvent(new BerryUsedEvent(bMod)); // trigger message + globalScene.eventTarget.dispatchEvent(new MovePPRestoredEvent(bMod)); // trigger message } // uncomment to make cheek pouch work with cud chew diff --git a/src/data/berry.ts b/src/data/berry.ts index 61235b75e21..f95eee9fd95 100644 --- a/src/data/berry.ts +++ b/src/data/berry.ts @@ -6,6 +6,7 @@ import { BattlerTagType } from "#enums/battler-tag-type"; import { BerryType } from "#enums/berry-type"; import { HitResult } from "#enums/hit-result"; import { type BattleStat, Stat } from "#enums/stat"; +import { MovePPRestoredEvent } from "#events/battle-scene"; import type { Pokemon } from "#field/pokemon"; import { NumberHolder, randSeedInt, toDmgValue } from "#utils/common"; import i18next from "i18next"; @@ -152,6 +153,7 @@ export function getBerryEffectFunc(berryType: BerryType): BerryEffectFunc { berryName: getBerryName(berryType), }), ); + globalScene.eventTarget.dispatchEvent(new MovePPRestoredEvent(m)); } } break; diff --git a/src/data/moves/move.ts b/src/data/moves/move.ts index 779f2d4fc76..002e46606db 100644 --- a/src/data/moves/move.ts +++ b/src/data/moves/move.ts @@ -65,7 +65,7 @@ import { import { StatusEffect } from "#enums/status-effect"; import { SwitchType } from "#enums/switch-type"; import { WeatherType } from "#enums/weather-type"; -import { MoveUsedEvent } from "#events/battle-scene"; +import { MovesetChangedEvent } from "#events/battle-scene"; import type { EnemyPokemon, Pokemon } from "#field/pokemon"; import { AttackTypeBoosterModifier, @@ -7257,7 +7257,7 @@ export class ReducePpMoveAttr extends MoveEffectAttr { const lastPpUsed = movesetMove.ppUsed; movesetMove.ppUsed = Math.min(lastPpUsed + this.reduction, movesetMove.getMovePp()); - globalScene.eventTarget.dispatchEvent(new MoveUsedEvent(target.id, movesetMove.getMove(), movesetMove.ppUsed)); + globalScene.eventTarget.dispatchEvent(new MovesetChangedEvent(target.id, movesetMove)); globalScene.phaseManager.queueMessage(i18next.t("battle:ppReduced", { targetName: getPokemonNameWithAffix(target), moveName: movesetMove.getName(), reduction: (movesetMove.ppUsed) - lastPpUsed })); return true; @@ -7362,11 +7362,13 @@ export class MovesetCopyMoveAttr extends OverrideMoveEffectAttr { return false; } - // Populate summon data with a copy of the current moveset, replacing the copying move with the copied move + // Populate summon data with a copy of the current moveset, replacing the copying move with the copied move. user.summonData.moveset = user.getMoveset().slice(0); - user.summonData.moveset[thisMoveIndex] = new PokemonMove(copiedMove.id); + const newMove = new PokemonMove(copiedMove.id); + user.summonData.moveset[thisMoveIndex] = newMove; globalScene.phaseManager.queueMessage(i18next.t("moveTriggers:copiedMove", { pokemonName: getPokemonNameWithAffix(user), moveName: copiedMove.name })); + globalScene.eventTarget.dispatchEvent(new MovesetChangedEvent(target.id, newMove)); return true; } diff --git a/src/events/battle-scene.ts b/src/events/battle-scene.ts index 29aee1053cd..a75224fadb2 100644 --- a/src/events/battle-scene.ts +++ b/src/events/battle-scene.ts @@ -1,7 +1,6 @@ -import type { BerryModifier } from "#modifiers/modifier"; -import type { Move } from "#moves/move"; +import type { PokemonMove } from "#moves/pokemon-move"; -/** Alias for all {@linkcode BattleScene} events */ +/** Alias for all {@linkcode BattleScene} events. */ export enum BattleSceneEventType { /** * Triggers when the corresponding setting is changed @@ -10,15 +9,11 @@ export enum BattleSceneEventType { CANDY_UPGRADE_NOTIFICATION_CHANGED = "onCandyUpgradeNotificationChanged", /** - * Triggers when a move is successfully used - * @see {@linkcode MoveUsedEvent} + * Triggers whenever a Pokemon's moveset is changed or altered - whether from moveset-overridding effects, + * PP consumption or restoration. + * @see {@linkcode MovesetChangedEvent} */ - MOVE_USED = "onMoveUsed", - /** - * Triggers when a berry gets successfully used - * @see {@linkcode BerryUsedEvent} - */ - BERRY_USED = "onBerryUsed", + MOVESET_CHANGED = "onMovePPChanged", /** * Triggers at the start of each new encounter @@ -44,10 +39,20 @@ export enum BattleSceneEventType { } /** + * Abstract container class for all {@linkcode BattleSceneEventType} events. + */ +abstract class BattleSceneEvent extends Event { + public declare abstract readonly type: BattleSceneEventType; // that's a mouthful! + // biome-ignore lint/complexity/noUselessConstructor: changes the type of the type field + constructor(type: BattleSceneEventType) { + super(type); + } +} /** * Container class for {@linkcode BattleSceneEventType.CANDY_UPGRADE_NOTIFICATION_CHANGED} events * @extends Event */ -export class CandyUpgradeNotificationChangedEvent extends Event { +export class CandyUpgradeNotificationChangedEvent extends BattleSceneEvent { + declare type: BattleSceneEventType.CANDY_UPGRADE_NOTIFICATION_CHANGED; /** The new value the setting was changed to */ public newValue: number; constructor(newValue: number) { @@ -58,61 +63,54 @@ export class CandyUpgradeNotificationChangedEvent extends Event { } /** - * Container class for {@linkcode BattleSceneEventType.MOVE_USED} events - * @extends Event + * Container class for {@linkcode BattleSceneEventType.MOVESET_CHANGED} events. \ + * Emitted whenever the moveset of any on-field Pokemon is changed, or a move's PP is increased or decreased. */ -export class MoveUsedEvent extends Event { - /** The ID of the {@linkcode Pokemon} that used the {@linkcode Move} */ +export class MovesetChangedEvent extends BattleSceneEvent { + declare type: BattleSceneEventType.MOVESET_CHANGED; + + /** The {@linkcode Pokemon.ID | ID} of the {@linkcode Pokemon} whose moveset has changed. */ public pokemonId: number; - /** The {@linkcode Move} used */ - public move: Move; - /** The amount of PP used on the {@linkcode Move} this turn */ - public ppUsed: number; - constructor(userId: number, move: Move, ppUsed: number) { - super(BattleSceneEventType.MOVE_USED); + /** + * The {@linkcode PokemonMove} having been changed. + * Will override the corresponding slot of the moveset flyout for that Pokemon. + */ + public move: PokemonMove; - this.pokemonId = userId; + constructor(pokemonId: number, move: PokemonMove) { + super(BattleSceneEventType.MOVESET_CHANGED); + + this.pokemonId = pokemonId; this.move = move; - this.ppUsed = ppUsed; - } -} -/** - * Container class for {@linkcode BattleSceneEventType.BERRY_USED} events - * @extends Event - */ -export class BerryUsedEvent extends Event { - /** The {@linkcode BerryModifier} being used */ - public berryModifier: BerryModifier; - constructor(berry: BerryModifier) { - super(BattleSceneEventType.BERRY_USED); - - this.berryModifier = berry; } } /** - * Container class for {@linkcode BattleSceneEventType.ENCOUNTER_PHASE} events - * @extends Event + * Container class for {@linkcode BattleSceneEventType.ENCOUNTER_PHASE} events. */ -export class EncounterPhaseEvent extends Event { +export class EncounterPhaseEvent extends BattleSceneEvent { + declare type: BattleSceneEventType.ENCOUNTER_PHASE; constructor() { super(BattleSceneEventType.ENCOUNTER_PHASE); } } + /** - * Container class for {@linkcode BattleSceneEventType.TURN_INIT} events - * @extends Event + * Container class for {@linkcode BattleSceneEventType.TURN_INIT} events. */ -export class TurnInitEvent extends Event { +export class TurnInitEvent extends BattleSceneEvent { + declare type: BattleSceneEventType.TURN_INIT; constructor() { super(BattleSceneEventType.TURN_INIT); } } + /** * Container class for {@linkcode BattleSceneEventType.TURN_END} events * @extends Event */ -export class TurnEndEvent extends Event { +export class TurnEndEvent extends BattleSceneEvent { + declare type: BattleSceneEventType.TURN_END; /** The amount of turns in the current battle */ public turnCount: number; constructor(turnCount: number) { diff --git a/src/phases/berry-phase.ts b/src/phases/berry-phase.ts index 941406d0b96..77c2c4b3b58 100644 --- a/src/phases/berry-phase.ts +++ b/src/phases/berry-phase.ts @@ -2,7 +2,6 @@ import { applyAbAttrs } from "#abilities/apply-ab-attrs"; import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; import { CommonAnim } from "#enums/move-anims-common"; -import { BerryUsedEvent } from "#events/battle-scene"; import type { Pokemon } from "#field/pokemon"; import { BerryModifier } from "#modifiers/modifier"; import { FieldPhase } from "#phases/field-phase"; @@ -65,7 +64,6 @@ export class BerryPhase extends FieldPhase { berryModifier.consumed = false; pokemon.loseHeldItem(berryModifier); } - globalScene.eventTarget.dispatchEvent(new BerryUsedEvent(berryModifier)); } globalScene.updateModifiers(pokemon.isPlayer()); diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index 82bb6b153ef..585e2455589 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -20,7 +20,7 @@ import { MoveResult } from "#enums/move-result"; import { isIgnorePP, isIgnoreStatus, isReflected, isVirtual, MoveUseMode } from "#enums/move-use-mode"; import { PokemonType } from "#enums/pokemon-type"; import { StatusEffect } from "#enums/status-effect"; -import { MoveUsedEvent } from "#events/battle-scene"; +import { MovesetChangedEvent } from "#events/battle-scene"; import type { Pokemon } from "#field/pokemon"; import { applyMoveAttrs } from "#moves/apply-attrs"; import { frenzyMissFunc } from "#moves/move-utils"; @@ -334,7 +334,7 @@ export class MovePhase extends BattlePhase { // "commit" to using the move, deducting PP. const ppUsed = 1 + this.getPpIncreaseFromPressure(targets); this.move.usePp(ppUsed); - globalScene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon.id, move, this.move.ppUsed)); + globalScene.eventTarget.dispatchEvent(new MovesetChangedEvent(this.pokemon.id, move.id, this.move.ppUsed)); } /** @@ -640,7 +640,7 @@ export class MovePhase extends BattlePhase { // TODO: should this consider struggle? const ppUsed = isIgnorePP(this.useMode) ? 0 : 1; this.move.usePp(ppUsed); - globalScene.eventTarget.dispatchEvent(new MoveUsedEvent(this.pokemon?.id, this.move.getMove(), ppUsed)); + globalScene.eventTarget.dispatchEvent(new MovesetChangedEvent(this.pokemon.id, this.move)); } if (this.cancelled && this.pokemon.summonData.tags.some(t => t.tagType === BattlerTagType.FRENZY)) { diff --git a/src/ui/battle-flyout.ts b/src/ui/battle-flyout.ts index 083dc7bbf19..584ac12528e 100644 --- a/src/ui/battle-flyout.ts +++ b/src/ui/battle-flyout.ts @@ -1,26 +1,26 @@ import { globalScene } from "#app/global-scene"; import { getPokemonNameWithAffix } from "#app/messages"; -import { BerryType } from "#enums/berry-type"; import { MoveId } from "#enums/move-id"; import { UiTheme } from "#enums/ui-theme"; -import type { BerryUsedEvent, MoveUsedEvent } from "#events/battle-scene"; +import type { MovesetChangedEvent } from "#events/battle-scene"; import { BattleSceneEventType } from "#events/battle-scene"; -import type { EnemyPokemon, Pokemon } from "#field/pokemon"; -import type { Move } from "#moves/move"; +import type { Pokemon } from "#field/pokemon"; +import type { PokemonMove } from "#moves/pokemon-move"; +// biome-ignore lint/correctness/noUnusedImports: TSDoc +import type { BattleInfo } from "#ui/battle-info"; import { addTextObject, TextStyle } from "#ui/text"; import { fixedInt } from "#utils/common"; -/** Container for info about a {@linkcode Move} */ +/** Container for info about the {@linkcode PokemonMove}s having been */ interface MoveInfo { - /** The {@linkcode Move} itself */ - move: Move; - - /** The maximum PP of the {@linkcode Move} */ - maxPp: number; - /** The amount of PP used by the {@linkcode Move} */ - ppUsed: number; + /** The name of the {@linkcode Move} having been used. */ + name: string; + /** The {@linkcode PokemonMove} having been used. */ + move: PokemonMove; } +type MoveInfoTuple = [MoveInfo?, MoveInfo?, MoveInfo?, MoveInfo?]; + /** A Flyout Menu attached to each {@linkcode BattleInfo} object on the field UI */ export class BattleFlyout extends Phaser.GameObjects.Container { /** Is this object linked to a player's Pokemon? */ @@ -51,15 +51,14 @@ export class BattleFlyout extends Phaser.GameObjects.Container { /** The array of {@linkcode Phaser.GameObjects.Text} objects which are drawn on the flyout */ private flyoutText: Phaser.GameObjects.Text[] = new Array(4); - /** The array of {@linkcode MoveInfo} used to track moves for the {@linkcode Pokemon} linked to the flyout */ - private moveInfo: MoveInfo[] = []; + /** An array of {@linkcode MoveInfo}s used to track moves for the {@linkcode Pokemon} linked to the flyout. */ + private moveInfo: MoveInfoTuple = []; /** Current state of the flyout's visibility */ public flyoutVisible = false; // Stores callbacks in a variable so they can be unsubscribed from when destroyed - private readonly onMoveUsedEvent = (event: Event) => this.onMoveUsed(event); - private readonly onBerryUsedEvent = (event: Event) => this.onBerryUsed(event); + private readonly onMovesetChangedEvent = (event: MovesetChangedEvent) => this.onMovesetChanged(event); constructor(player: boolean) { super(globalScene, 0, 0); @@ -123,79 +122,67 @@ export class BattleFlyout extends Phaser.GameObjects.Container { } /** - * Links the given {@linkcode Pokemon} and subscribes to the {@linkcode BattleSceneEventType.MOVE_USED} event - * @param pokemon {@linkcode Pokemon} to link to this flyout + * Link the given {@linkcode Pokemon} to this flyout and subscribe to the {@linkcode BattleSceneEventType.MOVESET_CHANGED} event. + * @param pokemon - The {@linkcode Pokemon} to link to this flyout */ - initInfo(pokemon: EnemyPokemon) { + public initInfo(pokemon: Pokemon): void { this.pokemon = pokemon; this.name = `Flyout ${getPokemonNameWithAffix(this.pokemon)}`; this.flyoutParent.name = `Flyout Parent ${getPokemonNameWithAffix(this.pokemon)}`; - globalScene.eventTarget.addEventListener(BattleSceneEventType.MOVE_USED, this.onMoveUsedEvent); - globalScene.eventTarget.addEventListener(BattleSceneEventType.BERRY_USED, this.onBerryUsedEvent); + globalScene.eventTarget.addEventListener(BattleSceneEventType.MOVESET_CHANGED, this.onMovesetChangedEvent); } - /** Sets and formats the text property for all {@linkcode Phaser.GameObjects.Text} in the flyoutText array */ - private setText() { - for (let i = 0; i < this.flyoutText.length; i++) { - const flyoutText = this.flyoutText[i]; - const moveInfo = this.moveInfo[i]; - - if (!moveInfo) { - continue; - } - - const currentPp = moveInfo.maxPp - moveInfo.ppUsed; - flyoutText.text = `${moveInfo.move.name} ${currentPp}/${moveInfo.maxPp}`; - } - } - - /** Updates all of the {@linkcode MoveInfo} objects in the moveInfo array */ - private onMoveUsed(event: Event) { - const moveUsedEvent = event as MoveUsedEvent; - if (!moveUsedEvent || moveUsedEvent.pokemonId !== this.pokemon?.id || moveUsedEvent.move.id === MoveId.STRUGGLE) { - // Ignore Struggle + /** + * Set and formats the text property for all {@linkcode Phaser.GameObjects.Text} in the flyoutText array. + * @param index - The 0-indexed position of the flyout text object to update + */ + private updateText(index: number) { + const flyoutText = this.flyoutText[index]; + const moveInfo = this.moveInfo[index]; + if (!moveInfo) { return; } - const foundInfo = this.moveInfo.find(x => x?.move.id === moveUsedEvent.move.id); - if (foundInfo) { - foundInfo.ppUsed = moveUsedEvent.ppUsed; - } else { - this.moveInfo.push({ - move: moveUsedEvent.move, - maxPp: moveUsedEvent.move.pp, - ppUsed: moveUsedEvent.ppUsed, - }); - } - - this.setText(); + const maxPP = moveInfo.move.getMovePp(); + const currentPp = -moveInfo.move.ppUsed; + flyoutText.text = `${moveInfo.name} ${currentPp}/${maxPP}`; } - private onBerryUsed(event: Event) { - const berryUsedEvent = event as BerryUsedEvent; + /** + * Update the corresponding {@linkcode MoveInfo} object in the moveInfo array. + * @param event - The {@linkcode MovesetChangedEvent} having been emitted + */ + private onMovesetChanged(event: MovesetChangedEvent): void { + // Ignore other Pokemon's moves as well as Struggle and MoveId.NONE if ( - !berryUsedEvent || - berryUsedEvent.berryModifier.pokemonId !== this.pokemon?.id || - berryUsedEvent.berryModifier.berryType !== BerryType.LEPPA + event.pokemonId !== this.pokemon.id || + event.move.moveId === MoveId.NONE || + event.move.moveId === MoveId.STRUGGLE ) { - // We only care about Leppa berries return; } - const foundInfo = this.moveInfo.find(info => info.ppUsed === info.maxPp); - if (!foundInfo) { - // This will only happen on a de-sync of PP tracking - return; + // If we already have a move in that slot, update the corresponding slot of the Pokemon's moveset. + const index = this.pokemon.getMoveset().indexOf(event.move); + if (index === -1) { + console.error("Updated move passed to move flyout was undefined!"); + } + if (this.moveInfo[index]) { + this.moveInfo[index].move = event.move; + } else { + this.moveInfo[index] = { + name: event.move.getMove().name, + move: event.move, + }; } - foundInfo.ppUsed = Math.max(foundInfo.ppUsed - 10, 0); - this.setText(); + this.updateText(index); } /** Animates the flyout to either show or hide it by applying a fade and translation */ - toggleFlyout(visible: boolean): void { + public toggleFlyout(visible: boolean): void { this.flyoutVisible = visible; globalScene.tweens.add({ @@ -207,9 +194,9 @@ export class BattleFlyout extends Phaser.GameObjects.Container { }); } - destroy(fromScene?: boolean): void { - globalScene.eventTarget.removeEventListener(BattleSceneEventType.MOVE_USED, this.onMoveUsedEvent); - globalScene.eventTarget.removeEventListener(BattleSceneEventType.BERRY_USED, this.onBerryUsedEvent); + /** Destroy this element and remove all associated listeners. */ + public destroy(fromScene?: boolean): void { + globalScene.eventTarget.removeEventListener(BattleSceneEventType.MOVESET_CHANGED, this.onMovesetChangedEvent); super.destroy(fromScene); }