@pkmn/engine 0.1.0-dev.89cc1c98 → 0.1.0-dev.8b192003

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 (42) hide show
  1. package/LICENSE +1 -3
  2. package/README.md +44 -19
  3. package/build/pkg/addon.js +6 -7
  4. package/build/pkg/addon.js.map +1 -1
  5. package/build/pkg/data.d.ts +4 -4
  6. package/build/pkg/data.js.map +1 -1
  7. package/build/pkg/gen1.js.map +1 -1
  8. package/build/pkg/index.d.ts +2 -1
  9. package/build/pkg/index.js.map +1 -1
  10. package/build/pkg/protocol.js +5 -5
  11. package/build/pkg/protocol.js.map +1 -1
  12. package/build/test/display.js +2 -2
  13. package/build/test/display.js.map +1 -1
  14. package/build/tools/debug.js +5 -4
  15. package/build/tools/debug.js.map +1 -1
  16. package/build.zig +100 -102
  17. package/package.json +31 -23
  18. package/src/bin/install-pkmn-engine +26 -18
  19. package/src/lib/common/data.zig +4 -4
  20. package/src/lib/common/debug.zig +2 -2
  21. package/src/lib/common/optional.zig +1 -1
  22. package/src/lib/common/options.zig +2 -0
  23. package/src/lib/common/protocol.zig +393 -396
  24. package/src/lib/common/rational.zig +109 -15
  25. package/src/lib/common/rng.zig +20 -11
  26. package/src/lib/gen1/calc.zig +478 -92
  27. package/src/lib/gen1/chance.zig +195 -428
  28. package/src/lib/gen1/data/species.zig +1 -1
  29. package/src/lib/gen1/data.zig +21 -21
  30. package/src/lib/gen1/helpers.zig +88 -534
  31. package/src/lib/gen1/mechanics.zig +377 -360
  32. package/src/lib/gen2/chance.zig +25 -457
  33. package/src/lib/gen2/data.zig +13 -11
  34. package/src/lib/gen2/generated.zig +27 -78
  35. package/src/lib/gen2/helpers.zig +37 -7
  36. package/src/lib/gen2/mechanics.zig +1022 -456
  37. package/src/lib/{binding/node.zig → node.zig} +9 -12
  38. package/src/lib/pkmn.zig +27 -12
  39. package/src/lib/{binding/wasm.zig → wasm.zig} +2 -1
  40. package/src/test/display/pkmn.html.tmpl +5 -5
  41. package/src/lib/gen1/README.md +0 -894
  42. package/src/lib/gen2/README.md +0 -330
@@ -239,7 +239,7 @@ pub const DVs = packed struct(u16) {
239
239
  };
240
240
  const p: u6 = @intCast(1 + ((5 * @as(u8, (dvs.atk & 0b1000) | ((dvs.def & 0b1000)) >> 1 |
241
241
  ((dvs.spe & 0b1000)) >> 2 | ((dvs.spc & 0b1000)) >> 3) + (dvs.spc & 0b0011)) >> 1));
242
- var t = @as(u8, dvs.def & 0b0011) + (@as(u8, dvs.atk & 0b0011) << 2);
242
+ const t = @as(u8, dvs.def & 0b0011) + (@as(u8, dvs.atk & 0b0011) << 2);
243
243
  return .{ .gender = g, .pow = p, .type = Type.hiddenPower(t) };
244
244
  }
245
245
  };
