@pkmn/sim 0.5.21 → 0.5.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/LICENSE +1 -1
  2. package/build/config/formats.js +412 -419
  3. package/build/config/formats.js.map +1 -1
  4. package/build/data/abilities.js +1 -3
  5. package/build/data/abilities.js.map +1 -1
  6. package/build/data/aliases.js +4 -4
  7. package/build/data/aliases.js.map +1 -1
  8. package/build/data/conditions.js +5 -2
  9. package/build/data/conditions.js.map +1 -1
  10. package/build/data/formats-data.js +9 -10
  11. package/build/data/formats-data.js.map +1 -1
  12. package/build/data/mods/gen1/conditions.js +3 -0
  13. package/build/data/mods/gen1/conditions.js.map +1 -1
  14. package/build/data/mods/gen1/formats-data.js +1 -1
  15. package/build/data/mods/gen1/formats-data.js.map +1 -1
  16. package/build/data/mods/gen1/moves.js +5 -2
  17. package/build/data/mods/gen1/moves.js.map +1 -1
  18. package/build/data/mods/gen1/scripts.js +4 -2
  19. package/build/data/mods/gen1/scripts.js.map +1 -1
  20. package/build/data/mods/gen2/conditions.js +3 -0
  21. package/build/data/mods/gen2/conditions.js.map +1 -1
  22. package/build/data/mods/gen3/conditions.js +3 -0
  23. package/build/data/mods/gen3/conditions.js.map +1 -1
  24. package/build/data/mods/gen3/formats-data.js +3 -3
  25. package/build/data/mods/gen3/formats-data.js.map +1 -1
  26. package/build/data/mods/gen4/abilities.js +1 -1
  27. package/build/data/mods/gen4/abilities.js.map +1 -1
  28. package/build/data/mods/gen4/conditions.js +3 -0
  29. package/build/data/mods/gen4/conditions.js.map +1 -1
  30. package/build/data/mods/gen4/moves.js +9 -2
  31. package/build/data/mods/gen4/moves.js.map +1 -1
  32. package/build/data/mods/gen5/moves.js +4 -7
  33. package/build/data/mods/gen5/moves.js.map +1 -1
  34. package/build/data/mods/gen5/pokedex.js +24 -0
  35. package/build/data/mods/gen5/pokedex.js.map +1 -1
  36. package/build/data/mods/gen6/formats-data.js +64 -64
  37. package/build/data/mods/gen6/formats-data.js.map +1 -1
  38. package/build/data/mods/gen7/abilities.js +8 -0
  39. package/build/data/mods/gen7/abilities.js.map +1 -1
  40. package/build/data/mods/gen7/moves.js +2 -2
  41. package/build/data/mods/gen7/moves.js.map +1 -1
  42. package/build/data/moves.js +8 -3
  43. package/build/data/moves.js.map +1 -1
  44. package/build/data/pokedex.js +1 -1
  45. package/build/data/rulesets.js +270 -3
  46. package/build/data/rulesets.js.map +1 -1
  47. package/build/lib/utils.d.ts +4 -0
  48. package/build/lib/utils.js +20 -1
  49. package/build/lib/utils.js.map +1 -1
  50. package/build/sim/battle-stream.js +3 -0
  51. package/build/sim/battle-stream.js.map +1 -1
  52. package/build/sim/exported-global-types.d.ts +2 -0
  53. package/build/sim/global-types.d.ts +2 -0
  54. package/build/sim/pokemon.d.ts +1 -0
  55. package/build/sim/pokemon.js +9 -2
  56. package/build/sim/pokemon.js.map +1 -1
  57. package/build/sim/side.js +21 -0
  58. package/build/sim/side.js.map +1 -1
  59. package/config/formats.ts +407 -406
  60. package/data/abilities.ts +1 -3
  61. package/data/aliases.ts +4 -4
  62. package/data/conditions.ts +6 -2
  63. package/data/formats-data.ts +9 -10
  64. package/data/mods/gen1/conditions.ts +4 -0
  65. package/data/mods/gen1/formats-data.ts +1 -1
  66. package/data/mods/gen1/moves.ts +5 -1
  67. package/data/mods/gen1/scripts.ts +3 -2
  68. package/data/mods/gen2/conditions.ts +4 -0
  69. package/data/mods/gen3/conditions.ts +4 -0
  70. package/data/mods/gen3/formats-data.ts +3 -3
  71. package/data/mods/gen4/abilities.ts +1 -1
  72. package/data/mods/gen4/conditions.ts +4 -0
  73. package/data/mods/gen4/moves.ts +9 -2
  74. package/data/mods/gen5/moves.ts +4 -7
  75. package/data/mods/gen5/pokedex.ts +24 -0
  76. package/data/mods/gen6/formats-data.ts +64 -64
  77. package/data/mods/gen7/abilities.ts +8 -0
  78. package/data/mods/gen7/moves.ts +2 -2
  79. package/data/moves.ts +8 -3
  80. package/data/pokedex.ts +1 -1
  81. package/data/rulesets.ts +247 -3
  82. package/lib/utils.ts +16 -0
  83. package/package.json +2 -2
  84. package/sim/battle-stream.ts +3 -0
  85. package/sim/exported-global-types.ts +2 -0
  86. package/sim/global-types.ts +2 -0
  87. package/sim/pokemon.ts +9 -2
  88. package/sim/side.ts +20 -0
