@pkmn/engine 0.1.0-dev.028ab165 → 0.1.0-dev.047d6e71
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -3
- package/README.md +32 -9
- package/build/pkg/addon.js.map +1 -1
- package/build/pkg/data.d.ts +3 -2
- package/build/pkg/data.js.map +1 -1
- package/build/pkg/gen1.js.map +1 -1
- package/build/pkg/index.d.ts +2 -1
- package/build/pkg/index.js.map +1 -1
- package/build/pkg/protocol.js.map +1 -1
- package/build/test/display.js.map +1 -1
- package/build/tools/debug.js.map +1 -1
- package/build.zig +65 -57
- package/package.json +29 -21
- package/src/bin/install-pkmn-engine +14 -14
- package/src/lib/common/data.zig +4 -4
- package/src/lib/common/optional.zig +1 -1
- package/src/lib/common/protocol.zig +67 -72
- package/src/lib/common/rational.zig +7 -3
- package/src/lib/common/rng.zig +11 -11
- package/src/lib/gen1/calc.zig +5 -5
- package/src/lib/gen1/chance.zig +21 -21
- package/src/lib/gen1/data/species.zig +1 -1
- package/src/lib/gen1/data.zig +18 -18
- package/src/lib/gen1/helpers.zig +7 -7
- package/src/lib/gen1/mechanics.zig +57 -31
- package/src/lib/gen2/chance.zig +27 -27
- package/src/lib/gen2/data.zig +9 -9
- package/src/lib/gen2/generated.zig +1 -3
- package/src/lib/gen2/mechanics.zig +132 -51
- package/src/lib/{binding/node.zig → node.zig} +9 -12
- package/src/lib/pkmn.zig +26 -3
- package/src/lib/{binding/wasm.zig → wasm.zig} +2 -1
- package/src/lib/gen1/README.md +0 -894
- package/src/lib/gen2/README.md +0 -329
package/src/lib/gen2/chance.zig
CHANGED
|
@@ -14,7 +14,7 @@ const util = @import("../common/util.zig");
|
|
|
14
14
|
const Player = @import("../common/data.zig").Player;
|
|
15
15
|
const Optional = @import("../common/optional.zig").Optional;
|
|
16
16
|
|
|
17
|
-
const data = @import("
|
|
17
|
+
const data = @import("data.zig");
|
|
18
18
|
|
|
19
19
|
const enabled = options.chance;
|
|
20
20
|
const showdown = options.showdown;
|
|
@@ -699,7 +699,7 @@ test "Chance.damage" {
|
|
|
699
699
|
var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
|
|
700
700
|
|
|
701
701
|
try chance.damage(.P1, 217);
|
|
702
|
-
try expectValue(
|
|
702
|
+
try expectValue(217, chance.actions.p1.damage);
|
|
703
703
|
try expectProbability(&chance.probability, 1, 39);
|
|
704
704
|
}
|
|
705
705
|
|
|
@@ -824,7 +824,7 @@ test "Chance.present" {
|
|
|
824
824
|
} else {
|
|
825
825
|
try expectProbability(&chance.probability, 25, 256);
|
|
826
826
|
}
|
|
827
|
-
try expectValue(
|
|
827
|
+
try expectValue(4, chance.actions.p2.present);
|
|
828
828
|
|
|
829
829
|
chance.reset();
|
|
830
830
|
|
|
@@ -834,7 +834,7 @@ test "Chance.present" {
|
|
|
834
834
|
} else {
|
|
835
835
|
try expectProbability(&chance.probability, 51, 256);
|
|
836
836
|
}
|
|
837
|
-
try expectValue(
|
|
837
|
+
try expectValue(1, chance.actions.p1.present);
|
|
838
838
|
}
|
|
839
839
|
|
|
840
840
|
test "Chance.magnitude" {
|
|
@@ -846,7 +846,7 @@ test "Chance.magnitude" {
|
|
|
846
846
|
} else {
|
|
847
847
|
try expectProbability(&chance.probability, 13, 256);
|
|
848
848
|
}
|
|
849
|
-
try expectValue(
|
|
849
|
+
try expectValue(7, chance.actions.p2.magnitude);
|
|
850
850
|
|
|
851
851
|
chance.reset();
|
|
852
852
|
|
|
@@ -856,7 +856,7 @@ test "Chance.magnitude" {
|
|
|
856
856
|
} else {
|
|
857
857
|
try expectProbability(&chance.probability, 51, 256);
|
|
858
858
|
}
|
|
859
|
-
try expectValue(
|
|
859
|
+
try expectValue(5, chance.actions.p1.magnitude);
|
|
860
860
|
}
|
|
861
861
|
|
|
862
862
|
test "Chance.tripleKick" {
|
|
@@ -864,7 +864,7 @@ test "Chance.tripleKick" {
|
|
|
864
864
|
|
|
865
865
|
try chance.tripleKick(.P1, 2);
|
|
866
866
|
try expectProbability(&chance.probability, 1, 3);
|
|
867
|
-
try expectValue(
|
|
867
|
+
try expectValue(2, chance.actions.p1.triple_kick);
|
|
868
868
|
}
|
|
869
869
|
|
|
870
870
|
test "Chance.spite" {
|
|
@@ -872,7 +872,7 @@ test "Chance.spite" {
|
|
|
872
872
|
|
|
873
873
|
try chance.spite(.P1, 3);
|
|
874
874
|
try expectProbability(&chance.probability, 1, 4);
|
|
875
|
-
try expectValue(
|
|
875
|
+
try expectValue(3, chance.actions.p1.spite);
|
|
876
876
|
}
|
|
877
877
|
|
|
878
878
|
test "Chance.conversion2" {
|
|
@@ -899,7 +899,7 @@ test "Chance.psywave" {
|
|
|
899
899
|
|
|
900
900
|
try chance.psywave(.P2, 100, 150);
|
|
901
901
|
try expectProbability(&chance.probability, 1, 150);
|
|
902
|
-
try expectValue(
|
|
902
|
+
try expectValue(100, chance.actions.p2.psywave);
|
|
903
903
|
}
|
|
904
904
|
|
|
905
905
|
test "Chance.metronome" {
|
|
@@ -915,19 +915,19 @@ test "Chance.moveSlot" {
|
|
|
915
915
|
|
|
916
916
|
try chance.moveSlot(.P2, 2, 2);
|
|
917
917
|
try expectProbability(&chance.probability, 1, 2);
|
|
918
|
-
try expectValue(
|
|
918
|
+
try expectValue(2, chance.actions.p2.move_slot);
|
|
919
919
|
|
|
920
920
|
chance.reset();
|
|
921
921
|
|
|
922
922
|
try chance.moveSlot(.P1, 1, 3);
|
|
923
923
|
try expectProbability(&chance.probability, 1, 3);
|
|
924
|
-
try expectValue(
|
|
924
|
+
try expectValue(1, chance.actions.p1.move_slot);
|
|
925
925
|
|
|
926
926
|
chance.reset();
|
|
927
927
|
|
|
928
928
|
try chance.moveSlot(.P1, 4, 1);
|
|
929
929
|
try expectProbability(&chance.probability, 1, 1);
|
|
930
|
-
try expectValue(
|
|
930
|
+
try expectValue(4, chance.actions.p1.move_slot);
|
|
931
931
|
}
|
|
932
932
|
|
|
933
933
|
test "Chance.forceSwitch" {
|
|
@@ -935,19 +935,19 @@ test "Chance.forceSwitch" {
|
|
|
935
935
|
|
|
936
936
|
try chance.forceSwitch(.P2, 2, 2);
|
|
937
937
|
try expectProbability(&chance.probability, 1, 2);
|
|
938
|
-
try expectValue(
|
|
938
|
+
try expectValue(2, chance.actions.p2.force_switch);
|
|
939
939
|
|
|
940
940
|
chance.reset();
|
|
941
941
|
|
|
942
942
|
try chance.forceSwitch(.P1, 1, 3);
|
|
943
943
|
try expectProbability(&chance.probability, 1, 3);
|
|
944
|
-
try expectValue(
|
|
944
|
+
try expectValue(1, chance.actions.p1.force_switch);
|
|
945
945
|
|
|
946
946
|
chance.reset();
|
|
947
947
|
|
|
948
948
|
try chance.forceSwitch(.P1, 4, 1);
|
|
949
949
|
try expectProbability(&chance.probability, 1, 1);
|
|
950
|
-
try expectValue(
|
|
950
|
+
try expectValue(4, chance.actions.p1.force_switch);
|
|
951
951
|
}
|
|
952
952
|
|
|
953
953
|
test "Chance.multiHit" {
|
|
@@ -955,21 +955,21 @@ test "Chance.multiHit" {
|
|
|
955
955
|
|
|
956
956
|
try chance.multiHit(.P1, 3);
|
|
957
957
|
try expectProbability(&chance.probability, 3, 8);
|
|
958
|
-
try expectValue(
|
|
958
|
+
try expectValue(3, chance.actions.p1.multi_hit);
|
|
959
959
|
|
|
960
960
|
chance.reset();
|
|
961
961
|
|
|
962
962
|
try chance.multiHit(.P2, 5);
|
|
963
963
|
try expectProbability(&chance.probability, 1, 8);
|
|
964
|
-
try expectValue(
|
|
964
|
+
try expectValue(5, chance.actions.p2.multi_hit);
|
|
965
965
|
}
|
|
966
966
|
|
|
967
967
|
test "Chance.duration" {
|
|
968
968
|
var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
|
|
969
969
|
|
|
970
970
|
chance.duration(.sleep, .P1, .P2, 2);
|
|
971
|
-
try expectValue(
|
|
972
|
-
try expectValue(
|
|
971
|
+
try expectValue(2, chance.actions.p1.duration);
|
|
972
|
+
try expectValue(1, chance.actions.p2.durations.sleep);
|
|
973
973
|
}
|
|
974
974
|
|
|
975
975
|
test "Chance.sleep" {
|
|
@@ -979,7 +979,7 @@ test "Chance.sleep" {
|
|
|
979
979
|
chance.actions.p1.durations.sleep = @intCast(i);
|
|
980
980
|
try chance.sleep(.P1, 0);
|
|
981
981
|
try expectProbability(&chance.probability, 1, d);
|
|
982
|
-
try expectValue(
|
|
982
|
+
try expectValue(0, chance.actions.p1.durations.sleep);
|
|
983
983
|
|
|
984
984
|
chance.reset();
|
|
985
985
|
|
|
@@ -1003,7 +1003,7 @@ test "Chance.confusion" {
|
|
|
1003
1003
|
chance.actions.p2.durations.confusion = @intCast(i);
|
|
1004
1004
|
try chance.confusion(.P2, 0);
|
|
1005
1005
|
try expectProbability(&chance.probability, 1, d);
|
|
1006
|
-
try expectValue(
|
|
1006
|
+
try expectValue(0, chance.actions.p2.durations.confusion);
|
|
1007
1007
|
try expectValue(false, chance.actions.p2.durations.thrash);
|
|
1008
1008
|
|
|
1009
1009
|
chance.reset();
|
|
@@ -1028,7 +1028,7 @@ test "Chance.confusion" {
|
|
|
1028
1028
|
chance.actions.p2.durations.confusion = @intCast(i);
|
|
1029
1029
|
try chance.confusion(.P2, 0);
|
|
1030
1030
|
try expectProbability(&chance.probability, 1, d);
|
|
1031
|
-
try expectValue(
|
|
1031
|
+
try expectValue(0, chance.actions.p2.durations.confusion);
|
|
1032
1032
|
try expectValue(true, chance.actions.p2.durations.thrash);
|
|
1033
1033
|
|
|
1034
1034
|
chance.reset();
|
|
@@ -1054,7 +1054,7 @@ test "Chance.disable" {
|
|
|
1054
1054
|
chance.actions.p2.durations.disable = @intCast(i);
|
|
1055
1055
|
try chance.disable(.P2, 0);
|
|
1056
1056
|
try expectProbability(&chance.probability, 1, d);
|
|
1057
|
-
try expectValue(
|
|
1057
|
+
try expectValue(0, chance.actions.p2.durations.disable);
|
|
1058
1058
|
|
|
1059
1059
|
chance.reset();
|
|
1060
1060
|
}
|
|
@@ -1074,7 +1074,7 @@ test "Chance.disable" {
|
|
|
1074
1074
|
chance.actions.p2.durations.disable = @intCast(i);
|
|
1075
1075
|
try chance.disable(.P2, 0);
|
|
1076
1076
|
try expectProbability(&chance.probability, 1, d);
|
|
1077
|
-
try expectValue(
|
|
1077
|
+
try expectValue(0, chance.actions.p2.durations.disable);
|
|
1078
1078
|
|
|
1079
1079
|
chance.reset();
|
|
1080
1080
|
}
|
|
@@ -1099,7 +1099,7 @@ test "Chance.attacking" {
|
|
|
1099
1099
|
chance.actions.p2.durations.attacking = @intCast(i);
|
|
1100
1100
|
try chance.attacking(.P2, 0);
|
|
1101
1101
|
try expectProbability(&chance.probability, 1, d);
|
|
1102
|
-
try expectValue(
|
|
1102
|
+
try expectValue(0, chance.actions.p2.durations.attacking);
|
|
1103
1103
|
|
|
1104
1104
|
chance.reset();
|
|
1105
1105
|
}
|
|
@@ -1123,7 +1123,7 @@ test "Chance.binding" {
|
|
|
1123
1123
|
chance.actions.p2.durations.binding = @intCast(i);
|
|
1124
1124
|
try chance.binding(.P2, 0);
|
|
1125
1125
|
try expectProbability(&chance.probability, 1, d);
|
|
1126
|
-
try expectValue(
|
|
1126
|
+
try expectValue(0, chance.actions.p2.durations.binding);
|
|
1127
1127
|
|
|
1128
1128
|
chance.reset();
|
|
1129
1129
|
}
|
|
@@ -1147,7 +1147,7 @@ test "Chance.encore" {
|
|
|
1147
1147
|
chance.actions.p2.durations.encore = @intCast(i);
|
|
1148
1148
|
try chance.encore(.P2, 0);
|
|
1149
1149
|
try expectProbability(&chance.probability, 1, d);
|
|
1150
|
-
try expectValue(
|
|
1150
|
+
try expectValue(0, chance.actions.p2.durations.encore);
|
|
1151
1151
|
|
|
1152
1152
|
chance.reset();
|
|
1153
1153
|
}
|
package/src/lib/gen2/data.zig
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
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(
|
|
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);
|
|
@@ -380,9 +380,9 @@ test Stats {
|
|
|
380
380
|
try expectEqual(0, stats.def);
|
|
381
381
|
|
|
382
382
|
var base = Species.get(.Gyarados).stats;
|
|
383
|
-
try expectEqual(
|
|
383
|
+
try expectEqual(127, Stats(u16).calc("hp", base.hp, 0, 5120, 38));
|
|
384
384
|
base = Species.get(.Pidgeot).stats;
|
|
385
|
-
try expectEqual(
|
|
385
|
+
try expectEqual(279, Stats(u16).calc("spe", base.spe, 15, 63001, 100));
|
|
386
386
|
}
|
|
387
387
|
|
|
388
388
|
pub const Boosts = packed struct(u32) {
|
|
@@ -428,8 +428,8 @@ test Move {
|
|
|
428
428
|
try expectEqual(Move.Effect.ConfusionChance, move.effect);
|
|
429
429
|
try expectEqual(rng.Gen12.percent(50), move.accuracy);
|
|
430
430
|
try expectEqual(rng.Gen12.percent(100), move.chance);
|
|
431
|
-
try expectEqual(
|
|
432
|
-
try expectEqual(
|
|
431
|
+
try expectEqual(5, Move.pp(.DynamicPunch));
|
|
432
|
+
try expectEqual(-1, Move.get(.Counter).priority);
|
|
433
433
|
try expect(!move.effect.isHighCritical());
|
|
434
434
|
try expect(Move.get(.KarateChop).effect.isHighCritical());
|
|
435
435
|
try expect(Move.get(.RazorWind).effect.isHighCritical());
|
|
@@ -439,7 +439,7 @@ pub const Species = species.Species;
|
|
|
439
439
|
|
|
440
440
|
test Species {
|
|
441
441
|
try expectEqual(152, @intFromEnum(Species.Chikorita));
|
|
442
|
-
try expectEqual(
|
|
442
|
+
try expectEqual(100, Species.get(.Celebi).stats.spd);
|
|
443
443
|
}
|
|
444
444
|
|
|
445
445
|
pub const Type = types.Type;
|
|
@@ -452,7 +452,7 @@ test Types {
|
|
|
452
452
|
try expect(!Type.Steel.special());
|
|
453
453
|
try expect(Type.Dark.special());
|
|
454
454
|
|
|
455
|
-
try expectEqual(
|
|
455
|
+
try expectEqual(3, Type.Water.precedence());
|
|
456
456
|
try expect(Type.Bug.precedence() > Type.Poison.precedence());
|
|
457
457
|
|
|
458
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
|
-
|
|
43
|
+
const volatiles = &side.active.volatiles;
|
|
46
44
|
|
|
47
45
|
const ident = battle.active(player);
|
|
48
46
|
const foe_ident = battle.active(player.foe());
|
|
@@ -140,11 +140,11 @@ fn start(battle: anytype, options: anytype) !Result {
|
|
|
140
140
|
const p1 = battle.side(.P1);
|
|
141
141
|
const p2 = battle.side(.P2);
|
|
142
142
|
|
|
143
|
-
|
|
143
|
+
const p1_slot = findFirstAlive(p1);
|
|
144
144
|
assert(!showdown or p1_slot == 1);
|
|
145
145
|
if (p1_slot == 0) return if (findFirstAlive(p2) == 0) Result.Tie else Result.Lose;
|
|
146
146
|
|
|
147
|
-
|
|
147
|
+
const p2_slot = findFirstAlive(p2);
|
|
148
148
|
assert(!showdown or p2_slot == 1);
|
|
149
149
|
if (p2_slot == 0) return Result.Win;
|
|
150
150
|
|
|
@@ -403,7 +403,9 @@ fn executeMove(
|
|
|
403
403
|
|
|
404
404
|
assert(choice.type == .Move);
|
|
405
405
|
var mslot: u4 = @intCast(choice.data);
|
|
406
|
+
_ = &mslot;
|
|
406
407
|
var move = Move.Pound; // TODO
|
|
408
|
+
_ = &move;
|
|
407
409
|
|
|
408
410
|
if (!try beforeMove(battle, player, move, options)) return null;
|
|
409
411
|
|
|
@@ -565,7 +567,7 @@ fn resetCant(volatiles: *align(8) Volatiles) void {
|
|
|
565
567
|
|
|
566
568
|
pub fn canMove(battle: anytype, player: Player, state: *State, options: anytype) !bool {
|
|
567
569
|
var side = battle.side(player);
|
|
568
|
-
|
|
570
|
+
const volatiles = &side.active.volatiles;
|
|
569
571
|
|
|
570
572
|
// FIXME: pass in target to avoid lookup?
|
|
571
573
|
const target = if (Move.get(state.move).target == .Self) player else player.foe();
|
|
@@ -997,7 +999,6 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
997
999
|
|
|
998
1000
|
// Weather
|
|
999
1001
|
if (battle.field.weather != .None) {
|
|
1000
|
-
assert(battle.field.weather_duration > 0);
|
|
1001
1002
|
battle.field.weather_duration -= 1;
|
|
1002
1003
|
|
|
1003
1004
|
if (battle.field.weather_duration == 0) {
|
|
@@ -1056,7 +1057,6 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
1056
1057
|
var volatiles = &side.active.volatiles;
|
|
1057
1058
|
|
|
1058
1059
|
if (volatiles.PerishSong) {
|
|
1059
|
-
assert(volatiles.perish_song > 0);
|
|
1060
1060
|
volatiles.perish_song -= 1;
|
|
1061
1061
|
|
|
1062
1062
|
assert(volatiles.perish_song < 3);
|
|
@@ -1086,7 +1086,7 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
1086
1086
|
|
|
1087
1087
|
// Mystery Berry
|
|
1088
1088
|
inline for (players) |player| {
|
|
1089
|
-
|
|
1089
|
+
const stored = battle.side(player).stored();
|
|
1090
1090
|
|
|
1091
1091
|
if (stored.item == .MysteryBerry) {
|
|
1092
1092
|
// TODO stored and active moves, transform, etc
|
|
@@ -1193,7 +1193,6 @@ fn betweenTurns(battle: anytype, mslot: u4, options: anytype) !?Result {
|
|
|
1193
1193
|
var volatiles = &side.active.volatiles;
|
|
1194
1194
|
|
|
1195
1195
|
if (volatiles.Encore) {
|
|
1196
|
-
assert(volatiles.encore > 0);
|
|
1197
1196
|
volatiles.encore -= 1;
|
|
1198
1197
|
|
|
1199
1198
|
if (volatiles.encore == 0 or side.active.move(mslot).pp == 0) {
|
|
@@ -1215,7 +1214,6 @@ fn updateSideCondition(
|
|
|
1215
1214
|
) !void {
|
|
1216
1215
|
var side = battle.side(player);
|
|
1217
1216
|
if (@field(side.conditions, key)) {
|
|
1218
|
-
assert(@field(side.conditions, value) > 0);
|
|
1219
1217
|
@field(side.conditions, value) -= 1;
|
|
1220
1218
|
|
|
1221
1219
|
if (@field(side.conditions, value) == 0) {
|
|
@@ -1306,14 +1304,32 @@ pub const Effects = struct {
|
|
|
1306
1304
|
}
|
|
1307
1305
|
|
|
1308
1306
|
pub fn bellyDrum(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1307
|
+
assert(!state.miss);
|
|
1308
|
+
var side = battle.side(player);
|
|
1309
|
+
|
|
1310
|
+
const ident = battle.active(player);
|
|
1311
|
+
|
|
1312
|
+
assert(side.active.boosts.atk >= -6 and side.active.boosts.atk <= 6);
|
|
1313
|
+
if (side.active.boosts.atk == 6 or side.active.boosts.atk == MAX_STAT_VALUE) {
|
|
1314
|
+
return options.log.fail(.{ ident, .None });
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
const hp = @max(side.stored().stats.hp / 2, 1);
|
|
1318
|
+
if (side.stored().hp <= hp) {
|
|
1319
|
+
try boostStat(battle, side, &side.active.stats, &side.active.boosts, "atk", 2);
|
|
1320
|
+
try options.log.boost(.{ ident, .Attack, @as(i8, 2), .Silent });
|
|
1321
|
+
return options.log.fail(.{ ident, .None });
|
|
1322
|
+
} else {
|
|
1323
|
+
side.stored().hp -= hp;
|
|
1324
|
+
try options.log.damage(.{ ident, side.stored(), .None });
|
|
1325
|
+
|
|
1326
|
+
const before = side.active.boosts.atk;
|
|
1327
|
+
var i: usize = 0;
|
|
1328
|
+
while (i < 6 and side.active.stats.atk < MAX_STAT_VALUE) : (i += 1) {
|
|
1329
|
+
try boostStat(battle, side, &side.active.stats, &side.active.boosts, "atk", 2);
|
|
1330
|
+
}
|
|
1331
|
+
try options.log.boost(.{ ident, .Attack, @as(i8, side.active.boosts.atk - before) });
|
|
1332
|
+
}
|
|
1317
1333
|
}
|
|
1318
1334
|
|
|
1319
1335
|
pub const bide = struct {
|
|
@@ -1325,7 +1341,6 @@ pub const Effects = struct {
|
|
|
1325
1341
|
|
|
1326
1342
|
if (!volatiles.Bide) return;
|
|
1327
1343
|
|
|
1328
|
-
assert(volatiles.attacks > 0);
|
|
1329
1344
|
// if (options.calc.modify(player, .attacking)) |extend| {
|
|
1330
1345
|
// if (!extend) volatiles.attacks = 0;
|
|
1331
1346
|
// } else {
|
|
@@ -1478,8 +1493,8 @@ pub const Effects = struct {
|
|
|
1478
1493
|
side.stored().hp -= @max(side.stored().stats.hp / 2, 1);
|
|
1479
1494
|
try options.log.damage(.{ ident, side.stored(), .Curse });
|
|
1480
1495
|
} else {
|
|
1481
|
-
|
|
1482
|
-
|
|
1496
|
+
const stats = &side.active.stats;
|
|
1497
|
+
const boosts = &side.active.boosts;
|
|
1483
1498
|
|
|
1484
1499
|
const max_atk = boosts.atk == 6 or stats.atk == MAX_STAT_VALUE;
|
|
1485
1500
|
const max_def = boosts.def == 6 or stats.def == MAX_STAT_VALUE;
|
|
@@ -1599,8 +1614,16 @@ pub const Effects = struct {
|
|
|
1599
1614
|
try options.log.start(.{ foe_ident, .Encore, move.id });
|
|
1600
1615
|
}
|
|
1601
1616
|
|
|
1602
|
-
pub fn explode(battle: anytype, player: Player,
|
|
1603
|
-
|
|
1617
|
+
pub fn explode(battle: anytype, player: Player, _: *State, _: anytype) !void {
|
|
1618
|
+
var side = battle.side(player);
|
|
1619
|
+
var stored = side.stored();
|
|
1620
|
+
|
|
1621
|
+
stored.hp = 0;
|
|
1622
|
+
// Pokémon Showdown sets the status to 0 on faint(), and we need to do the same to be able
|
|
1623
|
+
// to correctly implement Pokémon Showdown's dumb speed-based switch mechanics
|
|
1624
|
+
if (!showdown) stored.status = 0;
|
|
1625
|
+
side.active.volatiles.LeechSeed = false;
|
|
1626
|
+
side.active.volatiles.DestinyBond = false;
|
|
1604
1627
|
}
|
|
1605
1628
|
|
|
1606
1629
|
pub fn falseSwipe(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
@@ -1726,8 +1749,26 @@ pub const Effects = struct {
|
|
|
1726
1749
|
foe.active.volatiles.frozen = true;
|
|
1727
1750
|
}
|
|
1728
1751
|
|
|
1729
|
-
|
|
1730
|
-
|
|
1752
|
+
// TODO FIXME showdown calls this before calcDamage (after checkHit) and modifies state.bp!
|
|
1753
|
+
pub fn furyCutter(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
1754
|
+
var side = battle.side(player);
|
|
1755
|
+
var volatiles = &side.active.volatiles;
|
|
1756
|
+
|
|
1757
|
+
if (state.miss) {
|
|
1758
|
+
volatiles.fury_cutter = 0;
|
|
1759
|
+
return;
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
assert(volatiles.fury_cutter >= 0 and volatiles.fury_cutter <= 5);
|
|
1763
|
+
if (volatiles.fury_cutter == 5) return;
|
|
1764
|
+
volatiles.fury_cutter += 1;
|
|
1765
|
+
|
|
1766
|
+
if (showdown) {
|
|
1767
|
+
state.bp *= 2;
|
|
1768
|
+
assert(state.bp <= 160);
|
|
1769
|
+
} else {
|
|
1770
|
+
state.damage *|= 2;
|
|
1771
|
+
}
|
|
1731
1772
|
}
|
|
1732
1773
|
|
|
1733
1774
|
pub fn futureSight(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
@@ -1888,7 +1929,29 @@ pub const Effects = struct {
|
|
|
1888
1929
|
}
|
|
1889
1930
|
|
|
1890
1931
|
pub fn mimic(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
1891
|
-
|
|
1932
|
+
var side = battle.side(player);
|
|
1933
|
+
|
|
1934
|
+
if (state.miss) {
|
|
1935
|
+
if (!showdown) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1936
|
+
try options.log.lastmiss(.{});
|
|
1937
|
+
return options.log.miss(.{battle.active(player)});
|
|
1938
|
+
}
|
|
1939
|
+
|
|
1940
|
+
const move = battle.foe(player).lastMove(false);
|
|
1941
|
+
if (move == .None or move == .Struggle) {
|
|
1942
|
+
return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
var slot: u4 = 0;
|
|
1946
|
+
for (side.active.moves, 0..) |m, i| {
|
|
1947
|
+
if (m.id == move) return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
1948
|
+
if (m.id == .Mimic) slot = @intCast(i + 1);
|
|
1949
|
+
}
|
|
1950
|
+
assert(slot != 0 and slot == state.mslot);
|
|
1951
|
+
|
|
1952
|
+
side.active.move(slot).id = move;
|
|
1953
|
+
side.active.move(slot).pp = 5;
|
|
1954
|
+
try options.log.start(.{ battle.active(player), .Mimic, move });
|
|
1892
1955
|
}
|
|
1893
1956
|
|
|
1894
1957
|
pub fn mirrorMove(
|
|
@@ -2185,11 +2248,10 @@ pub const Effects = struct {
|
|
|
2185
2248
|
}
|
|
2186
2249
|
|
|
2187
2250
|
pub fn damage(battle: anytype, player: Player, state: *State, _: anytype) !void {
|
|
2188
|
-
|
|
2189
|
-
var volatiles = &side.active.volatiles;
|
|
2251
|
+
const side = battle.side(player);
|
|
2190
2252
|
|
|
2191
|
-
assert(volatiles.Rage);
|
|
2192
|
-
state.damage *|= (volatiles.rage +| 1);
|
|
2253
|
+
assert(side.active.volatiles.Rage);
|
|
2254
|
+
state.damage *|= (side.active.volatiles.rage +| 1);
|
|
2193
2255
|
}
|
|
2194
2256
|
};
|
|
2195
2257
|
|
|
@@ -2397,17 +2459,37 @@ pub const Effects = struct {
|
|
|
2397
2459
|
}
|
|
2398
2460
|
|
|
2399
2461
|
pub fn spite(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2400
|
-
|
|
2401
|
-
// var foe = battle.foe(player);
|
|
2462
|
+
var foe = battle.foe(player);
|
|
2402
2463
|
|
|
2403
|
-
|
|
2464
|
+
const last = foe.lastMove(false);
|
|
2465
|
+
if (state.miss or last == .None or last == .Struggle) {
|
|
2466
|
+
return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
2467
|
+
}
|
|
2404
2468
|
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
//
|
|
2410
|
-
|
|
2469
|
+
var slot: u4 = 0;
|
|
2470
|
+
for (foe.active.moves, 0..) |m, i| {
|
|
2471
|
+
if (m.id == last) slot = @intCast(i + 1);
|
|
2472
|
+
}
|
|
2473
|
+
// If the move was called via Metronome it might not exist
|
|
2474
|
+
if (slot == 0 or foe.active.move(slot).pp == 0) {
|
|
2475
|
+
return options.log.fail(.{ battle.active(player.foe()), .None });
|
|
2476
|
+
}
|
|
2477
|
+
|
|
2478
|
+
// Pokémon Showdown always deducts 4 instead of randomizing
|
|
2479
|
+
const pp = @min(
|
|
2480
|
+
foe.active.move(slot).pp,
|
|
2481
|
+
if (showdown) 4 else try Rolls.spite(battle, player, options),
|
|
2482
|
+
);
|
|
2483
|
+
foe.active.move(slot).pp -= pp;
|
|
2484
|
+
if (!foe.active.volatiles.Transform) {
|
|
2485
|
+
var move_slot = foe.stored().move(slot);
|
|
2486
|
+
assert(move_slot.pp > 0);
|
|
2487
|
+
|
|
2488
|
+
move_slot.pp = @as(u6, @intCast(move_slot.pp)) -% 1;
|
|
2489
|
+
assert(foe.active.move(slot).pp == foe.stored().move(slot).pp);
|
|
2490
|
+
}
|
|
2491
|
+
|
|
2492
|
+
try options.log.activate(.{ battle.active(player.foe()), .Spite, last, pp });
|
|
2411
2493
|
}
|
|
2412
2494
|
|
|
2413
2495
|
pub fn splash(battle: anytype, player: Player, _: *State, options: anytype) !void {
|
|
@@ -2541,8 +2623,8 @@ pub const Effects = struct {
|
|
|
2541
2623
|
pub fn allStatUpChance(battle: anytype, player: Player, state: *State, options: anytype) !void {
|
|
2542
2624
|
var side = battle.side(player);
|
|
2543
2625
|
|
|
2544
|
-
|
|
2545
|
-
|
|
2626
|
+
const stats = &side.active.stats;
|
|
2627
|
+
const boosts = &side.active.boosts;
|
|
2546
2628
|
|
|
2547
2629
|
if (!state.proc) return;
|
|
2548
2630
|
inline for (STATS, STAT_UP_REASONS) |s, r| {
|
|
@@ -2563,7 +2645,7 @@ pub const Effects = struct {
|
|
|
2563
2645
|
|
|
2564
2646
|
if (state.miss) return log.fail(.{ ident, .None });
|
|
2565
2647
|
|
|
2566
|
-
|
|
2648
|
+
const stats = &side.active.stats;
|
|
2567
2649
|
var boosts = &side.active.boosts;
|
|
2568
2650
|
|
|
2569
2651
|
const move = Move.get(state.move);
|
|
@@ -2634,7 +2716,7 @@ pub const Effects = struct {
|
|
|
2634
2716
|
|
|
2635
2717
|
const fail = foe.active.volatiles.Substitute or state.miss or !state.proc;
|
|
2636
2718
|
|
|
2637
|
-
|
|
2719
|
+
const stats = &foe.active.stats;
|
|
2638
2720
|
var boosts = &foe.active.boosts;
|
|
2639
2721
|
|
|
2640
2722
|
const move = Move.get(state.move);
|
|
@@ -2718,9 +2800,9 @@ fn unmodifiedStats(battle: anytype, side: *Side) *Stats(u16) {
|
|
|
2718
2800
|
}
|
|
2719
2801
|
|
|
2720
2802
|
fn recomputeStats(battle: anytype, side: *Side) void {
|
|
2721
|
-
|
|
2803
|
+
const stats = unmodifiedStats(battle, side);
|
|
2722
2804
|
inline for (STATS) |s| {
|
|
2723
|
-
|
|
2805
|
+
const mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(side.active.boosts, s)) + 6))];
|
|
2724
2806
|
const val = @field(stats, s) * mod[0] / mod[1];
|
|
2725
2807
|
@field(side.active.stats, s) = @max(1, @min(MAX_STAT_VALUE, val));
|
|
2726
2808
|
}
|
|
@@ -2743,7 +2825,7 @@ fn boostStat(
|
|
|
2743
2825
|
n: u2,
|
|
2744
2826
|
) !void {
|
|
2745
2827
|
@field(boosts, s) = @intCast(@min(6, @as(i8, @field(boosts, s)) + n));
|
|
2746
|
-
|
|
2828
|
+
const mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(boosts, s)) + 6))];
|
|
2747
2829
|
const stat = @field(unmodifiedStats(battle, side), s);
|
|
2748
2830
|
@field(stats, s) = @min(MAX_STAT_VALUE, stat * mod[0] / mod[1]);
|
|
2749
2831
|
if (comptime std.mem.eql(u8, s, "atk")) {
|
|
@@ -2766,7 +2848,7 @@ fn unboostStat(
|
|
|
2766
2848
|
n: u2,
|
|
2767
2849
|
) !void {
|
|
2768
2850
|
@field(boosts, s) = @intCast(@max(-6, @as(i8, @field(boosts, s)) - n));
|
|
2769
|
-
|
|
2851
|
+
const mod = STAT_BOOSTS[@as(u4, @intCast(@as(i8, @field(boosts, s)) + 6))];
|
|
2770
2852
|
const stat = @field(unmodifiedStats(battle, side), s);
|
|
2771
2853
|
@field(stats, s) = @max(1, stat * mod[0] / mod[1]);
|
|
2772
2854
|
if (comptime std.mem.eql(u8, s, "atk")) {
|
|
@@ -3013,13 +3095,12 @@ pub const Rolls = struct {
|
|
|
3013
3095
|
return hits;
|
|
3014
3096
|
}
|
|
3015
3097
|
|
|
3016
|
-
fn spite(battle: anytype, player: Player, options: anytype) !
|
|
3017
|
-
|
|
3098
|
+
fn spite(battle: anytype, player: Player, options: anytype) !u3 {
|
|
3099
|
+
assert(!showdown);
|
|
3100
|
+
const pp: u3 = if (options.calc.overridden(player, .spite)) |val|
|
|
3018
3101
|
val + 1
|
|
3019
|
-
else if (showdown)
|
|
3020
|
-
battle.rng.range(u8, 2, 6)
|
|
3021
3102
|
else
|
|
3022
|
-
(battle.rng.next() & 3) + 2;
|
|
3103
|
+
@intCast((battle.rng.next() & 3) + 2);
|
|
3023
3104
|
|
|
3024
3105
|
assert(pp >= 2 and pp <= 5);
|
|
3025
3106
|
try options.chance.spite(player, pp);
|
|
@@ -3467,7 +3548,7 @@ pub fn choices(battle: anytype, player: Player, request: Choice.Type, out: []Cho
|
|
|
3467
3548
|
.Move => {
|
|
3468
3549
|
const side = battle.side(player);
|
|
3469
3550
|
|
|
3470
|
-
|
|
3551
|
+
const active = &side.active;
|
|
3471
3552
|
const stored = side.stored();
|
|
3472
3553
|
|
|
3473
3554
|
// While players are not given any input options on the cartridge in these cases,
|