[QoL] Starter UI selection update to allow removing specific pokemon from party (#1983)

* Initial commits with logic to remove starters if they're in your party. Still need to make it work so that the starter selection cursor disappears when a starter is unselected

* Updated code to be able to remove pokemon, including the side icons and cursor locations

* Fixed popstarter to work with any index

* Updating code to allow navigation of starter icons

* Updating code to allow navigation of party starter icons

* Updaing navigation of party icons

* Updated logic to fix incorrect icon in top left of pokemon options when navigating the starter icons

* Updated logic to include the ability to navigate and interact with the starter icons

* Forgot to push the actual starter-select-ui-handler. Might be a bit hard to test things out without that :)

* Removed some unnecessary comments

* Fixed small bug with not being able to move from the far right to the gen selection when the starter icons were empty

* Updated code to not be using a method to generate the party menu and made it more like it used to be. This should help with merge conflicts in the future

* I committed the merged version but forgot to make the starter-select-ui-handler staged after making the changes

* Accidentally broke challenges that had a specific typing requirement with last commit. This should fix it

* Changed how navigation worked based on popular demand

* Fixed code review comments

* Accidentally left in a whole block of commented code. Intentionally removing it now

* Started adding logic for mono type challenge runs to not break the game if the user tries to start a run with an invalid party

* Updated the text to say the party is invalid

* Updated logic to make invalid pokemon greyed out when no valid pokemon are in your party

* Added comments on some code

* Updated locales to include the key for trying to start with invalid parties during a challenge

* Fixed some code from a bad merge where a challenge related param that was previously a number now needed to be a boolean and wasn't

* Removed comment as per review
This commit is contained in:
Opaque02 2024-07-23 08:32:49 +10:00 committed by GitHub
parent e2829f5225
commit 32d1b6b914
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 407 additions and 168 deletions

1
.gitignore vendored
View File

@ -40,3 +40,4 @@ coverage
/typedoc
/dependency-graph.svg
/.vs

View File

