diff --git a/index.css b/index.css
index dd47387adee..df305781646 100644
--- a/index.css
+++ b/index.css
@@ -146,11 +146,11 @@ body {
margin-left: 10%;
}
-#touchControls:not([data-ui-mode='STARTER_SELECT']) #apad .apadRectBtnContainer > .apadSqBtn, #touchControls:not([data-ui-mode='STARTER_SELECT']) #apad .apadSqBtnContainer {
+#touchControls:not([data-ui-mode='STARTER_SELECT']) #apad .apadRectBtnContainer > .apadSqBtn, #touchControls:not([data-ui-mode='STARTER_SELECT']) #apad .apadSqBtnContainer > .apadSqBtn {
display: none;
}
-#touchControls:not([data-ui-mode='COMMAND']):not([data-ui-mode='FIGHT']):not([data-ui-mode='BALL']):not([data-ui-mode='TARGET_SELECT']) #apad #apadStats {
+#touchControls:not([data-ui-mode='COMMAND']):not([data-ui-mode='FIGHT']):not([data-ui-mode='BALL']):not([data-ui-mode='TARGET_SELECT']) #apad .apadBattle {
display: none;
}
diff --git a/index.html b/index.html
index d9d513e1663..e155b1c97ff 100644
--- a/index.html
+++ b/index.html
@@ -75,7 +75,7 @@
V
-
diff --git a/public/images/ui/legacy/pbinfo_enemy_boss_stats.png b/public/images/ui/legacy/pbinfo_enemy_boss_stats.png
index 94c9f2a1817..faca8887ff5 100644
Binary files a/public/images/ui/legacy/pbinfo_enemy_boss_stats.png and b/public/images/ui/legacy/pbinfo_enemy_boss_stats.png differ
diff --git a/src/battle-scene.ts b/src/battle-scene.ts
index db9ba1b2828..394c768d0c1 100644
--- a/src/battle-scene.ts
+++ b/src/battle-scene.ts
@@ -90,6 +90,7 @@ export default class BattleScene extends SceneBase {
public seVolume: number = 1;
public gameSpeed: integer = 1;
public damageNumbersMode: integer = 0;
+ public showMovesetFlyout: boolean = true;
public showLevelUpStats: boolean = true;
public enableTutorials: boolean = import.meta.env.VITE_BYPASS_TUTORIAL === "1";
public enableRetries: boolean = false;
@@ -207,6 +208,9 @@ export default class BattleScene extends SceneBase {
*
* Current Events:
* - {@linkcode BattleSceneEventType.MOVE_USED} {@linkcode MoveUsedEvent}
+ * - {@linkcode BattleSceneEventType.TURN_INIT} {@linkcode TurnInitEvent}
+ * - {@linkcode BattleSceneEventType.TURN_END} {@linkcode TurnEndEvent}
+ * - {@linkcode BattleSceneEventType.NEW_ARENA} {@linkcode NewArenaEvent}
*/
public readonly eventTarget: EventTarget = new EventTarget();
@@ -1346,6 +1350,15 @@ export default class BattleScene extends SceneBase {
this.ui?.achvBar.setY(this.game.canvas.height / 6 + offsetY);
}
+ /**
+ * Pushes all {@linkcode Phaser.GameObjects.Text} objects in the top right to the bottom of the canvas
+ */
+ sendTextToBack(): void {
+ this.fieldUI.sendToBack(this.biomeWaveText);
+ this.fieldUI.sendToBack(this.moneyText);
+ this.fieldUI.sendToBack(this.scoreText);
+ }
+
addFaintedEnemyScore(enemy: EnemyPokemon): void {
let scoreIncrease = enemy.getSpeciesForm().getBaseExp() * (enemy.level / this.getMaxExpLevel()) * ((enemy.ivs.reduce((iv: integer, total: integer) => total += iv, 0) / 93) * 0.2 + 0.8);
this.findModifiers(m => m instanceof PokemonHeldItemModifier && m.pokemonId === enemy.id, false).map(m => scoreIncrease *= (m as PokemonHeldItemModifier).getScoreMultiplier());
diff --git a/src/enums/buttons.ts b/src/enums/buttons.ts
index 034c5a2af83..fe26023f8e7 100644
--- a/src/enums/buttons.ts
+++ b/src/enums/buttons.ts
@@ -13,7 +13,7 @@ export enum Button {
CYCLE_GENDER,
CYCLE_ABILITY,
CYCLE_NATURE,
- CYCLE_VARIANT,
+ V,
SPEED_UP,
SLOW_DOWN
}
diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts
index 6a44c7ef31a..16be16e508c 100644
--- a/src/field/pokemon.ts
+++ b/src/field/pokemon.ts
@@ -1495,6 +1495,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const otherBattleInfo = this.scene.fieldUI.getAll().slice(0, 4).filter(ui => ui instanceof BattleInfo && ((ui as BattleInfo) instanceof PlayerBattleInfo) === this.isPlayer()).find(() => true);
if (!otherBattleInfo || !this.getFieldIndex()) {
this.scene.fieldUI.sendToBack(this.battleInfo);
+ this.scene.sendTextToBack(); // Push the top right text objects behind everything else
} else {
this.scene.fieldUI.moveAbove(this.battleInfo, otherBattleInfo);
}
@@ -1542,6 +1543,9 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
toggleStats(visible: boolean): void {
this.battleInfo.toggleStats(visible);
}
+ toggleFlyout(visible: boolean): void {
+ this.battleInfo.flyoutMenu?.toggleFlyout(visible);
+ }
addExp(exp: integer) {
const maxExpLevel = this.scene.getMaxExpLevel();
diff --git a/src/inputs-controller.ts b/src/inputs-controller.ts
index 9c1589b429b..83d26a83cff 100644
--- a/src/inputs-controller.ts
+++ b/src/inputs-controller.ts
@@ -255,7 +255,7 @@ export class InputsController {
gamepadMapping[this.player.LT] = Button.CYCLE_GENDER;
gamepadMapping[this.player.RT] = Button.CYCLE_ABILITY;
gamepadMapping[this.player.RC_W] = Button.CYCLE_NATURE;
- gamepadMapping[this.player.RC_N] = Button.CYCLE_VARIANT;
+ gamepadMapping[this.player.RC_N] = Button.V;
gamepadMapping[this.player.LS] = Button.SPEED_UP;
gamepadMapping[this.player.RS] = Button.SLOW_DOWN;
@@ -353,7 +353,7 @@ export class InputsController {
[Button.CYCLE_GENDER]: [keyCodes.G],
[Button.CYCLE_ABILITY]: [keyCodes.E],
[Button.CYCLE_NATURE]: [keyCodes.N],
- [Button.CYCLE_VARIANT]: [keyCodes.V],
+ [Button.V]: [keyCodes.V],
[Button.SPEED_UP]: [keyCodes.PLUS],
[Button.SLOW_DOWN]: [keyCodes.MINUS]
};
diff --git a/src/locales/en/tutorial.ts b/src/locales/en/tutorial.ts
index ccbc474c5ad..6361bd7d25f 100644
--- a/src/locales/en/tutorial.ts
+++ b/src/locales/en/tutorial.ts
@@ -22,7 +22,9 @@ export const tutorial: SimpleTranslationEntries = {
"statChange": `Stat changes persist across battles as long as your Pokémon aren't recalled.
$Your Pokémon are recalled before a trainer battle and before entering a new biome.
- $You can also view the stat changes for the Pokémon on the field by holding C or Shift.`,
+ $You can view the stat changes for any Pokémon on the field by holding C or Shift.
+ $You can also view the moveset for an enemy Pokémon by holding V.
+ $This only reveals moves that you've seen the Pokémon use this battle.`,
"selectItem": `After every battle, you are given a choice of 3 random items.\nYou may only pick one.
$These range from consumables, to Pokémon held items, to passive permanent items.
diff --git a/src/system/settings.ts b/src/system/settings.ts
index c38cea8863b..3cfc6ba9be4 100644
--- a/src/system/settings.ts
+++ b/src/system/settings.ts
@@ -24,6 +24,7 @@ export enum Setting {
Money_Format = "MONEY_FORMAT",
Sprite_Set = "SPRITE_SET",
Move_Animations = "MOVE_ANIMATIONS",
+ Show_Moveset_Flyout = "SHOW_MOVESET_FLYOUT",
Show_Stats_on_Level_Up = "SHOW_LEVEL_UP_STATS",
EXP_Gains_Speed = "EXP_GAINS_SPEED",
EXP_Party_Display = "EXP_PARTY_DISPLAY",
@@ -60,6 +61,7 @@ export const settingOptions: SettingOptions = {
[Setting.Money_Format]: ["Normal", "Abbreviated"],
[Setting.Sprite_Set]: ["Consistent", "Mixed Animated"],
[Setting.Move_Animations]: ["Off", "On"],
+ [Setting.Show_Moveset_Flyout]: ["Off", "On"],
[Setting.Show_Stats_on_Level_Up]: ["Off", "On"],
[Setting.EXP_Gains_Speed]: ["Normal", "Fast", "Faster", "Skip"],
[Setting.EXP_Party_Display]: ["Normal", "Level Up Notification", "Skip"],
@@ -88,6 +90,7 @@ export const settingDefaults: SettingDefaults = {
[Setting.Money_Format]: 0,
[Setting.Sprite_Set]: 0,
[Setting.Move_Animations]: 1,
+ [Setting.Show_Moveset_Flyout]: 1,
[Setting.Show_Stats_on_Level_Up]: 1,
[Setting.EXP_Gains_Speed]: 0,
[Setting.EXP_Party_Display]: 0,
@@ -164,6 +167,9 @@ export function setSetting(scene: BattleScene, setting: Setting, value: integer)
case Setting.Move_Animations:
scene.moveAnimations = settingOptions[setting][value] === "On";
break;
+ case Setting.Show_Moveset_Flyout:
+ scene.showMovesetFlyout = settingOptions[setting][value] === "On";
+ break;
case Setting.Show_Stats_on_Level_Up:
scene.showLevelUpStats = settingOptions[setting][value] === "On";
break;
diff --git a/src/ui-inputs.ts b/src/ui-inputs.ts
index 1151d4dd181..d443e4d85b7 100644
--- a/src/ui-inputs.ts
+++ b/src/ui-inputs.ts
@@ -66,7 +66,7 @@ export class UiInputs {
[Button.CYCLE_GENDER]: () => this.buttonCycleOption(Button.CYCLE_GENDER),
[Button.CYCLE_ABILITY]: () => this.buttonCycleOption(Button.CYCLE_ABILITY),
[Button.CYCLE_NATURE]: () => this.buttonCycleOption(Button.CYCLE_NATURE),
- [Button.CYCLE_VARIANT]: () => this.buttonCycleOption(Button.CYCLE_VARIANT),
+ [Button.V]: () => this.buttonCycleOption(Button.V),
[Button.SPEED_UP]: () => this.buttonSpeedChange(),
[Button.SLOW_DOWN]: () => this.buttonSpeedChange(false),
};
@@ -89,7 +89,7 @@ export class UiInputs {
[Button.CYCLE_GENDER]: () => undefined,
[Button.CYCLE_ABILITY]: () => undefined,
[Button.CYCLE_NATURE]: () => undefined,
- [Button.CYCLE_VARIANT]: () => undefined,
+ [Button.V]: () => this.buttonInfo(false),
[Button.SPEED_UP]: () => undefined,
[Button.SLOW_DOWN]: () => undefined,
};
@@ -111,14 +111,17 @@ export class UiInputs {
}
buttonStats(pressed: boolean = true): void {
- if (pressed) {
- for (const p of this.scene.getField().filter(p => p?.isActive(true))) {
- p.toggleStats(true);
- }
- } else {
- for (const p of this.scene.getField().filter(p => p?.isActive(true))) {
- p.toggleStats(false);
- }
+ for (const p of this.scene.getField().filter(p => p?.isActive(true))) {
+ p.toggleStats(pressed);
+ }
+ }
+ buttonInfo(pressed: boolean = true): void {
+ if (!this.scene.showMovesetFlyout) {
+ return;
+ }
+
+ for (const p of this.scene.getField().filter(p => p?.isActive(true))) {
+ p.toggleFlyout(pressed);
}
}
@@ -158,6 +161,8 @@ export class UiInputs {
buttonCycleOption(button: Button): void {
if (this.scene.ui?.getHandler() instanceof StarterSelectUiHandler) {
this.scene.ui.processInput(button);
+ } else if (button === Button.V) {
+ this.buttonInfo(true);
}
}
diff --git a/src/ui/battle-flyout.ts b/src/ui/battle-flyout.ts
new file mode 100644
index 00000000000..3add54920b0
--- /dev/null
+++ b/src/ui/battle-flyout.ts
@@ -0,0 +1,163 @@
+import { default as Pokemon } from "../field/pokemon";
+import { addTextObject, TextStyle } from "./text";
+import * as Utils from "../utils";
+import BattleScene from "#app/battle-scene.js";
+import { UiTheme } from "#app/enums/ui-theme.js";
+import Move from "#app/data/move.js";
+import { BattleSceneEventType, MoveUsedEvent } from "#app/battle-scene-events.js";
+
+/** Container for info about a {@linkcode Move} */
+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,
+}
+
+/** A Flyout Menu attached to each {@linkcode BattleInfo} object on the field UI */
+export default class BattleFlyout extends Phaser.GameObjects.Container {
+ /** An alias for the scene typecast to a {@linkcode BattleScene} */
+ private battleScene: BattleScene;
+
+ /** Is this object linked to a player's Pokemon? */
+ private player: boolean;
+
+ /** The Pokemon this object is linked to */
+ private pokemon: Pokemon;
+
+ /** The restricted width of the flyout which should be drawn to */
+ private flyoutWidth = 118;
+ /** The restricted height of the flyout which should be drawn to */
+ private flyoutHeight = 23;
+
+ /** The amount of translation animation on the x-axis */
+ private translationX: number;
+ /** The x-axis point where the flyout should sit when activated */
+ private anchorX: number;
+ /** The y-axis point where the flyout should sit when activated */
+ private anchorY: number;
+
+ /** The initial container which defines where the flyout should be attached */
+ private flyoutParent: Phaser.GameObjects.Container;
+ /** The background {@linkcode Phaser.GameObjects.Sprite;} for the flyout */
+ private flyoutBackground: Phaser.GameObjects.Sprite;
+
+ /** The container which defines the drawable dimensions of the flyout */
+ private flyoutContainer: 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[] = new Array();
+
+ private readonly onMoveUsed = (event) => this.updateInfo(event);
+
+ constructor(scene: Phaser.Scene, player: boolean) {
+ super(scene, 0, 0);
+ this.battleScene = scene as BattleScene;
+
+ // Note that all player based flyouts are disabled. This is included in case of future development
+ this.player = player;
+
+ this.translationX = this.player ? -this.flyoutWidth : this.flyoutWidth;
+ this.anchorX = (this.player ? -130 : -40);
+ this.anchorY = -2.5 + (this.player ? -18.5 : -13);
+
+ this.flyoutParent = this.scene.add.container(this.anchorX - this.translationX, this.anchorY);
+ this.flyoutParent.setAlpha(0);
+ this.add(this.flyoutParent);
+
+ // Load the background image
+ this.flyoutBackground = this.scene.add.sprite(0, 0, "pbinfo_enemy_boss_stats");
+ this.flyoutBackground.setOrigin(0, 0);
+
+ this.flyoutParent.add(this.flyoutBackground);
+
+ this.flyoutContainer = this.scene.add.container(44 + (this.player ? -this.flyoutWidth : 0), 2);
+ this.flyoutParent.add(this.flyoutContainer);
+
+ // Loops through and sets the position of each text object according to the width and height of the flyout
+ for (let i = 0; i < 4; i++) {
+ this.flyoutText[i] = addTextObject(
+ this.scene,
+ (this.flyoutWidth / 4) + (this.flyoutWidth / 2) * (i % 2),
+ (this.flyoutHeight / 4) + (this.flyoutHeight / 2) * (i < 2 ? 0 : 1), "???", TextStyle.BATTLE_INFO);
+ this.flyoutText[i].setFontSize(45);
+ this.flyoutText[i].setLineSpacing(-10);
+ this.flyoutText[i].setAlign("center");
+ this.flyoutText[i].setOrigin();
+ }
+
+ this.flyoutContainer.add(this.flyoutText);
+
+ this.flyoutContainer.add(
+ new Phaser.GameObjects.Rectangle(this.scene, this.flyoutWidth / 2, 0, 1, this.flyoutHeight + (this.battleScene.uiTheme === UiTheme.LEGACY ? 1 : 0), 0x212121).setOrigin(0.5, 0));
+ this.flyoutContainer.add(
+ new Phaser.GameObjects.Rectangle(this.scene, 0, this.flyoutHeight / 2, this.flyoutWidth + 6, 1, 0x212121).setOrigin(0, 0.5));
+ }
+
+ /**
+ * Links the given {@linkcode Pokemon} and subscribes to the {@linkcode BattleSceneEventType.MOVE_USED} event
+ * @param pokemon {@linkcode Pokemon} to link to this flyout
+ */
+ initInfo(pokemon: Pokemon) {
+ this.pokemon = pokemon;
+
+ this.name = `Flyout ${this.pokemon.name}`;
+ this.flyoutParent.name = `Flyout Parent ${this.pokemon.name}`;
+
+ this.battleScene.eventTarget.addEventListener(BattleSceneEventType.MOVE_USED, this.onMoveUsed);
+ }
+
+ /** Sets and formats the text property for all {@linkcode Phaser.GameObjects.Text} in the flyoutText array */
+ 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 = Math.max(moveInfo.maxPp - moveInfo.ppUsed, 0);
+ flyoutText.text = `${moveInfo.move.name} ${currentPp}/${moveInfo.maxPp}`;
+ }
+ }
+
+ /** Updates all of the {@linkcode MoveInfo} objects in the moveInfo array */
+ updateInfo(event: Event) {
+ const moveUsedEvent = event as MoveUsedEvent;
+ if (!moveUsedEvent || moveUsedEvent.userId !== this.pokemon?.id) {
+ 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();
+ }
+
+ /** Animates the flyout to either show or hide it by applying a fade and translation */
+ toggleFlyout(visible: boolean): void {
+ this.scene.tweens.add({
+ targets: this.flyoutParent,
+ x: visible ? this.anchorX : this.anchorX - this.translationX,
+ duration: Utils.fixedInt(125),
+ ease: "Sine.easeInOut",
+ alpha: visible ? 1 : 0,
+ });
+ }
+
+ destroy(fromScene?: boolean): void {
+ this.battleScene.eventTarget.removeEventListener(BattleSceneEventType.MOVE_USED, this.onMoveUsed);
+
+ super.destroy();
+ }
+}
diff --git a/src/ui/battle-info.ts b/src/ui/battle-info.ts
index 7c981ab8c27..9fb81f89698 100644
--- a/src/ui/battle-info.ts
+++ b/src/ui/battle-info.ts
@@ -8,6 +8,7 @@ import BattleScene from "../battle-scene";
import { Type, getTypeRgb } from "../data/type";
import { getVariantTint } from "#app/data/variant";
import { BattleStat } from "#app/data/battle-stat";
+import BattleFlyout from "./battle-flyout";
const battleStatOrder = [ BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.ACC, BattleStat.EVA, BattleStat.SPD ];
@@ -58,6 +59,8 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
private statValuesContainer: Phaser.GameObjects.Container;
private statNumbers: Phaser.GameObjects.Sprite[];
+ public flyoutMenu: BattleFlyout;
+
constructor(scene: Phaser.Scene, x: number, y: number, player: boolean) {
super(scene, x, y);
this.baseY = y;
@@ -226,6 +229,13 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
this.statValuesContainer.add(statNumber);
});
+ if (!this.player) {
+ this.flyoutMenu = new BattleFlyout(this.scene, this.player);
+ this.add(this.flyoutMenu);
+
+ this.moveBelow(this.flyoutMenu, this.box);
+ }
+
this.type1Icon = this.scene.add.sprite(player ? -139 : -15, player ? -17 : -15.5, `pbinfo_${player ? "player" : "enemy"}_type1`);
this.type1Icon.setName("icon_type_1");
this.type1Icon.setOrigin(0, 0);
@@ -246,6 +256,11 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
this.updateNameText(pokemon);
const nameTextWidth = this.nameText.displayWidth;
+ this.name = pokemon.name;
+ this.box.name = pokemon.name;
+
+ this.flyoutMenu?.initInfo(pokemon);
+
this.genderText.setText(getGenderSymbol(pokemon.gender));
this.genderText.setColor(getGenderColor(pokemon.gender));
this.genderText.setPositionRelative(this.nameText, nameTextWidth, 0);
@@ -454,8 +469,8 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
this.offset = offset;
- this.x += 10 * (offset === this.player ? 1 : -1);
- this.y += 27 * (offset ? 1 : -1);
+ this.x += 10 * (this.offset === this.player ? 1 : -1);
+ this.y += 27 * (this.offset ? 1 : -1);
this.baseY = this.y;
}
diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts
index f36e1eb2606..c54bc1924b9 100644
--- a/src/ui/starter-select-ui-handler.ts
+++ b/src/ui/starter-select-ui-handler.ts
@@ -1313,7 +1313,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
success = true;
}
break;
- case Button.CYCLE_VARIANT:
+ case Button.V:
if (this.canCycleVariant) {
let newVariant = props.variant;
do {