mirror of
https://github.com/smogon/pokemon-showdown.git
synced 2026-03-30 13:46:00 -05:00
Support importing teams
We now have a `Teams.import` function. This supports importing teams in any format, allowing it to be the backbone of a new series of commandline functions, which support teams in any format.
This commit is contained in:
parent
5d3b758564
commit
ddb6010bb9
|
|
@ -40,7 +40,7 @@ Note: Commands that ask for a team want the team in [packed team format][packed-
|
|||
|
||||
`./pokemon-showdown validate-team [FORMAT-ID]`
|
||||
|
||||
: Reads a team from stdin, and validates it
|
||||
: Reads a team in any format from stdin, and validates it
|
||||
: - If valid: exits with code 0
|
||||
: - If invalid: writes errors to stderr, exits with code 1
|
||||
|
||||
|
|
@ -51,14 +51,90 @@ Note: Commands that ask for a team want the team in [packed team format][packed-
|
|||
: Using Pokémon Showdown as a command-line simulator is documented at:
|
||||
: https://github.com/smogon/pokemon-showdown/blob/master/sim/README.md
|
||||
|
||||
`./pokemon-showdown unpack-team`
|
||||
`./pokemon-showdown json-team`
|
||||
|
||||
: Reads a team from stdin, writes the unpacked JSON to stdout
|
||||
: Reads a team in any format from stdin, writes the unpacked JSON to stdout
|
||||
|
||||
`./pokemon-showdown pack-team`
|
||||
|
||||
: Reads a JSON team from stdin, writes the packed team to stdout
|
||||
: Reads a team in any format from stdin, writes the packed team to stdout
|
||||
|
||||
`./pokemon-showdown export-team`
|
||||
|
||||
: Reads a team in any format from stdin, writes the exported (human-readable) team to stdout
|
||||
|
||||
`./pokemon-showdown help`
|
||||
|
||||
: Displays this reference
|
||||
|
||||
|
||||
Piping
|
||||
------
|
||||
|
||||
These commands are very unixy (using stdin and stdout), so you can of course pipe them together:
|
||||
|
||||
```
|
||||
$ ./pokemon-showdown generate-team gen8randombattle | ./pokemon-showdown export-team
|
||||
Kartana @ Choice Band
|
||||
Ability: Beast Boost
|
||||
Level: 74
|
||||
EVs: 85 HP / 85 Atk / 85 Def / 85 SpA / 85 SpD / 85 Spe
|
||||
- Smart Strike
|
||||
- Sacred Sword
|
||||
- Knock Off
|
||||
- Leaf Blade
|
||||
|
||||
Rotom (Rotom-Heat) @ Heavy-Duty Boots
|
||||
Ability: Levitate
|
||||
Level: 82
|
||||
EVs: 85 HP / 85 Def / 85 SpA / 85 SpD / 85 Spe
|
||||
IVs: 0 Atk
|
||||
- Defog
|
||||
- Will-O-Wisp
|
||||
- Thunderbolt
|
||||
- Overheat
|
||||
|
||||
Kingler @ Life Orb
|
||||
Ability: Sheer Force
|
||||
Level: 84
|
||||
EVs: 85 HP / 85 Atk / 85 Def / 85 SpA / 85 SpD / 85 Spe
|
||||
- Liquidation
|
||||
- X-Scissor
|
||||
- Superpower
|
||||
- Rock Slide
|
||||
|
||||
Abomasnow @ Light Clay
|
||||
Ability: Snow Warning
|
||||
Level: 82
|
||||
EVs: 85 HP / 85 Atk / 85 Def / 85 SpA / 85 SpD / 85 Spe
|
||||
- Ice Shard
|
||||
- Aurora Veil
|
||||
- Earthquake
|
||||
- Blizzard
|
||||
|
||||
Goodra @ Assault Vest
|
||||
Ability: Sap Sipper
|
||||
Level: 82
|
||||
EVs: 85 HP / 85 Atk / 85 Def / 85 SpA / 85 SpD / 85 Spe
|
||||
- Earthquake
|
||||
- Power Whip
|
||||
- Draco Meteor
|
||||
- Fire Blast
|
||||
|
||||
Raikou @ Choice Specs
|
||||
Ability: Pressure
|
||||
Level: 80
|
||||
EVs: 85 HP / 85 Def / 85 SpA / 85 SpD / 85 Spe
|
||||
IVs: 0 Atk
|
||||
- Scald
|
||||
- Aura Sphere
|
||||
- Thunderbolt
|
||||
- Volt Switch
|
||||
```
|
||||
|
||||
```
|
||||
$ ./pokemon-showdown generate-team gen8randombattle | ./pokemon-showdown validate-team gen8ou
|
||||
Your set for Coalossal is flagged as Gigantamax, but Gigantamaxing is disallowed
|
||||
(If this was a mistake, disable Gigantamaxing on the set.)
|
||||
Octillery's ability Moody is banned.
|
||||
```
|
||||
|
|
|
|||
|
|
@ -29,6 +29,13 @@ try {
|
|||
build();
|
||||
}
|
||||
|
||||
function readTeam(stream) {
|
||||
return stream.readLine().then(line => {
|
||||
if (line.startsWith('[') || line.includes('|')) return line;
|
||||
return stream.readAll().then(all => (line + '\n' + all));
|
||||
});
|
||||
}
|
||||
|
||||
if (!process.argv[2] || /^[0-9]+$/.test(process.argv[2])) {
|
||||
// Start the server.
|
||||
//
|
||||
|
|
@ -68,16 +75,17 @@ if (!process.argv[2] || /^[0-9]+$/.test(process.argv[2])) {
|
|||
console.log(' Simulates a battle, taking input to stdin and writing output to stdout');
|
||||
console.log(' Protocol is documented in ./.sim-dist/README.md');
|
||||
console.log('');
|
||||
console.log('pokemon-showdown unpack-team');
|
||||
console.log('pokemon-showdown json-team');
|
||||
console.log('');
|
||||
console.log(' Reads a team from stdin, writes the unpacked JSON to stdout');
|
||||
console.log(' Reads a team in any format from stdin, writes the unpacked JSON to stdout');
|
||||
console.log('');
|
||||
console.log('pokemon-showdown pack-team');
|
||||
console.log('');
|
||||
console.log(' Reads a JSON team from stdin, writes the packed team to stdout');
|
||||
console.log(' NOTE for all team-processing functions: We can only handle JSON teams');
|
||||
console.log(' and packed teams; the PS server is incapable of processing exported');
|
||||
console.log(' teams.');
|
||||
console.log(' Reads a team in any format from stdin, writes the packed team to stdout');
|
||||
console.log('');
|
||||
console.log('pokemon-showdown export-team');
|
||||
console.log('');
|
||||
console.log(' Reads a team in any format from stdin, writes the exported (human-readable) team to stdout');
|
||||
console.log('');
|
||||
console.log('pokemon-showdown help');
|
||||
console.log('');
|
||||
|
|
@ -108,9 +116,9 @@ if (!process.argv[2] || /^[0-9]+$/.test(process.argv[2])) {
|
|||
var Streams = require('./.lib-dist/streams');
|
||||
var stdin = Streams.stdin();
|
||||
|
||||
stdin.readLine().then(function (textTeam) {
|
||||
readTeam(stdin).then(function (textTeam) {
|
||||
try {
|
||||
var team = Teams.unpack(textTeam);
|
||||
var team = Teams.import(textTeam);
|
||||
var result = validator.validateTeam(team);
|
||||
if (result) {
|
||||
console.error(result.join('\n'));
|
||||
|
|
@ -175,14 +183,15 @@ if (!process.argv[2] || /^[0-9]+$/.test(process.argv[2])) {
|
|||
}
|
||||
break;
|
||||
case 'unpack-team':
|
||||
case 'json-team':
|
||||
{
|
||||
var Teams = require('./.sim-dist/teams').Teams;
|
||||
var Streams = require('./.lib-dist/streams');
|
||||
var stdin = Streams.stdin();
|
||||
|
||||
stdin.readLine().then(function (packedTeam) {
|
||||
readTeam(stdin).then(function (team) {
|
||||
try {
|
||||
var unpackedTeam = Teams.unpack(packedTeam);
|
||||
var unpackedTeam = Teams.unpack(Teams.import(team));
|
||||
console.log(JSON.stringify(unpackedTeam));
|
||||
process.exit(0);
|
||||
} catch (e) {
|
||||
|
|
@ -198,9 +207,9 @@ if (!process.argv[2] || /^[0-9]+$/.test(process.argv[2])) {
|
|||
var Streams = require('./.lib-dist/streams');
|
||||
var stdin = Streams.stdin();
|
||||
|
||||
stdin.readLine().then(function (unpackedTeam) {
|
||||
readTeam(stdin).then(function (team) {
|
||||
try {
|
||||
var packedTeam = Teams.pack(JSON.parse(unpackedTeam));
|
||||
var packedTeam = Teams.pack(Teams.import(team));
|
||||
console.log(packedTeam);
|
||||
process.exit(0);
|
||||
} catch (e) {
|
||||
|
|
@ -210,6 +219,24 @@ if (!process.argv[2] || /^[0-9]+$/.test(process.argv[2])) {
|
|||
});
|
||||
}
|
||||
break;
|
||||
case 'export-team':
|
||||
{
|
||||
var Teams = require('./.sim-dist/teams').Teams;
|
||||
var Streams = require('./.lib-dist/streams');
|
||||
var stdin = Streams.stdin();
|
||||
|
||||
readTeam(stdin).then(function (team) {
|
||||
try {
|
||||
var exportedTeam = Teams.export(Teams.import(team));
|
||||
console.log(exportedTeam);
|
||||
process.exit(0);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
break;
|
||||
default:
|
||||
console.error('Unrecognized command: ' + process.argv[2]);
|
||||
console.error('Use `pokemon-showdown help` for help');
|
||||
|
|
|
|||
|
|
@ -173,6 +173,10 @@ API:
|
|||
|
||||
- Converts a JSON team to a packed team
|
||||
|
||||
`Teams.import(exportedTeam: string): PokemonSet[]`
|
||||
|
||||
- Converts a team in any string format (JSON, exported, or packed) to a JSON team
|
||||
|
||||
`Teams.export(team: PokemonSet[]): string`
|
||||
|
||||
- Converts a JSON team to an export team
|
||||
|
|
@ -181,7 +185,7 @@ API:
|
|||
|
||||
- Converts a JSON set to export format
|
||||
|
||||
(Import is not available in this version; we'll add it to a future version.)
|
||||
To convert from export to packed (or vice versa), just round-trip through PokemonSet: `Teams.export(Teams.unpack(packedTeam))` will produce an exported team.
|
||||
|
||||
Example use:
|
||||
|
||||
|
|
|
|||
|
|
@ -307,6 +307,16 @@ export class DexTypes {
|
|||
}
|
||||
|
||||
const idsCache: readonly StatID[] = ['hp', 'atk', 'def', 'spa', 'spd', 'spe'];
|
||||
const reverseCache: {readonly [k: string]: StatID} = {
|
||||
__proto: null as any,
|
||||
"hitpoints": 'hp',
|
||||
"attack": 'atk',
|
||||
"defense": 'def',
|
||||
"specialattack": 'spa', "spatk": 'spa', "spattack": 'spa', "specialatk": 'spa',
|
||||
"special": 'spa', "spc": 'spa',
|
||||
"specialdefense": 'spd', "spdef": 'spd', "spdefense": 'spd', "specialdef": 'spd',
|
||||
"speed": 'spe',
|
||||
};
|
||||
export class DexStats {
|
||||
readonly shortNames: {readonly [k in StatID]: string};
|
||||
readonly mediumNames: {readonly [k in StatID]: string};
|
||||
|
|
@ -334,6 +344,13 @@ export class DexStats {
|
|||
} as any;
|
||||
}
|
||||
}
|
||||
getID(name: string) {
|
||||
if (name === 'Spd') return 'spe' as StatID;
|
||||
const id = toID(name);
|
||||
if (reverseCache[id]) return reverseCache[id];
|
||||
if (idsCache.includes(id as StatID)) return id as StatID;
|
||||
return null;
|
||||
}
|
||||
ids(): typeof idsCache {
|
||||
return idsCache;
|
||||
}
|
||||
|
|
|
|||
168
sim/teams.ts
168
sim/teams.ts
|
|
@ -7,7 +7,7 @@
|
|||
* @license MIT
|
||||
*/
|
||||
|
||||
import {Dex} from './dex';
|
||||
import {Dex, toID} from './dex';
|
||||
import type {PRNG, PRNGSeed} from './prng';
|
||||
|
||||
export interface PokemonSet {
|
||||
|
|
@ -421,6 +421,172 @@ export const Teams = new class Teams {
|
|||
return out;
|
||||
}
|
||||
|
||||
parseExportedTeamLine(line: string, isFirstLine: boolean, set: PokemonSet) {
|
||||
if (isFirstLine) {
|
||||
let item;
|
||||
[line, item] = line.split(' @ ');
|
||||
if (item) {
|
||||
set.item = item;
|
||||
if (toID(set.item) === 'noitem') set.item = '';
|
||||
}
|
||||
if (line.endsWith(' (M)')) {
|
||||
set.gender = 'M';
|
||||
line = line.slice(0, -4);
|
||||
}
|
||||
if (line.endsWith(' (F)')) {
|
||||
set.gender = 'F';
|
||||
line = line.slice(0, -4);
|
||||
}
|
||||
if (line.endsWith(')') && line.includes('(')) {
|
||||
const [name, species] = line.slice(0, -1).split('(');
|
||||
set.species = Dex.species.get(species).name;
|
||||
set.name = name.trim();
|
||||
} else {
|
||||
set.species = Dex.species.get(line).name;
|
||||
set.name = '';
|
||||
}
|
||||
} else if (line.startsWith('Trait: ')) {
|
||||
line = line.slice(7);
|
||||
set.ability = line;
|
||||
} else if (line.startsWith('Ability: ')) {
|
||||
line = line.slice(9);
|
||||
set.ability = line;
|
||||
} else if (line === 'Shiny: Yes') {
|
||||
set.shiny = true;
|
||||
} else if (line.startsWith('Level: ')) {
|
||||
line = line.slice(7);
|
||||
set.level = +line;
|
||||
} else if (line.startsWith('Happiness: ')) {
|
||||
line = line.slice(11);
|
||||
set.happiness = +line;
|
||||
} else if (line.startsWith('Pokeball: ')) {
|
||||
line = line.slice(10);
|
||||
set.pokeball = line;
|
||||
} else if (line.startsWith('Hidden Power: ')) {
|
||||
line = line.slice(14);
|
||||
set.hpType = line;
|
||||
} else if (line === 'Gigantamax: Yes') {
|
||||
set.gigantamax = true;
|
||||
} else if (line.startsWith('EVs: ')) {
|
||||
line = line.slice(5);
|
||||
const evLines = line.split('/');
|
||||
set.evs = {hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0};
|
||||
for (const evLine of evLines) {
|
||||
const [statValue, statName] = evLine.trim().split(' ');
|
||||
const statid = Dex.stats.getID(statName);
|
||||
if (!statid) continue;
|
||||
const value = parseInt(statValue);
|
||||
set.evs[statid] = value;
|
||||
}
|
||||
} else if (line.startsWith('IVs: ')) {
|
||||
line = line.slice(5);
|
||||
const ivLines = line.split('/');
|
||||
set.ivs = {hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31};
|
||||
for (const ivLine of ivLines) {
|
||||
const [statValue, statName] = ivLine.trim().split(' ');
|
||||
const statid = Dex.stats.getID(statName);
|
||||
if (!statid) continue;
|
||||
let value = parseInt(statValue);
|
||||
if (isNaN(value)) value = 31;
|
||||
set.ivs[statid] = value;
|
||||
}
|
||||
} else if (/^[A-Za-z]+ (N|n)ature/.test(line)) {
|
||||
let natureIndex = line.indexOf(' Nature');
|
||||
if (natureIndex === -1) natureIndex = line.indexOf(' nature');
|
||||
if (natureIndex === -1) return;
|
||||
line = line.substr(0, natureIndex);
|
||||
if (line !== 'undefined') set.nature = line;
|
||||
} else if (line.startsWith('-') || line.startsWith('~')) {
|
||||
line = line.slice(line.charAt(1) === ' ' ? 2 : 1);
|
||||
if (line.startsWith('Hidden Power [')) {
|
||||
const hpType = line.slice(14, -1);
|
||||
line = 'Hidden Power ' + hpType;
|
||||
if (!set.ivs && Dex.types.isName(hpType)) {
|
||||
set.ivs = {hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31};
|
||||
const hpIVs = Dex.types.get(hpType).HPivs || {};
|
||||
for (const statid in hpIVs) {
|
||||
set.ivs[statid as StatID] = hpIVs[statid as StatID]!;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (line === 'Frustration' && set.happiness === undefined) {
|
||||
set.happiness = 0;
|
||||
}
|
||||
set.moves.push(line);
|
||||
}
|
||||
}
|
||||
/** Accepts a team in any format (JSON, packed, or exported) */
|
||||
import(buffer: string): PokemonSet[] | null {
|
||||
if (buffer.startsWith('[')) {
|
||||
try {
|
||||
const team = JSON.parse(buffer);
|
||||
if (!Array.isArray(team)) throw new Error(`Team should be an Array but isn't`);
|
||||
for (const set of team) {
|
||||
set.name = Dex.getName(set.name);
|
||||
set.species = Dex.getName(set.species);
|
||||
set.item = Dex.getName(set.item);
|
||||
set.ability = Dex.getName(set.ability);
|
||||
set.gender = Dex.getName(set.gender);
|
||||
set.nature = Dex.getName(set.nature);
|
||||
const evs = {hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0};
|
||||
if (set.evs) {
|
||||
for (const statid in evs) {
|
||||
if (typeof set.evs[statid] === 'number') evs[statid as StatID] = set.evs[statid];
|
||||
}
|
||||
}
|
||||
set.evs = evs;
|
||||
const ivs = {hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31};
|
||||
if (set.ivs) {
|
||||
for (const statid in ivs) {
|
||||
if (typeof set.ivs[statid] === 'number') ivs[statid as StatID] = set.ivs[statid];
|
||||
}
|
||||
}
|
||||
set.ivs = ivs;
|
||||
if (!Array.isArray(set.moves)) {
|
||||
set.moves = [];
|
||||
} else {
|
||||
set.moves = set.moves.map(Dex.getName);
|
||||
}
|
||||
}
|
||||
return team;
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
const lines = buffer.split("\n");
|
||||
|
||||
const sets: PokemonSet[] = [];
|
||||
let curSet: PokemonSet | null = null;
|
||||
|
||||
while (lines.length && !lines[0]) lines.shift();
|
||||
while (lines.length && !lines[lines.length - 1]) lines.pop();
|
||||
|
||||
if (lines.length === 1 && lines[0].includes('|')) {
|
||||
return this.unpack(lines[0]);
|
||||
}
|
||||
for (let line of lines) {
|
||||
line = line.trim();
|
||||
if (line === '' || line === '---') {
|
||||
curSet = null;
|
||||
} else if (line.startsWith('===')) {
|
||||
// team backup format; ignore
|
||||
} else if (!curSet) {
|
||||
curSet = {
|
||||
name: '', species: '', item: '', ability: '', gender: '',
|
||||
nature: '',
|
||||
evs: {hp: 0, atk: 0, def: 0, spa: 0, spd: 0, spe: 0},
|
||||
ivs: {hp: 31, atk: 31, def: 31, spa: 31, spd: 31, spe: 31},
|
||||
level: 100,
|
||||
moves: [],
|
||||
};
|
||||
sets.push(curSet);
|
||||
this.parseExportedTeamLine(line, true, curSet);
|
||||
} else {
|
||||
this.parseExportedTeamLine(line, false, curSet);
|
||||
}
|
||||
}
|
||||
return sets;
|
||||
}
|
||||
|
||||
getGenerator(format: Format | string, seed: PRNG | PRNGSeed | null = null) {
|
||||
const TeamGenerator = require(Dex.forFormat(format).dataDir + '/random-teams').default;
|
||||
return new TeamGenerator(format, seed);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user