@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.
- package/build/config/formats.js +161 -166
- package/build/config/formats.js.map +1 -1
- package/build/data/aliases.js +2 -2
- package/build/data/aliases.js.map +1 -1
- package/build/data/formats-data.js +111 -115
- package/build/data/formats-data.js.map +1 -1
- package/build/data/items.js +7 -1
- package/build/data/items.js.map +1 -1
- package/build/data/learnsets.js +3 -2
- package/build/data/learnsets.js.map +1 -1
- package/build/data/mods/gen1/moves.js +0 -10
- package/build/data/mods/gen1/moves.js.map +1 -1
- package/build/data/mods/gen1/scripts.js +87 -52
- package/build/data/mods/gen1/scripts.js.map +1 -1
- package/build/data/mods/gen2/moves.js +0 -1
- package/build/data/mods/gen2/moves.js.map +1 -1
- package/build/data/mods/gen2/scripts.js +1 -1
- package/build/data/moves.js +44 -25
- package/build/data/moves.js.map +1 -1
- package/build/data/pokedex.js +1 -1
- package/build/data/rulesets.js +58 -0
- package/build/data/rulesets.js.map +1 -1
- package/build/sim/battle.js +8 -4
- package/build/sim/battle.js.map +1 -1
- package/build/sim/dex-conditions.d.ts +1 -0
- package/build/sim/dex-conditions.js.map +1 -1
- package/build/sim/field.js +1 -0
- package/build/sim/field.js.map +1 -1
- package/build/sim/pokemon.js +2 -10
- package/build/sim/pokemon.js.map +1 -1
- package/build/sim/team-validator.d.ts +2 -2
- package/build/sim/team-validator.js +19 -14
- package/build/sim/team-validator.js.map +1 -1
- package/config/formats.ts +168 -169
- package/data/aliases.ts +2 -2
- package/data/formats-data.ts +111 -115
- package/data/items.ts +7 -1
- package/data/learnsets.ts +3 -2
- package/data/mods/gen1/moves.ts +0 -10
- package/data/mods/gen1/scripts.ts +85 -51
- package/data/mods/gen2/moves.ts +0 -1
- package/data/mods/gen2/scripts.ts +1 -1
- package/data/moves.ts +43 -23
- package/data/pokedex.ts +1 -1
- package/data/rulesets.ts +59 -0
- package/package.json +1 -1
- package/sim/battle.ts +6 -4
- package/sim/dex-conditions.ts +1 -0
- package/sim/field.ts +1 -0
- package/sim/pokemon.ts +1 -8
- 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
|
-
|
|
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"
|
|
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", "
|
|
79287
|
+
{generation: 8, level: 50, moves: ["recover", "switcheroo", "darkpulse", "belch"], pokeball: "cherishball"},
|
|
79287
79288
|
],
|
|
79288
79289
|
},
|
|
79289
79290
|
syclant: {
|
package/data/mods/gen1/moves.ts
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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 (
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
if (
|
|
140
|
-
//
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
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
|
|
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);
|
package/data/mods/gen2/moves.ts
CHANGED
|
@@ -706,7 +706,7 @@ export const Scripts: ModdedBattleScriptsData = {
|
|
|
706
706
|
}
|
|
707
707
|
}
|
|
708
708
|
|
|
709
|
-
// Apply random factor
|
|
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
|
-
|
|
8711
|
-
|
|
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:
|
|
8741
|
+
duration: 1,
|
|
8728
8742
|
onLockMove: 'iceball',
|
|
8729
8743
|
onStart() {
|
|
8730
|
-
this.effectState.hitCount =
|
|
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())
|
|
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
|
-
|
|
14394
|
-
|
|
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:
|
|
14435
|
+
duration: 1,
|
|
14410
14436
|
onLockMove: 'rollout',
|
|
14411
14437
|
onStart() {
|
|
14412
|
-
this.effectState.hitCount =
|
|
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", "
|
|
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
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
|
package/sim/dex-conditions.ts
CHANGED
|
@@ -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
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'];
|
package/sim/team-validator.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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(
|
|
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) {
|