diff --git a/.gitignore b/.gitignore index 1b1bc0743c3..669f8df003f 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,7 @@ src/data/battle-anim-data.ts src/overrides.ts coverage +/.vs # Local Documentation /typedoc diff --git a/src/locales/en/pokemon-info-container.ts b/src/locales/en/pokemon-info-container.ts index 068c9ebb431..6b3ef954f58 100644 --- a/src/locales/en/pokemon-info-container.ts +++ b/src/locales/en/pokemon-info-container.ts @@ -7,5 +7,6 @@ export const pokemonInfoContainer: SimpleTranslationEntries = { "nature": "Nature:", "epic": "Epic", "rare": "Rare", - "common": "Common" + "common": "Common", + "form": "Form:" } as const; diff --git a/src/ui/pokemon-info-container.ts b/src/ui/pokemon-info-container.ts index 1ffa32d2394..0a63c817762 100644 --- a/src/ui/pokemon-info-container.ts +++ b/src/ui/pokemon-info-container.ts @@ -11,6 +11,7 @@ import ConfirmUiHandler from "./confirm-ui-handler"; import { StatsContainer } from "./stats-container"; import { TextStyle, addBBCodeTextObject, addTextObject, getTextColor } from "./text"; import { addWindow } from "./ui-theme"; +import { DexAttr } from "../system/game-data"; interface LanguageSetting { infoContainerTextSize: string; @@ -47,19 +48,24 @@ const languageSettings: { [key: string]: LanguageSetting } = { export default class PokemonInfoContainer extends Phaser.GameObjects.Container { private readonly infoWindowWidth = 104; - private pokemonGenderLabelText: Phaser.GameObjects.Text; + private pokemonFormLabelText: Phaser.GameObjects.Text; + private pokemonFormText: Phaser.GameObjects.Text; private pokemonGenderText: Phaser.GameObjects.Text; + private pokemonGenderNewText: Phaser.GameObjects.Text; private pokemonAbilityLabelText: Phaser.GameObjects.Text; private pokemonAbilityText: Phaser.GameObjects.Text; private pokemonNatureLabelText: Phaser.GameObjects.Text; private pokemonNatureText: BBCodeText; private pokemonShinyIcon: Phaser.GameObjects.Image; + private pokemonShinyNewIcon: Phaser.GameObjects.Text; private pokemonFusionShinyIcon: Phaser.GameObjects.Image; private pokemonMovesContainer: Phaser.GameObjects.Container; private pokemonMovesContainers: Phaser.GameObjects.Container[]; private pokemonMoveBgs: Phaser.GameObjects.NineSlice[]; private pokemonMoveLabels: Phaser.GameObjects.Text[]; + private numCharsBeforeCutoff = 16; + private initialX: number; private movesContainerInitialX: number; @@ -137,34 +143,44 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { // The font size should be set by language const infoContainerTextSize = textSettings?.infoContainerTextSize || "64px"; - this.pokemonGenderLabelText = addTextObject(this.scene, infoContainerLabelXPos, 18, i18next.t("pokemonInfoContainer:gender"), TextStyle.WINDOW, { fontSize: infoContainerTextSize }); - this.pokemonGenderLabelText.setOrigin(1, 0); - this.pokemonGenderLabelText.setVisible(false); - this.pokemonGenderLabelText.setName("text-pkmn-gender-label"); - this.add(this.pokemonGenderLabelText); + this.pokemonFormLabelText = addTextObject(this.scene, infoContainerLabelXPos, 19, i18next.t("pokemonInfoContainer:form"), TextStyle.WINDOW, { fontSize: infoContainerTextSize }); + this.pokemonFormLabelText.setOrigin(1, 0); + this.pokemonFormLabelText.setVisible(false); + this.add(this.pokemonFormLabelText); - this.pokemonGenderText = addTextObject(this.scene, infoContainerTextXPos, 18, "", TextStyle.WINDOW, { fontSize: infoContainerTextSize }); + this.pokemonFormText = addTextObject(this.scene, infoContainerTextXPos, 19, "", TextStyle.WINDOW, { fontSize: infoContainerTextSize }); + this.pokemonFormText.setOrigin(0, 0); + this.pokemonFormText.setVisible(false); + this.add(this.pokemonFormText); + + this.pokemonGenderText = addTextObject(this.scene, -42, -61, "", TextStyle.WINDOW, { fontSize: infoContainerTextSize }); this.pokemonGenderText.setOrigin(0, 0); this.pokemonGenderText.setVisible(false); this.pokemonGenderText.setName("text-pkmn-gender"); this.add(this.pokemonGenderText); - this.pokemonAbilityLabelText = addTextObject(this.scene, infoContainerLabelXPos, 28, i18next.t("pokemonInfoContainer:ability"), TextStyle.WINDOW, { fontSize: infoContainerTextSize }); + this.pokemonGenderNewText = addTextObject(this.scene, -36, -61, "", TextStyle.WINDOW, { fontSize: "64px" }); + this.pokemonGenderNewText.setOrigin(0, 0); + this.pokemonGenderNewText.setVisible(false); + this.pokemonGenderNewText.setName("text-pkmn-new-gender"); + this.add(this.pokemonGenderNewText); + + this.pokemonAbilityLabelText = addTextObject(this.scene, infoContainerLabelXPos, 29, i18next.t("pokemonInfoContainer:ability"), TextStyle.WINDOW, { fontSize: infoContainerTextSize }); this.pokemonAbilityLabelText.setOrigin(1, 0); this.pokemonAbilityLabelText.setName("text-pkmn-ability-label"); this.add(this.pokemonAbilityLabelText); - this.pokemonAbilityText = addTextObject(this.scene, infoContainerTextXPos, 28, "", TextStyle.WINDOW, { fontSize: infoContainerTextSize }); + this.pokemonAbilityText = addTextObject(this.scene, infoContainerTextXPos, 29, "", TextStyle.WINDOW, { fontSize: infoContainerTextSize }); this.pokemonAbilityText.setOrigin(0, 0); this.pokemonAbilityText.setName("text-pkmn-ability"); this.add(this.pokemonAbilityText); - this.pokemonNatureLabelText = addTextObject(this.scene, infoContainerLabelXPos, 38, i18next.t("pokemonInfoContainer:nature"), TextStyle.WINDOW, { fontSize: infoContainerTextSize }); + this.pokemonNatureLabelText = addTextObject(this.scene, infoContainerLabelXPos, 39, i18next.t("pokemonInfoContainer:nature"), TextStyle.WINDOW, { fontSize: infoContainerTextSize }); this.pokemonNatureLabelText.setOrigin(1, 0); this.pokemonNatureLabelText.setName("text-pkmn-nature-label"); this.add(this.pokemonNatureLabelText); - this.pokemonNatureText = addBBCodeTextObject(this.scene, infoContainerTextXPos, 38, "", TextStyle.WINDOW, { fontSize: infoContainerTextSize, lineSpacing: 3, maxLines: 2 }); + this.pokemonNatureText = addBBCodeTextObject(this.scene, infoContainerTextXPos, 39, "", TextStyle.WINDOW, { fontSize: infoContainerTextSize, lineSpacing: 3, maxLines: 2 }); this.pokemonNatureText.setOrigin(0, 0); this.pokemonNatureText.setName("text-pkmn-nature"); this.add(this.pokemonNatureText); @@ -176,6 +192,12 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { this.pokemonShinyIcon.setName("img-pkmn-shiny-icon"); this.add(this.pokemonShinyIcon); + this.pokemonShinyNewIcon = addTextObject(this.scene, this.pokemonShinyIcon.x + 12, this.pokemonShinyIcon.y, "", TextStyle.WINDOW, { fontSize: infoContainerTextSize }); + this.pokemonShinyNewIcon.setOrigin(0, 0); + this.pokemonShinyNewIcon.setName("text-pkmn-shiny-new-icon"); + this.add(this.pokemonShinyNewIcon); + this.pokemonShinyNewIcon.setVisible(false); + this.pokemonFusionShinyIcon = this.scene.add.image(this.pokemonShinyIcon.x, this.pokemonShinyIcon.y, "shiny_star_2"); this.pokemonFusionShinyIcon.setOrigin(0, 0); this.pokemonFusionShinyIcon.setScale(0.75); @@ -187,23 +209,83 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { show(pokemon: Pokemon, showMoves: boolean = false, speedMultiplier: number = 1): Promise { return new Promise(resolve => { + const caughtAttr = BigInt(pokemon.scene.gameData.dexData[pokemon.species.speciesId].caughtAttr); if (pokemon.gender > Gender.GENDERLESS) { this.pokemonGenderText.setText(getGenderSymbol(pokemon.gender)); this.pokemonGenderText.setColor(getGenderColor(pokemon.gender)); this.pokemonGenderText.setShadowColor(getGenderColor(pokemon.gender, true)); - this.pokemonGenderLabelText.setVisible(true); this.pokemonGenderText.setVisible(true); + + const newGender = BigInt(Math.pow(2, pokemon.gender)) * DexAttr.MALE; + this.pokemonGenderNewText.setText("(+)"); + this.pokemonGenderNewText.setColor(getTextColor(TextStyle.SUMMARY_BLUE, false, this.scene.uiTheme)); + this.pokemonGenderNewText.setShadowColor(getTextColor(TextStyle.SUMMARY_BLUE, true, this.scene.uiTheme)); + this.pokemonGenderNewText.setVisible((newGender & caughtAttr) === BigInt(0)); } else { this.pokemonGenderText.setVisible(false); } + if (pokemon.species.forms?.[pokemon.formIndex]?.formName) { + this.pokemonFormLabelText.setVisible(true); + this.pokemonFormText.setVisible(true); + const newForm = BigInt(Math.pow(2, pokemon.formIndex)) * DexAttr.DEFAULT_FORM; + + if ((newForm & caughtAttr) === BigInt(0)) { + this.pokemonFormLabelText.setColor(getTextColor(TextStyle.SUMMARY_BLUE, false, this.scene.uiTheme)); + this.pokemonFormLabelText.setShadowColor(getTextColor(TextStyle.SUMMARY_BLUE, true, this.scene.uiTheme)); + } else { + this.pokemonFormLabelText.setColor(getTextColor(TextStyle.WINDOW, false, this.scene.uiTheme)); + this.pokemonFormLabelText.setShadowColor(getTextColor(TextStyle.WINDOW, true, this.scene.uiTheme)); + } + + const formName = pokemon.species.forms?.[pokemon.formIndex]?.formName; + this.pokemonFormText.setText(formName.length > this.numCharsBeforeCutoff ? formName.substring(0, this.numCharsBeforeCutoff - 3) + "..." : formName); + if (formName.length > this.numCharsBeforeCutoff) { + this.pokemonFormText.setInteractive(new Phaser.Geom.Rectangle(0, 0, this.pokemonFormText.width, this.pokemonFormText.height), Phaser.Geom.Rectangle.Contains); + this.pokemonFormText.on("pointerover", () => (this.scene as BattleScene).ui.showTooltip(null, pokemon.species.forms?.[pokemon.formIndex]?.formName, true)); + this.pokemonFormText.on("pointerout", () => (this.scene as BattleScene).ui.hideTooltip()); + } else { + this.pokemonFormText.disableInteractive(); + } + } + const abilityTextStyle = pokemon.abilityIndex === (pokemon.species.ability2 ? 2 : 1) ? TextStyle.MONEY : TextStyle.WINDOW; this.pokemonAbilityText.setText(pokemon.getAbility(true).name); this.pokemonAbilityText.setColor(getTextColor(abilityTextStyle, false, this.scene.uiTheme)); this.pokemonAbilityText.setShadowColor(getTextColor(abilityTextStyle, true, this.scene.uiTheme)); + /** + * If the opposing Pokemon only has 1 normal ability and is using the hidden ability it should have the same behavior + * if it had 2 normal abilities. This code checks if that is the case and uses the correct opponent Pokemon abilityIndex (2) + * for calculations so it aligns with where the hidden ability is stored in the starter data's abilityAttr (4) + */ + const opponentPokemonOneNormalAbility = (pokemon.species.getAbilityCount() === 2); + const opponentPokemonAbilityIndex = (opponentPokemonOneNormalAbility && pokemon.abilityIndex === 1) ? 2 : pokemon.abilityIndex; + const opponentPokemonAbilityAttr = Math.pow(2, opponentPokemonAbilityIndex); + + const rootFormHasHiddenAbility = pokemon.scene.gameData.starterData[pokemon.species.getRootSpeciesId()].abilityAttr & opponentPokemonAbilityAttr; + + if (!rootFormHasHiddenAbility) { + this.pokemonAbilityLabelText.setColor(getTextColor(TextStyle.SUMMARY_BLUE, false, this.scene.uiTheme)); + this.pokemonAbilityLabelText.setShadowColor(getTextColor(TextStyle.SUMMARY_BLUE, true, this.scene.uiTheme)); + } else { + this.pokemonAbilityLabelText.setColor(getTextColor(TextStyle.WINDOW, false, this.scene.uiTheme)); + this.pokemonAbilityLabelText.setShadowColor(getTextColor(TextStyle.WINDOW, true, this.scene.uiTheme)); + } + this.pokemonNatureText.setText(getNatureName(pokemon.getNature(), true, false, false, this.scene.uiTheme)); + const dexNatures = pokemon.scene.gameData.dexData[pokemon.species.speciesId].natureAttr; + const newNature = Math.pow(2, pokemon.nature + 1); + + if (!(dexNatures & newNature)) { + this.pokemonNatureLabelText.setColor(getTextColor(TextStyle.SUMMARY_BLUE, false, this.scene.uiTheme)); + this.pokemonNatureLabelText.setShadowColor(getTextColor(TextStyle.SUMMARY_BLUE, true, this.scene.uiTheme)); + } else { + this.pokemonNatureLabelText.setColor(getTextColor(TextStyle.WINDOW, false, this.scene.uiTheme)); + this.pokemonNatureLabelText.setShadowColor(getTextColor(TextStyle.WINDOW, true, this.scene.uiTheme)); + } + const isFusion = pokemon.isFusion(); const doubleShiny = isFusion && pokemon.shiny && pokemon.fusionShiny; const baseVariant = !doubleShiny ? pokemon.getVariant() : pokemon.variant; @@ -217,6 +299,15 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { : ""; this.pokemonShinyIcon.on("pointerover", () => (this.scene as BattleScene).ui.showTooltip(null, `Shiny${shinyDescriptor ? ` (${shinyDescriptor})` : ""}`, true)); this.pokemonShinyIcon.on("pointerout", () => (this.scene as BattleScene).ui.hideTooltip()); + + const newShiny = BigInt(Math.pow(2, (pokemon.shiny ? 1 : 0))); + const newVariant = BigInt(Math.pow(2, pokemon.variant + 4)); + + this.pokemonShinyNewIcon.setText("(+)"); + this.pokemonShinyNewIcon.setColor(getTextColor(TextStyle.SUMMARY_BLUE, false, this.scene.uiTheme)); + this.pokemonShinyNewIcon.setShadowColor(getTextColor(TextStyle.SUMMARY_BLUE, true, this.scene.uiTheme)); + const newShinyOrVariant = ((newShiny & caughtAttr) === BigInt(0)) || ((newVariant & caughtAttr) === BigInt(0)); + this.pokemonShinyNewIcon.setVisible(!!newShinyOrVariant); } this.pokemonFusionShinyIcon.setPosition(this.pokemonShinyIcon.x, this.pokemonShinyIcon.y); diff --git a/src/ui/stats-container.ts b/src/ui/stats-container.ts index 6dccba18fa8..5414508ef70 100644 --- a/src/ui/stats-container.ts +++ b/src/ui/stats-container.ts @@ -4,7 +4,10 @@ import { Stat, getStatName } from "../data/pokemon-stat"; import { TextStyle, addBBCodeTextObject, addTextObject, getTextColor } from "./text"; const ivChartSize = 24; -const ivChartStatCoordMultipliers = [ [ 0, -1 ], [ 0.825, -0.5 ], [ 0.825, 0.5 ], [ -0.825, -0.5 ], [ -0.825, 0.5 ], [ 0, 1 ] ]; +const ivChartStatCoordMultipliers = [[0, -1], [0.825, -0.5], [0.825, 0.5], [-0.825, -0.5], [-0.825, 0.5], [0, 1]]; +const speedLabelOffset = -3; +const sideLabelOffset = 1; +const ivLabelOffset = [0, sideLabelOffset, -sideLabelOffset, sideLabelOffset, -sideLabelOffset, speedLabelOffset]; const ivChartStatIndexes = [0,1,2,5,4,3]; // swap special attack and speed const defaultIvChartData = new Array(12).fill(null).map(() => 0); @@ -51,7 +54,7 @@ export class StatsContainer extends Phaser.GameObjects.Container { this.ivStatValueTexts = []; new Array(6).fill(null).map((_, i: integer) => { - const statLabel = addTextObject(this.scene, ivChartBg.x + (ivChartSize) * ivChartStatCoordMultipliers[i][0] * 1.325, ivChartBg.y + (ivChartSize) * ivChartStatCoordMultipliers[i][1] * 1.325 - 4, getStatName(i as Stat), TextStyle.TOOLTIP_CONTENT); + const statLabel = addTextObject(this.scene, ivChartBg.x + (ivChartSize) * ivChartStatCoordMultipliers[i][0] * 1.325, ivChartBg.y + (ivChartSize) * ivChartStatCoordMultipliers[i][1] * 1.325 - 4 + ivLabelOffset[i], getStatName(i as Stat), TextStyle.TOOLTIP_CONTENT); statLabel.setOrigin(0.5); this.ivStatValueTexts[i] = addBBCodeTextObject(this.scene, statLabel.x, statLabel.y + 8, "0", TextStyle.TOOLTIP_CONTENT);