From cbd4340b139280554b1b0bd704f2e2f4f98201d7 Mon Sep 17 00:00:00 2001 From: Deukhoofd Date: Sun, 18 May 2025 14:15:37 +0200 Subject: [PATCH] Make another pass through moves file --- .../DataTests/MoveDataTests.cs | 107 ++++++++++++++++++ Plugins/PkmnLib.Plugin.Gen7/Data/Moves.jsonc | 98 +++++++++++++--- .../Scripts/Battle/UproarEffect.cs | 53 +++++++++ .../Scripts/Moves/Feint.cs | 16 +++ .../Scripts/Moves/HappyHour.cs | 7 ++ .../Scripts/Moves/IncreasedCriticalStage.cs | 3 - .../Scripts/Moves/MagneticFlux.cs | 25 ++++ .../Scripts/Moves/Uproar.cs | 27 +++++ .../Scripts/Pokemon/BanefulBunkerEffect.cs | 1 + .../Scripts/Pokemon/ProtectionEffectScript.cs | 1 + .../Scripts/Pokemon/SpikyShieldEffect.cs | 1 + 11 files changed, 322 insertions(+), 17 deletions(-) create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Battle/UproarEffect.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Feint.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/HappyHour.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/MagneticFlux.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Uproar.cs diff --git a/Plugins/PkmnLib.Plugin.Gen7.Tests/DataTests/MoveDataTests.cs b/Plugins/PkmnLib.Plugin.Gen7.Tests/DataTests/MoveDataTests.cs index 00a265b..ff85260 100644 --- a/Plugins/PkmnLib.Plugin.Gen7.Tests/DataTests/MoveDataTests.cs +++ b/Plugins/PkmnLib.Plugin.Gen7.Tests/DataTests/MoveDataTests.cs @@ -1,6 +1,11 @@ +using System.Buffers; +using System.Text.Json; using PkmnLib.Dynamic.Libraries; +using PkmnLib.Dynamic.Libraries.DataLoaders.Models; using PkmnLib.Dynamic.ScriptHandling; +using PkmnLib.Dynamic.ScriptHandling.Registry; using PkmnLib.Static.Moves; +using TUnit.Core.Logging; namespace PkmnLib.Plugin.Gen7.Tests.DataTests; @@ -84,4 +89,106 @@ public class MoveDataTests await Assert.That(test.Library.ScriptResolver.TryResolve(ScriptCategory.Status, status, null, out _)).IsTrue(); } + + public record HasEitherEffectOrComment(string MoveName, bool HasEffect, bool HasComment) + { + /// + public override string ToString() => MoveName + " has effect: " + HasEffect + ", comment: " + HasComment; + } + + public static IEnumerable> EveryMoveHasEitherEffectOrCommentData() + { + IResourceProvider plugin = new Gen7Plugin(); + using var movesJson = plugin.GetResource(ResourceFileType.Moves)!.Open(); + var json = new BinaryReader(movesJson); + var moves = json.ReadBytes((int)movesJson.Length); + var reader = new Utf8JsonReader(new ReadOnlySequence(moves), new JsonReaderOptions + { + CommentHandling = JsonCommentHandling.Allow, + AllowTrailingCommas = true, + }); + Console.WriteLine("Reading moves.json"); + + // The JSON is an object { "data": [] }. + // Each move is an object in the "data" array. + // Each object needs to have either an "effect" or a "comment" property. + + // Read the first object + reader.Read(); + // Read the properties, until we find the "data" property + while (reader.TokenType != JsonTokenType.PropertyName || reader.GetString() != "data") + { + reader.Read(); + } + // Read the "data" property + reader.Read(); + // Read the start of the array + reader.Read(); + + var results = new List(); + // Read each move object + while (reader.TokenType != JsonTokenType.EndArray) + { + // Read the start of the object + if (!reader.Read()) + break; + // Read each property + var name = ""; + var hasEffect = false; + var hasComment = false; + while (reader.TokenType != JsonTokenType.EndObject) + { + if (reader.TokenType == JsonTokenType.PropertyName) + { + var propertyName = reader.GetString(); + if (propertyName == "name") + { + reader.Read(); + name = reader.GetString()!; + } + else if (propertyName == "effect") + { + hasEffect = true; + reader.Read(); + // Read the effect object + while (reader.TokenType != JsonTokenType.EndObject) + { + reader.Read(); + if (reader.TokenType != JsonTokenType.StartObject) + continue; + while (reader.TokenType != JsonTokenType.EndObject) + { + reader.Read(); + } + reader.Read(); + } + } + } + else if (reader.TokenType == JsonTokenType.Comment) + { + // Read the comment + var comment = reader.GetComment(); + hasComment = comment.Trim() == "No secondary effect"; + } + + reader.Read(); + } + results.Add(new HasEitherEffectOrComment(name, hasEffect, hasComment)); + } + return results.Where(x => !string.IsNullOrWhiteSpace(x.MoveName)) + .Select>(x => () => x).ToList(); + } + + /// + /// To ensure that all moves have been properly implemented, we require that each move has either an "effect" property, + /// or a comment with the exact text "No secondary effect". This ensures that we have not missed any moves. + /// + /// The implementation of this test is a bit tricky, as we need to read the JSON file and parse it ourselves, + /// as System.Text.Json does not support reading comments in JSON files beyond the low-level API. + /// + /// + [Test, MethodDataSource(nameof(EveryMoveHasEitherEffectOrCommentData))] + public async Task AllMovesHaveEitherEffectOrComment(HasEitherEffectOrComment test) => + // Check that each move has either an "effect" or a "comment" property + await Assert.That(test.HasEffect || test.HasComment).IsTrue(); } \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Data/Moves.jsonc b/Plugins/PkmnLib.Plugin.Gen7/Data/Moves.jsonc index 9531be0..255ce00 100755 --- a/Plugins/PkmnLib.Plugin.Gen7/Data/Moves.jsonc +++ b/Plugins/PkmnLib.Plugin.Gen7/Data/Moves.jsonc @@ -49,6 +49,7 @@ "protect", "mirror" ] + // No secondary effect }, { "name": "acid", @@ -101,6 +102,7 @@ "target": "Any", "category": "physical", "flags": [] + // No secondary effect }, { "name": "acid_downpour__special", @@ -112,6 +114,7 @@ "target": "Any", "category": "special", "flags": [] + // No secondary effect }, { "name": "acid_spray", @@ -175,7 +178,7 @@ "type": "flying", "power": 60, "pp": 20, - "accuracy": 0, + "accuracy": 255, "priority": 0, "target": "Any", "category": "physical", @@ -185,6 +188,7 @@ "mirror", "distance" ] + // No secondary effect }, { "name": "aeroblast", @@ -285,6 +289,7 @@ "target": "Any", "category": "physical", "flags": [] + // No secondary effect }, { "name": "all_out_pummeling__special", @@ -296,6 +301,7 @@ "target": "Any", "category": "special", "flags": [] + // No secondary effect }, { "name": "ally_switch", @@ -383,6 +389,7 @@ "protect", "mirror" ] + // No secondary effect }, { "name": "aqua_ring", @@ -417,6 +424,7 @@ "protect", "mirror" ] + // No secondary effect }, { "name": "arm_thrust", @@ -576,6 +584,7 @@ "pulse", "ballistics" ] + // No secondary effect }, { "name": "aurora_beam", @@ -881,6 +890,7 @@ "target": "Any", "category": "physical", "flags": [] + // No secondary effect }, { "name": "black_hole_eclipse__special", @@ -892,6 +902,7 @@ "target": "Any", "category": "special", "flags": [] + // No secondary effect }, { "name": "blast_burn", @@ -981,6 +992,7 @@ "target": "Any", "category": "physical", "flags": [] + // No secondary effect }, { "name": "bloom_doom__special", @@ -992,6 +1004,7 @@ "target": "Any", "category": "special", "flags": [] + // No secondary effect }, { "name": "blue_flare", @@ -1126,6 +1139,7 @@ "sound", "ignore-substitute" ] + // No secondary effect }, { "name": "bounce", @@ -1180,6 +1194,7 @@ "target": "Any", "category": "physical", "flags": [] + // No secondary effect }, { "name": "breakneck_blitz__special", @@ -1191,6 +1206,7 @@ "target": "Any", "category": "special", "flags": [] + // No secondary effect }, { "name": "brick_break", @@ -1241,6 +1257,7 @@ "protect", "mirror" ] + // No secondary effect }, { "name": "bubble", @@ -1381,6 +1398,7 @@ "mirror", "punch" ] + // No secondary effect }, { "name": "bullet_seed", @@ -1484,6 +1502,7 @@ "flags": [ "contact" ] + // No secondary effect }, { "name": "celebrate", @@ -1823,6 +1842,7 @@ "target": "Any", "category": "physical", "flags": [] + // No secondary effect }, { "name": "continental_crush__special", @@ -1834,6 +1854,7 @@ "target": "Any", "category": "special", "flags": [] + // No secondary effect }, { "name": "conversion", @@ -1908,6 +1929,7 @@ "target": "Any", "category": "physical", "flags": [] + // No secondary effect }, { "name": "corkscrew_crash__special", @@ -1919,6 +1941,7 @@ "target": "Any", "category": "special", "flags": [] + // No secondary effect }, { "name": "cosmic_power", @@ -2029,7 +2052,10 @@ "contact", "protect", "mirror" - ] + ], + "effect": { + "name": "increased_critical_stage" + } }, { "name": "crafty_shield", @@ -2058,7 +2084,10 @@ "contact", "protect", "mirror" - ] + ], + "effect": { + "name": "increased_critical_stage" + } }, { "name": "cross_poison", @@ -2175,6 +2204,7 @@ "protect", "mirror" ] + // No secondary effect }, { "name": "dark_pulse", @@ -2248,6 +2278,7 @@ "protect", "mirror" ] + // No secondary effect }, { "name": "defend_order", @@ -2347,6 +2378,7 @@ "target": "Any", "category": "physical", "flags": [] + // No secondary effect }, { "name": "devastating_drake__special", @@ -2358,6 +2390,7 @@ "target": "Any", "category": "special", "flags": [] + // No secondary effect }, { "name": "diamond_storm", @@ -2425,7 +2458,7 @@ "type": "fairy", "power": 40, "pp": 15, - "accuracy": 0, + "accuracy": 255, "priority": 0, "target": "AllOpponent", "category": "special", @@ -2435,6 +2468,7 @@ "sound", "ignore-substitute" ] + // No secondary effect }, { "name": "discharge", @@ -2683,6 +2717,7 @@ "protect", "mirror" ] + // No secondary effect }, { "name": "dragon_dance", @@ -2719,6 +2754,7 @@ "protect", "mirror" ] + // No secondary effect }, { "name": "dragon_pulse", @@ -2735,6 +2771,7 @@ "distance", "pulse" ] + // No secondary effect }, { "name": "dragon_rage", @@ -2871,6 +2908,7 @@ "mirror", "distance" ] + // No secondary effect }, { "name": "drill_run", @@ -2885,7 +2923,10 @@ "contact", "protect", "mirror" - ] + ], + "effect": { + "name": "increased_critical_stage" + } }, { "name": "dual_chop", @@ -2962,6 +3003,7 @@ "hit_underground", "effective_against_underground" ] + // No secondary effect }, { "name": "echoed_voice", @@ -3017,6 +3059,7 @@ "mirror", "ballistics" ] + // No secondary effect }, { "name": "electric_terrain", @@ -3304,6 +3347,7 @@ "protect", "mirror" ] + // No secondary effect }, { "name": "facade", @@ -3353,6 +3397,7 @@ "protect", "mirror" ] + // No secondary effect }, { "name": "fake_out", @@ -3444,14 +3489,17 @@ "category": "physical", "flags": [ "mirror" - ] + ], + "effect": { + "name": "feint" + } }, { "name": "feint_attack", "type": "dark", "power": 60, "pp": 20, - "accuracy": 0, + "accuracy": 255, "priority": 0, "target": "Any", "category": "physical", @@ -3460,6 +3508,7 @@ "protect", "mirror" ] + // No secondary effect }, { "name": "fell_stinger", @@ -4436,6 +4485,7 @@ "target": "Any", "category": "physical", "flags": [] + // No secondary effect }, { "name": "gigavolt_havoc__special", @@ -4447,6 +4497,7 @@ "target": "Any", "category": "special", "flags": [] + // No secondary effect }, { "name": "glaciate", @@ -4652,7 +4703,10 @@ "category": "status", "flags": [ "protect" - ] + ], + "effect": { + "name": "guard_split" + } }, { "name": "guard_swap", @@ -4741,6 +4795,7 @@ "hit_flying", "effective_against_fly" ] + // No secondary effect }, { "name": "gyro_ball", @@ -4809,8 +4864,10 @@ "priority": 0, "target": "AllAlly", "category": "status", - "flags": [] - // TODO: Add effect + "flags": [], + "effect": { + "name": "happy_hour" + } }, { "name": "harden", @@ -5217,7 +5274,7 @@ "flags": [ "ignore-substitute" ] - // Does nothing + // No secondary effect }, { "name": "hone_claws", @@ -5376,6 +5433,7 @@ "target": "Any", "category": "physical", "flags": [] + // No secondary effect }, { "name": "hydro_vortex__special", @@ -5387,6 +5445,7 @@ "target": "Any", "category": "special", "flags": [] + // No secondary effect }, { "name": "hyper_beam", @@ -5757,6 +5816,7 @@ "target": "Any", "category": "physical", "flags": [] + // No secondary effect }, { "name": "inferno_overdrive__special", @@ -5768,6 +5828,7 @@ "target": "Any", "category": "special", "flags": [] + // No secondary effect }, { "name": "infestation", @@ -6565,7 +6626,10 @@ "snatch", "distance", "ignore-substitute" - ] + ], + "effect": { + "name": "magnetic_flux" + } }, { "name": "magnitude", @@ -9238,7 +9302,10 @@ "mirror", "sound", "ignore-substitute" - ] + ], + "effect": { + "name": "round" + } }, { "name": "sacred_fire", @@ -12232,7 +12299,10 @@ "mirror", "sound", "ignore-substitute" - ] + ], + "effect": { + "name": "uproar" + } }, { "name": "v_create", diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Battle/UproarEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Battle/UproarEffect.cs new file mode 100644 index 0000000..ee8fbd8 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Battle/UproarEffect.cs @@ -0,0 +1,53 @@ +using PkmnLib.Static.Utils; + +namespace PkmnLib.Plugin.Gen7.Scripts.Battle; + +[Script(ScriptCategory.Battle, "uproar_effect")] +public class UproarEffect : Script +{ + private IPokemon? _placer; + private bool _hasUsedUproar; + private int _turns = 3; + + public void SetPlacer(IPokemon placer) + { + _placer = placer; + _hasUsedUproar = true; + } + + /// + public override void PreventStatusChange(IPokemon pokemonImpl, StringKey status, ref bool preventStatus) + { + if (status == ScriptUtils.ResolveName()) + { + preventStatus = true; + } + } + + /// + public override void OnBeforeTurnStart(ITurnChoice choice) + { + _hasUsedUproar = false; + } + + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + if (move.User == _placer && move.UseMove.Name == "uproar") + _hasUsedUproar = true; + } + + /// + public override void OnEndTurn(IBattle battle) + { + if (!_hasUsedUproar) + { + RemoveSelf(); + } + _turns--; + if (_turns <= 0) + { + RemoveSelf(); + } + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Feint.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Feint.cs new file mode 100644 index 0000000..d51bd1c --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Feint.cs @@ -0,0 +1,16 @@ +using PkmnLib.Plugin.Gen7.Scripts.Pokemon; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "feint")] +public class Feint : Script +{ + /// + public override void OnBeforeHit(IExecutingMove move, IPokemon target, byte hitIndex) + { + if (target.Volatile.Contains()) + { + target.Volatile.Remove(); + } + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/HappyHour.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/HappyHour.cs new file mode 100644 index 0000000..8136f88 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/HappyHour.cs @@ -0,0 +1,7 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "happy_hour")] +public class HappyHour : Script +{ + // TODO: Implement Happy Hour +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/IncreasedCriticalStage.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/IncreasedCriticalStage.cs index fd68924..f5f675b 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/IncreasedCriticalStage.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/IncreasedCriticalStage.cs @@ -8,10 +8,7 @@ public class IncreasedCriticalStage : Script { // Extreme edge case, should never happen if (stage == byte.MaxValue) - { - move.GetHitData(target, hit).Fail(); return; - } stage += 1; } } \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/MagneticFlux.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/MagneticFlux.cs new file mode 100644 index 0000000..e28175c --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/MagneticFlux.cs @@ -0,0 +1,25 @@ +using PkmnLib.Static.Utils; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "magnetic_flux")] +public class MagneticFlux : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + var battleData = move.User.BattleData; + if (battleData is null) + return; + + foreach (var pokemon in battleData.BattleSide.Pokemon.WhereNotNull()) + { + if (pokemon.ActiveAbility?.Name != "plus" && pokemon.ActiveAbility?.Name != "minus") + continue; + + EventBatchId batch = new(); + pokemon.ChangeStatBoost(Statistic.Defense, 1, pokemon == move.User, batch); + pokemon.ChangeStatBoost(Statistic.SpecialDefense, 1, pokemon == move.User, batch); + } + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Uproar.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Uproar.cs new file mode 100644 index 0000000..694bbb6 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Uproar.cs @@ -0,0 +1,27 @@ +using PkmnLib.Plugin.Gen7.Scripts.Battle; +using PkmnLib.Static.Utils; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "uproar")] +public class Uproar : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + var battle = move.Battle; + + foreach (var pokemon in battle.Sides.SelectMany(x => x.Pokemon).WhereNotNull()) + { + if (pokemon.HasStatus(ScriptUtils.ResolveName())) + { + pokemon.ClearStatus(); + } + } + var script = battle.Volatile.Add(new UproarEffect()); + if (script?.Script is UproarEffect uproarEffect) + { + uproarEffect.SetPlacer(move.User); + } + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/BanefulBunkerEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/BanefulBunkerEffect.cs index c562e25..4066d18 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/BanefulBunkerEffect.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/BanefulBunkerEffect.cs @@ -2,6 +2,7 @@ using PkmnLib.Static.Moves; namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon; +[Script(ScriptCategory.Pokemon, "baneful_bunker")] public class BanefulBunkerEffect : ProtectionEffectScript { /// diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/ProtectionEffectScript.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/ProtectionEffectScript.cs index c67cb50..e8efba2 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/ProtectionEffectScript.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/ProtectionEffectScript.cs @@ -1,5 +1,6 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon; +[Script(ScriptCategory.Pokemon, "protect")] public class ProtectionEffectScript : Script { /// diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/SpikyShieldEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/SpikyShieldEffect.cs index a56b6f3..8e0169c 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/SpikyShieldEffect.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/SpikyShieldEffect.cs @@ -1,5 +1,6 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon; +[Script(ScriptCategory.Pokemon, "spiky_shield")] public class SpikyShieldEffect : ProtectionEffectScript { ///