@pkmn/sim 0.6.1 → 0.6.3

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 (51) hide show
  1. package/build/config/formats.js +161 -166
  2. package/build/config/formats.js.map +1 -1
  3. package/build/data/aliases.js +2 -2
  4. package/build/data/aliases.js.map +1 -1
  5. package/build/data/formats-data.js +111 -115
  6. package/build/data/formats-data.js.map +1 -1
  7. package/build/data/items.js +7 -1
  8. package/build/data/items.js.map +1 -1
  9. package/build/data/learnsets.js +3 -2
  10. package/build/data/learnsets.js.map +1 -1
  11. package/build/data/mods/gen1/moves.js +0 -10
  12. package/build/data/mods/gen1/moves.js.map +1 -1
  13. package/build/data/mods/gen1/scripts.js +87 -52
  14. package/build/data/mods/gen1/scripts.js.map +1 -1
  15. package/build/data/mods/gen2/moves.js +0 -1
  16. package/build/data/mods/gen2/moves.js.map +1 -1
  17. package/build/data/mods/gen2/scripts.js +1 -1
  18. package/build/data/moves.js +44 -25
  19. package/build/data/moves.js.map +1 -1
  20. package/build/data/pokedex.js +1 -1
  21. package/build/data/rulesets.js +58 -0
  22. package/build/data/rulesets.js.map +1 -1
  23. package/build/sim/battle.js +8 -4
  24. package/build/sim/battle.js.map +1 -1
  25. package/build/sim/dex-conditions.d.ts +1 -0
  26. package/build/sim/dex-conditions.js.map +1 -1
  27. package/build/sim/field.js +1 -0
  28. package/build/sim/field.js.map +1 -1
  29. package/build/sim/pokemon.js +2 -10
  30. package/build/sim/pokemon.js.map +1 -1
  31. package/build/sim/team-validator.d.ts +2 -2
  32. package/build/sim/team-validator.js +19 -14
  33. package/build/sim/team-validator.js.map +1 -1
  34. package/config/formats.ts +168 -169
  35. package/data/aliases.ts +2 -2
  36. package/data/formats-data.ts +111 -115
  37. package/data/items.ts +7 -1
  38. package/data/learnsets.ts +3 -2
  39. package/data/mods/gen1/moves.ts +0 -10
  40. package/data/mods/gen1/scripts.ts +85 -51
  41. package/data/mods/gen2/moves.ts +0 -1
  42. package/data/mods/gen2/scripts.ts +1 -1
  43. package/data/moves.ts +43 -23
  44. package/data/pokedex.ts +1 -1
  45. package/data/rulesets.ts +59 -0
  46. package/package.json +1 -1
  47. package/sim/battle.ts +6 -4
  48. package/sim/dex-conditions.ts +1 -0
  49. package/sim/field.ts +1 -0
  50. package/sim/pokemon.ts +1 -8
  51. package/sim/team-validator.ts +27 -15
