diff --git a/package.json b/package.json index b23b9b9b75d..102c76830b1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "pokemon-rogue-battle", "private": true, - "version": "1.11.0", + "version": "1.11.1", "type": "module", "scripts": { "start:prod": "vite --mode production", diff --git a/src/data/balance/biomes.ts b/src/data/balance/biomes.ts index 562c07396e0..f81f76058ec 100644 --- a/src/data/balance/biomes.ts +++ b/src/data/balance/biomes.ts @@ -163,15 +163,15 @@ export const biomePokemonPools: BiomePokemonPools = { [BiomePoolTier.UNCOMMON]: { [TimeOfDay.DAWN]: [ SpeciesId.SUNKERN, SpeciesId.COMBEE ], [TimeOfDay.DAY]: [ SpeciesId.SUNKERN, SpeciesId.COMBEE ], - [TimeOfDay.DUSK]: [ SpeciesId.SEEDOT, SpeciesId.NOIBAT ], - [TimeOfDay.NIGHT]: [ SpeciesId.SEEDOT, SpeciesId.NOIBAT ], + [TimeOfDay.DUSK]: [ SpeciesId.SEEDOT ], + [TimeOfDay.NIGHT]: [ SpeciesId.SEEDOT ], [TimeOfDay.ALL]: [ SpeciesId.MILTANK, SpeciesId.CHERUBI, SpeciesId.FOONGUS, ] }, [BiomePoolTier.RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], - [TimeOfDay.DUSK]: [], - [TimeOfDay.NIGHT]: [], + [TimeOfDay.DUSK]: [ SpeciesId.NOIBAT ], + [TimeOfDay.NIGHT]: [ SpeciesId.NOIBAT ], [TimeOfDay.ALL]: [ SpeciesId.BULBASAUR, SpeciesId.GROWLITHE, SpeciesId.TURTWIG, SpeciesId.BONSLY ] }, [BiomePoolTier.SUPER_RARE]: { [TimeOfDay.DAWN]: [], [TimeOfDay.DAY]: [], [TimeOfDay.DUSK]: [], [TimeOfDay.NIGHT]: [], [TimeOfDay.ALL]: [] }, diff --git a/src/data/balance/init-biomes.ts b/src/data/balance/init-biomes.ts index 1b410e637e8..eb2d4151fbf 100644 --- a/src/data/balance/init-biomes.ts +++ b/src/data/balance/init-biomes.ts @@ -3547,7 +3547,7 @@ export function initBiomes() { ], [SpeciesId.NOIBAT, PokemonType.FLYING, PokemonType.DRAGON, [ [BiomeId.CAVE, BiomePoolTier.UNCOMMON], - [BiomeId.GRASS, BiomePoolTier.UNCOMMON, [TimeOfDay.DUSK, TimeOfDay.NIGHT]] + [BiomeId.GRASS, BiomePoolTier.RARE, [TimeOfDay.DUSK, TimeOfDay.NIGHT]] ] ], [SpeciesId.NOIVERN, PokemonType.FLYING, PokemonType.DRAGON, [ diff --git a/src/data/trainers/rival-party-config.ts b/src/data/trainers/rival-party-config.ts index b5a8cb532b3..67b3f50d379 100644 --- a/src/data/trainers/rival-party-config.ts +++ b/src/data/trainers/rival-party-config.ts @@ -253,7 +253,7 @@ const SLOT_3_FIGHT_2 = [ SpeciesId.MACHOP, SpeciesId.GASTLY, SpeciesId.MAGNEMITE, - SpeciesId.RHYDON, + SpeciesId.RHYHORN, SpeciesId.TANGELA, SpeciesId.PORYGON, SpeciesId.ELEKID, @@ -298,7 +298,7 @@ const SLOT_3_FIGHT_3 = [ SpeciesId.RHYDON, SpeciesId.TANGROWTH, SpeciesId.PORYGON2, - SpeciesId.ELECTIVIRE, + SpeciesId.ELECTABUZZ, SpeciesId.MAGMAR, SpeciesId.AZUMARILL, SpeciesId.URSARING, diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index be85e2ebe3a..6ce5c1ffc2c 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -6388,12 +6388,14 @@ export class EnemyPokemon extends Pokemon { } const eventBossVariant = getDailyEventSeedBossVariant(globalScene.seed); - if (eventBossVariant != null && globalScene.gameMode.isWaveFinal(globalScene.currentBattle.waveIndex)) { + const eventBossVariantEnabled = + eventBossVariant != null && globalScene.gameMode.isWaveFinal(globalScene.currentBattle.waveIndex); + if (eventBossVariantEnabled) { this.shiny = true; } if (this.shiny) { - this.variant = eventBossVariant ?? this.generateShinyVariant(); + this.variant = eventBossVariantEnabled ? eventBossVariant : this.generateShinyVariant(); if (Overrides.ENEMY_VARIANT_OVERRIDE !== null) { this.variant = Overrides.ENEMY_VARIANT_OVERRIDE; } diff --git a/src/phases/move-phase.ts b/src/phases/move-phase.ts index 6350791e9bb..5115e3da595 100644 --- a/src/phases/move-phase.ts +++ b/src/phases/move-phase.ts @@ -286,7 +286,7 @@ export class MovePhase extends PokemonPhase { // Apply queenly majesty / dazzling if (!failed) { - const defendingSidePlayField = user.isPlayer() ? globalScene.getPlayerField() : globalScene.getEnemyField(); + const defendingSidePlayField = user.isPlayer() ? globalScene.getEnemyField() : globalScene.getPlayerField(); const cancelled = new BooleanHolder(false); defendingSidePlayField.forEach((pokemon: Pokemon) => { applyAbAttrs("FieldPriorityMoveImmunityAbAttr", { diff --git a/src/queues/post-summon-phase-priority-queue.ts b/src/queues/post-summon-phase-priority-queue.ts index 28e727de01b..fe08713e501 100644 --- a/src/queues/post-summon-phase-priority-queue.ts +++ b/src/queues/post-summon-phase-priority-queue.ts @@ -11,7 +11,7 @@ import { sortInSpeedOrder } from "#app/utils/speed-order"; */ export class PostSummonPhasePriorityQueue extends PokemonPhasePriorityQueue { protected override reorder(): void { - this.queue = sortInSpeedOrder(this.queue, false); + this.queue = sortInSpeedOrder(this.queue); this.queue.sort((phaseA, phaseB) => phaseB.getPriority() - phaseA.getPriority()); } diff --git a/src/ui/handlers/party-ui-handler.ts b/src/ui/handlers/party-ui-handler.ts index 7806a6111c1..6fbb4052aeb 100644 --- a/src/ui/handlers/party-ui-handler.ts +++ b/src/ui/handlers/party-ui-handler.ts @@ -1586,9 +1586,8 @@ export class PartyUiHandler extends MessageUiHandler { this.updateOptionsWithModifierTransferMode(pokemon); break; case PartyUiMode.SWITCH: - this.options.push(PartyOption.RELEASE); - break; case PartyUiMode.RELEASE: + case PartyUiMode.CHECK: this.options.push(PartyOption.RELEASE); break; } diff --git a/src/ui/handlers/pokedex-page-ui-handler.ts b/src/ui/handlers/pokedex-page-ui-handler.ts index fa10c88952c..c3be9f87d21 100644 --- a/src/ui/handlers/pokedex-page-ui-handler.ts +++ b/src/ui/handlers/pokedex-page-ui-handler.ts @@ -776,7 +776,8 @@ export class PokedexPageUiHandler extends MessageUiHandler { || (this.tmMoves.length === 0 && o === MenuOptions.TM_MOVES) || (!globalScene.gameData.dexData[this.species.speciesId].ribbons.getRibbons() && o === MenuOptions.RIBBONS - && !globalScene.showMissingRibbons); + && !globalScene.showMissingRibbons + && !globalScene.gameData.starterData[this.species.speciesId]?.classicWinCount); const color = getTextColor(isDark ? TextStyle.SHADOW_TEXT : TextStyle.SETTINGS_VALUE, false); const shadow = getTextColor(isDark ? TextStyle.SHADOW_TEXT : TextStyle.SETTINGS_VALUE, true); return `[shadow=${shadow}][color=${color}]${label}[/color][/shadow]`; @@ -1778,6 +1779,7 @@ export class PokedexPageUiHandler extends MessageUiHandler { } else if ( !globalScene.gameData.dexData[this.species.speciesId].ribbons.getRibbons() && !globalScene.showMissingRibbons + && !globalScene.gameData.starterData[this.species.speciesId]?.classicWinCount ) { ui.showText(i18next.t("pokedexUiHandler:noRibbons")); error = true; diff --git a/src/utils/speed-order.ts b/src/utils/speed-order.ts index f2733a74998..aaf3a4526c7 100644 --- a/src/utils/speed-order.ts +++ b/src/utils/speed-order.ts @@ -12,15 +12,13 @@ interface hasPokemon { /** * Sorts an array of {@linkcode Pokemon} by speed, taking Trick Room into account. * @param pokemonList - The list of Pokemon or objects containing Pokemon - * @param shuffleFirst - Whether to shuffle the list before sorting (to handle speed ties). Default `true`. * @returns The sorted array of {@linkcode Pokemon} */ -export function sortInSpeedOrder(pokemonList: T[], shuffleFirst = true): T[] { - if (shuffleFirst) { - shufflePokemonList(pokemonList); - } - sortBySpeed(pokemonList); - return pokemonList; +export function sortInSpeedOrder(pokemonList: T[]): T[] { + const grouped = groupPokemon(pokemonList); + shufflePokemonList(grouped); + sortBySpeed(grouped); + return grouped.flat(); } /** @@ -28,7 +26,7 @@ export function sortInSpeedOrder(pokemonList: T[ * @param pokemonList - The array of Pokemon or objects containing Pokemon * @returns The same array instance that was passed in, shuffled. */ -function shufflePokemonList(pokemonList: T[]): T[] { +function shufflePokemonList(pokemonList: T[][]): void { // This is seeded with the current turn to prevent an inconsistency where it // was varying based on how long since you last reloaded globalScene.executeWithSeedOffset( @@ -36,7 +34,6 @@ function shufflePokemonList(pokemonList: T[]): T globalScene.currentBattle.turn * 1000 + pokemonList.length, globalScene.waveSeed, ); - return pokemonList; } /** Type guard for {@linkcode sortBySpeed} to avoid importing {@linkcode Pokemon} */ @@ -44,11 +41,15 @@ function isPokemon(p: Pokemon | hasPokemon): p is Pokemon { return typeof (p as hasPokemon).getPokemon !== "function"; } +function getPokemon(p: Pokemon | hasPokemon): Pokemon { + return isPokemon(p) ? p : p.getPokemon(); +} + /** Sorts an array of {@linkcode Pokemon} by speed (without shuffling) */ -function sortBySpeed(pokemonList: T[]): void { - pokemonList.sort((a, b) => { - const aSpeed = (isPokemon(a) ? a : a.getPokemon()).getEffectiveStat(Stat.SPD); - const bSpeed = (isPokemon(b) ? b : b.getPokemon()).getEffectiveStat(Stat.SPD); +function sortBySpeed(groupedPokemonList: T[][]): void { + groupedPokemonList.sort((a, b) => { + const aSpeed = getPokemon(a[0]).getEffectiveStat(Stat.SPD); + const bSpeed = getPokemon(b[0]).getEffectiveStat(Stat.SPD); return bSpeed - aSpeed; }); @@ -57,6 +58,21 @@ function sortBySpeed(pokemonList: T[]): void { const speedReversed = new BooleanHolder(false); globalScene.arena.applyTags(ArenaTagType.TRICK_ROOM, speedReversed); if (speedReversed.value) { - pokemonList.reverse(); + groupedPokemonList.reverse(); } } + +function groupPokemon(pokemonList: T[]): T[][] { + const runs: T[][] = []; + for (const pkmn of pokemonList) { + const pokemon = getPokemon(pkmn); + const lastGroup = runs.at(-1); + if (lastGroup != null && lastGroup.length > 0 && getPokemon(lastGroup[0]) === pokemon) { + lastGroup.push(pkmn); + } else { + runs.push([pkmn]); + } + } + + return runs; +} diff --git a/test/utils/speed-order.test.ts b/test/utils/speed-order.test.ts new file mode 100644 index 00000000000..e408c5823c4 --- /dev/null +++ b/test/utils/speed-order.test.ts @@ -0,0 +1,58 @@ +import { AbilityId } from "#enums/ability-id"; +import { MoveId } from "#enums/move-id"; +import { SpeciesId } from "#enums/species-id"; +import { Stat } from "#enums/stat"; +import { GameManager } from "#test/test-utils/game-manager"; +import { sortInSpeedOrder } from "#utils/speed-order"; +import { afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; + +describe("Utils - Speed Order", () => { + 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 + .battleStyle("single") + .startingLevel(100) + .enemyLevel(100) + .enemyMoveset(MoveId.SPLASH) + .enemyAbility(AbilityId.BALL_FETCH) + .ability(AbilityId.BALL_FETCH) + .enemySpecies(SpeciesId.REGIELEKI); + }); + + it("Sorts correctly in the basic case", async () => { + await game.classicMode.startBattle([SpeciesId.SLOWPOKE, SpeciesId.MEW]); + const [slowpoke, mew] = game.field.getPlayerParty(); + const regieleki = game.field.getEnemyPokemon(); + const pkmnList = [slowpoke, regieleki, mew]; + + expect(sortInSpeedOrder(pkmnList)).toEqual([regieleki, mew, slowpoke]); + }); + + it("Correctly sorts grouped pokemon", async () => { + await game.classicMode.startBattle([SpeciesId.SLOWPOKE, SpeciesId.MEW, SpeciesId.DITTO]); + const [slowpoke, mew, ditto] = game.field.getPlayerParty(); + const regieleki = game.field.getEnemyPokemon(); + ditto.stats[Stat.SPD] = slowpoke.getStat(Stat.SPD); + + const pkmnList = [slowpoke, slowpoke, ditto, ditto, mew, regieleki, regieleki]; + const sorted = sortInSpeedOrder(pkmnList); + + expect([ + [regieleki, regieleki, mew, slowpoke, slowpoke, ditto, ditto], + [regieleki, regieleki, mew, ditto, ditto, slowpoke, slowpoke], + ]).toContainEqual(sorted); + }); +});