@pkmn/engine 0.1.0-dev.f3aaee39 → 0.1.0-dev.f918c3bd

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.
@@ -33,7 +33,7 @@ pub const Type = enum(u8) {
33
33
  Dragon,
34
34
  Dark,
35
35
 
36
- const CHART = [18][18]Effectiveness{
36
+ const CHART: [18][18]Effectiveness = .{
37
37
  [_]Effectiveness{ N, N, N, N, N, R, N, I, R, N, N, N, N, N, N, N, N, N }, // Normal
38
38
  [_]Effectiveness{ S, N, R, R, N, S, R, I, S, N, N, N, N, N, R, S, N, S }, // Fighting
39
39
  [_]Effectiveness{ N, S, N, N, N, R, S, N, R, N, N, N, S, R, N, N, N, N }, // Flying
@@ -5,6 +5,7 @@ const pkmn = @import("../pkmn.zig");
5
5
 
6
6
  const data = @import("../common/data.zig");
7
7
  const DEBUG = @import("../common/debug.zig").print;
8
+ const protocol = @import("../common/protocol.zig");
8
9
  const rng = @import("../common/rng.zig");
9
10
  const util = @import("../common/util.zig");
10
11
 
@@ -15,6 +16,8 @@ const types = @import("data/types.zig");
15
16
 
16
17
  const gen1 = @import("../gen1/data.zig");
17
18
 
19
+ const calc = @import("calc.zig");
20
+ const chance = @import("chance.zig");
18
21
  const mechanics = @import("mechanics.zig");
19
22
 
20
23
  const assert = std.debug.assert;
@@ -33,6 +36,9 @@ pub const LOGS_SIZE = if (builtin.mode == .ReleaseSmall)
33
36
  else
34
37
  std.math.ceilPowerOfTwo(usize, MAX_LOGS) catch unreachable;
35
38
 
39
+ /// Null object pattern implementation of pkmn.battle.Options for Generation II.
40
+ pub const NULL = pkmn.battle.options(protocol.NULL, chance.NULL, calc.NULL);
41
+
36
42
  const Choice = data.Choice;
37
43
  const ID = data.ID;
38
44
  const Player = data.Player;
@@ -53,6 +59,9 @@ pub fn Battle(comptime RNG: anytype) type {
53
59
  turn: u16 = 0,
54
60
  field: Field = .{},
55
61
 
62
+ _1: u8 = 0,
63
+ _2: if (!showdown) u16 else u32 = 0,
64
+
56
65
  pub inline fn side(self: anytype, player: Player) PointerType(@TypeOf(self), Side) {
57
66
  assert(@typeInfo(@TypeOf(self)).Pointer.child == Self);
58
67
  return &self.sides[@intFromEnum(player)];
@@ -77,35 +86,34 @@ pub fn Battle(comptime RNG: anytype) type {
77
86
  };
78
87
  }
79
88
 
80
- // TODO
81
89
  test Battle {
82
- try expectEqual(496, @sizeOf(Battle(PRNG)));
90
+ try expectEqual(512, @sizeOf(Battle(PRNG)));
83
91
  }
84
92
 
85
- const Field = packed struct {
93
+ pub const Field = packed struct(u8) {
86
94
  weather: Weather = .None,
87
95
  weather_duration: u4 = 0,
88
-
89
- comptime {
90
- assert(@sizeOf(Field) == 1);
91
- }
92
96
  };
93
97
 
94
- const Weather = enum(u4) {
98
+ pub const Weather = enum(u4) {
95
99
  None,
96
100
  Rain,
97
101
  Sun,
98
102
  Sandstorm,
99
103
  };
100
104
 
101
- const Side = extern struct {
105
+ pub const Side = extern struct {
102
106
  pokemon: [6]Pokemon = [_]Pokemon{.{}} ** 6,
103
- active: ActivePokemon = .{},
104
- conditions: Conditions,
107
+ active: ActivePokemon align(8) = .{},
108
+ conditions: Conditions = .{},
109
+ _1: u32 = 0,
110
+ _2: u16 = 0,
111
+ // NB: wCurPlayerMove / wCurEnemyMove (?)
112
+ _3: u8 = 0,
113
+ // NB: wLastPlayerCounterMove / wLastEnemyCounterMove (wLastPlayerMove / wLastEnemyMove)
105
114
  last_used_move: Move = .None,
106
- _: u8 = 0,
107
115
 
108
- const Conditions = packed struct {
116
+ const Conditions = packed struct(u32) {
109
117
  Spikes: bool = false,
110
118
  Safeguard: bool = false,
111
119
  LightScreen: bool = false,
@@ -115,13 +123,16 @@ const Side = extern struct {
115
123
  light_screen: u4 = 0,
116
124
  reflect: u4 = 0,
117
125
 
118
- comptime {
119
- assert(@sizeOf(Conditions) == 2);
120
- }
126
+ future_sight: FutureSight = .{},
127
+
128
+ const FutureSight = packed struct(u16) {
129
+ damage: u12 = 0,
130
+ count: u4 = 0,
131
+ };
121
132
  };
122
133
 
123
134
  comptime {
124
- assert(@sizeOf(Side) == 240);
135
+ assert(@sizeOf(Side) == 248);
125
136
  }
126
137
 
127
138
  pub inline fn get(self: anytype, slot: u8) PointerType(@TypeOf(self), Pokemon) {
@@ -136,17 +147,30 @@ const Side = extern struct {
136
147
  assert(@typeInfo(@TypeOf(self)).Pointer.child == Side);
137
148
  return self.get(1);
138
149
  }
150
+
151
+ pub inline fn lastMove(self: anytype, encore: bool) Move {
152
+ assert(self.last_used_move != .None or !self.active.volatiles.dirty);
153
+ if (encore) return self.last_used_move;
154
+ return if (self.active.volatiles.dirty) .None else self.last_used_move;
155
+ }
156
+
157
+ // TODO
158
+ // pub inline fn useMove(self: anytype) void {
159
+ // assert(side.last_selected_move != .None);
160
+ // self.last_used_move = side.last_selected_move;
161
+ // self.active.volatiles.dirty = false;
162
+ // }
139
163
  };
140
164
 
141
165
  // NOTE: DVs (Gender & Hidden Power) and Happiness are stored only in Pokemon
142
- const ActivePokemon = extern struct {
143
- volatiles: Volatile align(4) = .{},
166
+ pub const ActivePokemon = extern struct {
167
+ volatiles: Volatiles align(4) = .{},
144
168
  stats: Stats(u16) = .{},
145
169
  moves: [4]MoveSlot = [_]MoveSlot{.{}} ** 4,
146
170
  boosts: Boosts = .{},
171
+ types: Types = .{},
147
172
  species: Species = .None,
148
- item: Item = .None,
149
- last_move: Move = .None,
173
+
150
174
  _: u8 = 0,
151
175
 
152
176
  comptime {
@@ -161,7 +185,7 @@ const ActivePokemon = extern struct {
161
185
  }
162
186
  };
163
187
 
164
- const Pokemon = extern struct {
188
+ pub const Pokemon = extern struct {
165
189
  stats: Stats(u16) = .{},
166
190
  moves: [4]MoveSlot = [_]MoveSlot{.{}} ** 4,
167
191
  types: Types = .{},
@@ -192,7 +216,7 @@ pub const Gender = enum(u2) {
192
216
  Unknown,
193
217
  };
194
218
 
195
- const DVs = packed struct {
219
+ pub const DVs = packed struct(u16) {
196
220
  gender: Gender = .Male,
197
221
  pow: u6 = 40,
198
222
  type: Type = .Dark,
@@ -200,7 +224,7 @@ const DVs = packed struct {
200
224
  pub fn init(g: Gender, p: u6, t: Type) DVs {
201
225
  assert(p >= 1 and p <= 40);
202
226
  assert(t != .Normal and t != .@"???");
203
- return DVs{ .gender = g, .pow = p, .type = t };
227
+ return .{ .gender = g, .pow = p, .type = t };
204
228
  }
205
229
 
206
230
  pub inline fn power(dvs: DVs) u8 {
@@ -219,11 +243,7 @@ const DVs = packed struct {
219
243
  ((dvs.spe & 0b1000)) >> 2 | ((dvs.spc & 0b1000)) >> 3) + (dvs.spc & 0b0011)) >> 1));
220
244
  var t = @as(u8, dvs.def & 0b0011) + (@as(u8, dvs.atk & 0b0011) << 2) + 1;
221
245
  if (t >= @intFromEnum(Type.@"???")) t = t + 1;
222
- return DVs{ .gender = g, .pow = p, .type = @enumFromInt(t) };
223
- }
224
-
225
- comptime {
226
- assert(@sizeOf(DVs) == 2);
246
+ return .{ .gender = g, .pow = p, .type = @enumFromInt(t) };
227
247
  }
228
248
  };
229
249
 
@@ -248,10 +268,12 @@ test DVs {
248
268
  try expectEqual(Gender.Female, dvs.gender);
249
269
  }
250
270
 
251
- const MoveSlot = packed struct {
271
+ /// Representation of a Generation II Pokémon's move slot in a battle.
272
+ pub const MoveSlot = extern struct {
273
+ /// The identifier for the move.
252
274
  id: Move = .None,
253
- pp_ups: u2 = 3,
254
- pp: u6 = 0,
275
+ /// The remaining PP of the move.
276
+ pp: u8 = 0,
255
277
 
256
278
  comptime {
257
279
  assert(@sizeOf(MoveSlot) == 2);
@@ -260,14 +282,16 @@ const MoveSlot = packed struct {
260
282
 
261
283
  pub const Status = gen1.Status;
262
284
 
263
- const Volatile = packed struct {
285
+ pub const Volatiles = packed struct(u128) {
264
286
  Bide: bool = false,
265
287
  Thrashing: bool = false,
288
+ BeatUp: bool = false,
266
289
  Flinch: bool = false,
267
290
  Charging: bool = false,
268
291
  Underground: bool = false,
269
292
  Flying: bool = false,
270
293
  Confusion: bool = false,
294
+
271
295
  Mist: bool = false,
272
296
  FocusEnergy: bool = false,
273
297
  Substitute: bool = false,
@@ -278,27 +302,28 @@ const Volatile = packed struct {
278
302
  Transform: bool = false,
279
303
 
280
304
  Nightmare: bool = false,
281
- Cure: bool = false,
305
+ Curse: bool = false,
282
306
  Protect: bool = false,
283
307
  Foresight: bool = false,
284
308
  PerishSong: bool = false,
285
309
  Endure: bool = false,
286
310
  Rollout: bool = false,
287
311
  Attract: bool = false,
312
+
288
313
  DefenseCurl: bool = false,
289
314
  Encore: bool = false,
290
315
  LockOn: bool = false,
291
316
  DestinyBond: bool = false,
292
- BeatUp: bool = false,
293
317
 
294
318
  trapped: bool = false,
295
319
  switched: bool = false,
296
320
  switching: bool = false,
321
+ dirty: bool = false,
297
322
 
298
- _: u5 = 0,
323
+ frozen: bool = false,
324
+ _: u19 = 0,
299
325
 
300
326
  bind: u4 = 0,
301
- future_sight: FutureSight = .{},
302
327
  bide: u16 = 0,
303
328
  disabled: Disabled = .{},
304
329
  rage: u8 = 0,
@@ -306,24 +331,16 @@ const Volatile = packed struct {
306
331
  toxic: u8 = 0,
307
332
  confusion: u4 = 0,
308
333
  encore: u4 = 0,
334
+ // TODO merge with attacks (ie rollout/thrashing)? need to be zeroed at same time
309
335
  fury_cutter: u4 = 0,
310
336
  perish_song: u4 = 0,
311
337
  protect: u4 = 0,
312
- rollout: u4 = 0,
338
+ attacks: u4 = 0, // bide/thrashing/rollout turns
313
339
 
314
- const Disabled = packed struct {
340
+ const Disabled = packed struct(u8) {
315
341
  move: u4 = 0,
316
342
  duration: u4 = 0,
317
343
  };
318
-
319
- const FutureSight = packed struct {
320
- damage: u12 = 0,
321
- count: u4 = 0,
322
- };
323
-
324
- comptime {
325
- assert(@sizeOf(Volatile) == 16);
326
- }
327
344
  };
328
345
 
329
346
  pub fn Stats(comptime T: type) type {
@@ -358,7 +375,7 @@ test Stats {
358
375
  try expectEqual(@as(u16, 279), Stats(u16).calc("spe", base.spe, 15, 63001, 100));
359
376
  }
360
377
 
361
- pub const Boosts = packed struct {
378
+ pub const Boosts = packed struct(u32) {
362
379
  atk: i4 = 0,
363
380
  def: i4 = 0,
364
381
  spe: i4 = 0,
@@ -374,7 +391,7 @@ pub const Boosts = packed struct {
374
391
  };
375
392
 
376
393
  test Boosts {
377
- const boosts = Boosts{ .spd = -6 };
394
+ const boosts: Boosts = .{ .spd = -6 };
378
395
  try expectEqual(0, boosts.atk);
379
396
  try expectEqual(-6, boosts.spd);
380
397
  }
@@ -404,8 +421,13 @@ test Move {
404
421
  try expectEqual(251, @intFromEnum(Move.BeatUp));
405
422
  const move = Move.get(.DynamicPunch);
406
423
  try expectEqual(Move.Effect.ConfusionChance, move.effect);
407
- try expectEqual(@as(u8, 50), move.accuracy);
408
- try expectEqual(@as(u8, 5), move.pp);
424
+ try expectEqual(rng.Gen12.percent(50), move.accuracy);
425
+ try expectEqual(rng.Gen12.percent(100), move.chance);
426
+ try expectEqual(@as(u8, 5), Move.pp(.DynamicPunch));
427
+ try expectEqual(@as(i8, -1), Move.get(.Counter).priority);
428
+ try expect(!move.effect.isHighCritical());
429
+ try expect(Move.get(.KarateChop).effect.isHighCritical());
430
+ try expect(Move.get(.RazorWind).effect.isHighCritical());
409
431
  }
410
432
 
411
433
  pub const Species = species.Species;
@@ -48,7 +48,7 @@ pub const Battle = struct {
48
48
  comptime rolls: anytype,
49
49
  p1: []const Pokemon,
50
50
  p2: []const Pokemon,
51
- ) data.Battle(rng.FixedRNG(1, rolls.len)) {
51
+ ) data.Battle(rng.FixedRNG(2, rolls.len)) {
52
52
  return .{
53
53
  .rng = .{ .rolls = rolls },
54
54
  .sides = .{ Side.init(p1), Side.init(p2) },
@@ -87,7 +87,7 @@ pub const Side = struct {
87
87
 
88
88
  for (0..ps.len) |i| {
89
89
  side.pokemon[i] = Pokemon.init(ps[i]);
90
- // FIXME side.order[i] = i + 1;
90
+ side.pokemon[i].position = @as(u4, @intCast(i)) + 1;
91
91
  }
92
92
  return side;
93
93
  }
@@ -98,7 +98,7 @@ pub const Side = struct {
98
98
 
99
99
  for (0..n) |i| {
100
100
  side.pokemon[i] = Pokemon.random(rand, opt);
101
- side.pokemon[i].position = i + 1;
101
+ side.pokemon[i].position = @as(u4, @intCast(i)) + 1;
102
102
  }
103
103
 
104
104
  return side;
@@ -135,12 +135,9 @@ pub const Pokemon = struct {
135
135
  }
136
136
  assert(p.moves.len > 0 and p.moves.len <= 4);
137
137
  for (p.moves, 0..) |m, j| {
138
- pokemon.moves[j] = .{
139
- .id = m,
140
- .pp_ups = 3,
141
- // NB: PP can be at most 61 legally (though can overflow to 63)
142
- .pp = @intCast(@min(Move.pp(m) / 5 * 8, 61)),
143
- };
138
+ pokemon.moves[j].id = m;
139
+ // NB: PP can be at most 61 legally (though can overflow to 63)
140
+ pokemon.moves[j].pp = @intCast(@min(Move.pp(m) / 5 * 8, 61));
144
141
  }
145
142
  pokemon.item = p.item;
146
143
  if (p.hp) |hp| {
@@ -151,7 +148,7 @@ pub const Pokemon = struct {
151
148
  pokemon.status = p.status;
152
149
  pokemon.types = species.types;
153
150
  pokemon.level = p.level;
154
- pokemon.dvs = DVs.from(p.dvs);
151
+ pokemon.dvs = DVs.from(p.species, p.dvs);
155
152
  return pokemon;
156
153
  }
157
154
 
@@ -192,14 +189,13 @@ pub const Pokemon = struct {
192
189
  const max_pp: u8 = @intCast(Move.pp(m) + pp_ups * @min(Move.pp(m) / 5, 7));
193
190
  ms[i] = .{
194
191
  .id = m,
195
- .pp_ups = pp_ups,
196
192
  .pp = if (opt.cleric) max_pp else rand.range(u8, 0, max_pp + 1),
197
193
  };
198
194
  }
199
195
  return .{
200
196
  .species = s,
201
197
  .types = species.types,
202
- .dvs = DVs.from(dvs),
198
+ .dvs = DVs.from(s, dvs),
203
199
  .level = lvl,
204
200
  .stats = stats,
205
201
  .hp = if (opt.cleric) stats.hp else rand.range(u16, 0, stats.hp + 1),