diff --git a/biome.jsonc b/biome.jsonc index d2f7c711dc9..d4cb67d33a6 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -19,6 +19,7 @@ // and having to verify whether each individual file is ignored "includes": [ "**", + "!**/*.d.ts", "!**/dist/**/*", "!**/build/**/*", "!**/coverage/**/*", @@ -176,10 +177,9 @@ } }, - // Overrides to prevent unused import removal inside `overrides.ts` and enums files (for TSDoc linkcodes), - // as well as in all TS files in `scripts/` (which are assumed to be boilerplate templates). + // Overrides to prevent unused import removal inside `overrides.ts` and enums files (for TSDoc linkcodes) { - "includes": ["**/src/overrides.ts", "**/src/enums/**/*", "**/scripts/**/*.ts", "**/*.d.ts"], + "includes": ["**/src/overrides.ts", "**/src/enums/**/*"], "linter": { "rules": { "correctness": { @@ -189,7 +189,7 @@ } }, { - "includes": ["**/src/overrides.ts", "**/scripts/**/*.ts"], + "includes": ["**/src/overrides.ts"], "linter": { "rules": { "style": { diff --git a/package.json b/package.json index d3494da677c..87ba8309c97 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "test:watch": "vitest watch --coverage --no-isolate", "test:silent": "vitest run --silent='passed-only' --no-isolate", "test:create": "node scripts/create-test/create-test.js", + "eggMoves:parse": "node scripts/parse-egg-moves/main.js", "typecheck": "tsc --noEmit", "eslint": "eslint --fix .", "eslint-ci": "eslint .", diff --git a/scripts/biome.jsonc b/scripts/biome.jsonc new file mode 100644 index 00000000000..848e2a87184 --- /dev/null +++ b/scripts/biome.jsonc @@ -0,0 +1,38 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.0.0/schema.json", + // extend from base config in root dir + "extends": "//", + "files": { + "includes": ["**/*.{js,ts,jsx,tsx}"] + }, + "linter": { + "rules": { + "style": { + // Unfortunately, TS does not support multiple configs, so we need to have Biome pull the slack + // and effectively enforce `noErasableSyntax` + "noEnum": "error", + "noNamespace": "error", + "noNonNullAssertion": "error", // TODO: Remove once added to main config + "useForOf": "error" // TODO: Move to main config + }, + "suspicious": { + "noImplicitAnyLet": "error" + } + } + }, + "overrides": [ + // Prevent unused import removal inside boilerplate files. + // These are unused in the file themselves, but will become used once copied and converted to actual TS files. + // (If not, the generated files will themselves produce errors.) + { + "includes": ["**/*.boilerplate.ts", "**/boilerplates/*.ts"], // TODO: Rename existing boilerplates in the folder and remove this 2nd alias + "linter": { + "rules": { + "correctness": { + "noUnusedImports": "off" + } + } + } + } + ] +} diff --git a/scripts/jsconfig.json b/scripts/jsconfig.json new file mode 100644 index 00000000000..34a18a515e7 --- /dev/null +++ b/scripts/jsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../tsconfig", + "include": ["**/*.{js,jsx}"], + "compilerOptions": { + "checkJs": true, + "target": "esnext", + "module": "nodenext", + "moduleResolution": "nodenext", + "erasableSyntaxOnly": true, + "strict": true + } +} diff --git a/scripts/parse-egg-moves/egg-move-template.ts b/scripts/parse-egg-moves/egg-move-template.ts new file mode 100644 index 00000000000..bfac05f4bde --- /dev/null +++ b/scripts/parse-egg-moves/egg-move-template.ts @@ -0,0 +1,10 @@ +//! DO NOT EDIT THIS FILE - CREATED BY THE `eggMoves:parse` script automatically +import { MoveId } from "#enums/move-id"; +import { SpeciesId } from "#enums/species-id"; + +/** + * An object mapping all base form {@linkcode SpeciesId}s to an array of {@linkcode MoveId}s corresponding + * to their current egg moves. + * Generated by the `eggMoves:parse` script using a CSV sourced from the current Balance Team spreadsheet. + */ +export const speciesEggMoves = "{{table}}"; diff --git a/scripts/parse-egg-moves/help-message.js b/scripts/parse-egg-moves/help-message.js new file mode 100644 index 00000000000..6b5f1965a2b --- /dev/null +++ b/scripts/parse-egg-moves/help-message.js @@ -0,0 +1,16 @@ +import chalk from "chalk"; + +export function showHelpText() { + console.log(` +Usage: ${chalk.cyan("pnpm eggMoves:parse [options]")} +If given no options, assumes ${chalk.blue("\`--interactive\`")}. +If given only a file path, assumes ${chalk.blue("\`--file\`")}. + +${chalk.hex("#ffa500")("Options:")} + ${chalk.blue("-h, --help")} Show this help message. + ${chalk.blue("-f, --file[=PATH]")} Specify a path to a CSV file to read, or provide one from stdin. + ${chalk.blue("-t, --text[=TEXT]")} + ${chalk.blue("-c, --console[=TEXT]")} Specify CSV text to read, or provide it from stdin. + ${chalk.blue("-i, --interactive")} Run in interactive mode (default) +`); +} diff --git a/scripts/parse-egg-moves/interactive.js b/scripts/parse-egg-moves/interactive.js new file mode 100644 index 00000000000..814957dc09a --- /dev/null +++ b/scripts/parse-egg-moves/interactive.js @@ -0,0 +1,104 @@ +import fs from "fs"; +import chalk from "chalk"; +import inquirer from "inquirer"; +import { showHelpText } from "./help-message.js"; + +/** + * Prompt the user to interactively select an option (console/file) to retrieve the egg move CSV. + * @returns {Promise<{type: "Console" | "File", value: string} | {type: "Exit"}>} The selected option with value + */ +export async function runInteractive() { + /** @type {"Console" | "File" | "Help" | "Exit"} */ + const answer = await inquirer + .prompt([ + { + type: "list", + name: "type", + message: "Select the method to obtain egg moves.", + choices: ["Console", "File", "Help", "Exit"], + }, + ]) + .then(a => a.type); + + if (answer === "Exit") { + console.log("Exiting..."); + process.exitCode = 1; + return { type: "Exit" }; + } + + if (answer === "Help") { + showHelpText(); + return { type: "Exit" }; + } + + if (!["Console", "File"].includes(answer)) { + console.error(chalk.red("Please provide a valid type!")); + return await runInteractive(); + } + + return { type: answer, value: await promptForValue(answer) }; +} + +/** + * Prompt the user to give a value (either the direct CSV or the file path). + * @param {"Console" | "File"} type - The input method + * @returns {Promise} A Promise resolving with the CSV/file path. + */ +function promptForValue(type) { + switch (type) { + case "Console": + return doPromptConsole(); + case "File": + return getFilePath(); + } +} + +/** + * Prompt the user to enter a file path from the console. + * @returns {Promise} The file path inputted by the user. + */ +async function getFilePath() { + return await inquirer + .prompt([ + { + type: "input", + name: "path", + message: "Please enter the path to the egg move CSV file.", + validate: input => { + if (input.trim() === "") { + return "File path cannot be empty!"; + } + if (!fs.existsSync(input)) { + return "File does not exist!"; + } + return true; + }, + }, + ]) + .then(answer => answer.path); +} + +/** + * Prompt the user for CSV input from the console. + * @returns {Promise} The CSV input from the user. + */ +async function doPromptConsole() { + return await inquirer + .prompt([ + { + type: "input", + name: "csv", + message: "Please enter the egg move CSV text.", + validate: input => { + if (input.trim() === "") { + return "CSV text cannot be empty!"; + } + if (!input.match(/^[^,]+(,[^,]+){4}$/gm)) { + return "CSV text malformed - should contain 5 consecutive comma-separated values per line!"; + } + return true; + }, + }, + ]) + .then(answer => answer.csv); +} diff --git a/scripts/parse-egg-moves/main.js b/scripts/parse-egg-moves/main.js new file mode 100644 index 00000000000..1ab0e30bda0 --- /dev/null +++ b/scripts/parse-egg-moves/main.js @@ -0,0 +1,162 @@ +/* + * This script accepts a CSV value or file path as input, parses the egg moves, + * and writes the output to a TypeScript file. + * It can be run interactively or with command line arguments. + * Usage: `pnpm eggMoves:parse` + */ + +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import chalk from "chalk"; +import { showHelpText } from "./help-message.js"; +import { runInteractive } from "./interactive.js"; +import { parseEggMoves } from "./parse.js"; + +const version = "1.0.0"; + +// Get the directory name of the current module file +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const projectRoot = path.join(__dirname, "..", ".."); +const templatePath = path.join(__dirname, "egg-move-template.ts"); +// TODO: Do we want this to be configurable? +const eggMoveTargetPath = path.join(projectRoot, "src/data/balance/egg-moves.ts"); + +/** + * Runs the interactive eggMoves:parse CLI. + * @returns {Promise} + */ +async function start() { + console.log(chalk.yellow(`🄚 Egg Move Parser - v${version}`)); + + if (process.argv.length > 4) { + console.error( + chalk.redBright.bold( + `āœ— Error: Too many arguments provided!\nArgs: ${chalk.hex("#7310fdff")(process.argv.slice(2).join(" "))}`, + ), + ); + showHelpText(); + process.exitCode = 1; + return; + } + + let csv = ""; + const inputType = await parseArguments(); + if (process.exitCode) { + // If exit code is non-zero, return to allow it to propagate up the chain. + return; + } + switch (inputType.type) { + case "Console": + csv = inputType.value; + break; + case "File": + csv = await fs.promises.readFile(inputType.value, "utf-8"); + break; + case "Exit": + // Help screen triggered; break out + return; + } + + await writeToFile(parseEggMoves(csv)); +} + +/** + * Handle the arguments passed to the script and obtain the CSV input type. + * @returns {Promise<{type: "Console" | "File", value: string} | {type: "Exit"}>} The input method selected by the user + */ +async function parseArguments() { + const args = process.argv.slice(2); // first 2 args are node and script name (irrelevant) + + /** @type {string | undefined} */ + const arg = args[0].split("=")[0]; // Yoink everything up to the first "=" to get the raw command + switch (arg) { + case "-f": + case "--file": + return { type: "File", value: getArgValue() }; + case "-t": + case "--text": + case "-c": + case "--console": + return { type: "Console", value: getArgValue() }; + case "-h": + case "--help": + showHelpText(); + process.exitCode = 0; + return { type: "Exit" }; + case "--interactive": + case "-i": + case undefined: + return await runInteractive(); + default: + // If no arguments are found, check if it's a file path + if (fs.existsSync(arg)) { + console.log(chalk.green(`Using file path from stdin: ${chalk.blue(arg)}`)); + return { type: "File", value: arg }; + } + badArgs(); + return { type: "Exit" }; + } +} + +/** + * Get the value of the argument provided. + * @returns {string} The CSV or file path from the arguments + * @throws {Error} If arguments are malformed + */ +function getArgValue() { + // If the user provided a value as argument 2, take that as the argument. + // Otherwise, check the 1st argument to see if it contains an `=` and extract everything afterwards. + /** @type {string | undefined} */ + let filePath = process.argv[3]; + const equalsIndex = process.argv[2].indexOf("="); + if (equalsIndex > -1) { + // If arg 3 was aleady existing and someone used `=` notation to assign a property, throw an error. + filePath = filePath ? undefined : process.argv[2].slice(equalsIndex + 1); + } + + if (!filePath?.trim()) { + badArgs(); + return ""; + } + // NB: It doesn't really matter that this can be `undefined` - we'll always break out by lieu of setting the exit code + return filePath; +} + +/** + * Write out the parsed CSV to a file. + * @param {string} moves - The parsed CSV + * @returns {Promise} + */ +export async function writeToFile(moves) { + try { + // Read the template file, replacing the placeholder with the move table. + const content = fs.readFileSync(templatePath, "utf8").replace(`"{{table}}"`, moves); + + if (fs.existsSync(eggMoveTargetPath)) { + console.warn(chalk.hex("#ffa500")("\nEgg moves file already exists, overwriting...\n")); + } + + // Write the template content to the file + fs.writeFileSync(eggMoveTargetPath, content, "utf8"); + + console.log(chalk.green.bold(`\nāœ” Egg Moves written to ${eggMoveTargetPath}`)); + console.groupEnd(); + } catch (err) { + console.error(chalk.red("āœ— Error while writing egg moves!", err.message)); + process.exitCode = 1; + } +} + +/** + * Do logging for incorrect or malformed CLI arguments. + * @returns {void} + */ +function badArgs() { + chalk.red.bold(`āœ— Error: Malformed arguments!\nArgs: ${chalk.hex("#7310fdff")(process.argv.slice(2).join(" "))}`); + showHelpText(); + process.exitCode = 1; +} + +start(); diff --git a/scripts/parse-egg-moves/parse.js b/scripts/parse-egg-moves/parse.js new file mode 100644 index 00000000000..209a350df42 --- /dev/null +++ b/scripts/parse-egg-moves/parse.js @@ -0,0 +1,61 @@ +import chalk from "chalk"; + +/** + * Given a CSV string, parse it and return a structured table ready to be inputted into code. + * @param {string} csv - The formatted CSV string. + * @returns {string} The fully formatted table. + */ +export function parseEggMoves(csv) { + console.log(chalk.grey("āš™ļø Parsing egg moves...")); + let output = "{\n"; + + const lines = csv.split(/\n/g); + + for (const line of lines) { + /** + * The individual CSV column for this species. + */ + const cols = + /** @type {[speciesName: string, move1: string, move2: string, move3: string, move4: string]} */ + (line.split(",").slice(0, 5)); + const speciesName = toUpperSnakeCase(cols[0]); + + /** @type {string[]} */ + const eggMoves = []; + + for (let m = 1; m < 5; m++) { + const moveName = cols[m].trim(); + if (moveName === "N/A") { + console.warn(`Species ${speciesName} missing ${m}th egg move!`); + eggMoves.push("MoveId.NONE"); + continue; + } + + // Remove (N) and (P) from the ends of move names before UPPER_SNAKE_CASE-ing them + const moveNameTitle = toUpperSnakeCase(moveName.replace(/ \([A-Z]\)$/, "")); + eggMoves.push("MoveId." + moveNameTitle); + } + + if (eggMoves.every(move => move === "MoveId.NONE")) { + console.warn(`Species ${speciesName} could not be parsed, excluding from output...`); + output += ` // [SpeciesId.${speciesName}]: [ MoveId.NONE, MoveId.NONE, MoveId.NONE, MoveId.NONE ],\n`; + } else { + output += ` [SpeciesId.${speciesName}]: [ ${eggMoves.join(", ")} ],\n`; + } + } + + // NB: We omit the semicolon as it is contained in the template string itself + return output + "} satisfies Partial>"; +} + +/** + * Helper method to convert a string into `UPPER_SNAKE_CASE`. + * @param {string} str - The string being converted + * @returns {string} The result of converting `str` into upper snake case. + */ +function toUpperSnakeCase(str) { + return str + .split(/[_ -]+/g) + .map(word => word.toUpperCase()) + .join("_"); +} diff --git a/src/data/balance/egg-moves.ts b/src/data/balance/egg-moves.ts index e936afcdc08..c5d2397ca1b 100644 --- a/src/data/balance/egg-moves.ts +++ b/src/data/balance/egg-moves.ts @@ -1,9 +1,12 @@ -import { allMoves } from "#data/data-lists"; +//! DO NOT EDIT THIS FILE - CREATED BY THE `eggMoves:parse` script automatically import { MoveId } from "#enums/move-id"; import { SpeciesId } from "#enums/species-id"; -import { getEnumKeys, getEnumValues } from "#utils/enums"; -import { toTitleCase } from "#utils/strings"; +/** + * An object mapping all base form {@linkcode SpeciesId}s to an array of {@linkcode MoveId}s corresponding + * to their current egg moves. + * Generated by the `eggMoves:parse` script using a CSV sourced from the current Balance Team spreadsheet. + */ export const speciesEggMoves = { [SpeciesId.BULBASAUR]: [ MoveId.SAPPY_SEED, MoveId.MALIGNANT_CHAIN, MoveId.EARTH_POWER, MoveId.MATCHA_GOTCHA ], [SpeciesId.CHARMANDER]: [ MoveId.DRAGON_DANCE, MoveId.BITTER_BLADE, MoveId.EARTH_POWER, MoveId.OBLIVION_WING ], @@ -15,7 +18,7 @@ export const speciesEggMoves = { [SpeciesId.SPEAROW]: [ MoveId.FLOATY_FALL, MoveId.EXTREME_SPEED, MoveId.KNOCK_OFF, MoveId.TRIPLE_ARROWS ], [SpeciesId.EKANS]: [ MoveId.NOXIOUS_TORQUE, MoveId.DRAGON_DANCE, MoveId.SLACK_OFF, MoveId.SHED_TAIL ], [SpeciesId.SANDSHREW]: [ MoveId.HIGH_HORSEPOWER, MoveId.DIRE_CLAW, MoveId.SHORE_UP, MoveId.MIGHTY_CLEAVE ], - [SpeciesId.NIDORAN_F]: [ MoveId.BANEFUL_BUNKER, MoveId.MOONLIGHT, MoveId.BARB_BARRAGE, MoveId.THOUSAND_WAVES ], + [SpeciesId.NIDORAN_F]: [ MoveId.CALM_MIND, MoveId.MOONLIGHT, MoveId.MALIGNANT_CHAIN, MoveId.SANDSEAR_STORM ], [SpeciesId.NIDORAN_M]: [ MoveId.DRAGON_DANCE, MoveId.MOUNTAIN_GALE, MoveId.NOXIOUS_TORQUE, MoveId.PRECIPICE_BLADES ], [SpeciesId.VULPIX]: [ MoveId.MOONBLAST, MoveId.INFERNAL_PARADE, MoveId.MORNING_SUN, MoveId.TAIL_GLOW ], [SpeciesId.ZUBAT]: [ MoveId.FLOATY_FALL, MoveId.DIRE_CLAW, MoveId.SWORDS_DANCE, MoveId.COLLISION_COURSE ], @@ -293,7 +296,7 @@ export const speciesEggMoves = { [SpeciesId.ARCHEN]: [ MoveId.ROOST, MoveId.EARTHQUAKE, MoveId.FLOATY_FALL, MoveId.MIGHTY_CLEAVE ], [SpeciesId.TRUBBISH]: [ MoveId.COIL, MoveId.RECOVER, MoveId.DIRE_CLAW, MoveId.GIGATON_HAMMER ], [SpeciesId.ZORUA]: [ MoveId.MALIGNANT_CHAIN, MoveId.MOONBLAST, MoveId.SECRET_SWORD, MoveId.FIERY_WRATH ], - [SpeciesId.MINCCINO]: [ MoveId.ICICLE_SPEAR, MoveId.TIDY_UP, MoveId.LOW_KICK, MoveId.POPULATION_BOMB ], + [SpeciesId.MINCCINO]: [ MoveId.ICICLE_SPEAR, MoveId.TIDY_UP, MoveId.KNOCK_OFF, MoveId.POPULATION_BOMB ], [SpeciesId.GOTHITA]: [ MoveId.RECOVER, MoveId.MOONBLAST, MoveId.AURA_SPHERE, MoveId.LUMINA_CRASH ], [SpeciesId.SOLOSIS]: [ MoveId.MIST_BALL, MoveId.SPEED_SWAP, MoveId.FLAMETHROWER, MoveId.LIGHT_OF_RUIN ], [SpeciesId.DUCKLETT]: [ MoveId.SPLISHY_SPLASH, MoveId.SANDSEAR_STORM, MoveId.WILDBOLT_STORM, MoveId.QUIVER_DANCE ], @@ -310,7 +313,7 @@ export const speciesEggMoves = { [SpeciesId.TYNAMO]: [ MoveId.SCALD, MoveId.STRENGTH_SAP, MoveId.FIRE_LASH, MoveId.AURA_WHEEL ], [SpeciesId.ELGYEM]: [ MoveId.THUNDERCLAP, MoveId.BADDY_BAD, MoveId.AURA_SPHERE, MoveId.PHOTON_GEYSER ], [SpeciesId.LITWICK]: [ MoveId.GIGA_DRAIN, MoveId.EARTH_POWER, MoveId.MOONBLAST, MoveId.TORCH_SONG ], - [SpeciesId.AXEW]: [ MoveId.STONE_AXE, MoveId.DIRE_CLAW, MoveId.RAGING_FURY, MoveId.BITTER_BLADE ], + [SpeciesId.AXEW]: [ MoveId.STONE_AXE, MoveId.DIRE_CLAW, MoveId.BITTER_BLADE, MoveId.GLAIVE_RUSH ], [SpeciesId.CUBCHOO]: [ MoveId.MOUNTAIN_GALE, MoveId.AQUA_STEP, MoveId.ICE_SHARD, MoveId.COLLISION_COURSE ], [SpeciesId.CRYOGONAL]: [ MoveId.FREEZING_GLARE, MoveId.AURORA_VEIL, MoveId.NASTY_PLOT, MoveId.ORIGIN_PULSE ], [SpeciesId.SHELMET]: [ MoveId.POWER_GEM, MoveId.NASTY_PLOT, MoveId.EARTH_POWER, MoveId.STEAM_ERUPTION ], @@ -448,7 +451,7 @@ export const speciesEggMoves = { [SpeciesId.ROOKIDEE]: [ MoveId.ROOST, MoveId.BODY_PRESS, MoveId.KINGS_SHIELD, MoveId.BEHEMOTH_BASH ], [SpeciesId.BLIPBUG]: [ MoveId.HEAL_ORDER, MoveId.LUSTER_PURGE, MoveId.SLEEP_POWDER, MoveId.TAIL_GLOW ], [SpeciesId.NICKIT]: [ MoveId.BADDY_BAD, MoveId.MYSTICAL_FIRE, MoveId.SPARKLY_SWIRL, MoveId.MAKE_IT_RAIN ], - [SpeciesId.GOSSIFLEUR]: [ MoveId.BATON_PASS, MoveId.TAILWIND, MoveId.SAPPY_SEED, MoveId.SPORE ], + [SpeciesId.GOSSIFLEUR]: [ MoveId.PARTING_SHOT, MoveId.STRENGTH_SAP, MoveId.SAPPY_SEED, MoveId.SEED_FLARE ], [SpeciesId.WOOLOO]: [ MoveId.NUZZLE, MoveId.MILK_DRINK, MoveId.BODY_PRESS, MoveId.MULTI_ATTACK ], [SpeciesId.CHEWTLE]: [ MoveId.ICE_FANG, MoveId.PSYCHIC_FANGS, MoveId.SHELL_SMASH, MoveId.MIGHTY_CLEAVE ], [SpeciesId.YAMPER]: [ MoveId.ICE_FANG, MoveId.SWORDS_DANCE, MoveId.THUNDER_FANG, MoveId.BOLT_STRIKE ], @@ -514,7 +517,7 @@ export const speciesEggMoves = { [SpeciesId.TAROUNTULA]: [ MoveId.STONE_AXE, MoveId.LEECH_LIFE, MoveId.FAKE_OUT, MoveId.SPORE ], [SpeciesId.NYMBLE]: [ MoveId.KNOCK_OFF, MoveId.FELL_STINGER, MoveId.ATTACK_ORDER, MoveId.WICKED_BLOW ], [SpeciesId.PAWMI]: [ MoveId.DRAIN_PUNCH, MoveId.METEOR_MASH, MoveId.JET_PUNCH, MoveId.PLASMA_FISTS ], - [SpeciesId.TANDEMAUS]: [ MoveId.BATON_PASS, MoveId.FAKE_OUT, MoveId.POWER_UP_PUNCH, MoveId.REVIVAL_BLESSING ], + [SpeciesId.TANDEMAUS]: [ MoveId.BATON_PASS, MoveId.COVET, MoveId.SIZZLY_SLIDE, MoveId.REVIVAL_BLESSING ], [SpeciesId.FIDOUGH]: [ MoveId.SOFT_BOILED, MoveId.HIGH_HORSEPOWER, MoveId.SIZZLY_SLIDE, MoveId.TIDY_UP ], [SpeciesId.SMOLIV]: [ MoveId.STRENGTH_SAP, MoveId.EARTH_POWER, MoveId.CALM_MIND, MoveId.BOOMBURST ], [SpeciesId.SQUAWKABILLY]: [ MoveId.PARTING_SHOT, MoveId.EARTHQUAKE, MoveId.FLARE_BLITZ, MoveId.EXTREME_SPEED ], @@ -582,55 +585,4 @@ export const speciesEggMoves = { [SpeciesId.PALDEA_TAUROS]: [ MoveId.NO_RETREAT, MoveId.BLAZING_TORQUE, MoveId.AQUA_STEP, MoveId.THUNDEROUS_KICK ], [SpeciesId.PALDEA_WOOPER]: [ MoveId.STONE_AXE, MoveId.RECOVER, MoveId.BANEFUL_BUNKER, MoveId.BARB_BARRAGE ], [SpeciesId.BLOODMOON_URSALUNA]: [ MoveId.NASTY_PLOT, MoveId.ROCK_POLISH, MoveId.SANDSEAR_STORM, MoveId.BOOMBURST ] -}; - -/** - * Parse a CSV-separated list of Egg Moves (such as one sourced from a Google Sheets) - * into code able to form the `speciesEggMoves` const object as above. - * @param content - The CSV-formatted string to convert into code. - */ -// TODO: Move this into the scripts folder and stop running it on initialization -function parseEggMoves(content: string): void { - let output = ""; - - const speciesNames = getEnumKeys(SpeciesId); - const speciesValues = getEnumValues(SpeciesId); - const moveNames = allMoves.map(m => m.name.replace(/ \([A-Z]\)$/, "").toLowerCase()); - const lines = content.split(/\n/g); - - for (const line of lines) { - const cols = line.split(",").slice(0, 5); - const enumSpeciesName = cols[0].toUpperCase().replace(/[ -]/g, "_") as keyof typeof SpeciesId; - // TODO: This should use reverse mapping instead of `indexOf` - const species = speciesValues[speciesNames.indexOf(enumSpeciesName)]; - - const eggMoves: MoveId[] = []; - - for (let m = 0; m < 4; m++) { - const moveName = cols[m + 1].trim(); - const moveIndex = moveName !== "N/A" ? moveNames.indexOf(moveName.toLowerCase()) : -1; - if (moveIndex === -1) { - console.warn(moveName, "could not be parsed"); - } - - eggMoves.push(moveIndex > -1 ? moveIndex as MoveId : MoveId.NONE); - } - - if (eggMoves.every(m => m === MoveId.NONE)) { - console.warn(`Species ${toTitleCase(SpeciesId[species])} could not be parsed, excluding from output...`) - } else { - output += `[SpeciesId.${SpeciesId[species]}]: [ ${eggMoves.map(m => `MoveId.${MoveId[m]}`).join(", ")} ],\n`; - } - } - - console.log(output); -} - -export function initEggMoves() { - const eggMovesStr = ""; - if (eggMovesStr) { - setTimeout(() => { - parseEggMoves(eggMovesStr); - }, 1000); - } -} +} satisfies Partial>; diff --git a/src/init/init.ts b/src/init/init.ts index ba9738e2be8..8452278b3f1 100644 --- a/src/init/init.ts +++ b/src/init/init.ts @@ -1,6 +1,5 @@ import { initAbilities } from "#abilities/ability"; import { initBiomes } from "#balance/biomes"; -import { initEggMoves } from "#balance/egg-moves"; import { initPokemonPrevolutions, initPokemonStarters } from "#balance/pokemon-evolutions"; import { initSpecies } from "#balance/pokemon-species"; import { initChallenges } from "#data/challenge"; @@ -24,7 +23,6 @@ export function initializeGame() { initPokemonPrevolutions(); initPokemonStarters(); initBiomes(); - initEggMoves(); initPokemonForms(); initTrainerTypeDialogue(); initSpecies(); diff --git a/tsconfig.json b/tsconfig.json index dcbf7456df8..0338c306d49 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,46 +18,45 @@ "esModuleInterop": true, "strictNullChecks": true, "sourceMap": false, - "checkJs": true, "strict": false, // TODO: Enable this eventually "rootDir": ".", - "baseUrl": "./src", - "paths": { - "#abilities/*": ["./data/abilities/*.ts"], - "#api/*": ["./plugins/api/*.ts"], - "#balance/*": ["./data/balance/*.ts"], - "#enums/*": ["./enums/*.ts"], - "#events/*": ["./events/*.ts"], - "#field/*": ["./field/*.ts"], - "#inputs/*": ["./configs/inputs/*.ts"], - "#modifiers/*": ["./modifier/*.ts"], - "#moves/*": ["./data/moves/*.ts"], - "#mystery-encounters/*": [ - "./data/mystery-encounters/utils/*.ts", - "./data/mystery-encounters/encounters/*.ts", - "./data/mystery-encounters/requirements/*.ts", - "./data/mystery-encounters/*.ts" - ], - "#package.json": ["../package.json"], - "#phases/*": ["./phases/*.ts"], - "#plugins/*": ["./plugins/vite/*.ts", "./plugins/*.ts"], - "#sprites/*": ["./sprites/*.ts"], - "#system/*": [ - "./system/settings/*.ts", - "./system/version-migration/versions/*.ts", - "./system/version-migration/*.ts", - "./system/*.ts" - ], - "#trainers/*": ["./data/trainers/*.ts"], - "#types/*": ["./@types/helpers/*.ts", "./@types/*.ts", "./typings/phaser/*.ts"], - "#ui/*": ["./ui/battle-info/*.ts", "./ui/settings/*.ts", "./ui/*.ts"], - "#utils/*": ["./utils/*.ts"], - "#data/*": ["./data/pokemon-forms/*.ts", "./data/pokemon/*.ts", "./data/*.ts"], - "#test/*": ["../test/*.ts"], - "#app/*": ["*.ts"] - }, "outDir": "./build", - "noEmit": true + "noEmit": true, + "paths": { + "#abilities/*": ["./src/data/abilities/*.ts"], + "#api/*": ["./src/plugins/api/*.ts"], + "#balance/*": ["./src/data/balance/*.ts"], + "#enums/*": ["./src/enums/*.ts"], + "#events/*": ["./src/events/*.ts"], + "#field/*": ["./src/field/*.ts"], + "#inputs/*": ["./src/configs/inputs/*.ts"], + "#modifiers/*": ["./src/modifier/*.ts"], + "#moves/*": ["./src/data/moves/*.ts"], + "#mystery-encounters/*": [ + "./src/data/mystery-encounters/utils/*.ts", + "./src/data/mystery-encounters/encounters/*.ts", + "./src/data/mystery-encounters/requirements/*.ts", + "./src/data/mystery-encounters/*.ts" + ], + "#package.json": ["./package.json"], + "#phases/*": ["./src/phases/*.ts"], + "#plugins/*": ["./src/plugins/vite/*.ts", "./src/plugins/*.ts"], + "#sprites/*": ["./src/sprites/*.ts"], + "#system/*": [ + "./src/system/settings/*.ts", + "./src/system/version-migration/versions/*.ts", + "./src/system/version-migration/*.ts", + "./src/system/*.ts" + ], + "#trainers/*": ["./src/data/trainers/*.ts"], + "#types/*": ["./src/@types/helpers/*.ts", "./src/@types/*.ts", "./src/typings/phaser/*.ts"], + "#ui/*": ["./src/ui/battle-info/*.ts", "./src/ui/settings/*.ts", "./src/ui/*.ts"], + "#utils/*": ["./src/utils/*.ts"], + "#data/*": ["./src/data/pokemon-forms/*.ts", "./src/data/pokemon/*.ts", "./src/data/*.ts"], + "#test/*": ["./test/*.ts"], + "#app/*": ["./src/*.ts"] + } }, + "include": ["**/*.{ts,tsx}"], "exclude": ["node_modules", "dist", "vite.config.ts", "vitest.config.ts", "vitest.workspace.ts"] }