diff --git a/src/typings/phaser/index.d.ts b/src/typings/phaser/index.d.ts index f3665768cec..ef67c107ca5 100644 --- a/src/typings/phaser/index.d.ts +++ b/src/typings/phaser/index.d.ts @@ -20,37 +20,37 @@ declare module "phaser" { /** * Sets this object's position relative to another object with a given offset */ - setPositionRelative(guideObject: any, x: number, y: number): void; + setPositionRelative(guideObject: any, x: number, y: number): self; } interface Sprite { /** * Sets this object's position relative to another object with a given offset */ - setPositionRelative(guideObject: any, x: number, y: number): void; + setPositionRelative(guideObject: any, x: number, y: number): self; } interface Image { /** * Sets this object's position relative to another object with a given offset */ - setPositionRelative(guideObject: any, x: number, y: number): void; + setPositionRelative(guideObject: any, x: number, y: number): self; } interface NineSlice { /** * Sets this object's position relative to another object with a given offset */ - setPositionRelative(guideObject: any, x: number, y: number): void; + setPositionRelative(guideObject: any, x: number, y: number): self; } interface Text { /** * Sets this object's position relative to another object with a given offset */ - setPositionRelative(guideObject: any, x: number, y: number): void; + setPositionRelative(guideObject: any, x: number, y: number): self; } interface Rectangle { /** * Sets this object's position relative to another object with a given offset */ - setPositionRelative(guideObject: any, x: number, y: number): void; + setPositionRelative(guideObject: any, x: number, y: number): self; } } diff --git a/src/ui/battle-info/battle-info.ts b/src/ui/battle-info/battle-info.ts index b3fd3e241ad..1c6f4494e3e 100644 --- a/src/ui/battle-info/battle-info.ts +++ b/src/ui/battle-info/battle-info.ts @@ -10,10 +10,38 @@ import { getVariantTint } from "#app/sprites/variant"; import { Stat } from "#enums/stat"; import i18next from "i18next"; +/** + * Parameters influencing the position of elements within the battle info container + */ +export type BattleInfoParamList = { + /** X offset for the name text*/ + nameTextX: number; + /** Y offset for the name text */ + nameTextY: number; + /** X offset for the level container */ + levelContainerX: number; + /** Y offset for the level container */ + levelContainerY: number; + /** X offset for the hp bar */ + hpBarX: number; + /** Y offset for the hp bar */ + hpBarY: number; + /** Parameters for the stat box container */ + statBox: { + /** The starting offset from the left of the label for the entries in the stat box */ + xOffset: number; + /** The X between each number */ + paddingX: number; + /** The index of the stat entries at which paddingX is used instead of startingX */ + statOverflow: number; + }; +}; + export default abstract class BattleInfo extends Phaser.GameObjects.Container { public static readonly EXP_GAINS_DURATION_BASE = 1650; protected baseY: number; + protected baseLvContainerX: number; protected player: boolean; protected mini: boolean; @@ -102,7 +130,83 @@ export default abstract class BattleInfo extends Phaser.GameObjects.Container { this.add([this.teraIcon, this.shinyIcon, this.fusionShinyIcon, this.splicedIcon]); } - constructor(x: number, y: number, player: boolean) { + /** + * Submethod of the constructor that creates and adds the stats container to the battle info + */ + protected constructStatContainer({ xOffset, paddingX, statOverflow }: BattleInfoParamList["statBox"]): void { + this.statsContainer = globalScene.add.container(0, 0).setName("container_stats").setAlpha(0); + this.add(this.statsContainer); + + this.statsBox = globalScene.add + .sprite(0, 0, `${this.getTextureName()}_stats`) + .setName("box_stats") + .setOrigin(1, 0.5); + this.statsContainer.add(this.statsBox); + + const statLabels: Phaser.GameObjects.Sprite[] = []; + this.statNumbers = []; + + this.statValuesContainer = globalScene.add.container(); + this.statsContainer.add(this.statValuesContainer); + + const startingX = -this.statsBox.width + xOffset; + + // this gives us a different starting location from the left of the label and padding between stats for a player vs enemy + // since the player won't have HP to show, it doesn't need to change from the current version + + for (const [i, s] of this.statOrder.entries()) { + const isHp = s === Stat.HP; + // we do a check for i > statOverflow to see when the stat labels go onto the next column + // For enemies, we have HP (i=0) by itself then a new column, so we check for i > 0 + // For players, we don't have HP, so we start with i = 0 and i = 1 for our first column, and so need to check for i > 1 + const statX = + i > statOverflow + ? this.statNumbers[Math.max(i - 2, 0)].x + this.statNumbers[Math.max(i - 2, 0)].width + paddingX + : startingX; // we have the Math.max(i - 2, 0) in there so for i===1 to not return a negative number; since this is now based on anything >0 instead of >1, we need to allow for i-2 < 0 + + let statY = -this.statsBox.height / 2 + 4; // this is the baseline for the y-axis + if (isHp || s === Stat.SPD) { + statY += 5; + } else if (this.player === !!(i % 2)) { + // we compare i % 2 against this.player to tell us where to place the label + // because this.battleStatOrder for enemies has HP, this.battleStatOrder[1]=ATK, but for players + // this.battleStatOrder[0]=ATK, so this comparing i % 2 to this.player fixes this issue for us + statY += 10; + } + + const statLabel = globalScene.add + .sprite(statX, statY, "pbinfo_stat", Stat[s]) + .setName("icon_stat_label_" + i.toString()) + .setOrigin(0); + statLabels.push(statLabel); + this.statValuesContainer.add(statLabel); + + const statNumber = globalScene.add + .sprite(statX + statLabel.width, statY, "pbinfo_stat_numbers", !isHp ? "3" : "empty") + .setName("icon_stat_number_" + i.toString()) + .setOrigin(0); + this.statNumbers.push(statNumber); + this.statValuesContainer.add(statNumber); + + if (isHp) { + statLabel.setVisible(false); + statNumber.setVisible(false); + } + } + } + + /** + * Submethod of the constructor that creates and adds the pokemon type icons to the battle info + */ + protected abstract constructTypeIcons(): void; + + /** + * @param x - The x position of the battle info container + * @param y - The y position of the battle info container + * @param player - Whether this battle info belongs to a player or an enemy + * @param posParams - The parameters influencing the position of elements within the battle info container + */ + constructor(x: number, y: number, player: boolean, posParams: BattleInfoParamList) { super(globalScene, x, y); this.baseY = y; this.player = player; @@ -118,6 +222,7 @@ export default abstract class BattleInfo extends Phaser.GameObjects.Container { this.lastExp = -1; this.lastLevelExp = -1; this.lastLevel = -1; + this.baseLvContainerX = posParams.levelContainerX; // Initially invisible and shown via Pokemon.showInfo this.setVisible(false); @@ -144,16 +249,15 @@ export default abstract class BattleInfo extends Phaser.GameObjects.Container { this.statusIndicator.setPositionRelative(this.nameText, 0, 11.5); this.add(this.statusIndicator); - this.levelContainer = globalScene.add.container(player ? -41 : -50, player ? -10 : -5).setName("container_level"); + this.levelContainer = globalScene.add + .container(posParams.levelContainerX, posParams.levelContainerY) + .setName("container_level"); this.add(this.levelContainer); const levelOverlay = globalScene.add.image(0, 0, "overlay_lv"); this.levelContainer.add(levelOverlay); - this.hpBar = globalScene.add - .image(player ? -61 : -71, player ? -1 : 4.5, "overlay_hp") - .setName("hp_bar") - .setOrigin(0); + this.hpBar = globalScene.add.image(posParams.hpBarX, posParams.hpBarY, "overlay_hp").setName("hp_bar").setOrigin(0); this.add(this.hpBar); this.levelNumbersContainer = globalScene.add @@ -161,81 +265,9 @@ export default abstract class BattleInfo extends Phaser.GameObjects.Container { .setName("container_level"); this.levelContainer.add(this.levelNumbersContainer); - this.statsContainer = globalScene.add.container(0, 0).setName("container_stats").setAlpha(0); - this.add(this.statsContainer); + this.constructStatContainer(posParams.statBox); - this.statsBox = globalScene.add - .sprite(0, 0, `${this.getTextureName()}_stats`) - .setName("box_stats") - .setOrigin(1, 0.5); - this.statsContainer.add(this.statsBox); - - const statLabels: Phaser.GameObjects.Sprite[] = []; - this.statNumbers = []; - - this.statValuesContainer = globalScene.add.container(0, 0); - this.statsContainer.add(this.statValuesContainer); - - // this gives us a different starting location from the left of the label and padding between stats for a player vs enemy - // since the player won't have HP to show, it doesn't need to change from the current version - const startingX = this.player ? -this.statsBox.width + 8 : -this.statsBox.width + 5; - const paddingX = this.player ? 4 : 2; - const statOverflow = this.player ? 1 : 0; - - for (const [i, s] of this.statOrder.entries()) { - // we do a check for i > statOverflow to see when the stat labels go onto the next column - // For enemies, we have HP (i=0) by itself then a new column, so we check for i > 0 - // For players, we don't have HP, so we start with i = 0 and i = 1 for our first column, and so need to check for i > 1 - const statX = - i > statOverflow - ? this.statNumbers[Math.max(i - 2, 0)].x + this.statNumbers[Math.max(i - 2, 0)].width + paddingX - : startingX; // we have the Math.max(i - 2, 0) in there so for i===1 to not return a negative number; since this is now based on anything >0 instead of >1, we need to allow for i-2 < 0 - - const baseY = -this.statsBox.height / 2 + 4; // this is the baseline for the y-axis - let statY: number; // this will be the y-axis placement for the labels - if (this.statOrder[i] === Stat.SPD || this.statOrder[i] === Stat.HP) { - statY = baseY + 5; - } else { - statY = baseY + (!!(i % 2) === this.player ? 10 : 0); // we compare i % 2 against this.player to tell us where to place the label; because this.battleStatOrder for enemies has HP, this.battleStatOrder[1]=ATK, but for players this.battleStatOrder[0]=ATK, so this comparing i % 2 to this.player fixes this issue for us - } - - const statLabel = globalScene.add - .sprite(statX, statY, "pbinfo_stat", Stat[s]) - .setName("icon_stat_label_" + i.toString()) - .setOrigin(0); - statLabels.push(statLabel); - this.statValuesContainer.add(statLabel); - - const statNumber = globalScene.add - .sprite(statX + statLabel.width, statY, "pbinfo_stat_numbers", this.statOrder[i] !== Stat.HP ? "3" : "empty") - .setName("icon_stat_number_" + i.toString()) - .setOrigin(0); - this.statNumbers.push(statNumber); - this.statValuesContainer.add(statNumber); - - if (this.statOrder[i] === Stat.HP) { - statLabel.setVisible(false); - statNumber.setVisible(false); - } - } - - this.type1Icon = globalScene.add - .sprite(player ? -139 : -15, player ? -17 : -15.5, `pbinfo_${player ? "player" : "enemy"}_type1`) - .setName("icon_type_1") - .setOrigin(0); - this.add(this.type1Icon); - - this.type2Icon = globalScene.add - .sprite(player ? -139 : -15, player ? -1 : -2.5, `pbinfo_${player ? "player" : "enemy"}_type2`) - .setName("icon_type_2") - .setOrigin(0); - this.add(this.type2Icon); - - this.type3Icon = globalScene.add - .sprite(player ? -154 : 0, player ? -17 : -15.5, `pbinfo_${player ? "player" : "enemy"}_type`) - .setName("icon_type_3") - .setOrigin(0); - this.add(this.type3Icon); + this.constructTypeIcons(); } getStatsValueContainer(): Phaser.GameObjects.Container { @@ -305,21 +337,6 @@ export default abstract class BattleInfo extends Phaser.GameObjects.Container { .on("pointerout", () => globalScene.ui.hideTooltip()); } - /** Called by {@linkcode initInfo} to initialize the type icons beside the battle info */ - initTypes(types: PokemonType[]) { - this.type1Icon - .setTexture(`pbinfo_${this.player ? "player" : "enemy"}_type${types.length > 1 ? "1" : ""}`) - .setFrame(PokemonType[types[0]].toLowerCase()); - this.type2Icon.setVisible(types.length > 1); - this.type3Icon.setVisible(types.length > 2); - if (types.length > 1) { - this.type2Icon.setFrame(PokemonType[types[1]].toLowerCase()); - } - if (types.length > 2) { - this.type3Icon.setFrame(PokemonType[types[2]].toLowerCase()); - } - } - initInfo(pokemon: Pokemon) { this.updateNameText(pokemon); const nameTextWidth = this.nameText.displayWidth; @@ -371,7 +388,7 @@ export default abstract class BattleInfo extends Phaser.GameObjects.Container { this.shinyIcon.setVisible(pokemon.isShiny()); - this.initTypes(pokemon.getTypes(true, false, undefined, true)); + this.setTypes(pokemon.getTypes(true, false, undefined, true)); const stats = this.statOrder.map(() => 0); @@ -452,7 +469,10 @@ export default abstract class BattleInfo extends Phaser.GameObjects.Container { return true; } - updateTypes(types: PokemonType[]): void { + /** + * Update the type icons to match the pokemon's types + */ + setTypes(types: PokemonType[]): void { this.type1Icon .setTexture(`pbinfo_${this.player ? "player" : "enemy"}_type${types.length > 1 ? "1" : ""}`) .setFrame(PokemonType[types[0]].toLowerCase()); @@ -628,16 +648,24 @@ export default abstract class BattleInfo extends Phaser.GameObjects.Container { } } - setLevel(level: number): void { - const isCapped = level >= globalScene.getMaxExpLevel(); + /** + * Set the level numbers container to display the provided level + * + * @remarks + * The numbers in the pokemon's level uses images for each number rather than a text object with a special font. + * This method sets the images for each digit of the level number and then positions the level container based + * on the number of digits. + * + * @param level - The level to display + * @param textureKey - The texture key for the level numbers + */ + setLevel(level: number, textureKey: "numbers" | "numbers_red" = "numbers"): void { this.levelNumbersContainer.removeAll(true); const levelStr = level.toString(); for (let i = 0; i < levelStr.length; i++) { - this.levelNumbersContainer.add( - globalScene.add.image(i * 8, 0, `numbers${isCapped && this.player ? "_red" : ""}`, levelStr[i]), - ); + this.levelNumbersContainer.add(globalScene.add.image(i * 8, 0, textureKey, levelStr[i])); } - this.levelContainer.setX((this.player ? -41 : -50) - 8 * Math.max(levelStr.length - 3, 0)); + this.levelContainer.setX(this.baseLvContainerX - 8 * Math.max(levelStr.length - 3, 0)); } updateStats(stats: number[]): void { diff --git a/src/ui/battle-info/enemy-battle-info.ts b/src/ui/battle-info/enemy-battle-info.ts index 195d1802f41..314143b50d4 100644 --- a/src/ui/battle-info/enemy-battle-info.ts +++ b/src/ui/battle-info/enemy-battle-info.ts @@ -1,4 +1,3 @@ -import BattleInfo from "./battle-info"; import { globalScene } from "#app/global-scene"; import BattleFlyout from "../battle-flyout"; import { addTextObject, TextStyle } from "#app/ui/text"; @@ -7,6 +6,8 @@ import { Stat } from "#enums/stat"; import i18next from "i18next"; import type { EnemyPokemon } from "#app/field/pokemon"; import type { GameObjects } from "phaser"; +import BattleInfo from "./battle-info"; +import type { BattleInfoParamList } from "./battle-info"; export class EnemyBattleInfo extends BattleInfo { protected player: false = false; @@ -31,8 +32,29 @@ export class EnemyBattleInfo extends BattleInfo { return this.boss ? "pbinfo_enemy_boss_mini" : "pbinfo_enemy_mini"; } + override constructTypeIcons(): void { + this.type1Icon = globalScene.add.sprite(-15, -15.5, "pbinfo_enemy_type1").setName("icon_type_1").setOrigin(0); + this.type2Icon = globalScene.add.sprite(-15, -2.5, "pbinfo_enemy_type2").setName("icon_type_2").setOrigin(0); + this.type3Icon = globalScene.add.sprite(0, 15.5, "pbinfo_enemy_type3").setName("icon_type_3").setOrigin(0); + this.add([this.type1Icon, this.type2Icon, this.type3Icon]); + } + constructor() { - super(140, -141, false); + const posParams: BattleInfoParamList = { + nameTextX: -124, + nameTextY: -11.2, + levelContainerX: -50, + levelContainerY: -5, + hpBarX: -71, + hpBarY: 4.5, + statBox: { + xOffset: 5, + paddingX: 2, + statOverflow: 0, + }, + }; + + super(140, -141, false, posParams); this.ownedIcon = globalScene.add.sprite(0, 0, "icon_owned").setName("icon_owned").setVisible(false).setOrigin(0, 0); this.ownedIcon.setPositionRelative(this.nameText, 0, 11.75); diff --git a/src/ui/battle-info/player-battle-info.ts b/src/ui/battle-info/player-battle-info.ts index 2a19f2c690c..634f89b7922 100644 --- a/src/ui/battle-info/player-battle-info.ts +++ b/src/ui/battle-info/player-battle-info.ts @@ -4,6 +4,7 @@ import { globalScene } from "#app/global-scene"; import { ExpGainsSpeed } from "#enums/exp-gains-speed"; import { Stat } from "#enums/stat"; import BattleInfo from "./battle-info"; +import type { BattleInfoParamList } from "./battle-info"; export class PlayerBattleInfo extends BattleInfo { protected player: true = true; @@ -17,8 +18,28 @@ export class PlayerBattleInfo extends BattleInfo { return this.mini ? "pbinfo_player_mini" : "pbinfo_player"; } + override constructTypeIcons(): void { + this.type1Icon = globalScene.add.sprite(-139, -17, "pbinfo_player_type1").setName("icon_type_1").setOrigin(0); + this.type2Icon = globalScene.add.sprite(-139, -1, "pbinfo_player_type2").setName("icon_type_2").setOrigin(0); + this.type3Icon = globalScene.add.sprite(-154, -17, "pbinfo_player_type3").setName("icon_type_3").setOrigin(0); + this.add([this.type1Icon, this.type2Icon, this.type3Icon]); + } + constructor() { - super(Math.floor(globalScene.game.canvas.width / 6) - 10, -72, true); + const posParams: BattleInfoParamList = { + nameTextX: -115, + nameTextY: -15.2, + levelContainerX: -41, + levelContainerY: -10, + hpBarX: -61, + hpBarY: -1, + statBox: { + xOffset: 8, + paddingX: 4, + statOverflow: 1, + }, + }; + super(Math.floor(globalScene.game.canvas.width / 6) - 10, -72, true, posParams); this.hpNumbersContainer = globalScene.add.container(-15, 10).setName("container_hp"); @@ -208,4 +229,14 @@ export class PlayerBattleInfo extends BattleInfo { this.hpNumbersContainer.add(globalScene.add.image(offset++ * -8, 0, "numbers", hpStr[i])); } } + + /** + * Set the level numbers container to display the provided level + * + * Overrides the default implementation to handle displaying level capped numbers in red. + * @param level - The level to display + */ + override setLevel(level: number): void { + super.setLevel(level, level >= globalScene.getMaxExpLevel() ? "numbers_red" : "numbers"); + } }