package/data/items.ts CHANGED
@@ -4576,7 +4576,13 @@ export const Items: {[itemid: string]: ItemData} = {
4576
4576
  fling: {
4577
4577
  basePower: 100,
4578
4578
  },
4579
- onUpdate(pokemon) {
4579
+ onStart(pokemon) {
4580
+ if (!pokemon.ignoringItem() && this.field.getPseudoWeather('trickroom')) {
4581
+ pokemon.useItem();
4582
+ }
4583
+ },
4584
+ onAnyPseudoWeatherStart() {
4585
+ const pokemon = this.effectState.target;
4580
4586
  if (this.field.getPseudoWeather('trickroom')) {
4581
4587
  pokemon.useItem();
4582
4588
  }
package/data/learnsets.ts CHANGED
@@ -79223,7 +79223,7 @@ export const Learnsets: {[speciesid: string]: LearnsetData} = {
79223
79223
  blizzard: ["8M"],
79224
79224
  bodyslam: ["8M"],
79225
79225
  boomburst: ["8L75"],
79226
- calmmind: ["8M", "8S0"],
79226
+ calmmind: ["8M"],
79227
79227
  charm: ["8M"],
79228
79228
  crunch: ["8M", "8L35"],
79229
79229
  darkpulse: ["8M", "8S0"],
@@ -79271,6 +79271,7 @@ export const Learnsets: {[speciesid: string]: LearnsetData} = {
79271
79271
  stompingtantrum: ["8M", "8L30"],
79272
79272
  substitute: ["8M"],
79273
79273
  sunnyday: ["8M"],
79274
+ switcheroo: ["8S0"],
79274
79275
  taunt: ["8M"],
79275
79276
  thief: ["8M"],
79276
79277
  thunder: ["8M"],
@@ -79283,7 +79284,7 @@ export const Learnsets: {[speciesid: string]: LearnsetData} = {
79283
79284
  wideguard: ["8L50"],
79284
79285
  },
79285
79286
  eventData: [
79286
- {generation: 8, level: 50, moves: ["recover", "calmmind", "darkpulse", "belch"], pokeball: "cherishball"},
79287
+ {generation: 8, level: 50, moves: ["recover", "switcheroo", "darkpulse", "belch"], pokeball: "cherishball"},
79287
79288
  ],
79288
79289
  },
79289
79290
  syclant: {
@@ -884,16 +884,6 @@ export const Moves: {[k: string]: ModdedMoveData} = {
884
884
  status: 'par',
885
885
  },
886
886
  },
887
- thunderwave: {
888
- inherit: true,
889
- accuracy: 100,
890
- onTryHit(target) {
891
- if (target.hasType('Ground')) {
892
- this.add('-immune', target);
893
- return null;
894
- }
895
- },
896
- },
897
887
  triattack: {
898
888
  inherit: true,
899
889
  onHit() {},
@@ -18,7 +18,7 @@ export const Scripts: ModdedBattleScriptsData = {
18
18
  getStat(statName, unmodified) {
19
19
  // @ts-ignore - type checking prevents 'hp' from being passed, but we're paranoid
20
20
  if (statName === 'hp') throw new Error("Please read `maxhp` directly");
21
- if (unmodified) return this.storedStats[statName];
21
+ if (unmodified) return this.baseStoredStats[statName];
22
22
  return this.modifiedStats![statName];
23
23
  },
24
24
  // Gen 1 function to apply a stat modification that is only active until the stat is recalculated or mon switched.
@@ -46,9 +46,7 @@ export const Scripts: ModdedBattleScriptsData = {
46
46
  changed = true;
47
47
  // Recalculate the modified stat
48
48
  if (i === 'evasion' || i === 'accuracy') continue;
49
- let stat = this.species.baseStats[i];
50
- stat = Math.floor(Math.floor(2 * stat + this.set.ivs[i] + Math.floor(this.set.evs[i] / 4)) * this.level / 100 + 5);
51
- this.modifiedStats![i] = this.storedStats[i] = Math.floor(stat);
49
+ this.modifiedStats![i] = this.storedStats[i];
52
50
  if (this.boosts[i] >= 0) {
53
51
  this.modifyStat!(i, [1, 1.5, 2, 2.5, 3, 3.5, 4][this.boosts[i]]);
54
52
  } else {
@@ -63,16 +61,13 @@ export const Scripts: ModdedBattleScriptsData = {
63
61
  this.boosts[i] = 0;
64
62
  // Recalculate the modified stat
65
63
  if (i === 'evasion' || i === 'accuracy') continue;
66
- let stat = this.species.baseStats[i];
67
- stat = Math.floor(Math.floor(2 * stat + this.set.ivs[i] + Math.floor(this.set.evs[i] / 4)) * this.level / 100 + 5);
68
- this.modifiedStats![i] = this.storedStats[i] = Math.floor(stat);
64
+ this.modifiedStats![i] = this.storedStats[i];
69
65
  }
70
66
  },
71
67
  },
72
68
  actions: {
73
69
  // This function is the main one when running a move.
74
- // It deals with the beforeMove and AfterMoveSelf events.
75
- // This leads with partial trapping moves shennanigans after the move has been used.
70
+ // It deals with the beforeMove event.
76
71
  // It also deals with how PP reduction works on gen 1.
77
72
  runMove(moveOrMoveName, pokemon, targetLoc, sourceEffect) {
78
73
  const target = this.battle.getTarget(pokemon, moveOrMoveName, targetLoc);
@@ -119,46 +114,79 @@ export const Scripts: ModdedBattleScriptsData = {
119
114
  }
120
115
  }
121
116
  this.useMove(move, pokemon, target, sourceEffect);
122
- this.battle.singleEvent('AfterMove', move, null, pokemon, target, move);
117
+ },
118
+ // This function deals with AfterMoveSelf events.
119
+ // This leads with partial trapping moves shenanigans after the move has been used.
120
+ useMove(moveOrMoveName, pokemon, target, sourceEffect) {
121
+ const moveResult = this.useMoveInner(moveOrMoveName, pokemon, target, sourceEffect);
123
122
 
124
- // If target fainted
125
- if (target && target.hp <= 0) {
126
- // We remove recharge
127
- if (pokemon.volatiles['mustrecharge']) pokemon.removeVolatile('mustrecharge');
128
- delete pokemon.volatiles['partialtrappinglock'];
129
- // We remove screens
130
- target.side.removeSideCondition('reflect');
131
- target.side.removeSideCondition('lightscreen');
132
- pokemon.removeVolatile('twoturnmove');
133
- } else if (pokemon.hp) {
134
- this.battle.runEvent('AfterMoveSelf', pokemon, target, move);
123
+ if (!sourceEffect && this.battle.effect.id) sourceEffect = this.battle.effect;
124
+ const baseMove = this.battle.dex.moves.get(moveOrMoveName);
125
+ let move = this.battle.dex.getActiveMove(baseMove);
126
+ if (target === undefined) target = this.battle.getRandomTarget(pokemon, move);
127
+ if (move.target === 'self') {
128
+ target = pokemon;
135
129
  }
136
- if (pokemon.volatiles['mustrecharge']) this.battle.add('-mustrecharge', pokemon);
137
-
138
- // For partial trapping moves, we are saving the target
139
- if (move.volatileStatus === 'partiallytrapped' && target && target.hp > 0) {
140
- // Let's check if the lock exists
141
- if (pokemon.volatiles['partialtrappinglock'] && target.volatiles['partiallytrapped']) {
142
- // Here the partialtrappinglock volatile has been already applied
143
- const sourceVolatile = pokemon.volatiles['partialtrappinglock'];
144
- const targetVolatile = target.volatiles['partiallytrapped'];
145
- if (!sourceVolatile.locked) {
146
- // If it's the first hit, we save the target
147
- sourceVolatile.locked = target;
148
- } else if (target !== pokemon && target !== sourceVolatile.locked) {
149
- // Our target switched out! Re-roll the duration, damage, and accuracy.
150
- const duration = this.battle.sample([2, 2, 2, 3, 3, 3, 4, 5]);
151
- sourceVolatile.duration = duration;
152
- sourceVolatile.locked = target;
153
- // Duration reset thus partially trapped at 2 always.
154
- targetVolatile.duration = 2;
130
+ if (sourceEffect) move.sourceEffect = sourceEffect.id;
131
+
132
+ this.battle.singleEvent('ModifyMove', move, null, pokemon, target, move, move);
133
+ if (baseMove.target !== move.target) {
134
+ // Target changed in ModifyMove, so we must adjust it here
135
+ target = this.battle.getRandomTarget(pokemon, move);
136
+ }
137
+ move = this.battle.runEvent('ModifyMove', pokemon, target, move, move);
138
+ if (baseMove.target !== move.target) {
139
+ // Check again, this shouldn't ever happen on Gen 1.
140
+ target = this.battle.getRandomTarget(pokemon, move);
141
+ }
142
+
143
+ if (move.id !== 'metronome') {
144
+ if (move.id !== 'mirrormove' ||
145
+ (!pokemon.side.foe.active[0]?.lastMove || pokemon.side.foe.active[0].lastMove?.id === 'mirrormove')) {
146
+ // The move is our 'final' move (a failed Mirror Move, or any move that isn't Metronome or Mirror Move).
147
+ this.battle.singleEvent('AfterMove', move, null, pokemon, target, move);
148
+
149
+ // If target fainted
150
+ if (target && target.hp <= 0) {
151
+ // We remove recharge
152
+ if (pokemon.volatiles['mustrecharge']) pokemon.removeVolatile('mustrecharge');
153
+ delete pokemon.volatiles['partialtrappinglock'];
154
+ // We remove screens
155
+ target.side.removeSideCondition('reflect');
156
+ target.side.removeSideCondition('lightscreen');
157
+ pokemon.removeVolatile('twoturnmove');
158
+ } else if (pokemon.hp) {
159
+ this.battle.runEvent('AfterMoveSelf', pokemon, target, move);
155
160
  }
156
- } // If we move to here, the move failed and there's no partial trapping lock.
161
+ if (pokemon.volatiles['mustrecharge']) this.battle.add('-mustrecharge', pokemon);
162
+
163
+ // For partial trapping moves, we are saving the target
164
+ if (move.volatileStatus === 'partiallytrapped' && target && target.hp > 0) {
165
+ // Let's check if the lock exists
166
+ if (pokemon.volatiles['partialtrappinglock'] && target.volatiles['partiallytrapped']) {
167
+ // Here the partialtrappinglock volatile has been already applied
168
+ const sourceVolatile = pokemon.volatiles['partialtrappinglock'];
169
+ const targetVolatile = target.volatiles['partiallytrapped'];
170
+ if (!sourceVolatile.locked) {
171
+ // If it's the first hit, we save the target
172
+ sourceVolatile.locked = target;
173
+ } else if (target !== pokemon && target !== sourceVolatile.locked) {
174
+ // Our target switched out! Re-roll the duration, damage, and accuracy.
175
+ const duration = this.battle.sample([2, 2, 2, 3, 3, 3, 4, 5]);
176
+ sourceVolatile.duration = duration;
177
+ sourceVolatile.locked = target;
178
+ // Duration reset thus partially trapped at 2 always.
179
+ targetVolatile.duration = 2;
180
+ }
181
+ } // If we move to here, the move failed and there's no partial trapping lock.
182
+ }
183
+ }
157
184
  }
185
+ return moveResult;
158
186
  },
159
187
  // This is the function that actually uses the move, running ModifyMove events.
160
188
  // It uses the move and then deals with the effects after the move.
161
- useMove(moveOrMoveName, pokemon, target, sourceEffect) {
189
+ useMoveInner(moveOrMoveName, pokemon, target, sourceEffect) {
162
190
  if (!sourceEffect && this.battle.effect.id) sourceEffect = this.battle.effect;
163
191
  const baseMove = this.battle.dex.moves.get(moveOrMoveName);
164
192
  let move = this.battle.dex.getActiveMove(baseMove);
@@ -334,6 +362,7 @@ export const Scripts: ModdedBattleScriptsData = {
334
362
  let i: number;
335
363
  for (i = 0; i < hits && target.hp && pokemon.hp; i++) {
336
364
  move.hit = i + 1;
365
+ if (move.hit === hits) move.lastHit = true;
337
366
  moveDamage = this.moveHit(target, pokemon, move);
338
367
  if (moveDamage === false) break;
339
368
  damage = (moveDamage || 0);
@@ -396,6 +425,7 @@ export const Scripts: ModdedBattleScriptsData = {
396
425
  // We get the sub to the target to see if it existed
397
426
  const targetSub = (target) ? target.volatiles['substitute'] : false;
398
427
  const targetHadSub = (targetSub !== null && targetSub !== false && (typeof targetSub !== 'undefined'));
428
+ let targetHasSub: boolean;
399
429
 
400
430
  if (target) {
401
431
  hitResult = this.battle.singleEvent('TryHit', moveData, {}, target, pokemon, move);
@@ -428,6 +458,7 @@ export const Scripts: ModdedBattleScriptsData = {
428
458
  }
429
459
 
430
460
  if (hitResult === 0) {
461
+ targetHasSub = !!(target?.volatiles['substitute']);
431
462
  target = null;
432
463
  } else if (!hitResult) {
433
464
  if (hitResult === false) this.battle.add('-fail', target);
@@ -557,7 +588,7 @@ export const Scripts: ModdedBattleScriptsData = {
557
588
  return false;
558
589
  }
559
590
  }
560
- const targetHasSub = !!(target?.volatiles['substitute']);
591
+ targetHasSub ??= !!(target?.volatiles['substitute']);
561
592
 
562
593
  // Here's where self effects are applied.
563
594
  const doSelf = (targetHadSub && targetHasSub) || !targetHadSub;
@@ -573,13 +604,16 @@ export const Scripts: ModdedBattleScriptsData = {
573
604
  // Apply move secondaries.
574
605
  if (moveData.secondaries) {
575
606
  for (const secondary of moveData.secondaries) {
576
- // We check here whether to negate the probable secondary status if it's para, burn, or freeze.
577
- // In the game, this is checked and if true, the random number generator is not called.
578
- // That means that a move that does not share the type of the target can status it.
579
- // If a move that was not fire-type would exist on Gen 1, it could burn a Pokémon.
580
- if (!(secondary.status && ['par', 'brn', 'frz'].includes(secondary.status) && target && target.hasType(move.type))) {
581
- if (secondary.chance === undefined || this.battle.randomChance(Math.ceil(secondary.chance * 256 / 100), 256)) {
582
- this.moveHit(target, pokemon, move, secondary, true, isSelf);
607
+ // Multi-hit moves only roll for status once
608
+ if (!move.multihit || move.lastHit) {
609
+ // We check here whether to negate the probable secondary status if it's para, burn, or freeze.
610
+ // In the game, this is checked and if true, the random number generator is not called.
611
+ // That means that a move that does not share the type of the target can status it.
612
+ // If a move that was not fire-type would exist on Gen 1, it could burn a Pokémon.
613
+ if (!(secondary.status && ['par', 'brn', 'frz'].includes(secondary.status) && target && target.hasType(move.type))) {
614
+ if (secondary.chance === undefined || this.battle.randomChance(Math.ceil(secondary.chance * 256 / 100), 256)) {
615
+ this.moveHit(target, pokemon, move, secondary, true, isSelf);
616
+ }
583
617
  }
584
618
  }
585
619
  }
@@ -798,7 +832,7 @@ export const Scripts: ModdedBattleScriptsData = {
798
832
  // This occurs when damage was either 2 or 3 prior to applying STAB/Type matchup, and target is 4x resistant to the move.
799
833
  if (damage === 0) return damage;
800
834
 
801
- // Apply random factor is damage is greater than 1
835
+ // Apply random factor if damage is greater than 1
802
836
  if (damage > 1) {
803
837
  damage *= this.battle.random(217, 256);
804
838
  damage = Math.floor(damage / 255);
@@ -415,7 +415,6 @@ export const Moves: {[k: string]: ModdedMoveData} = {
415
415
  },
416
416
  mimic: {
417
417
  inherit: true,
418
- accuracy: true,
419
418
  ignoreAccuracy: undefined,
420
419
  ignoreEvasion: undefined,
421
420
  noSketch: true,
@@ -706,7 +706,7 @@ export const Scripts: ModdedBattleScriptsData = {
706
706
  }
707
707
  }
708
708
 
709
- // Apply random factor is damage is greater than 1, except for Flail and Reversal
709
+ // Apply random factor if damage is greater than 1, except for Flail and Reversal
710
710
  if (!move.noDamageVariance && damage > 1) {
711
711
  damage *= this.battle.random(217, 256);
712
712
  damage = Math.floor(damage / 255);
package/data/moves.ts CHANGED
@@ -8707,10 +8707,16 @@ export const Moves: {[moveid: string]: MoveData} = {
8707
8707
  basePower: 30,
8708
8708
  basePowerCallback(pokemon, target, move) {
8709
8709
  let bp = move.basePower;
8710
- if (pokemon.volatiles['iceball'] && pokemon.volatiles['iceball'].hitCount) {
8711
- bp *= Math.pow(2, pokemon.volatiles['iceball'].hitCount);
8710
+ const iceballData = pokemon.volatiles['iceball'];
8711
+ if (iceballData?.hitCount) {
8712
+ bp *= Math.pow(2, iceballData.hitCount);
8713
+ }
8714
+ if (iceballData && pokemon.status !== 'slp') {
8715
+ iceballData.hitCount++;
8716
+ if (iceballData.hitCount < 5) {
8717
+ iceballData.duration = 2;
8718
+ }
8712
8719
  }
8713
- if (pokemon.status !== 'slp') pokemon.addVolatile('iceball');
8714
8720
  if (pokemon.volatiles['defensecurl']) {
8715
8721
  bp *= 2;
8716
8722
  }
@@ -8723,17 +8729,19 @@ export const Moves: {[moveid: string]: MoveData} = {
8723
8729
  pp: 20,
8724
8730
  priority: 0,
8725
8731
  flags: {bullet: 1, contact: 1, protect: 1, mirror: 1},
8732
+ onModifyMove(move, pokemon, target) {
8733
+ if (pokemon.volatiles['iceball'] || pokemon.status === 'slp' || !target) return;
8734
+ pokemon.addVolatile('iceball');
8735
+ // @ts-ignore
8736
+ // TS thinks pokemon.volatiles['iceball'] doesn't exist because of the condition on the return above
8737
+ // but it does exist now because addVolatile created it
8738
+ pokemon.volatiles['iceball'].targetSlot = move.sourceEffect ? pokemon.lastMoveTargetLoc : pokemon.getLocOf(target);
8739
+ },
8726
8740
  condition: {
8727
- duration: 2,
8741
+ duration: 1,
8728
8742
  onLockMove: 'iceball',
8729
8743
  onStart() {
8730
- this.effectState.hitCount = 1;
8731
- },
8732
- onRestart() {
8733
- this.effectState.hitCount++;
8734
- if (this.effectState.hitCount < 5) {
8735
- this.effectState.duration = 2;
8736
- }
8744
+ this.effectState.hitCount = 0;
8737
8745
  },
8738
8746
  onResidual(target) {
8739
8747
  if (target.lastMove && target.lastMove.id === 'struggle') {
@@ -13434,7 +13442,11 @@ export const Moves: {[moveid: string]: MoveData} = {
13434
13442
  priority: 0,
13435
13443
  flags: {protect: 1, reflectable: 1, heal: 1},
13436
13444
  onHit(target, source) {
13437
- if (!target.cureStatus()) return this.NOT_FAIL;
13445
+ if (!target.cureStatus()) {
13446
+ this.add('-fail', source);
13447
+ this.attrLastMove('[still]');
13448
+ return this.NOT_FAIL;
13449
+ }
13438
13450
  this.heal(Math.ceil(source.maxhp * 0.5), source);
13439
13451
  },
13440
13452
  secondary: null,
@@ -14390,10 +14402,16 @@ export const Moves: {[moveid: string]: MoveData} = {
14390
14402
  basePower: 30,
14391
14403
  basePowerCallback(pokemon, target, move) {
14392
14404
  let bp = move.basePower;
14393
- if (pokemon.volatiles['rollout'] && pokemon.volatiles['rollout'].hitCount) {
14394
- bp *= Math.pow(2, pokemon.volatiles['rollout'].hitCount);
14405
+ const rolloutData = pokemon.volatiles['rollout'];
14406
+ if (rolloutData?.hitCount) {
14407
+ bp *= Math.pow(2, rolloutData.hitCount);
14408
+ }
14409
+ if (rolloutData && pokemon.status !== 'slp') {
14410
+ rolloutData.hitCount++;
14411
+ if (rolloutData.hitCount < 5) {
14412
+ rolloutData.duration = 2;
14413
+ }
14395
14414
  }
14396
- if (pokemon.status !== 'slp') pokemon.addVolatile('rollout');
14397
14415
  if (pokemon.volatiles['defensecurl']) {
14398
14416
  bp *= 2;
14399
14417
  }
@@ -14405,17 +14423,19 @@ export const Moves: {[moveid: string]: MoveData} = {
14405
14423
  pp: 20,
14406
14424
  priority: 0,
14407
14425
  flags: {contact: 1, protect: 1, mirror: 1},
14426
+ onModifyMove(move, pokemon, target) {
14427
+ if (pokemon.volatiles['rollout'] || pokemon.status === 'slp' || !target) return;
14428
+ pokemon.addVolatile('rollout');
14429
+ // @ts-ignore
14430
+ // TS thinks pokemon.volatiles['rollout'] doesn't exist because of the condition on the return above
14431
+ // but it does exist now because addVolatile created it
14432
+ pokemon.volatiles['rollout'].targetSlot = move.sourceEffect ? pokemon.lastMoveTargetLoc : pokemon.getLocOf(target);
14433
+ },
14408
14434
  condition: {
14409
- duration: 2,
14435
+ duration: 1,
14410
14436
  onLockMove: 'rollout',
14411
14437
  onStart() {
14412
- this.effectState.hitCount = 1;
14413
- },
14414
- onRestart() {
14415
- this.effectState.hitCount++;
14416
- if (this.effectState.hitCount < 5) {
14417
- this.effectState.duration = 2;
14418
- }
14438
+ this.effectState.hitCount = 0;
14419
14439
  },
14420
14440
  onResidual(target) {
14421
14441
  if (target.lastMove && target.lastMove.id === 'struggle') {
package/data/pokedex.ts CHANGED
@@ -17309,7 +17309,7 @@ export const Pokedex: {[speciesid: string]: SpeciesData} = {
17309
17309
  chromera: {
17310
17310
  num: -60,
17311
17311
  name: "Chromera",
17312
- types: ["Dark", "Poison"],
17312
+ types: ["Dark", "Normal"],
17313
17313
  gender: "N",
17314
17314
  baseStats: {hp: 85, atk: 85, def: 115, spa: 115, spd: 100, spe: 100},
17315
17315
  abilities: {0: "Color Change"},
package/data/rulesets.ts CHANGED
@@ -2097,4 +2097,63 @@ export const Rulesets: {[k: string]: FormatData} = {
2097
2097
  }
2098
2098
  },
2099
2099
  },
2100
+ godlygiftmod: {
2101
+ effectType: 'Rule',
2102
+ name: "Godly Gift Mod",
2103
+ onValidateTeam(team) {
2104
+ const gods = new Set<string>();
2105
+ for (const set of team) {
2106
+ let species = this.dex.species.get(set.species);
2107
+ if (typeof species.battleOnly === 'string') species = this.dex.species.get(species.battleOnly);
2108
+ if (set.item && this.dex.items.get(set.item).megaStone) {
2109
+ const item = this.dex.items.get(set.item);
2110
+ if (item.megaEvolves === species.baseSpecies) {
2111
+ species = this.dex.species.get(item.megaStone);
2112
+ }
2113
+ }
2114
+ if (
2115
+ ['ag', 'uber'].includes(this.toID(this.ruleTable.has('standardnatdex') ? species.natDexTier : species.tier)) ||
2116
+ this.toID(set.ability) === 'powerconstruct'
2117
+ ) {
2118
+ gods.add(species.name);
2119
+ }
2120
+ }
2121
+ if (gods.size > 1) {
2122
+ return [`You have too many Gods.`, `(${Array.from(gods).join(', ')} are Gods.)`];
2123
+ }
2124
+ },
2125
+ onModifySpeciesPriority: 3,
2126
+ onModifySpecies(species, target, source) {
2127
+ if (source || !target?.side) return;
2128
+ const god = target.side.team.find(set => {
2129
+ let godSpecies = this.dex.species.get(set.species);
2130
+ const isNatDex = this.format.ruleTable?.has('standardnatdex');
2131
+ const validator = this.dex.formats.getRuleTable(
2132
+ this.dex.formats.get(`gen${this.gen}${isNatDex && this.gen >= 8 ? 'nationaldex' : 'ou'}`)
2133
+ );
2134
+ if (this.toID(set.ability) === 'powerconstruct') {
2135
+ return true;
2136
+ }
2137
+ if (set.item) {
2138
+ const item = this.dex.items.get(set.item);
2139
+ if (item.megaEvolves === set.species) godSpecies = this.dex.species.get(item.megaStone);
2140
+ }
2141
+ const isBanned = validator.isBannedSpecies(godSpecies);
2142
+ return isBanned;
2143
+ }) || target.side.team[0];
2144
+ const stat = Dex.stats.ids()[target.side.team.indexOf(target.set)];
2145
+ const newSpecies = this.dex.deepClone(species);
2146
+ let godSpecies = this.dex.species.get(god.species);
2147
+ if (typeof godSpecies.battleOnly === 'string') {
2148
+ godSpecies = this.dex.species.get(godSpecies.battleOnly);
2149
+ }
2150
+ newSpecies.bst -= newSpecies.baseStats[stat];
2151
+ newSpecies.baseStats[stat] = godSpecies.baseStats[stat];
2152
+ if (this.gen === 1 && (stat === 'spa' || stat === 'spd')) {
2153
+ newSpecies.baseStats['spa'] = newSpecies.baseStats['spd'] = godSpecies.baseStats[stat];
2154
+ }
2155
+ newSpecies.bst += newSpecies.baseStats[stat];
2156
+ return newSpecies;
2157
+ },
2158
+ },
2100
2159
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pkmn/sim",
3
- "version": "0.6.1",
3
+ "version": "0.6.3",
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",
package/sim/battle.ts CHANGED
@@ -2247,13 +2247,15 @@ export class Battle {
2247
2247
  // when used without an explicit target.
2248
2248
 
2249
2249
  move = this.dex.moves.get(move);
2250
- if (move.target === 'adjacentAlly') {
2251
- const adjacentAllies = pokemon.adjacentAllies();
2252
- return adjacentAllies.length ? this.sample(adjacentAllies) : null;
2253
- }
2254
2250
  if (['self', 'all', 'allySide', 'allyTeam', 'adjacentAllyOrSelf'].includes(move.target)) {
2255
2251
  return pokemon;
2252
+ } else if (move.target === 'adjacentAlly') {
2253
+ if (this.gameType === 'singles') return null;
2254
+ const adjacentAllies = pokemon.adjacentAllies();
2255
+ return adjacentAllies.length ? this.sample(adjacentAllies) : null;
2256
2256
  }
2257
+ if (this.gameType === 'singles') return pokemon.side.foe.active[0];
2258
+
2257
2259
  if (this.activePerHalf > 2) {
2258
2260
  if (move.target === 'adjacentFoe' || move.target === 'normal' || move.target === 'randomNormal') {
2259
2261
  // even if a move can target an ally, auto-resolution will never make it target an ally
@@ -390,6 +390,7 @@ export interface EventMethods {
390
390
  onAnyNegateImmunity?: ((this: Battle, pokemon: Pokemon, type: string) => boolean | void) | boolean;
391
391
  onAnyOverrideAction?: (this: Battle, pokemon: Pokemon, target: Pokemon, move: ActiveMove) => string | void;
392
392
  onAnyPrepareHit?: CommonHandlers['ResultSourceMove'];
393
+ onAnyPseudoWeatherStart?: (this: Battle, target: Pokemon, source: Pokemon, pseudoWeather: Condition) => void;
393
394
  onAnyRedirectTarget?: (
394
395
  this: Battle, target: Pokemon, source: Pokemon, source2: Effect, move: ActiveMove
395
396
  ) => Pokemon | void;
package/sim/field.ts CHANGED
@@ -218,6 +218,7 @@ export class Field {
218
218
  delete this.pseudoWeather[status.id];
219
219
  return false;
220
220
  }
221
+ this.battle.runEvent('PseudoWeatherStart', source, source, status);
221
222
  return true;
222
223
  }
223
224
 
package/sim/pokemon.ts CHANGED
@@ -1180,6 +1180,7 @@ export class Pokemon {
1180
1180
  let statName: StatIDExceptHP;
1181
1181
  for (statName in this.storedStats) {
1182
1182
  this.storedStats[statName] = pokemon.storedStats[statName];
1183
+ if (this.modifiedStats) this.modifiedStats[statName] = pokemon.modifiedStats![statName]; // Gen 1: Copy modified stats.
1183
1184
  }
1184
1185
  this.moveSlots = [];
1185
1186
  this.set.ivs = (this.battle.gen >= 5 ? this.set.ivs : pokemon.set.ivs);
@@ -1204,14 +1205,6 @@ export class Pokemon {
1204
1205
  let boostName: BoostID;
1205
1206
  for (boostName in pokemon.boosts) {
1206
1207
  this.boosts[boostName] = pokemon.boosts[boostName];
1207
- if (this.battle.gen <= 1) {
1208
- if (boostName === 'evasion' || boostName === 'accuracy') continue;
1209
- if (this.boosts[boostName] >= 0) {
1210
- this.modifyStat!(boostName, [1, 1.5, 2, 2.5, 3, 3.5, 4][this.boosts[boostName]]);
1211
- } else {
1212
- this.modifyStat!(boostName, [100, 66, 50, 40, 33, 28, 25][-this.boosts[boostName]] / 100);
1213
- }
1214
- }
1215
1208
  }
1216
1209
  if (this.battle.gen >= 6) {
1217
1210
  const volatilesToCopy = ['focusenergy', 'gmaxchistrike', 'laserfocus'];
@@ -687,7 +687,7 @@ export class TeamValidator {
687
687
  const {species: eventSpecies, eventData} = eventOnlyData;
688
688
  let legal = false;
689
689
  for (const event of eventData) {
690
- if (this.validateEvent(set, event, eventSpecies)) continue;
690
+ if (this.validateEvent(set, setSources, event, eventSpecies)) continue;
691
691
  legal = true;
692
692
  break;
693
693
  }
@@ -698,20 +698,19 @@ export class TeamValidator {
698
698
  if (eventData.length === 1) {
699
699
  problems.push(`${species.name} is only obtainable from an event - it needs to match its event:`);
700
700
  } else {
701
- problems.push(`${species.name} is only obtainable from events - it needs to match one of its events, such as:`);
701
+ problems.push(`${species.name} is only obtainable from events - it needs to match one of its events:`);
702
702
  }
703
- let eventInfo = eventData[0];
704
- let eventNum = 1;
705
703
  for (const [i, event] of eventData.entries()) {
706
704
  if (event.generation <= dex.gen && event.generation >= this.minSourceGen) {
707
- eventInfo = event;
708
- eventNum = i + 1;
709
- break;
705
+ const eventInfo = event;
706
+ const eventNum = i + 1;
707
+ const eventName = eventData.length > 1 ? ` #${eventNum}` : ``;
708
+ const eventProblems = this.validateEvent(
709
+ set, setSources, eventInfo, eventSpecies, ` to be`, `from its event${eventName}`
710
+ );
711
+ if (eventProblems) problems.push(...eventProblems);
710
712
  }
711
713
  }
712
- const eventName = eventData.length > 1 ? ` #${eventNum}` : ``;
713
- const eventProblems = this.validateEvent(set, eventInfo, eventSpecies, ` to be`, `from its event${eventName}`);
714
- if (eventProblems) problems.push(...eventProblems);
715
714
  }
716
715
  }
717
716
 
@@ -1102,7 +1101,7 @@ export class TeamValidator {
1102
1101
  }
1103
1102
 
1104
1103
  // complicated fancy return signature
1105
- return this.validateEvent(set, eventData, eventSpecies, because as any) as any;
1104
+ return this.validateEvent(set, setSources, eventData, eventSpecies, because as any) as any;
1106
1105
  }
1107
1106
 
1108
1107
  findEggMoveFathers(source: PokemonSource, species: Species, setSources: PokemonSources): boolean;
@@ -1674,16 +1673,22 @@ export class TeamValidator {
1674
1673
  return null;
1675
1674
  }
1676
1675
 
1677
- validateEvent(set: PokemonSet, eventData: EventInfo, eventSpecies: Species): true | undefined;
1678
1676
  validateEvent(
1679
- set: PokemonSet, eventData: EventInfo, eventSpecies: Species, because: string, from?: string
1677
+ set: PokemonSet, setSources: PokemonSources, eventData: EventInfo, eventSpecies: Species
1678
+ ): true | undefined;
1679
+ validateEvent(
1680
+ set: PokemonSet, setSources: PokemonSources, eventData: EventInfo, eventSpecies: Species,
1681
+ because: string, from?: string
1680
1682
  ): string[] | undefined;
1681
1683
  /**
1682
1684
  * Returns array of error messages if invalid, undefined if valid
1683
1685
  *
1684
1686
  * If `because` is not passed, instead returns true if invalid.
1685
1687
  */
1686
- validateEvent(set: PokemonSet, eventData: EventInfo, eventSpecies: Species, because = ``, from = `from an event`) {
1688
+ validateEvent(
1689
+ set: PokemonSet, setSources: PokemonSources, eventData: EventInfo, eventSpecies: Species,
1690
+ because = ``, from = `from an event`
1691
+ ) {
1687
1692
  const dex = this.dex;
1688
1693
  let name = set.species;
1689
1694
  const species = dex.species.get(set.species);
@@ -1784,8 +1789,15 @@ export class TeamValidator {
1784
1789
  problems.push(`${name} can only use Hidden Power Dark/Dragon/Electric/Steel/Ice because it must have at least 5 perfect IVs${etc}.`);
1785
1790
  }
1786
1791
  }
1787
- // Event-related ability restrictions only matter if we care about illegal abilities
1788
1792
  const ruleTable = this.ruleTable;
1793
+ if (ruleTable.has('obtainablemoves')) {
1794
+ const ssMaxSourceGen = setSources.maxSourceGen();
1795
+ const tradebackEligible = dex.gen === 2 && species.gen === 1;
1796
+ if (ssMaxSourceGen && eventData.generation > ssMaxSourceGen && !tradebackEligible) {
1797
+ if (fastReturn) return true;
1798
+ problems.push(`${name} must not have moves only learnable in gen ${ssMaxSourceGen}${etc}.`);
1799
+ }
1800
+ }
1789
1801
  if (ruleTable.has('obtainableabilities')) {
1790
1802
  if (dex.gen <= 5 && eventData.abilities && eventData.abilities.length === 1 && !eventData.isHidden) {
1791
1803
  if (species.name === eventSpecies.name) {