@pkmn/sim 0.6.2 → 0.6.4

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 (39) hide show
  1. package/build/config/formats.js +98 -76
  2. package/build/config/formats.js.map +1 -1
  3. package/build/data/abilities.js +1 -1
  4. package/build/data/abilities.js.map +1 -1
  5. package/build/data/formats-data.js +3 -3
  6. package/build/data/formats-data.js.map +1 -1
  7. package/build/data/mods/gen1/moves.js +3 -2
  8. package/build/data/mods/gen1/moves.js.map +1 -1
  9. package/build/data/mods/gen1/scripts.js +75 -40
  10. package/build/data/mods/gen1/scripts.js.map +1 -1
  11. package/build/data/mods/gen2/scripts.js +1 -1
  12. package/build/data/moves.js +8 -6
  13. package/build/data/moves.js.map +1 -1
  14. package/build/data/pokedex.js +5 -0
  15. package/build/data/pokedex.js.map +1 -1
  16. package/build/data/rulesets.js +58 -0
  17. package/build/data/rulesets.js.map +1 -1
  18. package/build/sim/battle-queue.js +4 -3
  19. package/build/sim/battle-queue.js.map +1 -1
  20. package/build/sim/battle.js +8 -4
  21. package/build/sim/battle.js.map +1 -1
  22. package/build/sim/dex-species.d.ts +2 -0
  23. package/build/sim/dex-species.js.map +1 -1
  24. package/build/sim/team-validator.js +4 -0
  25. package/build/sim/team-validator.js.map +1 -1
  26. package/config/formats.ts +101 -73
  27. package/data/abilities.ts +1 -1
  28. package/data/formats-data.ts +3 -3
  29. package/data/mods/gen1/moves.ts +3 -2
  30. package/data/mods/gen1/scripts.ts +73 -39
  31. package/data/mods/gen2/scripts.ts +1 -1
  32. package/data/moves.ts +8 -6
  33. package/data/pokedex.ts +5 -0
  34. package/data/rulesets.ts +59 -0
  35. package/package.json +1 -1
  36. package/sim/battle-queue.ts +4 -3
  37. package/sim/battle.ts +6 -4
  38. package/sim/dex-species.ts +2 -0
  39. package/sim/team-validator.ts +5 -0
package/config/formats.ts CHANGED
@@ -164,7 +164,7 @@ export const Formats: FormatList = [
164
164
  mod: 'gen8',
165
165
  ruleset: ['Little Cup', 'Standard', 'Dynamax Clause'],
166
166
  banlist: [
167
- 'Corsola-Galar', 'Cutiefly', 'Drifloon', 'Gastly', 'Gothita', 'Rufflet', 'Scraggy', 'Scyther', 'Sneasel', 'Swirlix',
167
+ 'Corsola-Galar', 'Cutiefly', 'Drifloon', 'Gastly', 'Gothita', 'Magby', 'Rufflet', 'Scraggy', 'Scyther', 'Sneasel', 'Swirlix',
168
168
  'Tangela', 'Vullaby', 'Vulpix-Alola', 'Woobat', 'Zigzagoon-Base', 'Chlorophyll', 'Moody', 'Baton Pass', 'Sticky Web',
169
169
  ],
170
170
  },
