[UI/UX][Refactor] Starter select UI refactor (#6390)

* Moved some functions to utility file

* Moved some process input code to its own functions

* Moving processinput for cycle buttons to its own function

* Moved pokemon menu to its own function

* Replaced setSpecies(null) with setNoSpecies (still some code duplication)

* Hide sprite when moving to random starter button

* Renamed `StarterAttributes` to `StarterPreferences` and related changes

* Cleaned up redundancy; no need to set ivs to `null` since the container is hidden anyway

* Refactoring caughtAttr / seenAttr / no switch in `setSpecies`

* Reducing complexity

* Setting sprite correctly for seen mons

* Extracting logic for seen species from setSpeciesDetails

* Separate setup function for filter bar and instruction buttons

* Moved preferences and other info to their own containers

* Setup function for starter column

* Reduced number of containers; cursor is broken

* Fixed cursor movement

* ??= {}

* Split up statistics from permanent info

* Removed "forSeen" variable

* Function which updates displayed moves

* updateCanCycle function

* set passive details to its own function

* Removed pointless conditionals from setSpeciesDetails

* Added some comments

* Small adjustments

* Splitting up starter summary

* Finished splitting off starter summary (minor bugs)

* Added some comments

* Introduced `getSpeciesPropsFromPreferences` utility function

* Moved some constants and functions around

* Moved ssui to handlers folder

* Also moved some other files

* Formatting

* Fixed wrong input

* Fixed another broken import
This commit is contained in:
Wlowscha 2025-09-08 13:36:22 +02:00 committed by GitHub
parent a55994bc0a
commit 5a803f80eb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 3270 additions and 3110 deletions

View File

@ -107,3 +107,8 @@ export const FAKE_TITLE_LOGO_CHANCE = 10000;
* Using rare candies will never increase friendship beyond this value.
*/
export const RARE_CANDY_FRIENDSHIP_CAP = 200;
/**
* The maximum number of cost reduction upgrades that can be bought with candy.
*/
export const VALUE_REDUCTION_MAX = 2;

View File

@ -167,7 +167,7 @@ export interface StarterMoveData {
[key: number]: StarterMoveset | StarterFormMoveData;
}
export interface StarterAttributes {
export interface StarterPreferences {
nature?: number;
ability?: number;
variant?: number;
@ -2104,6 +2104,20 @@ export class GameData {
return ret;
}
getSpeciesDefaultDexAttrProps(): DexAttrProps {
const shiny = false;
const female = false;
const variant = 0;
const formIndex = 0;
return {
shiny,
female,
variant,
formIndex,
};
}
getSpeciesDexAttrProps(_species: PokemonSpecies, dexAttr: bigint): DexAttrProps {
const shiny = !(dexAttr & DexAttr.NON_SHINY);
const female = !(dexAttr & DexAttr.MALE);

View File

@ -3,7 +3,7 @@ import type { InputsController } from "#app/inputs-controller";
import { Button } from "#enums/buttons";
import { UiMode } from "#enums/ui-mode";
import { Setting, SettingKeys, settingIndex } from "#system/settings";
import { PokedexPageUiHandler } from "#ui/containers/pokedex-page-ui-handler";
import { PokedexPageUiHandler } from "#ui/handlers/pokedex-page-ui-handler";
import type { MessageUiHandler } from "#ui/handlers/message-ui-handler";
import { PokedexUiHandler } from "#ui/handlers/pokedex-ui-handler";
import { RunInfoUiHandler } from "#ui/handlers/run-info-ui-handler";

View File

@ -1,6 +1,7 @@
import { globalScene } from "#app/global-scene";
import type { PokemonSpecies } from "#data/pokemon-species";
import { TextStyle } from "#enums/text-style";
import type { DexAttrProps } from "#system/game-data";
import { addTextObject } from "#ui/text";
export class StarterContainer extends Phaser.GameObjects.Container {
@ -19,10 +20,9 @@ export class StarterContainer extends Phaser.GameObjects.Container {
constructor(species: PokemonSpecies) {
super(globalScene, 0, 0);
this.species = species;
const defaultDexAttr = globalScene.gameData.getSpeciesDefaultDexAttr(species, false, true);
const defaultProps = globalScene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr);
this.setSpecies(species, defaultProps);
// starter passive bg
const starterPassiveBg = globalScene.add.image(2, 5, "passive_bg");
@ -32,21 +32,6 @@ export class StarterContainer extends Phaser.GameObjects.Container {
this.add(starterPassiveBg);
this.starterPassiveBgs = starterPassiveBg;
// icon
this.icon = globalScene.add.sprite(
-2,
2,
species.getIconAtlasKey(defaultProps.formIndex, defaultProps.shiny, defaultProps.variant),
);
this.icon.setScale(0.5);
this.icon.setOrigin(0, 0);
this.icon.setFrame(
species.getIconId(defaultProps.female, defaultProps.formIndex, defaultProps.shiny, defaultProps.variant),
);
this.checkIconId(defaultProps.female, defaultProps.formIndex, defaultProps.shiny, defaultProps.variant);
this.icon.setTint(0);
this.add(this.icon);
// shiny icons
for (let i = 0; i < 3; i++) {
const shinyIcon = globalScene.add.image(i * -3 + 12, 2, "shiny_star_small");
@ -108,6 +93,38 @@ export class StarterContainer extends Phaser.GameObjects.Container {
this.candyUpgradeOverlayIcon = candyUpgradeOverlayIcon;
}
setSpecies(species: PokemonSpecies, props: DexAttrProps) {
this.species = species;
const { shiny, formIndex, female, variant } = props;
if (this.icon) {
this.remove(this.icon);
this.icon.destroy(); // Properly removes the sprite from memory
}
// icon
this.icon = globalScene.add.sprite(-2, 2, species.getIconAtlasKey(formIndex, shiny, variant));
this.icon.setScale(0.5);
this.icon.setOrigin(0, 0);
this.icon.setFrame(species.getIconId(female, formIndex, shiny, variant));
this.checkIconId(female, formIndex, shiny, variant);
this.icon.setTint(0);
this.add(this.icon);
[
this.hiddenAbilityIcon,
this.favoriteIcon,
this.classicWinIcon,
this.candyUpgradeIcon,
this.candyUpgradeOverlayIcon,
].forEach(icon => {
if (icon) {
this.bringToTop(icon);
}
});
}
checkIconId(female, formIndex, shiny, variant) {
if (this.icon.frame.name !== this.species.getIconId(female, formIndex, shiny, variant)) {
console.log(`${this.species.name}'s variant icon does not exist. Replacing with default.`);

View File

@ -1,3 +1,4 @@
import { VALUE_REDUCTION_MAX } from "#app/constants";
import { globalScene } from "#app/global-scene";
import { starterColors } from "#app/global-vars/starter-colors";
import Overrides from "#app/overrides";
@ -43,7 +44,7 @@ import { TimeOfDay } from "#enums/time-of-day";
import { UiMode } from "#enums/ui-mode";
import type { Variant } from "#sprites/variant";
import { getVariantIcon, getVariantTint } from "#sprites/variant";
import type { StarterAttributes } from "#system/game-data";
import type { StarterPreferences } from "#system/game-data";
import { SettingKeyboard } from "#system/settings-keyboard";
import type { DexEntry } from "#types/dex-data";
import { BaseStatsOverlay } from "#ui/containers/base-stats-overlay";
@ -61,6 +62,7 @@ import { toCamelCase, toTitleCase } from "#utils/strings";
import { argbFromRgba } from "@material/material-color-utilities";
import i18next from "i18next";
import type BBCodeText from "phaser3-rex-plugins/plugins/gameobjects/tagtext/bbcodetext/BBCodeText";
import { isPassiveAvailable, isSameSpeciesEggAvailable, isValueReductionAvailable } from "../starter-select-ui-utils";
interface LanguageSetting {
starterInfoTextSize: string;
@ -148,8 +150,6 @@ const languageSettings: { [key: string]: LanguageSetting } = {
},
};
const valueReductionMax = 2;
// Position of UI elements
const speciesContainerX = 109;
@ -263,11 +263,11 @@ export class PokedexPageUiHandler extends MessageUiHandler {
private filterInstructionRowX = 0;
private filterInstructionRowY = 0;
private starterAttributes: StarterAttributes;
private savedStarterAttributes: StarterAttributes;
private starterPreferences: StarterPreferences;
private savedStarterPreferences: StarterPreferences;
private previousSpecies: PokemonSpecies[];
private previousStarterAttributes: StarterAttributes[];
private previousStarterPreferences: StarterPreferences[];
protected blockInput = false;
protected blockInputOverlay = false;
@ -704,7 +704,7 @@ export class PokedexPageUiHandler extends MessageUiHandler {
this.starterSelectContainer.bringToTop(this.starterSelectMessageBoxContainer);
this.previousSpecies = [];
this.previousStarterAttributes = [];
this.previousStarterPreferences = [];
}
show(args: any[]): boolean {
@ -717,13 +717,13 @@ export class PokedexPageUiHandler extends MessageUiHandler {
return false;
}
this.species = args[0];
this.savedStarterAttributes = args[1] ?? {
this.savedStarterPreferences = args[1] ?? {
shiny: false,
female: true,
variant: 0,
form: 0,
};
this.formIndex = this.savedStarterAttributes.form ?? 0;
this.formIndex = this.savedStarterPreferences.form ?? 0;
this.filteredIndices = args[2] ?? null;
this.starterSetup();
@ -739,7 +739,7 @@ export class PokedexPageUiHandler extends MessageUiHandler {
this.starterSelectContainer.setVisible(true);
this.getUi().bringToTop(this.starterSelectContainer);
this.starterAttributes = this.initStarterPrefs();
this.starterPreferences = this.initStarterPrefs();
this.menuOptions = getEnumValues(MenuOptions);
@ -785,8 +785,8 @@ export class PokedexPageUiHandler extends MessageUiHandler {
this.isFormGender = formKey === "male" || formKey === "female";
if (
this.isFormGender &&
((this.savedStarterAttributes.female === true && formKey === "male") ||
(this.savedStarterAttributes.female === false && formKey === "female"))
((this.savedStarterPreferences.female === true && formKey === "male") ||
(this.savedStarterPreferences.female === false && formKey === "female"))
) {
this.formIndex = (this.formIndex + 1) % 2;
formKey = this.species.forms[this.formIndex].formKey;
@ -1015,26 +1015,26 @@ export class PokedexPageUiHandler extends MessageUiHandler {
* 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
* @returns StarterPreferences for the species
*/
initStarterPrefs(): StarterAttributes {
const starterAttributes: StarterAttributes | null = this.species ? { ...this.savedStarterAttributes } : null;
initStarterPrefs(): StarterPreferences {
const starterPreferences: StarterPreferences | null = this.species ? { ...this.savedStarterPreferences } : null;
const caughtAttr = this.isCaught();
// no preferences or Pokemon wasn't caught, return empty attribute
if (!starterAttributes || !this.isSeen()) {
if (!starterPreferences || !this.isSeen()) {
return {};
}
const hasShiny = caughtAttr & DexAttr.SHINY;
const hasNonShiny = caughtAttr & DexAttr.NON_SHINY;
if (!hasShiny || (starterAttributes.shiny === undefined && hasNonShiny)) {
if (!hasShiny || (starterPreferences.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;
starterPreferences.shiny = false;
starterPreferences.variant = 0;
} else if (!hasNonShiny || (starterPreferences.shiny === undefined && hasShiny)) {
starterPreferences.shiny = true;
starterPreferences.variant = 0;
}
this.unlockedVariants = [
@ -1043,38 +1043,38 @@ export class PokedexPageUiHandler extends MessageUiHandler {
!!(hasShiny && caughtAttr & DexAttr.VARIANT_3),
];
if (
starterAttributes.variant === undefined ||
Number.isNaN(starterAttributes.variant) ||
starterAttributes.variant < 0
starterPreferences.variant === undefined ||
Number.isNaN(starterPreferences.variant) ||
starterPreferences.variant < 0
) {
starterAttributes.variant = 0;
} else if (!this.unlockedVariants[starterAttributes.variant]) {
starterPreferences.variant = 0;
} else if (!this.unlockedVariants[starterPreferences.variant]) {
let highestValidIndex = -1;
for (let i = 0; i <= starterAttributes.variant && i < this.unlockedVariants.length; i++) {
for (let i = 0; i <= starterPreferences.variant && i < this.unlockedVariants.length; i++) {
if (this.unlockedVariants[i]) {
highestValidIndex = i;
}
}
// Set to the highest valid index found or default to 0
starterAttributes.variant = highestValidIndex !== -1 ? highestValidIndex : 0;
starterPreferences.variant = highestValidIndex !== -1 ? highestValidIndex : 0;
}
if (starterAttributes.female !== undefined) {
if (starterPreferences.female !== undefined) {
if (
(starterAttributes.female && !(caughtAttr & DexAttr.FEMALE)) ||
(!starterAttributes.female && !(caughtAttr & DexAttr.MALE))
(starterPreferences.female && !(caughtAttr & DexAttr.FEMALE)) ||
(!starterPreferences.female && !(caughtAttr & DexAttr.MALE))
) {
starterAttributes.female = !starterAttributes.female;
starterPreferences.female = !starterPreferences.female;
}
} else {
if (caughtAttr & DexAttr.FEMALE) {
starterAttributes.female = true;
starterPreferences.female = true;
} else if (caughtAttr & DexAttr.MALE) {
starterAttributes.female = false;
starterPreferences.female = false;
}
}
return starterAttributes;
return starterPreferences;
}
showText(
@ -1186,10 +1186,10 @@ export class PokedexPageUiHandler extends MessageUiHandler {
this.blockInput = true;
ui.setModeWithoutClear(UiMode.OPTION_SELECT).then(() => {
const species = this.previousSpecies.pop();
const starterAttributes = this.previousStarterAttributes.pop();
const starterPreferences = this.previousStarterPreferences.pop();
this.moveInfoOverlay.clear();
this.clearText();
ui.setModeForceTransition(UiMode.POKEDEX_PAGE, species, starterAttributes);
ui.setModeForceTransition(UiMode.POKEDEX_PAGE, species, starterPreferences);
success = true;
});
this.blockInput = false;
@ -1206,7 +1206,7 @@ export class PokedexPageUiHandler extends MessageUiHandler {
} else {
const starterData = globalScene.gameData.starterData[this.starterId];
// prepare persistent starter data to store changes
const starterAttributes = this.starterAttributes;
const starterPreferences = this.starterPreferences;
if (button === Button.ACTION) {
switch (this.cursor) {
@ -1616,7 +1616,7 @@ export class PokedexPageUiHandler extends MessageUiHandler {
: (preSpecies ?? this.species).getExpandedSpeciesName(),
handler: () => {
this.previousSpecies.push(this.species);
this.previousStarterAttributes.push({ ...this.savedStarterAttributes });
this.previousStarterPreferences.push({ ...this.savedStarterPreferences });
const newSpecies = allSpecies.find(
species => species.speciesId === pokemonPrevolutions[pre.speciesId],
);
@ -1628,11 +1628,11 @@ export class PokedexPageUiHandler extends MessageUiHandler {
: "";
const matchingForm = newSpecies?.forms.find(form => form.formKey === newFormKey);
const newFormIndex = matchingForm ? matchingForm.formIndex : 0;
this.starterAttributes.form = newFormIndex;
this.savedStarterAttributes.form = newFormIndex;
this.starterPreferences.form = newFormIndex;
this.savedStarterPreferences.form = newFormIndex;
this.moveInfoOverlay.clear();
this.clearText();
ui.setMode(UiMode.POKEDEX_PAGE, newSpecies, this.savedStarterAttributes);
ui.setMode(UiMode.POKEDEX_PAGE, newSpecies, this.savedStarterPreferences);
return true;
},
onHover: () => this.showText(conditionText),
@ -1669,12 +1669,12 @@ export class PokedexPageUiHandler extends MessageUiHandler {
style: isCaughtEvo && isFormCaughtEvo ? TextStyle.WINDOW : TextStyle.SHADOW_TEXT,
handler: () => {
this.previousSpecies.push(this.species);
this.previousStarterAttributes.push({ ...this.savedStarterAttributes });
this.starterAttributes.form = newFormIndex;
this.savedStarterAttributes.form = newFormIndex;
this.previousStarterPreferences.push({ ...this.savedStarterPreferences });
this.starterPreferences.form = newFormIndex;
this.savedStarterPreferences.form = newFormIndex;
this.moveInfoOverlay.clear();
this.clearText();
ui.setMode(UiMode.POKEDEX_PAGE, evoSpecies, this.savedStarterAttributes);
ui.setMode(UiMode.POKEDEX_PAGE, evoSpecies, this.savedStarterPreferences);
return true;
},
onHover: () => this.showText(conditionText),
@ -1711,17 +1711,17 @@ export class PokedexPageUiHandler extends MessageUiHandler {
style: isFormCaught ? TextStyle.WINDOW : TextStyle.SHADOW_TEXT,
handler: () => {
this.previousSpecies.push(this.species);
this.previousStarterAttributes.push({ ...this.savedStarterAttributes });
this.previousStarterPreferences.push({ ...this.savedStarterPreferences });
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.starterPreferences.form = newFormIndex;
this.savedStarterPreferences.form = newFormIndex;
this.moveInfoOverlay.clear();
this.clearText();
ui.setMode(
UiMode.POKEDEX_PAGE,
newSpecies,
this.savedStarterAttributes,
this.savedStarterPreferences,
this.filteredIndices,
);
return true;
@ -1814,9 +1814,9 @@ export class PokedexPageUiHandler extends MessageUiHandler {
switch (button) {
case Button.CYCLE_SHINY:
if (this.canCycleShiny) {
if (!starterAttributes.shiny) {
if (!starterPreferences.shiny) {
// Change to shiny, we need to get the proper default variant
const newVariant = starterAttributes.variant ? (starterAttributes.variant as Variant) : 0;
const newVariant = starterPreferences.variant ? (starterPreferences.variant as Variant) : 0;
this.setSpeciesDetails(this.species, {
shiny: true,
variant: newVariant,
@ -1824,8 +1824,8 @@ export class PokedexPageUiHandler extends MessageUiHandler {
globalScene.playSound("se/sparkle");
starterAttributes.shiny = true;
this.savedStarterAttributes.shiny = starterAttributes.shiny;
starterPreferences.shiny = true;
this.savedStarterPreferences.shiny = starterPreferences.shiny;
} else {
let newVariant = props.variant;
do {
@ -1845,16 +1845,16 @@ export class PokedexPageUiHandler extends MessageUiHandler {
}
} while (newVariant !== props.variant);
starterAttributes.variant = newVariant; // store the selected variant
this.savedStarterAttributes.variant = starterAttributes.variant;
starterPreferences.variant = newVariant; // store the selected variant
this.savedStarterPreferences.variant = starterPreferences.variant;
if (this.isCaught() & DexAttr.NON_SHINY && newVariant <= props.variant) {
this.setSpeciesDetails(this.species, {
shiny: false,
variant: 0,
});
success = true;
starterAttributes.shiny = false;
this.savedStarterAttributes.shiny = starterAttributes.shiny;
starterPreferences.shiny = false;
this.savedStarterPreferences.shiny = starterPreferences.shiny;
} else {
this.setSpeciesDetails(this.species, {
variant: newVariant as Variant,
@ -1875,16 +1875,16 @@ export class PokedexPageUiHandler extends MessageUiHandler {
break;
}
} while (newFormIndex !== props.formIndex || this.species.forms[newFormIndex].isUnobtainable);
starterAttributes.form = newFormIndex; // store the selected form
this.savedStarterAttributes.form = starterAttributes.form;
starterPreferences.form = newFormIndex; // store the selected form
this.savedStarterPreferences.form = starterPreferences.form;
this.formIndex = newFormIndex;
// Some forms are tied to the gender and should change accordingly
let newFemale = props.female;
if (this.isFormGender) {
newFemale = !props.female;
}
starterAttributes.female = newFemale;
this.savedStarterAttributes.female = starterAttributes.female;
starterPreferences.female = newFemale;
this.savedStarterPreferences.female = starterPreferences.female;
this.starterSetup();
this.setSpeciesDetails(this.species, {
formIndex: newFormIndex,
@ -1895,15 +1895,15 @@ export class PokedexPageUiHandler extends MessageUiHandler {
break;
case Button.CYCLE_GENDER:
if (this.canCycleGender) {
starterAttributes.female = !props.female;
this.savedStarterAttributes.female = starterAttributes.female;
starterPreferences.female = !props.female;
this.savedStarterPreferences.female = starterPreferences.female;
let newFormIndex = this.formIndex;
// Some forms are tied to the gender and should change accordingly
if (this.isFormGender) {
newFormIndex = this.formIndex === 0 ? 1 : 0;
}
starterAttributes.form = newFormIndex; // store the selected form
this.savedStarterAttributes.form = starterAttributes.form;
starterPreferences.form = newFormIndex; // store the selected form
this.savedStarterPreferences.form = starterPreferences.form;
this.formIndex = newFormIndex;
this.starterSetup();
this.setSpeciesDetails(this.species, {
@ -1948,15 +1948,17 @@ export class PokedexPageUiHandler extends MessageUiHandler {
}
return false;
},
style: this.isPassiveAvailable() ? TextStyle.WINDOW : TextStyle.SHADOW_TEXT,
style: isPassiveAvailable(this.species.speciesId) ? TextStyle.WINDOW : TextStyle.SHADOW_TEXT,
item: "candy",
itemArgs: this.isPassiveAvailable() ? starterColors[this.starterId] : ["808080", "808080"],
itemArgs: isPassiveAvailable(this.species.speciesId)
? starterColors[this.starterId]
: ["808080", "808080"],
});
}
// Reduce cost option
const valueReduction = starterData.valueReduction;
if (valueReduction < valueReductionMax) {
if (valueReduction < VALUE_REDUCTION_MAX) {
const reductionCost = getValueReductionCandyCounts(speciesStarterCosts[this.starterId])[valueReduction];
options.push({
label: `×${reductionCost} ${i18next.t("pokedexUiHandler:reduceCost")}`,
@ -1979,9 +1981,11 @@ export class PokedexPageUiHandler extends MessageUiHandler {
}
return false;
},
style: this.isValueReductionAvailable() ? TextStyle.WINDOW : TextStyle.SHADOW_TEXT,
style: isValueReductionAvailable(this.species.speciesId) ? TextStyle.WINDOW : TextStyle.SHADOW_TEXT,
item: "candy",
itemArgs: this.isValueReductionAvailable() ? starterColors[this.starterId] : ["808080", "808080"],
itemArgs: isValueReductionAvailable(this.species.speciesId)
? starterColors[this.starterId]
: ["808080", "808080"],
});
}
@ -2028,9 +2032,11 @@ export class PokedexPageUiHandler extends MessageUiHandler {
}
return false;
},
style: this.isSameSpeciesEggAvailable() ? TextStyle.WINDOW : TextStyle.SHADOW_TEXT,
style: isSameSpeciesEggAvailable(this.species.speciesId) ? TextStyle.WINDOW : TextStyle.SHADOW_TEXT,
item: "candy",
itemArgs: this.isSameSpeciesEggAvailable() ? starterColors[this.starterId] : ["808080", "808080"],
itemArgs: isSameSpeciesEggAvailable(this.species.speciesId)
? starterColors[this.starterId]
: ["808080", "808080"],
});
options.push({
label: i18next.t("menu:cancel"),
@ -2081,7 +2087,7 @@ export class PokedexPageUiHandler extends MessageUiHandler {
// Always go back to first selection after scrolling around
if (this.previousSpecies.length === 0) {
this.previousSpecies.push(this.species);
this.previousStarterAttributes.push({ ...this.savedStarterAttributes });
this.previousStarterPreferences.push({ ...this.savedStarterPreferences });
}
let newSpecies: PokemonSpecies;
if (this.filteredIndices) {
@ -2097,14 +2103,14 @@ export class PokedexPageUiHandler extends MessageUiHandler {
form => form.formKey === this.species?.forms[this.formIndex]?.formKey,
);
const newFormIndex = matchingForm ? matchingForm.formIndex : 0;
this.starterAttributes.form = newFormIndex;
this.savedStarterAttributes.form = newFormIndex;
this.starterPreferences.form = newFormIndex;
this.savedStarterPreferences.form = newFormIndex;
this.moveInfoOverlay.clear();
this.clearText();
ui.setModeForceTransition(
UiMode.POKEDEX_PAGE,
newSpecies,
this.savedStarterAttributes,
this.savedStarterPreferences,
this.filteredIndices,
);
});
@ -2120,7 +2126,7 @@ export class PokedexPageUiHandler extends MessageUiHandler {
// Always go back to first selection after scrolling around
if (this.previousSpecies.length === 0) {
this.previousSpecies.push(this.species);
this.previousStarterAttributes.push({ ...this.savedStarterAttributes });
this.previousStarterPreferences.push({ ...this.savedStarterPreferences });
}
let newSpecies: PokemonSpecies;
if (this.filteredIndices) {
@ -2136,14 +2142,14 @@ export class PokedexPageUiHandler extends MessageUiHandler {
form => form.formKey === this.species?.forms[this.formIndex]?.formKey,
);
const newFormIndex = matchingForm ? matchingForm.formIndex : 0;
this.starterAttributes.form = newFormIndex;
this.savedStarterAttributes.form = newFormIndex;
this.starterPreferences.form = newFormIndex;
this.savedStarterPreferences.form = newFormIndex;
this.moveInfoOverlay.clear();
this.clearText();
ui.setModeForceTransition(
UiMode.POKEDEX_PAGE,
newSpecies,
this.savedStarterAttributes,
this.savedStarterPreferences,
this.filteredIndices,
);
});
@ -2295,49 +2301,9 @@ export class PokedexPageUiHandler extends MessageUiHandler {
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;
const starterPreferences: StarterPreferences | null = species ? { ...this.starterPreferences } : null;
if (!species && globalScene.ui.getTooltip().visible) {
globalScene.ui.hideTooltip();
@ -2359,15 +2325,15 @@ export class PokedexPageUiHandler extends MessageUiHandler {
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 && !Number.isNaN(starterAttributes.variant)) {
// Set default attributes if for some reason starterPreferences does not exist or attributes missing
const props: StarterPreferences = globalScene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr);
if (starterPreferences?.variant && !Number.isNaN(starterPreferences.variant)) {
if (props.shiny) {
props.variant = starterAttributes.variant as Variant;
props.variant = starterPreferences.variant as Variant;
}
}
props.form = starterAttributes?.form ?? props.form;
props.female = starterAttributes?.female ?? props.female;
props.form = starterPreferences?.form ?? props.form;
props.female = starterPreferences?.female ?? props.female;
this.setSpeciesDetails(species, {
shiny: props.shiny,
@ -2433,7 +2399,7 @@ export class PokedexPageUiHandler extends MessageUiHandler {
setSpeciesDetails(species: PokemonSpecies, options: SpeciesDetails = {}, forceUpdate?: boolean): void {
let { shiny, formIndex, female, variant } = options;
const oldProps = species ? this.starterAttributes : null;
const oldProps = species ? this.starterPreferences : null;
// We will only update the sprite if there is a change to form, shiny/variant
// or gender for species with gender sprite differences
@ -2488,7 +2454,7 @@ export class PokedexPageUiHandler extends MessageUiHandler {
const caughtAttr = this.isCaught(species);
if (!caughtAttr) {
const props = this.starterAttributes;
const props = this.starterPreferences;
if (shiny === undefined || shiny !== props.shiny) {
shiny = props.shiny;
@ -2745,7 +2711,7 @@ export class PokedexPageUiHandler extends MessageUiHandler {
}
/**
* Creates a temporary dex attr props that will be used to display the correct shiny, variant, and form based on this.starterAttributes
* Creates a temporary dex attr props that will be used to display the correct shiny, variant, and form based on this.starterPreferences
*
* @param speciesId the id of the species to get props for
* @returns the dex props
@ -2762,7 +2728,7 @@ export class PokedexPageUiHandler extends MessageUiHandler {
* 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)) {
if (this.starterPreferences?.female || ((caughtAttr & DexAttr.FEMALE) > 0n && (caughtAttr & DexAttr.MALE) === 0n)) {
props += DexAttr.FEMALE;
} else {
props += DexAttr.MALE;
@ -2771,12 +2737,12 @@ export class PokedexPageUiHandler extends MessageUiHandler {
* 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)
this.starterPreferences?.shiny ||
((caughtAttr & DexAttr.SHINY) > 0n && this.starterPreferences?.shiny !== false)
) {
props += DexAttr.SHINY;
if (this.starterAttributes?.variant !== undefined) {
props += BigInt(Math.pow(2, this.starterAttributes?.variant)) * DexAttr.DEFAULT_VARIANT;
if (this.starterPreferences?.variant !== undefined) {
props += BigInt(Math.pow(2, this.starterPreferences?.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
@ -2793,9 +2759,9 @@ export class PokedexPageUiHandler extends MessageUiHandler {
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) {
if (this.starterPreferences?.form) {
// this checks for the form of the pokemon
props += BigInt(Math.pow(2, this.starterAttributes?.form)) * DexAttr.DEFAULT_FORM;
props += BigInt(Math.pow(2, this.starterPreferences?.form)) * DexAttr.DEFAULT_FORM;
} else {
// Get the first unlocked form
props += globalScene.gameData.getFormAttr(globalScene.gameData.getFormIndex(caughtAttr));

View File

@ -4,14 +4,7 @@ import { catchableSpecies } from "#balance/biomes";
import { speciesEggMoves } from "#balance/egg-moves";
import { pokemonStarters } from "#balance/pokemon-evolutions";
import { pokemonFormLevelMoves, pokemonSpeciesLevelMoves } from "#balance/pokemon-level-moves";
import {
getPassiveCandyCount,
getSameSpeciesEggCandyCounts,
getStarterValueFriendshipCap,
getValueReductionCandyCounts,
POKERUS_STARTER_COUNT,
speciesStarterCosts,
} from "#balance/starters";
import { getStarterValueFriendshipCap, POKERUS_STARTER_COUNT, speciesStarterCosts } from "#balance/starters";
import { speciesTmMoves } from "#balance/tms";
import { allAbilities, allMoves, allSpecies } from "#data/data-lists";
import type { PokemonForm, PokemonSpecies } from "#data/pokemon-species";
@ -23,7 +16,6 @@ import { Button } from "#enums/buttons";
import { DexAttr } from "#enums/dex-attr";
import { DropDownColumn } from "#enums/drop-down-column";
import type { Nature } from "#enums/nature";
import { Passive as PassiveAttr } from "#enums/passive";
import { PokemonType } from "#enums/pokemon-type";
import type { SpeciesId } from "#enums/species-id";
import { TextStyle } from "#enums/text-style";
@ -31,7 +23,7 @@ import { UiMode } from "#enums/ui-mode";
import { UiTheme } from "#enums/ui-theme";
import type { Variant } from "#sprites/variant";
import { getVariantIcon, getVariantTint } from "#sprites/variant";
import type { DexAttrProps, StarterAttributes } from "#system/game-data";
import type { DexAttrProps, StarterPreferences } from "#system/game-data";
import { SettingKeyboard } from "#system/settings-keyboard";
import type { DexEntry } from "#types/dex-data";
import {
@ -52,12 +44,13 @@ import { PokemonIconAnimHandler, PokemonIconAnimMode } from "#ui/handlers/pokemo
import { addTextObject, getTextColor } from "#ui/text";
import { addWindow } from "#ui/ui-theme";
import { BooleanHolder, fixedInt, getLocalizedSpriteKey, padInt, randIntRange, rgbHexToRgba } from "#utils/common";
import type { StarterPreferences } from "#utils/data";
import type { AllStarterPreferences } from "#utils/data";
import { loadStarterPreferences } from "#utils/data";
import { getPokemonSpeciesForm, getPokerusStarters } from "#utils/pokemon-utils";
import { toCamelCase } from "#utils/strings";
import { argbFromRgba } from "@material/material-color-utilities";
import i18next from "i18next";
import { isPassiveAvailable, isSameSpeciesEggAvailable, isValueReductionAvailable } from "../starter-select-ui-utils";
interface LanguageSetting {
starterInfoTextSize: string;
@ -138,8 +131,6 @@ interface ContainerData {
passive2?: boolean;
}
const valueReductionMax = 2;
// Position of UI elements
const filterBarHeight = 17;
const speciesContainerX = 143;
@ -200,7 +191,7 @@ export class PokedexUiHandler extends MessageUiHandler {
private iconAnimHandler: PokemonIconAnimHandler;
private starterPreferences: StarterPreferences;
private starterPreferences: AllStarterPreferences;
protected blockInput = false;
@ -688,15 +679,15 @@ export class PokedexUiHandler extends MessageUiHandler {
* 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
* @returns StarterPreferences for the species
*/
initStarterPrefs(species: PokemonSpecies): StarterAttributes {
const starterAttributes = this.starterPreferences[species.speciesId];
initStarterPrefs(species: PokemonSpecies): StarterPreferences {
const starterPreferences = this.starterPreferences[species.speciesId];
const dexEntry = globalScene.gameData.dexData[species.speciesId];
const starterData = globalScene.gameData.starterData[species.speciesId];
// no preferences or Pokemon wasn't caught, return empty attribute
if (!starterAttributes || !dexEntry.caughtAttr) {
if (!starterPreferences || !dexEntry.caughtAttr) {
return {};
}
@ -704,40 +695,40 @@ export class PokedexUiHandler extends MessageUiHandler {
const hasShiny = caughtAttr & DexAttr.SHINY;
const hasNonShiny = caughtAttr & DexAttr.NON_SHINY;
if (starterAttributes.shiny && !hasShiny) {
if (starterPreferences.shiny && !hasShiny) {
// shiny form wasn't unlocked, purging shiny and variant setting
starterAttributes.shiny = undefined;
starterAttributes.variant = undefined;
} else if (starterAttributes.shiny === false && !hasNonShiny) {
starterPreferences.shiny = undefined;
starterPreferences.variant = undefined;
} else if (starterPreferences.shiny === false && !hasNonShiny) {
// non shiny form wasn't unlocked, purging shiny setting
starterAttributes.shiny = undefined;
starterPreferences.shiny = undefined;
}
if (starterAttributes.variant !== undefined) {
if (starterPreferences.variant !== undefined) {
const unlockedVariants = [
hasShiny && caughtAttr & DexAttr.DEFAULT_VARIANT,
hasShiny && caughtAttr & DexAttr.VARIANT_2,
hasShiny && caughtAttr & DexAttr.VARIANT_3,
];
if (
Number.isNaN(starterAttributes.variant) ||
starterAttributes.variant < 0 ||
!unlockedVariants[starterAttributes.variant]
Number.isNaN(starterPreferences.variant) ||
starterPreferences.variant < 0 ||
!unlockedVariants[starterPreferences.variant]
) {
// variant value is invalid or requested variant wasn't unlocked, purging setting
starterAttributes.variant = undefined;
starterPreferences.variant = undefined;
}
}
if (starterAttributes.female !== undefined) {
if (!(starterAttributes.female ? caughtAttr & DexAttr.FEMALE : caughtAttr & DexAttr.MALE)) {
if (starterPreferences.female !== undefined) {
if (!(starterPreferences.female ? caughtAttr & DexAttr.FEMALE : caughtAttr & DexAttr.MALE)) {
// requested gender wasn't unlocked, purging setting
starterAttributes.female = undefined;
starterPreferences.female = undefined;
}
}
if (starterAttributes.ability !== undefined) {
if (starterPreferences.ability !== undefined) {
const speciesHasSingleAbility = species.ability2 === species.ability1;
const abilityAttr = starterData.abilityAttr;
const hasAbility1 = abilityAttr & AbilityAttr.ABILITY_1;
@ -750,31 +741,31 @@ export class PokedexUiHandler extends MessageUiHandler {
speciesHasSingleAbility ? hasAbility2 && !hasAbility1 : hasAbility2,
hasHiddenAbility,
];
if (!unlockedAbilities[starterAttributes.ability]) {
if (!unlockedAbilities[starterPreferences.ability]) {
// requested ability wasn't unlocked, purging setting
starterAttributes.ability = undefined;
starterPreferences.ability = undefined;
}
}
const selectedForm = starterAttributes.form;
const selectedForm = starterPreferences.form;
if (
selectedForm !== undefined &&
(!species.forms[selectedForm]?.isStarterSelectable ||
!(caughtAttr & globalScene.gameData.getFormAttr(selectedForm)))
) {
// requested form wasn't unlocked/isn't a starter form, purging setting
starterAttributes.form = undefined;
starterPreferences.form = undefined;
}
if (starterAttributes.nature !== undefined) {
if (starterPreferences.nature !== undefined) {
const unlockedNatures = globalScene.gameData.getNaturesForAttr(dexEntry.natureAttr);
if (unlockedNatures.indexOf(starterAttributes.nature as unknown as Nature) < 0) {
if (unlockedNatures.indexOf(starterPreferences.nature as unknown as Nature) < 0) {
// requested nature wasn't unlocked, purging setting
starterAttributes.nature = undefined;
starterPreferences.nature = undefined;
}
}
return starterAttributes;
return starterPreferences;
}
/**
@ -850,52 +841,6 @@ export class PokedexUiHandler extends MessageUiHandler {
return pokemonStarters[speciesId];
}
/**
* Determines if a passive upgrade is available for the given species ID
* @param speciesId The ID of the species to check the passive of
* @returns true if the user has enough candies and a passive has not been unlocked already
*/
isPassiveAvailable(speciesId: number): boolean {
// Get this species ID's starter data
const starterData = globalScene.gameData.starterData[this.getStarterSpeciesId(speciesId)];
return (
starterData.candyCount >= getPassiveCandyCount(speciesStarterCosts[this.getStarterSpeciesId(speciesId)]) &&
!(starterData.passiveAttr & PassiveAttr.UNLOCKED)
);
}
/**
* Determines if a value reduction upgrade is available for the given species ID
* @param speciesId The ID of the species to check the value reduction of
* @returns true if the user has enough candies and all value reductions have not been unlocked already
*/
isValueReductionAvailable(speciesId: number): boolean {
// Get this species ID's starter data
const starterData = globalScene.gameData.starterData[this.getStarterSpeciesId(speciesId)];
return (
starterData.candyCount >=
getValueReductionCandyCounts(speciesStarterCosts[this.getStarterSpeciesId(speciesId)])[
starterData.valueReduction
] && starterData.valueReduction < valueReductionMax
);
}
/**
* Determines if an same species egg can be bought for the given species ID
* @param speciesId The ID of the species to check the value reduction of
* @returns true if the user has enough candies
*/
isSameSpeciesEggAvailable(speciesId: number): boolean {
// Get this species ID's starter data
const starterData = globalScene.gameData.starterData[this.getStarterSpeciesId(speciesId)];
return (
starterData.candyCount >= getSameSpeciesEggCandyCounts(speciesStarterCosts[this.getStarterSpeciesId(speciesId)])
);
}
/**
* Sets a bounce animation if enabled and the Pokemon has an upgrade
* @param icon {@linkcode Phaser.GameObjects.GameObject} to animate
@ -937,9 +882,9 @@ export class PokedexUiHandler extends MessageUiHandler {
};
if (
this.isPassiveAvailable(species.speciesId) ||
isPassiveAvailable(species.speciesId) ||
(globalScene.candyUpgradeNotification === 2 &&
(this.isValueReductionAvailable(species.speciesId) || this.isSameSpeciesEggAvailable(species.speciesId)))
(isValueReductionAvailable(species.speciesId) || isSameSpeciesEggAvailable(species.speciesId)))
) {
const chain = globalScene.tweens.chain(tweenChain);
if (!startPaused) {
@ -965,19 +910,19 @@ export class PokedexUiHandler extends MessageUiHandler {
return;
}
const isPassiveAvailable = this.isPassiveAvailable(species.speciesId);
const isValueReductionAvailable = this.isValueReductionAvailable(species.speciesId);
const isSameSpeciesEggAvailable = this.isSameSpeciesEggAvailable(species.speciesId);
const passiveAvailable = isPassiveAvailable(species.speciesId);
const valueReductionAvailable = isValueReductionAvailable(species.speciesId);
const sameSpeciesEggAvailable = isSameSpeciesEggAvailable(species.speciesId);
// 'Passive Only' mode
if (globalScene.candyUpgradeNotification === 1) {
starter.candyUpgradeIcon.setVisible(slotVisible && isPassiveAvailable);
starter.candyUpgradeIcon.setVisible(slotVisible && passiveAvailable);
starter.candyUpgradeOverlayIcon.setVisible(slotVisible && starter.candyUpgradeIcon.visible);
// 'On' mode
} else if (globalScene.candyUpgradeNotification === 2) {
starter.candyUpgradeIcon.setVisible(
slotVisible && (isPassiveAvailable || isValueReductionAvailable || isSameSpeciesEggAvailable),
slotVisible && (passiveAvailable || valueReductionAvailable || sameSpeciesEggAvailable),
);
starter.candyUpgradeOverlayIcon.setVisible(slotVisible && starter.candyUpgradeIcon.visible);
}
@ -1543,7 +1488,7 @@ export class PokedexUiHandler extends MessageUiHandler {
// Passive Filter
const isPassiveUnlocked = starterData.passiveAttr > 0;
const isPassiveUnlockable = this.isPassiveAvailable(species.speciesId) && !isPassiveUnlocked;
const isPassiveUnlockable = isPassiveAvailable(species.speciesId) && !isPassiveUnlocked;
const fitsPassive = this.filterBar.getVals(DropDownColumn.UNLOCKS).some(unlocks => {
if (unlocks.val === "PASSIVE" && unlocks.state === DropDownState.ON) {
return isPassiveUnlocked;
@ -1562,7 +1507,7 @@ export class PokedexUiHandler extends MessageUiHandler {
// Cost Reduction Filter
const isCostReducedByOne = starterData.valueReduction === 1;
const isCostReducedByTwo = starterData.valueReduction === 2;
const isCostReductionUnlockable = this.isValueReductionAvailable(species.speciesId);
const isCostReductionUnlockable = isValueReductionAvailable(species.speciesId);
const fitsCostReduction = this.filterBar.getVals(DropDownColumn.UNLOCKS).some(unlocks => {
if (unlocks.val === "COST_REDUCTION" && unlocks.state === DropDownState.ON) {
return isCostReducedByOne || isCostReducedByTwo;
@ -1674,7 +1619,7 @@ export class PokedexUiHandler extends MessageUiHandler {
});
// Egg Purchasable Filter
const isEggPurchasable = this.isSameSpeciesEggAvailable(species.speciesId);
const isEggPurchasable = isSameSpeciesEggAvailable(species.speciesId);
const fitsEgg = this.filterBar.getVals(DropDownColumn.MISC).some(misc => {
if (misc.val === "EGG" && misc.state === DropDownState.ON) {
return isEggPurchasable;
@ -2357,7 +2302,7 @@ export class PokedexUiHandler extends MessageUiHandler {
/**
* Creates a temporary dex attr props that will be used to
* display the correct shiny, variant, and form based on the StarterPreferences
* display the correct shiny, variant, and form based on the AllStarterPreferences
*
* @param speciesId the id of the species to get props for
* @returns the dex props

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,336 @@
import { VALUE_REDUCTION_MAX } from "#app/constants";
import { globalScene } from "#app/global-scene";
import {
getPassiveCandyCount,
getSameSpeciesEggCandyCounts,
getStarterValueFriendshipCap,
getValueReductionCandyCounts,
speciesStarterCosts,
} from "#balance/starters";
import type { PokemonSpecies } from "#data/pokemon-species";
import { ChallengeType } from "#enums/challenge-type";
import { DexAttr } from "#enums/dex-attr";
import { GameModes } from "#enums/game-modes";
import { Passive } from "#enums/passive";
import type { PokemonType } from "#enums/pokemon-type";
import type { SpeciesId } from "#enums/species-id";
import type { Variant } from "#sprites/variant";
import type { DexAttrProps, StarterDataEntry, StarterPreferences } from "#system/game-data";
import type { DexEntry } from "#types/dex-data";
import { applyChallenges, checkStarterValidForChallenge } from "#utils/challenge-utils";
import { NumberHolder } from "#utils/common";
import i18next from "i18next";
export interface SpeciesDetails {
shiny?: boolean;
formIndex?: number;
female?: boolean;
variant?: Variant;
abilityIndex?: number;
natureIndex?: number;
teraType?: PokemonType;
}
/**
* Determines if a passive upgrade is available for the given species ID
* @param speciesId The ID of the species to check the passive of
* @returns true if the user has enough candies and a passive has not been unlocked already
*/
export function isPassiveAvailable(speciesId: number): boolean {
// Get this species ID's starter data
const starterData = globalScene.gameData.starterData[speciesId];
return (
starterData.candyCount >= getPassiveCandyCount(speciesStarterCosts[speciesId]) &&
!(starterData.passiveAttr & Passive.UNLOCKED)
);
}
/**
* Determines if a value reduction upgrade is available for the given species ID
* @param speciesId The ID of the species to check the value reduction of
* @returns true if the user has enough candies and all value reductions have not been unlocked already
*/
export function isValueReductionAvailable(speciesId: number): boolean {
// Get this species ID's starter data
const starterData = globalScene.gameData.starterData[speciesId];
return (
starterData.candyCount >=
getValueReductionCandyCounts(speciesStarterCosts[speciesId])[starterData.valueReduction] &&
starterData.valueReduction < VALUE_REDUCTION_MAX
);
}
/**
* Determines if an same species egg can be bought for the given species ID
* @param speciesId The ID of the species to check the value reduction of
* @returns true if the user has enough candies
*/
export function isSameSpeciesEggAvailable(speciesId: number): boolean {
// Get this species ID's starter data
const starterData = globalScene.gameData.starterData[speciesId];
return starterData.candyCount >= getSameSpeciesEggCandyCounts(speciesStarterCosts[speciesId]);
}
export function isStarterValidForChallenge(species: PokemonSpecies) {
let allFormsValid = false;
if (species.forms?.length > 0) {
for (let i = 0; i < species.forms.length; i++) {
/* Here we are making a fake form index dex props for challenges
* Since some pokemon rely on forms to be valid (i.e. blaze tauros for fire challenges), we make a fake form and dex props to use in the challenge
*/
if (!species.forms[i].isStarterSelectable) {
continue;
}
const tempFormProps = BigInt(Math.pow(2, i)) * DexAttr.DEFAULT_FORM;
const isValidForChallenge = checkStarterValidForChallenge(
species,
globalScene.gameData.getSpeciesDexAttrProps(species, tempFormProps),
true,
);
allFormsValid ||= isValidForChallenge;
}
} else {
const isValidForChallenge = checkStarterValidForChallenge(
species,
globalScene.gameData.getSpeciesDexAttrProps(
species,
globalScene.gameData.getSpeciesDefaultDexAttr(species, false, true),
),
true,
);
allFormsValid = isValidForChallenge;
}
return allFormsValid;
}
/**
* Determines if 'Icon' based upgrade notifications should be shown
* @returns true if upgrade notifications are enabled and set to display an 'Icon'
*/
export function 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'
*/
export function isUpgradeAnimationEnabled(): boolean {
return globalScene.candyUpgradeNotification !== 0 && globalScene.candyUpgradeDisplay === 1;
}
interface StarterSelectLanguageSetting {
starterInfoTextSize: string;
instructionTextSize: string;
starterInfoXPos?: number;
starterInfoYOffset?: number;
}
const languageSettings: { [key: string]: StarterSelectLanguageSetting } = {
en: {
starterInfoTextSize: "56px",
instructionTextSize: "38px",
},
de: {
starterInfoTextSize: "54px",
instructionTextSize: "35px",
starterInfoXPos: 35,
},
"es-ES": {
starterInfoTextSize: "50px",
instructionTextSize: "38px",
starterInfoYOffset: 0.5,
starterInfoXPos: 38,
},
"es-MX": {
starterInfoTextSize: "50px",
instructionTextSize: "38px",
starterInfoYOffset: 0.5,
starterInfoXPos: 38,
},
fr: {
starterInfoTextSize: "54px",
instructionTextSize: "38px",
},
it: {
starterInfoTextSize: "56px",
instructionTextSize: "38px",
},
"pt-BR": {
starterInfoTextSize: "48px",
instructionTextSize: "42px",
starterInfoYOffset: 0.5,
starterInfoXPos: 33,
},
zh: {
starterInfoTextSize: "56px",
instructionTextSize: "36px",
starterInfoXPos: 26,
},
ko: {
starterInfoTextSize: "60px",
instructionTextSize: "38px",
starterInfoYOffset: -0.5,
starterInfoXPos: 30,
},
ja: {
starterInfoTextSize: "48px",
instructionTextSize: "40px",
starterInfoYOffset: 1,
starterInfoXPos: 32,
},
ca: {
starterInfoTextSize: "48px",
instructionTextSize: "38px",
starterInfoYOffset: 0.5,
starterInfoXPos: 29,
},
da: {
starterInfoTextSize: "56px",
instructionTextSize: "38px",
},
tr: {
starterInfoTextSize: "56px",
instructionTextSize: "38px",
},
ro: {
starterInfoTextSize: "56px",
instructionTextSize: "38px",
},
ru: {
starterInfoTextSize: "46px",
instructionTextSize: "38px",
starterInfoYOffset: 0.5,
starterInfoXPos: 26,
},
tl: {
starterInfoTextSize: "56px",
instructionTextSize: "38px",
},
};
export function getStarterSelectTextSettings(): StarterSelectLanguageSetting {
const currentLanguage = i18next.resolvedLanguage ?? "en";
const langSettingKey = Object.keys(languageSettings).find(lang => currentLanguage.includes(lang)) ?? "en";
const textSettings = languageSettings[langSettingKey];
return textSettings;
}
export function getSpeciesData(
speciesId: SpeciesId,
applyChallenge = true,
): { dexEntry: DexEntry; starterDataEntry: StarterDataEntry } {
const dexEntry = globalScene.gameData.dexData[speciesId];
const starterDataEntry = globalScene.gameData.starterData[speciesId];
// Unpacking to make a copy by values, not references
const copiedDexEntry = { ...dexEntry };
copiedDexEntry.ivs = [...dexEntry.ivs];
const copiedStarterDataEntry = { ...starterDataEntry };
if (applyChallenge) {
applyChallenges(ChallengeType.STARTER_SELECT_MODIFY, speciesId, copiedDexEntry, copiedStarterDataEntry);
}
return { dexEntry: { ...copiedDexEntry }, starterDataEntry: { ...copiedStarterDataEntry } };
}
export function getFriendship(speciesId: number) {
let currentFriendship = globalScene.gameData.starterData[speciesId].friendship;
if (!currentFriendship || currentFriendship === undefined) {
currentFriendship = 0;
}
const friendshipCap = getStarterValueFriendshipCap(speciesStarterCosts[speciesId]);
return { currentFriendship, friendshipCap };
}
/**
* Creates a temporary dex attr props that will be used to check whether a pokemon is valid for a challenge
* and to display the correct shiny, variant, and form based on the AllStarterPreferences
*
* @param speciesId the id of the species to get props for
* @returns the dex props
*/
export function getDexAttrFromPreferences(speciesId: number, starterPreferences: StarterPreferences = {}): bigint {
let props = 0n;
const { dexEntry } = getSpeciesData(speciesId);
const caughtAttr = dexEntry.caughtAttr;
/* 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 (
starterPreferences[speciesId]?.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 (
starterPreferences[speciesId]?.shiny ||
((caughtAttr & DexAttr.SHINY) > 0n && starterPreferences[speciesId]?.shiny !== false)
) {
props += DexAttr.SHINY;
if (starterPreferences[speciesId]?.variant !== undefined) {
props += BigInt(Math.pow(2, starterPreferences[speciesId]?.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 (starterPreferences[speciesId]?.form) {
// this checks for the form of the pokemon
props += BigInt(Math.pow(2, starterPreferences[speciesId]?.form)) * DexAttr.DEFAULT_FORM;
} else {
// Get the first unlocked form
props += globalScene.gameData.getFormAttr(globalScene.gameData.getFormIndex(caughtAttr));
}
return props;
}
export function getSpeciesPropsFromPreferences(
species: PokemonSpecies,
starterPreferences: StarterPreferences = {},
): DexAttrProps {
return globalScene.gameData.getSpeciesDexAttrProps(
species,
getDexAttrFromPreferences(species.speciesId, starterPreferences),
);
}
export function getRunValueLimit(): 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;
}
applyChallenges(ChallengeType.STARTER_POINTS, valueLimit);
return valueLimit.value;
}

877
src/ui/starter-summary.ts Normal file
View File

@ -0,0 +1,877 @@
import { globalScene } from "#app/global-scene";
import { starterColors } from "#app/global-vars/starter-colors";
import { speciesEggMoves } from "#balance/egg-moves";
import { pokemonPrevolutions } from "#balance/pokemon-evolutions";
import { allAbilities, allMoves } from "#data/data-lists";
import { getEggTierForSpecies } from "#data/egg";
import { GrowthRate, getGrowthRateColor } from "#data/exp";
import { Gender, getGenderColor, getGenderSymbol } from "#data/gender";
import { getNatureName } from "#data/nature";
import type { PokemonSpecies } from "#data/pokemon-species";
import { Challenges } from "#enums/challenges";
import type { Nature } from "#enums/nature";
import { Passive } from "#enums/passive";
import { PokemonType } from "#enums/pokemon-type";
import { SpeciesId } from "#enums/species-id";
import { TextStyle } from "#enums/text-style";
import { getVariantIcon, getVariantTint, type Variant } from "#sprites/variant";
import { achvs } from "#system/achv";
import type { StarterMoveset, StarterPreferences } from "#system/game-data";
import type { Ability } from "#types/ability-types";
import { BooleanHolder, getLocalizedSpriteKey, isNullOrUndefined, padInt, rgbHexToRgba } from "#utils/common";
import { getPokemonSpecies, getPokemonSpeciesForm } from "#utils/pokemon-utils";
import { toCamelCase, toTitleCase } from "#utils/strings";
import { argbFromRgba } from "@material/material-color-utilities";
import i18next from "i18next";
import type { GameObjects } from "phaser";
import type BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext";
import { StatsContainer } from "./containers/stats-container";
import {
getDexAttrFromPreferences,
getFriendship,
getSpeciesData,
getStarterSelectTextSettings,
type SpeciesDetails,
} from "./starter-select-ui-utils";
import { addBBCodeTextObject, addTextObject, getTextColor } from "./text";
export class StarterSummary extends Phaser.GameObjects.Container {
private pokemonSprite: Phaser.GameObjects.Sprite;
private pokemonNumberText: Phaser.GameObjects.Text;
private shinyOverlay: Phaser.GameObjects.Image;
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 pokemonAbilityLabelText: Phaser.GameObjects.Text;
private pokemonAbilityText: Phaser.GameObjects.Text;
private pokemonPassiveLabelText: Phaser.GameObjects.Text;
private pokemonPassiveText: Phaser.GameObjects.Text;
private pokemonNatureLabelText: Phaser.GameObjects.Text;
private pokemonNatureText: BBCodeText;
private pokemonMovesContainer: Phaser.GameObjects.Container;
private pokemonMoveContainers: Phaser.GameObjects.Container[];
private pokemonMoveBgs: Phaser.GameObjects.NineSlice[];
private pokemonMoveLabels: Phaser.GameObjects.Text[];
private pokemonAdditionalMoveCountLabel: Phaser.GameObjects.Text;
private eggMovesLabel: Phaser.GameObjects.Text;
private pokemonEggMovesContainer: Phaser.GameObjects.Container;
private pokemonEggMoveContainers: Phaser.GameObjects.Container[];
private pokemonEggMoveBgs: Phaser.GameObjects.NineSlice[];
private pokemonEggMoveLabels: 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 pokemonPassiveDisabledIcon: Phaser.GameObjects.Sprite;
private pokemonPassiveLockedIcon: Phaser.GameObjects.Sprite;
private teraIcon: Phaser.GameObjects.Sprite;
// Whether the tera type icon should be displayed
private allowTera: boolean;
// Container for ivs, whether they should be shown
private statsContainer: StatsContainer;
private statsMode = false;
private assetLoadCancelled: BooleanHolder | null;
// Which of the tooltips is displayed (on mouse hover)
private activeTooltip: "ABILITY" | "PASSIVE" | "CANDY" | undefined;
// Container for type, growth rate, luck
private pokemonPermanentInfoContainer: GameObjects.Container;
// Container for numbers of caught pokémon, eggs
private pokemonStatisticsContainer: GameObjects.Container;
// Container for everything that's a preference (abilities, nature, form...)
private pokemonPreferencesContainer: GameObjects.Container;
private speciesId: SpeciesId;
constructor(x: number, y: number) {
super(globalScene, x, y);
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.shinyOverlay = globalScene.add
.image(6, 111, getLocalizedSpriteKey("summary_dexnb_label_overlay_shiny"))
.setOrigin(0, 1)
.setVisible(false); // Pixel text 'No' shiny
this.pokemonNumberText = addTextObject(17, 1, "0000", TextStyle.SUMMARY_DEX_NUM).setOrigin(0);
this.pokemonNameText = addTextObject(6, 112, "", TextStyle.SUMMARY).setOrigin(0);
this.pokemonUncaughtText = addTextObject(
6,
127,
i18next.t("starterSelectUiHandler:uncaught"),
TextStyle.SUMMARY_ALT,
{ fontSize: "56px" },
).setOrigin(0);
this.pokemonMoveContainers = [];
this.pokemonMoveBgs = [];
this.pokemonMoveLabels = [];
this.pokemonEggMoveContainers = [];
this.pokemonEggMoveBgs = [];
this.pokemonEggMoveLabels = [];
this.pokemonPreferencesContainer = this.setupPokemonPreferencesContainer();
this.pokemonPermanentInfoContainer = this.setupPokemonPermanentInfoContainer();
this.pokemonStatisticsContainer = this.setupPokemonStatisticsContainer();
for (let m = 0; m < 4; m++) {
const moveContainer = globalScene.add.container(0, 14 * m);
const moveBg = globalScene.add.nineslice(0, 0, "type_bgs", "unknown", 92, 14, 2, 2, 2, 2);
moveBg.setOrigin(1, 0);
const moveLabel = addTextObject(-moveBg.width / 2, 0, "-", TextStyle.MOVE_LABEL);
moveLabel.setOrigin(0.5, 0);
this.pokemonMoveBgs.push(moveBg);
this.pokemonMoveLabels.push(moveLabel);
moveContainer.add([moveBg, moveLabel]);
this.pokemonMoveContainers.push(moveContainer);
this.pokemonMovesContainer.add(moveContainer);
}
this.pokemonAdditionalMoveCountLabel = addTextObject(
-this.pokemonMoveBgs[0].width / 2,
56,
"(+0)",
TextStyle.MOVE_LABEL,
).setOrigin(0.5, 0);
this.pokemonMovesContainer.add(this.pokemonAdditionalMoveCountLabel);
this.pokemonEggMovesContainer = globalScene.add.container(102, 85).setScale(0.375);
this.eggMovesLabel = addTextObject(
-46,
0,
i18next.t("starterSelectUiHandler:eggMoves"),
TextStyle.WINDOW_ALT,
).setOrigin(0.5, 0);
this.pokemonEggMovesContainer.add(this.eggMovesLabel);
for (let m = 0; m < 4; m++) {
const eggMoveContainer = globalScene.add.container(0, 16 + 14 * m);
const eggMoveBg = globalScene.add.nineslice(0, 0, "type_bgs", "unknown", 92, 14, 2, 2, 2, 2);
eggMoveBg.setOrigin(1, 0);
const eggMoveLabel = addTextObject(-eggMoveBg.width / 2, 0, "???", TextStyle.MOVE_LABEL);
eggMoveLabel.setOrigin(0.5, 0);
this.pokemonEggMoveBgs.push(eggMoveBg);
this.pokemonEggMoveLabels.push(eggMoveLabel);
eggMoveContainer.add([eggMoveBg, eggMoveLabel]);
this.pokemonEggMoveContainers.push(eggMoveContainer);
this.pokemonEggMovesContainer.add(eggMoveContainer);
}
this.statsContainer = new StatsContainer(6, 16).setVisible(false);
globalScene.add.existing(this.statsContainer);
this.add([
this.pokemonSprite,
this.shinyOverlay,
this.pokemonNumberText,
this.pokemonNameText,
this.pokemonUncaughtText,
this.pokemonPreferencesContainer,
this.pokemonPermanentInfoContainer,
this.pokemonStatisticsContainer,
this.pokemonMovesContainer,
this.pokemonEggMovesContainer,
this.statsContainer,
]);
this.allowTera = globalScene.gameData.achvUnlocks.hasOwnProperty(achvs.TERASTALLIZE.id);
}
setupPokemonPreferencesContainer(): GameObjects.Container {
const pokemonPreferencesContainer = globalScene.add.container(0, 0);
const textSettings = getStarterSelectTextSettings();
// The position should be set per language
const starterInfoXPos = textSettings?.starterInfoXPos || 31;
const starterInfoYOffset = textSettings?.starterInfoYOffset || 0;
// The font size should be set per language
const starterInfoTextSize = textSettings?.starterInfoTextSize || 56;
this.pokemonGenderText = addTextObject(96, 112, "", TextStyle.SUMMARY_ALT).setOrigin(0);
this.pokemonFormText = addTextObject(6, 42, "Form", TextStyle.WINDOW_ALT, {
fontSize: "42px",
}).setOrigin(0);
this.pokemonAbilityLabelText = addTextObject(
6,
127 + starterInfoYOffset,
i18next.t("starterSelectUiHandler:ability"),
TextStyle.SUMMARY_ALT,
{ fontSize: starterInfoTextSize },
).setOrigin(0);
this.pokemonAbilityText = addTextObject(starterInfoXPos, 127 + starterInfoYOffset, "", TextStyle.SUMMARY_ALT, {
fontSize: starterInfoTextSize,
})
.setOrigin(0)
.setInteractive(new Phaser.Geom.Rectangle(0, 0, 250, 55), Phaser.Geom.Rectangle.Contains);
this.pokemonPassiveLabelText = addTextObject(
6,
136 + starterInfoYOffset,
i18next.t("starterSelectUiHandler:passive"),
TextStyle.SUMMARY_ALT,
{ fontSize: starterInfoTextSize },
).setOrigin(0);
this.pokemonPassiveText = addTextObject(starterInfoXPos, 136 + starterInfoYOffset, "", TextStyle.SUMMARY_ALT, {
fontSize: starterInfoTextSize,
})
.setOrigin(0)
.setInteractive(new Phaser.Geom.Rectangle(0, 0, 250, 55), Phaser.Geom.Rectangle.Contains);
this.pokemonPassiveDisabledIcon = globalScene.add
.sprite(starterInfoXPos, 137 + starterInfoYOffset, "icon_stop")
.setOrigin(0, 0.5)
.setScale(0.35)
.setVisible(false);
this.pokemonPassiveLockedIcon = globalScene.add
.sprite(starterInfoXPos, 137 + starterInfoYOffset, "icon_lock")
.setOrigin(0, 0.5)
.setScale(0.42, 0.38)
.setVisible(false);
this.pokemonNatureLabelText = addTextObject(
6,
145 + starterInfoYOffset,
i18next.t("starterSelectUiHandler:nature"),
TextStyle.SUMMARY_ALT,
{ fontSize: starterInfoTextSize },
).setOrigin(0);
this.pokemonNatureText = addBBCodeTextObject(starterInfoXPos, 145 + starterInfoYOffset, "", TextStyle.SUMMARY_ALT, {
fontSize: starterInfoTextSize,
}).setOrigin(0);
this.pokemonShinyIcon = globalScene.add.sprite(12, 0, "shiny_icons").setScale(0.5);
this.teraIcon = globalScene.add.sprite(85, 63, "button_tera").setName("terastallize-icon").setFrame("fire");
pokemonPreferencesContainer.add([
this.pokemonGenderText,
this.pokemonFormText,
this.pokemonAbilityLabelText,
this.pokemonAbilityText,
this.pokemonPassiveLabelText,
this.pokemonPassiveText,
this.pokemonPassiveDisabledIcon,
this.pokemonPassiveLockedIcon,
this.pokemonNatureLabelText,
this.pokemonNatureText,
this.pokemonShinyIcon,
this.teraIcon,
]);
return pokemonPreferencesContainer;
}
setupPokemonPermanentInfoContainer(): GameObjects.Container {
const pokemonPermanentInfoContainer = globalScene.add.container(0, 0);
this.type1Icon = globalScene.add.sprite(8, 98, getLocalizedSpriteKey("types")).setScale(0.5).setOrigin(0);
this.type2Icon = globalScene.add.sprite(26, 98, getLocalizedSpriteKey("types")).setScale(0.5).setOrigin(0);
this.pokemonGrowthRateLabelText = addTextObject(
8,
106,
i18next.t("starterSelectUiHandler:growthRate"),
TextStyle.SUMMARY_ALT,
{ fontSize: "36px" },
).setOrigin(0);
this.pokemonGrowthRateText = addTextObject(34, 106, "", TextStyle.GROWTH_RATE_TYPE, { fontSize: "36px" }).setOrigin(
0,
);
this.pokemonLuckLabelText = addTextObject(8, 89, i18next.t("common:luckIndicator"), TextStyle.WINDOW_ALT, {
fontSize: "56px",
}).setOrigin(0);
this.pokemonLuckText = addTextObject(
8 + this.pokemonLuckLabelText.displayWidth + 2,
89,
"0",
TextStyle.LUCK_VALUE,
{ fontSize: "56px" },
).setOrigin(0);
pokemonPermanentInfoContainer.add([
this.type1Icon,
this.type2Icon,
this.pokemonGrowthRateLabelText,
this.pokemonGrowthRateText,
this.pokemonLuckLabelText,
this.pokemonLuckText,
]);
return pokemonPermanentInfoContainer;
}
setupPokemonStatisticsContainer(): GameObjects.Container {
const pokemonStatisticsContainer = globalScene.add.container(0, 0);
// Candy icon and count
this.pokemonCandyContainer = globalScene.add
.container(4.5, 18)
.setInteractive(new Phaser.Geom.Rectangle(0, 0, 30, 20), Phaser.Geom.Rectangle.Contains);
this.pokemonCandyIcon = globalScene.add.sprite(0, 0, "candy").setScale(0.5).setOrigin(0);
this.pokemonCandyOverlayIcon = globalScene.add.sprite(0, 0, "candy_overlay").setScale(0.5).setOrigin(0);
this.pokemonCandyDarknessOverlay = globalScene.add
.sprite(0, 0, "candy")
.setScale(0.5)
.setOrigin(0)
.setTint(0x000000)
.setAlpha(0.5);
this.pokemonCandyCountText = addTextObject(9.5, 0, "x0", TextStyle.WINDOW_ALT, { fontSize: "56px" }).setOrigin(0);
this.pokemonCandyContainer.add([
this.pokemonCandyIcon,
this.pokemonCandyOverlayIcon,
this.pokemonCandyDarknessOverlay,
this.pokemonCandyCountText,
]);
this.pokemonCaughtHatchedContainer = globalScene.add.container(2, 25).setScale(0.5);
const pokemonCaughtIcon = globalScene.add.sprite(1, 0, "items", "pb").setOrigin(0).setScale(0.75);
this.pokemonCaughtCountText = addTextObject(24, 4, "0", TextStyle.SUMMARY_ALT).setOrigin(0);
this.pokemonHatchedIcon = globalScene.add.sprite(1, 14, "egg_icons").setOrigin(0.15, 0.2).setScale(0.8);
this.pokemonHatchedCountText = addTextObject(24, 19, "0", TextStyle.SUMMARY_ALT).setOrigin(0);
this.pokemonMovesContainer = globalScene.add.container(102, 16).setScale(0.375);
this.pokemonCaughtHatchedContainer.add([
pokemonCaughtIcon,
this.pokemonCaughtCountText,
this.pokemonHatchedIcon,
this.pokemonHatchedCountText,
]);
pokemonStatisticsContainer.add([this.pokemonCandyContainer, this.pokemonCaughtHatchedContainer]);
return pokemonStatisticsContainer;
}
applyChallengeVisibility() {
const notFreshStart = !globalScene.gameMode.hasChallenge(Challenges.FRESH_START);
for (const container of this.pokemonEggMoveContainers) {
container.setVisible(notFreshStart);
}
this.eggMovesLabel.setVisible(notFreshStart);
// This is not enough, we need individual checks in setStarterSpecies too! :)
this.pokemonPassiveDisabledIcon.setVisible(notFreshStart);
this.pokemonPassiveLabelText.setVisible(notFreshStart);
this.pokemonPassiveLockedIcon.setVisible(notFreshStart);
this.pokemonPassiveText.setVisible(notFreshStart);
}
updateName(name: string) {
this.pokemonNameText.setText(name);
}
updateCandyCount(count: number) {
this.pokemonCandyCountText.setText(`×${count}`);
}
setNameAndNumber(species: PokemonSpecies, starterPreferences: StarterPreferences) {
this.pokemonNumberText.setText(padInt(species.speciesId, 4));
if (starterPreferences?.nickname) {
const name = decodeURIComponent(escape(atob(starterPreferences.nickname)));
this.pokemonNameText.setText(name);
} else {
this.pokemonNameText.setText(species.name);
}
}
setTypeIcons(type1: PokemonType | null, type2: PokemonType | null): void {
if (type1 !== null) {
this.type1Icon.setVisible(true).setFrame(PokemonType[type1].toLowerCase());
} else {
this.type1Icon.setVisible(false);
}
if (type2 !== null) {
this.type2Icon.setVisible(true).setFrame(PokemonType[type2].toLowerCase());
} else {
this.type2Icon.setVisible(false);
}
}
setShinyIcon(shiny = true, variant: Variant = 0) {
const tint = getVariantTint(variant);
this.pokemonShinyIcon.setFrame(getVariantIcon(variant)).setTint(tint).setVisible(shiny);
}
setNoSpecies() {
if (globalScene.ui.getTooltip().visible) {
globalScene.ui.hideTooltip();
}
this.pokemonAbilityText.off("pointerover");
this.pokemonPassiveText.off("pointerover");
if (this.statsMode) {
this.statsContainer.setVisible(false);
}
this.cleanStarterSprite();
}
setSpecies(species: PokemonSpecies, starterPreferences: StarterPreferences) {
this.speciesId = species.speciesId;
// First, we load from the dex entry to get defaults
const { dexEntry } = getSpeciesData(species.speciesId);
this.pokemonAbilityText.off("pointerover");
this.pokemonPassiveText.off("pointerover");
// Hiding ivs container if the species is not caught
if (this.statsMode) {
if (dexEntry?.caughtAttr) {
this.statsContainer.setVisible(true);
this.showStats();
} else {
this.statsContainer.setVisible(false);
}
}
if (dexEntry.caughtAttr) {
this.setNameAndNumber(species, starterPreferences);
const colorScheme = starterColors[species.speciesId];
this.pokemonUncaughtText.setVisible(false);
this.pokemonPermanentInfoContainer.setVisible(true);
this.pokemonStatisticsContainer.setVisible(true);
const luck = globalScene.gameData.getDexAttrLuck(dexEntry.caughtAttr);
this.pokemonLuckText
.setVisible(!!luck)
.setText(luck.toString())
.setTint(getVariantTint(Math.min(luck - 1, 2) as Variant));
this.pokemonLuckLabelText.setVisible(this.pokemonLuckText.visible);
//Growth translate
let growthReadable = toTitleCase(GrowthRate[species.growthRate]);
const growthAux = toCamelCase(growthReadable);
if (i18next.exists("growth:" + growthAux)) {
growthReadable = i18next.t(("growth:" + growthAux) as any);
}
this.pokemonGrowthRateText
.setText(growthReadable)
.setColor(getGrowthRateColor(species.growthRate))
.setShadowColor(getGrowthRateColor(species.growthRate, true));
this.pokemonCaughtCountText.setText(`${dexEntry.caughtCount}`);
if (species.speciesId === SpeciesId.MANAPHY || species.speciesId === SpeciesId.PHIONE) {
this.pokemonHatchedIcon.setFrame("manaphy");
} else {
this.pokemonHatchedIcon.setFrame(getEggTierForSpecies(species));
}
this.pokemonHatchedCountText.setText(`${dexEntry.hatchedCount}`);
const defaultDexAttr = getDexAttrFromPreferences(species.speciesId, starterPreferences);
const defaultProps = globalScene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr);
this.setShinyIcon(defaultProps.shiny, defaultProps.variant);
if (pokemonPrevolutions.hasOwnProperty(species.speciesId)) {
this.pokemonCaughtHatchedContainer.setVisible(false);
this.pokemonShinyIcon.setY(104);
this.pokemonFormText.setY(25);
} else {
this.pokemonCaughtHatchedContainer.setVisible(true);
this.pokemonShinyIcon.setY(86);
this.pokemonCandyIcon.setTint(argbFromRgba(rgbHexToRgba(colorScheme[0])));
this.pokemonCandyOverlayIcon.setTint(argbFromRgba(rgbHexToRgba(colorScheme[1])));
this.pokemonCandyCountText.setText(`×${globalScene.gameData.starterData[species.speciesId].candyCount}`);
this.pokemonFormText.setY(42);
this.pokemonHatchedIcon.setVisible(true);
this.pokemonHatchedCountText.setVisible(true);
const { currentFriendship, friendshipCap } = getFriendship(species.speciesId);
const candyCropY = 16 - 16 * (currentFriendship / friendshipCap);
this.pokemonCandyDarknessOverlay.setCrop(0, 0, 16, candyCropY);
this.pokemonCandyContainer
.setVisible(true)
.on("pointerover", () => {
globalScene.ui.showTooltip("", `${currentFriendship}/${friendshipCap}`, true);
this.activeTooltip = "CANDY";
})
.on("pointerout", () => {
globalScene.ui.hideTooltip();
this.activeTooltip = undefined;
});
}
const props = globalScene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr);
props.formIndex = starterPreferences?.form ?? props.formIndex;
const speciesForm = getPokemonSpeciesForm(species.speciesId, props.formIndex);
this.setTypeIcons(speciesForm.type1, speciesForm.type2);
this.pokemonSprite.clearTint();
} else if (dexEntry.seenAttr) {
this.cleanStarterSprite(species, true);
const props = globalScene.gameData.getSpeciesDefaultDexAttrProps();
const formIndex = props.formIndex;
const female = props.female;
const shiny = props.shiny;
const variant = props.variant;
this.updateSprite(species, female, formIndex, shiny, variant);
this.pokemonSprite.setVisible(true);
this.pokemonSprite.setTint(0x808080);
} else {
this.cleanStarterSprite(species);
const props = globalScene.gameData.getSpeciesDefaultDexAttrProps();
const formIndex = props.formIndex;
const female = props.female;
const shiny = props.shiny;
const variant = props.variant;
this.updateSprite(species, female, formIndex, shiny, variant);
this.pokemonSprite.setVisible(true);
this.pokemonSprite.setTint(0x000000);
}
}
cleanStarterSprite(species?: PokemonSpecies, isSeen = false) {
if (isSeen && species) {
this.setNameAndNumber(species, {});
} else {
this.pokemonNumberText.setText(padInt(0, 4));
this.pokemonNameText.setText(species ? "???" : "");
}
this.pokemonSprite.setVisible(!!species);
this.pokemonUncaughtText.setVisible(!!species);
this.pokemonPermanentInfoContainer.setVisible(false);
this.pokemonStatisticsContainer.setVisible(false);
this.resetSpeciesDetails();
}
resetSpeciesDetails() {
globalScene.ui.hideTooltip();
this.pokemonPreferencesContainer.setVisible(false);
if (this.assetLoadCancelled) {
this.assetLoadCancelled.value = true;
this.assetLoadCancelled = null;
}
this.shinyOverlay.setVisible(false);
this.pokemonNumberText
.setColor(getTextColor(TextStyle.SUMMARY))
.setShadowColor(getTextColor(TextStyle.SUMMARY, true));
for (let m = 0; m < 4; m++) {
this.pokemonMoveContainers[m].setVisible(false);
}
this.pokemonEggMovesContainer.setVisible(false);
this.pokemonAdditionalMoveCountLabel.setVisible(false);
}
setSpeciesDetails(species: PokemonSpecies, options: SpeciesDetails = {}): void {
// Here we pass some options to override everything else
let { shiny, formIndex, female, variant, abilityIndex, natureIndex, teraType } = options;
// 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);
this.updateCandyTooltip();
// Ensuring that gender and form are consistent
if (species.forms?.find(f => f.formKey === "female")) {
if (female !== undefined) {
formIndex = female ? 1 : 0;
} else if (formIndex !== undefined) {
female = formIndex === 1;
}
}
this.pokemonSprite.setVisible(false);
this.teraIcon.setVisible(false);
if (this.assetLoadCancelled) {
this.assetLoadCancelled.value = true;
this.assetLoadCancelled = null;
}
this.pokemonPreferencesContainer.setVisible(true);
this.shinyOverlay.setVisible(shiny ?? false); // TODO: is false the correct default?
this.pokemonNumberText.setColor(
getTextColor(shiny ? TextStyle.SUMMARY_DEX_NUM_GOLD : TextStyle.SUMMARY_DEX_NUM, false),
);
this.pokemonNumberText.setShadowColor(
getTextColor(shiny ? TextStyle.SUMMARY_DEX_NUM_GOLD : TextStyle.SUMMARY_DEX_NUM, true),
);
const assetLoadCancelled = new BooleanHolder(false);
this.assetLoadCancelled = assetLoadCancelled;
// TODO: should this line be here, or in .updateSprite?
female ??= false;
if (shouldUpdateSprite) {
this.updateSprite(species, female, formIndex, shiny, variant);
} else {
this.pokemonSprite.setVisible(!this.statsMode);
}
// Set the gender text
if (species.malePercent !== null) {
const gender = !female ? Gender.MALE : Gender.FEMALE;
this.pokemonGenderText
.setText(getGenderSymbol(gender))
.setColor(getGenderColor(gender))
.setShadowColor(getGenderColor(gender, true));
} else {
this.pokemonGenderText.setText("");
}
// Update ability text
let ability: Ability;
if (species.forms?.length > 1) {
ability = allAbilities[species.forms[formIndex ?? 0].getAbility(abilityIndex!)];
} else {
ability = allAbilities[species.getAbility(abilityIndex!)]; // TODO: is this bang correct?
}
const isHidden = abilityIndex === (species.ability2 ? 2 : 1);
this.pokemonAbilityText
.setText(ability.name)
.setColor(getTextColor(!isHidden ? TextStyle.SUMMARY_ALT : TextStyle.SUMMARY_GOLD))
.setShadowColor(getTextColor(!isHidden ? TextStyle.SUMMARY_ALT : TextStyle.SUMMARY_GOLD, true));
if (this.pokemonAbilityText.visible) {
if (this.activeTooltip === "ABILITY") {
globalScene.ui.editTooltip(`${ability.name}`, `${ability.description}`);
}
this.pokemonAbilityText.on("pointerover", () => {
globalScene.ui.showTooltip(`${ability.name}`, `${ability.description}`, true);
this.activeTooltip = "ABILITY";
});
this.pokemonAbilityText.on("pointerout", () => {
globalScene.ui.hideTooltip();
this.activeTooltip = undefined;
});
}
this.updatePassiveDisplay(species.speciesId, formIndex);
// Update nature text
this.pokemonNatureText.setText(getNatureName(natureIndex as unknown as Nature, true, true, false));
// Update form text
const speciesForm = getPokemonSpeciesForm(species.speciesId, formIndex!); // TODO: is the bang correct?
const formText = species.getFormNameToDisplay(formIndex);
this.pokemonFormText.setText(formText);
// Update type icons
this.setTypeIcons(speciesForm.type1, speciesForm.type2);
// Update tera icon
const newTeraType = teraType ?? speciesForm.type1;
this.teraIcon.setFrame(PokemonType[newTeraType].toLowerCase());
this.teraIcon.setVisible(!this.statsMode && this.allowTera);
}
showStats(): void {
const { dexEntry } = getSpeciesData(this.speciesId);
this.statsContainer.setVisible(true);
this.statsContainer.updateIvs(dexEntry.ivs);
}
updatePassiveDisplay(speciesId: SpeciesId, formIndex = 0) {
this.pokemonPassiveLabelText.setVisible(false);
this.pokemonPassiveText.setVisible(false);
this.pokemonPassiveDisabledIcon.setVisible(false);
this.pokemonPassiveLockedIcon.setVisible(false);
const isFreshStartChallenge = globalScene.gameMode.hasChallenge(Challenges.FRESH_START);
const { starterDataEntry } = getSpeciesData(speciesId);
const passiveAttr = starterDataEntry.passiveAttr;
const passiveAbility = allAbilities[getPokemonSpecies(speciesId).getPassiveAbility(formIndex)];
if (passiveAbility) {
const isUnlocked = !!(passiveAttr & Passive.UNLOCKED);
const isEnabled = !!(passiveAttr & Passive.ENABLED);
const textStyle = isUnlocked && isEnabled ? TextStyle.SUMMARY_ALT : TextStyle.SUMMARY_GRAY;
const textAlpha = isUnlocked && isEnabled ? 1 : 0.5;
this.pokemonPassiveLabelText
.setVisible(!isFreshStartChallenge)
.setColor(getTextColor(TextStyle.SUMMARY_ALT))
.setShadowColor(getTextColor(TextStyle.SUMMARY_ALT, true));
this.pokemonPassiveText
.setVisible(!isFreshStartChallenge)
.setText(passiveAbility.name)
.setColor(getTextColor(textStyle))
.setAlpha(textAlpha)
.setShadowColor(getTextColor(textStyle, true));
if (this.activeTooltip === "PASSIVE") {
globalScene.ui.editTooltip(`${passiveAbility.name}`, `${passiveAbility.description}`);
}
if (this.pokemonPassiveText.visible) {
this.pokemonPassiveText.on("pointerover", () => {
globalScene.ui.showTooltip(`${passiveAbility.name}`, `${passiveAbility.description}`, true);
this.activeTooltip = "PASSIVE";
});
this.pokemonPassiveText.on("pointerout", () => {
globalScene.ui.hideTooltip();
this.activeTooltip = undefined;
});
}
const iconPosition = {
x: this.pokemonPassiveText.x + this.pokemonPassiveText.displayWidth + 1,
y: this.pokemonPassiveText.y + this.pokemonPassiveText.displayHeight / 2,
};
this.pokemonPassiveDisabledIcon
.setVisible(isUnlocked && !isEnabled && !isFreshStartChallenge)
.setPosition(iconPosition.x, iconPosition.y);
this.pokemonPassiveLockedIcon
.setVisible(!isUnlocked && !isFreshStartChallenge)
.setPosition(iconPosition.x, iconPosition.y);
} else if (this.activeTooltip === "PASSIVE") {
// No passive and passive tooltip is active > hide it
globalScene.ui.hideTooltip();
}
}
updateSprite(
species: PokemonSpecies,
female: boolean,
formIndex?: number | undefined,
shiny?: boolean,
variant?: Variant | undefined,
) {
species.loadAssets(female, formIndex, shiny, variant, true).then(() => {
if (this.assetLoadCancelled?.value) {
return;
}
this.assetLoadCancelled = null;
// Note: Bangs are correct due to `female ??= false` above
this.pokemonSprite
.play(species.getSpriteKey(female!, formIndex, shiny, variant))
.setPipelineData("shiny", shiny)
.setPipelineData("variant", variant)
.setPipelineData("spriteKey", species.getSpriteKey(female!, formIndex, shiny, variant))
.setVisible(!this.statsMode);
});
}
updateCandyTooltip() {
if (this.activeTooltip === "CANDY") {
if (this.speciesId && this.pokemonCandyContainer.visible) {
const { currentFriendship, friendshipCap } = getFriendship(this.speciesId);
globalScene.ui.editTooltip("", `${currentFriendship}/${friendshipCap}`);
} else {
globalScene.ui.hideTooltip();
}
}
}
updateMoveset(starterMoveset: StarterMoveset, totalMoves: number) {
for (let m = 0; m < 4; m++) {
const move = m < starterMoveset.length ? allMoves[starterMoveset[m]] : null;
this.pokemonMoveBgs[m].setFrame(PokemonType[move ? move.type : PokemonType.UNKNOWN].toString().toLowerCase());
this.pokemonMoveLabels[m].setText(move ? move.name : "-");
this.pokemonMoveContainers[m].setVisible(!!move);
}
this.pokemonAdditionalMoveCountLabel.setText(`(+${Math.max(totalMoves - 4, 0)})`).setVisible(totalMoves > 4);
}
updateEggMoves(eggMoves: number) {
for (let em = 0; em < 4; em++) {
const eggMove = allMoves[speciesEggMoves[this.speciesId][em]];
const eggMoveUnlocked = eggMove && eggMoves & (1 << em);
this.pokemonEggMoveBgs[em].setFrame(
PokemonType[eggMove ? eggMove.type : PokemonType.UNKNOWN].toString().toLowerCase(),
);
this.pokemonEggMoveLabels[em].setText(eggMove && eggMoveUnlocked ? eggMove.name : "???");
}
this.pokemonEggMovesContainer.setVisible(true);
}
showIvs() {
this.showStats();
this.statsMode = true;
this.pokemonSprite.setVisible(false);
this.teraIcon.setVisible(false);
}
hideIvs(caught = true) {
this.statsMode = false;
this.statsContainer.setVisible(false);
this.pokemonSprite.setVisible(caught);
this.teraIcon.setVisible(this.allowTera);
}
clear() {
globalScene.ui.hideTooltip();
this.activeTooltip = undefined;
}
}

View File

@ -6,9 +6,10 @@ import { TextStyle } from "#enums/text-style";
import { UiMode } from "#enums/ui-mode";
import { AchvBar } from "#ui/containers/achv-bar";
import type { BgmBar } from "#ui/containers/bgm-bar";
import { PokedexPageUiHandler } from "#ui/containers/pokedex-page-ui-handler";
import { PokedexPageUiHandler } from "#ui/handlers/pokedex-page-ui-handler";
import { SavingIconHandler } from "#ui/containers/saving-icon-handler";
import { GamepadBindingUiHandler } from "#ui/gamepad-binding-ui-handler";
import { PokedexUiHandler } from "#ui/handlers/pokedex-ui-handler";
import { AchvsUiHandler } from "#ui/handlers/achvs-ui-handler";
import { AutoCompleteUiHandler } from "#ui/handlers/autocomplete-ui-handler";
import { AwaitableUiHandler } from "#ui/handlers/awaitable-ui-handler";
@ -33,7 +34,6 @@ import { ModifierSelectUiHandler } from "#ui/handlers/modifier-select-ui-handler
import { MysteryEncounterUiHandler } from "#ui/handlers/mystery-encounter-ui-handler";
import { PartyUiHandler } from "#ui/handlers/party-ui-handler";
import { PokedexScanUiHandler } from "#ui/handlers/pokedex-scan-ui-handler";
import { PokedexUiHandler } from "#ui/handlers/pokedex-ui-handler";
import { RegistrationFormUiHandler } from "#ui/handlers/registration-form-ui-handler";
import { RenameFormUiHandler } from "#ui/handlers/rename-form-ui-handler";
import { RunHistoryUiHandler } from "#ui/handlers/run-history-ui-handler";

View File

@ -1,6 +1,6 @@
import { loggedInUser } from "#app/account";
import { saveKey } from "#app/constants";
import type { StarterAttributes } from "#system/game-data";
import type { StarterPreferences } from "#system/game-data";
import { AES, enc } from "crypto-js";
/**
@ -78,19 +78,19 @@ export function isBareObject(obj: any): boolean {
const StarterPrefers_DEFAULT: string = "{}";
let StarterPrefers_private_latest: string = StarterPrefers_DEFAULT;
export interface StarterPreferences {
[key: number]: StarterAttributes | undefined;
export interface AllStarterPreferences {
[key: number]: StarterPreferences | undefined;
}
// called on starter selection show once
export function loadStarterPreferences(): StarterPreferences {
// called on starter selection show once
export function loadStarterPreferences(): AllStarterPreferences {
return JSON.parse(
(StarterPrefers_private_latest =
localStorage.getItem(`starterPrefs_${loggedInUser?.username}`) || StarterPrefers_DEFAULT),
);
}
export function saveStarterPreferences(prefs: StarterPreferences): void {
export function saveStarterPreferences(prefs: AllStarterPreferences): void {
// Fastest way to check if an object has any properties (does no allocation)
if (isBareObject(prefs)) {
console.warn("Refusing to save empty starter preferences");

View File

@ -6,10 +6,10 @@ import { DropDownColumn } from "#enums/drop-down-column";
import { PokemonType } from "#enums/pokemon-type";
import { SpeciesId } from "#enums/species-id";
import { UiMode } from "#enums/ui-mode";
import type { StarterAttributes } from "#system/game-data";
import type { StarterPreferences } from "#system/game-data";
import { GameManager } from "#test/test-utils/game-manager";
import { FilterTextRow } from "#ui/containers/filter-text";
import { PokedexPageUiHandler } from "#ui/containers/pokedex-page-ui-handler";
import { PokedexPageUiHandler } from "#ui/handlers/pokedex-page-ui-handler";
import { PokedexUiHandler } from "#ui/handlers/pokedex-ui-handler";
import { getPokemonSpecies } from "#utils/pokemon-utils";
import Phaser from "phaser";
@ -84,12 +84,12 @@ describe("UI - Pokedex", () => {
*/
async function runToPokedexPage(
species: PokemonSpecies,
starterAttributes: StarterAttributes = {},
starterPreferences: StarterPreferences = {},
): Promise<PokedexPageUiHandler> {
// Open the pokedex UI.
await game.runToTitle();
await game.scene.ui.setOverlayMode(UiMode.POKEDEX_PAGE, species, starterAttributes);
await game.scene.ui.setOverlayMode(UiMode.POKEDEX_PAGE, species, starterPreferences);
// Get the handler for the current UI.
const handler = game.scene.ui.getHandler();