package/data/rulesets.ts CHANGED
@@ -14,7 +14,7 @@ export const Rulesets: {[k: string]: FormatData} = {
14
14
  name: 'Standard',
15
15
  desc: "The standard ruleset for all offical Smogon singles tiers (Ubers, OU, etc.)",
16
16
  ruleset: [
17
- 'Obtainable', 'Team Preview', 'Sleep Clause Mod', 'Species Clause', 'Nickname Clause', 'OHKO Clause', 'Evasion Moves Clause', 'Endless Battle Clause', 'HP Percentage Mod', 'Cancel Mod',
17
+ 'Obtainable', 'Team Preview', 'Sleep Clause Mod', 'Species Clause', 'Nickname Clause', 'OHKO Clause', 'Evasion Items Clause', 'Evasion Moves Clause', 'Endless Battle Clause', 'HP Percentage Mod', 'Cancel Mod',
18
18
  ],
19
19
  },
20
20
  standardnext: {
@@ -71,6 +71,14 @@ export const Rulesets: {[k: string]: FormatData} = {
71
71
  'Obtainable', 'Team Preview', 'Species Clause', 'Nickname Clause', 'OHKO Clause', 'Evasion Moves Clause', 'Gravity Sleep Clause', 'Endless Battle Clause', 'HP Percentage Mod', 'Cancel Mod',
72
72
  ],
73
73
  },
74
+ standardoms: {
75
+ effectType: 'ValidatorRule',
76
+ name: 'Standard OMs',
77
+ desc: "The standard ruleset for all Smogon OMs (Almost Any Ability, STABmons, etc.)",
78
+ ruleset: [
79
+ 'Obtainable', 'Team Preview', 'Species Clause', 'Nickname Clause', 'OHKO Clause', 'Evasion Moves Clause', 'Endless Battle Clause', 'Dynamax Clause', 'HP Percentage Mod', 'Cancel Mod', 'Overflow Stat Mod',
80
+ ],
81
+ },
74
82
  standardnatdex: {
75
83
  effectType: 'ValidatorRule',
76
84
  name: 'Standard NatDex',
@@ -399,6 +407,29 @@ export const Rulesets: {[k: string]: FormatData} = {
399
407
  }
400
408
  },
401
409
  },