@@ -248,12 +248,12 @@ test DVs {
248
248
  var dvs = DVs.from(.Mewtwo, .{ .def = 13 });
249
249
  try expectEqual(Gender.Unknown, dvs.gender);
250
250
  try expectEqual(Type.Ice, dvs.type);
251
- try expectEqual(@as(u8, 70), dvs.power());
251
+ try expectEqual(70, dvs.power());
252
252
 
253
253
  dvs = DVs.from(.Pikachu, .{ .spc = 14 });
254
254
  try expectEqual(Gender.Male, dvs.gender);
255
255
  try expectEqual(Type.Dark, dvs.type);
256
- try expectEqual(@as(u8, 69), dvs.power());
256
+ try expectEqual(69, dvs.power());
257
257
 
258
258
  dvs = DVs.from(.Cyndaquil, .{ .atk = 1, .def = 3, .spe = 10, .spc = 9 });
259
259
  try expectEqual(Gender.Female, dvs.gender);
@@ -314,16 +314,18 @@ pub const Volatiles = packed struct(u128) {
314
314
  Encore: bool = false,
315
315
  LockOn: bool = false,
316
316
  DestinyBond: bool = false,
317
+ Trapped: bool = false,
317
318
 
318
- trapped: bool = false,
319
319
  switched: bool = false,
320
+ // TODO: replace with simply looking at Choice on Battle
320
321
  switching: bool = false,
322
+ // TODO: simply store both last moves on side
321
323
  dirty: bool = false,
322
324
 
323
325
  frozen: bool = false,
324
326
  minimized: bool = false,
325
327
  _: u10 = 0,
326
- // TODO?
328
+ // TODO: is this needed for unmodified stats?
327
329
  transform: u4 = 0,
328
330
 
329
331
  bide: u16 = 0,
@@ -378,9 +380,9 @@ test Stats {
378
380
  try expectEqual(0, stats.def);
379
381
 
380
382
  var base = Species.get(.Gyarados).stats;
381
- try expectEqual(@as(u16, 127), Stats(u16).calc("hp", base.hp, 0, 5120, 38));
383
+ try expectEqual(127, Stats(u16).calc("hp", base.hp, 0, 5120, 38));
382
384
  base = Species.get(.Pidgeot).stats;
383
- try expectEqual(@as(u16, 279), Stats(u16).calc("spe", base.spe, 15, 63001, 100));
385
+ try expectEqual(279, Stats(u16).calc("spe", base.spe, 15, 63001, 100));
384
386
  }
385
387
 
386
388
  pub const Boosts = packed struct(u32) {
@@ -426,8 +428,8 @@ test Move {
426
428
  try expectEqual(Move.Effect.ConfusionChance, move.effect);
427
429
  try expectEqual(rng.Gen12.percent(50), move.accuracy);
428
430
  try expectEqual(rng.Gen12.percent(100), move.chance);
429
- try expectEqual(@as(u8, 5), Move.pp(.DynamicPunch));
430
- try expectEqual(@as(i8, -1), Move.get(.Counter).priority);
431
+ try expectEqual(5, Move.pp(.DynamicPunch));
432
+ try expectEqual(-1, Move.get(.Counter).priority);
431
433
  try expect(!move.effect.isHighCritical());
432
434
  try expect(Move.get(.KarateChop).effect.isHighCritical());
433
435
  try expect(Move.get(.RazorWind).effect.isHighCritical());
@@ -437,7 +439,7 @@ pub const Species = species.Species;
437
439
 
438
440
  test Species {
439
441
  try expectEqual(152, @intFromEnum(Species.Chikorita));
440
- try expectEqual(@as(u8, 100), Species.get(.Celebi).stats.spd);
442
+ try expectEqual(100, Species.get(.Celebi).stats.spd);
441
443
  }
442
444
 
443
445
  pub const Type = types.Type;
@@ -450,7 +452,7 @@ test Types {
450
452
  try expect(!Type.Steel.special());
451
453
  try expect(Type.Dark.special());
452
454
 
453
- try expectEqual(@as(u8, 3), Type.Water.precedence());
455
+ try expectEqual(3, Type.Water.precedence());
454
456
  try expect(Type.Bug.precedence() > Type.Poison.precedence());
455
457
 
456
458
  try expectEqual(Effectiveness.Super, Type.effectiveness(.Ghost, .Psychic));
@@ -7,8 +7,6 @@ const common = @import("../common/data.zig");
7
7
  const data = @import("data.zig");
8
8
  const mechanics = @import("mechanics.zig");
9
9
 
10
- const assert = std.debug.assert;
11
-
12
10
  const Player = common.Player;
13
11
  const Result = common.Result;
14
12
 
@@ -42,7 +40,7 @@ pub fn runMove(battle: anytype, player: Player, state: *State, options: anytype)
42
40
  var log = options.log;
43
41
 
44
42
  var side = battle.side(player);
45
- var volatiles = &side.active.volatiles;
43
+ const volatiles = &side.active.volatiles;
46
44
 
47
45
  const ident = battle.active(player);
48
46
  const foe_ident = battle.active(player.foe());
@@ -97,17 +95,8 @@ pub fn runMove(battle: anytype, player: Player, state: *State, options: anytype)
97
95
  },
98
96
  .OHKO => {
99
97
  if (!try canMove(battle, player, state, options)) return;
100
-
101
- // OHKO calls adjustDamage but only actually cares about immunity
102
- assert(move.type == .Normal or move.type == .Ground);
103
- const foe = battle.foe(player);
104
- const immune = if (move.type == .Normal)
105
- foe.active.types.includes(.Ghost) and !foe.active.volatiles.Foresight
106
- else
107
- foe.active.types.includes(.Ghost);
108
- state.effectiveness = if (immune) 0 else Effectiveness.neutral;
109
-
110
- try Effects.ohko(battle, player, state, options);
98
+ try Effects.ohko.immune(battle, player, state, options);
99
+ try Effects.ohko.damage(battle, player, state, options);
111
100
  try applyDamage(battle, player, state, options);
112
101
  try reportOutcome(battle, player, state, options);
113
102
  _ = try afterMove(!KINGS, battle, player, state, options);
@@ -118,9 +107,7 @@ pub fn runMove(battle: anytype, player: Player, state: *State, options: anytype)
118
107
  try calcDamage(battle, player, state, options);
119
108
  try adjustDamage(battle, player, state, options);
120
109
  try randomizeDamage(battle, player, state, options);
121
-
122
- if (battle.foe(player).active.volatiles.Flying) state.damage *|= 2;
123
-
110
+ try Effects.double.flying(battle, player, state, options);
124
111
  try checkHit(battle, player, state, options);
125
112
  try applyDamage(battle, player, state, options);
126
113
  try reportOutcome(battle, player, state, options);
@@ -157,9 +144,7 @@ pub fn runMove(battle: anytype, player: Player, state: *State, options: anytype)
157
144
  try calcDamage(battle, player, state, options);
158
145
  try adjustDamage(battle, player, state, options);
159
146
  try randomizeDamage(battle, player, state, options);
160
-
161
- if (battle.foe(player).active.volatiles.minimized) state.damage *|= 2;
162
-
147
+ try Effects.double.minimize(battle, player, state, options);
163
148
  try checkHit(battle, player, state, options);
164
149
  try effectChance(battle, player, state, options);
165
150
  try applyDamage(battle, player, state, options);
@@ -188,7 +173,7 @@ pub fn runMove(battle: anytype, player: Player, state: *State, options: anytype)
188
173
  if (!skip_pp) _ = decrementPP(side, state.move, state.mslot); // TODO if no pp return
189
174
  // TODO rampage
190
175
  // usedmovetext
191
- try log.move(ident, state.move, foe_ident); // FIXME self? from?
176
+ try log.move(.{ ident, state.move, foe_ident }); // FIXME self? from?
192
177
  try checkHit(battle, player, state, options);
193
178
  try checkCriticalHit(battle, player, state, options);
194
179
  try calcDamage(battle, player, state, options);
@@ -273,7 +258,7 @@ pub fn runMove(battle: anytype, player: Player, state: *State, options: anytype)
273
258
  try Effects.hyperBeam(battle, player, state, options);
274
259
  _ = try afterMove(!KINGS, battle, player, state, options);
275
260
  },
276
- .Counter => {
261
+ .MirrorCoat, .Counter => {
277
262
  if (!try canMove(battle, player, state, options)) return;
278
263
  try Effects.counter(battle, player, state, options);
279
264
  try applyDamage(battle, player, state, options);
@@ -308,7 +293,6 @@ pub fn runMove(battle: anytype, player: Player, state: *State, options: anytype)
308
293
  if (!try canMove(battle, player, state, options)) return;
309
294
  try checkCriticalHit(battle, player, state, options);
310
295
  try calcDamage(battle, player, state, options);
311
- // TODO thunderaccuracy
312
296
  try checkHit(battle, player, state, options);
313
297
  try effectChance(battle, player, state, options);
314
298
  try adjustDamage(battle, player, state, options);
@@ -324,9 +308,7 @@ pub fn runMove(battle: anytype, player: Player, state: *State, options: anytype)
324
308
  try calcDamage(battle, player, state, options);
325
309
  try adjustDamage(battle, player, state, options);
326
310
  try randomizeDamage(battle, player, state, options);
327
-
328
- if (battle.foe(player).active.volatiles.Underground) state.damage *|= 2;
329
-
311
+ try Effects.double.underground(battle, player, state, options);
330
312
  try checkHit(battle, player, state, options);
331
313
  try effectChance(battle, player, state, options);
332
314
  try applyDamage(battle, player, state, options);
@@ -339,13 +321,10 @@ pub fn runMove(battle: anytype, player: Player, state: *State, options: anytype)
339
321
  try calcDamage(battle, player, state, options);
340
322
  try adjustDamage(battle, player, state, options);
341
323
  try checkHit(battle, player, state, options);
342
-
343
- assert(volatiles.Rage);
344
- state.damage *|= (volatiles.rage +| 1);
345
-
324
+ try Effects.rage.damage(battle, player, state, options);
346
325
  try randomizeDamage(battle, player, state, options);
347
326
  // TODO failuretext
348
- try Effects.rage(battle, player, state, options);
327
+ try Effects.rage.start(battle, player, state, options);
349
328
  try applyDamage(battle, player, state, options);
350
329
  try reportOutcome(battle, player, state, options);
351
330
  _ = try afterMove(KINGS, battle, player, state, options);
@@ -363,47 +342,32 @@ pub fn runMove(battle: anytype, player: Player, state: *State, options: anytype)
363
342
  if (!try canMove(battle, player, state, options)) return;
364
343
  try Effects.heal(battle, player, state, options);
365
344
  },
345
+ .LightScreen => {
346
+ if (!try canMove(battle, player, state, options)) return;
347
+ try Effects.lightScreen(battle, player, state, options);
348
+ },
366
349
  .Haze => {
367
350
  if (!try canMove(battle, player, state, options)) return;
368
351
  try Effects.haze(battle, player, state, options);
369
352
  },
370
- .LightScreen, .Reflect => {
353
+ .Reflect => {
371
354
  if (!try canMove(battle, player, state, options)) return;
372
- try Effects.screens(battle, player, state, options);
355
+ try Effects.reflect(battle, player, state, options);
373
356
  },
374
357
  .FocusEnergy => {
375
358
  if (!try canMove(battle, player, state, options)) return;
376
359
  try Effects.focusEnergy(battle, player, state, options);
377
360
  },
378
361
  .Bide => {
379
- if (!volatiles.Bide) return;
380
-
381
- assert(volatiles.attacks > 0);
382
- // if (options.calc.modify(player, .attacking)) |extend| {
383
- // if (!extend) volatiles.attacks = 0;
384
- // } else {
385
- volatiles.attacks -= 1;
386
- // }
387
- try options.chance.attacking(player, volatiles.attacks);
388
-
389
- if (volatiles.attacks != 0) return try options.log.activate(ident, .Bide);
390
-
391
- volatiles.Bide = false;
392
- try options.log.end(ident, .Bide);
393
-
394
- state.damage = volatiles.bide *% 2;
395
- volatiles.bide = 0;
396
-
397
- if (state.damage == 0) state.miss = true;
398
-
362
+ try Effects.bide.store(battle, player, state, options);
399
363
  // doturn
400
364
  const charging = false; // TODO
401
365
  const skip_pp = charging or state.move == .Struggle or
402
366
  (volatiles.BeatUp or volatiles.Thrashing or volatiles.Bide);
403
367
  if (!skip_pp) _ = decrementPP(side, state.move, state.mslot); // TODO if no pp return
404
368
  // usedmovetext
405
- try log.move(ident, state.move, foe_ident); // FIXME self? from?
406
- // TODO unleashenergy
369
+ try log.move(.{ ident, state.move, foe_ident }); // FIXME self? from?
370
+ try Effects.bide.unleash(battle, player, state, options);
407
371
 
408
372
  if (state.immune()) {
409
373
  state.damage = 0;
@@ -522,7 +486,7 @@ pub fn runMove(battle: anytype, player: Player, state: *State, options: anytype)
522
486
  try randomizeDamage(battle, player, state, options);
523
487
  try checkHit(battle, player, state, options);
524
488
  try effectChance(battle, player, state, options);
525
- try Effects.snore(battle, player, state, options);
489
+ if (!try Effects.snore(battle, player, state, options)) return;
526
490
  try applyDamage(battle, player, state, options);
527
491
  try reportOutcome(battle, player, state, options);
528
492
  if (!try afterMove(!KINGS, battle, player, state, options)) return;
@@ -582,19 +546,19 @@ pub fn runMove(battle: anytype, player: Player, state: *State, options: anytype)
582
546
  try Effects.sandstorm(battle, player, state, options);
583
547
  },
584
548
  .Rollout => {
585
- // TODO checkcurl
549
+ try Effects.rollout.check(battle, player, state, options);
586
550
  // doturn
587
551
  const charging = false; // TODO
588
552
  const skip_pp = charging or state.move == .Struggle or
589
553
  (volatiles.BeatUp or volatiles.Thrashing or volatiles.Bide);
590
554
  if (!skip_pp) _ = decrementPP(side, state.move, state.mslot); // TODO if no pp return
591
555
  // usedmovetext
592
- try log.move(ident, state.move, foe_ident); // FIXME self? from?
556
+ try log.move(.{ ident, state.move, foe_ident }); // FIXME self? from?
593
557
  try checkCriticalHit(battle, player, state, options);
594
558
  try calcDamage(battle, player, state, options);
595
559
  try adjustDamage(battle, player, state, options);
596
560
  try checkHit(battle, player, state, options);
597
- // TODO rolloutpower
561
+ try Effects.rollout.power(battle, player, state, options);
598
562
  try randomizeDamage(battle, player, state, options);
599
563
  try applyDamage(battle, player, state, options);
600
564
  try reportOutcome(battle, player, state, options);
@@ -615,12 +579,7 @@ pub fn runMove(battle: anytype, player: Player, state: *State, options: anytype)
615
579
  .Swagger => {
616
580
  if (!try canMove(battle, player, state, options)) return;
617
581
  try checkHit(battle, player, state, options);
618
- // TODO switchturn
619
- // TODO attackup2
620
- // TODO switchturn
621
- // TODO failuretext
622
- // TODO switchturn
623
- // TODO switchturn
582
+ try Effects.boost(battle, player.foe(), state, options);
624
583
  try Effects.confusionChance(battle, player, state, options);
625
584
  },
626
585
  .FuryCutter => {
@@ -694,14 +653,12 @@ pub fn runMove(battle: anytype, player: Player, state: *State, options: anytype)
694
653
  .Magnitude => {
695
654
  if (!try canMove(battle, player, state, options)) return;
696
655
  try checkCriticalHit(battle, player, state, options);
697
- // TODO getmagnitude
656
+ try Effects.magnitude(battle, player, state, options);
698
657
  try calcDamage(battle, player, state, options);
699
658
  try adjustDamage(battle, player, state, options);
700
659
  try randomizeDamage(battle, player, state, options);
701
660
  try checkHit(battle, player, state, options);
702
-
703
- if (battle.foe(player).active.volatiles.Underground) state.damage *|= 2;
704
-
661
+ try Effects.double.underground(battle, player, state, options);
705
662
  try applyDamage(battle, player, state, options);
706
663
  try reportOutcome(battle, player, state, options);
707
664
  _ = try afterMove(KINGS, battle, player, state, options);
@@ -755,9 +712,7 @@ pub fn runMove(battle: anytype, player: Player, state: *State, options: anytype)
755
712
  try calcDamage(battle, player, state, options);
756
713
  try adjustDamage(battle, player, state, options);
757
714
  try randomizeDamage(battle, player, state, options);
758
-
759
- if (battle.foe(player).active.volatiles.Flying) state.damage *|= 2;
760
-
715
+ try Effects.double.flying(battle, player, state, options);
761
716
  try checkHit(battle, player, state, options);
762
717
  try effectChance(battle, player, state, options);
763
718
  try applyDamage(battle, player, state, options);
@@ -773,12 +728,6 @@ pub fn runMove(battle: anytype, player: Player, state: *State, options: anytype)
773
728
  if (!try canMove(battle, player, state, options)) return;
774
729
  try Effects.sunnyDay(battle, player, state, options);
775
730
  },
776
- .MirrorCoat => {
777
- if (!try canMove(battle, player, state, options)) return;
778
- try Effects.mirrorCoat(battle, player, state, options);
779
- try applyDamage(battle, player, state, options);
780
- _ = try afterMove(KINGS, battle, player, state, options);
781
- },
782
731
  .PsychUp => {
783
732
  if (!try canMove(battle, player, state, options)) return;
784
733
  try Effects.psychUp(battle, player, state, options);
@@ -65,17 +65,47 @@ pub const Battle = struct {
65
65
  };
66
66
 
67
67
  pub fn prng(rand: *PSRNG) data.PRNG {
68
- // FIXME: what is max range in gen2?
69
68
  return .{
70
69
  .src = .{
71
70
  .seed = if (showdown)
72
71
  rand.newSeed()
73
- else
74
- .{
75
- rand.range(u8, 0, 256), rand.range(u8, 0, 256), rand.range(u8, 0, 256),
76
- rand.range(u8, 0, 256), rand.range(u8, 0, 256), rand.range(u8, 0, 256),
77
- rand.range(u8, 0, 256), rand.range(u8, 0, 256), rand.range(u8, 0, 256),
78
- },
72
+ else seed: {
73
+ // GLITCH: The initial RNG seed in Gen II link battles is heavily constrained
74
+ // https://www.smogon.com/forums/threads/rng-in-rby-link-battles.3746779
75
+ var seed = [_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0 };
76
+
77
+ // Bytes in seed can only range from 0-252, not 0-255
78
+ const max: u8 = 253;
79
+ // Instructions take multiples of 4 cycles to execute
80
+ var timer: u16 = 4 * rand.range(u16, 0, 16384);
81
+ var add: u8 = rand.range(u8, 0, 256);
82
+ var sub: u8 = rand.range(u8, 0, 256);
83
+ var carry: u8 = 0;
84
+ var div: u8 = 0;
85
+
86
+ var i: u8 = 0;
87
+ while (i < seed.len) {
88
+ div = @intCast(timer >> 8);
89
+ // This is effectively @addWithOverflow but with three operands
90
+ const result = @as(u16, add) + @as(u16, div) + @as(u16, carry);
91
+ add = @intCast(result & 255);
92
+ carry = @intFromBool(result > 255);
93
+ timer +%= 44;
94
+ div = @intCast(timer >> 8);
95
+ sub -%= div +% carry;
96
+
97
+ if (sub < max) {
98
+ seed[i] = sub;
99
+ i += 1;
100
+ timer +%= 152;
101
+ carry = 1;
102
+ } else {
103
+ timer +%= 132;
104
+ carry = 0;
105
+ }
106
+ }
107
+ break :seed seed;
108
+ },
79
109
  },
80
110
  };
81
111
  }