File size: 39,764 Bytes
5c2ed06
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
/**
 * Gen 1 mechanics are fairly different to those we know on current gen.
 * Therefor we need to make a lot of changes to the battle engine for this game simulation.
 * This generation inherits all the changes from older generations, that must be taken into account when editing code.
 */

const SKIP_LASTDAMAGE = new Set([
	'confuseray', 'conversion', 'counter', 'focusenergy', 'glare', 'haze', 'leechseed', 'lightscreen',
	'mimic', 'mist', 'poisongas', 'poisonpowder', 'recover', 'reflect', 'rest', 'softboiled',
	'splash', 'stunspore', 'substitute', 'supersonic', 'teleport', 'thunderwave', 'toxic', 'transform',
]);

const TWO_TURN_MOVES = ['dig', 'fly', 'razorwind', 'skullbash', 'skyattack', 'solarbeam'];

export const Scripts: ModdedBattleScriptsData = {
	inherit: 'gen2',
	gen: 1,
	init() {
		for (const i in this.data.Pokedex) {
			const poke = this.modData('Pokedex', i);
			poke.gender = 'N';
			poke.eggGroups = null;
		}
	},
	// BattlePokemon scripts.
	pokemon: {
		inherit: true,
		getStat(statName, unmodified) {
			// @ts-expect-error type checking prevents 'hp' from being passed, but we're paranoid
			if (statName === 'hp') throw new Error("Please read `maxhp` directly");
			if (unmodified) return this.baseStoredStats[statName];
			return this.modifiedStats![statName];
		},
		// Gen 1 function to apply a stat modification that is only active until the stat is recalculated or mon switched.
		modifyStat(statName, modifier) {
			if (!(statName in this.storedStats)) throw new Error("Invalid `statName` passed to `modifyStat`");
			const modifiedStats = this.battle.clampIntRange(Math.floor(this.modifiedStats![statName] * modifier), 1);
			this.modifiedStats![statName] = modifiedStats;
		},
		// In generation 1, boosting function increases the stored modified stat and checks for opponent's status.
		boostBy(boost) {
			let changed: boolean | number = false;
			let i: BoostID;
			for (i in boost) {
				const delta = boost[i];
				if (delta === undefined) continue;
				if (delta > 0 && this.boosts[i] >= 6) continue;
				if (delta < 0 && this.boosts[i] <= -6) continue;
				if (i === 'evasion' || i === 'accuracy') {
					this.boosts[i] += delta;
					if (this.boosts[i] > 6) {
						this.boosts[i] = 6;
					}
					if (this.boosts[i] < -6) {
						this.boosts[i] = -6;
					}
					changed = true;
					continue;
				}
				// Stat being modified is not evasion or accuracy, so change modifiedStats.
				if (delta > 0) {
					if (this.modifiedStats![i] === 999) {
						// Intended max stat value
						this.boosts[i] += delta;
						if (this.boosts[i] > 6) {
							this.boosts[i] = 6;
						}
						this.boosts[i]--;
						// changed = 0 corresponds to increasing stats at 999 (or decreasing at 1).
						changed = 0;
					} else {
						this.boosts[i] += delta;
						if (this.boosts[i] > 6) {
							this.boosts[i] = 6;
						}
						changed = true;
					}
				}
				if (delta < 0) {
					if (this.modifiedStats![i] === 1) {
						// Minimum stat value
						this.boosts[i] += delta;
						if (this.boosts[i] < -6) {
							this.boosts[i] = -6;
						}
						this.boosts[i]++;
						// changed = 0 corresponds to increasing stats at 999 (or decreasing at 1).
						changed = 0;
					} else {
						this.boosts[i] += delta;
						if (this.boosts[i] < -6) {
							this.boosts[i] = -6;
						}
						changed = true;
					}
				}
				if (changed) {
					// Recalculate the modified stat
					this.modifiedStats![i] = this.storedStats[i];
					if (this.boosts[i] >= 0) {
						this.modifyStat!(i, [1, 1.5, 2, 2.5, 3, 3.5, 4][this.boosts[i]]);
					} else {
						this.modifyStat!(i, [100, 66, 50, 40, 33, 28, 25][-this.boosts[i]] / 100);
					}
					if (delta > 0 && this.modifiedStats![i] > 999) {
						// Cap the stat at 999
						this.modifiedStats![i] = 999;
					}
				}
			}
			return changed;
		},
		clearBoosts() {
			let i: BoostID;
			for (i in this.boosts) {
				this.boosts[i] = 0;
				// Recalculate the modified stat
				if (i === 'evasion' || i === 'accuracy') continue;
				this.modifiedStats![i] = this.storedStats[i];
			}
		},
	},
	actions: {
		inherit: true,
		// This function is the main one when running a move.
		// It deals with the beforeMove event.
		// It also deals with how PP reduction works on gen 1.
		runMove(moveOrMoveName, pokemon, targetLoc, options) {
			let sourceEffect = options?.sourceEffect;
			const target = this.battle.getTarget(pokemon, moveOrMoveName, targetLoc);
			let move = this.battle.dex.getActiveMove(moveOrMoveName);

			// If a faster partial trapping move misses against a user of Hyper Beam during a recharge turn,
			// the user of Hyper Beam will automatically use Hyper Beam during that turn.
			const autoHyperBeam = (
				move.id === 'recharge' && !pokemon.volatiles['mustrecharge'] && !pokemon.volatiles['partiallytrapped']
			);
			if (autoHyperBeam) {
				move = this.battle.dex.getActiveMove('hyperbeam');
				this.battle.hint(`In Gen 1, If a faster partial trapping move misses against a user of Hyper Beam during a recharge turn, ` +
					`the user of Hyper Beam will automatically use Hyper Beam during that turn.`, true);
			}

			if (target?.subFainted) target.subFainted = null;

			this.battle.setActiveMove(move, pokemon, target);

			if (pokemon.moveThisTurn || !this.battle.runEvent('BeforeMove', pokemon, target, move)) {
				this.battle.clearActiveMove(true);
				// This is only run for sleep.
				this.battle.runEvent('AfterMoveSelf', pokemon, target, move);
				return;
			}
			if (move.beforeMoveCallback) {
				if (move.beforeMoveCallback.call(this.battle, pokemon, target, move)) {
					this.battle.clearActiveMove(true);
					return;
				}
			}
			let lockedMove = this.battle.runEvent('LockMove', pokemon);
			if (lockedMove === true) lockedMove = false;
			if (
				!lockedMove &&
				(!pokemon.volatiles['partialtrappinglock'] || pokemon.volatiles['partialtrappinglock'].locked !== target)
			) {
				pokemon.deductPP(move, null, target);
			} else {
				sourceEffect = move;
				if (pokemon.volatiles['twoturnmove']) {
					// Two-turn moves like Sky Attack deduct PP on their second turn.
					pokemon.deductPP(pokemon.volatiles['twoturnmove'].originalMove, null, target);
				}
			}
			if (
				(pokemon.volatiles['partialtrappinglock'] && target !== pokemon.volatiles['partialtrappinglock'].locked) ||
				autoHyperBeam
			) {
				const moveSlot = pokemon.moveSlots.find(ms => ms.id === move.id);
				if (moveSlot && moveSlot.pp < 0) {
					moveSlot.pp = 63;
					this.battle.hint("In Gen 1, if a player is forced to use a move with 0 PP, the move will underflow to have 63 PP.");
				}
			}
			this.useMove(move, pokemon, { target, sourceEffect });
			// Restore PP if the move is the first turn of a charging move. Save the move from which PP should be deducted if the move succeeds.
			if (pokemon.volatiles['twoturnmove']) {
				pokemon.deductPP(move, -1, target);
				pokemon.volatiles['twoturnmove'].originalMove = move.id;
			}
		},
		// This function deals with AfterMoveSelf events.
		// This leads with partial trapping moves shenanigans after the move has been used.
		useMove(moveOrMoveName, pokemon, options) {
			let sourceEffect = options?.sourceEffect;
			let target = options?.target;
			if (!sourceEffect && this.battle.effect.id) sourceEffect = this.battle.effect;
			const baseMove = this.battle.dex.moves.get(moveOrMoveName);
			let move = this.battle.dex.getActiveMove(baseMove);
			if (target === undefined) target = this.battle.getRandomTarget(pokemon, move);
			if (move.target === 'self') {
				target = pokemon;
			}
			if (sourceEffect) move.sourceEffect = sourceEffect.id;

			this.battle.singleEvent('ModifyMove', move, null, pokemon, target, move, move);
			if (baseMove.target !== move.target) {
				// Target changed in ModifyMove, so we must adjust it here
				target = this.battle.getRandomTarget(pokemon, move);
			}
			move = this.battle.runEvent('ModifyMove', pokemon, target, move, move);
			if (baseMove.target !== move.target) {
				// Check again, this shouldn't ever happen on Gen 1.
				target = this.battle.getRandomTarget(pokemon, move);
			}
			// The charging turn of a two-turn move does not update pokemon.lastMove
			if (!TWO_TURN_MOVES.includes(move.id) || pokemon.volatiles['twoturnmove']) pokemon.lastMove = move;

			const moveResult = this.useMoveInner(moveOrMoveName, pokemon, { target, sourceEffect });

			if (move.id !== 'metronome') {
				if (move.id !== 'mirrormove' ||
					(!pokemon.side.foe.active[0]?.lastMove || pokemon.side.foe.active[0].lastMove?.id === 'mirrormove')) {
					// The move is our 'final' move (a failed Mirror Move, or any move that isn't Metronome or Mirror Move).
					pokemon.side.lastMove = move;

					if (pokemon.volatiles['lockedmove']?.time <= 0) pokemon.removeVolatile('lockedmove');

					// If target fainted
					if (target && target.hp <= 0) {
						// We remove recharge
						if (pokemon.volatiles['mustrecharge']) pokemon.removeVolatile('mustrecharge');
						delete pokemon.volatiles['partialtrappinglock'];
					} else {
						if (pokemon.volatiles['mustrecharge']) this.battle.add('-mustrecharge', pokemon);
						if (pokemon.hp) this.battle.runEvent('AfterMoveSelf', pokemon, target, move);
					}

					// For partial trapping moves, we are saving the target
					if (move.volatileStatus === 'partiallytrapped' && target && target.hp > 0) {
						// Let's check if the lock exists
						if (pokemon.volatiles['partialtrappinglock'] && target.volatiles['partiallytrapped']) {
							// Here the partialtrappinglock volatile has been already applied
							const sourceVolatile = pokemon.volatiles['partialtrappinglock'];
							const targetVolatile = target.volatiles['partiallytrapped'];
							if (!sourceVolatile.locked) {
								// If it's the first hit, we save the target
								sourceVolatile.locked = target;
							} else if (target !== pokemon && target !== sourceVolatile.locked) {
								// Our target switched out! Re-roll the duration, damage, and accuracy.
								const duration = this.battle.sample([2, 2, 2, 3, 3, 3, 4, 5]);
								sourceVolatile.duration = duration;
								sourceVolatile.locked = target;
								// Duration reset thus partially trapped at 2 always.
								targetVolatile.duration = 2;
							}
						} // If we move to here, the move failed and there's no partial trapping lock.
					}
				}
			}
			return moveResult;
		},
		// This is the function that actually uses the move, running ModifyMove events.
		// It uses the move and then deals with the effects after the move.
		useMoveInner(moveOrMoveName, pokemon, options) {
			let sourceEffect = options?.sourceEffect;
			let target = options?.target;
			if (!sourceEffect && this.battle.effect.id) sourceEffect = this.battle.effect;
			const baseMove = this.battle.dex.moves.get(moveOrMoveName);
			let move = this.battle.dex.getActiveMove(baseMove);
			if (target === undefined) target = this.battle.getRandomTarget(pokemon, move);
			if (move.target === 'self') {
				target = pokemon;
			}
			if (sourceEffect) move.sourceEffect = sourceEffect.id;

			this.battle.setActiveMove(move, pokemon, target);

			this.battle.singleEvent('ModifyMove', move, null, pokemon, target, move, move);
			if (baseMove.target !== move.target) {
				// Target changed in ModifyMove, so we must adjust it here
				target = this.battle.getRandomTarget(pokemon, move);
			}
			move = this.battle.runEvent('ModifyMove', pokemon, target, move, move);
			if (baseMove.target !== move.target) {
				// Check again, this shouldn't ever happen on Gen 1.
				target = this.battle.getRandomTarget(pokemon, move);
				this.battle.debug('not a gen 1 mechanic');
			}
			if (!move) return false;

			let attrs = '';
			if (pokemon.fainted) {
				return false;
			}

			if (sourceEffect) attrs += `|[from]${this.battle.dex.conditions.get(sourceEffect)}`;
			this.battle.addMove('move', pokemon, move.name, `${target}${attrs}`);

			if (!this.battle.singleEvent('Try', move, null, pokemon, target, move)) {
				return true;
			}
			if (!this.battle.singleEvent('TryMove', move, null, pokemon, target, move) ||
				!this.battle.runEvent('TryMove', pokemon, target, move)) {
				return true;
			}

			if (move.ignoreImmunity === undefined) {
				move.ignoreImmunity = (move.category === 'Status');
			}

			let damage: number | undefined | false | '' = false;
			if (!target || target.fainted) {
				this.battle.attrLastMove('[notarget]');
				this.battle.add('-notarget');
				return true;
			}
			// Store 0 damage for last damage if the move is not in the array.
			if (!SKIP_LASTDAMAGE.has(move.id)) this.battle.lastDamage = 0;

			damage = this.tryMoveHit(target, pokemon, move);

			// Disable and Selfdestruct/Explosion boost rage, regardless of whether they miss/fail.
			if (target.boosts.atk < 6 && (move.selfdestruct || move.id === 'disable') && target.volatiles['rage']) {
				this.battle.boost({ atk: 1 }, target, pokemon, this.dex.conditions.get('rage'));
				this.battle.hint(`In Gen 1, using ${move.name} causes the target to build Rage, ` +
					`even if it misses or fails`, true);
			}

			// Go ahead with results of the used move.
			if (damage === false) {
				this.battle.singleEvent('MoveFail', move, null, target, pokemon, move);
				return true;
			}

			if (!move.negateSecondary) {
				this.battle.singleEvent('AfterMoveSecondarySelf', move, null, pokemon, target, move);
				this.battle.runEvent('AfterMoveSecondarySelf', pokemon, target, move);
			}
			return true;
		},
		// This function attempts a move hit and returns the attempt result before the actual hit happens.
		// It deals with partial trapping weirdness and accuracy bugs as well.
		tryMoveHit(target, pokemon, move) {
			let damage: number | false | undefined = 0;

			// Add Thrashing effect before the move does damage, or add confusion if Thrash effect is ending
			if (move?.self?.volatileStatus === 'lockedmove') {
				if (pokemon.volatiles['lockedmove']) {
					pokemon.volatiles['lockedmove'].time--;
					if (!pokemon.volatiles['lockedmove'].time) {
						// Confusion begins even if already confused.
						// Remove lockedmove volatile when dealing with after move effects.
						delete pokemon.volatiles['confusion'];
						pokemon.addVolatile('confusion', pokemon, this.dex.conditions.get('lockedmove'));
					}
				} else {
					pokemon.addVolatile('lockedmove', pokemon, move);
				}
			}

			// First, check if the target is semi-invulnerable
			let hitResult = this.battle.runEvent('Invulnerability', target, pokemon, move);
			if (hitResult === false) {
				this.battle.attrLastMove('[miss]');
				this.battle.add('-miss', pokemon);
				if (move.selfdestruct) {
					this.battle.faint(pokemon, pokemon, move);
				}
				return false;
			}

			// Then, check if the Pokémon is immune to this move.
			if (
				(!move.ignoreImmunity || (move.ignoreImmunity !== true && !move.ignoreImmunity[move.type])) &&
				!target.runImmunity(move.type, true)
			) {
				if (move.selfdestruct) {
					this.battle.faint(pokemon, pokemon, move);
				}
				return false;
			}
			hitResult = this.battle.singleEvent('TryImmunity', move, null, target, pokemon, move);
			if (hitResult === false) {
				this.battle.add('-immune', target);
				return false;
			}

			// Now, let's calculate the accuracy.
			let accuracy = move.accuracy;

			// Partial trapping moves: true accuracy while it lasts
			if (move.volatileStatus === 'partiallytrapped' && target === pokemon.volatiles['partialtrappinglock']?.locked) {
				accuracy = true;
			}

			// If a sleep inducing move is used while the user is recharging, the accuracy is true.
			if (move.status === 'slp' && target?.volatiles['mustrecharge']) {
				accuracy = true;
			}

			// OHKO moves only have a chance to hit if the user is at least as fast as the target
			if (move.ohko) {
				if (target.getStat('spe') > pokemon.getStat('spe')) {
					this.battle.add('-immune', target, '[ohko]');
					return false;
				}
			}

			// Calculate true accuracy for gen 1, which uses 0-255.
			// Gen 1 uses the same boost table for accuracy and evasiveness as every other stat
			const boostTable = [25, 28, 33, 40, 50, 66, 100, 150, 200, 250, 300, 350, 400];
			if (accuracy !== true) {
				accuracy = Math.floor(accuracy * 255 / 100);
				// Rage and Thrash/Petal Dance accuracy bug
				if (pokemon.volatiles['lockedmove']) accuracy = pokemon.volatiles['lockedmove'].accuracy;
				if (pokemon.volatiles['rage']) accuracy = pokemon.volatiles['rage'].accuracy;

				// This line is just to satisfy TypeScript, accuracy should never be true at this point
				if (accuracy !== true) {
					// Check also for accuracy modifiers.
					if (!move.ignoreAccuracy) {
						accuracy = Math.floor(accuracy * (boostTable[pokemon.boosts.accuracy + 6] / 100));
					}
					if (!move.ignoreEvasion) {
						accuracy = Math.floor(accuracy * (boostTable[-target.boosts.evasion + 6] / 100));
					}
					accuracy = this.battle.clampIntRange(accuracy, 1, 255);
				}
				if (pokemon.volatiles['lockedmove']) pokemon.volatiles['lockedmove'].accuracy = accuracy;
				if (pokemon.volatiles['rage']) pokemon.volatiles['rage'].accuracy = accuracy;
			}
			accuracy = this.battle.runEvent('Accuracy', target, pokemon, move, accuracy);
			// Moves that target the user do not suffer from the 1/256 miss chance.
			if (move.target === 'self' && accuracy !== true) accuracy++;
			// 1/256 chance of missing always, no matter what. Besides the aforementioned exceptions.
			if (accuracy !== true && !this.battle.randomChance(accuracy, 256)) {
				this.battle.attrLastMove('[miss]');
				this.battle.add('-miss', pokemon);
				if (accuracy === 255) this.battle.hint("In Gen 1, moves with 100% accuracy can still miss 1/256 of the time.");
				damage = false;
				this.battle.lastDamage = 0;
			}

			// If damage is 0 and not false it means it didn't miss, let's calc.
			if (damage !== false) {
				if (move.multihit) {
					let hits = move.multihit;
					if (Array.isArray(hits)) {
						// Yes, it's hardcoded... meh
						if (hits[0] === 2 && hits[1] === 5) {
							hits = this.battle.sample([2, 2, 2, 3, 3, 3, 4, 5]);
						} else {
							hits = this.battle.random(hits[0], hits[1] + 1);
						}
					}
					hits = Math.floor(hits);
					// In gen 1, all the hits have the same damage for multihits move
					let moveDamage: number | undefined | false = 0;
					let i: number;
					for (i = 0; i < hits && target.hp && pokemon.hp; i++) {
						move.hit = i + 1;
						if (move.hit === hits) move.lastHit = true;
						moveDamage = this.moveHit(target, pokemon, move);
						if (moveDamage === false) break;
						damage = (moveDamage || 0);
						// Move damage is fixed to be the first move's damage
						if (i === 0) move.damage = damage;
						if (target.subFainted) {
							i++;
							break;
						}
					}
					move.damage = null;
					if (i === 0) return 1;
					this.battle.add('-hitcount', target, i);
				} else {
					damage = this.moveHit(target, pokemon, move);
				}
			}

			if (move.category !== 'Status') {
				target.gotAttacked(move, damage, pokemon);
			}

			if (move.selfdestruct) {
				if (!target.subFainted) {
					this.battle.faint(pokemon, pokemon, move);
				} else {
					this.battle.hint(`In Gen 1, the user of ${move.name} will not take damage if it breaks a Substitute.`);
				}
			}

			// The move missed.
			if (damage === false) {
				// Delete the partial trap lock if necessary.
				delete pokemon.volatiles['partialtrappinglock'];
				return false;
			}

			if (move.ohko) this.battle.add('-ohko');

			if (!move.negateSecondary) {
				this.battle.singleEvent('AfterMoveSecondary', move, null, target, pokemon, move);
				this.battle.runEvent('AfterMoveSecondary', target, pokemon, move);
			}

			return damage;
		},
		// It deals with the actual move hit, as the name indicates, dealing damage and/or effects.
		// This function also deals with the Gen 1 Substitute behaviour on the hitting process.
		moveHit(target, pokemon, move, moveData, isSecondary, isSelf) {
			let damage: number | false | null | undefined = 0;

			if (!isSecondary && !isSelf) this.battle.setActiveMove(move, pokemon, target);
			let hitResult: number | boolean = true;
			if (!moveData) moveData = move;

			if (move.ignoreImmunity === undefined) {
				move.ignoreImmunity = (move.category === 'Status');
			}

			// We get the sub to the target to see if it existed
			const targetSub = (target) ? target.volatiles['substitute'] : false;
			const targetHadSub = (targetSub !== null && targetSub !== false && (typeof targetSub !== 'undefined'));
			let targetHasSub: boolean | undefined = undefined;

			if (target) {
				hitResult = this.battle.singleEvent('TryHit', moveData, {}, target, pokemon, move);

				// Handle here the applying of partial trapping moves to Pokémon with Substitute
				if (targetSub && moveData.volatileStatus && moveData.volatileStatus === 'partiallytrapped') {
					target.addVolatile(moveData.volatileStatus, pokemon, move);
					if (!pokemon.volatiles['partialtrappinglock'] || pokemon.volatiles['partialtrappinglock'].duration! > 1) {
						target.volatiles[moveData.volatileStatus].duration = 2;
					}
				}

				if (!hitResult) {
					if (hitResult === false) this.battle.add('-fail', target);
					return false;
				}

				// Only run the hit events for the hit itself, not the secondary or self hits
				if (!isSelf && !isSecondary) {
					hitResult = this.battle.runEvent('TryHit', target, pokemon, move);
					if (!hitResult) {
						if (hitResult === false) this.battle.add('-fail', target);
						// Special Substitute hit flag
						if (hitResult !== 0) {
							return false;
						}
					}
					if (!this.battle.runEvent('TryFieldHit', target, pokemon, move)) {
						return false;
					}
				} else if (isSecondary && !moveData.self) {
					hitResult = this.battle.runEvent('TrySecondaryHit', target, pokemon, moveData);
				}

				if (hitResult === 0) {
					targetHasSub = !!(target?.volatiles['substitute']);
					target = null;
				} else if (!hitResult) {
					if (hitResult === false) this.battle.add('-fail', target);
					return false;
				}
			}

			if (target) {
				let didSomething = false;

				damage = this.getDamage(pokemon, target, moveData);

				// getDamage has several possible return values:
				//
				//   a number:
				//     means that much damage is dealt (0 damage still counts as dealing
				//     damage for the purposes of things like Static)
				//   false:
				//     gives error message: "But it failed!" and move ends
				//   null:
				//     the move ends, with no message (usually, a custom fail message
				//     was already output by an event handler)
				//   undefined:
				//     means no damage is dealt and the move continues
				//
				// basically, these values have the same meanings as they do for event
				// handlers.

				if (damage && damage > target.hp) {
					damage = target.hp;
				}
				if ((damage || damage === 0) && !target.fainted) {
					damage = this.battle.damage(damage, target, pokemon, move);
					if (!(damage || damage === 0)) return false;
					didSomething = true;
				} else if (damage === false && typeof hitResult === 'undefined') {
					this.battle.add('-fail', target);
				}
				if (damage === false || damage === null) {
					return false;
				}
				if (moveData.boosts && target.hp) {
					const willBoost = this.battle.boost(moveData.boosts, target, pokemon, move);
					if (!willBoost) {
						this.battle.add('-fail', target);
						return false;
					}
					didSomething = true;
					// Check the status of the Pokémon whose turn is not.
					// When a move that affects stat levels is used, if the Pokémon whose turn it is not right now is paralyzed or
					// burned, the correspoding stat penalties will be applied again to that Pokémon.
					if (pokemon.side.foe.active[0].status) {
						// If it's paralysed, quarter its speed.
						if (pokemon.side.foe.active[0].status === 'par') {
							pokemon.side.foe.active[0].modifyStat!('spe', 0.25);
						}
						// If it's burned, halve its attack.
						if (pokemon.side.foe.active[0].status === 'brn') {
							pokemon.side.foe.active[0].modifyStat!('atk', 0.5);
						}
					}
				}
				if (moveData.heal && !target.fainted) {
					const d = target.heal(Math.floor(target.maxhp * moveData.heal[0] / moveData.heal[1]));
					if (!d) {
						this.battle.add('-fail', target);
						return false;
					}
					this.battle.add('-heal', target, target.getHealth);
					didSomething = true;
				}
				if (moveData.status) {
					// Gen 1 bug: If the target has just used hyperbeam and must recharge, its status will be ignored and put to sleep.
					// This does NOT revert the paralyse speed drop or the burn attack drop.
					// Also, being put to sleep clears the recharge condition.
					if (moveData.status === 'slp' && target.volatiles['mustrecharge']) {
						// The sleep move is guaranteed to hit in this situation, unless Sleep Clause activates.
						// Do not clear recharge in that case.
						if (target.setStatus(moveData.status, pokemon, move)) {
							target.removeVolatile('mustrecharge');
							this.battle.hint(
								"In Gen 1, if a Pokémon that has just used Hyper Beam and has yet to recharge is targeted with a sleep inducing move, " +
								"any other status it may already have will be ignored and sleep will be induced regardless."
							);
						}
					} else if (!target.status) {
						if (target.setStatus(moveData.status, pokemon, move)) {
							// Gen 1 mechanics: The burn attack drop and the paralyse speed drop are applied here directly on stat modifiers.
							if (moveData.status === 'brn') target.modifyStat!('atk', 0.5);
							if (moveData.status === 'par') target.modifyStat!('spe', 0.25);
						}
					} else if (!isSecondary) {
						if (target.status === moveData.status) {
							this.battle.add('-fail', target, target.status);
						} else {
							this.battle.add('-fail', target);
						}
					}
					didSomething = true;
				}
				if (moveData.forceStatus) {
					if (target.setStatus(moveData.forceStatus, pokemon, move)) {
						if (moveData.forceStatus === 'brn') target.modifyStat!('atk', 0.5);
						if (moveData.forceStatus === 'par') target.modifyStat!('spe', 0.25);
						didSomething = true;
					}
				}
				if (moveData.volatileStatus) {
					if (target.addVolatile(moveData.volatileStatus, pokemon, move)) {
						didSomething = true;
					}
				}
				if (moveData.sideCondition) {
					if (target.side.addSideCondition(moveData.sideCondition, pokemon, move)) {
						didSomething = true;
					}
				}
				if (moveData.pseudoWeather) {
					if (this.battle.field.addPseudoWeather(moveData.pseudoWeather, pokemon, move)) {
						didSomething = true;
					}
				}
				// Hit events
				hitResult = this.battle.singleEvent('Hit', moveData, {}, target, pokemon, move);
				if (!isSelf && !isSecondary) {
					this.battle.runEvent('Hit', target, pokemon, move);
				}
				if (!hitResult && !didSomething) {
					if (hitResult === false) this.battle.add('-fail', target);
					return false;
				}
			}
			if (targetHasSub === undefined) targetHasSub = !!(target?.volatiles['substitute']);

			// Here's where self effects are applied.
			const doSelf = (targetHadSub && targetHasSub) || !targetHadSub;
			if (moveData.self && (moveData.self.volatileStatus !== 'lockedmove') &&
				(doSelf || moveData.self.volatileStatus === 'partialtrappinglock')) {
				this.moveHit(pokemon, pokemon, move, moveData.self, isSecondary, true);
			}

			// Now we can save the partial trapping damage.
			if (pokemon.volatiles['partialtrappinglock']) {
				pokemon.volatiles['partialtrappinglock'].damage = this.battle.lastDamage;
			}

			// Apply move secondaries.
			if (moveData.secondaries && target && target.hp > 0) {
				for (const secondary of moveData.secondaries) {
					// Multi-hit moves only roll for status once
					if (!move.multihit || move.lastHit) {
						// We check here whether to negate the probable secondary status if it's para, burn, or freeze.
						// In the game, this is checked and if true, the random number generator is not called.
						// That means that a move that does not share the type of the target can status it.
						// If a move that was not fire-type would exist on Gen 1, it could burn a Pokémon.
						if (!(secondary.status && ['par', 'brn', 'frz'].includes(secondary.status) && target.hasType(move.type))) {
							if (secondary.chance === undefined) {
								this.moveHit(target, pokemon, move, secondary, true, isSelf);
							} else {
								let secondaryChance = Math.ceil(secondary.chance * 256 / 100);
								// If the secondary effect is confusion, the numerator should be decreased by 1 (10% = 25/256 not 26/256).
								if (secondary?.volatileStatus === 'confusion') secondaryChance--;
								if (this.battle.randomChance(secondaryChance, 256)) {
									this.moveHit(target, pokemon, move, secondary, true, isSelf);
								}
							}
						}
					}
				}
			}
			if (move.selfSwitch && pokemon.hp) {
				pokemon.switchFlag = move.selfSwitch === true ? true : this.dex.toID(move.selfSwitch);
			}

			return damage;
		},
		// This calculates the damage pokemon does to target with move.
		getDamage(source, target, move, suppressMessages) {
			// First of all, we get the move.
			if (typeof move === 'string') {
				move = this.battle.dex.getActiveMove(move);
			} else if (typeof move === 'number') {
				move = {
					basePower: move,
					type: '???',
					category: 'Physical',
					willCrit: false,
					flags: {},
				} as ActiveMove;
			}

			// Let's see if the target is immune to the move.
			if (!move.ignoreImmunity || (move.ignoreImmunity !== true && !move.ignoreImmunity[move.type])) {
				if (!target.runImmunity(move.type, true)) {
					return false;
				}
			}

			// Is it an OHKO move?
			if (move.ohko) {
				return 65535;
			}

			// We edit the damage through move's damage callback if necessary.
			if (move.damageCallback) {
				return move.damageCallback.call(this.battle, source, target);
			}

			// We take damage from damage=level moves (seismic toss).
			if (move.damage === 'level') {
				return source.level;
			}

			// If there's a fix move damage, we return that.
			if (move.damage || move.damage === 0) {
				return move.damage;
			}

			// If it's the first hit on a Normal-type partially trap move, it hits Ghosts anyways but damage is 0.
			if (move.volatileStatus === 'partiallytrapped' && move.type === 'Normal' && target.hasType('Ghost')) {
				return 0;
			}

			// Let's check if we are in middle of a partial trap sequence to return the previous damage.
			if (source.volatiles['partialtrappinglock'] && (target === source.volatiles['partialtrappinglock'].locked)) {
				return source.volatiles['partialtrappinglock'].damage;
			}

			// We check the category and typing to calculate later on the damage.
			if (!move.category) move.category = 'Physical';
			// '???' is typeless damage: used for Struggle and Confusion etc
			if (!move.type) move.type = '???';
			const type = move.type;

			// We get the base power and apply basePowerCallback if necessary.
			let basePower: number | false | null = move.basePower;
			if (move.basePowerCallback) {
				basePower = move.basePowerCallback.call(this.battle, source, target, move);
			}
			if (!basePower) {
				return basePower === 0 ? undefined : basePower;
			}
			basePower = this.battle.clampIntRange(basePower, 1);

			// Checking for the move's Critical Hit possibility. We check if it's a 100% crit move, otherwise we calculate the chance.
			let isCrit = move.willCrit || false;
			if (!isCrit) {
				// In gen 1, the critical chance is based on speed.
				// First, we get the base speed, divide it by 2 and floor it. This is our current crit chance.
				let critChance = Math.floor(this.dex.species.get(source.set.species).baseStats['spe'] / 2);

				// Now we check for focus energy volatile.
				if (source.volatiles['focusenergy']) {
					// If it exists, crit chance is divided by 2 again and floored.
					critChance = Math.floor(critChance / 2);
				} else {
					// Normally, without focus energy, crit chance is multiplied by 2 and capped at 255 here.
					critChance = this.battle.clampIntRange(critChance * 2, 1, 255);
				}

				// Now we check for the move's critical hit ratio.
				if (move.critRatio === 1) {
					// Normal hit ratio, we divide the crit chance by 2 and floor the result again.
					critChance = Math.floor(critChance / 2);
				} else if (move.critRatio === 2) {
					// High crit ratio, we multiply the result so far by 4 and cap it at 255.
					critChance = this.battle.clampIntRange(critChance * 4, 1, 255);
				}

				// Last, we check deppending on ratio if the move critical hits or not.
				// We compare our critical hit chance against a random number between 0 and 255.
				// If the random number is lower, we get a critical hit. This means there is always a 1/255 chance of not hitting critically.
				if (critChance > 0) {
					isCrit = this.battle.randomChance(critChance, 256);
				}
			}
			if (isCrit) target.getMoveHitData(move).crit = true;

			// Happens after crit calculation.
			if (basePower) {
				basePower = this.battle.runEvent('BasePower', source, target, move, basePower);
				if (basePower && move.basePowerModifier) {
					basePower *= move.basePowerModifier;
				}
			}
			if (!basePower) return 0;
			basePower = this.battle.clampIntRange(basePower, 1);

			// We now check attacker's and defender's stats.
			let level = source.level;
			const attacker = move.overrideOffensivePokemon === 'target' ? target : source;
			const defender = move.overrideDefensivePokemon === 'source' ? source : target;

			const isPhysical = move.category === 'Physical';
			const atkType: StatIDExceptHP = move.overrideOffensiveStat || (isPhysical ? 'atk' : 'spa');
			const defType: StatIDExceptHP = move.overrideDefensiveStat || (isPhysical ? 'def' : 'spd');

			let attack = attacker.getStat(atkType);
			let defense = defender.getStat(defType);

			// In gen 1, screen effect is applied here.
			if ((defType === 'def' && defender.volatiles['reflect']) || (defType === 'spd' && defender.volatiles['lightscreen'])) {
				this.battle.debug('Screen doubling (Sp)Def');
				defense *= 2;
			}

			// In the event of a critical hit, the offense and defense changes are ignored.
			// This includes both boosts and screens.
			// Also, level is doubled in damage calculation.
			if (isCrit) {
				move.ignoreOffensive = true;
				move.ignoreDefensive = true;
				level *= 2;
				if (!suppressMessages) this.battle.add('-crit', target);
			}

			if (move.ignoreOffensive) {
				this.battle.debug('Negating (sp)atk boost/penalty.');
				attack = attacker.getStat(atkType, true);
			}

			if (move.ignoreDefensive) {
				this.battle.debug('Negating (sp)def boost/penalty.');
				// No screens
				defense = target.getStat(defType, true);
			}

			// When either attack or defense are higher than 256, both are divided by 4.
			// If that's still over 256, it rolls over (%256), which is what causes rollover bugs.
			if (attack >= 256 || defense >= 256) {
				if (attack >= 1024 || defense >= 1024) {
					this.battle.hint("In Gen 1, a stat will roll over to a small number if it is larger than 1024.");
				}
				attack = this.battle.clampIntRange(Math.floor(attack / 4) % 256, 1);
				// Defense isn't checked on the cartridge, but we don't want those / 0 bugs on the sim.
				defense = Math.floor(defense / 4) % 256;
				if (defense === 0) {
					this.battle.hint('Pokemon Showdown avoids division by zero by rounding defense up to 1. ' +
						'In game, the battle would have crashed.');
					defense = 1;
				}
			}

			// Self destruct moves halve defense at this point.
			if (move.selfdestruct && defType === 'def') {
				defense = this.battle.clampIntRange(Math.floor(defense / 2), 1);
			}

			// Let's go with the calculation now that we have what we need.
			// We do it step by step just like the game does.
			let damage = level * 2;
			damage = Math.floor(damage / 5);
			damage += 2;
			damage *= basePower;
			damage *= attack;
			damage = Math.floor(damage / defense);
			damage = this.battle.clampIntRange(Math.floor(damage / 50), 0, 997);
			damage += 2;

			// STAB damage bonus, the "???" type never gets STAB
			if (type !== '???' && source.hasType(type)) {
				damage += Math.floor(damage / 2);
			}

			// Type effectiveness.
			// In Gen 1, type effectiveness is applied against each of the target's types.
			for (const targetType of target.types) {
				let typeMod = this.battle.dex.getEffectiveness(type, targetType);
				typeMod = this.battle.runEvent('Effectiveness', this.battle, targetType, move, typeMod);
				if (typeMod > 0) {
					// Super effective against targetType
					damage *= 20;
					damage = Math.floor(damage / 10);
				}
				if (typeMod < 0) {
					// Not very effective against targetType
					damage *= 5;
					damage = Math.floor(damage / 10);
				}
			}
			const totalTypeMod = target.runEffectiveness(move);
			if (totalTypeMod > 0) {
				if (!suppressMessages) this.battle.add('-supereffective', target);
			}
			if (totalTypeMod < 0) {
				if (!suppressMessages) this.battle.add('-resisted', target);
			}

			// If damage becomes 0, the move is made to miss.
			// This occurs when damage was either 2 or 3 prior to applying STAB/Type matchup, and target is 4x resistant to the move.
			if (damage === 0) return damage;

			// Apply random factor if damage is greater than 1
			if (damage > 1) {
				damage *= this.battle.random(217, 256);
				damage = Math.floor(damage / 255);
			}

			// And we are done.
			return Math.floor(damage);
		},
	},
	// deals with Pokémon stat boosting.
	boost(boost, target, source = null, effect = null) {
		if (this.event) {
			if (!target) target = this.event.target;
			if (!source) source = this.event.source;
			if (!effect) effect = this.effect;
		}
		if (typeof effect === 'string') effect = this.dex.conditions.get(effect);
		if (!target?.hp) return 0;
		let success = null;
		boost = this.runEvent('TryBoost', target, source, effect, { ...boost });
		let i: BoostID;
		for (i in boost) {
			const currentBoost: SparseBoostsTable = {};
			currentBoost[i] = boost[i];
			if (boost[i] !== 0) {
				const boostResult = target.boostBy(currentBoost);
				if (boostResult) {
					success = true;
					let msg = '-boost';
					if (boost[i]! < 0) {
						msg = '-unboost';
						boost[i] = -boost[i]!;
					}
					if (!effect || effect.effectType === 'Move') {
						this.add(msg, target, i, boost[i]);
					} else {
						this.add(msg, target, i, boost[i], '[from] ' + effect.fullname);
					}
					this.runEvent('AfterEachBoost', target, source, effect, currentBoost);
				}
				// Tried to boost at 999 or unboost at 1. This does not count as a success: par/brn effects are not applied afterwards.
				if (boostResult === 0) {
					let msg = '-boost';
					let secondmsg = '-unboost';
					if (boost[i]! < 0) {
						msg = '-unboost';
						secondmsg = '-boost';
						boost[i] = -boost[i]!;
					}
					if (!effect || effect.effectType === 'Move') {
						this.add(msg, target, i, boost[i]);
					} else {
						this.add(msg, target, i, boost[i], '[from] ' + effect.fullname);
					}
					this.add(secondmsg, target, i, 1);
					if (msg === '-boost') {
						this.hint(`In Gen 1, boosting a stat at 999 will apply a -1 drop afterwards, and the stat remains at 999.`, true);
					} else {
						this.hint(`In Gen 1, dropping a stat at 1 will apply a +1 boost afterwards, and the stat remains at 1.`, true);
					}
					this.runEvent('AfterEachBoost', target, source, effect, currentBoost);
				}
			}
		}
		this.runEvent('AfterBoost', target, source, effect, boost);
		return success;
	},
};