• The moderator of this forum is jetou.
  • Welcome to Smogon! Take a moment to read the Introduction to Smogon for a run-down on everything Smogon, and make sure you take some time to read the global rules.

Programming Learnset queries

Hello, I'm attempting to write a script that queries the Smogon/Showdown learnset data to produce a list of Pokémon that learn a specific move in a specific generation (or Natdex). The functionality should be similar to the Showdown chat command "dexsearch." I'm willing to comb through learnset.json if necessary, but I was wondering if there is an easier method. I haven't seen anything like the query I need on Showdown APIs or fan projects, but I could've missed something.

Also, when using both dexsearch and learnset.json, certain alternate forms are missing most of the base form's moves. For example, Rotom-Wash will not appear when dexsearching Volt Switch, and only has Hydro Pump listed in the learnset data. How does the teambuilder determine that Rotom-Wash should have base Rotom's learnset as well?

Thanks!
 
I believe Pokemon Showdown uses logic to cumulatively apply data from leanest.json of base formes to their corresponding formes (e.g. on Deoxys to Deoxys Attack). I've found this package from Modular Pokemon Showdown, but I'd imagine there's a way to get it to work with the official Pokemon Showdown package as well.

Here's some code I'm using in a personal project to iterate over the leanest to convert it to a .csv to insert into an SQL database. You could probably iterate over the leanest data in a similar way to transform it into your own desired data structure.
 
Some of the links, particularly the one to the code, are broken now. Here's a TypeScript example:

JavaScript:
// Library to validate data before writing to .csv
import { z } from "zod";
// readonly ["M", "F", "N"]
import { genderNames } from "../enums";
// Constructed according to @pkmn package
import { gens } from "../gens";
// Writes JSON object to .csv using json-2-csv library
import { writeJunctionJsonToCsv } from "../path";
import { toID } from "@pkmn/data";

// Validates a .csv row for event data
const eventSchema = z.object({
  gen: z.number(),
  pokemonPsId: z.string(),
  eventIndex: z.number(),
  level: z.number().optional(),
  shinyChance: z.enum(["ALWAYS", "SOMETIMES", "NEVER"]),
  gender: z.enum(genderNames).optional(),
  nature: z.string().optional(),
  // Written to JSON string in the .csv
  guaranteedIvs: z
    .object({
      hp: z.number().optional(),
      atk: z.number().optional(),
      def: z.number().optional(),
      spa: z.number().optional(),
      spd: z.number().optional(),
      spe: z.number().optional(),
    })
    .optional(),
  numberPerfectIvs: z.number().optional(),
  hiddenAbilityChance: z.enum(["ALWAYS", "NEVER"]).optional(),
  // Written to JSON string in the .csv
  abilityPsIds: z.array(z.string()).optional(),
  movePsIds: z.array(z.string()).optional(),
  pokeball: z.string().optional(),
  japanOnly: z.boolean().optional(),
});

// Matches Pokemon to Move
const learnsMoveSchema = z.object({
  gen: z.number(),
  pokemonPsId: z.string(),
  movePsId: z.string(),
  // Semicolon separated
  learnMethods: z.string(),
});

export async function writeLearnsetAndEventCsv() {
  const eventRows: z.infer<typeof eventSchema>[] = [];
  const learnsMoveRows: z.infer<typeof learnsMoveSchema>[] = [];

  // Keys are "gen_pokemon_move", values are learn method codes
  const learnMethodByPokemonPsId_MovePsId = new Map<string, Set<string>>();

  for (const gen of gens) {
    const learnsets = gen.learnsets;
    const genString = gen.num.toString();

    for (const specie of gen.species) {
      for await (const learnset of learnsets.all(specie)) {
        const { learnset: learnData, eventData } = learnset;

        // Parse the event data
        if (eventData) {
          eventData.forEach((eventInfo, index) => {
            eventRows.push(
              eventSchema.parse({
                gen: gen.num,
                pokemonPsId: specie.id,
                eventIndex: index,
                level: eventInfo.level,
                shinyChance:
                  eventInfo.shiny === 1
                    ? "SOMETIMES"
                    : eventInfo.shiny
                    ? "ALWAYS"
                    : "NEVER",
                gender: eventInfo.gender,
                nature: toID(eventInfo.nature),
                guaranteedIvs: eventInfo.ivs,
                numberPerfectIvs: eventInfo.perfectIVs,
                hiddenAbilityChance: eventInfo.isHidden ? "ALWAYS" : "NEVER",
                abilityPsIds: eventInfo.abilities,
                movePsIds: eventInfo.moves,
                pokeball: eventInfo.pokeball,
                japanOnly: eventInfo.japan,
              })
            );
          });
        }

        // Parse the learnset data
        if (learnData) {
          for (const [movePsId, learnMethods] of Object.entries(learnData)) {
            for (let learnMethod of learnMethods) {
              if (learnMethod.indexOf(genString) !== 0) {
                continue;
              }
              // Not a real source, so I don't want to store it
              // See https://github.com/smogon/pokemon-showdown/blob/master/sim/global-types.ts, 'MoveSource' type for codes
              else if (learnMethod.includes("C")) {
                continue;
              }

              learnMethod = learnMethod.replace(genString, "");

              const pokemonPsId = specie.id;
              const key = [genString, pokemonPsId, movePsId].join("_");

              if (learnMethodByPokemonPsId_MovePsId.has(key)) {
                learnMethodByPokemonPsId_MovePsId.get(key)?.add(learnMethod);
              } else {
                learnMethodByPokemonPsId_MovePsId.set(
                  key,
                  new Set([learnMethod])
                );
              }
            }
          }
        }
      }
    }

    for (let [key, value] of learnMethodByPokemonPsId_MovePsId.entries()) {
      const [genString, pokemonId, moveId] = key.split("_");

      if (!genString || !pokemonId || !moveId) {
        continue;
      }

      learnsMoveRows.push({
        gen: +genString,
        pokemonPsId: pokemonId,
        movePsId: moveId,
        learnMethods: [...value].join(";"),
      });
    }
  }

  writeJunctionJsonToCsv("pokemon", "event", eventRows);
  writeJunctionJsonToCsv("pokemon", "learnsMove", learnsMoveRows);
}
 