410
+ forceselect: {
411
+ effectType: 'ValidatorRule',
412
+ name: 'Force Select',
413
+ desc: `Forces a Pokemon to be on the team and selected at Team Preview. Usage: Force Select = [Pokemon], e.g. "Force Select = Magikarp"`,
414
+ hasValue: true,
415
+ onValidateRule(value) {
416
+ if (!this.dex.species.get(value).exists) throw new Error(`Misspelled Pokemon "${value}"`);
417
+ },
418
+ onValidateTeam(team) {
419
+ let hasSelection = false;
420
+ const species = this.dex.species.get(this.ruleTable.valueRules.get('forceselect'));
421
+ for (const set of team) {
422
+ if (species.name === set.species) {
423
+ hasSelection = true;
424
+ break;
425
+ }
426
+ }
427
+ if (!hasSelection) {
428
+ return [`Your team must contain ${species.name}.`];
429
+ }
430
+ },
431
+ // hardcoded in sim/side
432
+ },
402
433
  evlimits: {
403
434
  effectType: 'ValidatorRule',
404
435
  name: 'EV Limits',
@@ -620,6 +651,15 @@ export const Rulesets: {[k: string]: FormatData} = {
620
651
  return problems;
621
652
  },
622
653
  },
654
+ evasionclause: {
655
+ effectType: 'ValidatorRule',
656
+ name: 'Evasion Clause',
657
+ desc: "Bans abilities, items, and moves that boost Evasion",
658
+ ruleset: ['Evasion Abilities Clause', 'Evasion Items Clause', 'Evasion Moves Clause'],
659
+ onBegin() {
660
+ this.add('rule', 'Evasion Clause: Evasion abilities, items, and moves are banned');
661
+ },
662
+ },
623
663
  evasionabilitiesclause: {
624
664
  effectType: 'ValidatorRule',
625
665
  name: 'Evasion Abilities Clause',
@@ -629,6 +669,15 @@ export const Rulesets: {[k: string]: FormatData} = {
629
669
  this.add('rule', 'Evasion Abilities Clause: Evasion abilities are banned');
630
670
  },
631
671
  },
672
+ evasionitemsclause: {
673
+ effectType: 'ValidatorRule',
674
+ name: 'Evasion Items Clause',
675
+ desc: "Bans moves that lower the accuracy of moves used against the user",
676
+ banlist: ['Bright Powder', 'Lax Incense'],
677
+ onBegin() {
678
+ this.add('rule', 'Evasion Items Clause: Evasion items are banned');
679
+ },
680
+ },
632
681
  evasionmovesclause: {
633
682
  effectType: 'ValidatorRule',
634
683
  name: 'Evasion Moves Clause',
@@ -1262,6 +1311,19 @@ export const Rulesets: {[k: string]: FormatData} = {
1262
1311
  }
1263
1312
  },
1264
1313
  },