@ -270,9 +270,11 @@ export abstract class Challenge {
* @param valid {@link Utils.BooleanHolder} A BooleanHolder, the value gets set to false if the pokemon isn't allowed.
* @param dexAttr {@link DexAttrProps} The dex attributes of the pokemon.
* @param soft {@link boolean} If true, allow it if it could become a valid pokemon.
* @param checkEvolutions {@link boolean} If true, check the pokemon's future evolutions
* @param checkForms {@link boolean} If true, check the pokemon's alternative forms
* @returns {@link boolean} Whether this function did anything.
*/
applyStarterChoice(pokemon: PokemonSpecies, valid: Utils.BooleanHolder, dexAttr: DexAttrProps, soft: boolean = false): boolean {
applyStarterChoice(pokemon: PokemonSpecies, valid: Utils.BooleanHolder, dexAttr: DexAttrProps, soft: boolean = false, checkEvolutions?: boolean, checkForms?: boolean): boolean {
return false;
}
@ -400,7 +402,7 @@ export class SingleGenerationChallenge extends Challenge {
super(Challenges.SINGLE_GENERATION, 9);
}
applyStarterChoice(pokemon: PokemonSpecies, valid: Utils.BooleanHolder, dexAttr: DexAttrProps, soft: boolean = false): boolean {
applyStarterChoice(pokemon: PokemonSpecies, valid: Utils.BooleanHolder, dexAttr: DexAttrProps, soft: boolean = false, checkEvolutions?: boolean): boolean {
/**
* We have special code below for victini because it is classed as a generation 4 pokemon in the code
* despite being a generation 5 pokemon. This is due to UI constraints, the starter select screen has
@ -409,11 +411,12 @@ export class SingleGenerationChallenge extends Challenge {
*/
const starterGeneration = pokemon.speciesId === Species.VICTINI ? 5 : pokemon.generation;
const generations = [starterGeneration];
const checkPokemonEvolutions = checkEvolutions ?? true as boolean;
if (soft) {
const speciesToCheck = [pokemon.speciesId];
while (speciesToCheck.length) {
const checking = speciesToCheck.pop();
if (pokemonEvolutions.hasOwnProperty(checking)) {
if (pokemonEvolutions.hasOwnProperty(checking) && checkPokemonEvolutions) {
pokemonEvolutions[checking].forEach(e => {
speciesToCheck.push(e.speciesId);
generations.push(getPokemonSpecies(e.speciesId).generation);
@ -534,20 +537,22 @@ export class SingleTypeChallenge extends Challenge {
super(Challenges.SINGLE_TYPE, 18);
}
applyStarterChoice(pokemon: PokemonSpecies, valid: Utils.BooleanHolder, dexAttr: DexAttrProps, soft: boolean = false): boolean {
applyStarterChoice(pokemon: PokemonSpecies, valid: Utils.BooleanHolder, dexAttr: DexAttrProps, soft: boolean = false, checkEvolutions?: boolean, checkForms?: boolean): boolean {
const speciesForm = getPokemonSpeciesForm(pokemon.speciesId, dexAttr.formIndex);
const types = [speciesForm.type1, speciesForm.type2];
const checkPokemonEvolutions = checkEvolutions ?? true as boolean;
const checkPokemonForms = checkForms ?? true as boolean;
if (soft) {
const speciesToCheck = [pokemon.speciesId];
while (speciesToCheck.length) {
const checking = speciesToCheck.pop();
if (pokemonEvolutions.hasOwnProperty(checking)) {
if (pokemonEvolutions.hasOwnProperty(checking) && checkPokemonEvolutions) {
pokemonEvolutions[checking].forEach(e => {
speciesToCheck.push(e.speciesId);
types.push(getPokemonSpecies(e.speciesId).type1, getPokemonSpecies(e.speciesId).type2);
});
}
if (pokemonFormChanges.hasOwnProperty(checking)) {
if (pokemonFormChanges.hasOwnProperty(checking) && checkPokemonForms) {
pokemonFormChanges[checking].forEach(f1 => {
getPokemonSpecies(checking).forms.forEach(f2 => {
if (f1.formKey === f2.formKey) {
@ -743,7 +748,7 @@ export class LowerStarterPointsChallenge extends Challenge {
* @param soft {@link boolean} If true, allow it if it could become a valid pokemon.
* @returns True if any challenge was successfully applied.
*/
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.STARTER_CHOICE, pokemon: PokemonSpecies, valid: Utils.BooleanHolder, dexAttr: DexAttrProps, soft: boolean): boolean;
export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType.STARTER_CHOICE, pokemon: PokemonSpecies, valid: Utils.BooleanHolder, dexAttr: DexAttrProps, soft: boolean, checkEvolutions?: boolean, checkForms?: boolean): boolean;
/**
* Apply all challenges that modify available total starter points.
* @param gameMode {@link GameMode} The current gameMode
@ -851,7 +856,7 @@ export function applyChallenges(gameMode: GameMode, challengeType: ChallengeType
if (c.value !== 0) {
switch (challengeType) {
case ChallengeType.STARTER_CHOICE:
ret ||= c.applyStarterChoice(args[0], args[1], args[2], args[3]);
ret ||= c.applyStarterChoice(args[0], args[1], args[2], args[3], args[4], args[5]);
break;
case ChallengeType.STARTER_POINTS:
ret ||= c.applyStarterPoints(args[0]);

View File

@ -7,6 +7,7 @@ import { SimpleTranslationEntries } from "#app/interfaces/locales";
*/
export const starterSelectUiHandler: SimpleTranslationEntries = {
"confirmStartTeam": "Mit diesen Pokémon losziehen?",
"invalidParty": "This is not a valid starting party!",
"gen1": "I",
"gen2": "II",
"gen3": "III",

View File

@ -6,7 +6,8 @@ import { SimpleTranslationEntries } from "#app/interfaces/locales";
* account interactions, descriptive text, etc.
*/
export const starterSelectUiHandler: SimpleTranslationEntries = {
"confirmStartTeam":"Begin with these Pokémon?",
"confirmStartTeam": "Begin with these Pokémon?",
"invalidParty": "This is not a valid starting party!",
"gen1": "I",
"gen2": "II",
"gen3": "III",
@ -22,6 +23,7 @@ export const starterSelectUiHandler: SimpleTranslationEntries = {
"nature": "Nature:",
"eggMoves": "Egg Moves",
"addToParty": "Add to Party",
"removeFromParty": "Remove from Party",
"toggleIVs": "Toggle IVs",
"manageMoves": "Manage Moves",
"manageNature": "Manage Nature",

View File

@ -6,7 +6,8 @@ import { SimpleTranslationEntries } from "#app/interfaces/locales";
* account interactions, descriptive text, etc.
*/
export const starterSelectUiHandler: SimpleTranslationEntries = {
"confirmStartTeam":"¿Comenzar con estos Pokémon?",
"confirmStartTeam": "¿Comenzar con estos Pokémon?",
"invalidParty": "This is not a valid starting party!",
"gen1": "I",
"gen2": "II",
"gen3": "III",

View File

@ -6,7 +6,8 @@ import { SimpleTranslationEntries } from "#app/interfaces/locales";
* account interactions, descriptive text, etc.
*/
export const starterSelectUiHandler: SimpleTranslationEntries = {
"confirmStartTeam":"Commencer avec ces Pokémon ?",
"confirmStartTeam": "Commencer avec ces Pokémon ?",
"invalidParty": "This is not a valid starting party!",
"gen1": "1G",
"gen2": "2G",
"gen3": "3G",

View File

@ -6,7 +6,8 @@ import { SimpleTranslationEntries } from "#app/interfaces/locales";
* account interactions, descriptive text, etc.
*/
export const starterSelectUiHandler: SimpleTranslationEntries = {
"confirmStartTeam":"Vuoi iniziare con questi Pokémon?",
"confirmStartTeam": "Vuoi iniziare con questi Pokémon?",
"invalidParty": "This is not a valid starting party!",
"gen1": "1ª",
"gen2": "2ª",
"gen3": "3ª",

View File

@ -7,6 +7,7 @@ import { SimpleTranslationEntries } from "#app/interfaces/locales";
*/
export const starterSelectUiHandler: SimpleTranslationEntries = {
"confirmStartTeam": "이 포켓몬들로 시작하시겠습니까?",
"invalidParty": "This is not a valid starting party!",
"gen1": "1세대",
"gen2": "2세대",
"gen3": "3세대",

View File

@ -7,6 +7,7 @@ import { SimpleTranslationEntries } from "#app/interfaces/locales";
*/
export const starterSelectUiHandler: SimpleTranslationEntries = {
"confirmStartTeam": "Começar com esses Pokémon?",
"invalidParty": "This is not a valid starting party!",
"gen1": "G1",
"gen2": "G2",
"gen3": "G3",

View File

@ -6,7 +6,8 @@ import { SimpleTranslationEntries } from "#app/interfaces/locales";
* account interactions, descriptive text, etc.
*/
export const starterSelectUiHandler: SimpleTranslationEntries = {
"confirmStartTeam":"使用这些宝可梦开始游戏吗?",
"confirmStartTeam": "使用这些宝可梦开始游戏吗?",
"invalidParty": "This is not a valid starting party!",
"gen1": "I",
"gen2": "II",
"gen3": "III",

View File

@ -6,7 +6,8 @@ import { SimpleTranslationEntries } from "#app/interfaces/locales";
* account interactions, descriptive text, etc.
*/
export const starterSelectUiHandler: SimpleTranslationEntries = {
"confirmStartTeam":"使用這些寶可夢開始嗎?",
"confirmStartTeam": "使用這些寶可夢開始嗎?",
"invalidParty": "This is not a valid starting party!",
"gen1": "I",
"gen2": "II",
"gen3": "III",

View File

@ -39,6 +39,7 @@ import { Moves } from "#enums/moves";
import { Species } from "#enums/species";
import {Button} from "#enums/buttons";
import { EggSourceType } from "#app/enums/egg-source-types.js";
import AwaitableUiHandler from "./awaitable-ui-handler";
export type StarterSelectCallback = (starters: Starter[]) => void;
@ -224,6 +225,9 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
private genMode: boolean;
private statsMode: boolean;
private starterIconsCursorXOffset: number = -2;
private starterIconsCursorYOffset: number = 1;
private starterIconsCursorIndex: number;
private dexAttrCursor: bigint = 0n;
private abilityCursor: integer = -1;
private natureCursor: integer = -1;
@ -260,6 +264,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
private starterIcons: Phaser.GameObjects.Sprite[];
private genCursorObj: Phaser.GameObjects.Image;
private genCursorHighlightObj: Phaser.GameObjects.Image;
private starterIconsCursorObj: Phaser.GameObjects.Image;
private valueLimitLabel: Phaser.GameObjects.Text;
private startCursorObj: Phaser.GameObjects.NineSlice;
private starterValueLabels: Phaser.GameObjects.Text[];
@ -447,6 +452,12 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.genCursorObj.setOrigin(0, 0);
this.starterSelectContainer.add(this.genCursorObj);
this.starterIconsCursorObj = this.scene.add.image(this.genCursorObj.x, 64, "select_gen_cursor");
this.starterIconsCursorObj.setName("starter-icons-cursor");
this.starterIconsCursorObj.setVisible(false);
this.starterIconsCursorObj.setOrigin(0, 0);
this.starterSelectContainer.add(this.starterIconsCursorObj);
this.valueLimitLabel = addTextObject(this.scene, 124, 150, "0/10", TextStyle.TOOLTIP_CONTENT);
this.valueLimitLabel.setOrigin(0.5, 0);
this.starterSelectContainer.add(this.valueLimitLabel);
@ -1064,7 +1075,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.toggleStatsMode(false);
success = true;
} else if (this.starterCursors.length) {
this.popStarter();
this.popStarter(this.starterCursors.length - 1);
success = true;
this.updateInstructions();
} else {
@ -1078,7 +1089,9 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.scene.getCurrentPhase().end();
success = true;
}
} else if (this.startCursorObj.visible) {
} else if (this.startCursorObj.visible) { // this checks to see if the start button is selected
const genStarters = this.starterSelectGenIconContainers[this.getGenCursorWithScroll()].getAll().length;
const rows = Math.ceil(genStarters / 9);
switch (button) {
case Button.ACTION:
if (this.tryStart(true)) {
@ -1088,6 +1101,16 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
}
break;
case Button.UP:
this.startCursorObj.setVisible(false);
if (this.starterCursors.length > 0) {
this.starterIconsCursorIndex = this.starterCursors.length - 1;
this.moveStarterIconsCursor(this.starterIconsCursorIndex);
} else {
this.setGenMode(true);
}
success = true;
break;
case Button.DOWN:
this.startCursorObj.setVisible(false);
this.setGenMode(true);
success = true;
@ -1095,27 +1118,37 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
case Button.LEFT:
this.startCursorObj.setVisible(false);
this.setGenMode(false);
this.setCursor(this.cursor + 8);
this.setCursor(genStarters - 1);
success = true;
break;
case Button.RIGHT:
this.startCursorObj.setVisible(false);
this.setGenMode(false);
this.setCursor((rows - 1) * 9);
success = true;
break;
}
} else if (this.genMode) {
} else if (this.genMode && this.genCursorObj.visible) { // this checks to see if the generation selection icons are selected
switch (button) {
case Button.UP:
if (this.genCursor) {
if (this.genCursor > 0) {
success = this.setCursor(this.genCursor - 1);
} else {
this.startCursorObj.setVisible(true);
this.setGenMode(true);
success = true;
}
break;
case Button.DOWN:
if (this.genCursor < 2) {
success = this.setCursor(this.genCursor + 1);
} else {
if (this.starterCursors.length === 0) {
this.startCursorObj.setVisible(true);
} else {
this.starterIconsCursorIndex = 0;
this.moveStarterIconsCursor(this.starterIconsCursorIndex);
}
this.setGenMode(true);
success = true;
}
@ -1129,66 +1162,72 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
break;
}
} else {
/**
* This code generates the menu for a pokemon when you press the action button
* This works in modules; it does a check for each option to see if it's valid, and if so, will add that option to the menu
* As an example, if you try to add an invalid pokemon, the "Add to Party" option won't show up
* But if you can still use candies or change natures, those menu items will be added
* Once it's all done, it displays the menu for you
**/
if (button === Button.ACTION) {
if (!this.speciesStarterDexEntry?.caughtAttr) {
error = true;
} else if (this.starterCursors.length < 6) {
const options = [
} else if (this.starterCursors.length < 6) { // checks to see you have less than 6 pokemon in your party
let pokemonGen;
let pokemonCursor;
// this gets the correct generation and pokemon cursor depending on whether you're in the starter screen or the party icons
if (!this.starterIconsCursorObj.visible) {
pokemonGen = this.getGenCursorWithScroll();
pokemonCursor = this.cursor;
} else {
pokemonGen = this.starterGens[this.starterIconsCursorIndex];
pokemonCursor = this.starterCursors[this.starterIconsCursorIndex];
}
const ui = this.getUi();
let options = [];
const [isDupe, removeIndex]: [boolean, number] = this.isInParty(pokemonGen, pokemonCursor); // checks to see if the pokemon is a duplicate; if it is, returns the index that will be removed
const species = this.genSpecies[pokemonGen][pokemonCursor];
const isPartyValid = this.isPartyValid();
const isValidForChallenge = new Utils.BooleanHolder(true);
if (isPartyValid) {
Challenge.applyChallenges(this.scene.gameMode, Challenge.ChallengeType.STARTER_CHOICE, species, isValidForChallenge, this.scene.gameData.getSpeciesDexAttrProps(species, this.scene.gameData.getSpeciesDefaultDexAttr(species, false, true)), !!(this.starterGens.length));
} else {
Challenge.applyChallenges(this.scene.gameMode, Challenge.ChallengeType.STARTER_CHOICE, species, isValidForChallenge, this.scene.gameData.getSpeciesDexAttrProps(species, this.scene.gameData.getSpeciesDefaultDexAttr(species, false, true)), !!(this.starterGens.length), false, false);
}
const currentPartyValue = this.starterGens.reduce((total: number, gen: number, i: number) => total += this.scene.gameData.getSpeciesStarterValue(this.genSpecies[gen][this.starterCursors[i]].speciesId), 0);
const newCost = this.scene.gameData.getSpeciesStarterValue(species.speciesId);
if (!isDupe && isValidForChallenge.value && currentPartyValue + newCost <= this.getValueLimit()) { // this checks to make sure the pokemon doesn't exist in your party, it's valid for the challenge and that it won't go over the cost limit; if it meets all these criteria it will add it to your party
options = [
{
label: i18next.t("starterSelectUiHandler:addToParty"),
handler: () => {
ui.setMode(Mode.STARTER_SELECT);
let isDupe = false;
for (let s = 0; s < this.starterCursors.length; s++) {
if (this.starterGens[s] === this.getGenCursorWithScroll() && this.starterCursors[s] === this.cursor) {
isDupe = true;
break;
}
}
const species = this.genSpecies[this.getGenCursorWithScroll()][this.cursor];
const isValidForChallenge = new Utils.BooleanHolder(true);
Challenge.applyChallenges(this.scene.gameMode, Challenge.ChallengeType.STARTER_CHOICE, species, isValidForChallenge, this.scene.gameData.getSpeciesDexAttrProps(species, this.dexAttrCursor), !!this.starterGens.length);
if (!isDupe && isValidForChallenge.value && this.tryUpdateValue(this.scene.gameData.getSpeciesStarterValue(species.speciesId))) {
const cursorObj = this.starterCursorObjs[this.starterCursors.length];
cursorObj.setVisible(true);
cursorObj.setPosition(this.cursorObj.x, this.cursorObj.y);
const props = this.scene.gameData.getSpeciesDexAttrProps(species, this.dexAttrCursor);
this.starterIcons[this.starterCursors.length].setTexture(species.getIconAtlasKey(props.formIndex, props.shiny, props.variant));
this.starterIcons[this.starterCursors.length].setFrame(species.getIconId(props.female, props.formIndex, props.shiny, props.variant));
this.checkIconId(this.starterIcons[this.starterCursors.length], species, props.female, props.formIndex, props.shiny, props.variant);
this.starterGens.push(this.getGenCursorWithScroll());
this.starterCursors.push(this.cursor);
this.starterAttr.push(this.dexAttrCursor);
this.starterAbilityIndexes.push(this.abilityCursor);
this.starterNatures.push(this.natureCursor as unknown as Nature);
this.starterMovesets.push(this.starterMoveset.slice(0) as StarterMoveset);
if (this.speciesLoaded.get(species.speciesId)) {
getPokemonSpeciesForm(species.speciesId, props.formIndex).cry(this.scene);
}
if (this.starterCursors.length === 6 || this.value === this.getValueLimit()) {
this.tryStart();
}
this.updateInstructions();
/**
* If the user can't select a pokemon anymore,
* go to start button.
*/
if (!this.canAddParty) {
this.startCursorObj.setVisible(true);
this.setGenMode(true);
}
if (!isDupe && isValidForChallenge.value && this.tryUpdateValue(this.scene.gameData.getSpeciesStarterValue(species.speciesId), true)) {
this.addToParty(species, pokemonGen, pokemonCursor);
ui.playSelect();
} else {
ui.playError();
ui.playError(); // this should be redundant as there is now a trigger for when a pokemon can't be added to party
}
return true;
},
overrideSound: true
},
}];
} else if (isDupe) { // if it already exists in your party, it will give you the option to remove from your party
options = [{
label: i18next.t("starterSelectUiHandler:removeFromParty"),
handler: () => {
this.popStarter(removeIndex);
ui.setMode(Mode.STARTER_SELECT);
return true;
}
}];
}
options.push( // this shows the IVs for the pokemon
{
label: i18next.t("starterSelectUiHandler:toggleIVs"),
handler: () => {
@ -1196,9 +1235,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
ui.setMode(Mode.STARTER_SELECT);
return true;
}
}
];
if (this.speciesStarterMoves.length > 1) {
});
if (this.speciesStarterMoves.length > 1) { // this lets you change the pokemon moves
const showSwapOptions = (moveset: StarterMoveset) => {
ui.setMode(Mode.STARTER_SELECT).then(() => {
ui.showText(i18next.t("starterSelectUiHandler:selectMoveSwapOut"), null, () => {
@ -1294,7 +1332,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
handler: () => {
// update default nature in starter save data
if (!starterAttributes) {
starterAttributes=
starterAttributes =
this.starterPreferences[this.lastSpecies.speciesId] = {};
}
starterAttributes.nature = n as unknown as integer;
@ -1330,7 +1368,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
}
const candyCount = starterData.candyCount;
const passiveAttr = starterData.passiveAttr;
if (passiveAttr & PassiveAttr.UNLOCKED) {
if (passiveAttr & PassiveAttr.UNLOCKED) { // this is for enabling and disabling the passive
if (!(passiveAttr & PassiveAttr.ENABLED)) {
options.push({
label: i18next.t("starterSelectUiHandler:enablePassive"),
@ -1353,11 +1391,12 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
});
}
}
const showUseCandies = () => {
const showUseCandies = () => { // this lets you use your candies
const options = [];
if (!(passiveAttr & PassiveAttr.UNLOCKED)) {
const passiveCost = getPassiveCandyCount(speciesStarters[this.lastSpecies.speciesId]);
options.push({
label: `x${passiveCost} ${i18next.t("starterSelectUiHandler:unlockPassive")} (${allAbilities[starterPassiveAbilities[this.lastSpecies.speciesId]].name})`,
handler: () => {
if (Overrides.FREE_CANDY_UPGRADE_OVERRIDE || candyCount >= passiveCost) {
@ -1375,8 +1414,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.setSpeciesDetails(this.lastSpecies, undefined, undefined, undefined, undefined, undefined, undefined);
// Update the candy upgrade display
if (this.isUpgradeIconEnabled() ) {
this.setUpgradeIcon(this.cursor);
if (this.isUpgradeIconEnabled()) {
this.setUpgradeIcon(pokemonCursor);
}
if (this.isUpgradeAnimationEnabled()) {
const genSpecies = this.genSpecies[this.lastSpecies.generation - 1];
@ -1408,15 +1447,15 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
return this.scene.reset(true);
}
});
this.updateStarterValueLabel(this.cursor);
this.updateStarterValueLabel(pokemonCursor);
this.tryUpdateValue(0);
ui.setMode(Mode.STARTER_SELECT);
this.scene.playSound("buy");
// If the notification setting is set to 'On', update the candy upgrade display
if (this.scene.candyUpgradeNotification === 2) {
if (this.isUpgradeIconEnabled() ) {
this.setUpgradeIcon(this.cursor);
if (this.isUpgradeIconEnabled()) {
this.setUpgradeIcon(pokemonCursor);
}
if (this.isUpgradeAnimationEnabled()) {
const genSpecies = this.genSpecies[this.lastSpecies.generation - 1];
@ -1521,6 +1560,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.starterPreferences[this.lastSpecies.speciesId] = {};
}
switch (button) {
case Button.CYCLE_SHINY:
if (this.canCycleShiny) {
const newVariant = props.variant;
@ -1624,7 +1664,9 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
success = true;
}
break;
case Button.UP:
if (!this.starterIconsCursorObj.visible) {
if (row) {
success = this.setCursor(this.cursor - 9);
} else {
@ -1636,8 +1678,19 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
success = this.setCursor(this.cursor + (rows - 1) * 9);
}
}
} else {
if (this.starterIconsCursorIndex === 0) {
this.starterIconsCursorObj.setVisible(false);
this.setSpecies(null);
this.setGenMode(true);
} else {
this.starterIconsCursorIndex--;
this.moveStarterIconsCursor(this.starterIconsCursorIndex);
}
}
break;
case Button.DOWN:
if (!this.starterIconsCursorObj.visible) {
if (row < rows - 2 || (row < rows - 1 && this.cursor % 9 <= (genStarters - 1) % 9)) {
success = this.setCursor(this.cursor + 9);
} else {
@ -1649,25 +1702,80 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
success = this.setCursor(this.cursor - (rows - 1) * 9);
}
}
} else {
if (this.starterIconsCursorIndex <= this.starterCursors.length - 2) {
this.starterIconsCursorIndex++;
this.moveStarterIconsCursor(this.starterIconsCursorIndex);
} else {
this.starterIconsCursorObj.setVisible(false);
this.setSpecies(null);
this.startCursorObj.setVisible(true);
}
}
break;
case Button.LEFT:
if (this.cursor % 9) {
if (!this.starterIconsCursorObj.visible) {
if (this.cursor % 9 !== 0) {
success = this.setCursor(this.cursor - 1);
} else {
if (this.starterCursors.length === 0) {
if (row >= Math.min(5, rows - 1)) {
this.startCursorObj.setVisible(true);
}
success = this.setGenMode(true);
} else {
if (row >= rows - 1) { // the last row will always go to the starter button
this.startCursorObj.setVisible(true);
} else if (row > 2) { // the first three rows will always go to the gen select, so anything else will go to the starterIcons party section
if (this.starterCursors.length >= row - 2) {
this.starterIconsCursorIndex = row - 3;
} else {
this.starterIconsCursorIndex = this.starterCursors.length - 1;
}
this.moveStarterIconsCursor(this.starterIconsCursorIndex);
}
success = this.setGenMode(true);
}
}
} else {
this.starterIconsCursorObj.setVisible(false);
this.setGenMode(false);
const rowToUse = Math.min(this.starterIconsCursorIndex + 3, rows - 1);
this.setCursor(Math.min((rowToUse * 9) + 8, genStarters - 1));
success = true;
}
break;
case Button.RIGHT:
if (!this.starterIconsCursorObj.visible) {
if (this.cursor % 9 < (row < rows - 1 ? 8 : (genStarters - 1) % 9)) {
success = this.setCursor(this.cursor + 1);
} else {
if (this.starterCursors.length === 0) {
if (row >= Math.min(5, rows - 1)) {
this.startCursorObj.setVisible(true);
}
success = this.setGenMode(true);
} else {
if (row >= rows - 1) { // the last row will always go to the starter button
this.startCursorObj.setVisible(true);
} else if (row > 2) { // the first three rows will always go to the gen select, so anything else will go to the starterIcons party section
if (this.starterCursors.length >= row - 2) {
this.starterIconsCursorIndex = row - 3;
} else {
this.starterIconsCursorIndex = this.starterCursors.length - 1;
}
this.moveStarterIconsCursor(this.starterIconsCursorIndex);
}
success = this.setGenMode(true);
}
}
} else {
this.starterIconsCursorObj.setVisible(false);
this.setGenMode(false);
const rowToUse = Math.min(this.starterIconsCursorIndex + 3, rows - 1);
this.setCursor(Math.min((rowToUse * 9), genStarters - 1));
this.setSpecies(this.genSpecies[this.getGenCursorWithScroll()][this.cursor]);
success = true;
}
break;
}
@ -1683,6 +1791,39 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
return success || error;
}
isInParty(pokemonGen: number, pokemonCursor: number): [boolean, number] {
let removeIndex = 0;
let isDupe = false;
for (let s = 0; s < this.starterCursors.length; s++) {
if (this.starterGens[s] === pokemonGen && this.starterCursors[s] === pokemonCursor) {
isDupe = true;
removeIndex = s;
break;
}
}
return [isDupe, removeIndex];
}
addToParty(species: PokemonSpecies, pokemonGen: number, pokemonCursor: number) {
const cursorObj = this.starterCursorObjs[this.starterCursors.length];
cursorObj.setVisible(true);
cursorObj.setPosition(this.cursorObj.x, this.cursorObj.y);
const props = this.scene.gameData.getSpeciesDexAttrProps(species, this.dexAttrCursor);
this.starterIcons[this.starterCursors.length].setTexture(species.getIconAtlasKey(props.formIndex, props.shiny, props.variant));
this.starterIcons[this.starterCursors.length].setFrame(species.getIconId(props.female, props.formIndex, props.shiny, props.variant));
this.checkIconId(this.starterIcons[this.starterCursors.length], species, props.female, props.formIndex, props.shiny, props.variant);
this.starterGens.push(pokemonGen);
this.starterCursors.push(pokemonCursor);
this.starterAttr.push(this.dexAttrCursor);
this.starterAbilityIndexes.push(this.abilityCursor);
this.starterNatures.push(this.natureCursor as unknown as Nature);
this.starterMovesets.push(this.starterMoveset.slice(0) as StarterMoveset);
if (this.speciesLoaded.get(species.speciesId)) {
getPokemonSpeciesForm(species.speciesId, props.formIndex).cry(this.scene);
}
this.updateInstructions();
}
switchMoveHandler(i: number, newMove: Moves, move: Moves) {
const speciesId = this.lastSpecies.speciesId;
const existingMoveIndex = this.starterMoveset.indexOf(newMove);
@ -1939,8 +2080,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
}
setGenMode(genMode: boolean): boolean {
this.genCursorObj.setVisible(genMode && !this.startCursorObj.visible);
this.cursorObj.setVisible(!genMode && !this.startCursorObj.visible);
this.genCursorObj.setVisible(genMode && !(this.startCursorObj.visible || this.starterIconsCursorObj.visible));
this.cursorObj.setVisible(!genMode && !(this.startCursorObj.visible || this.starterIconsCursorObj.visible));
if (genMode !== this.genMode) {
this.genMode = genMode;
@ -1949,6 +2090,9 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
if (genMode) {
this.setSpecies(null);
}
if (this.starterIconsCursorObj.visible) {
this.setSpecies(this.genSpecies[this.starterGens[this.starterIconsCursorIndex]][this.starterCursors[this.starterIconsCursorIndex]]);
}
return true;
}
@ -1956,7 +2100,17 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
return false;
}
moveStarterIconsCursor(index: number): void {
this.starterIconsCursorObj.x = this.starterIcons[index].x + this.starterIconsCursorXOffset;
this.starterIconsCursorObj.y = this.starterIcons[index].y + this.starterIconsCursorYOffset;
if (this.starterCursors.length > 0) {
this.starterIconsCursorObj.setVisible(true);
this.setSpecies(this.genSpecies[this.starterGens[index]][this.starterCursors[index]]);
} else {
this.starterIconsCursorObj.setVisible(false);
this.setSpecies(null);
}
}
setSpecies(species: PokemonSpecies) {
this.speciesStarterDexEntry = species ? this.scene.gameData.dexData[species.speciesId] : null;
@ -2032,7 +2186,6 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
const dexAttr = this.scene.gameData.getSpeciesDefaultDexAttr(this.lastSpecies, false, true);
const props = this.scene.gameData.getSpeciesDexAttrProps(this.lastSpecies, dexAttr);
const lastSpeciesIcon = (this.starterSelectGenIconContainers[this.lastSpecies.generation - 1].getAt(this.genSpecies[this.lastSpecies.generation - 1].indexOf(this.lastSpecies)) as Phaser.GameObjects.Sprite);
lastSpeciesIcon.setTexture(this.lastSpecies.getIconAtlasKey(props.formIndex, props.shiny, props.variant), this.lastSpecies.getIconId(props.female, props.formIndex, props.shiny, props.variant));
this.checkIconId(lastSpeciesIcon, this.lastSpecies, props.female, props.formIndex, props.shiny, props.variant);
this.iconAnimHandler.addOrUpdate(lastSpeciesIcon, PokemonIconAnimMode.NONE);
@ -2342,16 +2495,15 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.pokemonSprite.setVisible(!this.statsMode);
});
const isValidForChallenge = new Utils.BooleanHolder(true);
const currentPartyValue = this.starterGens.reduce((total: number, gen: number, i: number) => total += this.scene.gameData.getSpeciesStarterValue(this.genSpecies[gen][this.starterCursors[i]].speciesId), 0);
const cursorCost = this.scene.gameData.getSpeciesStarterValue(species.speciesId);
const isValidNextPartyValue = (currentPartyValue + cursorCost) <= this.getValueLimit();
Challenge.applyChallenges(this.scene.gameMode, Challenge.ChallengeType.STARTER_CHOICE, species, isValidForChallenge, this.scene.gameData.getSpeciesDexAttrProps(species, this.dexAttrCursor), !!this.starterGens.length);
const starterSprite = this.starterSelectGenIconContainers[this.getGenCursorWithScroll()].getAt(this.cursor) as Phaser.GameObjects.Sprite;
starterSprite.setTexture(species.getIconAtlasKey(formIndex, shiny, variant), species.getIconId(female, formIndex, shiny, variant));
starterSprite.setAlpha(isValidForChallenge.value && isValidNextPartyValue ? 1 : 0.375);
if (!this.starterIconsCursorObj.visible) {
(this.starterSelectGenIconContainers[this.getGenCursorWithScroll()].getAt(this.cursor) as Phaser.GameObjects.Sprite)
.setTexture(species.getIconAtlasKey(formIndex, shiny, variant), species.getIconId(female, formIndex, shiny, variant));
this.checkIconId((this.starterSelectGenIconContainers[this.getGenCursorWithScroll()].getAt(this.cursor) as Phaser.GameObjects.Sprite), species, female, formIndex, shiny, variant);
} else {
(this.starterSelectGenIconContainers[this.starterGens[this.starterIconsCursorIndex]].getAt(this.starterCursors[this.starterIconsCursorIndex]) as Phaser.GameObjects.Sprite)
.setTexture(species.getIconAtlasKey(formIndex, shiny, variant), species.getIconId(female, formIndex, shiny, variant));
this.checkIconId((this.starterSelectGenIconContainers[this.starterGens[this.starterIconsCursorIndex]].getAt(this.starterCursors[this.starterIconsCursorIndex]) as Phaser.GameObjects.Sprite), species, female, formIndex, shiny, variant);
}
this.canCycleShiny = !!(dexEntry.caughtAttr & DexAttr.NON_SHINY && dexEntry.caughtAttr & DexAttr.SHINY);
this.canCycleGender = !!(dexEntry.caughtAttr & DexAttr.MALE && dexEntry.caughtAttr & DexAttr.FEMALE);
this.canCycleAbility = [ abilityAttr & AbilityAttr.ABILITY_1, (abilityAttr & AbilityAttr.ABILITY_2) && species.ability2, abilityAttr & AbilityAttr.ABILITY_HIDDEN ].filter(a => a).length > 1;
@ -2489,16 +2641,42 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
}
}
popStarter(): void {
this.starterGens.pop();
this.starterCursors.pop();
this.starterAttr.pop();
this.starterAbilityIndexes.pop();
this.starterNatures.pop();
this.starterMovesets.pop();
popStarter(index: number): void {
this.starterGens.splice(index, 1);
this.starterCursors.splice(index, 1);
this.starterAttr.splice(index, 1);
this.starterAbilityIndexes.splice(index, 1);
this.starterNatures.splice(index, 1);
this.starterMovesets.splice(index, 1);
for (let s = 0; s < this.starterCursors.length; s++) {
const species = this.genSpecies[this.starterGens[s]][this.starterCursors[s]];
const currentDexAttr = this.scene.gameData.getSpeciesDefaultDexAttr(species, false, true);
const props = this.scene.gameData.getSpeciesDexAttrProps(species, currentDexAttr);
this.starterIcons[s].setTexture(species.getIconAtlasKey(props.formIndex, props.shiny, props.variant));
this.starterIcons[s].setFrame(species.getIconId(props.female, props.formIndex, props.shiny, props.variant));
if (s >= index) {
this.starterCursorObjs[s].setPosition(this.starterCursorObjs[s + 1].x, this.starterCursorObjs[s + 1].y);
this.starterCursorObjs[s].setVisible(this.starterCursorObjs[s + 1].visible);
}
}
this.starterCursorObjs[this.starterCursors.length].setVisible(false);
this.starterIcons[this.starterCursors.length].setTexture("pokemon_icons_0");
this.starterIcons[this.starterCursors.length].setFrame("unknown");
if (this.starterIconsCursorObj.visible) {
if (this.starterIconsCursorIndex === this.starterCursors.length) {
if (this.starterCursors.length > 0) {
this.starterIconsCursorIndex--;
} else {
this.starterIconsCursorObj.setVisible(false);
this.setSpecies(null);
this.setGenMode(true);
}
}
this.moveStarterIconsCursor(this.starterIconsCursorIndex);
}
this.tryUpdateValue();
}
@ -2528,7 +2706,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.starterValueLabels[cursor].setShadowColor(this.getTextColor(textStyle, true));
}
tryUpdateValue(add?: integer): boolean {
tryUpdateValue(add?: integer, addingToParty?: boolean): boolean {
const value = this.starterGens.reduce((total: integer, gen: integer, i: integer) => total += this.scene.gameData.getSpeciesStarterValue(this.genSpecies[gen][this.starterCursors[i]].speciesId), 0);
const newValue = value + (add || 0);
const valueLimit = this.getValueLimit();
@ -2545,6 +2723,13 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
return false;
}
let isPartyValid: boolean = this.isPartyValid(); // this checks to see if the party is valid
if (addingToParty) { // this does a check to see if the pokemon being added is valid; if so, it will update the isPartyValid boolean
const isNewPokemonValid = new Utils.BooleanHolder(true);
Challenge.applyChallenges(this.scene.gameMode, Challenge.ChallengeType.STARTER_CHOICE, this.genSpecies[this.getGenCursorWithScroll()][this.cursor], isNewPokemonValid, this.scene.gameData.getSpeciesDexAttrProps(this.genSpecies[this.getGenCursorWithScroll()][this.cursor], this.scene.gameData.getSpeciesDefaultDexAttr(this.genSpecies[this.getGenCursorWithScroll()][this.cursor], false, true)), !!(this.starterGens.length), false, false);
isPartyValid = isPartyValid || isNewPokemonValid.value;
}
/**
* this loop is used to set the Sprite's alpha value and check if the user can select other pokemon more.
*/
@ -2563,15 +2748,32 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
* If remainValue greater than or equal pokemon species and the pokemon is legal for this challenge, the user can select.
* so that the alpha value of pokemon sprite set 1.
*
* However, if isPartyValid is false, that means none of the party members are valid for the run. In this case, we should
* check the challenge to make sure evolutions and forms aren't being checked for mono type runs.
* This will let us set the sprite's alpha to show it can't be selected
*
* If speciesStarterDexEntry?.caughtAttr is true, this species registered in stater.
* we change to can AddParty value to true since the user has enough cost to choose this pokemon and this pokemon registered too.
*/
const isValidForChallenge = new Utils.BooleanHolder(true);
Challenge.applyChallenges(this.scene.gameMode, Challenge.ChallengeType.STARTER_CHOICE, this.genSpecies[g][s], isValidForChallenge, this.scene.gameData.getSpeciesDexAttrProps(this.genSpecies[g][s], this.scene.gameData.getSpeciesDefaultDexAttr(this.genSpecies[g][s], false, true)), !!(this.starterGens.length || add));
if (isPartyValid) { // we have two checks here - one for the party being valid and one for not. This comes from mono type challenges - if the party is valid it will check pokemon's evolutions and forms, and if it's not valid it won't check their evolutions and forms
Challenge.applyChallenges(this.scene.gameMode, Challenge.ChallengeType.STARTER_CHOICE, this.genSpecies[g][s], isValidForChallenge, this.scene.gameData.getSpeciesDexAttrProps(this.genSpecies[g][s], this.scene.gameData.getSpeciesDefaultDexAttr(this.genSpecies[g][s], false, true)), !!(this.starterGens.length + (add ? 1 : 0)));
} else {
Challenge.applyChallenges(this.scene.gameMode, Challenge.ChallengeType.STARTER_CHOICE, this.genSpecies[g][s], isValidForChallenge, this.scene.gameData.getSpeciesDexAttrProps(this.genSpecies[g][s], this.scene.gameData.getSpeciesDefaultDexAttr(this.genSpecies[g][s], false, true)), !!(this.starterGens.length + (add ? 1 : 0)), false, false);
}
const canBeChosen = remainValue >= speciesStarterValue && isValidForChallenge.value;
if (canBeChosen) {
const isPokemonInParty = this.isInParty(g, s)[0]; // this will get the valud of isDupe from isInParty. This will let us see if the pokemon in question is in our party already so we don't grey out the sprites if they're invalid
/* This code does a check to tell whether or not a sprite should be lit up or greyed out. There are 3 ways a pokemon's sprite should be lit up:
* 1) If it's in your party, it's a valid pokemon (i.e. for challenge) and you have enough points to have it
* 2) If it's in your party, it's not valid (i.e. for challenges), and you have enough points to have it
* 3) If it's not in your party, but it's a valid pokemon and you have enough points for it
* Any other time, the sprite should be greyed out.
* For example, if it's in your party, valid, but costs too much, or if it's not in your party and not valid, regardless of cost
*/
if (canBeChosen || (isPokemonInParty && remainValue >= speciesStarterValue)) {
speciesSprite.setAlpha(1);
if (speciesStarterDexEntry?.caughtAttr) {
this.canAddParty = true;
@ -2600,11 +2802,14 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
const cancel = () => {
ui.setMode(Mode.STARTER_SELECT);
if (!manualTrigger) {
this.popStarter();
this.popStarter(this.starterGens.length - 1);
}
this.clearText();
};
const canStart = this.isPartyValid();
if (canStart) {
ui.showText(i18next.t("starterSelectUiHandler:confirmStartTeam"), null, () => {
ui.setModeWithoutClear(Mode.CONFIRM, () => {
const startRun = () => {
@ -2622,17 +2827,35 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
passive: !(thisObj.scene.gameData.starterData[starterSpecies.speciesId].passiveAttr ^ (PassiveAttr.ENABLED | PassiveAttr.UNLOCKED)),
nature: thisObj.starterNatures[i] as Nature,
moveset: thisObj.starterMovesets[i],
pokerus: !![ 0, 1, 2 ].filter(n => thisObj.pokerusGens[n] === starterSpecies.generation - 1 && thisObj.pokerusCursors[n] === thisObj.genSpecies[starterSpecies.generation - 1].indexOf(starterSpecies)).length
pokerus: !![0, 1, 2].filter(n => thisObj.pokerusGens[n] === starterSpecies.generation - 1 && thisObj.pokerusCursors[n] === thisObj.genSpecies[starterSpecies.generation - 1].indexOf(starterSpecies)).length
};
}));
};
startRun();
}, cancel, null, null, 19);
});
} else {
const handler = this.scene.ui.getHandler() as AwaitableUiHandler;
handler.tutorialActive = true;
this.scene.ui.showText(i18next.t("starterSelectUiHandler:invalidParty"), null, () => this.scene.ui.showText(null, 0, () => handler.tutorialActive = false), null, true);
}
return true;
}
/* This block checks to see if your party is valid
* It checks each pokemon against the challenge - noting that due to monotype challenges it needs to check the pokemon while ignoring their evolutions/form change items
*/
isPartyValid(): boolean {
let canStart = false;
for (let s = 0; s < this.starterGens.length; s++) {
const isValidForChallenge = new Utils.BooleanHolder(true);
const species = this.genSpecies[this.starterGens[s]][this.starterCursors[s]];
Challenge.applyChallenges(this.scene.gameMode, Challenge.ChallengeType.STARTER_CHOICE, species, isValidForChallenge, this.scene.gameData.getSpeciesDexAttrProps(species, this.dexAttrCursor), !!(this.starterGens.length), false, false);
canStart = canStart || isValidForChallenge.value;
}
return canStart;
}
toggleStatsMode(on?: boolean): void {
if (on === undefined) {
on = !this.statsMode;
@ -2689,7 +2912,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler {
this.blockInput = false;
while (this.starterCursors.length) {
this.popStarter();
this.popStarter(this.starterCursors.length - 1);
}
if (this.statsMode) {