Skip to content

Instantly share code, notes, and snippets.

@HoeenCoder
Created June 18, 2020 04:34
Show Gist options
  • Save HoeenCoder/330a9533ad392c6ba4107172be05e65e to your computer and use it in GitHub Desktop.
Save HoeenCoder/330a9533ad392c6ba4107172be05e65e to your computer and use it in GitHub Desktop.
Script for converting raw datamine & existing learnsets.ts file to new learnsets.ts - Mess atm, want to improve later as its too brittle for me.
import {FS} from '../../lib/fs';
// import {BattleLearnsets} from '../../data/learnsets';
const BattleLearnsets = require('../../.data-dist/learnsets').BattleLearnsets;
// TO HANDLE
/**
* "1" versions of pokemon
* different pokemon stages
* ???
*/
interface DLCData {
species: string;
num: number;
baseStats: StatsTable;
genderRatio?: {M: number, F: number};
gender?: 'M' | 'F' | 'N';
abilities: SpeciesAbility;
types: string[];
eggGroups: string[];
weightkg: number;
color: string;
heightm: number;
movepool: LearnsetData;
}
const rawData = FS('dlc_dump.txt').readIfExistsSync();
if (!rawData) throw new Error(`Raw data for parsing not found.`);
const lines = rawData.split('\n').map(l => l.trim());
const data: {[species: string]: DLCData} = {};
function parseRawData() {
let currentSpecies = '';
let currentData: Partial<DLCData> = {};
let inHeader = false;
let lsetData: {[moveid: string]: MoveSource[]} = {};
let lsetArea = '';
const skipable = ['Galar Dex', 'EV Yield', 'Catch Rate', 'Item', 'EXP Group', 'Hatch Cycles'];
for (const line of lines) {
// console.log(line);
if (line === '======') {
inHeader = !inHeader;
continue;
}
if (inHeader) {
// 001 - Bulbasaur (Stage: 1)
currentData.num = parseInt(line.split('-')[0]);
currentSpecies = line.split('-')[1].trim();
currentSpecies = currentSpecies.substring(0, currentSpecies.indexOf('(')).trim();
const alola = ['Raichu', 'Sandshrew', 'Sandslash', 'Vulpix', 'Ninetales', 'Diglett', 'Dugtrio', 'Persian',
'Exeggutor', 'Marowak'];
const galar = ['Ponyta', 'Rapidash', 'Slowpoke', 'Slowbro', 'Farfetch’d', 'Weezing', 'Mr. Mime', 'Corsola',
'Zigzagoon', 'Linoone', 'Darumaka', 'Yamask', 'Stunfisk'];
// TODO special case for meowth & darmanitans because reasons
if (!Dex.getSpecies(currentSpecies).exists) {
const baseSpecies = currentSpecies.substring(0, currentSpecies.length - 2).trim();
if (Dex.getSpecies(baseSpecies).exists) {
if (alola.includes(baseSpecies)) currentSpecies = baseSpecies + '-Alola';
if (galar.includes(baseSpecies)) currentSpecies = baseSpecies + '-Galar';
if (baseSpecies === 'Meowth') {
if (currentSpecies.endsWith('1')) {
currentSpecies = baseSpecies + '-Alola';
} else {
currentSpecies = baseSpecies + '-Galar';
}
} else if (baseSpecies === 'Darmanitan') {
if (currentSpecies.endsWith('1')) {
// Zen - Ignore
} else if (currentSpecies.endsWith('2')) {
currentSpecies = baseSpecies + '-Galar';
} else {
// Zen - Ignore
}
} else if (baseSpecies === 'Urshifu') {
currentSpecies = baseSpecies + '-Rapid-Strike';
} else if (baseSpecies === 'Zarude') {
currentSpecies = baseSpecies + '-Dada';
} else if (baseSpecies === 'Toxtricity') {
currentSpecies = baseSpecies + '-Low-Key';
}
}
}
currentData.species = currentSpecies;
// console.log(`Species: ${currentSpecies}`);
continue;
}
let skipLine = false;
for (const skip of skipable) {
if (line.startsWith(skip)) {
skipLine = true;
break;
}
}
if (skipLine) continue;
if (line.startsWith('Base Stats:')) {
const rawStats = line.match(/(\d{1,3}\.?){6}/);
if (!rawStats) throw new Error(`Unable to extract stats from line: ${line}, result: ${rawStats}`);
const stats: {[stat: string]: number} = {};
const indexToStat: {[string: string]: string} = {
'0': 'hp',
'1': 'atk',
'2': 'def',
'3': 'spa',
'4': 'spd',
'5': 'spe',
};
rawStats[0].split('.').map((stat, index) => {
const statName = indexToStat[('' + index)];
if (!statName) throw new Error(`Could not find stat for index ${index}`);
stats[statName] = parseInt(stat);
return '';
});
currentData.baseStats = (stats as StatsTable);
continue;
} else if (line.startsWith('Gender Ratio:')) {
const ratio = line.split(':')[1].trim();
switch (ratio) {
case '31':
// 7/8 M, 1/8 F
currentData.genderRatio = {M: 0.875, F: 0.125};
break;
case '255':
// N
currentData.gender = 'N';
break;
case '127':
// 50/50
currentData.genderRatio = {M: 0.5, F: 0.5};
break;
case '254':
// F
currentData.gender = 'F';
break;
case '0':
// M
currentData.gender = 'M';
break;
case '191':
// 3/4 F, 1/4 M
currentData.genderRatio = {M: 0.25, F: 0.75};
break;
case '63':
// 1/4 F, 3/4 M
currentData.genderRatio = {M: 0.75, F: 0.25};
break;
default:
throw new Error(`Unexpected gender ratio for ${currentSpecies}: ${ratio}`);
}
continue;
} else if (line.startsWith('Abilities:')) {
// Abilities: Thick Fat (1) | Huge Power (2) | Sap Sipper (H)
const parts = line.slice(9).split('|').map(p => p.trim());
const abilities: {[key: string]: string} = {};
for (const a of parts) {
const key = a.match(/(\([12H]\))/);
if (!key) throw new Error(`Could not find key for ability: ${a} ${key} ${currentSpecies}`);
const trueKey = key[0] === '2' ? '1' : (key[0] === '1' ? '0' : 'H');
abilities[trueKey] = a.substring(0, a.indexOf('(')).trim();
}
currentData.abilities = (abilities as unknown as SpeciesAbility);
continue;
} else if (line.startsWith('Type:')) {
const types = line.slice(5).split('/').map(t => t.trim());
currentData.types = types;
continue;
} else if (line.startsWith('Egg Group:')) {
const groups = line.slice(10).split('/').map(t => t.trim());
currentData.eggGroups = groups;
continue;
} else if (line.startsWith('Height:')) {
// Height: 00.60 m, Weight: 008.5 kg, Color: Red
const parts = line.split(',').map(p => p.trim());
for (const p of parts) {
const subParts = p.split(':');
switch (subParts[0].trim()) {
case 'Height':
currentData.heightm = parseFloat(subParts[1]);
break;
case 'Weight':
currentData.weightkg = parseFloat(subParts[1]);
break;
case 'Color':
currentData.color = subParts[1].trim();
break;
}
}
continue;
} else if (line.startsWith('Level Up Moves:')) {
lsetArea = 'L';
continue;
} else if (line.startsWith('Egg Moves:')) {
lsetArea = 'E';
continue;
} else if (line.startsWith('TMs:') || line.startsWith('TRs:')) {
lsetArea = 'M';
continue;
} else if (line.startsWith('Armor Tutors:')) {
lsetArea = 'T';
continue;
} else if (!line.startsWith('- ')) {
// console.log(line);
lsetArea = '';
}
// Learnset data or garbage data
// console.log(lsetArea);
let matches: RegExpMatchArray | null;
let move: string;
switch (lsetArea) {
case 'L':
matches = line.match(/\[(\d{2,3})\] (.*)/);
// [0] - full match, [1] is the level, [2] is the move
if (!matches) throw new Error(`Unable to match for learnset data: ${lsetArea} ${line} ${matches}`);
const lvl = parseInt(matches[1]);
move = toID(matches[2]);
if (!lsetData[move]) lsetData[move] = [];
lsetData[move].push(`8L${lvl}`);
break;
case 'E':
move = toID(line.slice(1));
if (!lsetData[move]) lsetData[move] = [];
lsetData[move].push(`8E`);
break;
case 'M':
matches = line.match(/\[T[MR]\d{2}\] (.*)/);
if (!matches) throw new Error(`Unable to match for learnset data: ${lsetArea} ${line} ${matches}`);
move = toID(matches[1]);
if (!lsetData[move]) lsetData[move] = [];
lsetData[move].push(`8M`);
break;
case 'T':
move = toID(line.slice(1));
if (!lsetData[move]) lsetData[move] = [];
lsetData[move].push(`8T`);
break;
case '':
if (!currentSpecies) continue;
currentData.movepool = {learnset: lsetData};
if (data[currentSpecies]) throw new Error(`Duplicate species! ${currentSpecies}.`);
const spec = Dex.getSpecies(currentSpecies);
// console.log(spec.exists);
if (!spec.exists) {
if (!isNaN(parseInt(currentSpecies[currentSpecies.length - 1]))) {
console.log(`SKIP: ${currentSpecies}`);
currentSpecies = '';
currentData = {};
lsetData = {};
continue;
} else {
console.log(`NEW SPECIES: ${currentSpecies}`);
}
}
data[toID(currentSpecies)] = (currentData as DLCData);
currentSpecies = '';
currentData = {};
lsetData = {};
continue;
default:
throw new Error(`Unexpected lsetArea type: ${lsetArea} ${currentSpecies}`);
}
}
}
parseRawData();
const output: {[speciesid: string]: LearnsetData} = Dex.deepClone(BattleLearnsets);
// Now we read learnset data and update it
function updateLearnsets() {
for (const species in data) {
const newData = data[species];
if (!(species in output)) {
console.log(`NEW LEARNSET: ${species}`);
output[species] = newData.movepool;
} else {
if (!output[species].learnset) output[species].learnset = {};
for (const move in newData.movepool.learnset) {
// @ts-ignore
const a = newData.movepool.learnset[move].concat(output[species].learnset[move]);
// @ts-ignore defined literally 5 lines above this
output[species].learnset[move] = Array.from(new Set(a)).filter(m => m);
}
}
}
console.log('updated');
}
updateLearnsets();
const WRITE_TO = 'learnsets.ts';
function writeLearnsets() {
const outputStream = FS(WRITE_TO).createAppendStream();
// Write headers
outputStream.writeLine('/* eslint-disable max-len */');
outputStream.writeLine('');
outputStream.writeLine('export const BattleLearnsets: {[speciesid: string]: LearnsetData} = {');
// loop and log each species learnsets
for (const species in output) {
const block = output[species];
outputStream.writeLine(`\t${species}: {`);
const lsets = block.learnset;
if (lsets) {
outputStream.writeLine(`\t\tlearnset: {`);
const movepool = Object.keys(lsets).sort();
for (const move of movepool) {
lsets[move].sort((a, b) => {
const LEARN_ORDER = 'MTLREDSVC';
const aGen = parseInt(a.charAt(0));
const aLearn = LEARN_ORDER.indexOf(a.charAt(1));
const aLvl = parseInt(a.slice(2) || '1');
const bGen = parseInt(b.charAt(0));
const bLearn = LEARN_ORDER.indexOf(b.charAt(1));
const bLvl = parseInt(b.slice(2) || '1');
if (aGen === bGen) {
if (aLearn === bLearn) {
return aLvl - bLvl;
}
return aLearn - bLearn;
} else {
return bGen - aGen;
}
});
outputStream.writeLine(`\t\t\t${move}: ["${lsets[move].join('", "')}"],`);
}
outputStream.writeLine(`\t\t},`);
}
if (block.eventData) {
outputStream.writeLine(`\t\teventData: [`);
for (const event of block.eventData) {
outputStream.write('\t\t\t{');
const keys = Object.keys(event);
for (const key in event) {
// @ts-ignore
const part = event[key];
outputStream.write(`${key}: `);
if (Array.isArray(part)) {
outputStream.write(`["${part.join(`", "`)}"]`);
} else if (typeof part === 'object') {
outputStream.write('{');
const ks = Object.keys(part);
for (const k in part) {
outputStream.write(`${k}: ${part[k]}`);
if ((ks.indexOf(k) + 1) < ks.length) {
outputStream.write(', ');
}
}
outputStream.write('}');
} else if (typeof part === 'string') {
outputStream.write(`"${part}"`);
} else {
outputStream.write('' + part);
}
if ((keys.indexOf(key) + 1) < keys.length) {
outputStream.write(', ');
}
}
outputStream.write(`},\n`);
}
outputStream.writeLine(`\t\t],`);
}
if (block.encounters) {
outputStream.writeLine(`\t\tencounters: [`);
for (const encounter of block.encounters) {
outputStream.write('\t\t\t{');
const keys = Object.keys(encounter);
for (const key in encounter) {
// @ts-ignore
const part = encounter[key];
outputStream.write(`${key}: `);
if (Array.isArray(part)) {
outputStream.write(`["${part.join(`", "`)}"]`);
} else if (typeof part === 'object') {
outputStream.write('{');
const ks = Object.keys(part);
for (const k in part) {
outputStream.write(`${k}: ${part[k]}`);
if ((ks.indexOf(k) + 1) < ks.length) {
outputStream.write(', ');
}
}
outputStream.write('}');
} else if (typeof part === 'string') {
outputStream.write(`"${part}"`);
} else {
outputStream.write('' + part);
}
if ((keys.indexOf(key) + 1) < keys.length) {
outputStream.write(', ');
}
}
outputStream.write(`},\n`);
}
outputStream.writeLine(`\t\t],`);
}
if (block.eventOnly) {
outputStream.writeLine('\t\teventOnly: true,');
}
outputStream.writeLine(`\t},`);
}
outputStream.writeLine(`};`);
// Newline at end of file to appease github and the linter
outputStream.writeLine(``);
}
writeLearnsets();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment