pokerogue/src/ui/pokedex-page-ui-handler.ts
Wlowscha be0f3b2ab3
[Bug][UI/UX] Dex bug fixes 12 feb (#5307)
* Fixed bug when setting tint of eggs for egg move filter

* Form text is properly hidden when going to filters

* Displaying level up moves of alternate forms

* Offsetting form cycle button when uncaught text is shown

* Pokedex buttons now appear on mobile touchpad

* Hotfix to prevent "Normal" form to showing the text

* Preventing filter texts from showing gibberish

* Moving cursor to input text when pressing on filter

* Introducing method to get full obtainable unlocks for a given species

* Filtering obtainable unlocks in dex

* Buying eggs in dex does not crash the game

* Shiny icon does not overlap with luck text for evolutions

* Shiny is maintained correctly when cycling through different pages

* Displacing text elements to avoid overlap for localized form names

* Checking starter caughtAttr in addition to pokemon caughtAttr
2025-02-12 19:14:22 -08:00

2446 lines
99 KiB
TypeScript

import type { SpeciesFormEvolution } from "#app/data/balance/pokemon-evolutions";
import { pokemonEvolutions, pokemonPrevolutions, pokemonStarters } from "#app/data/balance/pokemon-evolutions";
import type { Variant } from "#app/data/variant";
import { getVariantTint, getVariantIcon } from "#app/data/variant";
import { argbFromRgba } from "@material/material-color-utilities";
import i18next from "i18next";
import { starterColors } from "#app/battle-scene";
import { allAbilities } from "#app/data/ability";
import { speciesEggMoves } from "#app/data/balance/egg-moves";
import { GrowthRate, getGrowthRateColor } from "#app/data/exp";
import { Gender, getGenderColor, getGenderSymbol } from "#app/data/gender";
import { allMoves } from "#app/data/move";
import { getNatureName } from "#app/data/nature";
import type { SpeciesFormChange } from "#app/data/pokemon-forms";
import { pokemonFormChanges } from "#app/data/pokemon-forms";
import type { LevelMoves } from "#app/data/balance/pokemon-level-moves";
import { pokemonFormLevelMoves, pokemonSpeciesLevelMoves } from "#app/data/balance/pokemon-level-moves";
import type PokemonSpecies from "#app/data/pokemon-species";
import { allSpecies, getPokemonSpeciesForm, normalForm } from "#app/data/pokemon-species";
import { getStarterValueFriendshipCap, speciesStarterCosts } from "#app/data/balance/starters";
import { starterPassiveAbilities } from "#app/data/balance/passives";
import { Type } from "#enums/type";
import { GameModes } from "#app/game-mode";
import type { DexEntry, StarterAttributes } from "#app/system/game-data";
import { AbilityAttr, DexAttr } from "#app/system/game-data";
import type { OptionSelectItem } from "#app/ui/abstact-option-select-ui-handler";
import MessageUiHandler from "#app/ui/message-ui-handler";
import { StatsContainer } from "#app/ui/stats-container";
import { TextStyle, addTextObject, getTextStyleOptions } from "#app/ui/text";
import { Mode } from "#app/ui/ui";
import { addWindow } from "#app/ui/ui-theme";
import { Egg } from "#app/data/egg";
import Overrides from "#app/overrides";
import { SettingKeyboard } from "#app/system/settings/settings-keyboard";
import { Passive as PassiveAttr } from "#enums/passive";
import * as Challenge from "#app/data/challenge";
import MoveInfoOverlay from "#app/ui/move-info-overlay";
import PokedexInfoOverlay from "#app/ui/pokedex-info-overlay";
import { getEggTierForSpecies } from "#app/data/egg";
import { Device } from "#enums/devices";
import type { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import { Button } from "#enums/buttons";
import { EggSourceType } from "#enums/egg-source-types";
import { getPassiveCandyCount, getValueReductionCandyCounts, getSameSpeciesEggCandyCounts } from "#app/data/balance/starters";
import { BooleanHolder, getLocalizedSpriteKey, isNullOrUndefined, NumberHolder, padInt, rgbHexToRgba, toReadableString } from "#app/utils";
import type { Nature } from "#enums/nature";
import BgmBar from "./bgm-bar";
import * as Utils from "../utils";
import { speciesTmMoves } from "#app/data/balance/tms";
import type { BiomeTierTod } from "#app/data/balance/biomes";
import { BiomePoolTier, catchableSpecies } from "#app/data/balance/biomes";
import { Biome } from "#app/enums/biome";
import { TimeOfDay } from "#app/enums/time-of-day";
import type { Abilities } from "#app/enums/abilities";
import { BaseStatsOverlay } from "#app/ui/base-stats-overlay";
import { globalScene } from "#app/global-scene";
interface LanguageSetting {
starterInfoTextSize: string,
instructionTextSize: string,
starterInfoXPos?: number,
starterInfoYOffset?: number
}
const languageSettings: { [key: string]: LanguageSetting } = {
"en":{
starterInfoTextSize: "56px",
instructionTextSize: "38px",
},
"de":{
starterInfoTextSize: "48px",
instructionTextSize: "35px",
starterInfoXPos: 33,
},
"es-ES":{
starterInfoTextSize: "56px",
instructionTextSize: "35px",
},
"fr":{
starterInfoTextSize: "54px",
instructionTextSize: "38px",
},
"it":{
starterInfoTextSize: "56px",
instructionTextSize: "38px",
},
"pt_BR":{
starterInfoTextSize: "47px",
instructionTextSize: "38px",
starterInfoXPos: 33,
},
"zh":{
starterInfoTextSize: "47px",
instructionTextSize: "38px",
starterInfoYOffset: 1,
starterInfoXPos: 24,
},
"pt":{
starterInfoTextSize: "48px",
instructionTextSize: "42px",
starterInfoXPos: 33,
},
"ko":{
starterInfoTextSize: "52px",
instructionTextSize: "38px",
},
"ja":{
starterInfoTextSize: "51px",
instructionTextSize: "38px",
},
"ca-ES":{
starterInfoTextSize: "56px",
instructionTextSize: "38px",
},
};
const valueReductionMax = 2;
// Position of UI elements
const speciesContainerX = 109;
interface SpeciesDetails {
shiny?: boolean,
formIndex?: number
female?: boolean,
variant?: number,
}
enum MenuOptions {
BASE_STATS,
ABILITIES,
LEVEL_MOVES,
EGG_MOVES,
TM_MOVES,
BIOMES,
NATURES,
TOGGLE_IVS,
EVOLUTIONS
}
export default class PokedexPageUiHandler extends MessageUiHandler {
private starterSelectContainer: Phaser.GameObjects.Container;
private shinyOverlay: Phaser.GameObjects.Image;
private pokemonNumberText: Phaser.GameObjects.Text;
private pokemonSprite: Phaser.GameObjects.Sprite;
private pokemonNameText: Phaser.GameObjects.Text;
private pokemonGrowthRateLabelText: Phaser.GameObjects.Text;
private pokemonGrowthRateText: Phaser.GameObjects.Text;
private type1Icon: Phaser.GameObjects.Sprite;
private type2Icon: Phaser.GameObjects.Sprite;
private pokemonLuckLabelText: Phaser.GameObjects.Text;
private pokemonLuckText: Phaser.GameObjects.Text;
private pokemonGenderText: Phaser.GameObjects.Text;
private pokemonUncaughtText: Phaser.GameObjects.Text;
private pokemonCandyContainer: Phaser.GameObjects.Container;
private pokemonCandyIcon: Phaser.GameObjects.Sprite;
private pokemonCandyDarknessOverlay: Phaser.GameObjects.Sprite;
private pokemonCandyOverlayIcon: Phaser.GameObjects.Sprite;
private pokemonCandyCountText: Phaser.GameObjects.Text;
private pokemonCaughtHatchedContainer: Phaser.GameObjects.Container;
private pokemonCaughtCountText: Phaser.GameObjects.Text;
private pokemonFormText: Phaser.GameObjects.Text;
private pokemonHatchedIcon : Phaser.GameObjects.Sprite;
private pokemonHatchedCountText: Phaser.GameObjects.Text;
private pokemonShinyIcon: Phaser.GameObjects.Sprite;
private activeTooltip: "ABILITY" | "PASSIVE" | "CANDY" | undefined;
private instructionsContainer: Phaser.GameObjects.Container;
private filterInstructionsContainer: Phaser.GameObjects.Container;
private shinyIconElement: Phaser.GameObjects.Sprite;
private formIconElement: Phaser.GameObjects.Sprite;
private genderIconElement: Phaser.GameObjects.Sprite;
private variantIconElement: Phaser.GameObjects.Sprite;
private shinyLabel: Phaser.GameObjects.Text;
private formLabel: Phaser.GameObjects.Text;
private genderLabel: Phaser.GameObjects.Text;
private variantLabel: Phaser.GameObjects.Text;
private candyUpgradeIconElement: Phaser.GameObjects.Sprite;
private candyUpgradeLabel: Phaser.GameObjects.Text;
private showBackSpriteIconElement: Phaser.GameObjects.Sprite;
private showBackSpriteLabel: Phaser.GameObjects.Text;
private starterSelectMessageBox: Phaser.GameObjects.NineSlice;
private starterSelectMessageBoxContainer: Phaser.GameObjects.Container;
private statsContainer: StatsContainer;
private moveInfoOverlay: MoveInfoOverlay;
private infoOverlay: PokedexInfoOverlay;
private baseStatsOverlay: BaseStatsOverlay;
private statsMode: boolean;
private allSpecies: PokemonSpecies[] = [];
private species: PokemonSpecies;
private starterId: number;
private formIndex: number;
private speciesLoaded: Map<Species, boolean> = new Map<Species, boolean>();
private levelMoves: LevelMoves;
private eggMoves: Moves[] = [];
private hasEggMoves: boolean[] = [];
private tmMoves: Moves[] = [];
private ability1: Abilities;
private ability2: Abilities | undefined;
private abilityHidden: Abilities | undefined;
private passive: Abilities;
private hasPassive: boolean;
private hasAbilities: number[];
private biomes: BiomeTierTod[];
private preBiomes: BiomeTierTod[];
private baseStats: number[];
private baseTotal: number;
private evolutions: SpeciesFormEvolution[];
private battleForms: SpeciesFormChange[];
private prevolutions: SpeciesFormEvolution[];
private speciesStarterDexEntry: DexEntry | null;
private canCycleShiny: boolean;
private canCycleForm: boolean;
private canCycleGender: boolean;
private assetLoadCancelled: BooleanHolder | null;
public cursorObj: Phaser.GameObjects.Image;
// variables to keep track of the dynamically rendered list of instruction prompts for starter select
private instructionRowX = 0;
private instructionRowY = 0;
private instructionRowTextOffset = 9;
private filterInstructionRowX = 0;
private filterInstructionRowY = 0;
private starterAttributes: StarterAttributes;
private savedStarterAttributes: StarterAttributes;
protected blockInput: boolean = false;
protected blockInputOverlay: boolean = false;
private showBackSprite: boolean = false;
// Menu
private menuContainer: Phaser.GameObjects.Container;
private menuBg: Phaser.GameObjects.NineSlice;
protected optionSelectText: Phaser.GameObjects.Text;
public bgmBar: BgmBar;
private menuOptions: MenuOptions[];
protected scale: number = 0.1666666667;
private menuDescriptions: string[];
constructor() {
super(Mode.POKEDEX_PAGE);
}
setup() {
const ui = this.getUi();
const currentLanguage = i18next.resolvedLanguage ?? "en";
const langSettingKey = Object.keys(languageSettings).find(lang => currentLanguage.includes(lang)) ?? "en";
const textSettings = languageSettings[langSettingKey];
this.starterSelectContainer = globalScene.add.container(0, -globalScene.game.canvas.height / 6);
this.starterSelectContainer.setVisible(false);
ui.add(this.starterSelectContainer);
const bgColor = globalScene.add.rectangle(0, 0, globalScene.game.canvas.width / 6, globalScene.game.canvas.height / 6, 0x006860);
bgColor.setOrigin(0, 0);
this.starterSelectContainer.add(bgColor);
const starterSelectBg = globalScene.add.image(0, 0, "pokedex_summary_bg");
starterSelectBg.setOrigin(0, 0);
this.starterSelectContainer.add(starterSelectBg);
this.shinyOverlay = globalScene.add.image(6, 6, "summary_overlay_shiny");
this.shinyOverlay.setOrigin(0, 0);
this.shinyOverlay.setVisible(false);
this.starterSelectContainer.add(this.shinyOverlay);
this.pokemonNumberText = addTextObject(17, 1, "0000", TextStyle.SUMMARY);
this.pokemonNumberText.setOrigin(0, 0);
this.starterSelectContainer.add(this.pokemonNumberText);
this.pokemonNameText = addTextObject(6, 112, "", TextStyle.SUMMARY);
this.pokemonNameText.setOrigin(0, 0);
this.starterSelectContainer.add(this.pokemonNameText);
this.pokemonGrowthRateLabelText = addTextObject(8, 106, i18next.t("pokedexUiHandler:growthRate"), TextStyle.SUMMARY_ALT, { fontSize: "36px" });
this.pokemonGrowthRateLabelText.setOrigin(0, 0);
this.pokemonGrowthRateLabelText.setVisible(false);
this.starterSelectContainer.add(this.pokemonGrowthRateLabelText);
this.pokemonGrowthRateText = addTextObject(34, 106, "", TextStyle.SUMMARY_PINK, { fontSize: "36px" });
this.pokemonGrowthRateText.setOrigin(0, 0);
this.starterSelectContainer.add(this.pokemonGrowthRateText);
this.pokemonGenderText = addTextObject(96, 112, "", TextStyle.SUMMARY_ALT);
this.pokemonGenderText.setOrigin(0, 0);
this.starterSelectContainer.add(this.pokemonGenderText);
this.pokemonUncaughtText = addTextObject(6, 127, i18next.t("pokedexUiHandler:uncaught"), TextStyle.WINDOW, { fontSize: "56px" });
this.pokemonUncaughtText.setOrigin(0, 0);
this.starterSelectContainer.add(this.pokemonUncaughtText);
const starterBoxContainer = globalScene.add.container(speciesContainerX + 6, 9); //115
for (const species of allSpecies) {
if (!speciesStarterCosts.hasOwnProperty(species.speciesId) || !species.isObtainable()) {
continue;
}
this.speciesLoaded.set(species.speciesId, false);
this.allSpecies.push(species);
}
this.starterSelectContainer.add(starterBoxContainer);
this.pokemonSprite = globalScene.add.sprite(53, 63, "pkmn__sub");
this.pokemonSprite.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], ignoreTimeTint: true });
this.starterSelectContainer.add(this.pokemonSprite);
this.type1Icon = globalScene.add.sprite(8, 98, getLocalizedSpriteKey("types"));
this.type1Icon.setScale(0.5);
this.type1Icon.setOrigin(0, 0);
this.starterSelectContainer.add(this.type1Icon);
this.type2Icon = globalScene.add.sprite(26, 98, getLocalizedSpriteKey("types"));
this.type2Icon.setScale(0.5);
this.type2Icon.setOrigin(0, 0);
this.starterSelectContainer.add(this.type2Icon);
this.pokemonLuckLabelText = addTextObject(8, 89, i18next.t("common:luckIndicator"), TextStyle.WINDOW_ALT, { fontSize: "56px" });
this.pokemonLuckLabelText.setOrigin(0, 0);
this.starterSelectContainer.add(this.pokemonLuckLabelText);
this.pokemonLuckText = addTextObject(8 + this.pokemonLuckLabelText.displayWidth + 2, 89, "0", TextStyle.WINDOW, { fontSize: "56px" });
this.pokemonLuckText.setOrigin(0, 0);
this.starterSelectContainer.add(this.pokemonLuckText);
// Candy icon and count
this.pokemonCandyContainer = globalScene.add.container(4.5, 18);
this.pokemonCandyIcon = globalScene.add.sprite(0, 0, "candy");
this.pokemonCandyIcon.setScale(0.5);
this.pokemonCandyIcon.setOrigin(0, 0);
this.pokemonCandyContainer.add(this.pokemonCandyIcon);
this.pokemonCandyOverlayIcon = globalScene.add.sprite(0, 0, "candy_overlay");
this.pokemonCandyOverlayIcon.setScale(0.5);
this.pokemonCandyOverlayIcon.setOrigin(0, 0);
this.pokemonCandyContainer.add(this.pokemonCandyOverlayIcon);
this.pokemonCandyDarknessOverlay = globalScene.add.sprite(0, 0, "candy");
this.pokemonCandyDarknessOverlay.setScale(0.5);
this.pokemonCandyDarknessOverlay.setOrigin(0, 0);
this.pokemonCandyDarknessOverlay.setTint(0x000000);
this.pokemonCandyDarknessOverlay.setAlpha(0.50);
this.pokemonCandyContainer.add(this.pokemonCandyDarknessOverlay);
this.pokemonCandyCountText = addTextObject(9.5, 0, "x0", TextStyle.WINDOW_ALT, { fontSize: "56px" });
this.pokemonCandyCountText.setOrigin(0, 0);
this.pokemonCandyContainer.add(this.pokemonCandyCountText);
this.pokemonCandyContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, 30, 20), Phaser.Geom.Rectangle.Contains);
this.starterSelectContainer.add(this.pokemonCandyContainer);
this.pokemonFormText = addTextObject(6, 42, "Form", TextStyle.WINDOW_ALT, { fontSize: "42px" });
this.pokemonFormText.setOrigin(0, 0);
this.starterSelectContainer.add(this.pokemonFormText);
this.pokemonCaughtHatchedContainer = globalScene.add.container(2, 25);
this.pokemonCaughtHatchedContainer.setScale(0.5);
this.starterSelectContainer.add(this.pokemonCaughtHatchedContainer);
const pokemonCaughtIcon = globalScene.add.sprite(1, 0, "items", "pb");
pokemonCaughtIcon.setOrigin(0, 0);
pokemonCaughtIcon.setScale(0.75);
this.pokemonCaughtHatchedContainer.add(pokemonCaughtIcon);
this.pokemonCaughtCountText = addTextObject(24, 4, "0", TextStyle.SUMMARY_ALT);
this.pokemonCaughtCountText.setOrigin(0, 0);
this.pokemonCaughtHatchedContainer.add(this.pokemonCaughtCountText);
this.pokemonHatchedIcon = globalScene.add.sprite(1, 14, "egg_icons");
this.pokemonHatchedIcon.setOrigin(0.15, 0.2);
this.pokemonHatchedIcon.setScale(0.8);
this.pokemonCaughtHatchedContainer.add(this.pokemonHatchedIcon);
this.pokemonShinyIcon = globalScene.add.sprite(14, 117, "shiny_icons");
this.pokemonShinyIcon.setOrigin(0.15, 0.2);
this.pokemonShinyIcon.setScale(1);
this.pokemonCaughtHatchedContainer.add(this.pokemonShinyIcon);
this.pokemonHatchedCountText = addTextObject(24, 19, "0", TextStyle.SUMMARY_ALT);
this.pokemonHatchedCountText.setOrigin(0, 0);
this.pokemonCaughtHatchedContainer.add(this.pokemonHatchedCountText);
// The font size should be set per language
const instructionTextSize = textSettings.instructionTextSize;
this.instructionsContainer = globalScene.add.container(4, 128);
this.instructionsContainer.setVisible(true);
this.starterSelectContainer.add(this.instructionsContainer);
this.candyUpgradeIconElement = new Phaser.GameObjects.Sprite(globalScene, this.instructionRowX, this.instructionRowY, "keyboard", "C.png");
this.candyUpgradeIconElement.setName("sprite-candyUpgrade-icon-element");
this.candyUpgradeIconElement.setScale(0.675);
this.candyUpgradeIconElement.setOrigin(0.0, 0.0);
this.candyUpgradeLabel = addTextObject(this.instructionRowX + this.instructionRowTextOffset, this.instructionRowY, i18next.t("pokedexUiHandler:candyUpgrade"), TextStyle.PARTY, { fontSize: instructionTextSize });
this.candyUpgradeLabel.setName("text-candyUpgrade-label");
// instruction rows that will be pushed into the container dynamically based on need
// creating new sprites since they will be added to the scene later
this.shinyIconElement = new Phaser.GameObjects.Sprite(globalScene, this.instructionRowX, this.instructionRowY, "keyboard", "R.png");
this.shinyIconElement.setName("sprite-shiny-icon-element");
this.shinyIconElement.setScale(0.675);
this.shinyIconElement.setOrigin(0.0, 0.0);
this.shinyLabel = addTextObject(this.instructionRowX + this.instructionRowTextOffset, this.instructionRowY, i18next.t("pokedexUiHandler:cycleShiny"), TextStyle.PARTY, { fontSize: instructionTextSize });
this.shinyLabel.setName("text-shiny-label");
this.formIconElement = new Phaser.GameObjects.Sprite(globalScene, this.instructionRowX, this.instructionRowY, "keyboard", "F.png");
this.formIconElement.setName("sprite-form-icon-element");
this.formIconElement.setScale(0.675);
this.formIconElement.setOrigin(0.0, 0.0);
this.formLabel = addTextObject(this.instructionRowX + this.instructionRowTextOffset, this.instructionRowY, i18next.t("pokedexUiHandler:cycleForm"), TextStyle.PARTY, { fontSize: instructionTextSize });
this.formLabel.setName("text-form-label");
this.genderIconElement = new Phaser.GameObjects.Sprite(globalScene, this.instructionRowX, this.instructionRowY, "keyboard", "G.png");
this.genderIconElement.setName("sprite-gender-icon-element");
this.genderIconElement.setScale(0.675);
this.genderIconElement.setOrigin(0.0, 0.0);
this.genderLabel = addTextObject(this.instructionRowX + this.instructionRowTextOffset, this.instructionRowY, i18next.t("pokedexUiHandler:cycleGender"), TextStyle.PARTY, { fontSize: instructionTextSize });
this.genderLabel.setName("text-gender-label");
this.variantIconElement = new Phaser.GameObjects.Sprite(globalScene, this.instructionRowX, this.instructionRowY, "keyboard", "V.png");
this.variantIconElement.setName("sprite-variant-icon-element");
this.variantIconElement.setScale(0.675);
this.variantIconElement.setOrigin(0.0, 0.0);
this.variantLabel = addTextObject(this.instructionRowX + this.instructionRowTextOffset, this.instructionRowY, i18next.t("pokedexUiHandler:cycleVariant"), TextStyle.PARTY, { fontSize: instructionTextSize });
this.variantLabel.setName("text-variant-label");
this.showBackSpriteIconElement = new Phaser.GameObjects.Sprite(globalScene, 50, 7, "keyboard", "E.png");
this.showBackSpriteIconElement.setName("show-backSprite-icon-element");
this.showBackSpriteIconElement.setScale(0.675);
this.showBackSpriteIconElement.setOrigin(0.0, 0.0);
this.showBackSpriteLabel = addTextObject(60, 7, i18next.t("pokedexUiHandler:showBackSprite"), TextStyle.PARTY, { fontSize: instructionTextSize });
this.showBackSpriteLabel.setName("show-backSprite-label");
this.starterSelectContainer.add(this.showBackSpriteIconElement);
this.starterSelectContainer.add(this.showBackSpriteLabel);
this.hideInstructions();
this.filterInstructionsContainer = globalScene.add.container(50, 5);
this.filterInstructionsContainer.setVisible(true);
this.starterSelectContainer.add(this.filterInstructionsContainer);
this.starterSelectMessageBoxContainer = globalScene.add.container(0, globalScene.game.canvas.height / 6);
this.starterSelectMessageBoxContainer.setVisible(false);
this.starterSelectContainer.add(this.starterSelectMessageBoxContainer);
this.starterSelectMessageBox = addWindow(1, -1, 318, 28);
this.starterSelectMessageBox.setOrigin(0, 1);
this.starterSelectMessageBoxContainer.add(this.starterSelectMessageBox);
this.message = addTextObject(8, 8, "", TextStyle.WINDOW, { maxLines: 2 });
this.message.setOrigin(0, 0);
this.starterSelectMessageBoxContainer.add(this.message);
// arrow icon for the message box
this.initPromptSprite(this.starterSelectMessageBoxContainer);
this.statsContainer = new StatsContainer(6, 16);
globalScene.add.existing(this.statsContainer);
this.statsContainer.setVisible(false);
this.starterSelectContainer.add(this.statsContainer);
// Adding menu container
this.menuContainer = globalScene.add.container(-130, 0);
this.menuContainer.setName("menu");
this.menuContainer.setInteractive(new Phaser.Geom.Rectangle(0, 0, globalScene.game.canvas.width / 6, globalScene.game.canvas.height / 6), Phaser.Geom.Rectangle.Contains);
this.bgmBar = new BgmBar();
this.bgmBar.setup();
ui.bgmBar = this.bgmBar;
this.menuContainer.add(this.bgmBar);
this.menuContainer.setVisible(false);
this.menuOptions = Utils.getEnumKeys(MenuOptions).map(m => parseInt(MenuOptions[m]) as MenuOptions);
this.optionSelectText = addTextObject(0, 0, this.menuOptions.map(o => `${i18next.t(`pokedexUiHandler:${MenuOptions[o]}`)}`).join("\n"), TextStyle.WINDOW, { maxLines: this.menuOptions.length });
this.optionSelectText.setLineSpacing(12);
this.menuDescriptions = [
i18next.t("pokedexUiHandler:showBaseStats"),
i18next.t("pokedexUiHandler:showAbilities"),
i18next.t("pokedexUiHandler:showLevelMoves"),
i18next.t("pokedexUiHandler:showEggMoves"),
i18next.t("pokedexUiHandler:showTmMoves"),
i18next.t("pokedexUiHandler:showBiomes"),
i18next.t("pokedexUiHandler:showNatures"),
i18next.t("pokedexUiHandler:toggleIVs"),
i18next.t("pokedexUiHandler:showEvolutions")
];
this.scale = getTextStyleOptions(TextStyle.WINDOW, globalScene.uiTheme).scale;
this.menuBg = addWindow(
(globalScene.game.canvas.width / 6 - 83),
0,
this.optionSelectText.displayWidth + 19 + 24 * this.scale,
(globalScene.game.canvas.height / 6) - 2
);
this.menuBg.setOrigin(0, 0);
this.optionSelectText.setPositionRelative(this.menuBg, 10 + 24 * this.scale, 6);
this.menuContainer.add(this.menuBg);
this.menuContainer.add(this.optionSelectText);
ui.add(this.menuContainer);
this.starterSelectContainer.add(this.menuContainer);
// adding base stats
this.baseStatsOverlay = new BaseStatsOverlay({ x: 317, y: 0, width:133 });
this.menuContainer.add(this.baseStatsOverlay);
this.menuContainer.bringToTop(this.baseStatsOverlay);
// add the info overlay last to be the top most ui element and prevent the IVs from overlaying this
const overlayScale = 1;
this.moveInfoOverlay = new MoveInfoOverlay({
scale: overlayScale,
top: true,
x: 1,
y: globalScene.game.canvas.height / 6 - MoveInfoOverlay.getHeight(overlayScale) - 29,
});
this.starterSelectContainer.add(this.moveInfoOverlay);
this.infoOverlay = new PokedexInfoOverlay({
scale: overlayScale,
x: 1,
y: globalScene.game.canvas.height / 6 - PokedexInfoOverlay.getHeight(overlayScale) - 29,
});
this.starterSelectContainer.add(this.infoOverlay);
// Filter bar sits above everything, except the message box
this.starterSelectContainer.bringToTop(this.starterSelectMessageBoxContainer);
}
show(args: any[]): boolean {
if (args.length >= 1 && args[0] === "refresh") {
return false;
} else {
this.species = args[0];
this.formIndex = args[1] ?? 0;
this.savedStarterAttributes = args[2] ?? { shiny:false, female:true, variant:0, form:0 };
this.starterSetup();
}
this.moveInfoOverlay.clear(); // clear this when removing a menu; the cancel button doesn't seem to trigger this automatically on controllers
this.infoOverlay.clear();
super.show(args);
this.starterSelectContainer.setVisible(true);
this.getUi().bringToTop(this.starterSelectContainer);
this.starterAttributes = this.initStarterPrefs();
this.menuOptions = Utils.getEnumKeys(MenuOptions).map(m => parseInt(MenuOptions[m]) as MenuOptions);
this.menuContainer.setVisible(true);
this.speciesStarterDexEntry = this.species ? globalScene.gameData.dexData[this.species.speciesId] : null;
this.setSpecies();
this.updateInstructions();
this.setCursor(0);
return true;
}
starterSetup(): void {
this.evolutions = [];
this.prevolutions = [];
this.battleForms = [];
const species = this.species;
const formIndex = this.formIndex ?? 0;
this.starterId = this.getStarterSpeciesId(this.species.speciesId);
const allEvolutions = pokemonEvolutions.hasOwnProperty(species.speciesId) ? pokemonEvolutions[species.speciesId] : [];
if (species.forms.length > 0) {
const form = species.forms[formIndex];
// If this form has a specific set of moves, we get them.
this.levelMoves = (formIndex > 0 && pokemonFormLevelMoves.hasOwnProperty(species.speciesId) && pokemonFormLevelMoves[species.speciesId].hasOwnProperty(formIndex)) ? pokemonFormLevelMoves[species.speciesId][formIndex] : pokemonSpeciesLevelMoves[species.speciesId];
this.ability1 = form.ability1;
this.ability2 = (form.ability2 === form.ability1) ? undefined : form.ability2;
this.abilityHidden = (form.abilityHidden === form.ability1) ? undefined : form.abilityHidden;
this.evolutions = allEvolutions.filter(e => (e.preFormKey === form.formKey || e.preFormKey === null));
this.baseStats = form.baseStats;
this.baseTotal = form.baseTotal;
} else {
this.levelMoves = pokemonSpeciesLevelMoves[species.speciesId];
this.ability1 = species.ability1;
this.ability2 = (species.ability2 === species.ability1) ? undefined : species.ability2;
this.abilityHidden = (species.abilityHidden === species.ability1) ? undefined : species.abilityHidden;
this.evolutions = allEvolutions;
this.baseStats = species.baseStats;
this.baseTotal = species.baseTotal;
}
this.eggMoves = speciesEggMoves[this.starterId] ?? [];
this.hasEggMoves = Array.from({ length: 4 }, (_, em) => (globalScene.gameData.starterData[this.starterId].eggMoves & (1 << em)) !== 0);
const formKey = this.species?.forms.length > 0 ? this.species.forms[this.formIndex].formKey : "";
this.tmMoves = speciesTmMoves[species.speciesId]?.filter(m => Array.isArray(m) ? (m[0] === formKey ? true : false ) : true)
.map(m => Array.isArray(m) ? m[1] : m).sort((a, b) => allMoves[a].name > allMoves[b].name ? 1 : -1) ?? [];
const passiveId = starterPassiveAbilities.hasOwnProperty(species.speciesId) ? species.speciesId :
starterPassiveAbilities.hasOwnProperty(this.starterId) ? this.starterId : pokemonPrevolutions[this.starterId];
const passives = starterPassiveAbilities[passiveId];
this.passive = (this.formIndex in passives) ? passives[formIndex] : passives[0];
const starterData = globalScene.gameData.starterData[this.starterId];
const abilityAttr = starterData.abilityAttr;
this.hasPassive = starterData.passiveAttr > 0;
const hasAbility1 = abilityAttr & AbilityAttr.ABILITY_1;
const hasAbility2 = abilityAttr & AbilityAttr.ABILITY_2;
const hasHiddenAbility = abilityAttr & AbilityAttr.ABILITY_HIDDEN;
this.hasAbilities = [
hasAbility1,
hasAbility2,
hasHiddenAbility
];
const allBiomes = catchableSpecies[species.speciesId] ?? [];
this.preBiomes = this.sanitizeBiomes(
(catchableSpecies[this.starterId] ?? [])
.filter(b => !allBiomes.some(bm => (b.biome === bm.biome && b.tier === bm.tier)) && !(b.biome === Biome.TOWN)),
this.starterId);
this.biomes = this.sanitizeBiomes(allBiomes, species.speciesId);
const allFormChanges = pokemonFormChanges.hasOwnProperty(species.speciesId) ? pokemonFormChanges[species.speciesId] : [];
this.battleForms = allFormChanges.filter(f => (f.preFormKey === this.species.forms[this.formIndex].formKey));
const preSpecies = pokemonPrevolutions.hasOwnProperty(this.species.speciesId) ? allSpecies.find(sp => sp.speciesId === pokemonPrevolutions[this.species.speciesId]) : null;
if (preSpecies) {
const preEvolutions = pokemonEvolutions.hasOwnProperty(preSpecies.speciesId) ? pokemonEvolutions[preSpecies.speciesId] : [];
this.prevolutions = preEvolutions.filter(
e => e.speciesId === species.speciesId && (
(
(e.evoFormKey === "" || e.evoFormKey === null) &&
(
// This takes care of Cosplay Pikachu (Pichu is not shown)
(preSpecies.forms.some(form => form.formKey === species.forms[formIndex]?.formKey)) ||
// This takes care of Gholdengo
(preSpecies.forms.length > 0 && species.forms.length === 0) ||
// This takes care of everything else
(preSpecies.forms.length === 0 && (species.forms.length === 0 || species.forms[formIndex]?.formKey === ""))
)
)
// This takes care of Burmy, Shellos etc
|| e.evoFormKey === species.forms[formIndex]?.formKey
)
);
}
}
// Function to ensure that forms appear in the appropriate biome and tod
sanitizeBiomes(biomes: BiomeTierTod[], speciesId: number): BiomeTierTod[] {
if (speciesId === Species.BURMY || speciesId === Species.WORMADAM) {
return biomes.filter(b => {
const formIndex = (() => {
switch (b.biome) {
case Biome.BEACH:
return 1;
case Biome.SLUM:
return 2;
default:
return 0;
}
})();
return this.formIndex === formIndex;
});
} else if (speciesId === Species.ROTOM) {
return biomes.filter(b => {
const formIndex = (() => {
switch (b.biome) {
case Biome.VOLCANO:
return 1;
case Biome.SEA:
return 2;
case Biome.ICE_CAVE:
return 3;
case Biome.MOUNTAIN:
return 4;
case Biome.TALL_GRASS:
return 5;
default:
return 0;
}
})();
return this.formIndex === formIndex;
});
} else if (speciesId === Species.LYCANROC) {
return biomes.filter(b => {
const formIndex = (() => {
switch (b.tod[0]) {
case TimeOfDay.DAY:
case TimeOfDay.DAWN:
return 0;
case TimeOfDay.DUSK:
return 2;
case TimeOfDay.NIGHT:
return 1;
default:
return 0;
}
})();
return this.formIndex === formIndex;
});
}
return biomes;
}
isCaught(otherSpecies?: PokemonSpecies): bigint {
if (globalScene.dexForDevs) {
return 255n;
}
const species = otherSpecies ? otherSpecies : this.species;
const dexEntry = globalScene.gameData.dexData[species.speciesId];
const starterDexEntry = globalScene.gameData.dexData[this.getStarterSpeciesId(species.speciesId)];
return (dexEntry?.caughtAttr ?? 0n) & (starterDexEntry?.caughtAttr ?? 0n) & species.getFullUnlocksData();
}
/**
* Check whether a given form is caught for a given species.
* All forms that can be reached through a form change during battle are considered caught and show up in the dex as such.
*
* @param otherSpecies The species to check; defaults to current species
* @param otherFormIndex The form index of the form to check; defaults to current form
* @returns StarterAttributes for the species
*/
isFormCaught(otherSpecies?: PokemonSpecies, otherFormIndex?: number | undefined): boolean {
if (globalScene.dexForDevs) {
return true;
}
const species = otherSpecies ? otherSpecies : this.species;
const formIndex = otherFormIndex !== undefined ? otherFormIndex : this.formIndex;
const caughtAttr = this.isCaught(species);
const isFormCaught = (caughtAttr & globalScene.gameData.getFormAttr(formIndex ?? 0)) > 0n;
return isFormCaught;
}
/**
* Get the starter attributes for the given PokemonSpecies, after sanitizing them.
* If somehow a preference is set for a form, variant, gender, ability or nature
* that wasn't actually unlocked or is invalid it will be cleared here
*
* @param species The species to get Starter Preferences for
* @returns StarterAttributes for the species
*/
initStarterPrefs(): StarterAttributes {
const starterAttributes : StarterAttributes | null = this.species ? { ...this.savedStarterAttributes } : null;
const caughtAttr = this.isCaught();
// no preferences or Pokemon wasn't caught, return empty attribute
if (!starterAttributes || !caughtAttr) {
return {};
}
const hasShiny = caughtAttr & DexAttr.SHINY;
const hasNonShiny = caughtAttr & DexAttr.NON_SHINY;
if (!hasShiny || (starterAttributes.shiny === undefined && hasNonShiny)) {
// shiny form wasn't unlocked, purging shiny and variant setting
starterAttributes.shiny = false;
starterAttributes.variant = 0;
} else if (!hasNonShiny || (starterAttributes.shiny === undefined && hasShiny)) {
starterAttributes.shiny = true;
starterAttributes.variant = 0;
}
const unlockedVariants = [
hasShiny && caughtAttr & DexAttr.DEFAULT_VARIANT,
hasShiny && caughtAttr & DexAttr.VARIANT_2,
hasShiny && caughtAttr & DexAttr.VARIANT_3
];
if (starterAttributes.variant === undefined || isNaN(starterAttributes.variant) || starterAttributes.variant < 0) {
starterAttributes.variant = 0;
} else if (!unlockedVariants[starterAttributes.variant]) {
let highestValidIndex = -1;
for (let i = 0; i <= starterAttributes.variant && i < unlockedVariants.length; i++) {
if (unlockedVariants[i] !== 0n) {
highestValidIndex = i;
}
}
// Set to the highest valid index found or default to 0
starterAttributes.variant = highestValidIndex !== -1 ? highestValidIndex : 0;
}
if (starterAttributes.female !== undefined) {
if ((starterAttributes.female && !(caughtAttr & DexAttr.FEMALE)) || (!starterAttributes.female && !(caughtAttr & DexAttr.MALE))) {
starterAttributes.female = !starterAttributes.female;
}
} else {
if (caughtAttr & DexAttr.FEMALE) {
starterAttributes.female = true;
} else if (caughtAttr & DexAttr.MALE) {
starterAttributes.female = false;
}
}
return starterAttributes;
}
showText(text: string, delay?: number, callback?: Function, callbackDelay?: number, prompt?: boolean, promptDelay?: number, moveToTop?: boolean) {
super.showText(text, delay, callback, callbackDelay, prompt, promptDelay);
const singleLine = text?.indexOf("\n") === -1;
this.starterSelectMessageBox.setSize(318, singleLine ? 28 : 42);
if (moveToTop) {
this.starterSelectMessageBox.setOrigin(0, 0);
this.starterSelectMessageBoxContainer.setY(0);
this.message.setY(4);
} else {
this.starterSelectMessageBoxContainer.setY(globalScene.game.canvas.height / 6);
this.starterSelectMessageBox.setOrigin(0, 1);
this.message.setY(singleLine ? -22 : -37);
}
this.starterSelectMessageBoxContainer.setVisible(!!text?.length);
}
/**
* Determines if 'Icon' based upgrade notifications should be shown
* @returns true if upgrade notifications are enabled and set to display an 'Icon'
*/
isUpgradeIconEnabled(): boolean {
return globalScene.candyUpgradeNotification !== 0 && globalScene.candyUpgradeDisplay === 0;
}
/**
* Determines if 'Animation' based upgrade notifications should be shown
* @returns true if upgrade notifications are enabled and set to display an 'Animation'
*/
isUpgradeAnimationEnabled(): boolean {
return globalScene.candyUpgradeNotification !== 0 && globalScene.candyUpgradeDisplay === 1;
}
/**
* If the pokemon is an evolution, find speciesId of its starter.
* @param speciesId the id of the species to check
* @returns the id of the corresponding starter
*/
getStarterSpeciesId(speciesId): number {
if (speciesId === Species.PIKACHU) {
if ([ 0, 1, 8 ].includes(this.formIndex)) {
return Species.PICHU;
} else {
return Species.PIKACHU;
}
}
if (speciesStarterCosts.hasOwnProperty(speciesId)) {
return speciesId;
} else {
return pokemonStarters[speciesId];
}
}
getStarterSpecies(species): PokemonSpecies {
if (speciesStarterCosts.hasOwnProperty(species.speciesId)) {
return species;
} else {
return allSpecies.find(sp => sp.speciesId === pokemonStarters[species.speciesId]) ?? species;
}
}
processInput(button: Button): boolean {
if (this.blockInput) {
return false;
}
const ui = this.getUi();
let success = false;
let error = false;
const isCaught = this.isCaught();
const isFormCaught = this.isFormCaught();
if (this.blockInputOverlay) {
if (button === Button.CANCEL || button === Button.ACTION) {
this.blockInputOverlay = false;
this.baseStatsOverlay.clear();
ui.showText("");
return true;
} else if (button === Button.UP || button === Button.DOWN) {
this.blockInputOverlay = false;
this.baseStatsOverlay.clear();
ui.showText("");
} else {
return false;
}
}
if (button === Button.SUBMIT) {
success = true;
} else if (button === Button.CANCEL) {
if (this.statsMode) {
this.toggleStatsMode(false);
success = true;
} else {
this.getUi().revertMode();
success = true;
}
} else {
const starterData = globalScene.gameData.starterData[this.starterId];
// prepare persistent starter data to store changes
const starterAttributes = this.starterAttributes;
if (button === Button.ACTION) {
switch (this.cursor) {
case MenuOptions.BASE_STATS:
if (!isCaught || !isFormCaught) {
error = true;
} else {
this.blockInput = true;
ui.setMode(Mode.POKEDEX_PAGE, "refresh").then(() => {
ui.showText(i18next.t("pokedexUiHandler:showBaseStats"), null, () => {
this.baseStatsOverlay.show(this.baseStats, this.baseTotal);
this.blockInput = false;
this.blockInputOverlay = true;
return true;
});
success = true;
});
}
break;
case MenuOptions.LEVEL_MOVES:
if (!isCaught || !isFormCaught) {
error = true;
} else {
this.blockInput = true;
ui.setMode(Mode.POKEDEX_PAGE, "refresh").then(() => {
ui.showText(i18next.t("pokedexUiHandler:showLevelMoves"), null, () => {
this.moveInfoOverlay.show(allMoves[this.levelMoves[0][1]]);
ui.setModeWithoutClear(Mode.OPTION_SELECT, {
options: this.levelMoves.map(m => {
const option: OptionSelectItem = {
label: String(m[0]).padEnd(4, " ") + allMoves[m[1]].name,
handler: () => {
return false;
},
onHover: () => {
this.moveInfoOverlay.show(allMoves[m[1]]);
},
};
return option;
}).concat({
label: i18next.t("menu:cancel"),
handler: () => {
this.moveInfoOverlay.clear();
this.clearText();
ui.setMode(Mode.POKEDEX_PAGE, "refresh");
return true;
},
onHover: () => {
this.moveInfoOverlay.clear();
},
}),
supportHover: true,
maxOptions: 8,
yOffset: 19
});
this.blockInput = false;
});
});
success = true;
}
break;
case MenuOptions.EGG_MOVES:
if (!isCaught || !isFormCaught) {
error = true;
} else {
this.blockInput = true;
ui.setMode(Mode.POKEDEX_PAGE, "refresh").then(() => {
if (this.eggMoves.length === 0) {
ui.showText(i18next.t("pokedexUiHandler:noEggMoves"));
this.blockInput = false;
return true;
}
ui.showText(i18next.t("pokedexUiHandler:showEggMoves"), null, () => {
this.moveInfoOverlay.show(allMoves[this.eggMoves[0]]);
ui.setModeWithoutClear(Mode.OPTION_SELECT, {
options: [
{
label: i18next.t("pokedexUiHandler:common"),
skip: true,
style: TextStyle.MONEY_WINDOW,
handler: () => false, // Non-selectable, but handler is required
onHover: () => this.moveInfoOverlay.clear() // No hover behavior for titles
},
...this.eggMoves.slice(0, 3).map((m, i) => ({
label: allMoves[m].name,
style: this.hasEggMoves[i] ? TextStyle.SETTINGS_VALUE : TextStyle.SHADOW_TEXT,
handler: () => false,
onHover: () => this.moveInfoOverlay.show(allMoves[m])
})),
{
label: i18next.t("pokedexUiHandler:rare"),
skip: true,
style: TextStyle.MONEY_WINDOW,
handler: () => false,
onHover: () => this.moveInfoOverlay.clear()
},
{
label: allMoves[this.eggMoves[3]].name,
style: this.hasEggMoves[3] ? TextStyle.SETTINGS_VALUE : TextStyle.SHADOW_TEXT,
handler: () => false,
onHover: () => this.moveInfoOverlay.show(allMoves[this.eggMoves[3]])
},
{
label: i18next.t("menu:cancel"),
handler: () => {
this.moveInfoOverlay.clear();
this.clearText();
ui.setMode(Mode.POKEDEX_PAGE, "refresh");
return true;
},
onHover: () => this.moveInfoOverlay.clear()
}
],
supportHover: true,
maxOptions: 8,
yOffset: 19
});
this.blockInput = false;
});
});
success = true;
}
break;
case MenuOptions.TM_MOVES:
if (!isCaught || !isFormCaught) {
error = true;
} else if (this.tmMoves.length < 1) {
ui.showText(i18next.t("pokedexUiHandler:noTmMoves"));
error = true;
} else {
this.blockInput = true;
ui.setMode(Mode.POKEDEX_PAGE, "refresh").then(() => {
ui.showText(i18next.t("pokedexUiHandler:showTmMoves"), null, () => {
this.moveInfoOverlay.show(allMoves[this.tmMoves[0]]);
ui.setModeWithoutClear(Mode.OPTION_SELECT, {
options: this.tmMoves.map(m => {
const option: OptionSelectItem = {
label: allMoves[m].name,
handler: () => {
return false;
},
onHover: () => {
this.moveInfoOverlay.show(allMoves[m]);
},
};
return option;
}).concat({
label: i18next.t("menu:cancel"),
handler: () => {
this.moveInfoOverlay.clear();
this.clearText();
ui.setMode(Mode.POKEDEX_PAGE, "refresh");
return true;
},
onHover: () => {
this.moveInfoOverlay.clear();
},
}),
supportHover: true,
maxOptions: 8,
yOffset: 19
});
this.blockInput = false;
});
});
success = true;
}
break;
case MenuOptions.ABILITIES:
if (!isCaught || !isFormCaught) {
error = true;
} else {
this.blockInput = true;
ui.setMode(Mode.POKEDEX_PAGE, "refresh").then(() => {
ui.showText(i18next.t("pokedexUiHandler:showAbilities"), null, () => {
this.infoOverlay.show(allAbilities[this.ability1].description);
const options: any[] = [];
if (this.ability1) {
options.push({
label: allAbilities[this.ability1].name,
style: this.hasAbilities[0] > 0 ? TextStyle.SETTINGS_VALUE : TextStyle.SHADOW_TEXT,
handler: () => false,
onHover: () => this.infoOverlay.show(allAbilities[this.ability1].description)
});
}
if (this.ability2) {
const ability = allAbilities[this.ability2];
options.push({
label: ability?.name,
style: this.hasAbilities[1] > 0 ? TextStyle.SETTINGS_VALUE : TextStyle.SHADOW_TEXT,
handler: () => false,
onHover: () => this.infoOverlay.show(ability?.description)
});
}
if (this.abilityHidden) {
options.push({
label: i18next.t("pokedexUiHandler:hidden"),
skip: true,
style: TextStyle.MONEY_WINDOW,
handler: () => false,
onHover: () => this.infoOverlay.clear()
});
const ability = allAbilities[this.abilityHidden];
options.push({
label: allAbilities[this.abilityHidden].name,
style: this.hasAbilities[2] > 0 ? TextStyle.SETTINGS_VALUE : TextStyle.SHADOW_TEXT,
handler: () => false,
onHover: () => this.infoOverlay.show(ability?.description)
});
}
if (this.passive) {
options.push({
label: i18next.t("pokedexUiHandler:passive"),
skip: true,
style: TextStyle.MONEY_WINDOW,
handler: () => false,
onHover: () => this.infoOverlay.clear()
});
options.push({
label: allAbilities[this.passive].name,
style: this.hasPassive ? TextStyle.SETTINGS_VALUE : TextStyle.SHADOW_TEXT,
handler: () => false,
onHover: () => this.infoOverlay.show(allAbilities[this.passive].description)
});
}
options.push({
label: i18next.t("menu:cancel"),
handler: () => {
this.infoOverlay.clear();
this.clearText();
ui.setMode(Mode.POKEDEX_PAGE, "refresh");
return true;
},
onHover: () => this.infoOverlay.clear()
});
ui.setModeWithoutClear(Mode.OPTION_SELECT, {
options: options,
supportHover: true,
maxOptions: 8,
yOffset: 19
});
this.blockInput = false;
});
});
success = true;
}
break;
case MenuOptions.BIOMES:
if (!(isCaught || this.speciesStarterDexEntry?.seenAttr)) {
error = true;
} else {
this.blockInput = true;
ui.setMode(Mode.POKEDEX_PAGE, "refresh").then(() => {
if ((!this.biomes || this.biomes?.length === 0) &&
(!this.preBiomes || this.preBiomes?.length === 0)) {
ui.showText(i18next.t("pokedexUiHandler:noBiomes"));
ui.playError();
this.blockInput = false;
return true;
}
const options: any[] = [];
ui.showText(i18next.t("pokedexUiHandler:showBiomes"), null, () => {
this.biomes.map(b => {
options.push({
label: i18next.t(`biome:${Biome[b.biome].toUpperCase()}`) + " - " +
i18next.t(`biome:${BiomePoolTier[b.tier].toUpperCase()}`) +
( b.tod.length === 1 && b.tod[0] === -1 ? "" : " (" + b.tod.map(tod => i18next.t(`biome:${TimeOfDay[tod].toUpperCase()}`)).join(", ") + ")"),
handler: () => false
});
});
if (this.preBiomes.length > 0) {
options.push({
label: i18next.t("pokedexUiHandler:preBiomes"),
skip: true,
handler: () => false
});
this.preBiomes.map(b => {
options.push({
label: i18next.t(`biome:${Biome[b.biome].toUpperCase()}`) + " - " +
i18next.t(`biome:${BiomePoolTier[b.tier].toUpperCase()}`) +
( b.tod.length === 1 && b.tod[0] === -1 ? "" : " (" + b.tod.map(tod => i18next.t(`biome:${TimeOfDay[tod].toUpperCase()}`)).join(", ") + ")"),
handler: () => false
});
});
}
options.push({
label: i18next.t("menu:cancel"),
handler: () => {
this.moveInfoOverlay.clear();
this.clearText();
ui.setMode(Mode.POKEDEX_PAGE, "refresh");
return true;
},
onHover: () => this.moveInfoOverlay.clear()
});
ui.setModeWithoutClear(Mode.OPTION_SELECT, {
options: options,
supportHover: true,
maxOptions: 8,
yOffset: 19
});
this.blockInput = false;
});
});
success = true;
}
break;
case MenuOptions.EVOLUTIONS:
if (!isCaught || !isFormCaught) {
error = true;
} else {
this.blockInput = true;
ui.setMode(Mode.POKEDEX_PAGE, "refresh").then(() => {
const options: any[] = [];
if ((!this.prevolutions || this.prevolutions?.length === 0) &&
(!this.evolutions || this.evolutions?.length === 0) &&
(!this.battleForms || this.battleForms?.length === 0)) {
ui.showText(i18next.t("pokedexUiHandler:noEvolutions"));
ui.playError();
this.blockInput = false;
return true;
}
ui.showText(i18next.t("pokedexUiHandler:showEvolutions"), null, () => {
if (this.prevolutions?.length > 0) {
options.push({
label: i18next.t("pokedexUiHandler:prevolutions"),
style: TextStyle.MONEY_WINDOW,
skip: true,
handler: () => false
});
this.prevolutions.map(pre => {
const preSpecies = allSpecies.find(species => species.speciesId === pokemonPrevolutions[this.species.speciesId]);
const preFormIndex: number = preSpecies?.forms.find(f => f.formKey === pre.preFormKey)?.formIndex ?? 0;
const conditionText: string = pre.description;
options.push({
label: pre.preFormKey ?
(preSpecies ?? this.species).getFormNameToDisplay(preFormIndex, true) :
(preSpecies ?? this.species).getExpandedSpeciesName(),
handler: () => {
const newSpecies = allSpecies.find(species => species.speciesId === pokemonPrevolutions[pre.speciesId]);
// Attempts to find the formIndex of the prevolved species
const newFormKey = pre.preFormKey ? pre.preFormKey : (this.species.forms.length > 0 ? this.species.forms[this.formIndex].formKey : "");
const matchingForm = newSpecies?.forms.find(form => form.formKey === newFormKey);
const newFormIndex = matchingForm ? matchingForm.formIndex : 0;
this.starterAttributes.form = newFormIndex;
this.savedStarterAttributes.form = newFormIndex;
this.moveInfoOverlay.clear();
this.clearText();
ui.setMode(Mode.POKEDEX_PAGE, newSpecies, newFormIndex, this.savedStarterAttributes);
return true;
},
onHover: () => this.showText(conditionText)
});
});
}
if (this.evolutions.length > 0) {
options.push({
label: i18next.t("pokedexUiHandler:evolutions"),
style: TextStyle.MONEY_WINDOW,
skip: true,
handler: () => false
});
this.evolutions.map(evo => {
const evoSpecies = allSpecies.find(species => species.speciesId === evo.speciesId);
const isCaughtEvo = this.isCaught(evoSpecies) ? true : false;
// Attempts to find the formIndex of the evolved species
const newFormKey = evo.evoFormKey ? evo.evoFormKey : (this.species.forms.length > 0 ? this.species.forms[this.formIndex].formKey : "");
const matchingForm = evoSpecies?.forms.find(form => form.formKey === newFormKey);
const newFormIndex = matchingForm ? matchingForm.formIndex : 0;
const isFormCaughtEvo = this.isFormCaught(evoSpecies, newFormIndex);
const conditionText: string = evo.description;
options.push({
label: evo.evoFormKey ?
(evoSpecies ?? this.species).getFormNameToDisplay(newFormIndex, true) :
(evoSpecies ?? this.species).getExpandedSpeciesName(),
style: isCaughtEvo && isFormCaughtEvo ? TextStyle.WINDOW : TextStyle.SHADOW_TEXT,
handler: () => {
this.starterAttributes.form = newFormIndex;
this.savedStarterAttributes.form = newFormIndex;
this.moveInfoOverlay.clear();
this.clearText();
ui.setMode(Mode.POKEDEX_PAGE, evoSpecies, newFormIndex, this.savedStarterAttributes);
return true;
},
onHover: () => this.showText(conditionText)
});
});
}
if (this.battleForms.length > 0) {
options.push({
label: i18next.t("pokedexUiHandler:forms"),
style: TextStyle.MONEY_WINDOW,
skip: true,
handler: () => false
});
this.battleForms.map(bf => {
const matchingForm = this.species?.forms.find(form => form.formKey === bf.formKey);
const newFormIndex = matchingForm ? matchingForm.formIndex : 0;
let conditionText:string = "";
if (bf.trigger) {
conditionText = bf.trigger.description;
} else {
conditionText = "";
}
let label: string = this.species.getFormNameToDisplay(newFormIndex);
if (label === "") {
label = this.species.name;
}
const isFormCaught = this.isFormCaught(this.species, newFormIndex);
if (conditionText) {
options.push({
label: label,
style: isFormCaught ? TextStyle.WINDOW : TextStyle.SHADOW_TEXT,
handler: () => {
const newSpecies = this.species;
const newFormIndex = this.species.forms.find(f => f.formKey === bf.formKey)?.formIndex;
this.starterAttributes.form = newFormIndex;
this.savedStarterAttributes.form = newFormIndex;
this.moveInfoOverlay.clear();
this.clearText();
ui.setMode(Mode.POKEDEX_PAGE, newSpecies, newFormIndex, this.savedStarterAttributes);
return true;
},
onHover: () => this.showText(conditionText)
});
}
});
}
options.push({
label: i18next.t("menu:cancel"),
handler: () => {
this.moveInfoOverlay.clear();
this.clearText();
ui.setMode(Mode.POKEDEX_PAGE, "refresh");
return true;
},
onHover: () => this.moveInfoOverlay.clear()
});
ui.setModeWithoutClear(Mode.OPTION_SELECT, {
options: options,
supportHover: true,
maxOptions: 8,
yOffset: 19
});
this.blockInput = false;
});
});
success = true;
}
break;
case MenuOptions.TOGGLE_IVS:
if (!isCaught || !isFormCaught) {
error = true;
} else {
this.toggleStatsMode();
ui.setMode(Mode.POKEDEX_PAGE, "refresh");
success = true;
}
break;
case MenuOptions.NATURES:
if (!isCaught || !isFormCaught) {
error = true;
} else {
this.blockInput = true;
ui.setMode(Mode.POKEDEX_PAGE, "refresh").then(() => {
ui.showText(i18next.t("pokedexUiHandler:showNature"), null, () => {
const natures = globalScene.gameData.getNaturesForAttr(this.speciesStarterDexEntry?.natureAttr);
ui.setModeWithoutClear(Mode.OPTION_SELECT, {
options: natures.map((n: Nature, i: number) => {
const option: OptionSelectItem = {
label: getNatureName(n, true, true, true, globalScene.uiTheme),
handler: () => {
return false;
}
};
return option;
}).concat({
label: i18next.t("menu:cancel"),
handler: () => {
this.clearText();
ui.setMode(Mode.POKEDEX_PAGE, "refresh");
this.blockInput = false;
return true;
}
}),
maxOptions: 8,
yOffset: 19
});
});
});
success = true;
}
break;
}
} else {
const props = globalScene.gameData.getSpeciesDexAttrProps(this.species, this.getCurrentDexProps(this.species.speciesId));
switch (button) {
case Button.CYCLE_SHINY:
if (this.canCycleShiny) {
if (!starterAttributes.shiny) {
// Change to shiny, we need to get the proper default variant
const newVariant = starterAttributes.variant ? starterAttributes.variant as Variant : 0;
this.setSpeciesDetails(this.species, { shiny: true, variant: newVariant });
globalScene.playSound("se/sparkle");
// Set the variant label to the shiny tint
const tint = getVariantTint(newVariant);
this.pokemonShinyIcon.setFrame(getVariantIcon(newVariant));
this.pokemonShinyIcon.setTint(tint);
this.pokemonShinyIcon.setVisible(true);
starterAttributes.shiny = true;
this.savedStarterAttributes.shiny = starterAttributes.shiny;
} else {
let newVariant = props.variant;
do {
newVariant = (newVariant + 1) % 3;
if (newVariant === 0) {
if (this.isCaught() & DexAttr.DEFAULT_VARIANT) {
break;
}
} else if (newVariant === 1) {
if (this.isCaught() & DexAttr.VARIANT_2) {
break;
}
} else {
if (this.isCaught() & DexAttr.VARIANT_3) {
break;
}
}
} while (newVariant !== props.variant);
starterAttributes.variant = newVariant; // store the selected variant
this.savedStarterAttributes.variant = starterAttributes.variant;
if (newVariant > props.variant) {
this.setSpeciesDetails(this.species, { variant: newVariant as Variant });
// Cycle tint based on current sprite tint
const tint = getVariantTint(newVariant as Variant);
this.pokemonShinyIcon.setFrame(getVariantIcon(newVariant as Variant));
this.pokemonShinyIcon.setTint(tint);
success = true;
} else {
this.setSpeciesDetails(this.species, { shiny: false, variant: 0 });
this.pokemonShinyIcon.setVisible(false);
success = true;
starterAttributes.shiny = false;
this.savedStarterAttributes.shiny = starterAttributes.shiny;
}
}
}
break;
case Button.CYCLE_FORM:
if (this.canCycleForm) {
const formCount = this.species.forms.length;
let newFormIndex = this.formIndex;
do {
newFormIndex = (newFormIndex + 1) % formCount;
if (this.species.forms[newFormIndex].isStarterSelectable || globalScene.dexForDevs) { // TODO: are those bangs correct?
break;
}
} while (newFormIndex !== props.formIndex || this.species.forms[newFormIndex].isUnobtainable);
starterAttributes.form = newFormIndex; // store the selected form
this.savedStarterAttributes.form = starterAttributes.form;
this.formIndex = newFormIndex;
this.starterSetup();
this.setSpeciesDetails(this.species, { formIndex: newFormIndex });
success = this.setCursor(this.cursor);
}
break;
case Button.CYCLE_GENDER:
if (this.canCycleGender) {
starterAttributes.female = !props.female;
this.savedStarterAttributes.female = starterAttributes.female;
this.setSpeciesDetails(this.species, { female: !props.female });
success = true;
}
break;
case Button.STATS:
if (!isCaught || !isFormCaught) {
error = true;
} else {
const ui = this.getUi();
ui.showText("");
const options: any[] = []; // TODO: add proper type
const passiveAttr = starterData.passiveAttr;
const candyCount = starterData.candyCount;
if (!(passiveAttr & PassiveAttr.UNLOCKED)) {
const passiveCost = getPassiveCandyCount(speciesStarterCosts[this.starterId]);
options.push({
label: `x${passiveCost} ${i18next.t("pokedexUiHandler:unlockPassive")} (${allAbilities[this.passive].name})`,
handler: () => {
if (Overrides.FREE_CANDY_UPGRADE_OVERRIDE || candyCount >= passiveCost) {
starterData.passiveAttr |= PassiveAttr.UNLOCKED | PassiveAttr.ENABLED;
if (!Overrides.FREE_CANDY_UPGRADE_OVERRIDE) {
starterData.candyCount -= passiveCost;
}
this.pokemonCandyCountText.setText(`x${starterData.candyCount}`);
globalScene.gameData.saveSystem().then(success => {
if (!success) {
return globalScene.reset(true);
}
});
this.setSpeciesDetails(this.species);
globalScene.playSound("se/buy");
ui.setMode(Mode.POKEDEX_PAGE, "refresh");
return true;
}
return false;
},
style: this.isPassiveAvailable() ? TextStyle.WINDOW : TextStyle.SHADOW_TEXT,
item: "candy",
itemArgs: this.isPassiveAvailable() ? starterColors[this.starterId] : [ "808080", "808080" ]
});
}
// Reduce cost option
const valueReduction = starterData.valueReduction;
if (valueReduction < valueReductionMax) {
const reductionCost = getValueReductionCandyCounts(speciesStarterCosts[this.starterId])[valueReduction];
options.push({
label: `x${reductionCost} ${i18next.t("pokedexUiHandler:reduceCost")}`,
handler: () => {
if (Overrides.FREE_CANDY_UPGRADE_OVERRIDE || candyCount >= reductionCost) {
starterData.valueReduction++;
if (!Overrides.FREE_CANDY_UPGRADE_OVERRIDE) {
starterData.candyCount -= reductionCost;
}
this.pokemonCandyCountText.setText(`x${starterData.candyCount}`);
globalScene.gameData.saveSystem().then(success => {
if (!success) {
return globalScene.reset(true);
}
});
ui.setMode(Mode.POKEDEX_PAGE, "refresh");
globalScene.playSound("se/buy");
return true;
}
return false;
},
style: this.isValueReductionAvailable() ? TextStyle.WINDOW : TextStyle.SHADOW_TEXT,
item: "candy",
itemArgs: this.isValueReductionAvailable() ? starterColors[this.starterId] : [ "808080", "808080" ]
});
}
// Same species egg menu option.
const sameSpeciesEggCost = getSameSpeciesEggCandyCounts(speciesStarterCosts[this.starterId]);
options.push({
label: `x${sameSpeciesEggCost} ${i18next.t("pokedexUiHandler:sameSpeciesEgg")}`,
handler: () => {
if (Overrides.FREE_CANDY_UPGRADE_OVERRIDE || candyCount >= sameSpeciesEggCost) {
if (globalScene.gameData.eggs.length >= 99 && !Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) {
// Egg list full, show error message at the top of the screen and abort
this.showText(i18next.t("egg:tooManyEggs"), undefined, () => this.showText("", 0, () => this.tutorialActive = false), 2000, false, undefined, true);
return false;
}
if (!Overrides.FREE_CANDY_UPGRADE_OVERRIDE) {
starterData.candyCount -= sameSpeciesEggCost;
}
this.pokemonCandyCountText.setText(`x${starterData.candyCount}`);
const egg = new Egg({ scene: globalScene, species: this.starterId, sourceType: EggSourceType.SAME_SPECIES_EGG });
egg.addEggToGameData();
globalScene.gameData.saveSystem().then(success => {
if (!success) {
return globalScene.reset(true);
}
});
ui.setMode(Mode.POKEDEX_PAGE, "refresh");
globalScene.playSound("se/buy");
return true;
}
return false;
},
style: this.isSameSpeciesEggAvailable() ? TextStyle.WINDOW : TextStyle.SHADOW_TEXT,
item: "candy",
itemArgs: this.isSameSpeciesEggAvailable() ? starterColors[this.starterId] : [ "808080", "808080" ]
});
options.push({
label: i18next.t("menu:cancel"),
handler: () => {
ui.setMode(Mode.POKEDEX_PAGE, "refresh");
return true;
}
});
ui.setModeWithoutClear(Mode.OPTION_SELECT, {
options: options,
yOffset: 47
});
success = true;
}
break;
case Button.CYCLE_ABILITY:
this.showBackSprite = !this.showBackSprite;
if (this.showBackSprite) {
this.showBackSpriteLabel.setText(i18next.t("pokedexUiHandler:showFrontSprite"));
} else {
this.showBackSpriteLabel.setText(i18next.t("pokedexUiHandler:showBackSprite"));
}
this.setSpeciesDetails(this.species, {}, true);
success = true;
break;
case Button.UP:
if (this.cursor) {
success = this.setCursor(this.cursor - 1);
} else {
success = this.setCursor(this.menuOptions.length - 1);
}
break;
case Button.DOWN:
if (this.cursor + 1 < this.menuOptions.length) {
success = this.setCursor(this.cursor + 1);
} else {
success = this.setCursor(0);
}
break;
case Button.LEFT:
this.blockInput = true;
ui.setModeWithoutClear(Mode.OPTION_SELECT).then(() => {
const index = allSpecies.findIndex(species => species.speciesId === this.species.speciesId);
const newIndex = index <= 0 ? allSpecies.length - 1 : index - 1;
const newSpecies = allSpecies[newIndex];
const matchingForm = newSpecies?.forms.find(form => form.formKey === this.species?.forms[this.formIndex]?.formKey);
const newFormIndex = matchingForm ? matchingForm.formIndex : 0;
this.starterAttributes.form = newFormIndex;
this.savedStarterAttributes.form = newFormIndex;
this.moveInfoOverlay.clear();
this.clearText();
ui.setModeForceTransition(Mode.POKEDEX_PAGE, newSpecies, newFormIndex, this.savedStarterAttributes);
});
this.blockInput = false;
break;
case Button.RIGHT:
ui.setModeWithoutClear(Mode.OPTION_SELECT).then(() => {
const index = allSpecies.findIndex(species => species.speciesId === this.species.speciesId);
const newIndex = index >= allSpecies.length - 1 ? 0 : index + 1;
const newSpecies = allSpecies[newIndex];
const matchingForm = newSpecies?.forms.find(form => form.formKey === this.species?.forms[this.formIndex]?.formKey);
const newFormIndex = matchingForm ? matchingForm.formIndex : 0;
this.starterAttributes.form = newFormIndex;
this.savedStarterAttributes.form = newFormIndex;
this.moveInfoOverlay.clear();
this.clearText();
ui.setModeForceTransition(Mode.POKEDEX_PAGE, newSpecies, newFormIndex, this.savedStarterAttributes);
});
break;
}
}
}
if (success) {
ui.playSelect();
} else if (error) {
ui.playError();
}
return success || error;
}
updateButtonIcon(iconSetting, gamepadType, iconElement, controlLabel): void {
let iconPath;
// touch controls cannot be rebound as is, and are just emulating a keyboard event.
// Additionally, since keyboard controls can be rebound (and will be displayed when they are), we need to have special handling for the touch controls
if (gamepadType === "touch") {
gamepadType = "keyboard";
switch (iconSetting) {
case SettingKeyboard.Button_Cycle_Shiny:
iconPath = "R.png";
break;
case SettingKeyboard.Button_Cycle_Form:
iconPath = "F.png";
break;
case SettingKeyboard.Button_Cycle_Gender:
iconPath = "G.png";
break;
case SettingKeyboard.Button_Cycle_Ability:
iconPath = "E.png";
break;
default:
break;
}
} else {
iconPath = globalScene.inputController?.getIconForLatestInputRecorded(iconSetting);
}
iconElement.setTexture(gamepadType, iconPath);
iconElement.setPosition(this.instructionRowX, this.instructionRowY);
controlLabel.setPosition(this.instructionRowX + this.instructionRowTextOffset, this.instructionRowY);
iconElement.setVisible(true);
controlLabel.setVisible(true);
this.instructionsContainer.add([ iconElement, controlLabel ]);
this.instructionRowY += 8;
if (this.instructionRowY >= 24) {
this.instructionRowY = 8;
this.instructionRowX += 50;
}
}
updateInstructions(): void {
this.instructionRowX = 0;
this.instructionRowY = 0;
this.filterInstructionRowX = 0;
this.filterInstructionRowY = 0;
this.hideInstructions();
this.instructionsContainer.removeAll();
this.filterInstructionsContainer.removeAll();
let gamepadType;
if (globalScene.inputMethod === "gamepad") {
gamepadType = globalScene.inputController.getConfig(globalScene.inputController.selectedDevice[Device.GAMEPAD]).padType;
} else {
gamepadType = globalScene.inputMethod;
}
if (!gamepadType) {
return;
}
const isFormCaught = this.isFormCaught();
if (this.isCaught()) {
if (isFormCaught) {
this.updateButtonIcon(SettingKeyboard.Button_Stats, gamepadType, this.candyUpgradeIconElement, this.candyUpgradeLabel);
if (this.canCycleShiny) {
this.updateButtonIcon(SettingKeyboard.Button_Cycle_Shiny, gamepadType, this.shinyIconElement, this.shinyLabel);
}
if (this.canCycleGender) {
this.updateButtonIcon(SettingKeyboard.Button_Cycle_Gender, gamepadType, this.genderIconElement, this.genderLabel);
}
} else {
// Making space for "Uncaught" text
this.instructionRowY += 8;
}
if (this.canCycleForm) {
this.updateButtonIcon(SettingKeyboard.Button_Cycle_Form, gamepadType, this.formIconElement, this.formLabel);
}
}
}
getValueLimit(): number {
const valueLimit = new NumberHolder(0);
switch (globalScene.gameMode.modeId) {
case GameModes.ENDLESS:
case GameModes.SPLICED_ENDLESS:
valueLimit.value = 15;
break;
default:
valueLimit.value = 10;
}
Challenge.applyChallenges(globalScene.gameMode, Challenge.ChallengeType.STARTER_POINTS, valueLimit);
return valueLimit.value;
}
setCursor(cursor: number): boolean {
const ret = super.setCursor(cursor);
if (!this.cursorObj) {
this.cursorObj = globalScene.add.image(0, 0, "cursor");
this.cursorObj.setOrigin(0, 0);
this.menuContainer.add(this.cursorObj);
}
this.cursorObj.setScale(this.scale * 6);
this.cursorObj.setPositionRelative(this.menuBg, 7, 6 + (18 + this.cursor * 96) * this.scale);
const ui = this.getUi();
const isFormCaught = this.isFormCaught();
if ((this.isCaught() && isFormCaught) || (this.speciesStarterDexEntry?.seenAttr && cursor === 5)) {
ui.showText(this.menuDescriptions[cursor]);
} else {
ui.showText("");
}
return ret;
}
getFriendship(speciesId: number) {
let currentFriendship = globalScene.gameData.starterData[this.starterId].friendship;
if (!currentFriendship || currentFriendship === undefined) {
currentFriendship = 0;
}
const friendshipCap = getStarterValueFriendshipCap(speciesStarterCosts[this.starterId]);
return { currentFriendship, friendshipCap };
}
/**
* Determines if a passive upgrade is available for the current species
* @returns true if the user has enough candies and a passive has not been unlocked already
*/
isPassiveAvailable(): boolean {
// Get this species ID's starter data
const starterData = globalScene.gameData.starterData[this.starterId];
return starterData.candyCount >= getPassiveCandyCount(speciesStarterCosts[this.starterId])
&& !(starterData.passiveAttr & PassiveAttr.UNLOCKED);
}
/**
* Determines if a value reduction upgrade is available for the current species
* @returns true if the user has enough candies and all value reductions have not been unlocked already
*/
isValueReductionAvailable(): boolean {
// Get this species ID's starter data
const starterData = globalScene.gameData.starterData[this.starterId];
return starterData.candyCount >= getValueReductionCandyCounts(speciesStarterCosts[this.starterId])[starterData.valueReduction]
&& starterData.valueReduction < valueReductionMax;
}
/**
* Determines if an same species egg can be bought for the current species
* @returns true if the user has enough candies
*/
isSameSpeciesEggAvailable(): boolean {
// Get this species ID's starter data
const starterData = globalScene.gameData.starterData[this.starterId];
return starterData.candyCount >= getSameSpeciesEggCandyCounts(speciesStarterCosts[this.starterId]);
}
setSpecies() {
const species = this.species;
const starterAttributes : StarterAttributes | null = species ? { ...this.starterAttributes } : null;
if (!species && globalScene.ui.getTooltip().visible) {
globalScene.ui.hideTooltip();
}
if (this.statsMode) {
if (this.isCaught()) {
this.statsContainer.setVisible(true);
this.showStats();
} else {
this.statsContainer.setVisible(false);
//@ts-ignore
this.statsContainer.updateIvs(null); // TODO: resolve ts-ignore. what. how? huh?
}
}
if (species && (this.speciesStarterDexEntry?.seenAttr || this.isCaught())) {
this.pokemonNumberText.setText(padInt(species.speciesId, 4));
if (this.isCaught()) {
const defaultDexAttr = this.getCurrentDexProps(species.speciesId);
// Set default attributes if for some reason starterAttributes does not exist or attributes missing
const props: StarterAttributes = globalScene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr);
if (starterAttributes?.variant && !isNaN(starterAttributes.variant)) {
if (props.shiny) {
props.variant = starterAttributes.variant as Variant;
}
}
props.form = starterAttributes?.form ?? props.form;
props.female = starterAttributes?.female ?? props.female;
this.setSpeciesDetails(species, {
shiny: props.shiny,
formIndex: props.form,
female: props.female,
variant: props.variant ?? 0,
});
} else {
this.pokemonGrowthRateText.setText("");
this.pokemonGrowthRateLabelText.setVisible(false);
this.type1Icon.setVisible(true);
this.type2Icon.setVisible(true);
this.pokemonLuckLabelText.setVisible(false);
this.pokemonLuckText.setVisible(false);
this.pokemonShinyIcon.setVisible(false);
this.pokemonUncaughtText.setVisible(true);
this.pokemonCaughtHatchedContainer.setVisible(true);
this.pokemonCandyContainer.setVisible(false);
this.pokemonFormText.setVisible(false);
const defaultDexAttr = globalScene.gameData.getSpeciesDefaultDexAttr(species, true, true);
const props = globalScene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr);
this.setSpeciesDetails(species, {
shiny: props.shiny,
formIndex: props.formIndex,
female: props.female,
variant: props.variant,
});
this.pokemonSprite.setTint(0x808080);
}
} else {
this.pokemonNumberText.setText(species ? padInt(species.speciesId, 4) : "");
this.pokemonNameText.setText(species ? "???" : "");
this.pokemonGrowthRateText.setText("");
this.pokemonGrowthRateLabelText.setVisible(false);
this.type1Icon.setVisible(false);
this.type2Icon.setVisible(false);
this.pokemonLuckLabelText.setVisible(false);
this.pokemonLuckText.setVisible(false);
this.pokemonShinyIcon.setVisible(false);
this.pokemonUncaughtText.setVisible(!!species);
this.pokemonCaughtHatchedContainer.setVisible(false);
this.pokemonCandyContainer.setVisible(false);
this.pokemonFormText.setVisible(false);
this.setSpeciesDetails(species!, { // TODO: is this bang correct?
shiny: false,
formIndex: 0,
female: false,
variant: 0,
});
this.pokemonSprite.setTint(0x000000);
}
}
setSpeciesDetails(species: PokemonSpecies, options: SpeciesDetails = {}, forceUpdate?: boolean): void {
let { shiny, formIndex, female, variant } = options;
const oldProps = species ? this.starterAttributes : null;
// We will only update the sprite if there is a change to form, shiny/variant
// or gender for species with gender sprite differences
const shouldUpdateSprite = (species?.genderDiffs && !isNullOrUndefined(female))
|| !isNullOrUndefined(formIndex) || !isNullOrUndefined(shiny) || !isNullOrUndefined(variant) || forceUpdate;
if (this.activeTooltip === "CANDY") {
if (this.species && this.pokemonCandyContainer.visible) {
const { currentFriendship, friendshipCap } = this.getFriendship(this.species.speciesId);
globalScene.ui.editTooltip("", `${currentFriendship}/${friendshipCap}`);
} else {
globalScene.ui.hideTooltip();
}
}
if (species?.forms?.find(f => f.formKey === "female")) {
if (female !== undefined) {
formIndex = female ? 1 : 0;
} else if (formIndex !== undefined) {
female = formIndex === 1;
}
}
if (species) {
// Only assign shiny, female, and variant if they are undefined
if (shiny === undefined) {
shiny = oldProps?.shiny ?? false;
}
if (female === undefined) {
female = oldProps?.female ?? false;
}
if (variant === undefined) {
variant = oldProps?.variant ?? 0;
}
if (formIndex === undefined) {
formIndex = oldProps?.form ?? 0;
}
}
this.pokemonSprite.setVisible(false);
if (this.assetLoadCancelled) {
this.assetLoadCancelled.value = true;
this.assetLoadCancelled = null;
}
if (species) {
const dexEntry = globalScene.gameData.dexData[species.speciesId];
const caughtAttr = this.isCaught(species);
if (!caughtAttr) {
const props = this.starterAttributes;
if (shiny === undefined || shiny !== props.shiny) {
shiny = props.shiny;
}
if (formIndex === undefined || formIndex !== props.form) {
formIndex = props.form;
}
if (female === undefined || female !== props.female) {
female = props.female;
}
if (variant === undefined || variant !== props.variant) {
variant = props.variant;
}
}
const isFormCaught = this.isFormCaught();
const isFormSeen = dexEntry ? (dexEntry.seenAttr & globalScene.gameData.getFormAttr(formIndex ?? 0)) > 0n : false;
this.shinyOverlay.setVisible(shiny ?? false); // TODO: is false the correct default?
this.pokemonNumberText.setColor(this.getTextColor(shiny ? TextStyle.SUMMARY_GOLD : TextStyle.SUMMARY, false));
this.pokemonNumberText.setShadowColor(this.getTextColor(shiny ? TextStyle.SUMMARY_GOLD : TextStyle.SUMMARY, true));
const assetLoadCancelled = new BooleanHolder(false);
this.assetLoadCancelled = assetLoadCancelled;
if (shouldUpdateSprite) {
const back = this.showBackSprite ? true : false;
species.loadAssets(female!, formIndex, shiny, variant as Variant, true, back).then(() => { // TODO: is this bang correct?
if (assetLoadCancelled.value) {
return;
}
this.assetLoadCancelled = null;
this.speciesLoaded.set(species.speciesId, true);
this.pokemonSprite.play(species.getSpriteKey(female!, formIndex, shiny, variant, back)); // TODO: is this bang correct?
this.pokemonSprite.setPipelineData("shiny", shiny);
this.pokemonSprite.setPipelineData("variant", variant);
this.pokemonSprite.setPipelineData("spriteKey", species.getSpriteKey(female!, formIndex, shiny, variant, back)); // TODO: is this bang correct?
this.pokemonSprite.setVisible(!this.statsMode);
});
} else {
this.pokemonSprite.setVisible(!this.statsMode);
}
const isNonShinyCaught = !!(caughtAttr & DexAttr.NON_SHINY);
const isShinyCaught = !!(caughtAttr & DexAttr.SHINY);
this.canCycleShiny = isNonShinyCaught && isShinyCaught;
const isMaleCaught = !!(caughtAttr & DexAttr.MALE);
const isFemaleCaught = !!(caughtAttr & DexAttr.FEMALE);
this.canCycleGender = isMaleCaught && isFemaleCaught;
// If the dev option for the dex is selected, all forms can be cycled through
this.canCycleForm = globalScene.dexForDevs ? species.forms.length > 1 :
species.forms.filter(f => f.isStarterSelectable).filter(f => f).length > 1;
if (caughtAttr && species.malePercent !== null) {
const gender = !female ? Gender.MALE : Gender.FEMALE;
this.pokemonGenderText.setText(getGenderSymbol(gender));
this.pokemonGenderText.setColor(getGenderColor(gender));
this.pokemonGenderText.setShadowColor(getGenderColor(gender, true));
} else {
this.pokemonGenderText.setText("");
}
// Setting the name
if (isFormCaught || isFormSeen) {
this.pokemonNameText.setText(species.name);
} else {
this.pokemonNameText.setText(species ? "???" : "");
}
// Setting tint of the sprite
if (isFormCaught) {
this.species.loadAssets(female!, formIndex, shiny, variant as Variant, true).then(() => {
const crier = (this.species.forms && this.species.forms.length > 0) ? this.species.forms[formIndex ?? this.formIndex] : this.species;
crier.cry();
});
this.pokemonSprite.clearTint();
} else if (isFormSeen) {
this.pokemonSprite.setTint(0x808080);
} else {
this.pokemonSprite.setTint(0);
}
// Setting luck text and sparks
if (isFormCaught) {
const luck = globalScene.gameData.getDexAttrLuck(this.isCaught());
this.pokemonLuckText.setVisible(!!luck);
this.pokemonLuckText.setText(luck.toString());
this.pokemonLuckText.setTint(getVariantTint(Math.min(luck - 1, 2) as Variant));
this.pokemonLuckLabelText.setVisible(this.pokemonLuckText.visible);
} else {
this.pokemonLuckText.setVisible(false);
this.pokemonLuckLabelText.setVisible(false);
}
// Setting growth rate text
if (isFormCaught) {
let growthReadable = toReadableString(GrowthRate[species.growthRate]);
const growthAux = growthReadable.replace(" ", "_");
if (i18next.exists("growth:" + growthAux)) {
growthReadable = i18next.t("growth:" + growthAux as any);
}
this.pokemonGrowthRateText.setText(growthReadable);
this.pokemonGrowthRateText.setColor(getGrowthRateColor(species.growthRate));
this.pokemonGrowthRateText.setShadowColor(getGrowthRateColor(species.growthRate, true));
this.pokemonGrowthRateLabelText.setVisible(true);
} else {
this.pokemonGrowthRateText.setText("");
this.pokemonGrowthRateLabelText.setVisible(false);
}
// Caught and hatched
if (isFormCaught) {
const colorScheme = starterColors[this.starterId];
this.pokemonUncaughtText.setVisible(false);
this.pokemonCaughtCountText.setText(`${this.speciesStarterDexEntry?.caughtCount}`);
if (species.speciesId === Species.MANAPHY || species.speciesId === Species.PHIONE) {
this.pokemonHatchedIcon.setFrame("manaphy");
} else {
this.pokemonHatchedIcon.setFrame(getEggTierForSpecies(species));
}
this.pokemonHatchedCountText.setText(`${this.speciesStarterDexEntry?.hatchedCount}`);
const defaultDexAttr = this.getCurrentDexProps(species.speciesId);
const defaultProps = globalScene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr);
const variant = defaultProps.variant;
const tint = getVariantTint(variant);
this.pokemonShinyIcon.setFrame(getVariantIcon(variant));
this.pokemonShinyIcon.setTint(tint);
this.pokemonShinyIcon.setVisible(defaultProps.shiny);
this.pokemonCaughtHatchedContainer.setVisible(true);
this.pokemonCaughtHatchedContainer.setY(25);
this.pokemonCandyIcon.setTint(argbFromRgba(rgbHexToRgba(colorScheme[0])));
this.pokemonCandyOverlayIcon.setTint(argbFromRgba(rgbHexToRgba(colorScheme[1])));
this.pokemonCandyCountText.setText(`x${globalScene.gameData.starterData[this.starterId].candyCount}`);
this.pokemonCandyContainer.setVisible(true);
if (pokemonPrevolutions.hasOwnProperty(species.speciesId)) {
this.pokemonShinyIcon.setFrame(getVariantIcon(variant));
this.pokemonHatchedIcon.setVisible(false);
this.pokemonHatchedCountText.setVisible(false);
this.pokemonFormText.setY(36);
} else {
this.pokemonHatchedIcon.setVisible(true);
this.pokemonHatchedCountText.setVisible(true);
this.pokemonFormText.setY(42);
const { currentFriendship, friendshipCap } = this.getFriendship(this.species.speciesId);
const candyCropY = 16 - (16 * (currentFriendship / friendshipCap));
this.pokemonCandyDarknessOverlay.setCrop(0, 0, 16, candyCropY);
this.pokemonCandyContainer.on("pointerover", () => {
globalScene.ui.showTooltip("", `${currentFriendship}/${friendshipCap}`, true);
this.activeTooltip = "CANDY";
});
this.pokemonCandyContainer.on("pointerout", () => {
globalScene.ui.hideTooltip();
this.activeTooltip = undefined;
});
}
} else {
this.pokemonUncaughtText.setVisible(true);
this.pokemonCaughtHatchedContainer.setVisible(false);
this.pokemonCandyContainer.setVisible(false);
this.pokemonShinyIcon.setVisible(false);
}
// Setting type icons and form text
if (isFormCaught || isFormSeen) {
const speciesForm = getPokemonSpeciesForm(species.speciesId, formIndex!); // TODO: is the bang correct?
this.setTypeIcons(speciesForm.type1, speciesForm.type2);
// TODO: change this once forms are refactored
if (normalForm.includes(species.speciesId) && !formIndex) {
this.pokemonFormText.setText("");
} else {
this.pokemonFormText.setText(species.getFormNameToDisplay(formIndex));
}
this.pokemonFormText.setVisible(true);
if (!isFormCaught) {
this.pokemonFormText.setY(18);
}
} else {
this.setTypeIcons(null, null);
this.pokemonFormText.setText("");
this.pokemonFormText.setVisible(false);
}
} else {
this.shinyOverlay.setVisible(false);
this.pokemonNumberText.setColor(this.getTextColor(TextStyle.SUMMARY));
this.pokemonNumberText.setShadowColor(this.getTextColor(TextStyle.SUMMARY, true));
this.pokemonGenderText.setText("");
this.setTypeIcons(null, null);
}
this.updateInstructions();
}
setTypeIcons(type1: Type | null, type2: Type | null): void {
if (type1 !== null) {
this.type1Icon.setVisible(true);
this.type1Icon.setFrame(Type[type1].toLowerCase());
} else {
this.type1Icon.setVisible(false);
}
if (type2 !== null) {
this.type2Icon.setVisible(true);
this.type2Icon.setFrame(Type[type2].toLowerCase());
} else {
this.type2Icon.setVisible(false);
}
}
/**
* Creates a temporary dex attr props that will be used to display the correct shiny, variant, and form based on this.starterAttributes
*
* @param speciesId the id of the species to get props for
* @returns the dex props
*/
getCurrentDexProps(speciesId: number): bigint {
let props = 0n;
const species = allSpecies.find(sp => sp.speciesId === speciesId);
const caughtAttr = globalScene.gameData.dexData[speciesId].caughtAttr & globalScene.gameData.dexData[this.getStarterSpeciesId(speciesId)].caughtAttr & (species?.getFullUnlocksData() ?? 0n);
/* this checks the gender of the pokemon; this works by checking a) that the starter preferences for the species exist, and if so, is it female. If so, it'll add DexAttr.FEMALE to our temp props
* It then checks b) if the caughtAttr for the pokemon is female and NOT male - this means that the ONLY gender we've gotten is female, and we need to add DexAttr.FEMALE to our temp props
* If neither of these pass, we add DexAttr.MALE to our temp props
*/
if (this.starterAttributes?.female || ((caughtAttr & DexAttr.FEMALE) > 0n && (caughtAttr & DexAttr.MALE) === 0n)) {
props += DexAttr.FEMALE;
} else {
props += DexAttr.MALE;
}
/* This part is very similar to above, but instead of for gender, it checks for shiny within starter preferences.
* If they're not there, it enables shiny state by default if any shiny was caught
*/
if (this.starterAttributes?.shiny || ((caughtAttr & DexAttr.SHINY) > 0n && this.starterAttributes?.shiny !== false)) {
props += DexAttr.SHINY;
if (this.starterAttributes?.variant !== undefined) {
props += BigInt(Math.pow(2, this.starterAttributes?.variant)) * DexAttr.DEFAULT_VARIANT;
} else {
/* This calculates the correct variant if there's no starter preferences for it.
* This gets the highest tier variant that you've caught and adds it to the temp props
*/
if ((caughtAttr & DexAttr.VARIANT_3) > 0) {
props += DexAttr.VARIANT_3;
} else if ((caughtAttr & DexAttr.VARIANT_2) > 0) {
props += DexAttr.VARIANT_2;
} else {
props += DexAttr.DEFAULT_VARIANT;
}
}
} else {
props += DexAttr.NON_SHINY;
props += DexAttr.DEFAULT_VARIANT; // we add the default variant here because non shiny versions are listed as default variant
}
if (this.starterAttributes?.form) { // this checks for the form of the pokemon
props += BigInt(Math.pow(2, this.starterAttributes?.form)) * DexAttr.DEFAULT_FORM;
} else {
// Get the first unlocked form
props += globalScene.gameData.getFormAttr(globalScene.gameData.getFormIndex(caughtAttr));
}
return props;
}
toggleStatsMode(on?: boolean): void {
if (on === undefined) {
on = !this.statsMode;
}
if (on) {
this.showStats();
this.statsMode = true;
this.pokemonSprite.setVisible(false);
} else {
this.statsMode = false;
this.statsContainer.setVisible(false);
this.pokemonSprite.setVisible(true);
//@ts-ignore
this.statsContainer.updateIvs(null); // TODO: resolve ts-ignore. !?!?
}
}
showStats(): void {
if (!this.speciesStarterDexEntry) {
return;
}
this.statsContainer.setVisible(true);
this.statsContainer.updateIvs(this.speciesStarterDexEntry.ivs);
}
clearText() {
this.starterSelectMessageBoxContainer.setVisible(false);
super.clearText();
}
hideInstructions(): void {
this.candyUpgradeIconElement.setVisible(false);
this.candyUpgradeLabel.setVisible(false);
this.shinyIconElement.setVisible(false);
this.shinyLabel.setVisible(false);
this.formIconElement.setVisible(false);
this.formLabel.setVisible(false);
this.genderIconElement.setVisible(false);
this.genderLabel.setVisible(false);
this.variantIconElement.setVisible(false);
this.variantLabel.setVisible(false);
}
clear(): void {
super.clear();
this.cursor = -1;
this.hideInstructions();
this.activeTooltip = undefined;
globalScene.ui.hideTooltip();
this.starterSelectContainer.setVisible(false);
this.blockInput = false;
this.showBackSprite = false;
this.showBackSpriteLabel.setText(i18next.t("pokedexUiHandler:showBackSprite"));
if (this.statsMode) {
this.toggleStatsMode(false);
}
}
checkIconId(icon: Phaser.GameObjects.Sprite, species: PokemonSpecies, female: boolean, formIndex: number, shiny: boolean, variant: number) {
if (icon.frame.name !== species.getIconId(female, formIndex, shiny, variant)) {
console.log(`${species.name}'s icon ${icon.frame.name} does not match getIconId with female: ${female}, formIndex: ${formIndex}, shiny: ${shiny}, variant: ${variant}`);
icon.setTexture(species.getIconAtlasKey(formIndex, false, variant));
icon.setFrame(species.getIconId(female, formIndex, false, variant));
}
}
}