diff --git a/package-lock.json b/package-lock.json index 228983a1036..06f7af96103 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,7 +38,7 @@ "vitest-canvas-mock": "^0.3.3" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/@ampproject/remapping": { diff --git a/src/battle-scene-events.ts b/src/battle-scene-events.ts index 22959493f06..128fd5b5ceb 100644 --- a/src/battle-scene-events.ts +++ b/src/battle-scene-events.ts @@ -2,6 +2,12 @@ import Move from "./data/move"; /** Alias for all {@linkcode BattleScene} events */ export enum BattleSceneEventType { + /** + * Triggers when the corresponding setting is changed + * @see {@linkcode CandyUpgradeNotificationChangedEvent} + */ + CANDY_UPGRADE_NOTIFICATION_CHANGED = "onCandyUpgradeDisplayChanged", + /** * Triggers when a move is successfully used * @see {@linkcode MoveUsedEvent} @@ -24,6 +30,20 @@ export enum BattleSceneEventType { NEW_ARENA = "onNewArena", } +/** + * Container class for {@linkcode BattleSceneEventType.CANDY_UPGRADE_NOTIFICATION_CHANGED} events + * @extends Event +*/ +export class CandyUpgradeNotificationChangedEvent extends Event { + /** The new value the setting was changed to */ + public newValue: number; + constructor(newValue: number) { + super(BattleSceneEventType.CANDY_UPGRADE_NOTIFICATION_CHANGED); + + this.newValue = newValue; + } +} + /** * Container class for {@linkcode BattleSceneEventType.MOVE_USED} events * @extends Event diff --git a/src/battle-scene.ts b/src/battle-scene.ts index f972097f4c2..9a2f22d5c90 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -93,6 +93,19 @@ export default class BattleScene extends SceneBase { public showLevelUpStats: boolean = true; public enableTutorials: boolean = import.meta.env.VITE_BYPASS_TUTORIAL === "1"; public enableRetries: boolean = false; + /** + * Determines the condition for a notification should be shown for Candy Upgrades + * - 0 = 'Off' + * - 1 = 'Passives Only' + * - 2 = 'On' + */ + public candyUpgradeNotification: integer = 0; + /** + * Determines what type of notification is used for Candy Upgrades + * - 0 = 'Icon' + * - 1 = 'Animation' + */ + public candyUpgradeDisplay: integer = 0; public moneyFormat: MoneyFormat = MoneyFormat.NORMAL; public uiTheme: UiTheme = UiTheme.DEFAULT; public windowType: integer = 0; diff --git a/src/system/settings.ts b/src/system/settings.ts index 34c54ff4db2..8ea7374a79f 100644 --- a/src/system/settings.ts +++ b/src/system/settings.ts @@ -5,6 +5,7 @@ import BattleScene from "../battle-scene"; import { hasTouchscreen } from "../touch-controls"; import { updateWindowType } from "../ui/ui-theme"; import { PlayerGender } from "./game-data"; +import { CandyUpgradeNotificationChangedEvent } from "#app/battle-scene-events.js"; import { MoneyFormat } from "../enums/money-format"; export enum Setting { @@ -18,6 +19,8 @@ export enum Setting { Window_Type = "WINDOW_TYPE", Tutorials = "TUTORIALS", Enable_Retries = "ENABLE_RETRIES", + Candy_Upgrade_Notification = "CANDY_UPGRADE_NOTIFICATION", + Candy_Upgrade_Display = "CANDY_UPGRADE_DISPLAY", Money_Format = "MONEY_FORMAT", Sprite_Set = "SPRITE_SET", Move_Animations = "MOVE_ANIMATIONS", @@ -52,6 +55,8 @@ export const settingOptions: SettingOptions = { [Setting.Window_Type]: new Array(5).fill(null).map((_, i) => (i + 1).toString()), [Setting.Tutorials]: ["Off", "On"], [Setting.Enable_Retries]: ["Off", "On"], + [Setting.Candy_Upgrade_Notification]: ["Off", "Passives Only", "On"], + [Setting.Candy_Upgrade_Display]: ["Icon", "Animation"], [Setting.Money_Format]: ["Normal", "Abbreviated"], [Setting.Sprite_Set]: ["Consistent", "Mixed Animated"], [Setting.Move_Animations]: ["Off", "On"], @@ -78,6 +83,8 @@ export const settingDefaults: SettingDefaults = { [Setting.Window_Type]: 0, [Setting.Tutorials]: 1, [Setting.Enable_Retries]: 0, + [Setting.Candy_Upgrade_Notification]: 0, + [Setting.Candy_Upgrade_Display]: 0, [Setting.Money_Format]: 0, [Setting.Sprite_Set]: 0, [Setting.Move_Animations]: 1, @@ -93,7 +100,7 @@ export const settingDefaults: SettingDefaults = { [Setting.Vibration]: 0 }; -export const reloadSettings: Setting[] = [Setting.UI_Theme, Setting.Language, Setting.Sprite_Set]; +export const reloadSettings: Setting[] = [Setting.UI_Theme, Setting.Language, Setting.Sprite_Set, Setting.Candy_Upgrade_Display]; export function setSetting(scene: BattleScene, setting: Setting, value: integer): boolean { switch (setting) { @@ -127,6 +134,16 @@ export function setSetting(scene: BattleScene, setting: Setting, value: integer) case Setting.Enable_Retries: scene.enableRetries = settingOptions[setting][value] === "On"; break; + case Setting.Candy_Upgrade_Notification: + if (scene.candyUpgradeNotification === value) { + break; + } + + scene.candyUpgradeNotification = value; + scene.eventTarget.dispatchEvent(new CandyUpgradeNotificationChangedEvent(value)); + break; + case Setting.Candy_Upgrade_Display: + scene.candyUpgradeDisplay = value; case Setting.Money_Format: switch (settingOptions[setting][value]) { case "Normal": diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 830459f8544..91b90f05a6b 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -1,3 +1,4 @@ +import { BattleSceneEventType, CandyUpgradeNotificationChangedEvent } from "#app/battle-scene-events.js"; import { pokemonPrevolutions } from "#app/data/pokemon-evolutions"; import { Variant, getVariantTint } from "#app/data/variant"; import { argbFromRgba } from "@material/material-color-utilities"; @@ -103,6 +104,29 @@ function getValueReductionCandyCounts(baseValue: integer): [integer, integer] { return starterCandyCosts[baseValue - 1].costReduction; } +/** + * Calculates the icon position for a Pokemon of a given UI index + * @param index UI index to calculate the icon position of + * @returns An interface with an x and y property + */ +function calcIconPosition(index: number): {x: number, y: number} { + const x = (index % 9) * 18; + const y = Math.floor(index / 9) * 18; + + return {x: x, y: y}; +} + +/** + * Calculates the {@linkcode Phaser.GameObjects.Sprite} position for a Pokemon of a given UI index + * @param index UI index to calculate the icon position of + * @returns An interface with an x and y property + */ +function calcSpritePosition(index: number): {x: number, y: number} { + const position = calcIconPosition(index); + + return {x: position.x - 2, y: position.y + 2}; +} + const gens = [ i18next.t("starterSelectUiHandler:gen1"), i18next.t("starterSelectUiHandler:gen2"), @@ -202,6 +226,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler { private shinyIcons: Phaser.GameObjects.Image[][]; private hiddenAbilityIcons: Phaser.GameObjects.Image[]; private classicWinIcons: Phaser.GameObjects.Image[]; + private candyUpgradeIcon: Phaser.GameObjects.Image[]; + private candyUpgradeOverlayIcon: Phaser.GameObjects.Image[]; private iconAnimHandler: PokemonIconAnimHandler; @@ -392,9 +418,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.genSpecies[g].push(species); const defaultDexAttr = this.scene.gameData.getSpeciesDefaultDexAttr(species, false, true); const defaultProps = this.scene.gameData.getSpeciesDexAttrProps(species, defaultDexAttr); - const x = (s % 9) * 18; - const y = Math.floor(s / 9) * 18; - const icon = this.scene.add.sprite(x - 2, y + 2, species.getIconAtlasKey(defaultProps.formIndex, defaultProps.shiny, defaultProps.variant)); + const position = calcIconPosition(s); + const icon = this.scene.add.sprite(position.x - 2, position.y + 2, species.getIconAtlasKey(defaultProps.formIndex, defaultProps.shiny, defaultProps.variant)); icon.setScale(0.5); icon.setOrigin(0, 0); icon.setFrame(species.getIconId(defaultProps.female, defaultProps.formIndex, defaultProps.shiny, defaultProps.variant)); @@ -417,9 +442,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler { }); this.starterValueLabels = new Array(81).fill(null).map((_, i) => { - const x = (i % 9) * 18; - const y = Math.floor(i / 9) * 18; - const ret = addTextObject(this.scene, x + 152, y + 11, "0", TextStyle.WINDOW, { fontSize: "32px" }); + const position = calcIconPosition(i); + const ret = addTextObject(this.scene, position.x + 152, position.y + 11, "0", TextStyle.WINDOW, { fontSize: "32px" }); ret.setShadowOffset(2, 2); ret.setOrigin(0, 0); ret.setVisible(false); @@ -428,9 +452,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler { }); const getShinyStar = (i: integer, v: integer): Phaser.GameObjects.Image => { - const x = (i % 9) * 18 - v * 3; - const y = Math.floor(i / 9) * 18; - const ret = this.scene.add.image(x + 163, y + 11, "shiny_star_small"); + const position = calcIconPosition(i); + const ret = this.scene.add.image(position.x + 163, position.y + 11, "shiny_star_small"); ret.setOrigin(0, 0); ret.setScale(0.5); ret.setVisible(false); @@ -443,9 +466,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler { }); this.hiddenAbilityIcons = new Array(81).fill(null).map((_, i) => { - const x = (i % 9) * 18; - const y = Math.floor(i / 9) * 18; - const ret = this.scene.add.image(x + 163, y + 16, "ha_capsule"); + const position = calcIconPosition(i); + const ret = this.scene.add.image(position.x + 163, position.y + 16, "ha_capsule"); ret.setOrigin(0, 0); ret.setScale(0.5); ret.setVisible(false); @@ -454,9 +476,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler { }); this.classicWinIcons = new Array(81).fill(null).map((_, i) => { - const x = (i % 9) * 18; - const y = Math.floor(i / 9) * 18; - const ret = this.scene.add.image(x + 153, y + 21, "champion_ribbon"); + const position = calcIconPosition(i); + const ret = this.scene.add.image(position.x + 153, position.y + 21, "champion_ribbon"); ret.setOrigin(0, 0); ret.setScale(0.5); ret.setVisible(false); @@ -464,6 +485,26 @@ export default class StarterSelectUiHandler extends MessageUiHandler { return ret; }); + this.candyUpgradeIcon = new Array(81).fill(null).map((_, i) => { + const position = calcIconPosition(i); + const ret = this.scene.add.image(position.x + 163, position.y + 21, "candy"); + ret.setOrigin(0, 0); + ret.setScale(0.25); + ret.setVisible(false); + this.starterSelectContainer.add(ret); + return ret; + }); + + this.candyUpgradeOverlayIcon = new Array(81).fill(null).map((_, i) => { + const position = calcIconPosition(i); + const ret = this.scene.add.image(position.x + 163, position.y + 21, "candy_overlay"); + ret.setOrigin(0, 0); + ret.setScale(0.25); + ret.setVisible(false); + this.starterSelectContainer.add(ret); + return ret; + }); + this.pokemonSprite = this.scene.add.sprite(53, 63, "pkmn__sub"); this.pokemonSprite.setPipeline(this.scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], ignoreTimeTint: true }); this.starterSelectContainer.add(this.pokemonSprite); @@ -653,6 +694,8 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.starterSelectContainer.add(this.statsContainer); + this.scene.eventTarget.addEventListener(BattleSceneEventType.CANDY_UPGRADE_NOTIFICATION_CHANGED, (e) => this.onCandyUpgradeDisplayChanged(e)); + this.updateInstructions(); } @@ -660,20 +703,6 @@ export default class StarterSelectUiHandler extends MessageUiHandler { if (args.length >= 2 && args[0] instanceof Function && typeof args[1] === "number") { super.show(args); - for (let g = 0; g < this.genSpecies.length; g++) { - this.genSpecies[g].forEach((species, s) => { - const dexEntry = this.scene.gameData.dexData[species.speciesId]; - const icon = this.starterSelectGenIconContainers[g].getAt(s) as Phaser.GameObjects.Sprite; - if (dexEntry.caughtAttr) { - icon.clearTint(); - } else if (dexEntry.seenAttr) { - icon.setTint(0x808080); - } - }); - } - - this.starterSelectCallback = args[0] as StarterSelectCallback; - this.starterSelectContainer.setVisible(true); this.gameMode = args[1]; @@ -684,6 +713,21 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.setCursor(0); this.tryUpdateValue(0); + for (let g = 0; g < this.genSpecies.length; g++) { + this.genSpecies[g].forEach((species, s) => { + const icon = this.starterSelectGenIconContainers[g].getAt(s) as Phaser.GameObjects.Sprite; + const dexEntry = this.scene.gameData.dexData[species.speciesId]; + + if (dexEntry.caughtAttr) { + icon.clearTint(); + } else if (dexEntry.seenAttr) { + icon.setTint(0x808080); + } + + this.setUpgradeAnimation(icon, species); + }); + } + handleTutorial(this.scene, Tutorial.Starter_Select); return true; @@ -706,6 +750,161 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.starterSelectMessageBoxContainer.setVisible(!!text?.length); } + /** + * Determines if 'Icon' based upgrade notifications should be shown + * @returns true if upgrade notifications are enabled and set to display an 'Icon' + */ + isUpgradeIconEnabled(): boolean { + return this.scene.candyUpgradeNotification !== 0 && this.scene.candyUpgradeDisplay === 0; + } + /** + * Determines if 'Animation' based upgrade notifications should be shown + * @returns true if upgrade notifications are enabled and set to display an 'Animation' + */ + isUpgradeAnimationEnabled(): boolean { + return this.scene.candyUpgradeNotification !== 0 && this.scene.candyUpgradeDisplay === 1; + } + + /** + * 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 = this.scene.gameData.starterData[speciesId]; + + return starterData.candyCount >= getPassiveCandyCount(speciesStarters[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 = this.scene.gameData.starterData[speciesId]; + + return starterData.candyCount >= getValueReductionCandyCounts(speciesStarters[speciesId])[starterData.valueReduction] + && starterData.valueReduction < 2; + } + + /** + * Sets a bounce animation if enabled and the Pokemon has an upgrade + * @param icon {@linkcode Phaser.GameObjects.GameObject} to animate + * @param species {@linkcode PokemonSpecies} of the icon used to check for upgrades + * @param startPaused Should this animation be paused after it is added? + */ + setUpgradeAnimation(icon: Phaser.GameObjects.Sprite, species: PokemonSpecies, startPaused: boolean = false): void { + this.scene.tweens.killTweensOf(icon); + // Skip animations if they are disabled + if (this.scene.candyUpgradeDisplay === 0 || species.speciesId !== species.getRootSpeciesId(false)) { + return; + } + + const position = calcSpritePosition(this.genSpecies[species.generation - 1].indexOf(species)); + icon.y = position.y; + + const tweenChain: Phaser.Types.Tweens.TweenChainBuilderConfig = { + targets: icon, + loop: -1, + // Make the initial bounce a little randomly delayed + delay: Utils.randIntRange(0, 50) * 5, + loopDelay: 1000, + tweens: [ + { + targets: icon, + y: position.y - 5, + duration: Utils.fixedInt(125), + ease: "Cubic.easeOut", + yoyo: true + }, + { + targets: icon, + y: position.y - 3, + duration: Utils.fixedInt(150), + ease: "Cubic.easeOut", + yoyo: true + } + ],}; + + const passiveAvailable = this.isPassiveAvailable(species.speciesId); + // 'Only Passives' mode + if (this.scene.candyUpgradeNotification === 1) { + if (passiveAvailable) { + this.scene.tweens.chain(tweenChain).paused = startPaused; + } + // 'On' mode + } else if (this.scene.candyUpgradeNotification === 2) { + if (passiveAvailable || this.isValueReductionAvailable(species.speciesId)) { + this.scene.tweens.chain(tweenChain).paused = startPaused; + } + } + } + + /** + * Sets the visibility of a Candy Upgrade Icon given an index + * @param index The UI index of the icon within this generation container + */ + setUpgradeIcon(index: number): void { + const species = this.genSpecies[this.getGenCursorWithScroll()][index]; + const slotVisible = !!species?.speciesId; + + if (!species // No Pokemon exists at that UI index + || this.scene.candyUpgradeNotification === 0 // Notification setting is 'Off' + || species?.getRootSpeciesId(false) !== species?.speciesId) { // Pokemon is not the base evolution and can't use candy + // Set all icons as hidden and exit early + this.candyUpgradeIcon[index].setVisible(false); + this.candyUpgradeOverlayIcon[index].setVisible(false); + + return; + } + + const passiveAvailable = this.isPassiveAvailable(species.speciesId); + // 'Only Passive Unlocks' mode + if (this.scene.candyUpgradeNotification === 1) { + this.candyUpgradeIcon[index].setVisible(slotVisible && passiveAvailable); + this.candyUpgradeOverlayIcon[index].setVisible(slotVisible && this.candyUpgradeIcon[index].visible); + + // 'On' mode + } else if (this.scene.candyUpgradeNotification === 2) { + this.candyUpgradeIcon[index].setVisible( + slotVisible && ( passiveAvailable || this.isValueReductionAvailable(species.speciesId))); + this.candyUpgradeOverlayIcon[index].setVisible(slotVisible && this.candyUpgradeIcon[index].visible); + } + } + + /** + * Processes an {@linkcode CandyUpgradeNotificationChangedEvent} sent when the corresponding setting changes + * @param event {@linkcode Event} sent by the callback + */ + onCandyUpgradeDisplayChanged(event: Event): void { + const candyUpgradeDisplayEvent = event as CandyUpgradeNotificationChangedEvent; + if (!candyUpgradeDisplayEvent) { + return; + } + + // Loop through all visible candy icons when set to 'Icon' mode + if (this.scene.candyUpgradeDisplay === 0) { + this.genSpecies[this.getGenCursorWithScroll()].forEach((_species, s) => { + this.setUpgradeIcon(s); + }); + + return; + } + + // Loop through all animations when set to 'Animation' mode + for (let g = 0; g < this.genSpecies.length; g++) { + this.genSpecies[g].forEach((species, s) => { + const icon = this.starterSelectGenIconContainers[g].getAt(s) as Phaser.GameObjects.Sprite; + + this.setUpgradeAnimation(icon, species); + }); + } + } + processInput(button: Button): boolean { if (this.blockInput) { return false; @@ -948,6 +1147,16 @@ export default class StarterSelectUiHandler extends MessageUiHandler { }); ui.setMode(Mode.STARTER_SELECT); this.setSpeciesDetails(this.lastSpecies, undefined, undefined, undefined, undefined, undefined, undefined); + + // Update the candy upgrade display + if (this.isUpgradeIconEnabled() ) { + this.setUpgradeIcon(this.cursor); + } + if (this.isUpgradeAnimationEnabled()) { + const genSpecies = this.genSpecies[this.lastSpecies.generation - 1]; + this.setUpgradeAnimation(this.starterSelectGenIconContainers[this.lastSpecies.generation - 1].getAt(genSpecies.indexOf(this.lastSpecies)), this.lastSpecies, true); + } + return true; } return false; @@ -975,6 +1184,18 @@ export default class StarterSelectUiHandler extends MessageUiHandler { 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.isUpgradeAnimationEnabled()) { + const genSpecies = this.genSpecies[this.lastSpecies.generation - 1]; + this.setUpgradeAnimation(this.starterSelectGenIconContainers[this.lastSpecies.generation - 1].getAt(genSpecies.indexOf(this.lastSpecies)), this.lastSpecies, true); + } + } + return true; } return false; @@ -1293,6 +1514,25 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } this.hiddenAbilityIcons[s].setVisible(slotVisible && !!this.scene.gameData.dexData[speciesId].caughtAttr && !!(this.scene.gameData.starterData[speciesId].abilityAttr & 4)); this.classicWinIcons[s].setVisible(slotVisible && this.scene.gameData.starterData[speciesId].classicWinCount > 0); + + const species = this.genSpecies[genCursorWithScroll][s]; + // 'Candy Icon' mode + if (this.scene.candyUpgradeDisplay === 0 && species?.getRootSpeciesId(false) === species?.speciesId) { + + if (!starterColors[speciesId]) { + // Default to white if no colors are found + starterColors[speciesId] = [ "ffffff", "ffffff" ]; + } + + // Set the candy colors + this.candyUpgradeIcon[s].setTint(argbFromRgba(Utils.rgbHexToRgba(starterColors[speciesId][0]))); + this.candyUpgradeOverlayIcon[s].setTint(argbFromRgba(Utils.rgbHexToRgba(starterColors[speciesId][1]))); + + this.setUpgradeIcon(s); + } else if (this.scene.candyUpgradeDisplay === 1) { + this.candyUpgradeIcon[s].setVisible(false); + this.candyUpgradeOverlayIcon[s].setVisible(false); + } } } else { changed = super.setCursor(cursor); @@ -1370,6 +1610,11 @@ export default class StarterSelectUiHandler extends MessageUiHandler { 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); + + // Resume the animation for the previously selected species + const speciesIndex = this.genSpecies[this.lastSpecies.generation - 1].indexOf(this.lastSpecies); + const icon = this.starterSelectGenIconContainers[this.lastSpecies.generation - 1].getAt(speciesIndex) as Phaser.GameObjects.Sprite; + this.scene.tweens.getTweensOf(icon).forEach(tween => tween.resume()); } this.lastSpecies = species; @@ -1434,7 +1679,22 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.pokemonCandyDarknessOverlay.setCrop(0,0,16, candyCropY); } - this.iconAnimHandler.addOrUpdate(this.starterSelectGenIconContainers[species.generation - 1].getAt(this.genSpecies[species.generation - 1].indexOf(species)) as Phaser.GameObjects.Sprite, PokemonIconAnimMode.PASSIVE); + + + // Pause the animation when the species is selected + const speciesIndex = this.genSpecies[species.generation - 1].indexOf(species); + const icon = this.starterSelectGenIconContainers[species.generation - 1].getAt(speciesIndex) as Phaser.GameObjects.Sprite; + + if (this.isUpgradeAnimationEnabled()) { + this.scene.tweens.getTweensOf(icon).forEach(tween => tween.pause()); + // Reset the position of the icon + const position = calcSpritePosition(speciesIndex); + icon.x = position.x; + icon.y = position.y; + } + + // Initiates the small up and down idle animation + this.iconAnimHandler.addOrUpdate(icon, PokemonIconAnimMode.PASSIVE); let starterIndex = -1;