1314
+ gemsclause: {
1315
+ effectType: 'ValidatorRule',
1316
+ name: 'Gems Clause',
1317
+ desc: "Bans all Gems",
1318
+ onValidateSet(set) {
1319
+ if (!set.item) return;
1320
+ const item = this.dex.items.get(set.item);
1321
+ if (item.isGem) {
1322
+ if (this.ruleTable.has(`+item:${item.id}`)) return;
1323
+ return [`${item.name} is banned due to Gems Clause.`];
1324
+ }
1325
+ },
1326
+ },
1265
1327
  'sketchgen8moves': {
1266
1328
  effectType: 'ValidatorRule',
1267
1329
  name: 'Sketch Gen 8 Moves',
@@ -1833,8 +1895,7 @@ export const Rulesets: {[k: string]: FormatData} = {
1833
1895
  reevolutionmod: {
1834
1896
  effectType: "Rule",
1835
1897
  name: "Re-Evolution Mod",
1836
- desc: "Pokémon gain the boosts they would gain from evolving again",
1837
- ruleset: ['Overflow Stat Mod'],
1898
+ desc: "Pokémon gain the stat changes they would gain from evolving again.",
1838
1899
  onBegin() {
1839
1900
  this.add('rule', 'Re-Evolution Mod: Pok\u00e9mon gain the boosts they would gain from evolving again');
1840
1901
  },
@@ -1852,4 +1913,187 @@ export const Rulesets: {[k: string]: FormatData} = {
1852
1913
  return newSpecies;
1853
1914
  },
1854
1915
  },
1916
+ brokenrecordmod: {
1917
+ effectType: "Rule",
1918
+ name: "Broken Record Mod",
1919
+ desc: `Pokémon can hold a TR to use that move in battle.`,
1920
+ onValidateSet(set) {
1921
+ if (!set.item) return;
1922
+ const item = this.dex.items.get(set.item);
1923
+ if (!/^tr\d\d/i.test(item.name)) return;
1924
+ const moveName = item.desc.split('move ')[1].split('.')[0];
1925
+ if (set.moves.map(this.toID).includes(this.toID(moveName))) {
1926
+ return [
1927
+ `${set.species} can't run ${item.name} (${moveName}) as its item because it already has that move in its moveset.`,
1928
+ ];
1929
+ }
1930
+ },
1931
+ onValidateTeam(team) {
1932
+ const trs = new Set<string>();
1933
+ for (const set of team) {
1934
+ if (!set.item) continue;
1935
+ const item = this.dex.items.get(set.item).name;
1936
+ if (!/^tr\d\d/i.test(item)) continue;
1937
+ if (trs.has(item)) {
1938
+ return [`Your team already has a Pok\u00e9mon with ${item}.`];
1939
+ }
1940
+ trs.add(item);
1941
+ }
1942
+ },
1943
+ onTakeItem(item) {
1944
+ return !/^tr\d\d/i.test(item.name);
1945
+ },
1946
+ onModifyMove(move) {
1947
+ if (move.id === 'knockoff') {
1948
+ move.onBasePower = function (basePower, source, target, m) {
1949
+ const item = target.getItem();
1950
+ if (!this.singleEvent('TakeItem', item, target.itemState, target, target, m, item)) return;
1951
+ // Very hardcode but I'd prefer to not make a mod for one damage calculation change
1952
+ if (item.id && !/^tr\d\d/i.test(item.id)) {
1953
+ return this.chainModify(1.5);
1954
+ }
1955
+ };
1956
+ } else if (move.id === 'fling') {
1957
+ move.onPrepareHit = function (target, source, m) {
1958
+ if (source.ignoringItem()) return false;
1959
+ const item = source.getItem();
1960
+ if (!this.singleEvent('TakeItem', item, source.itemState, source, source, m, item)) return false;
1961
+ if (!item.fling) return false;
1962
+ if (/^tr\d\d/i.test(item.id)) return false;
1963
+ m.basePower = item.fling.basePower;
1964
+ if (item.isBerry) {
1965
+ m.onHit = function (foe) {
1966
+ if (this.singleEvent('Eat', item, null, foe, null, null)) {
1967
+ this.runEvent('EatItem', foe, null, null, item);
1968
+ if (item.id === 'leppaberry') foe.staleness = 'external';
1969
+ }
1970
+ if (item.onEat) foe.ateBerry = true;
1971
+ };
1972
+ } else if (item.fling.effect) {
1973
+ m.onHit = item.fling.effect;
1974
+ } else {
1975
+ if (!m.secondaries) m.secondaries = [];
1976
+ if (item.fling.status) {
1977
+ m.secondaries.push({status: item.fling.status});
1978
+ } else if (item.fling.volatileStatus) {
1979
+ m.secondaries.push({volatileStatus: item.fling.volatileStatus});
1980
+ }
1981
+ }
1982
+ source.addVolatile('fling');
1983
+ };
1984
+ }
1985
+ },
1986
+ onBegin() {
1987
+ for (const pokemon of this.getAllPokemon()) {
1988
+ const item = pokemon.getItem();
1989
+ if (/^tr\d\d/i.test(item.name)) {
1990
+ const move = this.dex.moves.get(item.desc.split('move ')[1].split('.')[0]);
1991
+ pokemon.moveSlots = (pokemon as any).baseMoveSlots = [
1992
+ ...pokemon.baseMoveSlots, {
1993
+ id: move.id,
1994
+ move: move.name,
1995
+ pp: move.pp * 8 / 5,
1996
+ maxpp: move.pp * 8 / 5,
1997
+ target: move.target,
1998
+ disabled: false,
1999
+ disabledSource: '',
2000
+ used: false,
2001
+ },
2002
+ ];
2003
+ }
2004
+ }
2005
+ },
2006
+ },
2007
+ categoryswapmod: {
2008
+ effectType: 'Rule',
2009
+ name: 'Category Swap Mod',
2010
+ desc: `All physical moves become special, and all special moves become physical.`,
2011
+ onBegin() {
2012
+ this.add('rule', 'Category Swap Mod: All physical moves become special, and vice versa');
2013
+ },
2014
+ onModifyMove(move, pokemon, target) {
2015
+ if (move.category === "Status") return;
2016
+
2017
+ if (move.category === "Physical") {
2018
+ move.category = "Special";
2019
+ } else if (move.category === "Special") {
2020
+ move.category = "Physical";
2021
+ }
2022
+
2023
+ switch (move.id) {
2024
+ case 'doomdesire': {
2025
+ move.onTry = function (source, subtarget) {
2026
+ if (!subtarget.side.addSlotCondition(subtarget, 'futuremove')) return false;
2027
+ Object.assign(subtarget.side.slotConditions[subtarget.position]['futuremove'], {
2028
+ move: 'doomdesire',
2029
+ source: source,
2030
+ moveData: {
2031
+ id: 'doomdesire',
2032
+ name: "Doom Desire",
2033
+ accuracy: 100,
2034
+ basePower: 140,
2035
+ category: "Physical",
2036
+ priority: 0,
2037
+ flags: {},
2038
+ effectType: 'Move',
2039
+ isFutureMove: true,
2040
+ type: 'Steel',
2041
+ },
2042
+ });
2043
+ this.add('-start', source, 'Doom Desire');
2044
+ return this.NOT_FAIL;
2045
+ };
2046
+ break;
2047
+ }
2048
+ case 'futuresight': {
2049
+ move.onTry = function (source, subtarget) {
2050
+ if (!subtarget.side.addSlotCondition(subtarget, 'futuremove')) return false;
2051
+ Object.assign(subtarget.side.slotConditions[subtarget.position]['futuremove'], {
2052
+ duration: 3,
2053
+ move: 'futuresight',
2054
+ source: source,
2055
+ moveData: {
2056
+ id: 'futuresight',
2057
+ name: "Future Sight",
2058
+ accuracy: 100,
2059
+ basePower: 120,
2060
+ category: "Physical",
2061
+ priority: 0,
2062
+ flags: {},
2063
+ ignoreImmunity: false,
2064
+ effectType: 'Move',
2065
+ isFutureMove: true,
2066
+ type: 'Psychic',
2067
+ },
2068
+ });
2069
+ this.add('-start', source, 'move: Future Sight');
2070
+ return this.NOT_FAIL;
2071
+ };
2072
+ break;
2073
+ }
2074
+ // Moves with dynamic categories will always be physical if not special-cased
2075
+ case 'lightthatburnsthesky':
2076
+ case 'photongeyser': {
2077
+ move.category = 'Special';
2078
+ if (pokemon.getStat('atk', false, true) > pokemon.getStat('spa', false, true)) move.category = 'Physical';
2079
+ break;
2080
+ }
2081
+ case 'shellsidearm': {
2082
+ if (!target) return;
2083
+ move.category = 'Special';
2084
+ const atk = pokemon.getStat('atk', false, true);
2085
+ const spa = pokemon.getStat('spa', false, true);
2086
+ const def = target.getStat('def', false, true);
2087
+ const spd = target.getStat('spd', false, true);
2088
+ const physical = Math.floor(Math.floor(Math.floor(Math.floor(2 * pokemon.level / 5 + 2) * 90 * atk) / def) / 50);
2089
+ const special = Math.floor(Math.floor(Math.floor(Math.floor(2 * pokemon.level / 5 + 2) * 90 * spa) / spd) / 50);
2090
+ if (physical > special || (physical === special && this.random(2) === 0)) {
2091
+ move.category = 'Physical';
2092
+ move.flags.contact = 1;
2093
+ }
2094
+ break;
2095
+ }
2096
+ }
2097
+ },
2098
+ },
1855
2099
  };
package/lib/utils.ts CHANGED
@@ -63,6 +63,22 @@ export function stripHTML(htmlContent: string) {
63
63
  return htmlContent.replace(/<[^>]*>/g, '');
64
64
  }
65
65
 
66
+ /**
67
+ * Maps numbers to their ordinal string.
68
+ */
69
+ export function formatOrder(place: number) {
70
+ // anything between 10 and 20 should always end with -th
71
+ let remainder = place % 100;
72
+ if (remainder >= 10 && remainder <= 20) return place + 'th';
73
+
74
+ // follow standard rules with -st, -nd, -rd, and -th
75
+ remainder = place % 10;
76
+ if (remainder === 1) return place + 'st';
77
+ if (remainder === 2) return place + 'nd';
78
+ if (remainder === 3) return place + 'rd';
79
+ return place + 'th';
80
+ }
81
+
66
82
  /**
67
83
  * Visualizes eval output in a slightly more readable form
68
84
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pkmn/sim",
3
- "version": "0.5.21",
3
+ "version": "0.5.24",
4
4
  "description": "An automatically generated extraction of just the simulator portion of Pokémon Showdown",
5
5
  "homepage": "https://psim.us",
6
6
  "main": "build/sim/index.js",
@@ -27,7 +27,7 @@
27
27
  "sim"
28
28
  ],
29
29
  "dependencies": {
30
- "@pkmn/sets": "^3.0.0",
30
+ "@pkmn/sets": "^3.1.0",
31
31
  "@pkmn/streams": "^1.0.0"
32
32
  },
33
33
  "devDependencies": {
@@ -211,6 +211,9 @@ export class BattleStream extends Streams.ObjectReadWriteStream<string> {
211
211
  case 'requestlog':
212
212
  this.push(`requesteddata\n${this.battle!.inputLog.join('\n')}`);
213
213
  break;
214
+ case 'requestexport':
215
+ this.push(`requesteddata\n${this.battle!.prngSeed}\n${this.battle!.inputLog.join('\n')}`);
216
+ break;
214
217
  case 'requestteam':
215
218
  message = message.trim();
216
219
  const slotNum = parseInt(message.slice(1)) - 1;
@@ -515,6 +515,7 @@ export namespace RandomTeamsTypes {
515
515
  shiny: boolean;
516
516
  nature?: string;
517
517
  happiness?: number;
518
+ dynamaxLevel?: number;
518
519
  gigantamax?: boolean;
519
520
  }
520
521
  export interface RandomFactorySet {
@@ -530,6 +531,7 @@ export namespace RandomTeamsTypes {
530
531
  ivs: SparseStatsTable;
531
532
  nature: string;
532
533
  moves: string[];
534
+ dynamaxLevel?: number;
533
535
  gigantamax?: boolean;
534
536
  }
535
537
  }
@@ -515,6 +515,7 @@ namespace RandomTeamsTypes {
515
515
  shiny: boolean;
516
516
  nature?: string;
517
517
  happiness?: number;
518
+ dynamaxLevel?: number;
518
519
  gigantamax?: boolean;
519
520
  }
520
521
  export interface RandomFactorySet {
@@ -530,6 +531,7 @@ namespace RandomTeamsTypes {
530
531
  ivs: SparseStatsTable;
531
532
  nature: string;
532
533
  moves: string[];
534
+ dynamaxLevel?: number;
533
535
  gigantamax?: boolean;
534
536
  }
535
537
  }
package/sim/pokemon.ts CHANGED
@@ -77,6 +77,7 @@ export class Pokemon {
77
77
  readonly gender: GenderName;
78
78
  readonly happiness: number;
79
79
  readonly pokeball: string;
80
+ readonly dynamaxLevel: number;
80
81
  readonly gigantamax: boolean;
81
82
 
82
83
  /** Transform keeps the original pre-transformed Hidden Power in Gen 2-4. */
@@ -326,6 +327,7 @@ export class Pokemon {
326
327
  if (this.gender === 'N') this.gender = '';
327
328
  this.happiness = typeof set.happiness === 'number' ? this.battle.clampIntRange(set.happiness, 0, 255) : 255;
328
329
  this.pokeball = this.set.pokeball || 'pokeball';
330
+ this.dynamaxLevel = typeof set.dynamaxLevel === 'number' ? this.battle.clampIntRange(set.dynamaxLevel, 0, 10) : 10;
329
331
  this.gigantamax = this.set.gigantamax || false;
330
332
 
331
333
  this.baseMoveSlots = [];
@@ -1522,7 +1524,7 @@ export class Pokemon {
1522
1524
  cureStatus(silent = false) {
1523
1525
  if (!this.hp || !this.status) return false;
1524
1526
  this.battle.add('-curestatus', this, this.status, silent ? '[silent]' : '[msg]');
1525
- if (this.status === 'slp' && !this.hasAbility('comatose') && this.removeVolatile('nightmare')) {
1527
+ if (this.status === 'slp' && this.removeVolatile('nightmare')) {
1526
1528
  this.battle.add('-end', this, 'Nightmare', '[silent]');
1527
1529
  }
1528
1530
  this.setStatus('');
@@ -1599,7 +1601,12 @@ export class Pokemon {
1599
1601
  * Unlike cureStatus, does not give cure message
1600
1602
  */
1601
1603
  clearStatus() {
1602
- return this.setStatus('');
1604
+ if (!this.hp || !this.status) return false;
1605
+ if (this.status === 'slp' && this.removeVolatile('nightmare')) {
1606
+ this.battle.add('-end', this, 'Nightmare', '[silent]');
1607
+ }
1608
+ this.setStatus('');
1609
+ return true;
1603
1610
  }
1604
1611
 
1605
1612
  getStatus() {
package/sim/side.ts CHANGED
@@ -796,6 +796,26 @@ export class Side {
796
796
  }
797
797
  }
798
798
  }
799
+ if (ruleTable.valueRules.has('forceselect')) {
800
+ const species = this.battle.dex.species.get(ruleTable.valueRules.get('forceselect'));
801
+ if (!data) {
802
+ // autoChoose
803
+ positions = [...this.pokemon.keys()].filter(pos => this.pokemon[pos].species.name === species.name)
804
+ .concat([...this.pokemon.keys()].filter(pos => this.pokemon[pos].species.name !== species.name))
805
+ .slice(0, pickedTeamSize);
806
+ } else {
807
+ let hasSelection = false;
808
+ for (const pos of positions) {
809
+ if (this.pokemon[pos].species.name === species.name) {
810
+ hasSelection = true;
811
+ break;
812
+ }
813
+ }
814
+ if (!hasSelection) {
815
+ return this.emitChoiceError(`You must bring ${species.name} to the battle.`);
816
+ }
817
+ }
818
+ }
799
819
  for (const [index, pos] of positions.entries()) {
800
820
  this.choice.switchIns.add(pos);
801
821
  this.choice.actions.push({