Some of the links, particularly the one to the code, are broken now. Here's a TypeScript example:

JavaScript:
// Library to validate data before writing to .csv
import { z } from "zod";
// readonly ["M", "F", "N"]
import { genderNames } from "../enums";
// Constructed according to @pkmn package
import { gens } from "../gens";
// Writes JSON object to .csv using json-2-csv library
import { writeJunctionJsonToCsv } from "../path";
import { toID } from "@pkmn/data";

// Validates a .csv row for event data
const eventSchema = z.object({
  gen: z.number(),
  pokemonPsId: z.string(),
  eventIndex: z.number(),
  level: z.number().optional(),
  shinyChance: z.enum(["ALWAYS", "SOMETIMES", "NEVER"]),
  gender: z.enum(genderNames).optional(),
  nature: z.string().optional(),
  // Written to JSON string in the .csv
  guaranteedIvs: z
    .object({
      hp: z.number().optional(),
      atk: z.number().optional(),
      def: z.number().optional(),
      spa: z.number().optional(),
      spd: z.number().optional(),
      spe: z.number().optional(),
    })
    .optional(),
  numberPerfectIvs: z.number().optional(),
  hiddenAbilityChance: z.enum(["ALWAYS", "NEVER"]).optional(),
  // Written to JSON string in the .csv
  abilityPsIds: z.array(z.string()).optional(),
  movePsIds: z.array(z.string()).optional(),
  pokeball: z.string().optional(),
  japanOnly: z.boolean().optional(),
});

// Matches Pokemon to Move
const learnsMoveSchema = z.object({
  gen: z.number(),
  pokemonPsId: z.string(),
  movePsId: z.string(),
  // Semicolon separated
  learnMethods: z.string(),
});

export async function writeLearnsetAndEventCsv() {
  const eventRows: z.infer<typeof eventSchema>[] = [];
  const learnsMoveRows: z.infer<typeof learnsMoveSchema>[] = [];

  // Keys are "gen_pokemon_move", values are learn method codes
  const learnMethodByPokemonPsId_MovePsId = new Map<string, Set<string>>();

  for (const gen of gens) {
    const learnsets = gen.learnsets;
    const genString = gen.num.toString();

    for (const specie of gen.species) {
      for await (const learnset of learnsets.all(specie)) {
        const { learnset: learnData, eventData } = learnset;

        // Parse the event data
        if (eventData) {
          eventData.forEach((eventInfo, index) => {
            eventRows.push(
              eventSchema.parse({
                gen: gen.num,
                pokemonPsId: specie.id,
                eventIndex: index,
                level: eventInfo.level,
                shinyChance:
                  eventInfo.shiny === 1
                    ? "SOMETIMES"
                    : eventInfo.shiny
                    ? "ALWAYS"
                    : "NEVER",
                gender: eventInfo.gender,
                nature: toID(eventInfo.nature),
                guaranteedIvs: eventInfo.ivs,
                numberPerfectIvs: eventInfo.perfectIVs,
                hiddenAbilityChance: eventInfo.isHidden ? "ALWAYS" : "NEVER",
                abilityPsIds: eventInfo.abilities,
                movePsIds: eventInfo.moves,
                pokeball: eventInfo.pokeball,
                japanOnly: eventInfo.japan,
              })
            );
          });
        }

        // Parse the learnset data
        if (learnData) {
          for (const [movePsId, learnMethods] of Object.entries(learnData)) {
            for (let learnMethod of learnMethods) {
              if (learnMethod.indexOf(genString) !== 0) {
                continue;
              }
              // Not a real source, so I don't want to store it
              // See https://github.com/smogon/pokemon-showdown/blob/master/sim/global-types.ts, 'MoveSource' type for codes
              else if (learnMethod.includes("C")) {
                continue;
              }

              learnMethod = learnMethod.replace(genString, "");

              const pokemonPsId = specie.id;
              const key = [genString, pokemonPsId, movePsId].join("_");

              if (learnMethodByPokemonPsId_MovePsId.has(key)) {
                learnMethodByPokemonPsId_MovePsId.get(key)?.add(learnMethod);
              } else {
                learnMethodByPokemonPsId_MovePsId.set(
                  key,
                  new Set([learnMethod])
                );
              }
            }
          }
        }
      }
    }

    for (let [key, value] of learnMethodByPokemonPsId_MovePsId.entries()) {
      const [genString, pokemonId, moveId] = key.split("_");

      if (!genString || !pokemonId || !moveId) {
        continue;
      }

      learnsMoveRows.push({
        gen: +genString,
        pokemonPsId: pokemonId,
        movePsId: moveId,
        learnMethods: [...value].join(";"),
      });
    }
  }

  writeJunctionJsonToCsv("pokemon", "event", eventRows);
  writeJunctionJsonToCsv("pokemon", "learnsMove", learnsMoveRows);
}
Thanks! I forgot to post here, but I figured it out and your code was a big help.
 
Back
Top