[UI/UX] Pokedex updates batch (#5282)

* Introducing tray to display form icons in the pokedex; displaying correct information for uncaught and seen forms in pokedex page; dexForDevs now unlocks everything in the main page

* Filtering correctly passive abilities and form abilities. Passive candy symbol is now colored

* Pikachu does not break the dex due to having no passive

* Fixed position of pokemonFormText

* Added button instructions to show forms

* Allowing candy upgrades for evolutions; too expensive options shown in shadow text

* Apply suggestions from code review

Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>

* Fixed game crashing after save and quit

* Updating import of BBCodeText

* Restoring name on dex page

* getStarterSpecies now looks at speciesStarterCosts to determine what is a starter instead of looking at game data (exception for Pikachu)

* Selecting pokedex option in starter select menu does not play error sound

* Mons having no TM moves don't freeze the game in the dex

* Menu in pokedex page is not pushed to the left when localized options are long

* Removed spurious globalScene.clearPhaseQueue() call

* Showing error message when clicking tm option if no tm moves are available

* Egg move icon and passive icon are darkened when filtering if the respective move or passive has not been unlocked

* Hiding form button when switching to filters

* Hiding "Show forms" button while forms are being shown

---------

Co-authored-by: NightKev <34855794+DayKev@users.noreply.github.com>
This commit is contained in:
Wlowscha 2025-02-11 08:32:32 +01:00 committed by GitHub
parent f5ef4a5da9
commit 60b27f4f62
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 626 additions and 294 deletions

View File

@ -6,7 +6,7 @@ import { addWindow } from "./ui-theme";
import * as Utils from "../utils"; import * as Utils from "../utils";
import { argbFromRgba } from "@material/material-color-utilities"; import { argbFromRgba } from "@material/material-color-utilities";
import { Button } from "#enums/buttons"; import { Button } from "#enums/buttons";
import BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext"; import BBCodeText from "phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/BBCodeText";
export interface OptionSelectConfig { export interface OptionSelectConfig {
xOffset?: number; xOffset?: number;

View File

@ -1,7 +1,17 @@
import type { Variant } from "#app/data/variant";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { isNullOrUndefined } from "#app/utils";
import type PokemonSpecies from "../data/pokemon-species"; import type PokemonSpecies from "../data/pokemon-species";
import { addTextObject, TextStyle } from "./text"; import { addTextObject, TextStyle } from "./text";
interface SpeciesDetails {
shiny?: boolean,
formIndex?: number
female?: boolean,
variant?: Variant
}
export class PokedexMonContainer extends Phaser.GameObjects.Container { export class PokedexMonContainer extends Phaser.GameObjects.Container {
public species: PokemonSpecies; public species: PokemonSpecies;
public icon: Phaser.GameObjects.Sprite; public icon: Phaser.GameObjects.Sprite;
@ -19,16 +29,34 @@ export class PokedexMonContainer extends Phaser.GameObjects.Container {
public tmMove2Icon: Phaser.GameObjects.Image; public tmMove2Icon: Phaser.GameObjects.Image;
public passive1Icon: Phaser.GameObjects.Image; public passive1Icon: Phaser.GameObjects.Image;
public passive2Icon: Phaser.GameObjects.Image; public passive2Icon: Phaser.GameObjects.Image;
public passive1OverlayIcon: Phaser.GameObjects.Image;
public passive2OverlayIcon: Phaser.GameObjects.Image;
public cost: number = 0; public cost: number = 0;
constructor(species: PokemonSpecies) { constructor(species: PokemonSpecies, options: SpeciesDetails = {}) {
super(globalScene, 0, 0); super(globalScene, 0, 0);
this.species = species; this.species = species;
const { shiny, formIndex, female, variant } = options;
const defaultDexAttr = globalScene.gameData.getSpeciesDefaultDexAttr(species, false, true); const defaultDexAttr = globalScene.gameData.getSpeciesDefaultDexAttr(species, false, true);
const defaultProps = globalScene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr); const defaultProps = globalScene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr);
if (!isNullOrUndefined(formIndex)) {
defaultProps.formIndex = formIndex;
}
if (!isNullOrUndefined(shiny)) {
defaultProps.shiny = shiny;
}
if (!isNullOrUndefined(variant)) {
defaultProps.variant = variant;
}
if (!isNullOrUndefined(female)) {
defaultProps.female = female;
}
// starter passive bg // starter passive bg
const starterPassiveBg = globalScene.add.image(2, 5, "passive_bg"); const starterPassiveBg = globalScene.add.image(2, 5, "passive_bg");
starterPassiveBg.setOrigin(0, 0); starterPassiveBg.setOrigin(0, 0);
@ -137,7 +165,7 @@ export class PokedexMonContainer extends Phaser.GameObjects.Container {
this.tmMove2Icon = tmMove2Icon; this.tmMove2Icon = tmMove2Icon;
// move icons // passive icons
const passive1Icon = globalScene.add.image(3, 3, "candy"); const passive1Icon = globalScene.add.image(3, 3, "candy");
passive1Icon.setOrigin(0, 0); passive1Icon.setOrigin(0, 0);
passive1Icon.setScale(0.25); passive1Icon.setScale(0.25);
@ -145,13 +173,27 @@ export class PokedexMonContainer extends Phaser.GameObjects.Container {
this.add(passive1Icon); this.add(passive1Icon);
this.passive1Icon = passive1Icon; this.passive1Icon = passive1Icon;
// move icons const passive1OverlayIcon = globalScene.add.image(12, 12, "candy_overlay");
passive1OverlayIcon.setOrigin(0, 0);
passive1OverlayIcon.setScale(0.25);
passive1OverlayIcon.setVisible(false);
this.add(passive1OverlayIcon);
this.passive1OverlayIcon = passive1OverlayIcon;
// passive icons
const passive2Icon = globalScene.add.image(12, 3, "candy"); const passive2Icon = globalScene.add.image(12, 3, "candy");
passive2Icon.setOrigin(0, 0); passive2Icon.setOrigin(0, 0);
passive2Icon.setScale(0.25); passive2Icon.setScale(0.25);
passive2Icon.setVisible(false); passive2Icon.setVisible(false);
this.add(passive2Icon); this.add(passive2Icon);
this.passive2Icon = passive2Icon; this.passive2Icon = passive2Icon;
const passive2OverlayIcon = globalScene.add.image(12, 12, "candy_overlay");
passive2OverlayIcon.setOrigin(0, 0);
passive2OverlayIcon.setScale(0.25);
passive2OverlayIcon.setVisible(false);
this.add(passive2OverlayIcon);
this.passive2OverlayIcon = passive2OverlayIcon;
} }
checkIconId(female, formIndex, shiny, variant) { checkIconId(female, formIndex, shiny, variant) {

View File

@ -43,7 +43,6 @@ import type { Moves } from "#enums/moves";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { Button } from "#enums/buttons"; import { Button } from "#enums/buttons";
import { EggSourceType } from "#enums/egg-source-types"; import { EggSourceType } from "#enums/egg-source-types";
import { StarterContainer } from "#app/ui/starter-container";
import { getPassiveCandyCount, getValueReductionCandyCounts, getSameSpeciesEggCandyCounts } from "#app/data/balance/starters"; import { getPassiveCandyCount, getValueReductionCandyCounts, getSameSpeciesEggCandyCounts } from "#app/data/balance/starters";
import { BooleanHolder, capitalizeString, getLocalizedSpriteKey, isNullOrUndefined, NumberHolder, padInt, rgbHexToRgba, toReadableString } from "#app/utils"; import { BooleanHolder, capitalizeString, getLocalizedSpriteKey, isNullOrUndefined, NumberHolder, padInt, rgbHexToRgba, toReadableString } from "#app/utils";
import type { Nature } from "#enums/nature"; import type { Nature } from "#enums/nature";
@ -128,7 +127,6 @@ interface SpeciesDetails {
formIndex?: number formIndex?: number
female?: boolean, female?: boolean,
variant?: number, variant?: number,
forSeen?: boolean, // default = false
} }
enum MenuOptions { enum MenuOptions {
@ -147,8 +145,6 @@ enum MenuOptions {
export default class PokedexPageUiHandler extends MessageUiHandler { export default class PokedexPageUiHandler extends MessageUiHandler {
private starterSelectContainer: Phaser.GameObjects.Container; private starterSelectContainer: Phaser.GameObjects.Container;
private shinyOverlay: Phaser.GameObjects.Image; private shinyOverlay: Phaser.GameObjects.Image;
private starterContainers: StarterContainer[] = [];
private filteredStarterContainers: StarterContainer[] = [];
private pokemonNumberText: Phaser.GameObjects.Text; private pokemonNumberText: Phaser.GameObjects.Text;
private pokemonSprite: Phaser.GameObjects.Sprite; private pokemonSprite: Phaser.GameObjects.Sprite;
private pokemonNameText: Phaser.GameObjects.Text; private pokemonNameText: Phaser.GameObjects.Text;
@ -199,6 +195,7 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
private allSpecies: PokemonSpecies[] = []; private allSpecies: PokemonSpecies[] = [];
private species: PokemonSpecies; private species: PokemonSpecies;
private starterId: number;
private formIndex: number; private formIndex: number;
private speciesLoaded: Map<Species, boolean> = new Map<Species, boolean>(); private speciesLoaded: Map<Species, boolean> = new Map<Species, boolean>();
private levelMoves: LevelMoves; private levelMoves: LevelMoves;
@ -312,10 +309,6 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
this.speciesLoaded.set(species.speciesId, false); this.speciesLoaded.set(species.speciesId, false);
this.allSpecies.push(species); this.allSpecies.push(species);
const starterContainer = new StarterContainer(species).setVisible(false);
this.starterContainers.push(starterContainer);
starterBoxContainer.add(starterContainer);
} }
this.starterSelectContainer.add(starterBoxContainer); this.starterSelectContainer.add(starterBoxContainer);
@ -513,7 +506,7 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
this.scale = getTextStyleOptions(TextStyle.WINDOW, globalScene.uiTheme).scale; this.scale = getTextStyleOptions(TextStyle.WINDOW, globalScene.uiTheme).scale;
this.menuBg = addWindow( this.menuBg = addWindow(
(globalScene.game.canvas.width / 6) - (this.optionSelectText.displayWidth + 25), (globalScene.game.canvas.width / 6 - 83),
0, 0,
this.optionSelectText.displayWidth + 19 + 24 * this.scale, this.optionSelectText.displayWidth + 19 + 24 * this.scale,
(globalScene.game.canvas.height / 6) - 2 (globalScene.game.canvas.height / 6) - 2
@ -555,8 +548,6 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
// Filter bar sits above everything, except the message box // Filter bar sits above everything, except the message box
this.starterSelectContainer.bringToTop(this.starterSelectMessageBoxContainer); this.starterSelectContainer.bringToTop(this.starterSelectMessageBoxContainer);
this.updateInstructions();
} }
show(args: any[]): boolean { show(args: any[]): boolean {
@ -603,6 +594,8 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
const species = this.species; const species = this.species;
const formIndex = this.formIndex ?? 0; const formIndex = this.formIndex ?? 0;
this.starterId = this.getStarterSpeciesId(this.species.speciesId);
const allEvolutions = pokemonEvolutions.hasOwnProperty(species.speciesId) ? pokemonEvolutions[species.speciesId] : []; const allEvolutions = pokemonEvolutions.hasOwnProperty(species.speciesId) ? pokemonEvolutions[species.speciesId] : [];
if (species.forms.length > 0) { if (species.forms.length > 0) {
@ -629,17 +622,19 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
this.baseTotal = species.baseTotal; this.baseTotal = species.baseTotal;
} }
this.eggMoves = speciesEggMoves[this.getStarterSpeciesId(species.speciesId)] ?? []; this.eggMoves = speciesEggMoves[this.starterId] ?? [];
this.hasEggMoves = Array.from({ length: 4 }, (_, em) => (globalScene.gameData.starterData[this.getStarterSpeciesId(species.speciesId)].eggMoves & (1 << em)) !== 0); 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 : ""; 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) 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) ?? []; .map(m => Array.isArray(m) ? m[1] : m).sort((a, b) => allMoves[a].name > allMoves[b].name ? 1 : -1) ?? [];
const passives = starterPassiveAbilities[this.getStarterSpeciesId(species.speciesId)]; 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]; this.passive = (this.formIndex in passives) ? passives[formIndex] : passives[0];
const starterData = globalScene.gameData.starterData[this.getStarterSpeciesId(species.speciesId)]; const starterData = globalScene.gameData.starterData[this.starterId];
const abilityAttr = starterData.abilityAttr; const abilityAttr = starterData.abilityAttr;
this.hasPassive = starterData.passiveAttr > 0; this.hasPassive = starterData.passiveAttr > 0;
@ -655,9 +650,9 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
const allBiomes = catchableSpecies[species.speciesId] ?? []; const allBiomes = catchableSpecies[species.speciesId] ?? [];
this.preBiomes = this.sanitizeBiomes( this.preBiomes = this.sanitizeBiomes(
(catchableSpecies[this.getStarterSpeciesId(species.speciesId)] ?? []) (catchableSpecies[this.starterId] ?? [])
.filter(b => !allBiomes.some(bm => (b.biome === bm.biome && b.tier === bm.tier)) && !(b.biome === Biome.TOWN)), .filter(b => !allBiomes.some(bm => (b.biome === bm.biome && b.tier === bm.tier)) && !(b.biome === Biome.TOWN)),
this.getStarterSpeciesId(species.speciesId)); this.starterId);
this.biomes = this.sanitizeBiomes(allBiomes, species.speciesId); this.biomes = this.sanitizeBiomes(allBiomes, species.speciesId);
const allFormChanges = pokemonFormChanges.hasOwnProperty(species.speciesId) ? pokemonFormChanges[species.speciesId] : []; const allFormChanges = pokemonFormChanges.hasOwnProperty(species.speciesId) ? pokemonFormChanges[species.speciesId] : [];
@ -799,39 +794,43 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
const hasShiny = caughtAttr & DexAttr.SHINY; const hasShiny = caughtAttr & DexAttr.SHINY;
const hasNonShiny = caughtAttr & DexAttr.NON_SHINY; const hasNonShiny = caughtAttr & DexAttr.NON_SHINY;
if (starterAttributes.shiny && !hasShiny) { if (!hasShiny || (starterAttributes.shiny === undefined && hasNonShiny)) {
// shiny form wasn't unlocked, purging shiny and variant setting // shiny form wasn't unlocked, purging shiny and variant setting
starterAttributes.shiny = false; starterAttributes.shiny = false;
starterAttributes.variant = 0; starterAttributes.variant = 0;
} else if (starterAttributes.shiny === false && !hasNonShiny) { } else if (!hasNonShiny || (starterAttributes.shiny === undefined && hasShiny)) {
// non shiny form wasn't unlocked, purging shiny setting starterAttributes.shiny = true;
starterAttributes.shiny = false; starterAttributes.variant = 0;
} }
if (starterAttributes.variant !== undefined) { const unlockedVariants = [
const unlockedVariants = [ hasShiny && caughtAttr & DexAttr.DEFAULT_VARIANT,
hasShiny && caughtAttr & DexAttr.DEFAULT_VARIANT, hasShiny && caughtAttr & DexAttr.VARIANT_2,
hasShiny && caughtAttr & DexAttr.VARIANT_2, hasShiny && caughtAttr & DexAttr.VARIANT_3
hasShiny && caughtAttr & DexAttr.VARIANT_3 ];
]; if (starterAttributes.variant === undefined || isNaN(starterAttributes.variant) || starterAttributes.variant < 0) {
if (isNaN(starterAttributes.variant) || starterAttributes.variant < 0) { starterAttributes.variant = 0;
starterAttributes.variant = 0; } else if (!unlockedVariants[starterAttributes.variant]) {
} else if (!unlockedVariants[starterAttributes.variant]) { let highestValidIndex = -1;
let highestValidIndex = -1; for (let i = 0; i <= starterAttributes.variant && i < unlockedVariants.length; i++) {
for (let i = 0; i <= starterAttributes.variant && i < unlockedVariants.length; i++) { if (unlockedVariants[i] !== 0n) {
if (unlockedVariants[i] !== 0n) { highestValidIndex = i;
highestValidIndex = i;
}
} }
// Set to the highest valid index found or default to 0
starterAttributes.variant = highestValidIndex !== -1 ? highestValidIndex : 0;
} }
// Set to the highest valid index found or default to 0
starterAttributes.variant = highestValidIndex !== -1 ? highestValidIndex : 0;
} }
if (starterAttributes.female !== undefined) { if (starterAttributes.female !== undefined) {
if ((starterAttributes.female && !(caughtAttr & DexAttr.FEMALE)) || (!starterAttributes.female && !(caughtAttr & DexAttr.MALE))) { if ((starterAttributes.female && !(caughtAttr & DexAttr.FEMALE)) || (!starterAttributes.female && !(caughtAttr & DexAttr.MALE))) {
starterAttributes.female = !starterAttributes.female; starterAttributes.female = !starterAttributes.female;
} }
} else {
if (caughtAttr & DexAttr.FEMALE) {
starterAttributes.female = true;
} else if (caughtAttr & DexAttr.MALE) {
starterAttributes.female = false;
}
} }
return starterAttributes; return starterAttributes;
@ -878,7 +877,14 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
* @returns the id of the corresponding starter * @returns the id of the corresponding starter
*/ */
getStarterSpeciesId(speciesId): number { getStarterSpeciesId(speciesId): number {
if (globalScene.gameData.starterData.hasOwnProperty(speciesId)) { if (speciesId === Species.PIKACHU) {
if ([ 0, 1, 8 ].includes(this.formIndex)) {
return Species.PICHU;
} else {
return Species.PIKACHU;
}
}
if (speciesStarterCosts.hasOwnProperty(speciesId)) {
return speciesId; return speciesId;
} else { } else {
return pokemonStarters[speciesId]; return pokemonStarters[speciesId];
@ -886,7 +892,7 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
} }
getStarterSpecies(species): PokemonSpecies { getStarterSpecies(species): PokemonSpecies {
if (globalScene.gameData.starterData.hasOwnProperty(species.speciesId)) { if (speciesStarterCosts.hasOwnProperty(species.speciesId)) {
return species; return species;
} else { } else {
return allSpecies.find(sp => sp.speciesId === pokemonStarters[species.speciesId]) ?? species; return allSpecies.find(sp => sp.speciesId === pokemonStarters[species.speciesId]) ?? species;
@ -970,7 +976,7 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
} }
} else { } else {
const starterData = globalScene.gameData.starterData[this.getStarterSpeciesId(this.species.speciesId)]; const starterData = globalScene.gameData.starterData[this.starterId];
// prepare persistent starter data to store changes // prepare persistent starter data to store changes
const starterAttributes = this.starterAttributes; const starterAttributes = this.starterAttributes;
@ -1126,6 +1132,9 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
if (!isCaught || !isFormCaught) { if (!isCaught || !isFormCaught) {
error = true; error = true;
} else if (this.tmMoves.length < 1) {
ui.showText(i18next.t("pokedexUiHandler:noTmMoves"));
error = true;
} else { } else {
this.blockInput = true; this.blockInput = true;
@ -1633,90 +1642,55 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
error = true; error = true;
} else { } else {
const ui = this.getUi(); const ui = this.getUi();
ui.showText("");
const options: any[] = []; // TODO: add proper type const options: any[] = []; // TODO: add proper type
const passiveAttr = starterData.passiveAttr; const passiveAttr = starterData.passiveAttr;
const candyCount = starterData.candyCount; const candyCount = starterData.candyCount;
if (!pokemonPrevolutions.hasOwnProperty(this.species.speciesId)) { if (!(passiveAttr & PassiveAttr.UNLOCKED)) {
if (!(passiveAttr & PassiveAttr.UNLOCKED)) { const passiveCost = getPassiveCandyCount(speciesStarterCosts[this.starterId]);
const passiveCost = getPassiveCandyCount(speciesStarterCosts[this.getStarterSpeciesId(this.species.speciesId)]);
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);
}
});
ui.setMode(Mode.POKEDEX_PAGE, "refresh");
this.setSpeciesDetails(this.species);
globalScene.playSound("se/buy");
return true;
}
return false;
},
item: "candy",
itemArgs: starterColors[this.getStarterSpeciesId(this.species.speciesId)]
});
}
// Reduce cost option
const valueReduction = starterData.valueReduction;
if (valueReduction < valueReductionMax) {
const reductionCost = getValueReductionCandyCounts(speciesStarterCosts[this.getStarterSpeciesId(this.species.speciesId)])[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;
},
item: "candy",
itemArgs: starterColors[this.getStarterSpeciesId(this.species.speciesId)]
});
}
// Same species egg menu option.
const sameSpeciesEggCost = getSameSpeciesEggCandyCounts(speciesStarterCosts[this.getStarterSpeciesId(this.species.speciesId)]);
options.push({ options.push({
label: `x${sameSpeciesEggCost} ${i18next.t("pokedexUiHandler:sameSpeciesEgg")}`, label: `x${passiveCost} ${i18next.t("pokedexUiHandler:unlockPassive")} (${allAbilities[this.passive].name})`,
handler: () => { handler: () => {
if (Overrides.FREE_CANDY_UPGRADE_OVERRIDE || candyCount >= sameSpeciesEggCost) { if (Overrides.FREE_CANDY_UPGRADE_OVERRIDE || candyCount >= passiveCost) {
if (globalScene.gameData.eggs.length >= 99 && !Overrides.UNLIMITED_EGG_COUNT_OVERRIDE) { starterData.passiveAttr |= PassiveAttr.UNLOCKED | PassiveAttr.ENABLED;
// 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) { if (!Overrides.FREE_CANDY_UPGRADE_OVERRIDE) {
starterData.candyCount -= sameSpeciesEggCost; starterData.candyCount -= passiveCost;
} }
this.pokemonCandyCountText.setText(`x${starterData.candyCount}`); 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");
const egg = new Egg({ scene: globalScene, species: this.species.speciesId, sourceType: EggSourceType.SAME_SPECIES_EGG }); return true;
egg.addEggToGameData(); }
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 => { globalScene.gameData.saveSystem().then(success => {
if (!success) { if (!success) {
return globalScene.reset(true); return globalScene.reset(true);
@ -1729,24 +1703,59 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
} }
return false; return false;
}, },
style: this.isValueReductionAvailable() ? TextStyle.WINDOW : TextStyle.SHADOW_TEXT,
item: "candy", item: "candy",
itemArgs: starterColors[this.getStarterSpeciesId(this.species.speciesId)] itemArgs: this.isValueReductionAvailable() ? starterColors[this.starterId] : [ "808080", "808080" ]
}); });
options.push({ }
label: i18next.t("menu:cancel"),
handler: () => { // 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.species.speciesId, sourceType: EggSourceType.SAME_SPECIES_EGG });
egg.addEggToGameData();
globalScene.gameData.saveSystem().then(success => {
if (!success) {
return globalScene.reset(true);
}
});
ui.setMode(Mode.POKEDEX_PAGE, "refresh"); ui.setMode(Mode.POKEDEX_PAGE, "refresh");
globalScene.playSound("se/buy");
return true; return true;
} }
}); return false;
ui.setModeWithoutClear(Mode.OPTION_SELECT, { },
options: options, style: this.isSameSpeciesEggAvailable() ? TextStyle.WINDOW : TextStyle.SHADOW_TEXT,
yOffset: 47 item: "candy",
}); itemArgs: this.isSameSpeciesEggAvailable() ? starterColors[this.starterId] : [ "808080", "808080" ]
success = true; });
} else { options.push({
error = true; 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; break;
case Button.CYCLE_ABILITY: case Button.CYCLE_ABILITY:
@ -1877,9 +1886,7 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
if (this.isCaught()) { if (this.isCaught()) {
if (isFormCaught) { if (isFormCaught) {
if (!pokemonPrevolutions.hasOwnProperty(this.species.speciesId)) { this.updateButtonIcon(SettingKeyboard.Button_Stats, gamepadType, this.candyUpgradeIconElement, this.candyUpgradeLabel);
this.updateButtonIcon(SettingKeyboard.Button_Stats, gamepadType, this.candyUpgradeIconElement, this.candyUpgradeLabel);
}
if (this.canCycleShiny) { if (this.canCycleShiny) {
this.updateButtonIcon(SettingKeyboard.Button_Cycle_Shiny, gamepadType, this.shinyIconElement, this.shinyLabel); this.updateButtonIcon(SettingKeyboard.Button_Cycle_Shiny, gamepadType, this.shinyIconElement, this.shinyLabel);
} }
@ -1936,16 +1943,51 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
} }
getFriendship(speciesId: number) { getFriendship(speciesId: number) {
let currentFriendship = globalScene.gameData.starterData[this.getStarterSpeciesId(speciesId)].friendship; let currentFriendship = globalScene.gameData.starterData[this.starterId].friendship;
if (!currentFriendship || currentFriendship === undefined) { if (!currentFriendship || currentFriendship === undefined) {
currentFriendship = 0; currentFriendship = 0;
} }
const friendshipCap = getStarterValueFriendshipCap(speciesStarterCosts[this.getStarterSpeciesId(speciesId)]); const friendshipCap = getStarterValueFriendshipCap(speciesStarterCosts[this.starterId]);
return { currentFriendship, friendshipCap }; 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() { setSpecies() {
const species = this.species; const species = this.species;
const starterAttributes : StarterAttributes | null = species ? { ...this.starterAttributes } : null; const starterAttributes : StarterAttributes | null = species ? { ...this.starterAttributes } : null;
@ -1967,88 +2009,10 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
if (species && (this.speciesStarterDexEntry?.seenAttr || this.isCaught())) { if (species && (this.speciesStarterDexEntry?.seenAttr || this.isCaught())) {
this.pokemonNumberText.setText(padInt(species.speciesId, 4)); this.pokemonNumberText.setText(padInt(species.speciesId, 4));
if (starterAttributes?.nickname) {
const name = decodeURIComponent(escape(atob(starterAttributes.nickname)));
this.pokemonNameText.setText(name);
} else {
this.pokemonNameText.setText(species.name);
}
if (this.isCaught()) { if (this.isCaught()) {
const colorScheme = starterColors[species.speciesId];
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);
//Growth translate
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);
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 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.pokemonFormText.setVisible(true);
if (pokemonPrevolutions.hasOwnProperty(species.speciesId)) {
this.pokemonCaughtHatchedContainer.setY(16);
this.pokemonShinyIcon.setY(135);
this.pokemonShinyIcon.setFrame(getVariantIcon(variant));
[
this.pokemonCandyContainer,
this.pokemonHatchedIcon,
this.pokemonHatchedCountText
].map(c => c.setVisible(false));
this.pokemonFormText.setY(25);
} else {
this.pokemonCaughtHatchedContainer.setY(25);
this.pokemonShinyIcon.setY(117);
this.pokemonCandyIcon.setTint(argbFromRgba(rgbHexToRgba(colorScheme[0])));
this.pokemonCandyOverlayIcon.setTint(argbFromRgba(rgbHexToRgba(colorScheme[1])));
this.pokemonCandyCountText.setText(`x${globalScene.gameData.starterData[this.getStarterSpeciesId(species.speciesId)].candyCount}`);
this.pokemonCandyContainer.setVisible(true);
this.pokemonFormText.setY(42);
this.pokemonHatchedIcon.setVisible(true);
this.pokemonHatchedCountText.setVisible(true);
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;
});
}
// Set default attributes if for some reason starterAttributes does not exist or attributes missing // Set default attributes if for some reason starterAttributes does not exist or attributes missing
const props: StarterAttributes = globalScene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr); const props: StarterAttributes = globalScene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr);
if (starterAttributes?.variant && !isNaN(starterAttributes.variant)) { if (starterAttributes?.variant && !isNaN(starterAttributes.variant)) {
@ -2065,12 +2029,6 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
female: props.female, female: props.female,
variant: props.variant ?? 0, variant: props.variant ?? 0,
}); });
if (this.isFormCaught(this.species, props.form)) {
const speciesForm = getPokemonSpeciesForm(species.speciesId, props.form ?? 0);
this.setTypeIcons(speciesForm.type1, speciesForm.type2);
this.pokemonSprite.clearTint();
}
} else { } else {
this.pokemonGrowthRateText.setText(""); this.pokemonGrowthRateText.setText("");
this.pokemonGrowthRateLabelText.setVisible(false); this.pokemonGrowthRateLabelText.setVisible(false);
@ -2092,7 +2050,6 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
formIndex: props.formIndex, formIndex: props.formIndex,
female: props.female, female: props.female,
variant: props.variant, variant: props.variant,
forSeen: true
}); });
this.pokemonSprite.setTint(0x808080); this.pokemonSprite.setTint(0x808080);
} }
@ -2123,7 +2080,6 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
setSpeciesDetails(species: PokemonSpecies, options: SpeciesDetails = {}, forceUpdate?: boolean): void { setSpeciesDetails(species: PokemonSpecies, options: SpeciesDetails = {}, forceUpdate?: boolean): void {
let { shiny, formIndex, female, variant } = options; let { shiny, formIndex, female, variant } = options;
const forSeen: boolean = options.forSeen ?? false;
const oldProps = species ? this.starterAttributes : null; const oldProps = species ? this.starterAttributes : null;
// We will only update the sprite if there is a change to form, shiny/variant // We will only update the sprite if there is a change to form, shiny/variant
@ -2194,12 +2150,12 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
} }
const isFormCaught = this.isFormCaught(); 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.shinyOverlay.setVisible(shiny ?? false); // TODO: is false the correct default?
this.pokemonNumberText.setColor(this.getTextColor(shiny ? TextStyle.SUMMARY_GOLD : TextStyle.SUMMARY, false)); this.pokemonNumberText.setColor(this.getTextColor(shiny ? TextStyle.SUMMARY_GOLD : TextStyle.SUMMARY, false));
this.pokemonNumberText.setShadowColor(this.getTextColor(shiny ? TextStyle.SUMMARY_GOLD : TextStyle.SUMMARY, true)); this.pokemonNumberText.setShadowColor(this.getTextColor(shiny ? TextStyle.SUMMARY_GOLD : TextStyle.SUMMARY, true));
const assetLoadCancelled = new BooleanHolder(false); const assetLoadCancelled = new BooleanHolder(false);
this.assetLoadCancelled = assetLoadCancelled; this.assetLoadCancelled = assetLoadCancelled;
@ -2221,13 +2177,6 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
this.pokemonSprite.setVisible(!this.statsMode); this.pokemonSprite.setVisible(!this.statsMode);
} }
const currentFilteredContainer = this.filteredStarterContainers.find(p => p.species.speciesId === species.speciesId);
if (currentFilteredContainer) {
const starterSprite = currentFilteredContainer.icon as Phaser.GameObjects.Sprite;
starterSprite.setTexture(species.getIconAtlasKey(formIndex, shiny, variant), species.getIconId(female!, formIndex, shiny, variant));
currentFilteredContainer.checkIconId(female, formIndex, shiny, variant);
}
const isNonShinyCaught = !!(caughtAttr & DexAttr.NON_SHINY); const isNonShinyCaught = !!(caughtAttr & DexAttr.NON_SHINY);
const isShinyCaught = !!(caughtAttr & DexAttr.SHINY); const isShinyCaught = !!(caughtAttr & DexAttr.SHINY);
@ -2250,27 +2199,129 @@ export default class PokedexPageUiHandler extends MessageUiHandler {
this.pokemonGenderText.setText(""); this.pokemonGenderText.setText("");
} }
if (caughtAttr) { // Setting the name
if (isFormCaught) { if (isFormCaught || isFormSeen) {
this.species.loadAssets(female!, formIndex, shiny, variant as Variant, true).then(() => { this.pokemonNameText.setText(species.name);
const crier = (this.species.forms && this.species.forms.length > 0) ? this.species.forms[formIndex ?? this.formIndex] : this.species; } else {
crier.cry(); this.pokemonNameText.setText(species ? "???" : "");
});
this.pokemonSprite.clearTint();
} else {
this.pokemonSprite.setTint(0x000000);
}
} }
if (caughtAttr || forSeen) { // 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.setY(135);
this.pokemonShinyIcon.setFrame(getVariantIcon(variant));
this.pokemonHatchedIcon.setVisible(false);
this.pokemonHatchedCountText.setVisible(false);
this.pokemonFormText.setY(36);
} else {
this.pokemonShinyIcon.setY(117);
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? const speciesForm = getPokemonSpeciesForm(species.speciesId, formIndex!); // TODO: is the bang correct?
this.setTypeIcons(speciesForm.type1, speciesForm.type2); this.setTypeIcons(speciesForm.type1, speciesForm.type2);
this.pokemonFormText.setText(this.getFormString((speciesForm as PokemonForm).formKey, species)); this.pokemonFormText.setText(this.getFormString((speciesForm as PokemonForm).formKey, species));
this.pokemonFormText.setVisible(true);
if (!isFormCaught) {
this.pokemonFormText.setY(18);
}
} else { } else {
this.setTypeIcons(null, null); this.setTypeIcons(null, null);
this.pokemonFormText.setText(""); this.pokemonFormText.setText("");
this.pokemonFormText.setVisible(false);
} }
} else { } else {
this.shinyOverlay.setVisible(false); this.shinyOverlay.setVisible(false);

View File

@ -11,7 +11,7 @@ import { allSpecies, getPokemonSpeciesForm, getPokerusStarters } from "#app/data
import { getStarterValueFriendshipCap, speciesStarterCosts, POKERUS_STARTER_COUNT } from "#app/data/balance/starters"; import { getStarterValueFriendshipCap, speciesStarterCosts, POKERUS_STARTER_COUNT } from "#app/data/balance/starters";
import { catchableSpecies } from "#app/data/balance/biomes"; import { catchableSpecies } from "#app/data/balance/biomes";
import { Type } from "#enums/type"; import { Type } from "#enums/type";
import type { DexAttrProps, DexEntry, StarterMoveset, StarterAttributes, StarterPreferences } from "#app/system/game-data"; import type { DexAttrProps, DexEntry, StarterAttributes, StarterPreferences } from "#app/system/game-data";
import { AbilityAttr, DexAttr, StarterPrefs } from "#app/system/game-data"; import { AbilityAttr, DexAttr, StarterPrefs } from "#app/system/game-data";
import MessageUiHandler from "#app/ui/message-ui-handler"; import MessageUiHandler from "#app/ui/message-ui-handler";
import PokemonIconAnimHandler, { PokemonIconAnimMode } from "#app/ui/pokemon-icon-anim-handler"; import PokemonIconAnimHandler, { PokemonIconAnimMode } from "#app/ui/pokemon-icon-anim-handler";
@ -19,7 +19,6 @@ import { TextStyle, addTextObject } from "#app/ui/text";
import { Mode } from "#app/ui/ui"; import { Mode } from "#app/ui/ui";
import { SettingKeyboard } from "#app/system/settings/settings-keyboard"; import { SettingKeyboard } from "#app/system/settings/settings-keyboard";
import { Passive as PassiveAttr } from "#enums/passive"; import { Passive as PassiveAttr } from "#enums/passive";
import type { Moves } from "#enums/moves";
import type { Species } from "#enums/species"; import type { Species } from "#enums/species";
import { Button } from "#enums/buttons"; import { Button } from "#enums/buttons";
import { DropDown, DropDownLabel, DropDownOption, DropDownState, DropDownType, SortCriteria } from "#app/ui/dropdown"; import { DropDown, DropDownLabel, DropDownOption, DropDownState, DropDownType, SortCriteria } from "#app/ui/dropdown";
@ -42,7 +41,6 @@ import { pokemonStarters } from "#app/data/balance/pokemon-evolutions";
import { Biome } from "#enums/biome"; import { Biome } from "#enums/biome";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
interface LanguageSetting { interface LanguageSetting {
starterInfoTextSize: string, starterInfoTextSize: string,
instructionTextSize: string, instructionTextSize: string,
@ -139,7 +137,6 @@ interface SpeciesDetails {
variant?: Variant, variant?: Variant,
abilityIndex?: number, abilityIndex?: number,
natureIndex?: number, natureIndex?: number,
forSeen?: boolean, // default = false
} }
export default class PokedexUiHandler extends MessageUiHandler { export default class PokedexUiHandler extends MessageUiHandler {
@ -161,7 +158,6 @@ export default class PokedexUiHandler extends MessageUiHandler {
private filterMode: boolean; private filterMode: boolean;
private filterBarCursor: number = 0; private filterBarCursor: number = 0;
private starterMoveset: StarterMoveset | null;
private scrollCursor: number; private scrollCursor: number;
private allSpecies: PokemonSpecies[] = []; private allSpecies: PokemonSpecies[] = [];
@ -169,7 +165,6 @@ export default class PokedexUiHandler extends MessageUiHandler {
private speciesLoaded: Map<Species, boolean> = new Map<Species, boolean>(); private speciesLoaded: Map<Species, boolean> = new Map<Species, boolean>();
private pokerusSpecies: PokemonSpecies[] = []; private pokerusSpecies: PokemonSpecies[] = [];
private speciesStarterDexEntry: DexEntry | null; private speciesStarterDexEntry: DexEntry | null;
private speciesStarterMoves: Moves[];
private assetLoadCancelled: BooleanHolder | null; private assetLoadCancelled: BooleanHolder | null;
public cursorObj: Phaser.GameObjects.Image; public cursorObj: Phaser.GameObjects.Image;
@ -206,6 +201,20 @@ export default class PokedexUiHandler extends MessageUiHandler {
private toggleDecorationsIconElement: Phaser.GameObjects.Sprite; private toggleDecorationsIconElement: Phaser.GameObjects.Sprite;
private toggleDecorationsLabel: Phaser.GameObjects.Text; private toggleDecorationsLabel: Phaser.GameObjects.Text;
private formTrayContainer: Phaser.GameObjects.Container;
private trayBg: Phaser.GameObjects.NineSlice;
private trayForms: PokemonForm[];
private trayContainers: PokedexMonContainer[] = [];
private trayNumIcons: number;
private trayRows: number;
private trayColumns: number;
private trayCursorObj: Phaser.GameObjects.Image;
private trayCursor: number = 0;
private showingTray: boolean = false;
private showFormTrayIconElement: Phaser.GameObjects.Sprite;
private showFormTrayLabel: Phaser.GameObjects.Text;
private canShowFormTray: boolean;
constructor() { constructor() {
super(Mode.POKEDEX); super(Mode.POKEDEX);
} }
@ -425,7 +434,6 @@ export default class PokedexUiHandler extends MessageUiHandler {
this.cursorObj = globalScene.add.image(0, 0, "select_cursor"); this.cursorObj = globalScene.add.image(0, 0, "select_cursor");
this.cursorObj.setOrigin(0, 0); this.cursorObj.setOrigin(0, 0);
starterBoxContainer.add(this.cursorObj); starterBoxContainer.add(this.cursorObj);
for (const species of allSpecies) { for (const species of allSpecies) {
@ -438,6 +446,20 @@ export default class PokedexUiHandler extends MessageUiHandler {
starterBoxContainer.add(pokemonContainer); starterBoxContainer.add(pokemonContainer);
} }
// Tray to display forms
this.formTrayContainer = globalScene.add.container(0, 0);
this.trayBg = addWindow(0, 0, 0, 0);
this.trayBg.setOrigin(0, 0);
this.formTrayContainer.add(this.trayBg);
this.trayCursorObj = globalScene.add.image(0, 0, "select_cursor");
this.trayCursorObj.setOrigin(0, 0);
this.formTrayContainer.add(this.trayCursorObj);
starterBoxContainer.add(this.formTrayContainer);
starterBoxContainer.bringToTop(this.formTrayContainer);
this.formTrayContainer.setVisible(false);
this.starterSelectContainer.add(starterBoxContainer); this.starterSelectContainer.add(starterBoxContainer);
this.pokemonSprite = globalScene.add.sprite(96, 143, "pkmn__sub"); this.pokemonSprite = globalScene.add.sprite(96, 143, "pkmn__sub");
@ -449,7 +471,7 @@ export default class PokedexUiHandler extends MessageUiHandler {
this.type1Icon.setOrigin(0, 0); this.type1Icon.setOrigin(0, 0);
this.starterSelectContainer.add(this.type1Icon); this.starterSelectContainer.add(this.type1Icon);
this.type2Icon = globalScene.add.sprite(10, 166, getLocalizedSpriteKey("types")); this.type2Icon = globalScene.add.sprite(28, 158, getLocalizedSpriteKey("types"));
this.type2Icon.setScale(0.5); this.type2Icon.setScale(0.5);
this.type2Icon.setOrigin(0, 0); this.type2Icon.setOrigin(0, 0);
this.starterSelectContainer.add(this.type2Icon); this.starterSelectContainer.add(this.type2Icon);
@ -488,6 +510,17 @@ export default class PokedexUiHandler extends MessageUiHandler {
this.starterSelectContainer.add(this.toggleDecorationsIconElement); this.starterSelectContainer.add(this.toggleDecorationsIconElement);
this.starterSelectContainer.add(this.toggleDecorationsLabel); this.starterSelectContainer.add(this.toggleDecorationsLabel);
this.showFormTrayIconElement = new Phaser.GameObjects.Sprite(globalScene, 6, 168, "keyboard", "F.png");
this.showFormTrayIconElement.setName("sprite-showFormTray-icon-element");
this.showFormTrayIconElement.setScale(0.675);
this.showFormTrayIconElement.setOrigin(0.0, 0.0);
this.showFormTrayLabel = addTextObject(16, 168, i18next.t("pokedexUiHandler:showForms"), TextStyle.PARTY, { fontSize: instructionTextSize });
this.showFormTrayLabel.setName("text-showFormTray-label");
this.showFormTrayIconElement.setVisible(false);
this.showFormTrayLabel.setVisible(false);
this.starterSelectContainer.add(this.showFormTrayIconElement);
this.starterSelectContainer.add(this.showFormTrayLabel);
this.message = addTextObject(8, 8, "", TextStyle.WINDOW, { maxLines: 2 }); this.message = addTextObject(8, 8, "", TextStyle.WINDOW, { maxLines: 2 });
this.message.setOrigin(0, 0); this.message.setOrigin(0, 0);
this.starterSelectMessageBoxContainer.add(this.message); this.starterSelectMessageBoxContainer.add(this.message);
@ -527,7 +560,7 @@ export default class PokedexUiHandler extends MessageUiHandler {
this.starterPreferences[species.speciesId] = this.initStarterPrefs(species); this.starterPreferences[species.speciesId] = this.initStarterPrefs(species);
if (dexEntry.caughtAttr) { if (dexEntry.caughtAttr || globalScene.dexForDevs) {
icon.clearTint(); icon.clearTint();
} else if (dexEntry.seenAttr) { } else if (dexEntry.seenAttr) {
icon.setTint(0x808080); icon.setTint(0x808080);
@ -860,32 +893,42 @@ export default class PokedexUiHandler extends MessageUiHandler {
} else if (this.filterTextMode && !(this.filterText.getValue(this.filterTextCursor) === this.filterText.defaultText)) { } else if (this.filterTextMode && !(this.filterText.getValue(this.filterTextCursor) === this.filterText.defaultText)) {
this.filterText.resetSelection(this.filterTextCursor); this.filterText.resetSelection(this.filterTextCursor);
success = true; success = true;
} else if (this.showingTray) {
success = this.closeFormTray();
} else { } else {
this.tryExit(); this.tryExit();
success = true; success = true;
} }
} else if (button === Button.STATS) { } else if (button === Button.STATS) {
if (!this.filterMode) { if (!this.filterMode && !this.showingTray) {
this.cursorObj.setVisible(false); this.cursorObj.setVisible(false);
this.setSpecies(null); this.setSpecies(null);
this.filterText.cursorObj.setVisible(false); this.filterText.cursorObj.setVisible(false);
this.filterTextMode = false; this.filterTextMode = false;
this.filterBarCursor = 0; this.filterBarCursor = 0;
this.setFilterMode(true); this.setFilterMode(true);
} else {
error = true;
} }
} else if (button === Button.V) { } else if (button === Button.V) {
if (!this.filterTextMode) { if (!this.filterTextMode && !this.showingTray) {
this.cursorObj.setVisible(false); this.cursorObj.setVisible(false);
this.setSpecies(null); this.setSpecies(null);
this.filterBar.cursorObj.setVisible(false); this.filterBar.cursorObj.setVisible(false);
this.filterMode = false; this.filterMode = false;
this.filterTextCursor = 0; this.filterTextCursor = 0;
this.setFilterTextMode(true); this.setFilterTextMode(true);
} else {
error = true;
} }
} else if (button === Button.CYCLE_SHINY) { } else if (button === Button.CYCLE_SHINY) {
this.showDecorations = !this.showDecorations; if (!this.showingTray) {
this.updateScroll(); this.showDecorations = !this.showDecorations;
success = true; this.updateScroll();
success = true;
} else {
error = true;
}
} else if (this.filterMode) { } else if (this.filterMode) {
switch (button) { switch (button) {
case Button.LEFT: case Button.LEFT:
@ -982,8 +1025,55 @@ export default class PokedexUiHandler extends MessageUiHandler {
success = true; success = true;
break; break;
} }
} else if (this.showingTray) {
if (button === Button.ACTION) {
const formIndex = this.trayForms[this.trayCursor].formIndex;
ui.setOverlayMode(Mode.POKEDEX_PAGE, this.lastSpecies, formIndex, { form: formIndex });
success = true;
} else {
const numberOfForms = this.trayContainers.length;
const numOfRows = Math.ceil(numberOfForms / maxColumns);
const currentRow = Math.floor(this.trayCursor / maxColumns);
switch (button) {
case Button.UP:
if (currentRow > 0) {
success = this.setTrayCursor(this.trayCursor - 9);
} else {
const targetCol = this.trayCursor;
if (numberOfForms % 9 > targetCol) {
success = this.setTrayCursor(numberOfForms - (numberOfForms) % 9 + targetCol);
} else {
success = this.setTrayCursor(Math.max(numberOfForms - (numberOfForms) % 9 + targetCol - 9, 0));
}
}
break;
case Button.DOWN:
if (currentRow < numOfRows - 1) {
success = this.setTrayCursor(this.trayCursor + 9);
} else {
success = this.setTrayCursor(this.trayCursor % 9);
}
break;
case Button.LEFT:
if (this.trayCursor % 9 !== 0) {
success = this.setTrayCursor(this.trayCursor - 1);
} else {
success = this.setTrayCursor(currentRow < numOfRows - 1 ? (currentRow + 1) * maxColumns - 1 : numberOfForms - 1);
}
break;
case Button.RIGHT:
if (this.trayCursor % 9 < (currentRow < numOfRows - 1 ? 8 : (numberOfForms - 1) % 9)) {
success = this.setTrayCursor(this.trayCursor + 1);
} else {
success = this.setTrayCursor(currentRow * 9);
}
break;
case Button.CYCLE_FORM:
success = this.closeFormTray();
break;
}
}
} else { } else {
if (button === Button.ACTION) { if (button === Button.ACTION) {
ui.setOverlayMode(Mode.POKEDEX_PAGE, this.lastSpecies, 0); ui.setOverlayMode(Mode.POKEDEX_PAGE, this.lastSpecies, 0);
success = true; success = true;
@ -1042,6 +1132,12 @@ export default class PokedexUiHandler extends MessageUiHandler {
success = true; success = true;
} }
break; break;
case Button.CYCLE_FORM:
const species = this.filteredPokemonContainers[this.cursor].species;
if (this.canShowFormTray) {
success = this.openFormTray(species);
}
break;
} }
} }
} }
@ -1068,6 +1164,9 @@ export default class PokedexUiHandler extends MessageUiHandler {
case SettingKeyboard.Button_Cycle_Variant: case SettingKeyboard.Button_Cycle_Variant:
iconPath = "V.png"; iconPath = "V.png";
break; break;
case SettingKeyboard.Button_Cycle_Form:
iconPath = "F.png";
break;
case SettingKeyboard.Button_Stats: case SettingKeyboard.Button_Stats:
iconPath = "C.png"; iconPath = "C.png";
break; break;
@ -1145,13 +1244,15 @@ export default class PokedexUiHandler extends MessageUiHandler {
this.validPokemonContainers.forEach(container => { this.validPokemonContainers.forEach(container => {
container.setVisible(false); container.setVisible(false);
container.cost = globalScene.gameData.getSpeciesStarterValue(this.getStarterSpeciesId(container.species.speciesId)); const starterId = this.getStarterSpeciesId(container.species.speciesId);
container.cost = globalScene.gameData.getSpeciesStarterValue(starterId);
// First, ensure you have the caught attributes for the species else default to bigint 0 // First, ensure you have the caught attributes for the species else default to bigint 0
// TODO: This might be removed depending on how accessible we want the pokedex function to be // TODO: This might be removed depending on how accessible we want the pokedex function to be
const caughtAttr = globalScene.gameData.dexData[container.species.speciesId]?.caughtAttr || BigInt(0); const caughtAttr = globalScene.gameData.dexData[container.species.speciesId]?.caughtAttr || BigInt(0);
const starterData = globalScene.gameData.starterData[this.getStarterSpeciesId(container.species.speciesId)]; const starterData = globalScene.gameData.starterData[starterId];
const isStarterProgressable = speciesEggMoves.hasOwnProperty(this.getStarterSpeciesId(container.species.speciesId)); const isStarterProgressable = speciesEggMoves.hasOwnProperty(starterId);
// Name filter // Name filter
const selectedName = this.filterText.getValue(FilterTextRow.NAME); const selectedName = this.filterText.getValue(FilterTextRow.NAME);
@ -1162,8 +1263,8 @@ export default class PokedexUiHandler extends MessageUiHandler {
// On the other hand, in some cases it is possible to switch between different forms and combine (Deoxys) // On the other hand, in some cases it is possible to switch between different forms and combine (Deoxys)
const levelMoves = pokemonSpeciesLevelMoves[container.species.speciesId].map(m => allMoves[m[1]].name); const levelMoves = pokemonSpeciesLevelMoves[container.species.speciesId].map(m => allMoves[m[1]].name);
// This always gets egg moves from the starter // This always gets egg moves from the starter
const eggMoves = speciesEggMoves[this.getStarterSpeciesId(container.species.speciesId)]?.map(m => allMoves[m].name) ?? []; const eggMoves = speciesEggMoves[starterId]?.map(m => allMoves[m].name) ?? [];
const tmMoves = speciesTmMoves[this.getStarterSpeciesId(container.species.speciesId)]?.map(m => allMoves[Array.isArray(m) ? m[1] : m].name) ?? []; const tmMoves = speciesTmMoves[starterId]?.map(m => allMoves[Array.isArray(m) ? m[1] : m].name) ?? [];
const selectedMove1 = this.filterText.getValue(FilterTextRow.MOVE_1); const selectedMove1 = this.filterText.getValue(FilterTextRow.MOVE_1);
const selectedMove2 = this.filterText.getValue(FilterTextRow.MOVE_2); const selectedMove2 = this.filterText.getValue(FilterTextRow.MOVE_2);
@ -1185,27 +1286,40 @@ export default class PokedexUiHandler extends MessageUiHandler {
container.tmMove2Icon.setVisible(false); container.tmMove2Icon.setVisible(false);
if (fitsEggMove1 && !fitsLevelMove1) { if (fitsEggMove1 && !fitsLevelMove1) {
container.eggMove1Icon.setVisible(true); container.eggMove1Icon.setVisible(true);
const em1 = eggMoves.findIndex(name => name === selectedMove1);
if ((starterData[starterId].eggMoves & (1 << em1)) === 0) {
container.eggMove1Icon.setTint(0x808080);
} else {
container.eggMove1Icon.clearTint();
}
} else if (fitsTmMove1 && !fitsLevelMove1) { } else if (fitsTmMove1 && !fitsLevelMove1) {
container.tmMove1Icon.setVisible(true); container.tmMove1Icon.setVisible(true);
} }
if (fitsEggMove2 && !fitsLevelMove2) { if (fitsEggMove2 && !fitsLevelMove2) {
container.eggMove2Icon.setVisible(true); container.eggMove2Icon.setVisible(true);
const em2 = eggMoves.findIndex(name => name === selectedMove2);
if ((starterData[starterId].eggMoves & (1 << em2)) === 0) {
container.eggMove2Icon.setTint(0x808080);
} else {
container.eggMove2Icon.clearTint();
}
} else if (fitsTmMove2 && !fitsLevelMove2) { } else if (fitsTmMove2 && !fitsLevelMove2) {
container.tmMove2Icon.setVisible(true); container.tmMove2Icon.setVisible(true);
} }
// Ability filter // Ability filter
const abilities = [ container.species.ability1, container.species.ability2, container.species.abilityHidden ].map(a => allAbilities[a].name); const abilities = [ container.species.ability1, container.species.ability2, container.species.abilityHidden ].map(a => allAbilities[a].name);
const passives = starterPassiveAbilities[this.getStarterSpeciesId(container.species.speciesId)] ?? {} as PassiveAbilities; const passives = starterPassiveAbilities[starterId] ?? {} as PassiveAbilities;
const selectedAbility1 = this.filterText.getValue(FilterTextRow.ABILITY_1); const selectedAbility1 = this.filterText.getValue(FilterTextRow.ABILITY_1);
const fitsFormAbility = container.species.forms.some(form => allAbilities[form.ability1].name === selectedAbility1); const fitsFormAbility1 = container.species.forms.some(form => [ form.ability1, form.ability2, form.abilityHidden ].map(a => allAbilities[a].name).includes(selectedAbility1));
const fitsAbility1 = abilities.includes(selectedAbility1) || fitsFormAbility || selectedAbility1 === this.filterText.defaultText; const fitsAbility1 = abilities.includes(selectedAbility1) || fitsFormAbility1 || selectedAbility1 === this.filterText.defaultText;
const fitsPassive1 = Object.values(passives).some(p => p.name === selectedAbility1); const fitsPassive1 = Object.values(passives).some(p => allAbilities[p].name === selectedAbility1);
const selectedAbility2 = this.filterText.getValue(FilterTextRow.ABILITY_2); const selectedAbility2 = this.filterText.getValue(FilterTextRow.ABILITY_2);
const fitsAbility2 = abilities.includes(selectedAbility2) || fitsFormAbility || selectedAbility2 === this.filterText.defaultText; const fitsFormAbility2 = container.species.forms.some(form => [ form.ability1, form.ability2, form.abilityHidden ].map(a => allAbilities[a].name).includes(selectedAbility2));
const fitsPassive2 = Object.values(passives).some(p => p.name === selectedAbility2); const fitsAbility2 = abilities.includes(selectedAbility2) || fitsFormAbility2 || selectedAbility2 === this.filterText.defaultText;
const fitsPassive2 = Object.values(passives).some(p => allAbilities[p].name === selectedAbility2);
// If both fields have been set to the same ability, show both ability and passive // If both fields have been set to the same ability, show both ability and passive
const fitsAbilities = (fitsAbility1 && (fitsPassive2 || selectedAbility2 === this.filterText.defaultText)) || const fitsAbilities = (fitsAbility1 && (fitsPassive2 || selectedAbility2 === this.filterText.defaultText)) ||
@ -1213,11 +1327,26 @@ export default class PokedexUiHandler extends MessageUiHandler {
container.passive1Icon.setVisible(false); container.passive1Icon.setVisible(false);
container.passive2Icon.setVisible(false); container.passive2Icon.setVisible(false);
if (fitsPassive1) { if (fitsPassive1 || fitsPassive2) {
container.passive1Icon.setVisible(true); if (fitsPassive1) {
} if (starterData.passiveAttr > 0) {
if (fitsPassive2) { container.passive1Icon.clearTint();
container.passive2Icon.setVisible(true); container.passive1OverlayIcon.clearTint();
} else {
container.passive1Icon.setTint(0x808080);
container.passive1OverlayIcon.setTint(0x808080);
}
container.passive1Icon.setVisible(true);
} else {
if (starterData.passiveAttr > 0) {
container.passive2Icon.clearTint();
container.passive2OverlayIcon.clearTint();
} else {
container.passive2Icon.setTint(0x808080);
container.passive2OverlayIcon.setTint(0x808080);
}
container.passive2Icon.setVisible(true);
}
} }
// Gen filter // Gen filter
@ -1236,7 +1365,7 @@ export default class PokedexUiHandler extends MessageUiHandler {
// We get biomes for both the mon and its starters to ensure that evolutions get the correct filters. // We get biomes for both the mon and its starters to ensure that evolutions get the correct filters.
// TODO: We might also need to do it the other way around. // TODO: We might also need to do it the other way around.
const biomes = catchableSpecies[container.species.speciesId].concat(catchableSpecies[this.getStarterSpeciesId(container.species.speciesId)]).map(b => Biome[b.biome]); const biomes = catchableSpecies[container.species.speciesId].concat(catchableSpecies[starterId]).map(b => Biome[b.biome]);
if (biomes.length === 0) { if (biomes.length === 0) {
biomes.push("Uncatchable"); biomes.push("Uncatchable");
} }
@ -1530,6 +1659,8 @@ export default class PokedexUiHandler extends MessageUiHandler {
this.cursorObj.setVisible(!filterMode); this.cursorObj.setVisible(!filterMode);
this.filterBar.cursorObj.setVisible(filterMode); this.filterBar.cursorObj.setVisible(filterMode);
this.pokemonSprite.setVisible(false); this.pokemonSprite.setVisible(false);
this.showFormTrayIconElement.setVisible(false);
this.showFormTrayLabel.setVisible(false);
if (filterMode !== this.filterMode) { if (filterMode !== this.filterMode) {
this.filterMode = filterMode; this.filterMode = filterMode;
@ -1546,6 +1677,8 @@ export default class PokedexUiHandler extends MessageUiHandler {
this.cursorObj.setVisible(!filterTextMode); this.cursorObj.setVisible(!filterTextMode);
this.filterText.cursorObj.setVisible(filterTextMode); this.filterText.cursorObj.setVisible(filterTextMode);
this.pokemonSprite.setVisible(false); this.pokemonSprite.setVisible(false);
this.showFormTrayIconElement.setVisible(false);
this.showFormTrayLabel.setVisible(false);
if (filterTextMode !== this.filterTextMode) { if (filterTextMode !== this.filterTextMode) {
this.filterTextMode = filterTextMode; this.filterTextMode = filterTextMode;
@ -1558,6 +1691,101 @@ export default class PokedexUiHandler extends MessageUiHandler {
return false; return false;
} }
openFormTray(species: PokemonSpecies): boolean {
this.trayForms = species.forms;
this.trayNumIcons = this.trayForms.length;
this.trayRows = Math.floor(this.trayNumIcons / 9) + (this.trayNumIcons % 9 === 0 ? 0 : 1);
this.trayColumns = Math.min(this.trayNumIcons, 9);
const maxColumns = 9;
const onScreenFirstIndex = this.scrollCursor * maxColumns;
const boxCursor = this.cursor - onScreenFirstIndex;
const boxCursorY = Math.floor(boxCursor / maxColumns);
const boxCursorX = boxCursor - boxCursorY * 9;
const spaceBelow = 9 - 1 - boxCursorY;
const spaceRight = 9 - boxCursorX;
const boxPos = calcStarterPosition(this.cursor, this.scrollCursor);
const goUp = this.trayRows <= spaceBelow - 1 ? 0 : 1;
const goLeft = this.trayColumns <= spaceRight ? 0 : 1;
this.trayBg.setSize(13 + this.trayColumns * 17, 8 + this.trayRows * 18);
this.formTrayContainer.setX(
(goLeft ? boxPos.x - 18 * (this.trayColumns - spaceRight) : boxPos.x) - 3
);
this.formTrayContainer.setY(
goUp ? boxPos.y - this.trayBg.height : boxPos.y + 17
);
const dexEntry = globalScene.gameData.dexData[species.speciesId];
const dexAttr = this.getCurrentDexProps(species.speciesId);
const props = this.getSanitizedProps(globalScene.gameData.getSpeciesDexAttrProps(this.lastSpecies, dexAttr));
this.trayContainers = [];
this.trayForms.map((f, index) => {
const isFormCaught = dexEntry ? (dexEntry.caughtAttr & globalScene.gameData.getFormAttr(f.formIndex ?? 0)) > 0n : false;
const isFormSeen = dexEntry ? (dexEntry.seenAttr & globalScene.gameData.getFormAttr(f.formIndex ?? 0)) > 0n : false;
const formContainer = new PokedexMonContainer(species, { formIndex: f.formIndex, female: props.female, shiny: props.shiny, variant: props.variant });
this.iconAnimHandler.addOrUpdate(formContainer.icon, PokemonIconAnimMode.NONE);
// Setting tint, for all saves some caught forms may only show up as seen
if (isFormCaught || globalScene.dexForDevs) {
formContainer.icon.clearTint();
} else if (isFormSeen) {
formContainer.icon.setTint(0x808080);
}
formContainer.setPosition(5 + (index % 9) * 18, 4 + Math.floor(index / 9) * 17);
this.formTrayContainer.add(formContainer);
this.trayContainers.push(formContainer);
});
this.showingTray = true;
this.setTrayCursor(0);
this.formTrayContainer.setVisible(true);
this.showFormTrayIconElement.setVisible(false);
this.showFormTrayLabel.setVisible(false);
return true;
}
closeFormTray(): boolean {
this.trayContainers.forEach(obj => {
this.formTrayContainer.remove(obj, true); // Removes from container and destroys it
});
this.trayContainers = [];
this.formTrayContainer.setVisible(false);
this.showingTray = false;
this.setSpeciesDetails(this.lastSpecies);
return true;
}
setTrayCursor(cursor: number): boolean {
if (!this.showingTray) {
return false;
}
cursor = Phaser.Math.Clamp(this.trayContainers.length - 1, cursor, 0);
const changed = this.trayCursor !== cursor;
if (changed) {
this.trayCursor = cursor;
}
this.trayCursorObj.setPosition(5 + (cursor % 9) * 18, 4 + Math.floor(cursor / 9) * 17);
const species = this.lastSpecies;
const formIndex = this.trayForms[cursor].formIndex;
this.setSpeciesDetails(species, { formIndex: formIndex });
return changed;
}
getFriendship(speciesId: number) { getFriendship(speciesId: number) {
let currentFriendship = globalScene.gameData.starterData[this.getStarterSpeciesId(speciesId)].friendship; let currentFriendship = globalScene.gameData.starterData[this.getStarterSpeciesId(speciesId)].friendship;
if (!currentFriendship || currentFriendship === undefined) { if (!currentFriendship || currentFriendship === undefined) {
@ -1592,13 +1820,13 @@ export default class PokedexUiHandler extends MessageUiHandler {
this.lastSpecies = species!; // TODO: is this bang correct? this.lastSpecies = species!; // TODO: is this bang correct?
if (species && (this.speciesStarterDexEntry?.seenAttr || this.speciesStarterDexEntry?.caughtAttr)) { if (species && (this.speciesStarterDexEntry?.seenAttr || this.speciesStarterDexEntry?.caughtAttr || globalScene.dexForDevs)) {
this.pokemonNumberText.setText(i18next.t("pokedexUiHandler:pokemonNumber") + padInt(species.speciesId, 4)); this.pokemonNumberText.setText(i18next.t("pokedexUiHandler:pokemonNumber") + padInt(species.speciesId, 4));
this.pokemonNameText.setText(species.name); this.pokemonNameText.setText(species.name);
if (this.speciesStarterDexEntry?.caughtAttr) { if (this.speciesStarterDexEntry?.caughtAttr || globalScene.dexForDevs) {
// Pause the animation when the species is selected // Pause the animation when the species is selected
const speciesIndex = this.allSpecies.indexOf(species); const speciesIndex = this.allSpecies.indexOf(species);
@ -1627,9 +1855,7 @@ export default class PokedexUiHandler extends MessageUiHandler {
this.type1Icon.setVisible(true); this.type1Icon.setVisible(true);
this.type2Icon.setVisible(true); this.type2Icon.setVisible(true);
this.setSpeciesDetails(species, { this.setSpeciesDetails(species);
forSeen: true
});
this.pokemonSprite.setTint(0x808080); this.pokemonSprite.setTint(0x808080);
} }
} else { } else {
@ -1646,7 +1872,6 @@ export default class PokedexUiHandler extends MessageUiHandler {
setSpeciesDetails(species: PokemonSpecies, options: SpeciesDetails = {}): void { setSpeciesDetails(species: PokemonSpecies, options: SpeciesDetails = {}): void {
let { shiny, formIndex, female, variant } = options; let { shiny, formIndex, female, variant } = options;
const forSeen: boolean = options.forSeen ?? false;
// We will only update the sprite if there is a change to form, shiny/variant // We will only update the sprite if there is a change to form, shiny/variant
// or gender for species with gender sprite differences // or gender for species with gender sprite differences
@ -1667,34 +1892,33 @@ export default class PokedexUiHandler extends MessageUiHandler {
this.assetLoadCancelled = null; this.assetLoadCancelled = null;
} }
this.starterMoveset = null;
this.speciesStarterMoves = [];
if (species) { if (species) {
const dexEntry = globalScene.gameData.dexData[species.speciesId]; const dexEntry = globalScene.gameData.dexData[species.speciesId];
if (!dexEntry.caughtAttr) { if (!dexEntry.caughtAttr) {
const props = this.getSanitizedProps(globalScene.gameData.getSpeciesDexAttrProps(species, this.getCurrentDexProps(species.speciesId))); const props = this.getSanitizedProps(globalScene.gameData.getSpeciesDexAttrProps(species, this.getCurrentDexProps(species.speciesId)));
if (shiny === undefined || shiny !== props.shiny) { if (shiny === undefined) {
shiny = props.shiny; shiny = props.shiny;
} }
if (formIndex === undefined || formIndex !== props.formIndex) { if (formIndex === undefined) {
formIndex = props.formIndex; formIndex = props.formIndex;
} }
if (female === undefined || female !== props.female) { if (female === undefined) {
female = props.female; female = props.female;
} }
if (variant === undefined || variant !== props.variant) { if (variant === undefined) {
variant = props.variant; variant = props.variant;
} }
} }
const isFormCaught = dexEntry ? (dexEntry.caughtAttr & globalScene.gameData.getFormAttr(formIndex ?? 0)) > 0n : false;
const isFormSeen = dexEntry ? (dexEntry.seenAttr & globalScene.gameData.getFormAttr(formIndex ?? 0)) > 0n : false;
const assetLoadCancelled = new BooleanHolder(false); const assetLoadCancelled = new BooleanHolder(false);
this.assetLoadCancelled = assetLoadCancelled; this.assetLoadCancelled = assetLoadCancelled;
if (shouldUpdateSprite) { if (shouldUpdateSprite) {
species.loadAssets(female!, formIndex, shiny, variant, true).then(() => { // TODO: is this bang correct? species.loadAssets(female!, formIndex, shiny, variant, true).then(() => { // TODO: is this bang correct?
if (assetLoadCancelled.value) { if (assetLoadCancelled.value) {
return; return;
@ -1711,21 +1935,37 @@ export default class PokedexUiHandler extends MessageUiHandler {
this.pokemonSprite.setVisible(!(this.filterMode || this.filterTextMode)); this.pokemonSprite.setVisible(!(this.filterMode || this.filterTextMode));
} }
if (dexEntry.caughtAttr || forSeen) { if (isFormCaught || globalScene.dexForDevs) {
this.pokemonSprite.clearTint();
} else if (isFormSeen) {
this.pokemonSprite.setTint(0x808080);
} else {
this.pokemonSprite.setTint(0);
}
if (isFormCaught || isFormSeen || globalScene.dexForDevs) {
const speciesForm = getPokemonSpeciesForm(species.speciesId, 0); // TODO: always selecting the first form const speciesForm = getPokemonSpeciesForm(species.speciesId, 0); // TODO: always selecting the first form
this.setTypeIcons(speciesForm.type1, speciesForm.type2); this.setTypeIcons(speciesForm.type1, speciesForm.type2);
} else { } else {
this.setTypeIcons(null, null); this.setTypeIcons(null, null);
} }
if (species?.forms?.length > 1) {
if (!this.showingTray) {
this.showFormTrayIconElement.setVisible(true);
this.showFormTrayLabel.setVisible(true);
}
this.canShowFormTray = true;
} else {
this.showFormTrayIconElement.setVisible(false);
this.showFormTrayLabel.setVisible(false);
this.canShowFormTray = false;
}
} else { } else {
this.setTypeIcons(null, null); this.setTypeIcons(null, null);
} }
if (!this.starterMoveset) {
this.starterMoveset = this.speciesStarterMoves.slice(0, 4) as StarterMoveset;
}
} }
setTypeIcons(type1: Type | null, type2: Type | null): void { setTypeIcons(type1: Type | null, type2: Type | null): void {
@ -1784,7 +2024,6 @@ export default class PokedexUiHandler extends MessageUiHandler {
ui.showText(i18next.t("pokedexUiHandler:confirmExit"), null, () => { ui.showText(i18next.t("pokedexUiHandler:confirmExit"), null, () => {
ui.setModeWithoutClear(Mode.CONFIRM, () => { ui.setModeWithoutClear(Mode.CONFIRM, () => {
ui.setMode(Mode.POKEDEX, "refresh"); ui.setMode(Mode.POKEDEX, "refresh");
globalScene.clearPhaseQueue();
this.clearText(); this.clearText();
this.clear(); this.clear();
ui.revertMode(); ui.revertMode();

View File

@ -1981,8 +1981,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
female: starterAttributes.female female: starterAttributes.female
}; };
ui.setOverlayMode(Mode.POKEDEX_PAGE, this.lastSpecies, starterAttributes.form, attributes); ui.setOverlayMode(Mode.POKEDEX_PAGE, this.lastSpecies, starterAttributes.form, attributes);
return true;
}); });
return true;
} }
}); });
options.push({ options.push({