@pkmn/engine 0.1.0-dev.3a62fd35 → 0.1.0-dev.3de821b9

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.
@@ -256,7 +256,6 @@ fn switchIn(battle: anytype, player: Player, slot: u8, kind: SwitchIn, options:
256
256
  } else {
257
257
  try options.log.switched(.{ battle.active(player), incoming });
258
258
  }
259
- options.chance.switched(player, side.pokemon[0].position, out);
260
259
 
261
260
  if (side.conditions.Spikes and !active.types.includes(.Flying)) {
262
261
  incoming.hp -|= @max(incoming.stats.hp / 8, 1);
@@ -442,18 +441,9 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
442
441
  if (Status.is(stored.status, .SLP)) slp: {
443
442
  const before = stored.status;
444
443
  // Even if the EXT bit is set this will still correctly modify the sleep duration
445
- // if (options.calc.modify(player, .sleep)) |extend| {
446
- // if (!extend) stored.status = 0;
447
- // } else {
448
444
  stored.status -= 1;
449
- // }
450
-
451
- const duration = Status.duration(stored.status);
452
- if (!Status.is(stored.status, .EXT)) {
453
- try options.chance.sleep(player, duration);
454
- }
455
445
 
456
- if (duration == 0) {
446
+ if (Status.duration(stored.status) == 0) {
457
447
  try log.curestatus(.{ ident, before, .Message });
458
448
  stored.status = 0; // clears EXT if present
459
449
  resetCant(volatiles);
@@ -483,12 +473,7 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
483
473
  }
484
474
 
485
475
  if (volatiles.disable.duration > 0) {
486
- // if (options.calc.modify(player, .disable)) |extend| {
487
- // if (!extend) volatiles.disable.duration = 0;
488
- // } else {
489
476
  volatiles.disable.duration -= 1;
490
- // }
491
- try options.chance.disable(player, volatiles.disable.duration);
492
477
 
493
478
  if (volatiles.disable.duration == 0) {
494
479
  volatiles.disable.move = 0;
@@ -498,13 +483,7 @@ fn beforeMove(battle: anytype, player: Player, move: Move, options: anytype) !bo
498
483
 
499
484
  if (volatiles.Confusion) {
500
485
  assert(volatiles.confusion > 0);
501
-
502
- // if (options.calc.modify(player, .confusion)) |extend| {
503
- // if (!extend) volatiles.confusion = 0;
504
- // } else {
505
486
  volatiles.confusion -= 1;
506
- // }
507
- try options.chance.confusion(player, volatiles.confusion);
508
487
 
509
488
  if (volatiles.confusion == 0) {
510
489
  volatiles.Confusion = false;
@@ -1304,14 +1283,32 @@ pub const Effects = struct {
1304
1283
  }
1305
1284
 
1306
1285
  pub fn bellyDrum(battle: anytype, player: Player, state: *State, options: anytype) !void {
1307
- _ = .{ battle, player, state, options }; // TODO
1308
- // assert(!state.miss);
1309
- // var side = battle.side(player);
1310
- // try boostStat(battle, side, &side.active.stats, &side.active.boosts, "atk", 2);
1311
- // TODO
1312
- // |-boost|p1a: Poliwag|atk|2|[silent]
1313
- // |-fail|p1a: Poliwag
1314
- //
1286
+ assert(!state.miss);
1287
+ var side = battle.side(player);
1288
+
1289
+ const ident = battle.active(player);
1290
+
1291
+ assert(side.active.boosts.atk >= -6 and side.active.boosts.atk <= 6);
1292
+ if (side.active.boosts.atk == 6 or side.active.boosts.atk == MAX_STAT_VALUE) {
1293
+ return options.log.fail(.{ ident, .None });
1294
+ }
1295
+
1296
+ const hp = @max(side.stored().stats.hp / 2, 1);
1297
+ if (side.stored().hp <= hp) {
1298
+ try boostStat(battle, side, &side.active.stats, &side.active.boosts, "atk", 2);
1299
+ try options.log.boost(.{ ident, .Attack, @as(i8, 2), .Silent });
1300
+ return options.log.fail(.{ ident, .None });
1301
+ } else {
1302
+ side.stored().hp -= hp;
1303
+ try options.log.damage(.{ ident, side.stored(), .None });
1304
+
1305
+ const before = side.active.boosts.atk;
1306
+ var i: usize = 0;
1307
+ while (i < 6 and side.active.stats.atk < MAX_STAT_VALUE) : (i += 1) {
1308
+ try boostStat(battle, side, &side.active.stats, &side.active.boosts, "atk", 2);
1309
+ }
1310
+ try options.log.boost(.{ ident, .Attack, @as(i8, side.active.boosts.atk - before) });
1311
+ }
1315
1312
  }
1316
1313
 
1317
1314
  pub const bide = struct {
@@ -1323,13 +1320,7 @@ pub const Effects = struct {
1323
1320
 
1324
1321
  if (!volatiles.Bide) return;
1325
1322
 
1326
- // if (options.calc.modify(player, .attacking)) |extend| {
1327
- // if (!extend) volatiles.attacks = 0;
1328
- // } else {
1329
1323
  volatiles.attacks -= 1;
1330
- // }
1331
- try options.chance.attacking(player, volatiles.attacks);
1332
-
1333
1324
  if (volatiles.attacks != 0) return options.log.activate(.{ ident, .Bide });
1334
1325
 
1335
1326
  volatiles.Bide = false;
@@ -1596,8 +1587,16 @@ pub const Effects = struct {
1596
1587
  try options.log.start(.{ foe_ident, .Encore, move.id });
1597
1588
  }
1598
1589
 
1599
- pub fn explode(battle: anytype, player: Player, state: *State, options: anytype) !void {
1600
- _ = .{ battle, player, state, options }; // TODO
1590
+ pub fn explode(battle: anytype, player: Player, _: *State, _: anytype) !void {
1591
+ var side = battle.side(player);
1592
+ var stored = side.stored();
1593
+
1594
+ stored.hp = 0;
1595
+ // Pokémon Showdown sets the status to 0 on faint(), and we need to do the same to be able
1596
+ // to correctly implement Pokémon Showdown's dumb speed-based switch mechanics
1597
+ if (!showdown) stored.status = 0;
1598
+ side.active.volatiles.LeechSeed = false;
1599
+ side.active.volatiles.DestinyBond = false;
1601
1600
  }
1602
1601
 
1603
1602
  pub fn falseSwipe(battle: anytype, player: Player, state: *State, _: anytype) !void {
@@ -1723,8 +1722,26 @@ pub const Effects = struct {
1723
1722
  foe.active.volatiles.frozen = true;
1724
1723
  }
1725
1724
 
1726
- pub fn furyCutter(battle: anytype, player: Player, state: *State, options: anytype) !void {
1727
- _ = .{ battle, player, state, options }; // TODO
1725
+ // TODO FIXME showdown calls this before calcDamage (after checkHit) and modifies state.bp!
1726
+ pub fn furyCutter(battle: anytype, player: Player, state: *State, _: anytype) !void {
1727
+ var side = battle.side(player);
1728
+ var volatiles = &side.active.volatiles;
1729
+
1730
+ if (state.miss) {
1731
+ volatiles.fury_cutter = 0;
1732
+ return;
1733
+ }
1734
+
1735
+ assert(volatiles.fury_cutter >= 0 and volatiles.fury_cutter <= 5);
1736
+ if (volatiles.fury_cutter == 5) return;
1737
+ volatiles.fury_cutter += 1;
1738
+
1739
+ if (showdown) {
1740
+ state.bp *= 2;
1741
+ assert(state.bp <= 160);
1742
+ } else {
1743
+ state.damage *|= 2;
1744
+ }
1728
1745
  }
1729
1746
 
1730
1747
  pub fn futureSight(battle: anytype, player: Player, state: *State, options: anytype) !void {
@@ -1885,7 +1902,29 @@ pub const Effects = struct {
1885
1902
  }
1886
1903
 
1887
1904
  pub fn mimic(battle: anytype, player: Player, state: *State, options: anytype) !void {
1888
- _ = .{ battle, player, state, options }; // TODO
1905
+ var side = battle.side(player);
1906
+
1907
+ if (state.miss) {
1908
+ if (!showdown) return options.log.fail(.{ battle.active(player.foe()), .None });
1909
+ try options.log.lastmiss(.{});
1910
+ return options.log.miss(.{battle.active(player)});
1911
+ }
1912
+
1913
+ const move = battle.foe(player).lastMove(false);
1914
+ if (move == .None or move == .Struggle) {
1915
+ return options.log.fail(.{ battle.active(player.foe()), .None });
1916
+ }
1917
+
1918
+ var slot: u4 = 0;
1919
+ for (side.active.moves, 0..) |m, i| {
1920
+ if (m.id == move) return options.log.fail(.{ battle.active(player.foe()), .None });
1921
+ if (m.id == .Mimic) slot = @intCast(i + 1);
1922
+ }
1923
+ assert(slot != 0 and slot == state.mslot);
1924
+
1925
+ side.active.move(slot).id = move;
1926
+ side.active.move(slot).pp = 5;
1927
+ try options.log.start(.{ battle.active(player), .Mimic, move });
1889
1928
  }
1890
1929
 
1891
1930
  pub fn mirrorMove(
@@ -2393,17 +2432,37 @@ pub const Effects = struct {
2393
2432
  }
2394
2433
 
2395
2434
  pub fn spite(battle: anytype, player: Player, state: *State, options: anytype) !void {
2396
- _ = .{ battle, player, state, options }; // TODO
2397
- // var foe = battle.foe(player);
2435
+ var foe = battle.foe(player);
2398
2436
 
2399
- // if (state.miss) return options.log.fail(.{ battle.active(player), .None });
2437
+ const last = foe.lastMove(false);
2438
+ if (state.miss or last == .None or last == .Struggle) {
2439
+ return options.log.fail(.{ battle.active(player.foe()), .None });
2440
+ }
2400
2441
 
2401
- // const last = foe.lastMove(false);
2402
- // if (showdown) {
2403
- // const roll = try Rolls.spite(battle, player, options);
2404
- // } else {
2405
- // if (last != .None) {}
2406
- // }
2442
+ var slot: u4 = 0;
2443
+ for (foe.active.moves, 0..) |m, i| {
2444
+ if (m.id == last) slot = @intCast(i + 1);
2445
+ }
2446
+ // If the move was called via Metronome it might not exist
2447
+ if (slot == 0 or foe.active.move(slot).pp == 0) {
2448
+ return options.log.fail(.{ battle.active(player.foe()), .None });
2449
+ }
2450
+
2451
+ // Pokémon Showdown always deducts 4 instead of randomizing
2452
+ const pp = @min(
2453
+ foe.active.move(slot).pp,
2454
+ if (showdown) 4 else try Rolls.spite(battle, player, options),
2455
+ );
2456
+ foe.active.move(slot).pp -= pp;
2457
+ if (!foe.active.volatiles.Transform) {
2458
+ var move_slot = foe.stored().move(slot);
2459
+ assert(move_slot.pp > 0);
2460
+
2461
+ move_slot.pp = @as(u6, @intCast(move_slot.pp)) -% 1;
2462
+ assert(foe.active.move(slot).pp == foe.stored().move(slot).pp);
2463
+ }
2464
+
2465
+ try options.log.activate(.{ battle.active(player.foe()), .Spite, last, pp });
2407
2466
  }
2408
2467
 
2409
2468
  pub fn splash(battle: anytype, player: Player, _: *State, options: anytype) !void {
@@ -3009,13 +3068,12 @@ pub const Rolls = struct {
3009
3068
  return hits;
3010
3069
  }
3011
3070
 
3012
- fn spite(battle: anytype, player: Player, options: anytype) !u8 {
3013
- const pp = if (options.calc.overridden(player, .spite)) |val|
3071
+ fn spite(battle: anytype, player: Player, options: anytype) !u3 {
3072
+ assert(!showdown);
3073
+ const pp: u3 = if (options.calc.overridden(player, .spite)) |val|
3014
3074
  val + 1
3015
- else if (showdown)
3016
- battle.rng.range(u8, 2, 6)
3017
3075
  else
3018
- (battle.rng.next() & 3) + 2;
3076
+ @intCast((battle.rng.next() & 3) + 2);
3019
3077
 
3020
3078
  assert(pp >= 2 and pp <= 5);
3021
3079
  try options.chance.spite(player, pp);
@@ -3277,7 +3335,7 @@ pub const Rolls = struct {
3277
3335
  };
3278
3336
 
3279
3337
  assert(duration >= 1 and duration <= 6);
3280
- options.chance.duration(.sleep, player, player.foe(), duration);
3338
+ options.chance.duration(player, duration);
3281
3339
  return duration + 1;
3282
3340
  }
3283
3341
 
@@ -3291,7 +3349,7 @@ pub const Rolls = struct {
3291
3349
  @intCast((battle.rng.next() & if (self) 1 else 3) + 2);
3292
3350
 
3293
3351
  assert(duration >= 2 and duration <= if (!showdown and self) 3 else 5);
3294
- options.chance.duration(.confusion, player, if (self) player else player.foe(), duration);
3352
+ options.chance.duration(player, duration);
3295
3353
  return duration;
3296
3354
  }
3297
3355
 
@@ -3309,7 +3367,7 @@ pub const Rolls = struct {
3309
3367
  };
3310
3368
 
3311
3369
  assert(duration >= 2 and duration <= (if (showdown) 5 else 7));
3312
- options.chance.duration(.disable, player, player.foe(), duration);
3370
+ options.chance.duration(player, duration);
3313
3371
  return duration;
3314
3372
  }
3315
3373
 
@@ -3322,7 +3380,7 @@ pub const Rolls = struct {
3322
3380
  @intCast((battle.rng.next() & 1) + 2); // BUG: thrash is + 1 not +2
3323
3381
 
3324
3382
  assert(duration >= 2 and duration <= 3);
3325
- options.chance.duration(.attacking, player, player, duration);
3383
+ options.chance.duration(player, duration);
3326
3384
  return duration;
3327
3385
  }
3328
3386
 
@@ -3336,7 +3394,7 @@ pub const Rolls = struct {
3336
3394
  @intCast(battle.rng.next() & 3 + 2));
3337
3395
 
3338
3396
  assert(duration >= 2 and duration <= 5);
3339
- options.chance.duration(.binding, player, player.foe(), duration);
3397
+ options.chance.duration(player, duration);
3340
3398
  return duration + 1;
3341
3399
  }
3342
3400
 
@@ -3350,7 +3408,7 @@ pub const Rolls = struct {
3350
3408
  @intCast(battle.rng.next() & 3 + 2));
3351
3409
 
3352
3410
  assert(duration >= 2 and duration <= 5);
3353
- options.chance.duration(.encore, player, player.foe(), duration);
3411
+ options.chance.duration(player, duration);
3354
3412
  return duration + 1;
3355
3413
  }
3356
3414
  };
package/src/lib/pkmn.zig CHANGED
@@ -26,9 +26,7 @@ pub const Result = @import("common/data.zig").Result;
26
26
  pub const PSRNG = @import("common/rng.zig").PSRNG;
27
27
 
28
28
  /// Namespace for helpers related to protocol message logging.
29
- pub const protocol = if (options.internal) struct {
30
- pub usingnamespace @import("common/protocol.zig");
31
- } else struct {
29
+ pub const protocol = if (options.internal) @import("common/protocol.zig") else struct {
32
30
  /// Logs protocol information to its `Writer` during a battle update when
33
31
  /// `options.log` is enabled.
34
32
  pub const Log = @import("common/protocol.zig").Log;
@@ -82,9 +80,7 @@ pub const gen1 = struct {
82
80
  /// via `overrides` and tracks `summaries` of information relevant to damage calculation.
83
81
  pub const Calc = @import("gen1/calc.zig").Calc;
84
82
  /// Namespace for types associated with supported Generation I Pokémon damage calc features.
85
- pub const calc = if (options.internal) struct {
86
- pub usingnamespace @import("gen1/calc.zig");
87
- } else struct {
83
+ pub const calc = if (options.internal) @import("gen1/calc.zig") else struct {
88
84
  /// TODO
89
85
  pub const Overrides = @import("gen1/calc.zig").Overrides;
90
86
  /// Information relevant to damage calculation that occured during a Generation I
@@ -114,9 +110,7 @@ pub const gen2 = struct {
114
110
  pub const NULL = @import("gen2/chance.zig").NULL;
115
111
  };
116
112
  pub const Calc = @import("gen2/calc.zig").Calc;
117
- pub const calc = if (options.internal) struct {
118
- pub usingnamespace @import("gen2/calc.zig");
119
- } else struct {
113
+ pub const calc = if (options.internal) @import("gen2/calc.zig") else struct {
120
114
  pub const Overrides = @import("gen2/calc.zig").Overrides;
121
115
  pub const Summaries = @import("gen2/calc.zig").Summaries;
122
116
  pub const Summary = @import("gen2/calc.zig").Summary;
@@ -127,9 +121,7 @@ pub const gen2 = struct {
127
121
 
128
122
  // Internal APIs used by other pkmn libraries, not actually part of the public API.
129
123
  // NOTE: `pub usingnamespace struct { ... }` here results in a (false?) dependency loop
130
- pub const js = if (options.internal) struct {
131
- pub usingnamespace @import("common/js.zig");
132
- } else struct {};
124
+ pub const js = if (options.internal) @import("common/js.zig") else struct {};
133
125
  pub const bindings = if (options.internal) struct {
134
126
  pub const node = @import("node.zig");
135
127
  pub const wasm = @import("wasm.zig");
@@ -218,8 +218,9 @@
218
218
  border-radius: 0 4px 4px 0;
219
219
  }
220
220
  .moves ul {
221
- column-count: 2;
222
- column-gap: 2em;
221
+ display: grid;
222
+ grid-template-columns: repeat(2, auto);
223
+ gap: 0 2em;
223
224
  }
224
225
  .left {
225
226
  padding-right: 32px;
@@ -268,7 +269,7 @@
268
269
  }
269
270
  @media(max-width: 1200px) {
270
271
  .moves ul {
271
- column-count: 1;
272
+ grid-template-columns: repeat(1, auto);
272
273
  }
273
274
  }
274
275
  @media(max-width: 1100px) {
@@ -283,7 +284,7 @@
283
284
  margin-left: 5px;
284
285
  }
285
286
  .moves ul {
286
- column-count: 2;
287
+ grid-template-columns: repeat(2, auto);
287
288
  }
288
289
  .left {
289
290
  padding-right: 0;
@@ -309,7 +310,6 @@
309
310
  justify-content: center;
310
311
  }
311
312
  .moves ul {
312
- column-count: 1;
313
313
  display: inline-block;
314
314
  padding: 0;
315
315
  }