@pkmn/randoms 0.7.25 → 0.7.26

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.
package/build/index.js CHANGED
@@ -300,6 +300,10 @@ var RandomGen8Teams = class {
300
300
  random(m, n) {
301
301
  return this.prng.next(m, n);
302
302
  }
303
+ /**
304
+ * Remove an element from an unsorted array significantly faster
305
+ * than .splice
306
+ */
303
307
  fastPop(list, index) {
304
308
  const length = list.length;
305
309
  if (index < 0 || index >= list.length) {
@@ -310,6 +314,10 @@ var RandomGen8Teams = class {
310
314
  list.pop();
311
315
  return element;
312
316
  }
317
+ /**
318
+ * Remove a random element from an unsorted array and return it.
319
+ * Uses the battle's RNG if in a battle.
320
+ */
313
321
  sampleNoReplace(list) {
314
322
  const length = list.length;
315
323
  if (length === 0)
@@ -317,6 +325,11 @@ var RandomGen8Teams = class {
317
325
  const index = this.random(length);
318
326
  return this.fastPop(list, index);
319
327
  }
328
+ /**
329
+ * Removes n random elements from an unsorted array and returns them.
330
+ * If n is less than the array's length, randomly removes and returns all the elements
331
+ * in the array (so the returned array could have length < n).
332
+ */
320
333
  multipleSamplesNoReplace(list, n) {
321
334
  const samples = [];
322
335
  while (samples.length < n && list.length) {
@@ -324,6 +337,10 @@ var RandomGen8Teams = class {
324
337
  }
325
338
  return samples;
326
339
  }
340
+ /**
341
+ * Check if user has directly tried to ban/unban/restrict things in a custom battle.
342
+ * Doesn't count bans nested inside other formats/rules.
343
+ */
327
344
  hasDirectCustomBanlistChanges() {
328
345
  if (!this.format.customRules)
329
346
  return false;
@@ -335,11 +352,17 @@ var RandomGen8Teams = class {
335
352
  }
336
353
  return false;
337
354
  }
355
+ /**
356
+ * Inform user when custom bans are unsupported in a team generator.
357
+ */
338
358
  enforceNoDirectCustomBanlistChanges() {
339
359
  if (this.hasDirectCustomBanlistChanges()) {
340
360
  throw new Error(`Custom bans are not currently supported in ${this.format.name}.`);
341
361
  }
342
362
  }
363
+ /**
364
+ * Inform user when complex bans are unsupported in a team generator.
365
+ */
343
366
  enforceNoDirectComplexBans() {
344
367
  if (!this.format.customRules)
345
368
  return false;
@@ -349,6 +372,9 @@ var RandomGen8Teams = class {
349
372
  }
350
373
  }
351
374
  }
375
+ /**
376
+ * Validate set element pool size is sufficient to support size requirements after simple bans.
377
+ */
352
378
  enforceCustomPoolSizeNoComplexBans(effectTypeName, basicEffectPool, requiredCount, requiredCountExplanation) {
353
379
  if (basicEffectPool.length >= requiredCount)
354
380
  return;
@@ -884,7 +910,10 @@ var RandomGen8Teams = class {
884
910
  counter.add("stab");
885
911
  categories[move.category] += 0.1;
886
912
  }
887
- } else if (moveType === "Normal" && ["Aerilate", "Galvanize", "Pixilate", "Refrigerate"].some((abil) => abilities.has(abil)) || move.priority === 0 && (abilities.has("Libero") || abilities.has("Protean")) && !this.noStab.includes(moveid) || moveType === "Steel" && abilities.has("Steelworker")) {
913
+ } else if (
914
+ // Less obvious forms of STAB
915
+ moveType === "Normal" && ["Aerilate", "Galvanize", "Pixilate", "Refrigerate"].some((abil) => abilities.has(abil)) || move.priority === 0 && (abilities.has("Libero") || abilities.has("Protean")) && !this.noStab.includes(moveid) || moveType === "Steel" && abilities.has("Steelworker")
916
+ ) {
888
917
  counter.add("stab");
889
918
  }
890
919
  if (move.flags["bite"])
@@ -1169,7 +1198,10 @@ var RandomGen8Teams = class {
1169
1198
  case "leafstorm":
1170
1199
  const leafBladePossible = movePool.includes("leafblade") || moves.has("leafblade");
1171
1200
  return {
1172
- cull: counter.setupType === "Physical" && (species.id === "virizion" || leafBladePossible) || moves.has("gigadrain") && !!counter.get("Status") || isDoubles && moves.has("energyball")
1201
+ cull: (
1202
+ // Virizion should always prefer Leaf Blade to Leaf Storm on Physical sets
1203
+ counter.setupType === "Physical" && (species.id === "virizion" || leafBladePossible) || moves.has("gigadrain") && !!counter.get("Status") || isDoubles && moves.has("energyball")
1204
+ )
1173
1205
  };
1174
1206
  case "powerwhip":
1175
1207
  return { cull: moves.has("leechlife") };
@@ -1246,7 +1278,8 @@ var RandomGen8Teams = class {
1246
1278
  return { cull: moves.has("knockoff") };
1247
1279
  case "shadowball":
1248
1280
  return {
1249
- cull: isDoubles && moves.has("phantomforce") || abilities.has("Pixilate") && (!!counter.setupType || counter.get("Status") > 1) || !types.has("Ghost") && movePool.includes("focusblast")
1281
+ cull: isDoubles && moves.has("phantomforce") || // Special case for Sylveon, which never wants Shadow Ball as its only coverage move
1282
+ abilities.has("Pixilate") && (!!counter.setupType || counter.get("Status") > 1) || !types.has("Ghost") && movePool.includes("focusblast")
1250
1283
  };
1251
1284
  case "shadowclaw":
1252
1285
  return { cull: types.has("Steel") && moves.has("shadowsneak") && counter.get("Physical") < 4 };
@@ -1259,7 +1292,10 @@ var RandomGen8Teams = class {
1259
1292
  return { cull: pulseIncompatible && !shiftryCase && counter.setupType !== "Special" };
1260
1293
  case "suckerpunch":
1261
1294
  return {
1262
- cull: isNoDynamax && species.id === "shiftry" && moves.has("defog") || moves.has("rest") || counter.damagingMoves.size < 2 || counter.setupType === "Special" || counter.get("Dark") > 1 && !types.has("Dark")
1295
+ cull: (
1296
+ // Shiftry in No Dynamax would otherwise get Choice Scarf Sucker Punch sometimes.
1297
+ isNoDynamax && species.id === "shiftry" && moves.has("defog") || moves.has("rest") || counter.damagingMoves.size < 2 || counter.setupType === "Special" || counter.get("Dark") > 1 && !types.has("Dark")
1298
+ )
1263
1299
  };
1264
1300
  case "dazzlinggleam":
1265
1301
  return { cull: ["fleurcannon", "moonblast", "petaldance"].some((m) => moves.has(m)) };
@@ -1280,7 +1316,9 @@ var RandomGen8Teams = class {
1280
1316
  return { cull: moves.has("rest") || moves.has("wish") || move.id === "synthesis" && moves.has("gigadrain") };
1281
1317
  case "roost":
1282
1318
  return {
1283
- cull: moves.has("throatchop") || moves.has("stoneedge") && species.id === "hawlucha" || moves.has("dualwingbeat") && (moves.has("outrage") || species.id === "scizor")
1319
+ cull: moves.has("throatchop") || // Hawlucha doesn't want Roost + 3 attacks
1320
+ moves.has("stoneedge") && species.id === "hawlucha" || // Special cases for Salamence, Dynaless Dragonite, and Scizor to help prevent sets with poor coverage or no setup.
1321
+ moves.has("dualwingbeat") && (moves.has("outrage") || species.id === "scizor")
1284
1322
  };
1285
1323
  case "reflect":
1286
1324
  case "lightscreen":
@@ -1456,9 +1494,14 @@ var RandomGen8Teams = class {
1456
1494
  case "Synchronize":
1457
1495
  return counter.get("Status") < 3;
1458
1496
  case "Technician":
1459
- return !counter.get("technician") || moves.has("tailslap") || abilities.has("Punk Rock") || movePool.includes("snarl");
1497
+ return !counter.get("technician") || moves.has("tailslap") || abilities.has("Punk Rock") || // For Doubles Alolan Persian
1498
+ movePool.includes("snarl");
1460
1499
  case "Tinted Lens":
1461
- return moves.has("defog") || moves.has("hurricane") && abilities.has("Compound Eyes") || counter.get("Status") > 2 && !counter.setupType;
1500
+ return (
1501
+ // For Sigilyph
1502
+ moves.has("defog") || // For Butterfree
1503
+ moves.has("hurricane") && abilities.has("Compound Eyes") || counter.get("Status") > 2 && !counter.setupType
1504
+ );
1462
1505
  case "Torrent":
1463
1506
  return moves.has("focusenergy") || moves.has("hypervoice");
1464
1507
  case "Tough Claws":
@@ -1564,6 +1607,7 @@ var RandomGen8Teams = class {
1564
1607
  return "Heavy-Duty Boots";
1565
1608
  }
1566
1609
  }
1610
+ /** Item generation specific to Random Doubles */
1567
1611
  getDoublesItem(ability, types, moves, abilities, counter, teamDetails, species) {
1568
1612
  const defensiveStatTotal = species.baseStats.hp + species.baseStats.def + species.baseStats.spd;
1569
1613
  if (["dragonenergy", "eruption", "waterspout"].some((m) => moves.has(m)) && counter.damagingMoves.size >= 4)
@@ -1628,7 +1672,10 @@ var RandomGen8Teams = class {
1628
1672
  const defensiveStatTotal = species.baseStats.hp + species.baseStats.def + species.baseStats.spd;
1629
1673
  if (isLead && !isDoubles && !["Disguise", "Sturdy"].includes(ability) && !moves.has("substitute") && !counter.get("drain") && !counter.get("recoil") && !counter.get("recovery") && (defensiveStatTotal <= 250 && counter.get("hazards") || defensiveStatTotal <= 210))
1630
1674
  return "Focus Sash";
1631
- if (moves.has("clangoroussoul") || moves.has("boomburst") && Array.from(moves).some((m) => {
1675
+ if (moves.has("clangoroussoul") || // We manually check for speed-boosting moves, rather than using `counter.get('speedsetup')`,
1676
+ // because we want to check for ANY speed boosting move.
1677
+ // In particular, Shift Gear + Boomburst Toxtricity should get Throat Spray.
1678
+ moves.has("boomburst") && Array.from(moves).some((m) => {
1632
1679
  var _a;
1633
1680
  return (_a = this.dex.moves.get(m).boosts) == null ? void 0 : _a.spe;
1634
1681
  }))
@@ -1639,7 +1686,8 @@ var RandomGen8Teams = class {
1639
1686
  return "Heavy-Duty Boots";
1640
1687
  if (!isDoubles && this.dex.getEffectiveness("Ground", species) >= 2 && !types.has("Poison") && ability !== "Levitate" && !abilities.has("Iron Barbs"))
1641
1688
  return "Air Balloon";
1642
- if (!isDoubles && counter.damagingMoves.size >= 3 && !counter.get("damage") && ability !== "Sturdy" && (species.baseStats.spe >= 90 || !moves.has("voltswitch")) && ["foulplay", "rapidspin", "substitute", "uturn"].every((m) => !moves.has(m)) && (counter.get("speedsetup") || counter.get("drain") && (!isNoDynamax || species.id !== "buzzwole" || moves.has("roost")) || moves.has("trickroom") || moves.has("psystrike") || species.baseStats.spe > 40 && defensiveStatTotal < 275))
1689
+ if (!isDoubles && counter.damagingMoves.size >= 3 && !counter.get("damage") && ability !== "Sturdy" && (species.baseStats.spe >= 90 || !moves.has("voltswitch")) && ["foulplay", "rapidspin", "substitute", "uturn"].every((m) => !moves.has(m)) && (counter.get("speedsetup") || // No Dynamax Buzzwole doesn't want Life Orb with Bulk Up + 3 attacks
1690
+ counter.get("drain") && (!isNoDynamax || species.id !== "buzzwole" || moves.has("roost")) || moves.has("trickroom") || moves.has("psystrike") || species.baseStats.spe > 40 && defensiveStatTotal < 275))
1643
1691
  return "Life Orb";
1644
1692
  if (!isDoubles && counter.damagingMoves.size >= 4 && !counter.get("Dragon") && !counter.get("Normal")) {
1645
1693
  return "Expert Belt";
@@ -1669,6 +1717,7 @@ var RandomGen8Teams = class {
1669
1717
  NFE: 88
1670
1718
  };
1671
1719
  const customScale = {
1720
+ // These Pokemon are too strong and need a lower level
1672
1721
  zaciancrowned: 65,
1673
1722
  calyrexshadow: 68,
1674
1723
  xerneas: 70,
@@ -1691,6 +1740,7 @@ var RandomGen8Teams = class {
1691
1740
  polteageist: 84,
1692
1741
  wobbuffet: 86,
1693
1742
  scrafty: 86,
1743
+ // These Pokemon are too weak and need a higher level
1694
1744
  delibird: 100,
1695
1745
  vespiquen: 96,
1696
1746
  pikachu: 92,
@@ -1822,7 +1872,13 @@ var RandomGen8Teams = class {
1822
1872
  const moveIsRejectable = !(species.id === "genesectdouse" && move.id === "technoblast") && !(species.id === "togekiss" && move.id === "nastyplot") && !(species.id === "shuckle" && ["stealthrock", "stickyweb"].includes(move.id)) && (move.category === "Status" || !types.has(move.type) && move.id !== "judgment" || isLowBP && !move.multihit && !abilities.has("Technician"));
1823
1873
  const notImportantSetup = !counter.setupType || counter.setupType === "Mixed" || counter.get(counter.setupType) + counter.get("Status") > 3 && !counter.get("hazards") || move.category !== counter.setupType && move.category !== "Status";
1824
1874
  if (moveIsRejectable && (!cull && !isSetup && !move.weather && !move.stallingMove && notImportantSetup && !move.damage && (isDoubles ? this.unrejectableMovesInDoubles(move) : this.unrejectableMovesInSingles(move)))) {
1825
- if (!counter.get("stab") && counter.get("physicalpool") + counter.get("specialpool") > 0 && move.id !== "stickyweb" || moves.has("swordsdance") && species.id === "mew" && runEnforcementChecker("Flying") || abilities.has("Steelworker") && runEnforcementChecker("Steel") || !isDoubles && runEnforcementChecker("recovery") && move.id !== "stickyweb" || runEnforcementChecker("screens") || runEnforcementChecker("misc") || (isLead || species.id === "shuckle") && runEnforcementChecker("lead") || moves.has("leechseed") && runEnforcementChecker("leechseed")) {
1875
+ if (
1876
+ // Pokemon should have at least one STAB move
1877
+ !counter.get("stab") && counter.get("physicalpool") + counter.get("specialpool") > 0 && move.id !== "stickyweb" || // Swords Dance Mew should have Brave Bird
1878
+ moves.has("swordsdance") && species.id === "mew" && runEnforcementChecker("Flying") || // Dhelmise should have Anchor Shot
1879
+ abilities.has("Steelworker") && runEnforcementChecker("Steel") || // Check for miscellaneous important moves
1880
+ !isDoubles && runEnforcementChecker("recovery") && move.id !== "stickyweb" || runEnforcementChecker("screens") || runEnforcementChecker("misc") || (isLead || species.id === "shuckle") && runEnforcementChecker("lead") || moves.has("leechseed") && runEnforcementChecker("leechseed")
1881
+ ) {
1826
1882
  cull = true;
1827
1883
  } else if (move.id !== "stickyweb" && !(species.id === "azumarill" && move.id === "aquajet")) {
1828
1884
  for (const type of types) {
@@ -1914,7 +1970,8 @@ var RandomGen8Teams = class {
1914
1970
  } while (rejectAbility);
1915
1971
  if (forme === "Copperajah" && gmax) {
1916
1972
  ability = "Heavy Metal";
1917
- } else if (abilities.has("Guts") && !abilities.has("Quick Feet") && (species.id === "gurdurr" || species.id === "throh" || moves.has("facade") || moves.has("rest") && moves.has("sleeptalk"))) {
1973
+ } else if (abilities.has("Guts") && // for Ursaring in BDSP
1974
+ !abilities.has("Quick Feet") && (species.id === "gurdurr" || species.id === "throh" || moves.has("facade") || moves.has("rest") && moves.has("sleeptalk"))) {
1918
1975
  ability = "Guts";
1919
1976
  } else if (abilities.has("Moxie") && (counter.get("Physical") > 3 || moves.has("bounce")) && !isDoubles) {
1920
1977
  ability = "Moxie";
@@ -3000,7 +3057,8 @@ var RandomGen7Teams = class extends RandomGen8Teams {
3000
3057
  case "facade":
3001
3058
  return { cull: moves.has("bulkup") || hasRestTalk };
3002
3059
  case "hiddenpower":
3003
- return { cull: moves.has("rest") || !counter.get("stab") && counter.damagingMoves.size < 2 || counter.setupType === "Special" && types.has("Fairy") && movePool.includes("moonblast") };
3060
+ return { cull: moves.has("rest") || !counter.get("stab") && counter.damagingMoves.size < 2 || // Force Moonblast on Special-setup Fairies
3061
+ counter.setupType === "Special" && types.has("Fairy") && movePool.includes("moonblast") };
3004
3062
  case "hypervoice":
3005
3063
  return { cull: moves.has("blizzard") };
3006
3064
  case "judgment":
@@ -3544,7 +3602,8 @@ var RandomGen7Teams = class extends RandomGen8Teams {
3544
3602
  if (counter.setupType === "Special" && moveid === "hiddenpower" && species.types.length > 1 && counter.get("Special") <= 2 && !types.has(move.type) && !counter.get("Physical") && counter.get("specialpool")) {
3545
3603
  cull = true;
3546
3604
  }
3547
- const singlesEnforcement = !["judgment", "lightscreen", "quiverdance", "reflect", "sleeptalk", "toxic"].includes(moveid) && (move.category !== "Status" || !(move.flags.heal && species.id !== "meganium"));
3605
+ const singlesEnforcement = !["judgment", "lightscreen", "quiverdance", "reflect", "sleeptalk", "toxic"].includes(moveid) && (move.category !== "Status" || // should allow Meganium to cull a recovery move for the sake of STAB
3606
+ !(move.flags.heal && species.id !== "meganium"));
3548
3607
  if (!cull && !move.damage && !isSetup && !move.weather && !move.stallingMove && (isDoubles || singlesEnforcement) && (!counter.setupType || counter.setupType === "Mixed" || move.category !== counter.setupType && move.category !== "Status" || counter.get(counter.setupType) + counter.get("Status") > 3 && !counter.get("hazards")) && (move.category === "Status" || !types.has(move.type) || move.basePower && move.basePower < 40 && !move.multihit)) {
3549
3608
  if (!counter.get("stab") && !moves.has("nightshade") && !moves.has("seismictoss") && (species.types.length > 1 || species.types[0] !== "Normal" && species.types[0] !== "Psychic" || !moves.has("icebeam") || species.baseStats.spa >= species.baseStats.spd) || moves.has("suckerpunch") && !abilities.has("Contrary") && counter.get("stab") < species.types.length && species.id !== "honchkrow" || ["recover", "roost", "slackoff", "softboiled"].some((m) => movePool.includes(m)) && counter.get("Status") && !counter.setupType && ["healingwish", "switcheroo", "trick", "trickroom"].every((m) => !moves.has(m)) || (movePool.includes("milkdrink") || movePool.includes("shoreup") || movePool.includes("moonlight") && types.size < 2 || movePool.includes("stickyweb") && !counter.setupType && !teamDetails.stickyWeb || movePool.includes("quiverdance") && ["defog", "uturn", "stickyweb"].every((m) => !moves.has(m)) && counter.get("Special") < 4) || isLead && movePool.includes("stealthrock") && counter.get("Status") && !counter.setupType && !counter.get("speedsetup") && !moves.has("substitute") || species.requiredMove && movePool.includes(_sim.toID.call(void 0, species.requiredMove)) || !counter.get("Normal") && (abilities.has("Aerilate") || abilities.has("Pixilate") || abilities.has("Refrigerate") && !moves.has("blizzard"))) {
3550
3609
  cull = true;
@@ -3701,12 +3760,14 @@ var RandomGen7Teams = class extends RandomGen8Teams {
3701
3760
  } else if (!isDoubles) {
3702
3761
  const levelScale = { uber: 76, ou: 80, uu: 82, ru: 84, nu: 86, pu: 88 };
3703
3762
  const customScale = {
3763
+ // Banned Ability
3704
3764
  Dugtrio: 82,
3705
3765
  Gothitelle: 82,
3706
3766
  Pelipper: 84,
3707
3767
  Politoed: 84,
3708
3768
  Torkoal: 84,
3709
3769
  Wobbuffet: 82,
3770
+ // Holistic judgement
3710
3771
  "Castform-Rainy": 100,
3711
3772
  "Castform-Snowy": 100,
3712
3773
  "Castform-Sunny": 100,
@@ -4737,7 +4798,8 @@ var RandomGen6Teams = class extends RandomGen7Teams {
4737
4798
  case "extremespeed":
4738
4799
  return { cull: counter.setupType !== "Physical" && moves.has("vacuumwave") };
4739
4800
  case "hiddenpower":
4740
- return { cull: moves.has("rest") || !counter.get("stab") && counter.damagingMoves.size < 2 || counter.setupType === "Special" && types.has("Fairy") && movePool.includes("moonblast") };
4801
+ return { cull: moves.has("rest") || !counter.get("stab") && counter.damagingMoves.size < 2 || // Force Moonblast on Special-setup Fairies
4802
+ counter.setupType === "Special" && types.has("Fairy") && movePool.includes("moonblast") };
4741
4803
  case "hypervoice":
4742
4804
  return { cull: moves.has("blizzard") || moves.has("return") };
4743
4805
  case "judgment":
@@ -5289,11 +5351,13 @@ var RandomGen6Teams = class extends RandomGen7Teams {
5289
5351
  pu: 88
5290
5352
  };
5291
5353
  const customScale = {
5354
+ // Banned Ability
5292
5355
  Dugtrio: 82,
5293
5356
  Gothitelle: 82,
5294
5357
  Ninetales: 84,
5295
5358
  Politoed: 84,
5296
5359
  Wobbuffet: 82,
5360
+ // Holistic judgement
5297
5361
  "Castform-Rainy": 100,
5298
5362
  "Castform-Snowy": 100,
5299
5363
  "Castform-Sunny": 100,
@@ -5786,7 +5850,8 @@ var RandomGen5Teams = class extends RandomGen6Teams {
5786
5850
  const gliscorCase = species.id === "gliscor" && moves.has("protect");
5787
5851
  return { cull: ["leechseed", "rest", "wish"].some((m) => moves.has(m)) || gliscorCase };
5788
5852
  case "substitute":
5789
- return { cull: moves.has("doubleedge") && !abilities.has("rockhead") || ["pursuit", "rest", "superpower", "uturn", "voltswitch"].some((m) => moves.has(m)) || moves.has("acrobatics") && moves.has("earthquake") };
5853
+ return { cull: moves.has("doubleedge") && !abilities.has("rockhead") || ["pursuit", "rest", "superpower", "uturn", "voltswitch"].some((m) => moves.has(m)) || // Sceptile wants Swords Dance
5854
+ moves.has("acrobatics") && moves.has("earthquake") };
5790
5855
  case "thunderwave":
5791
5856
  return { cull: !!counter.setupType || !!counter.get("speedsetup") || hasRestTalk || moves.has("discharge") || moves.has("trickroom") };
5792
5857
  case "willowisp":
@@ -6837,7 +6902,8 @@ var RandomGen4Teams = class extends RandomGen5Teams {
6837
6902
  }
6838
6903
  }
6839
6904
  if (counter.setupType && !isSetup && move.category !== counter.setupType && counter.get(counter.setupType) < 2 && !moves.has("batonpass")) {
6840
- if (moveid !== "rest" && moveid !== "sleeptalk" && !(recoveryMoves.includes(moveid) && (moves.has("healbell") || moves.has("refresh"))) && !((moveid === "healbell" || moveid === "refresh") && Array.from(moves).some((id) => recoveryMoves.includes(id))) && (move.category !== "Status" || counter.get(counter.setupType) + counter.get("Status") > 3 && counter.get("physicalsetup") + counter.get("specialsetup") < 2)) {
6905
+ if (moveid !== "rest" && moveid !== "sleeptalk" && !(recoveryMoves.includes(moveid) && (moves.has("healbell") || moves.has("refresh"))) && !((moveid === "healbell" || moveid === "refresh") && Array.from(moves).some((id) => recoveryMoves.includes(id))) && // Reject Status moves only if there is nothing else to reject
6906
+ (move.category !== "Status" || counter.get(counter.setupType) + counter.get("Status") > 3 && counter.get("physicalsetup") + counter.get("specialsetup") < 2)) {
6841
6907
  cull = true;
6842
6908
  }
6843
6909
  }
@@ -6860,9 +6926,14 @@ var RandomGen4Teams = class extends RandomGen5Teams {
6860
6926
  teamDetails
6861
6927
  );
6862
6928
  };
6863
- const moveIsRejectable = !move.weather && !move.damage && (move.category !== "Status" || !move.flags.heal) && (move.category === "Status" || !types.has(move.type) || move.basePower && move.basePower < 40 && !move.multihit) && !["judgment", "lightscreen", "reflect", "sleeptalk"].includes(moveid) && (counter.get("physicalsetup") + counter.get("specialsetup") < 2 && (!counter.setupType || counter.setupType === "Mixed" || move.category !== counter.setupType && move.category !== "Status" || counter.get(counter.setupType) + counter.get("Status") > 3));
6929
+ const moveIsRejectable = !move.weather && !move.damage && (move.category !== "Status" || !move.flags.heal) && (move.category === "Status" || !types.has(move.type) || move.basePower && move.basePower < 40 && !move.multihit) && // These moves cannot be rejected in favor of a forced move
6930
+ !["judgment", "lightscreen", "reflect", "sleeptalk"].includes(moveid) && // Setup-supported moves should only be rejected under specific circumstances
6931
+ (counter.get("physicalsetup") + counter.get("specialsetup") < 2 && (!counter.setupType || counter.setupType === "Mixed" || move.category !== counter.setupType && move.category !== "Status" || counter.get(counter.setupType) + counter.get("Status") > 3));
6864
6932
  if (!cull && !isSetup && moveIsRejectable) {
6865
- const canRollForcedMoves = movePool.includes("spore") || !Array.from(moves).some((id) => recoveryMoves.includes(id)) && (movePool.includes("softboiled") && !moves.has("explosion") || species.baseSpecies === "Arceus" && movePool.includes("recover"));
6933
+ const canRollForcedMoves = (
6934
+ // These moves should always be rolled
6935
+ movePool.includes("spore") || !Array.from(moves).some((id) => recoveryMoves.includes(id)) && (movePool.includes("softboiled") && !moves.has("explosion") || species.baseSpecies === "Arceus" && movePool.includes("recover"))
6936
+ );
6866
6937
  const requiresStab = !counter.get("stab") && !counter.get("damage") && (species.types.length > 1 || species.types[0] !== "Normal" && species.types[0] !== "Psychic" || !moves.has("icebeam") || species.baseStats.spa >= species.baseStats.spd);
6867
6938
  if (canRollForcedMoves || requiresStab || species.requiredMove && movePool.includes(_sim.toID.call(void 0, species.requiredMove)) || counter.get("defensesetup") && !counter.get("recovery") && !moves.has("rest")) {
6868
6939
  cull = true;
@@ -7044,6 +7115,7 @@ var RandomGen3Teams = class extends RandomGen4Teams {
7044
7115
  Psychic: (movePool, moves, abilities, types, counter, species) => types.has("Psychic") && (movePool.includes("psychic") || movePool.includes("psychoboost")) && species.baseStats.spa >= 100,
7045
7116
  Rock: (movePool, moves, abilities, types, counter, species) => !counter.get("Rock") && species.baseStats.atk >= 100,
7046
7117
  Water: (movePool, moves, abilities, types, counter, species) => !counter.get("Water") && counter.setupType !== "Physical" && species.baseStats.spa >= 60,
7118
+ // If the Pokémon has this move, the other move will be forced
7047
7119
  protect: (movePool) => movePool.includes("wish"),
7048
7120
  sunnyday: (movePool) => movePool.includes("solarbeam"),
7049
7121
  sleeptalk: (movePool) => movePool.includes("rest")
@@ -7097,7 +7169,8 @@ var RandomGen3Teams = class extends RandomGen4Teams {
7097
7169
  case "sunnyday":
7098
7170
  return { cull: counter.damagingMoves.size < 2 || moves.has("rest") };
7099
7171
  case "focuspunch":
7100
- return { cull: counter.damagingMoves.size < 2 || moves.has("rest") || counter.setupType && !moves.has("spore") || !moves.has("substitute") && (counter.get("Physical") < 4 || moves.has("fakeout")) || species.id === "breloom" && (moves.has("machpunch") || moves.has("skyuppercut")) };
7172
+ return { cull: counter.damagingMoves.size < 2 || moves.has("rest") || counter.setupType && !moves.has("spore") || !moves.has("substitute") && (counter.get("Physical") < 4 || moves.has("fakeout")) || // Breloom likes to have coverage
7173
+ species.id === "breloom" && (moves.has("machpunch") || moves.has("skyuppercut")) };
7101
7174
  case "moonlight":
7102
7175
  return { cull: moves.has("wish") || moves.has("protect") };
7103
7176
  case "perishsong":
@@ -7201,7 +7274,8 @@ var RandomGen3Teams = class extends RandomGen4Teams {
7201
7274
  case "gigadrain":
7202
7275
  return { cull: moves.has("morningsun") || moves.has("toxic") };
7203
7276
  case "hiddenpower":
7204
- const stabCondition = types.has(move.type) && counter.get(move.type) > 1 && (moves.has("substitute") && !counter.setupType && !moves.has("toxic") || species.id !== "meganium" && moves.has("toxic") && !moves.has("substitute") || restTalk);
7277
+ const stabCondition = types.has(move.type) && counter.get(move.type) > 1 && (moves.has("substitute") && !counter.setupType && !moves.has("toxic") || // This otherwise causes STABless meganium
7278
+ species.id !== "meganium" && moves.has("toxic") && !moves.has("substitute") || restTalk);
7205
7279
  return { cull: stabCondition || move.type === "Grass" && moves.has("sunnyday") && moves.has("solarbeam") };
7206
7280
  case "brickbreak":
7207
7281
  case "crosschop":
@@ -7332,8 +7406,12 @@ var RandomGen3Teams = class extends RandomGen4Teams {
7332
7406
  if (counter.setupType === "Physical" && move.category === "Special" && !types.has(move.type) && move.type !== "Fire" || counter.setupType === "Special" && move.category === "Physical" && moveid !== "superpower") {
7333
7407
  cull = true;
7334
7408
  }
7335
- const moveIsRejectable = !move.weather && (move.category !== "Status" || !move.flags.heal) && (counter.setupType || !move.stallingMove) && !["batonpass", "sleeptalk", "solarbeam", "substitute", "sunnyday"].includes(moveid) && (move.category === "Status" || !types.has(move.type) || move.basePower && move.basePower < 40 && !move.multihit);
7336
- const requiresStab = !counter.get("stab") && !moves.has("seismictoss") && !moves.has("nightshade") && species.id !== "castform" && species.id !== "umbreon" && !(moves.has("psychic") && types.has("Flying")) && !(types.has("Ghost") && species.baseStats.spa > species.baseStats.atk) && !(counter.setupType === "Special" && (species.id === "lugia" || types.has("Normal") && species.types.length < 2)) && !(counter.setupType === "Physical" && (types.has("Water") && species.types.length < 2 || types.has("Dark"))) && counter.get("physicalpool") + counter.get("specialpool") > 0;
7409
+ const moveIsRejectable = !move.weather && (move.category !== "Status" || !move.flags.heal) && (counter.setupType || !move.stallingMove) && // These moves cannot be rejected in favor of a forced move
7410
+ !["batonpass", "sleeptalk", "solarbeam", "substitute", "sunnyday"].includes(moveid) && (move.category === "Status" || !types.has(move.type) || move.basePower && move.basePower < 40 && !move.multihit);
7411
+ const requiresStab = !counter.get("stab") && !moves.has("seismictoss") && !moves.has("nightshade") && species.id !== "castform" && species.id !== "umbreon" && // If a Flying-type has Psychic, it doesn't need STAB
7412
+ !(moves.has("psychic") && types.has("Flying")) && !(types.has("Ghost") && species.baseStats.spa > species.baseStats.atk) && !// With Calm Mind, Lugia and pure Normal-types are fine without STAB
7413
+ (counter.setupType === "Special" && (species.id === "lugia" || types.has("Normal") && species.types.length < 2)) && !// With Swords Dance, Dark-types and pure Water-types are fine without STAB
7414
+ (counter.setupType === "Physical" && (types.has("Water") && species.types.length < 2 || types.has("Dark"))) && counter.get("physicalpool") + counter.get("specialpool") > 0;
7337
7415
  const runEnforcementChecker = (checkerName) => {
7338
7416
  if (!this.moveEnforcementCheckers[checkerName])
7339
7417
  return false;
@@ -7732,9 +7810,15 @@ var RandomGen2Teams = class extends RandomGen3Teams {
7732
7810
  if (counter.setupType === "Physical" && move.category === "Special" && !counter.get("Physical")) {
7733
7811
  cull = true;
7734
7812
  }
7735
- const moveIsRejectable = (move.category !== "Status" || !move.flags.heal) && !["batonpass", "sleeptalk", "spikes", "sunnyday"].includes(move.id) && (move.category === "Status" || !types.has(move.type) || move.basePower && move.basePower < 40);
7813
+ const moveIsRejectable = (move.category !== "Status" || !move.flags.heal) && // These moves cannot be rejected in favor of a forced move
7814
+ !["batonpass", "sleeptalk", "spikes", "sunnyday"].includes(move.id) && (move.category === "Status" || !types.has(move.type) || move.basePower && move.basePower < 40);
7736
7815
  if (!cull && !isSetup && moveIsRejectable && (counter.setupType || !move.stallingMove)) {
7737
- if (!counter.get("stab") && !counter.get("damage") && !types.has("Ghost") && counter.get("physicalpool") + counter.get("specialpool") > 0 || (movePool.includes("megahorn") || movePool.includes("softboiled") && moves.has("present")) || (moves.has("rest") && movePool.includes("sleeptalk") || moves.has("sleeptalk") && movePool.includes("rest")) || (moves.has("sunnyday") && movePool.includes("solarbeam") || moves.has("solarbeam") && movePool.includes("sunnyday")) || ["milkdrink", "recover", "spore"].some((m) => movePool.includes(m))) {
7816
+ if (
7817
+ // Pokemon should usually have at least one STAB move
7818
+ !counter.get("stab") && !counter.get("damage") && !types.has("Ghost") && counter.get("physicalpool") + counter.get("specialpool") > 0 || (movePool.includes("megahorn") || movePool.includes("softboiled") && moves.has("present")) || // Rest + Sleep Talk should be selected together
7819
+ (moves.has("rest") && movePool.includes("sleeptalk") || moves.has("sleeptalk") && movePool.includes("rest")) || // Sunny Day + Solar Beam should be selected together
7820
+ (moves.has("sunnyday") && movePool.includes("solarbeam") || moves.has("solarbeam") && movePool.includes("sunnyday")) || ["milkdrink", "recover", "spore"].some((m) => movePool.includes(m))
7821
+ ) {
7738
7822
  cull = true;
7739
7823
  } else {
7740
7824
  for (const type of types) {
@@ -7812,6 +7896,7 @@ var RandomGen2Teams = class extends RandomGen3Teams {
7812
7896
  ivs,
7813
7897
  item: this.getItem("None", types, moves, counter, species),
7814
7898
  level,
7899
+ // No shiny chance because Gen 2 shinies have bad IVs
7815
7900
  shiny: false,
7816
7901
  gender: species.gender ? species.gender : "M"
7817
7902
  };
@@ -7820,6 +7905,7 @@ var RandomGen2Teams = class extends RandomGen3Teams {
7820
7905
 
7821
7906
  // src/gen1.ts
7822
7907
  var RandomGen1Teams = class extends RandomGen2Teams {
7908
+ // Challenge Cup or CC teams are basically fully random teams.
7823
7909
  randomCCTeam() {
7824
7910
  this.enforceNoDirectCustomBanlistChanges();
7825
7911
  const team = [];
@@ -7894,6 +7980,7 @@ var RandomGen1Teams = class extends RandomGen2Teams {
7894
7980
  }
7895
7981
  return team;
7896
7982
  }
7983
+ // Random team generation for Gen 1 Random Battles.
7897
7984
  randomTeam() {
7898
7985
  this.enforceNoDirectCustomBanlistChanges();
7899
7986
  const seed = this.prng.seed;
@@ -8012,6 +8099,9 @@ var RandomGen1Teams = class extends RandomGen2Teams {
8012
8099
  }
8013
8100
  return { cull: false };
8014
8101
  }
8102
+ /**
8103
+ * Random set generation for Gen 1 Random Battles.
8104
+ */
8015
8105
  randomSet(species) {
8016
8106
  species = this.dex.species.get(species);
8017
8107
  if (!species.exists)
@@ -8208,6 +8298,7 @@ var RandomGen1Teams = class extends RandomGen2Teams {
8208
8298
  nature: "",
8209
8299
  level,
8210
8300
  shiny: false,
8301
+ // Hacky but the only way to communicate stats/level generation properly
8211
8302
  hc: hackmonsCup[species.id]
8212
8303
  });
8213
8304
  }
@@ -8386,6 +8477,7 @@ function sereneGraceBenefits2(move) {
8386
8477
  }
8387
8478
  var RandomTeams = class {
8388
8479
  constructor(dex, format, prng) {
8480
+ // TODO: Make types for this
8389
8481
  this.randomSets = randomSetsJSON;
8390
8482
  this.randomDoublesSets = randomSetsJSON;
8391
8483
  this.dex = dex;
@@ -8465,6 +8557,10 @@ var RandomTeams = class {
8465
8557
  random(m, n) {
8466
8558
  return this.prng.next(m, n);
8467
8559
  }
8560
+ /**
8561
+ * Remove an element from an unsorted array significantly faster
8562
+ * than .splice
8563
+ */
8468
8564
  fastPop(list, index) {
8469
8565
  const length = list.length;
8470
8566
  if (index < 0 || index >= list.length) {
@@ -8475,6 +8571,10 @@ var RandomTeams = class {
8475
8571
  list.pop();
8476
8572
  return element;
8477
8573
  }
8574
+ /**
8575
+ * Remove a random element from an unsorted array and return it.
8576
+ * Uses the battle's RNG if in a battle.
8577
+ */
8478
8578
  sampleNoReplace(list) {
8479
8579
  const length = list.length;
8480
8580
  if (length === 0)
@@ -8482,6 +8582,11 @@ var RandomTeams = class {
8482
8582
  const index = this.random(length);
8483
8583
  return this.fastPop(list, index);
8484
8584
  }
8585
+ /**
8586
+ * Removes n random elements from an unsorted array and returns them.
8587
+ * If n is less than the array's length, randomly removes and returns all the elements
8588
+ * in the array (so the returned array could have length < n).
8589
+ */
8485
8590
  multipleSamplesNoReplace(list, n) {
8486
8591
  const samples = [];
8487
8592
  while (samples.length < n && list.length) {
@@ -8489,7 +8594,13 @@ var RandomTeams = class {
8489
8594
  }
8490
8595
  return samples;
8491
8596
  }
8597
+ /**
8598
+ * Check if user has directly tried to ban/unban/restrict things in a custom battle.
8599
+ * Doesn't count bans nested inside other formats/rules.
8600
+ */
8492
8601
  hasDirectCustomBanlistChanges() {
8602
+ if (this.format.banlist.length || this.format.restricted.length || this.format.unbanlist.length)
8603
+ return true;
8493
8604
  if (!this.format.customRules)
8494
8605
  return false;
8495
8606
  for (const rule of this.format.customRules) {
@@ -8500,11 +8611,17 @@ var RandomTeams = class {
8500
8611
  }
8501
8612
  return false;
8502
8613
  }
8614
+ /**
8615
+ * Inform user when custom bans are unsupported in a team generator.
8616
+ */
8503
8617
  enforceNoDirectCustomBanlistChanges() {
8504
8618
  if (this.hasDirectCustomBanlistChanges()) {
8505
8619
  throw new Error(`Custom bans are not currently supported in ${this.format.name}.`);
8506
8620
  }
8507
8621
  }
8622
+ /**
8623
+ * Inform user when complex bans are unsupported in a team generator.
8624
+ */
8508
8625
  enforceNoDirectComplexBans() {
8509
8626
  if (!this.format.customRules)
8510
8627
  return false;
@@ -8514,6 +8631,9 @@ var RandomTeams = class {
8514
8631
  }
8515
8632
  }
8516
8633
  }
8634
+ /**
8635
+ * Validate set element pool size is sufficient to support size requirements after simple bans.
8636
+ */
8517
8637
  enforceCustomPoolSizeNoComplexBans(effectTypeName, basicEffectPool, requiredCount, requiredCountExplanation) {
8518
8638
  if (basicEffectPool.length >= requiredCount)
8519
8639
  return;
@@ -8676,6 +8796,7 @@ var RandomTeams = class {
8676
8796
  this.incompatibleMoves(moves, movePool, "bodypress", "mirrorcoat");
8677
8797
  this.incompatibleMoves(moves, movePool, "toxic", "clearsmog");
8678
8798
  }
8799
+ // Checks for and removes incompatible moves, starting with the first move in movesA.
8679
8800
  incompatibleMoves(moves, movePool, movesA, movesB) {
8680
8801
  const moveArrayA = Array.isArray(movesA) ? movesA : [movesA];
8681
8802
  const moveArrayB = Array.isArray(movesB) ? movesB : [movesB];
@@ -8702,6 +8823,7 @@ var RandomTeams = class {
8702
8823
  }
8703
8824
  }
8704
8825
  }
8826
+ // Adds a move to the moveset, returns the MoveCounter
8705
8827
  addMove(move, moves, types, abilities, teamDetails, species, isLead, isDoubles, movePool, teraType, role) {
8706
8828
  moves.add(move);
8707
8829
  this.fastPop(movePool, movePool.indexOf(move));
@@ -8709,6 +8831,7 @@ var RandomTeams = class {
8709
8831
  this.cullMovePool(types, moves, abilities, counter, movePool, teamDetails, species, isLead, isDoubles, teraType, role);
8710
8832
  return counter;
8711
8833
  }
8834
+ // Returns the type of a given move for STAB/coverage enforcement purposes
8712
8835
  getMoveType(move, species, abilities, teraType) {
8713
8836
  if (move.id === "terablast")
8714
8837
  return teraType;
@@ -8735,6 +8858,7 @@ var RandomTeams = class {
8735
8858
  }
8736
8859
  return moveType;
8737
8860
  }
8861
+ // Generate random moveset for a given species, role, tera type.
8738
8862
  randomMoveset(types, abilities, teamDetails, species, isLead, isDoubles, movePool, teraType, role) {
8739
8863
  const moves = /* @__PURE__ */ new Set();
8740
8864
  let counter = this.queryMoves(moves, species, teraType, abilities);
@@ -9398,6 +9522,8 @@ var RandomTeams = class {
9398
9522
  if (this.dex.getEffectiveness("Rock", species) >= 2)
9399
9523
  return "Heavy-Duty Boots";
9400
9524
  }
9525
+ /** Item generation specific to Random Doubles */
9526
+ // This will be changed and used later, once doubles is actually coming out.
9401
9527
  getDoublesItem(ability, types, moves, counter, teamDetails, species, teraType, role) {
9402
9528
  const defensiveStatTotal = species.baseStats.hp + species.baseStats.def + species.baseStats.spd;
9403
9529
  if (["dragonenergy", "eruption", "waterspout"].some((m) => moves.has(m)) && counter.damagingMoves.size >= 4)
@@ -9652,6 +9778,7 @@ var RandomTeams = class {
9652
9778
  }
9653
9779
  return [pokemonPool, baseSpeciesPool];
9654
9780
  }
9781
+ // Doubles sets are the same as singles for now
9655
9782
  randomTeam() {
9656
9783
  this.enforceNoDirectCustomBanlistChanges();
9657
9784
  const seed = this.prng.seed;