@@ -249,8 +249,8 @@ export const Formats: FormatList = [
249
249
  banlist: [
250
250
  // LC OU
251
251
  'Abra', 'Carvanha', 'Diglett-Base', 'Dwebble', 'Ferroseed', 'Foongus', 'Frillish', 'Grookey', 'Koffing',
252
- 'Larvesta', 'Magby', 'Magnemite', 'Mareanie', 'Mienfoo', 'Mudbray', 'Natu', 'Onix', 'Pawniard',
253
- 'Ponyta-Base', 'Porygon', 'Staryu', 'Timburr', 'Trapinch', 'Tyrunt',
252
+ 'Larvesta', 'Magnemite', 'Mareanie', 'Mienfoo', 'Mudbray', 'Natu', 'Onix', 'Pawniard', 'Ponyta-Base',
253
+ 'Porygon', 'Staryu', 'Timburr', 'Trapinch', 'Tyrunt',
254
254
  // LC UUBL
255
255
  'Archen', 'Farfetch\u2019d-Galar', 'Scorbunny', 'Shellder', 'Wingull',
256
256
  ],
@@ -517,6 +517,7 @@ export const Formats: FormatList = [
517
517
  mod: 'gen8',
518
518
  ruleset: ['[Gen 8] National Dex'],
519
519
  banlist: ['ND OU', 'ND UUBL', 'Drizzle', 'Drought', 'Light Clay', 'Slowbronite'],
520
+ unbanlist: ['Hydreigon'],
520
521
  },
521
522
  {
522
523
  name: "[Gen 8] National Dex RU",
@@ -527,7 +528,7 @@ export const Formats: FormatList = [
527
528
  mod: 'gen8',
528
529
  searchShow: false,
529
530
  ruleset: ['[Gen 8] National Dex UU'],
530
- banlist: ['ND UU', 'ND RUBL'],
531
+ banlist: ['ND UU', 'ND RUBL', 'Hydreigon'],
531
532
  },
532
533
  {
533
534
  name: "[Gen 8] National Dex Monotype",
@@ -691,7 +692,7 @@ export const Formats: FormatList = [
691
692
  'Yveltal', 'Zacian', 'Zacian-Crowned', 'Zamazenta-Base', 'Zekrom', 'Zeraora', 'Zygarde-Base', 'Arena Trap', 'Comatose', 'Contrary', 'Fluffy',
692
693
  'Fur Coat', 'Gorilla Tactics', 'Huge Power', 'Ice Scales', 'Illusion', 'Imposter', 'Innards Out', 'Intrepid Sword', 'Libero', 'Magic Bounce',
693
694
  'Magnet Pull', 'Moody', 'Neutralizing Gas', 'Parental Bond', 'Poison Heal', 'Protean', 'Pure Power', 'Shadow Tag', 'Simple', 'Stakeout',
694
- 'Speed Boost', 'Unburden', 'Water Bubble', 'Wonder Guard', 'King\'s Rock', 'Baton Pass',
695
+ 'Speed Boost', 'Unburden', 'Water Bubble', 'Wonder Guard', 'King\'s Rock', 'Baton Pass', 'Electrify',
695
696
  ],
696
697
  },
697
698
  {
@@ -810,67 +811,12 @@ export const Formats: FormatList = [
810
811
  ],
811
812
 
812
813
  mod: 'gen8',
813
- ruleset: ['Standard OMs', 'Sleep Moves Clause'],
814
+ ruleset: ['Standard OMs', 'Sleep Moves Clause', 'Godly Gift Mod'],
814
815
  banlist: [
815
816
  'Blissey', 'Calyrex-Shadow', 'Chansey', 'Crawdaunt', 'Dragapult', 'Eternatus', 'Hawlucha', 'Kyogre', 'Marowak-Alola', 'Melmetal',
816
- 'Nidoking', 'Nidoqueen', 'Pikachu', 'Toxapex', 'Xerneas', 'Zacian', 'Zacian-Crowned', 'Uber > 1', 'AG ++ Uber > 1', 'Arena Trap',
817
- 'Huge Power', 'Moody', 'Pure Power', 'Shadow Tag', 'Swift Swim', 'Bright Powder', 'Focus Band', 'King\'s Rock', 'Lax Incense',
818
- 'Quick Claw', 'Baton Pass',
817
+ 'Nidoking', 'Nidoqueen', 'Pikachu', 'Toxapex', 'Xerneas', 'Zacian', 'Zacian-Crowned', 'Arena Trap', 'Huge Power', 'Moody',
818
+ 'Pure Power', 'Shadow Tag', 'Swift Swim', 'Bright Powder', 'Focus Band', 'King\'s Rock', 'Lax Incense', 'Quick Claw', 'Baton Pass',
819
819
  ],
820
- onValidateTeam(team) {
821
- const gods = new Set<string>();
822
- for (const set of team) {
823
- let species = this.dex.species.get(set.species);
824
- if (typeof species.battleOnly === 'string') species = this.dex.species.get(species.battleOnly);
825
- if (set.item && this.dex.items.get(set.item).megaStone) {
826
- const item = this.dex.items.get(set.item);
827
- if (item.megaEvolves === species.baseSpecies) {
828
- species = this.dex.species.get(item.megaStone);
829
- }
830
- }
831
- if (this.ruleTable.has('standardnatdex')) {
832
- const format = this.dex.formats.getRuleTable(this.dex.formats.get('gen8nationaldex'));
833
- if (format.isBannedSpecies(species)) gods.add(species.name);
834
- } else {
835
- if (['ag', 'uber'].includes(this.toID(species.tier)) || this.toID(set.ability) === 'powerconstruct') {
836
- gods.add(species.name);
837
- }
838
- }
839
- }
840
- if (gods.size > 1) {
841
- return [`You have too many Gods.`, `(${Array.from(gods).join(', ')} are Gods.)`];
842
- }
843
- },
844
- onModifySpeciesPriority: 3,
845
- onModifySpecies(species, target, source) {
846
- if (source || !target?.side) return;
847
- const god = target.side.team.find(set => {
848
- let godSpecies = this.dex.species.get(set.species);
849
- const isNatDex = this.format.ruleTable?.has('standardnatdex');
850
- const validator = this.dex.formats.getRuleTable(
851
- this.dex.formats.get(`gen${isNatDex && this.gen < 8 ? 8 : this.gen}${isNatDex ? 'nationaldex' : 'ou'}`)
852
- );
853
- if (this.toID(set.ability) === 'powerconstruct') {
854
- return true;
855
- }
856
- if (set.item) {
857
- const item = this.dex.items.get(set.item);
858
- if (item.megaEvolves === set.species) godSpecies = this.dex.species.get(item.megaStone);
859
- }
860
- const isBanned = validator.isBannedSpecies(godSpecies);
861
- return isBanned;
862
- }) || target.side.team[0];
863
- const stat = Dex.stats.ids()[target.side.team.indexOf(target.set)];
864
- const newSpecies = this.dex.deepClone(species);
865
- let godSpecies = this.dex.species.get(god.species);
866
- if (typeof godSpecies.battleOnly === 'string') {
867
- godSpecies = this.dex.species.get(godSpecies.battleOnly);
868
- }
869
- newSpecies.bst -= newSpecies.baseStats[stat];
870
- newSpecies.baseStats[stat] = godSpecies.baseStats[stat];
871
- newSpecies.bst += newSpecies.baseStats[stat];
872
- return newSpecies;
873
- },
874
820
  },
875
821
  {
876
822
  name: "[Gen 8] Free-For-All",
@@ -1669,9 +1615,9 @@ export const Formats: FormatList = [
1669
1615
  'Calyrex-Ice', 'Calyrex-Shadow', 'Dialga', 'Eternatus', 'Giratina', 'Giratina-Origin', 'Groudon', 'Ho-Oh',
1670
1616
  'Jirachi', 'Kyogre', 'Kyurem-White', 'Lugia', 'Lunala', 'Magearna', 'Marshadow', 'Melmetal', 'Mewtwo',
1671
1617
  'Necrozma-Dawn-Wings', 'Necrozma-Dusk-Mane', 'Palkia', 'Rayquaza', 'Reshiram', 'Solgaleo', 'Urshifu-Base',
1672
- 'Xerneas', 'Yveltal', 'Zacian', 'Zacian-Crowned', 'Zamazenta', 'Zamazenta-Crowned', 'Zekrom', 'Emergency Exit',
1673
- 'Huge Power', 'Moody', 'Power Construct', 'Shadow Tag', 'Wimp Out', 'Wonder Guard', 'Bolt Beak', 'Fishious Rend',
1674
- 'Shell Smash', 'Swagger',
1618
+ 'Xerneas', 'Yveltal', 'Zacian', 'Zacian-Crowned', 'Zamazenta', 'Zamazenta-Crowned', 'Zekrom', 'Contrary',
1619
+ 'Emergency Exit', 'Huge Power', 'Moody', 'Power Construct', 'Serene Grace', 'Shadow Tag', 'Wimp Out',
1620
+ 'Wonder Guard', 'Ally Switch', 'Bolt Beak', 'Fishious Rend', 'Shell Smash', 'Swagger',
1675
1621
  ],
1676
1622
  onBeforeSwitchIn(pokemon) {
1677
1623
  pokemon.m.curMoves = this.dex.deepClone(pokemon.moves);
@@ -2173,12 +2119,94 @@ export const Formats: FormatList = [
2173
2119
  column: 2,
2174
2120
  },
2175
2121
  {
2176
- name: "[Gen 7] Pick-Your-Team Random Battle",
2122
+ name: "[Gen 4] Shared Power Random Battle",
2123
+ desc: `[Gen 4] Random Battle with aspects of [Gen 8] Shared Power`,
2177
2124
 
2178
- mod: 'gen7',
2125
+ mod: 'gen4',
2179
2126
  team: 'random',
2180
- ruleset: ['[Gen 7] Random Battle', 'Max Team Size = 12', 'Picked Team Size = 6', 'Team Preview'],
2181
- desc: `Twelve Pokémon sets are randomly chosen, then you pick six for your team!`,
2127
+ ruleset: ['[Gen 4] Random Battle', 'Team Preview'],
2128
+ getSharedPower(pokemon) {
2129
+ const sharedPower = new Set<string>();
2130
+ for (const ally of pokemon.side.pokemon) {
2131
+ if (ally.previouslySwitchedIn > 0) {
2132
+ if (pokemon.battle.dex.currentMod !== 'sharedpower' && ['trace', 'mirrorarmor'].includes(ally.baseAbility)) {
2133
+ sharedPower.add('noability');
2134
+ continue;
2135
+ }
2136
+ sharedPower.add(ally.baseAbility);
2137
+ }
2138
+ }
2139
+ sharedPower.delete(pokemon.baseAbility);
2140
+ return sharedPower;
2141
+ },
2142
+ onBeforeSwitchIn(pokemon) {
2143
+ let format = this.format;
2144
+ if (!format.getSharedPower) format = this.dex.formats.get('gen8sharedpower');
2145
+ for (const ability of format.getSharedPower!(pokemon)) {
2146
+ const effect = 'ability:' + ability;
2147
+ pokemon.volatiles[effect] = {id: this.toID(effect), target: pokemon};
2148
+ if (!pokemon.m.abils) pokemon.m.abils = [];
2149
+ if (!pokemon.m.abils.includes(effect)) pokemon.m.abils.push(effect);
2150
+ }
2151
+ },
2152
+ onSwitchInPriority: 2,
2153
+ onSwitchIn(pokemon) {
2154
+ let format = this.format;
2155
+ if (!format.getSharedPower) format = this.dex.formats.get('gen8sharedpower');
2156
+ for (const ability of format.getSharedPower!(pokemon)) {
2157
+ if (ability === 'noability') {
2158
+ this.hint(`Mirror Armor and Trace break in Shared Power formats that don't use Shared Power as a base, so they get removed from non-base users.`);
2159
+ }
2160
+ const effect = 'ability:' + ability;
2161
+ delete pokemon.volatiles[effect];
2162
+ pokemon.addVolatile(effect);
2163
+ }
2164
+ },
2165
+ field: {
2166
+ suppressingWeather() {
2167
+ for (const pokemon of this.battle.getAllActive()) {
2168
+ const innates = Object.keys(pokemon.volatiles).filter(x => x.startsWith('ability:'));
2169
+ if (pokemon && !pokemon.ignoringAbility() &&
2170
+ (pokemon.getAbility().suppressWeather || innates.some(x => (
2171
+ this.battle.dex.abilities.get(x.replace('ability:', '')).suppressWeather
2172
+ )))) {
2173
+ return true;
2174
+ }
2175
+ }
2176
+ return false;
2177
+ },
2178
+ },
2179
+ pokemon: {
2180
+ hasAbility(ability) {
2181
+ if (this.ignoringAbility()) return false;
2182
+ if (Array.isArray(ability)) return ability.some(abil => this.hasAbility(abil));
2183
+ const abilityid = this.battle.toID(ability);
2184
+ return this.ability === abilityid || !!this.volatiles['ability:' + abilityid];
2185
+ },
2186
+ ignoringAbility() {
2187
+ // Check if any active pokemon have the ability Neutralizing Gas
2188
+ let neutralizinggas = false;
2189
+ for (const pokemon of this.battle.getAllActive()) {
2190
+ // can't use hasAbility because it would lead to infinite recursion
2191
+ if (
2192
+ (pokemon.ability === ('neutralizinggas' as ID) || pokemon.m.abils?.includes('ability:neutralizinggas')) &&
2193
+ !pokemon.volatiles['gastroacid'] && !pokemon.abilityState.ending
2194
+ ) {
2195
+ neutralizinggas = true;
2196
+ break;
2197
+ }
2198
+ }
2199
+
2200
+ return !!(
2201
+ (this.battle.gen >= 5 && !this.isActive) ||
2202
+ ((this.volatiles['gastroacid'] ||
2203
+ (neutralizinggas && (this.ability !== ('neutralizinggas' as ID) ||
2204
+ this.m.abils?.includes('ability:neutralizinggas'))
2205
+ )) && !this.getAbility().isPermanent
2206
+ )
2207
+ );
2208
+ },
2209
+ },
2182
2210
  },
2183
2211
 
2184
2212
  // Randomized Metas
@@ -2548,9 +2576,9 @@ export const Formats: FormatList = [
2548
2576
  mod: 'gen3',
2549
2577
  // searchShow: false,
2550
2578
  gameType: 'doubles',
2551
- ruleset: ['Standard', '!Sleep Clause Mod', '!Switch Priority Clause Mod'],
2552
- banlist: ['Uber'],
2553
- unbanlist: ['Deoxys-Speed', 'Wobbuffet', 'Wynaut'],
2579
+ ruleset: ['Standard', '!Switch Priority Clause Mod'],
2580
+ banlist: ['Uber', 'Soul Dew', 'Swagger'],
2581
+ unbanlist: ['Deoxys-Defense', 'Latias', 'Wobbuffet', 'Wynaut'],
2554
2582
  },
2555
2583
  {
2556
2584
  name: "[Gen 3] UU",
package/data/abilities.ts CHANGED
@@ -1234,7 +1234,7 @@ export const Abilities: {[abilityid: string]: AbilityData} = {
1234
1234
  if (move?.type === 'Flying' && pokemon.hp === pokemon.maxhp) return priority + 1;
1235
1235
  },
1236
1236
  name: "Gale Wings",
1237
- rating: 3,
1237
+ rating: 2.5,
1238
1238
  num: 177,
1239
1239
  },
1240
1240
  galvanize: {
@@ -1259,7 +1259,7 @@ export const FormatsData: {[k: string]: SpeciesFormatsData} = {
1259
1259
  natDexTier: "RU",
1260
1260
  },
1261
1261
  magby: {
1262
- tier: "LC",
1262
+ tier: "NFE",
1263
1263
  },
1264
1264
  magmar: {
1265
1265
  tier: "NFE",
@@ -1405,7 +1405,7 @@ export const FormatsData: {[k: string]: SpeciesFormatsData} = {
1405
1405
  natDexTier: "RU",
1406
1406
  },
1407
1407
  glaceon: {
1408
- randomBattleMoves: ["freezedry", "protect", "shadowball", "toxic", "wish"],
1408
+ randomBattleMoves: ["freezedry", "protect", "toxic", "wish"],
1409
1409
  randomBattleLevel: 88,
1410
1410
  randomDoubleBattleMoves: ["blizzard", "freezedry", "helpinghand", "protect", "shadowball", "wish"],
1411
1411
  randomDoubleBattleLevel: 88,
@@ -5841,7 +5841,7 @@ export const FormatsData: {[k: string]: SpeciesFormatsData} = {
5841
5841
  natDexTier: "RU",
5842
5842
  },
5843
5843
  silvallyghost: {
5844
- randomBattleMoves: ["multiattack", "partingshot", "swordsdance", "xscissor"],
5844
+ randomBattleMoves: ["flamecharge", "multiattack", "partingshot", "swordsdance", "xscissor"],
5845
5845
  randomBattleLevel: 84,
5846
5846
  randomDoubleBattleMoves: ["multiattack", "swordsdance", "tailwind", "xscissor"],
5847
5847
  randomDoubleBattleLevel: 88,
@@ -253,7 +253,7 @@ export const Moves: {[k: string]: ModdedMoveData} = {
253
253
  duration: 2,
254
254
  onLockMove: 'dig',
255
255
  onInvulnerability(target, source, move) {
256
- if (move.id === 'swift') return true;
256
+ if (move.id === 'swift' || move.id === 'transform') return true;
257
257
  this.add('-message', 'The foe ' + target.name + ' can\'t be hit underground!');
258
258
  return false;
259
259
  },
@@ -372,7 +372,7 @@ export const Moves: {[k: string]: ModdedMoveData} = {
372
372
  duration: 2,
373
373
  onLockMove: 'fly',
374
374
  onInvulnerability(target, source, move) {
375
- if (move.id === 'swift') return true;
375
+ if (move.id === 'swift' || move.id === 'transform') return true;
376
376
  this.add('-message', 'The foe ' + target.name + ' can\'t be hit while flying!');
377
377
  return false;
378
378
  },
@@ -835,6 +835,7 @@ export const Moves: {[k: string]: ModdedMoveData} = {
835
835
  uncappedDamage = this.runEvent('SubDamage', target, source, move, uncappedDamage);
836
836
  if (!uncappedDamage) return uncappedDamage;
837
837
  source.lastDamage = uncappedDamage;
838
+ this.lastDamage = uncappedDamage;
838
839
  target.volatiles['substitute'].hp -= uncappedDamage > target.volatiles['substitute'].hp ?
839
840
  target.volatiles['substitute'].hp : uncappedDamage;
840
841
  if (target.volatiles['substitute'].hp <= 0) {
@@ -67,8 +67,7 @@ export const Scripts: ModdedBattleScriptsData = {
67
67
  },
68
68
  actions: {
69
69
  // This function is the main one when running a move.
70
- // It deals with the beforeMove and AfterMoveSelf events.
71
- // This leads with partial trapping moves shennanigans after the move has been used.
70
+ // It deals with the beforeMove event.
72
71
  // It also deals with how PP reduction works on gen 1.
73
72
  runMove(moveOrMoveName, pokemon, targetLoc, sourceEffect) {
74
73
  const target = this.battle.getTarget(pokemon, moveOrMoveName, targetLoc);
@@ -115,46 +114,79 @@ export const Scripts: ModdedBattleScriptsData = {
115
114
  }
116
115
  }
117
116
  this.useMove(move, pokemon, target, sourceEffect);
118
- 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);
119
122
 
120
- // If target fainted
121
- if (target && target.hp <= 0) {
122
- // We remove recharge
123
- if (pokemon.volatiles['mustrecharge']) pokemon.removeVolatile('mustrecharge');
124
- delete pokemon.volatiles['partialtrappinglock'];
125
- // We remove screens
126
- target.side.removeSideCondition('reflect');
127
- target.side.removeSideCondition('lightscreen');
128
- pokemon.removeVolatile('twoturnmove');
129
- } else if (pokemon.hp) {
130
- 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;
129
+ }
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);
131
141
  }
132
- if (pokemon.volatiles['mustrecharge']) this.battle.add('-mustrecharge', pokemon);
133
-
134
- // For partial trapping moves, we are saving the target
135
- if (move.volatileStatus === 'partiallytrapped' && target && target.hp > 0) {
136
- // Let's check if the lock exists
137
- if (pokemon.volatiles['partialtrappinglock'] && target.volatiles['partiallytrapped']) {
138
- // Here the partialtrappinglock volatile has been already applied
139
- const sourceVolatile = pokemon.volatiles['partialtrappinglock'];
140
- const targetVolatile = target.volatiles['partiallytrapped'];
141
- if (!sourceVolatile.locked) {
142
- // If it's the first hit, we save the target
143
- sourceVolatile.locked = target;
144
- } else if (target !== pokemon && target !== sourceVolatile.locked) {
145
- // Our target switched out! Re-roll the duration, damage, and accuracy.
146
- const duration = this.battle.sample([2, 2, 2, 3, 3, 3, 4, 5]);
147
- sourceVolatile.duration = duration;
148
- sourceVolatile.locked = target;
149
- // Duration reset thus partially trapped at 2 always.
150
- targetVolatile.duration = 2;
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);
151
160
  }
152
- } // 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
+ }
153
184
  }
185
+ return moveResult;
154
186
  },
155
187
  // This is the function that actually uses the move, running ModifyMove events.
156
188
  // It uses the move and then deals with the effects after the move.
157
- useMove(moveOrMoveName, pokemon, target, sourceEffect) {
189
+ useMoveInner(moveOrMoveName, pokemon, target, sourceEffect) {
158
190
  if (!sourceEffect && this.battle.effect.id) sourceEffect = this.battle.effect;
159
191
  const baseMove = this.battle.dex.moves.get(moveOrMoveName);
160
192
  let move = this.battle.dex.getActiveMove(baseMove);
@@ -210,13 +242,13 @@ export const Scripts: ModdedBattleScriptsData = {
210
242
  }
211
243
  damage = this.tryMoveHit(target, pokemon, move);
212
244
 
213
- // Store 0 damage for last damage if move failed or dealt 0 damage.
245
+ // Store 0 damage for last damage if move failed.
214
246
  // This only happens on moves that don't deal damage but call GetDamageVarsForPlayerAttack (disassembly).
215
247
  const neverDamageMoves = [
216
248
  'conversion', 'haze', 'mist', 'focusenergy', 'confuseray', 'supersonic', 'transform', 'lightscreen', 'reflect', 'substitute', 'mimic', 'leechseed', 'splash', 'softboiled', 'recover', 'rest',
217
249
  ];
218
250
  if (
219
- !damage &&
251
+ !damage && damage !== 0 &&
220
252
  (move.category !== 'Status' || (move.status && !['psn', 'tox', 'par'].includes(move.status))) &&
221
253
  !neverDamageMoves.includes(move.id)
222
254
  ) {
@@ -393,6 +425,7 @@ export const Scripts: ModdedBattleScriptsData = {
393
425
  // We get the sub to the target to see if it existed
394
426
  const targetSub = (target) ? target.volatiles['substitute'] : false;
395
427
  const targetHadSub = (targetSub !== null && targetSub !== false && (typeof targetSub !== 'undefined'));
428
+ let targetHasSub: boolean | undefined = undefined;
396
429
 
397
430
  if (target) {
398
431
  hitResult = this.battle.singleEvent('TryHit', moveData, {}, target, pokemon, move);
@@ -425,6 +458,7 @@ export const Scripts: ModdedBattleScriptsData = {
425
458
  }
426
459
 
427
460
  if (hitResult === 0) {
461
+ targetHasSub = !!(target?.volatiles['substitute']);
428
462
  target = null;
429
463
  } else if (!hitResult) {
430
464
  if (hitResult === false) this.battle.add('-fail', target);
@@ -554,7 +588,7 @@ export const Scripts: ModdedBattleScriptsData = {
554
588
  return false;
555
589
  }
556
590
  }
557
- const targetHasSub = !!(target?.volatiles['substitute']);
591
+ if (targetHasSub === undefined) targetHasSub = !!(target?.volatiles['substitute']);
558
592
 
559
593
  // Here's where self effects are applied.
560
594
  const doSelf = (targetHadSub && targetHasSub) || !targetHadSub;
@@ -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);
@@ -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
@@ -11157,9 +11157,10 @@ export const Moves: {[moveid: string]: MoveData} = {
11157
11157
  priority: 0,
11158
11158
  flags: {protect: 1, mirror: 1},
11159
11159
  mindBlownRecoil: true,
11160
- onAfterMove(pokemon, target, move) {
11161
- if (move.mindBlownRecoil && !move.multihit) {
11162
- this.damage(Math.round(pokemon.maxhp / 2), pokemon, pokemon, this.dex.conditions.get('Mind Blown'), true);
11160
+ onAfterMoveSecondarySelf(pokemon, target, move) {
11161
+ const maxhp = pokemon.getUndynamaxedHP(pokemon.maxhp);
11162
+ if (pokemon.hp && pokemon.ability === "emergencyexit" && pokemon.getUndynamaxedHP() <= maxhp / 2) {
11163
+ this.runEvent('EmergencyExit', pokemon);
11163
11164
  }
11164
11165
  },
11165
11166
  secondary: null,
@@ -16768,9 +16769,10 @@ export const Moves: {[moveid: string]: MoveData} = {
16768
16769
  priority: 0,
16769
16770
  flags: {protect: 1, mirror: 1},
16770
16771
  mindBlownRecoil: true,
16771
- onAfterMove(pokemon, target, move) {
16772
- if (move.mindBlownRecoil && !move.multihit) {
16773
- this.damage(Math.round(pokemon.maxhp / 2), pokemon, pokemon, this.dex.conditions.get('Steel Beam'), true);
16772
+ onAfterMoveSecondarySelf(pokemon, target, move) {
16773
+ const maxhp = pokemon.getUndynamaxedHP(pokemon.maxhp);
16774
+ if (pokemon.hp && pokemon.ability === "emergencyexit" && pokemon.getUndynamaxedHP() <= maxhp / 2) {
16775
+ this.runEvent('EmergencyExit', pokemon);
16774
16776
  }
16775
16777
  },
16776
16778
  secondary: null,
package/data/pokedex.ts CHANGED
@@ -820,6 +820,7 @@ export const Pokedex: {[speciesid: string]: SpeciesData} = {
820
820
  prevo: "Pikachu",
821
821
  evoType: "useItem",
822
822
  evoItem: "Thunder Stone",
823
+ evoRegion: "Alola",
823
824
  eggGroups: ["Field", "Fairy"],
824
825
  },
825
826
  sandshrew: {
@@ -2350,6 +2351,7 @@ export const Pokedex: {[speciesid: string]: SpeciesData} = {
2350
2351
  prevo: "Exeggcute",
2351
2352
  evoType: "useItem",
2352
2353
  evoItem: "Leaf Stone",
2354
+ evoRegion: "Alola",
2353
2355
  eggGroups: ["Grass"],
2354
2356
  },
2355
2357
  cubone: {
@@ -2393,6 +2395,7 @@ export const Pokedex: {[speciesid: string]: SpeciesData} = {
2393
2395
  prevo: "Cubone",
2394
2396
  evoLevel: 28,
2395
2397
  evoCondition: "at night",
2398
+ evoRegion: "Alola",
2396
2399
  eggGroups: ["Monster"],
2397
2400
  },
2398
2401
  marowakalolatotem: {
@@ -2490,6 +2493,7 @@ export const Pokedex: {[speciesid: string]: SpeciesData} = {
2490
2493
  color: "Gray",
2491
2494
  prevo: "Koffing",
2492
2495
  evoLevel: 35,
2496
+ evoRegion: "Galar",
2493
2497
  eggGroups: ["Amorphous"],
2494
2498
  },
2495
2499
  rhyhorn: {
@@ -2687,6 +2691,7 @@ export const Pokedex: {[speciesid: string]: SpeciesData} = {
2687
2691
  prevo: "Mime Jr.",
2688
2692
  evoType: "levelMove",
2689
2693
  evoMove: "Mimic",
2694
+ evoRegion: "Galar",
2690
2695
  evos: ["Mr. Rime"],
2691
2696
  eggGroups: ["Human-Like"],
2692
2697
  canHatch: true,
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.2",
3
+ "version": "0.6.4",
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",
@@ -281,9 +281,10 @@ export class BattleQueue {
281
281
  for (const choice of choices) {
282
282
  const resolvedChoices = this.resolveAction(choice);
283
283
  this.list.push(...resolvedChoices);
284
- const resolvedChoice = resolvedChoices[0];
285
- if (resolvedChoice && resolvedChoice.choice === 'move' && resolvedChoice.move.id !== 'recharge') {
286
- resolvedChoice.pokemon.side.lastSelectedMove = resolvedChoice.move.id;
284
+ for (const resolvedChoice of resolvedChoices) {
285
+ if (resolvedChoice && resolvedChoice.choice === 'move' && resolvedChoice.move.id !== 'recharge') {
286
+ resolvedChoice.pokemon.side.lastSelectedMove = resolvedChoice.move.id;
287
+ }
287
288
  }
288
289
  }
289
290
  }