From c22ad1a793e9b67929b2876343427aa09e9ee165 Mon Sep 17 00:00:00 2001 From: Deukhoofd Date: Thu, 17 Apr 2025 17:51:42 +0200 Subject: [PATCH] Surprisingly, more moves --- .../Models/BattleFlow/MoveTurnExecutor.cs | 2 + PkmnLib.Dynamic/ScriptHandling/Script.cs | 8 +- .../ScriptHandling/ScriptContainer.cs | 4 +- PkmnLib.Static/StatisticSet.cs | 35 ++++---- PkmnLib.Tests/Data/Moves.jsonc | 89 +++++++++++++++---- .../Libraries/Gen7DamageCalculator.cs | 7 +- .../Scripts/Moves/Acupressure.cs | 2 +- .../Scripts/Moves/FoulPlay.cs | 3 +- .../Scripts/Moves/Present.cs | 57 ++++++++++++ .../Scripts/Moves/PsychUp.cs | 25 ++++++ .../Scripts/Moves/PsychicTerrain.cs | 12 +++ .../Scripts/Moves/PsychoShift.cs | 19 ++++ .../Scripts/Moves/Psyshock.cs | 14 +++ .../Scripts/Moves/Psywave.cs | 19 ++++ .../Scripts/Moves/Punishment.cs | 21 +++++ .../Scripts/Moves/Purify.cs | 18 ++++ .../Scripts/Pokemon/PowerTrickEffect.cs | 6 +- 17 files changed, 298 insertions(+), 43 deletions(-) create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Present.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/PsychUp.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/PsychicTerrain.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/PsychoShift.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Psyshock.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Psywave.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Punishment.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Purify.cs diff --git a/PkmnLib.Dynamic/Models/BattleFlow/MoveTurnExecutor.cs b/PkmnLib.Dynamic/Models/BattleFlow/MoveTurnExecutor.cs index 68a0426..a41c91a 100644 --- a/PkmnLib.Dynamic/Models/BattleFlow/MoveTurnExecutor.cs +++ b/PkmnLib.Dynamic/Models/BattleFlow/MoveTurnExecutor.cs @@ -119,6 +119,8 @@ internal static class MoveTurnExecutor break; var hitIndex = i; + executingMove.RunScriptHook(x => x.OnBeforeHit(executingMove, target, hitIndex)); + var useMove = executingMove.UseMove; var hitType = useMove.MoveType; executingMove.RunScriptHook(x => x.ChangeMoveType(executingMove, target, hitIndex, ref hitType)); diff --git a/PkmnLib.Dynamic/ScriptHandling/Script.cs b/PkmnLib.Dynamic/ScriptHandling/Script.cs index e185a4a..700b095 100644 --- a/PkmnLib.Dynamic/ScriptHandling/Script.cs +++ b/PkmnLib.Dynamic/ScriptHandling/Script.cs @@ -312,7 +312,7 @@ public abstract class Script : IDeepCloneable /// This function allows a script to change the actual offensive stat values used when calculating damage /// public virtual void ChangeOffensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint defensiveStat, - ref uint value) + ImmutableStatisticSet targetStats, ref uint value) { } @@ -320,7 +320,7 @@ public abstract class Script : IDeepCloneable /// This function allows a script to change the actual defensive stat values used when calculating damage. /// public virtual void ChangeDefensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint offensiveStat, - ref uint value) + ImmutableStatisticSet targetStats, ref uint value) { } @@ -610,4 +610,8 @@ public abstract class Script : IDeepCloneable public virtual void ChangeCategory(IExecutingMove move, IPokemon target, byte hitIndex, ref MoveCategory category) { } + + public virtual void OnBeforeHit(IExecutingMove move, IPokemon target, byte hitIndex) + { + } } \ No newline at end of file diff --git a/PkmnLib.Dynamic/ScriptHandling/ScriptContainer.cs b/PkmnLib.Dynamic/ScriptHandling/ScriptContainer.cs index ad9f2e8..f20e213 100644 --- a/PkmnLib.Dynamic/ScriptHandling/ScriptContainer.cs +++ b/PkmnLib.Dynamic/ScriptHandling/ScriptContainer.cs @@ -62,13 +62,15 @@ public class ScriptContainer : IReadOnlyScriptContainer /// /// Removes the script from this container. /// - public void Clear() + public Script? Clear() { if (Script is not null) { Script.OnRemove(); } + var script = Script; Script = null; + return script; } public void ClearWithoutRemoving() diff --git a/PkmnLib.Static/StatisticSet.cs b/PkmnLib.Static/StatisticSet.cs index 0594ae7..fc81dc7 100644 --- a/PkmnLib.Static/StatisticSet.cs +++ b/PkmnLib.Static/StatisticSet.cs @@ -83,7 +83,8 @@ public record ImmutableStatisticSet where T : struct /// A set of statistics that can be changed. /// /// -public record StatisticSet : ImmutableStatisticSet, IEnumerable, IDeepCloneable where T : struct +public record StatisticSet : ImmutableStatisticSet, IEnumerable<(Statistic statistic, T value)>, IDeepCloneable + where T : struct { /// public StatisticSet() : base(default, default, default, default, default, default) @@ -214,14 +215,14 @@ public record StatisticSet : ImmutableStatisticSet, IEnumerable, IDeepC } /// - public virtual IEnumerator GetEnumerator() + public virtual IEnumerator<(Statistic, T)> GetEnumerator() { - yield return Hp; - yield return Attack; - yield return Defense; - yield return SpecialAttack; - yield return SpecialDefense; - yield return Speed; + yield return (Statistic.Hp, Hp); + yield return (Statistic.Attack, Attack); + yield return (Statistic.Defense, Defense); + yield return (Statistic.SpecialAttack, SpecialAttack); + yield return (Statistic.SpecialDefense, SpecialDefense); + yield return (Statistic.Speed, Speed); } /// @@ -367,16 +368,16 @@ public record StatBoostStatisticSet : ClampedStatisticSet } /// - public override IEnumerator GetEnumerator() + public override IEnumerator<(Statistic, sbyte)> GetEnumerator() { - yield return Hp; - yield return Attack; - yield return Defense; - yield return SpecialAttack; - yield return SpecialDefense; - yield return Speed; - yield return Evasion; - yield return Accuracy; + yield return (Statistic.Hp, Hp); + yield return (Statistic.Attack, Attack); + yield return (Statistic.Defense, Defense); + yield return (Statistic.SpecialAttack, SpecialAttack); + yield return (Statistic.SpecialDefense, SpecialDefense); + yield return (Statistic.Speed, Speed); + yield return (Statistic.Evasion, Evasion); + yield return (Statistic.Accuracy, Accuracy); } } diff --git a/PkmnLib.Tests/Data/Moves.jsonc b/PkmnLib.Tests/Data/Moves.jsonc index acccfa2..4e9347c 100755 --- a/PkmnLib.Tests/Data/Moves.jsonc +++ b/PkmnLib.Tests/Data/Moves.jsonc @@ -8239,7 +8239,10 @@ "flags": [ "protect", "mirror" - ] + ], + "effect": { + "name": "present" + } }, { "name": "prismatic_laser", @@ -8254,7 +8257,10 @@ "recharge", "protect", "mirror" - ] + ], + "effect": { + "name": "requires_recharge" + } }, { "name": "protect", @@ -8265,7 +8271,10 @@ "priority": 4, "target": "Self", "category": "status", - "flags": [] + "flags": [], + "effect": { + "name": "protect" + } }, { "name": "psybeam", @@ -8279,7 +8288,11 @@ "flags": [ "protect", "mirror" - ] + ], + "effect": { + "name": "confuse", + "chance": 10 + } }, { "name": "psych_up", @@ -8292,7 +8305,10 @@ "category": "status", "flags": [ "ignore-substitute" - ] + ], + "effect": { + "name": "psych_up" + } }, { "name": "psychic", @@ -8306,7 +8322,14 @@ "flags": [ "protect", "mirror" - ] + ], + "effect": { + "name": "change_target_special_defense", + "chance": 10, + "parameters": { + "amount": -1 + } + } }, { "name": "psychic_fangs", @@ -8321,7 +8344,10 @@ "contact", "protect", "mirror" - ] + ], + "effect": { + "name": "brick_break" + } }, { "name": "psychic_terrain", @@ -8334,7 +8360,10 @@ "category": "status", "flags": [ "nonskybattle" - ] + ], + "effect": { + "name": "psychic_terrain" + } }, { "name": "psycho_boost", @@ -8348,7 +8377,13 @@ "flags": [ "protect", "mirror" - ] + ], + "effect": { + "name": "change_user_special_attack", + "parameters": { + "amount": -2 + } + } }, { "name": "psycho_cut", @@ -8362,7 +8397,10 @@ "flags": [ "protect", "mirror" - ] + ], + "effect": { + "name": "increased_critical_stage" + } }, { "name": "psycho_shift", @@ -8376,7 +8414,10 @@ "flags": [ "protect", "mirror" - ] + ], + "effect": { + "name": "psycho_shift" + } }, { "name": "psyshock", @@ -8390,7 +8431,10 @@ "flags": [ "protect", "mirror" - ] + ], + "effect": { + "name": "psyshock" + } }, { "name": "psystrike", @@ -8404,7 +8448,10 @@ "flags": [ "protect", "mirror" - ] + ], + "effect": { + "name": "psyshock" + } }, { "name": "psywave", @@ -8418,7 +8465,10 @@ "flags": [ "protect", "mirror" - ] + ], + "effect": { + "name": "psywave" + } }, { "name": "pulverizing_pancake", @@ -8432,6 +8482,7 @@ "flags": [ "contact" ] + // No secondary effect }, { "name": "punishment", @@ -8446,7 +8497,10 @@ "contact", "protect", "mirror" - ] + ], + "effect": { + "name": "punishment" + } }, { "name": "purify", @@ -8461,7 +8515,10 @@ "protect", "reflectable", "heal" - ] + ], + "effect": { + "name": "purify" + } }, { "name": "pursuit", diff --git a/Plugins/PkmnLib.Plugin.Gen7/Libraries/Gen7DamageCalculator.cs b/Plugins/PkmnLib.Plugin.Gen7/Libraries/Gen7DamageCalculator.cs index 846337c..ce56220 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Libraries/Gen7DamageCalculator.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Libraries/Gen7DamageCalculator.cs @@ -136,9 +136,10 @@ public class Gen7DamageCalculator(bool hasRandomness) : IDamageCalculator var origOffensiveStat = offensiveStat; executingMove.RunScriptHook(script => - script.ChangeOffensiveStatValue(executingMove, target, hitNumber, defensiveStat, ref offensiveStat)); - executingMove.RunScriptHook(script => - script.ChangeDefensiveStatValue(executingMove, target, hitNumber, origOffensiveStat, ref defensiveStat)); + script.ChangeOffensiveStatValue(executingMove, target, hitNumber, defensiveStat, targetStats, + ref offensiveStat)); + executingMove.RunScriptHook(script => script.ChangeDefensiveStatValue(executingMove, target, hitNumber, + origOffensiveStat, targetStats, ref defensiveStat)); var modifier = (float)offensiveStat / defensiveStat; executingMove.RunScriptHook(script => diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Acupressure.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Acupressure.cs index da4fc07..8397202 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Acupressure.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Acupressure.cs @@ -18,7 +18,7 @@ public class Acupressure : Script public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) { // If the target has no stats to raise, the move fails - if (target.StatBoost.All(s => s == 6)) + if (target.StatBoost.All(s => s.value == 6)) { move.GetHitData(target, hit).Fail(); return; diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/FoulPlay.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/FoulPlay.cs index 60e4c6c..4bf3df5 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/FoulPlay.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/FoulPlay.cs @@ -1,3 +1,4 @@ +using PkmnLib.Static; using PkmnLib.Static.Moves; namespace PkmnLib.Plugin.Gen7.Scripts.Moves; @@ -7,7 +8,7 @@ public class FoulPlay : Script { /// public override void ChangeOffensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint _, - ref uint value) + ImmutableStatisticSet targetStats, ref uint value) { value = move.UseMove.Category == MoveCategory.Physical ? target.BoostedStats.Attack diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Present.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Present.cs new file mode 100644 index 0000000..b9f77b9 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Present.cs @@ -0,0 +1,57 @@ +using PkmnLib.Static.Moves; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "present")] +public class Present : Script +{ + private bool _isHealing; + private byte _basePower; + + /// + public override void OnBeforeHit(IExecutingMove move, IPokemon target, byte hitIndex) + { + var battleRandom = move.User.BattleData?.Battle.Random; + if (battleRandom == null) + return; + var percentRoll = battleRandom.GetFloat() * 100.0; + if (percentRoll < 20.0) + { + _isHealing = true; + _basePower = 0; + } + else + { + _isHealing = false; + _basePower = percentRoll switch + { + < 30 => 120, + < 60 => 80, + _ => 40, + }; + } + } + + /// + public override void ChangeCategory(IExecutingMove move, IPokemon target, byte hitIndex, ref MoveCategory category) + { + if (_isHealing) + category = MoveCategory.Status; + } + + /// + public override void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref byte basePower) + { + basePower = _basePower; + } + + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + if (!_isHealing) + return; + + var healAmount = (ushort)(target.MaxHealth / 4); + target.Heal(healAmount); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/PsychUp.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/PsychUp.cs new file mode 100644 index 0000000..39c98b7 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/PsychUp.cs @@ -0,0 +1,25 @@ +using System; +using PkmnLib.Static; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "psych_up")] +public class PsychUp : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + var targetStats = target.StatBoost; + var userStats = move.User.StatBoost; + foreach (Statistic stat in Enum.GetValues(typeof(Statistic))) + { + var targetStat = targetStats.GetStatistic(stat); + var userStat = userStats.GetStatistic(stat); + if (targetStat != userStat) + { + userStats.SetStatistic(stat, targetStat); + } + } + move.User.RecalculateBoostedStats(); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/PsychicTerrain.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/PsychicTerrain.cs new file mode 100644 index 0000000..9f05acf --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/PsychicTerrain.cs @@ -0,0 +1,12 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "psychic_terrain")] +public class PsychicTerrain : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + var battleData = move.User.BattleData; + battleData?.Battle.SetTerrain(ScriptUtils.ResolveName()); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/PsychoShift.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/PsychoShift.cs new file mode 100644 index 0000000..6de20d3 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/PsychoShift.cs @@ -0,0 +1,19 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "psycho_shift")] +public class PsychoShift : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + var statusScript = move.User.StatusScript; + var targetStatusScript = target.StatusScript; + if (statusScript.IsEmpty || !targetStatusScript.IsEmpty) + { + move.GetHitData(target, hit).Fail(); + return; + } + var script = statusScript.Clear(); + targetStatusScript.Set(script!); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Psyshock.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Psyshock.cs new file mode 100644 index 0000000..ad5bca3 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Psyshock.cs @@ -0,0 +1,14 @@ +using PkmnLib.Static; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "psyshock")] +public class Psyshock : Script +{ + /// + public override void ChangeDefensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint offensiveStat, + ImmutableStatisticSet targetStats, ref uint value) + { + value = targetStats.Defense; + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Psywave.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Psywave.cs new file mode 100644 index 0000000..b35f974 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Psywave.cs @@ -0,0 +1,19 @@ +using PkmnLib.Static.Utils; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "psywave")] +public class Psywave : Script +{ + /// + public override void ChangeMoveDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage) + { + if (move.User.BattleData == null) + return; + + var random = move.User.BattleData.Battle.Random.GetInt(0, 10); + var level = (uint)move.User.Level; + var power = level.MultiplyOrMax(0.5f + 0.1f * random); + damage = power; + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Punishment.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Punishment.cs new file mode 100644 index 0000000..85fe709 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Punishment.cs @@ -0,0 +1,21 @@ +using System.Linq; +using PkmnLib.Static; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "punishment")] +public class Punishment : Script +{ + /// + public override void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref byte basePower) + { + if (move.User.BattleData == null) + return; + + var totalPower = 60 + 20 * target.StatBoost.Count(x => + x.statistic is not Statistic.Accuracy and Statistic.Evasion && x.value > 0); + if (totalPower > 200) + totalPower = 200; + basePower = (byte)totalPower; + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Purify.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Purify.cs new file mode 100644 index 0000000..60f0c80 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Purify.cs @@ -0,0 +1,18 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "purify")] +public class Purify : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + if (target.StatusScript.IsEmpty) + { + move.GetHitData(target, hit).Fail(); + return; + } + + target.ClearStatus(); + target.Heal(target.MaxHealth / 2); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/PowerTrickEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/PowerTrickEffect.cs index da0e747..3187079 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/PowerTrickEffect.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/PowerTrickEffect.cs @@ -1,3 +1,5 @@ +using PkmnLib.Static; + namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon; [Script(ScriptCategory.Pokemon, "power_trick")] @@ -5,14 +7,14 @@ public class PowerTrickEffect : Script { /// public override void ChangeOffensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint defensiveStat, - ref uint value) + ImmutableStatisticSet targetStats, ref uint value) { value = defensiveStat; } /// public override void ChangeDefensiveStatValue(IExecutingMove move, IPokemon target, byte hit, uint offensiveStat, - ref uint value) + ImmutableStatisticSet targetStats, ref uint value) { value = offensiveStat; }