Adds a Small Flyout Panel to the Battle Info Object (#1377)

* Initial Commit

* Update pbinfo_enemy_boss_stats.png

* Move to Separate Key

* Add Separate Mobile Control for Flyout

* Add Setting to Enable/Disable

* Add to the Tutorial

* Change to BUTTON.V
This commit is contained in:
Benjamin Odom 2024-05-29 19:29:59 -05:00 committed by GitHub
parent 73d3d8648f
commit 9b5c1cdadb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 231 additions and 20 deletions

View File

@ -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;
}

View File

@ -75,7 +75,7 @@
<div id="apadCycleVariant" class="apadSqBtn apadBtn" data-key="CYCLE_VARIANT">
<text class="apadLabel apadLabelSmall">V</text>
</div>
<div id="apadStats" class="apadRectBtn apadBtn" data-key="STATS">
<div id="apadStats" class="apadRectBtn apadBtn apadBattle" data-key="STATS">
<text class="apadLabel apadLabelSmall">C</text>
</div>
<div id="apadMenu" class="apadRectBtn apadBtn" data-key="MENU">
@ -95,6 +95,9 @@
<div id="apadCycleNature" class="apadSqBtn apadBtn" data-key="CYCLE_NATURE">
<text class="apadLabel apadLabelSmall">N</text>
</div>
<div id="apadInfo" class="apadRectBtn apadBtn apadBattle" data-key="CYCLE_VARIANT">
<text class="apadLabel apadLabelSmall">V</text>
</div>
</div>
</div>
</div>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 435 B

After

Width:  |  Height:  |  Size: 429 B

View File

@ -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());

View File

@ -13,7 +13,7 @@ export enum Button {
CYCLE_GENDER,
CYCLE_ABILITY,
CYCLE_NATURE,
CYCLE_VARIANT,
V,
SPEED_UP,
SLOW_DOWN
}

View File

@ -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();

View File

@ -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]
};

View File

@ -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.

View File

@ -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;

View File

@ -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);
}
}

163
src/ui/battle-flyout.ts Normal file
View File

@ -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();
}
}

View File

@ -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<Phaser.GameObjects.GameObject>(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;
}

View File

@ -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 {