diff --git a/src/ai/ai-moveset-gen.ts b/src/ai/ai-moveset-gen.ts index f392ca46d3f..fcfc93150b2 100644 --- a/src/ai/ai-moveset-gen.ts +++ b/src/ai/ai-moveset-gen.ts @@ -510,11 +510,7 @@ function forceStabMove( const chosenPool = stabMovePool.length > 0 || !forceAnyDamageIfNoStab ? stabMovePool - : filterPool( - pool, - m => allMoves[m[0]].category !== MoveCategory.STATUS && !STAB_BLACKLIST.has(m[0]), - totalWeight, - ); + : filterPool(pool, m => allMoves[m].category !== MoveCategory.STATUS && !STAB_BLACKLIST.has(m), totalWeight); if (chosenPool.length > 0) { let rand = randSeedInt(totalWeight.value); @@ -589,7 +585,7 @@ function fillInRemainingMovesetSlots( const tmCap = getMaxTmCount(pokemon.level); const eggCap = getMaxEggMoveCount(pokemon.level); const remainingPoolWeight = new NumberHolder(0); - while (remainingPool.length > pokemon.moveset.length && pokemon.moveset.length < 4) { + while (pokemon.moveset.length < 4) { const nonLevelMoveCount = tmCount.value + eggMoveCount.value; remainingPool = filterPool( baseWeights, @@ -605,6 +601,11 @@ function fillInRemainingMovesetSlots( if (pokemon.hasTrainer()) { filterRemainingTrainerMovePool(remainingPool, pokemon); } + // Ensure loop cannot run infinitely if there are no allowed moves left to + // fill the remaining slots + if (remainingPool.length === 0) { + return; + } const totalWeight = remainingPool.reduce((v, m) => v + m[1], 0); let rand = randSeedInt(totalWeight); let index = 0; @@ -719,7 +720,7 @@ export function generateMoveset(pokemon: Pokemon): void { tmCount, eggMoveCount, baseWeights, - filterPool(baseWeights, (m: MoveId) => !pokemon.moveset.some(mo => m[0] === mo.moveId)), + filterPool(baseWeights, (m: MoveId) => !pokemon.moveset.some(mo => m === mo.moveId)), ); } diff --git a/test/ai/ai-moveset-gen.test.ts b/test/ai/ai-moveset-gen.test.ts index 6d927926131..faec3856485 100644 --- a/test/ai/ai-moveset-gen.test.ts +++ b/test/ai/ai-moveset-gen.test.ts @@ -1,4 +1,4 @@ -import { __INTERNAL_TEST_EXPORTS } from "#app/ai/ai-moveset-gen"; +import { __INTERNAL_TEST_EXPORTS, generateMoveset } from "#app/ai/ai-moveset-gen"; import { COMMON_TIER_TM_LEVEL_REQUIREMENT, GREAT_TIER_TM_LEVEL_REQUIREMENT, @@ -282,4 +282,28 @@ describe("Regression Tests - ai-moveset-gen.ts", () => { ).not.toThrow(); }); }); + + describe("generateMoveset", () => { + it("should spawn with 4 moves if possible", async () => { + // Need to be in a wave for moveset generation to not actually break + await game.classicMode.startBattle([SpeciesId.PIKACHU]); + + // Create a pokemon that can learn at least 4 moves + pokemon = createTestablePokemon(SpeciesId.ROCKRUFF, { level: 15 }); + vi.spyOn(pokemon, "getLevelMoves").mockReturnValue([ + [1, MoveId.TACKLE], + [4, MoveId.LEER], + [7, MoveId.SAND_ATTACK], + [10, MoveId.ROCK_THROW], + [13, MoveId.DOUBLE_TEAM], + ]); + + // Generate the moveset + generateMoveset(pokemon); + expect(pokemon.moveset).toHaveLength(4); + // Unlike other test suites, phase interceptor is not automatically restored after the tests here, + // since most tests in this suite do not need the phase + game.phaseInterceptor.restoreOg(); + }); + }); }); diff --git a/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts b/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts index ae4eb0647ce..cdca4ab6326 100644 --- a/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts +++ b/test/mystery-encounter/encounters/the-expert-breeder-encounter.test.ts @@ -127,7 +127,8 @@ describe("The Expert Pokémon Breeder - Mystery Encounter", () => { }); }); - it("should start battle against the trainer with correctly loaded assets", async () => { + // TODO: This is a flaky test that needs to have its fishy logic revisited + it.todo("should start battle against the trainer with correctly loaded assets", async () => { await game.runToMysteryEncounter(MysteryEncounterType.THE_EXPERT_POKEMON_BREEDER, defaultParty); let successfullyLoaded = false;