diff --git a/public/fonts/pkmnems.ttf b/public/fonts/pkmnems.ttf index b0b50d0f10f..2cc7bfeee62 100644 Binary files a/public/fonts/pkmnems.ttf and b/public/fonts/pkmnems.ttf differ diff --git a/public/fonts/pokemon-emerald-pro.ttf b/public/fonts/pokemon-emerald-pro.ttf index 84e49ebbc40..e4ee49dbff3 100644 Binary files a/public/fonts/pokemon-emerald-pro.ttf and b/public/fonts/pokemon-emerald-pro.ttf differ diff --git a/public/images/pokemon/658-ash.png b/public/images/pokemon/658-ash.png index a122df859bd..fa6ce5cb165 100644 Binary files a/public/images/pokemon/658-ash.png and b/public/images/pokemon/658-ash.png differ diff --git a/public/images/pokemon/exp/658-ash.png b/public/images/pokemon/exp/658-ash.png index 6bb84f3e4fd..ced4cbcec71 100644 Binary files a/public/images/pokemon/exp/658-ash.png and b/public/images/pokemon/exp/658-ash.png differ diff --git a/public/images/pokemon/shiny/658-ash.png b/public/images/pokemon/shiny/658-ash.png index f5de608708e..b25693fd24e 100644 Binary files a/public/images/pokemon/shiny/658-ash.png and b/public/images/pokemon/shiny/658-ash.png differ diff --git a/public/images/pokemon/variant/658-ash.json b/public/images/pokemon/variant/658-ash.json index 29b5bd2560b..1845b2b1bea 100644 --- a/public/images/pokemon/variant/658-ash.json +++ b/public/images/pokemon/variant/658-ash.json @@ -4,6 +4,7 @@ "3f4447": "466698", "de3431": "3fca9f", "f8f8f8": "a1e9f0", + "f4f4f4": "d7eff4", "7b282e": "0e3e81", "6b1d1d": "206d74", "4ebdd9": "41a7b0", @@ -11,7 +12,7 @@ "bfbfbf": "8cc7d4", "ffb2bf": "b7e9ff", "bf4c60": "4386df", - "fff0a6": "271f4c", + "fff0a6": "208698", "3e7acc": "6b4592", "18335c": "170738", "f2798d": "8dcfff", @@ -25,6 +26,7 @@ "3f4447": "466698", "de3431": "9ceec6", "f8f8f8": "89d2b8", + "f4f4f4": "d7eff4", "7b282e": "152a5c", "6b1d1d": "356e8d", "4ebdd9": "2f6e74", diff --git a/public/images/pokemon/variant/exp/658-ash.json b/public/images/pokemon/variant/exp/658-ash.json index 96b60b02adf..79cad7ea42d 100644 --- a/public/images/pokemon/variant/exp/658-ash.json +++ b/public/images/pokemon/variant/exp/658-ash.json @@ -4,6 +4,7 @@ "3f4447": "466698", "de3431": "3fca9f", "f8f8f8": "a1e9f0", + "f4f4f4": "d7effa", "7b282e": "0e3e81", "6b1d1d": "206d74", "4ebdd9": "41a7b0", @@ -25,6 +26,7 @@ "3f4447": "466698", "de3431": "9ceec6", "f8f8f8": "89d2b8", + "f4f4f4": "d7effa", "7b282e": "152a5c", "6b1d1d": "356e8d", "4ebdd9": "2f6e74", diff --git a/public/locales b/public/locales index 2e03bc8f273..4928231e22a 160000 --- a/public/locales +++ b/public/locales @@ -1 +1 @@ -Subproject commit 2e03bc8f2736269bfa365faad587c3ec54a37621 +Subproject commit 4928231e22a06dce2b55d9b04cd2b283c2ee4afb diff --git a/src/battle-scene.ts b/src/battle-scene.ts index 327ab1cc926..ae992f5c00f 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -1424,6 +1424,8 @@ export default class BattleScene extends SceneBase { return 0; } + const isEggPhase: boolean = [ "EggLapsePhase", "EggHatchPhase" ].includes(this.getCurrentPhase()?.constructor.name ?? ""); + switch (species.speciesId) { case Species.UNOWN: case Species.SHELLOS: @@ -1455,7 +1457,7 @@ export default class BattleScene extends SceneBase { } return Utils.randSeedInt(8); case Species.EEVEE: - if (this.currentBattle?.battleType === BattleType.TRAINER && this.currentBattle?.waveIndex < 30) { + if (this.currentBattle?.battleType === BattleType.TRAINER && this.currentBattle?.waveIndex < 30 && !isEggPhase) { return 0; // No Partner Eevee for Wave 12 Preschoolers } return Utils.randSeedInt(2); @@ -1483,7 +1485,7 @@ export default class BattleScene extends SceneBase { return 0; case Species.GIMMIGHOUL: // Chest form can only be found in Mysterious Chest Encounter, if this is a game mode with MEs - if (this.gameMode.hasMysteryEncounters) { + if (this.gameMode.hasMysteryEncounters && !isEggPhase) { return 1; // Wandering form } else { return Utils.randSeedInt(species.forms.length); diff --git a/src/data/move.ts b/src/data/move.ts index 7a6f08a5372..c86b168fb57 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -9220,7 +9220,8 @@ export function initMoves() { .attr(ClearWeatherAttr, WeatherType.FOG) .attr(ClearTerrainAttr) .attr(RemoveScreensAttr, false) - .attr(RemoveArenaTrapAttr, true), + .attr(RemoveArenaTrapAttr, true) + .attr(RemoveArenaTagsAttr, [ ArenaTagType.MIST, ArenaTagType.SAFEGUARD ], false), new StatusMove(Moves.TRICK_ROOM, Type.PSYCHIC, -1, 5, -1, -7, 4) .attr(AddArenaTagAttr, ArenaTagType.TRICK_ROOM, 5) .ignoresProtect() diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index 11ff23fb9cb..9990cd1b9d0 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -1042,7 +1042,8 @@ class SpeciesStatBoosterModifierTypeGenerator extends ModifierTypeGenerator { for (const p of party) { const speciesId = p.getSpeciesForm(true).speciesId; const fusionSpeciesId = p.isFusion() ? p.getFusionSpeciesForm(true).speciesId : null; - const hasFling = p.getMoveset(true).some(m => m?.moveId === Moves.FLING); + // TODO: Use commented boolean when Fling is implemented + const hasFling = false; /* p.getMoveset(true).some(m => m?.moveId === Moves.FLING) */ for (const i in values) { const checkedSpecies = values[i].species; @@ -1757,56 +1758,69 @@ const modifierPool: ModifierPool = { }, 12), new WeightedModifierType(modifierTypes.TOXIC_ORB, (party: Pokemon[]) => { return party.some(p => { - const moveset = p.getMoveset(true).filter(m => !isNullOrUndefined(m)).map(m => m.moveId); - - const canSetStatus = p.canSetStatus(StatusEffect.TOXIC, true, true, null, true); const isHoldingOrb = p.getHeldItems().some(i => i.type.id === "FLAME_ORB" || i.type.id === "TOXIC_ORB"); - // Moves that take advantage of obtaining the actual status effect - const hasStatusMoves = [ Moves.FACADE, Moves.PSYCHO_SHIFT ] - .some(m => moveset.includes(m)); - // Moves that take advantage of being able to give the target a status orb - // TODO: Take moves from comment they are implemented - const hasItemMoves = [ /* Moves.TRICK, Moves.FLING, Moves.SWITCHEROO */ ] - .some(m => moveset.includes(m)); - // Abilities that take advantage of obtaining the actual status effect - const hasRelevantAbilities = [ Abilities.QUICK_FEET, Abilities.GUTS, Abilities.MARVEL_SCALE, Abilities.TOXIC_BOOST, Abilities.POISON_HEAL, Abilities.MAGIC_GUARD ] - .some(a => p.hasAbility(a, false, true)); - if (!isHoldingOrb) { + const moveset = p.getMoveset(true).filter(m => !isNullOrUndefined(m)).map(m => m.moveId); + const canSetStatus = p.canSetStatus(StatusEffect.TOXIC, true, true, null, true); + + // Moves that take advantage of obtaining the actual status effect + const hasStatusMoves = [ Moves.FACADE, Moves.PSYCHO_SHIFT ] + .some(m => moveset.includes(m)); + // Moves that take advantage of being able to give the target a status orb + // TODO: Take moves (Trick, Fling, Switcheroo) from comment when they are implemented + const hasItemMoves = [ /* Moves.TRICK, Moves.FLING, Moves.SWITCHEROO */ ] + .some(m => moveset.includes(m)); + if (canSetStatus) { - return hasRelevantAbilities || hasStatusMoves; + // Abilities that take advantage of obtaining the actual status effect, separated based on specificity to the orb + const hasGeneralAbility = [ Abilities.QUICK_FEET, Abilities.GUTS, Abilities.MARVEL_SCALE, Abilities.MAGIC_GUARD ] + .some(a => p.hasAbility(a, false, true)); + const hasSpecificAbility = [ Abilities.TOXIC_BOOST, Abilities.POISON_HEAL ] + .some(a => p.hasAbility(a, false, true)); + const hasOppositeAbility = [ Abilities.FLARE_BOOST ] + .some(a => p.hasAbility(a, false, true)); + + return hasSpecificAbility || (hasGeneralAbility && !hasOppositeAbility) || hasStatusMoves; } else { return hasItemMoves; } } + return false; }) ? 10 : 0; }, 10), new WeightedModifierType(modifierTypes.FLAME_ORB, (party: Pokemon[]) => { return party.some(p => { - const moveset = p.getMoveset(true).filter(m => !isNullOrUndefined(m)).map(m => m.moveId); - const canSetStatus = p.canSetStatus(StatusEffect.BURN, true, true, null, true); const isHoldingOrb = p.getHeldItems().some(i => i.type.id === "FLAME_ORB" || i.type.id === "TOXIC_ORB"); - // Moves that take advantage of obtaining the actual status effect - const hasStatusMoves = [ Moves.FACADE, Moves.PSYCHO_SHIFT ] - .some(m => moveset.includes(m)); - // Moves that take advantage of being able to give the target a status orb - // TODO: Take moves from comment they are implemented - const hasItemMoves = [ /* Moves.TRICK, Moves.FLING, Moves.SWITCHEROO */ ] - .some(m => moveset.includes(m)); - // Abilities that take advantage of obtaining the actual status effect - const hasRelevantAbilities = [ Abilities.QUICK_FEET, Abilities.GUTS, Abilities.MARVEL_SCALE, Abilities.FLARE_BOOST, Abilities.MAGIC_GUARD ] - .some(a => p.hasAbility(a, false, true)); - if (!isHoldingOrb) { + const moveset = p.getMoveset(true).filter(m => !isNullOrUndefined(m)).map(m => m.moveId); + const canSetStatus = p.canSetStatus(StatusEffect.TOXIC, true, true, null, true); + + // Moves that take advantage of obtaining the actual status effect + const hasStatusMoves = [ Moves.FACADE, Moves.PSYCHO_SHIFT ] + .some(m => moveset.includes(m)); + // Moves that take advantage of being able to give the target a status orb + // TODO: Take moves (Trick, Fling, Switcheroo) from comment when they are implemented + const hasItemMoves = [ /* Moves.TRICK, Moves.FLING, Moves.SWITCHEROO */ ] + .some(m => moveset.includes(m)); + if (canSetStatus) { - return hasRelevantAbilities || hasStatusMoves; + // Abilities that take advantage of obtaining the actual status effect, separated based on specificity to the orb + const hasGeneralAbility = [ Abilities.QUICK_FEET, Abilities.GUTS, Abilities.MARVEL_SCALE, Abilities.MAGIC_GUARD ] + .some(a => p.hasAbility(a, false, true)); + const hasSpecificAbility = [ Abilities.FLARE_BOOST ] + .some(a => p.hasAbility(a, false, true)); + const hasOppositeAbility = [ Abilities.TOXIC_BOOST, Abilities.POISON_HEAL ] + .some(a => p.hasAbility(a, false, true)); + + return hasSpecificAbility || (hasGeneralAbility && !hasOppositeAbility) || hasStatusMoves; } else { return hasItemMoves; } } + return false; }) ? 10 : 0; }, 10), @@ -1845,7 +1859,7 @@ const modifierPool: ModifierPool = { new WeightedModifierType(modifierTypes.BATON, 2), new WeightedModifierType(modifierTypes.SOUL_DEW, 7), //new WeightedModifierType(modifierTypes.OVAL_CHARM, 6), - new WeightedModifierType(modifierTypes.CATCHING_CHARM, (party: Pokemon[]) => !party[0].scene.gameMode.isFreshStartChallenge() && party[0].scene.gameData.getSpeciesCount(d => !!d.caughtAttr) > 100 ? 4 : 0, 4), + new WeightedModifierType(modifierTypes.CATCHING_CHARM, (party: Pokemon[]) => party[0].scene.gameMode.isDaily || (!party[0].scene.gameMode.isFreshStartChallenge() && party[0].scene.gameData.getSpeciesCount(d => !!d.caughtAttr) > 100) ? 4 : 0, 4), new WeightedModifierType(modifierTypes.ABILITY_CHARM, skipInClassicAfterWave(189, 6)), new WeightedModifierType(modifierTypes.FOCUS_BAND, 5), new WeightedModifierType(modifierTypes.KINGS_ROCK, 3), diff --git a/src/test/moves/defog.test.ts b/src/test/moves/defog.test.ts new file mode 100644 index 00000000000..c83cdc192bf --- /dev/null +++ b/src/test/moves/defog.test.ts @@ -0,0 +1,71 @@ +import { Stat } from "#enums/stat"; +import { Abilities } from "#enums/abilities"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import GameManager from "#test/utils/gameManager"; +import Phaser from "phaser"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Moves - Defog", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + game.override + .moveset([ Moves.MIST, Moves.SAFEGUARD, Moves.SPLASH ]) + .ability(Abilities.BALL_FETCH) + .battleType("single") + .disableCrits() + .enemySpecies(Species.SHUCKLE) + .enemyAbility(Abilities.BALL_FETCH) + .enemyMoveset([ Moves.DEFOG, Moves.GROWL ]); + }); + + it("should not allow Safeguard to be active", async () => { + await game.classicMode.startBattle([ Species.REGIELEKI ]); + + const playerPokemon = game.scene.getPlayerField(); + const enemyPokemon = game.scene.getEnemyField(); + + game.move.select(Moves.SAFEGUARD); + await game.forceEnemyMove(Moves.DEFOG); + await game.phaseInterceptor.to("BerryPhase"); + + expect(playerPokemon[0].isSafeguarded(enemyPokemon[0])).toBe(false); + + + expect(true).toBe(true); + }); + + + it("should not allow Mist to be active", async () => { + await game.classicMode.startBattle([ Species.REGIELEKI ]); + + const playerPokemon = game.scene.getPlayerField(); + + game.move.select(Moves.MIST); + await game.forceEnemyMove(Moves.DEFOG); + + await game.toNextTurn(); + + game.move.select(Moves.SPLASH); + await game.forceEnemyMove(Moves.GROWL); + + await game.phaseInterceptor.to("BerryPhase"); + + expect(playerPokemon[0].getStatStage(Stat.ATK)).toBe(-1); + + expect(true).toBe(true); + }); +}); diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 691e339eafc..38a2bb85de6 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -130,9 +130,10 @@ const valueReductionMax = 2; const filterBarHeight = 17; const speciesContainerX = 109; // if team on the RIGHT: 109 / if on the LEFT: 143 const teamWindowX = 285; // if team on the RIGHT: 285 / if on the LEFT: 109 -const teamWindowY = 18; +const teamWindowY = 38; const teamWindowWidth = 34; -const teamWindowHeight = 132; +const teamWindowHeight = 107; +const randomSelectionWindowHeight = 20; /** * Calculates the starter position for a Pokemon of a given UI index @@ -318,6 +319,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { private starterIconsCursorObj: Phaser.GameObjects.Image; private valueLimitLabel: Phaser.GameObjects.Text; private startCursorObj: Phaser.GameObjects.NineSlice; + private randomCursorObj: Phaser.GameObjects.NineSlice; private iconAnimHandler: PokemonIconAnimHandler; @@ -366,8 +368,9 @@ export default class StarterSelectUiHandler extends MessageUiHandler { starterContainerBg.setOrigin(0, 0); this.starterSelectContainer.add(starterContainerBg); - this.starterSelectContainer.add(addWindow(this.scene, teamWindowX, teamWindowY, teamWindowWidth, teamWindowHeight)); - this.starterSelectContainer.add(addWindow(this.scene, teamWindowX, teamWindowY + teamWindowHeight - 5, teamWindowWidth, teamWindowWidth, true)); + this.starterSelectContainer.add(addWindow(this.scene, teamWindowX, teamWindowY - randomSelectionWindowHeight, teamWindowWidth, randomSelectionWindowHeight, true)); + this.starterSelectContainer.add(addWindow(this.scene, teamWindowX, teamWindowY, teamWindowWidth, teamWindowHeight )); + this.starterSelectContainer.add(addWindow(this.scene, teamWindowX, teamWindowY + teamWindowHeight, teamWindowWidth, teamWindowWidth, true)); this.starterSelectContainer.add(starterContainerWindow); // Create and initialise filter bar @@ -605,6 +608,15 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.startCursorObj.setOrigin(0, 0); this.starterSelectContainer.add(this.startCursorObj); + const randomSelectLabel = addTextObject(this.scene, teamWindowX + 17, 23, i18next.t("starterSelectUiHandler:randomize"), TextStyle.TOOLTIP_CONTENT); + randomSelectLabel.setOrigin(0.5, 0); + this.starterSelectContainer.add(randomSelectLabel); + + this.randomCursorObj = this.scene.add.nineslice(teamWindowX + 4, 21, "select_cursor", undefined, 26, 15, 6, 6, 6, 6); + this.randomCursorObj.setVisible(false); + this.randomCursorObj.setOrigin(0, 0); + this.starterSelectContainer.add(this.randomCursorObj); + const starterSpecies: Species[] = []; const starterBoxContainer = this.scene.add.container(speciesContainerX + 6, 9); //115 @@ -1337,9 +1349,9 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.starterIconsCursorIndex = this.starterSpecies.length - 1; this.moveStarterIconsCursor(this.starterIconsCursorIndex); } else { + // TODO: how can we get here if start button can't be selected? this appears to be redundant this.startCursorObj.setVisible(false); - this.filterBarCursor = Math.max(1, this.filterBar.numFilters - 1); - this.setFilterMode(true); + this.randomCursorObj.setVisible(true); } success = true; break; @@ -1386,14 +1398,18 @@ export default class StarterSelectUiHandler extends MessageUiHandler { case Button.UP: if (this.filterBar.openDropDown) { success = this.filterBar.decDropDownCursor(); - } else if (this.filterBarCursor === this.filterBar.numFilters - 1 && this.starterSpecies.length > 0) { + } else if (this.filterBarCursor === this.filterBar.numFilters - 1 ) { // UP from the last filter, move to start button this.setFilterMode(false); this.cursorObj.setVisible(false); - this.startCursorObj.setVisible(true); + if (this.starterSpecies.length > 0) { + this.startCursorObj.setVisible(true); + } else { + this.randomCursorObj.setVisible(true); + } success = true; } else if (numberOfStarters > 0) { - // UP from filter bar to bottom of Pokemon list + // UP from filter bar to bottom of Pokemon list this.setFilterMode(false); this.scrollCursor = Math.max(0, numOfRows - 9); this.updateScroll(); @@ -1410,12 +1426,11 @@ export default class StarterSelectUiHandler extends MessageUiHandler { case Button.DOWN: if (this.filterBar.openDropDown) { success = this.filterBar.incDropDownCursor(); - } else if (this.filterBarCursor === this.filterBar.numFilters - 1 && this.starterSpecies.length > 0) { - // DOWN from the last filter, move to Pokemon in party if any + } else if (this.filterBarCursor === this.filterBar.numFilters - 1) { + // DOWN from the last filter, move to random selection label this.setFilterMode(false); this.cursorObj.setVisible(false); - this.starterIconsCursorIndex = 0; - this.moveStarterIconsCursor(this.starterIconsCursorIndex); + this.randomCursorObj.setVisible(true); success = true; } else if (numberOfStarters > 0) { // DOWN from filter bar to top of Pokemon list @@ -1437,8 +1452,100 @@ export default class StarterSelectUiHandler extends MessageUiHandler { success = true; break; } + } else if (this.randomCursorObj.visible) { + switch (button) { + case Button.ACTION: + if (this.starterSpecies.length >= 6) { + error = true; + break; + } + const currentPartyValue = this.starterSpecies.map(s => s.generation).reduce((total: number, _gen: number, i: number ) => total + this.scene.gameData.getSpeciesStarterValue(this.starterSpecies[i].speciesId), 0); + // Filter valid starters + const validStarters = this.filteredStarterContainers.filter(starter => { + const species = starter.species; + const [ isDupe ] = this.isInParty(species); + const starterCost = this.scene.gameData.getSpeciesStarterValue(species.speciesId); + const isValidForChallenge = new BooleanHolder(true); + Challenge.applyChallenges( + this.scene.gameMode, + Challenge.ChallengeType.STARTER_CHOICE, + species, + isValidForChallenge, + this.scene.gameData.getSpeciesDexAttrProps( + species, + this.getCurrentDexProps(species.speciesId) + ), + this.isPartyValid() + ); + const isCaught = this.scene.gameData.dexData[species.speciesId].caughtAttr; + return ( + !isDupe && + isValidForChallenge.value && + currentPartyValue + starterCost <= this.getValueLimit() && + isCaught + ); + }); + if (validStarters.length === 0) { + error = true; // No valid starters available + break; + } + // Select random starter + const randomStarter = validStarters[Math.floor(Math.random() * validStarters.length)]; + const randomSpecies = randomStarter.species; + // Set species and prepare attributes + this.setSpecies(randomSpecies); + const dexAttr = this.getCurrentDexProps(randomSpecies.speciesId); + const props = this.scene.gameData.getSpeciesDexAttrProps(randomSpecies, dexAttr); + const abilityIndex = this.abilityCursor; + const nature = this.natureCursor as unknown as Nature; + const moveset = this.starterMoveset?.slice(0) as StarterMoveset; + const starterCost = this.scene.gameData.getSpeciesStarterValue(randomSpecies.speciesId); + const speciesForm = getPokemonSpeciesForm(randomSpecies.speciesId, props.formIndex); + // Load assets and add to party + speciesForm + .loadAssets(this.scene, props.female, props.formIndex, props.shiny, props.variant, true) + .then(() => { + if (this.tryUpdateValue(starterCost, true)) { + this.addToParty(randomSpecies, dexAttr, abilityIndex, nature, moveset, true); + ui.playSelect(); + } + }); + break; + case Button.UP: + this.randomCursorObj.setVisible(false); + this.filterBarCursor = this.filterBar.numFilters - 1; + this.setFilterMode(true); + success = true; + break; + case Button.DOWN: + this.randomCursorObj.setVisible(false); + if (this.starterSpecies.length > 0) { + this.starterIconsCursorIndex = 0; + this.moveStarterIconsCursor(this.starterIconsCursorIndex); + } else { + this.filterBarCursor = this.filterBar.numFilters - 1; + this.setFilterMode(true); + } + success = true; + break; + case Button.LEFT: + if (numberOfStarters > 0) { + this.randomCursorObj.setVisible(false); + this.cursorObj.setVisible(true); + this.setCursor(onScreenFirstIndex + 8); // set last column + success = true; + } + break; + case Button.RIGHT: + if (numberOfStarters > 0) { + this.randomCursorObj.setVisible(false); + this.cursorObj.setVisible(true); + this.setCursor(onScreenFirstIndex); // set first column + success = true; + } + break; + } } else { - let starterContainer; const starterData = this.scene.gameData.starterData[this.lastSpecies.speciesId]; // prepare persistent starter data to store changes @@ -1466,7 +1573,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { Challenge.applyChallenges(this.scene.gameMode, Challenge.ChallengeType.STARTER_CHOICE, this.lastSpecies, isValidForChallenge, this.scene.gameData.getSpeciesDexAttrProps(this.lastSpecies, this.getCurrentDexProps(this.lastSpecies.speciesId)), isPartyValid); - const currentPartyValue = this.starterSpecies.map(s => s.generation).reduce((total: number, gen: number, i: number) => total += this.scene.gameData.getSpeciesStarterValue(this.starterSpecies[i].speciesId), 0); + const currentPartyValue = this.starterSpecies.map(s => s.generation).reduce((total: number, _gen: number, i: number) => total += this.scene.gameData.getSpeciesStarterValue(this.starterSpecies[i].speciesId), 0); const newCost = this.scene.gameData.getSpeciesStarterValue(this.lastSpecies.speciesId); if (!isDupe && isValidForChallenge.value && currentPartyValue + newCost <= this.getValueLimit() && this.starterSpecies.length < PLAYER_PARTY_MAX_SIZE) { // 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 = [ @@ -1605,7 +1712,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { ui.showText(i18next.t("starterSelectUiHandler:selectNature"), null, () => { const natures = this.scene.gameData.getNaturesForAttr(this.speciesStarterDexEntry?.natureAttr); ui.setModeWithoutClear(Mode.OPTION_SELECT, { - options: natures.map((n: Nature, i: number) => { + options: natures.map((n: Nature, _i: number) => { const option: OptionSelectItem = { label: getNatureName(n, true, true, true, this.scene.uiTheme), handler: () => { @@ -2016,11 +2123,10 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } } else { if (this.starterIconsCursorIndex === 0) { - // Up from first Pokemon in the team > go to filter + // Up from first Pokemon in the team > go to Random selection this.starterIconsCursorObj.setVisible(false); this.setSpecies(null); - this.filterBarCursor = Math.max(1, this.filterBar.numFilters - 1); - this.setFilterMode(true); + this.randomCursorObj.setVisible(true); } else { this.starterIconsCursorIndex--; this.moveStarterIconsCursor(this.starterIconsCursorIndex); @@ -2065,9 +2171,12 @@ export default class StarterSelectUiHandler extends MessageUiHandler { success = this.setCursor(this.cursor - 1); } else { // LEFT from filtered Pokemon, on the left edge - - if (this.starterSpecies.length === 0) { - // no starter in team > wrap around to the last column + if ( onScreenCurrentRow === 0 ) { + // from the first row of starters we go to the random selection + this.cursorObj.setVisible(false); + this.randomCursorObj.setVisible(true); + } else if (this.starterSpecies.length === 0) { + // no starter in team and not on first row > wrap around to the last column success = this.setCursor(this.cursor + Math.min(8, numberOfStarters - this.cursor)); } else if (onScreenCurrentRow < 7) { @@ -2103,7 +2212,11 @@ export default class StarterSelectUiHandler extends MessageUiHandler { success = this.setCursor(this.cursor + 1); } else { // RIGHT from filtered Pokemon, on the right edge - if (this.starterSpecies.length === 0) { + if ( onScreenCurrentRow === 0 ) { + // from the first row of starters we go to the random selection + this.cursorObj.setVisible(false); + this.randomCursorObj.setVisible(true); + } else if (this.starterSpecies.length === 0) { // no selected starter in team > wrap around to the first column success = this.setCursor(this.cursor - Math.min(8, this.cursor % 9)); @@ -2159,7 +2272,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { return [ isDupe, removeIndex ]; } - addToParty(species: PokemonSpecies, dexAttr: bigint, abilityIndex: integer, nature: Nature, moveset: StarterMoveset) { + addToParty(species: PokemonSpecies, dexAttr: bigint, abilityIndex: integer, nature: Nature, moveset: StarterMoveset, randomSelection: boolean = false) { const props = this.scene.gameData.getSpeciesDexAttrProps(species, dexAttr); this.starterIcons[this.starterSpecies.length].setTexture(species.getIconAtlasKey(props.formIndex, props.shiny, props.variant)); this.starterIcons[this.starterSpecies.length].setFrame(species.getIconId(props.female, props.formIndex, props.shiny, props.variant)); @@ -2170,7 +2283,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.starterAbilityIndexes.push(abilityIndex); this.starterNatures.push(nature); this.starterMovesets.push(moveset); - if (this.speciesLoaded.get(species.speciesId)) { + if (this.speciesLoaded.get(species.speciesId) || randomSelection ) { getPokemonSpeciesForm(species.speciesId, props.formIndex).cry(this.scene); } this.updateInstructions(); @@ -3001,7 +3114,6 @@ export default class StarterSelectUiHandler extends MessageUiHandler { this.dexAttrCursor = 0n; this.abilityCursor = -1; this.natureCursor = -1; - // 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)) @@ -3431,7 +3543,7 @@ export default class StarterSelectUiHandler extends MessageUiHandler { } tryUpdateValue(add?: integer, addingToParty?: boolean): boolean { - const value = this.starterSpecies.map(s => s.generation).reduce((total: integer, gen: integer, i: integer) => total += this.scene.gameData.getSpeciesStarterValue(this.starterSpecies[i].speciesId), 0); + const value = this.starterSpecies.map(s => s.generation).reduce((total: integer, _gen: integer, i: integer) => total += this.scene.gameData.getSpeciesStarterValue(this.starterSpecies[i].speciesId), 0); const newValue = value + (add || 0); const valueLimit = this.getValueLimit(); const overLimit = newValue > valueLimit;