From 3a75493912679627d873c1fddb39cf4601d56c70 Mon Sep 17 00:00:00 2001 From: Deukhoofd Date: Mon, 27 Jan 2025 12:18:48 +0100 Subject: [PATCH] Implements several more moves --- PkmnLib.Dynamic/Models/Pokemon.cs | 46 ++++- PkmnLib.Static/Libraries/TypeLibrary.cs | 13 ++ PkmnLib.Static/Statistic.cs | 12 ++ PkmnLib.Static/StatisticSet.cs | 60 ++++++- PkmnLib.Tests/Data/Moves.json | 162 +++++++++++++++--- .../Scripts/Moves/BulkUp.cs | 5 +- .../Scripts/Moves/CalmMind.cs | 15 ++ .../Scripts/Moves/Camouflage.cs | 7 + .../Scripts/Moves/Captivate.cs | 25 +++ .../Scripts/Moves/Celebrate.cs | 13 ++ .../Scripts/Moves/ChangeUserStats.cs | 79 +++++++++ .../Scripts/Moves/Charge.cs | 15 ++ .../Scripts/Moves/ChipAway.cs | 13 ++ .../Scripts/Moves/CircleThrow.cs | 7 + .../Scripts/Moves/CloseCombat.cs | 15 ++ .../PkmnLib.Plugin.Gen7/Scripts/Moves/Coil.cs | 16 ++ .../Scripts/Moves/Confuse.cs | 13 ++ .../Scripts/Moves/Conversion.cs | 20 +++ .../Scripts/Moves/Conversion2.cs | 57 ++++++ .../Scripts/Moves/Copycat.cs | 24 +++ .../Scripts/Moves/CoreEnforcer.cs | 25 +++ .../Scripts/Moves/CosmicPower.cs | 15 ++ .../Scripts/Moves/ResetTargetStats.cs | 18 ++ .../Scripts/Pokemon/ChargeEffect.cs | 30 ++++ .../Scripts/Pokemon/Confusion.cs | 7 + .../Scripts/Weather/Hail.cs | 2 +- 26 files changed, 676 insertions(+), 38 deletions(-) create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/CalmMind.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Camouflage.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Captivate.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Celebrate.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ChangeUserStats.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Charge.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ChipAway.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/CircleThrow.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/CloseCombat.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Coil.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Confuse.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Conversion.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Conversion2.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Copycat.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/CoreEnforcer.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/CosmicPower.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ResetTargetStats.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/ChargeEffect.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/Confusion.cs diff --git a/PkmnLib.Dynamic/Models/Pokemon.cs b/PkmnLib.Dynamic/Models/Pokemon.cs index 18713cc..ce9983b 100644 --- a/PkmnLib.Dynamic/Models/Pokemon.cs +++ b/PkmnLib.Dynamic/Models/Pokemon.cs @@ -252,12 +252,18 @@ public interface IPokemon : IScriptSource, IDeepCloneable /// The stat to be changed /// The amount to change the stat by /// Whether the change was self-inflicted. This can be relevant in scripts. - bool ChangeStatBoost(Statistic stat, sbyte change, bool selfInflicted); + /// The event batch ID this change is a part of. This is relevant for visual handling + bool ChangeStatBoost(Statistic stat, sbyte change, bool selfInflicted, EventBatchId batchId = default); + /// + /// Suppresses the ability of the Pokémon. + /// + public void SuppressAbility(); + /// /// Returns the currently active ability. /// - IAbility ActiveAbility { get; } + IAbility? ActiveAbility { get; } /// /// Calculates the flat stats on the Pokemon. This should be called when for example the base @@ -362,6 +368,11 @@ public interface IPokemon : IScriptSource, IDeepCloneable /// the Pokémon already has it. /// bool AddType(TypeIdentifier type); + + /// + /// Replace the types of the Pokémon with the provided types. + /// + void SetTypes(IReadOnlyList types); /// /// Converts the data structure to a serializable format. @@ -714,7 +725,7 @@ public class PokemonImpl : ScriptSource, IPokemon } /// - public bool ChangeStatBoost(Statistic stat, sbyte change, bool selfInflicted) + public bool ChangeStatBoost(Statistic stat, sbyte change, bool selfInflicted, EventBatchId batchId = default) { var prevented = false; this.RunScriptHook(script => script.PreventStatBoostChange(this, stat, change, selfInflicted, ref prevented)); @@ -736,18 +747,34 @@ public class PokemonImpl : ScriptSource, IPokemon if (BattleData != null) { var newBoost = StatBoost.GetStatistic(stat); - BattleData.Battle.EventHook.Invoke(new StatBoostEvent(this, stat, oldBoost, newBoost)); + BattleData.Battle.EventHook.Invoke(new StatBoostEvent(this, stat, oldBoost, newBoost) + { + BatchId = batchId, + }); } RecalculateBoostedStats(); return true; } + + /// + /// Whether the ability of the Pokémon is suppressed. + /// + public bool AbilitySuppressed { get; private set; } + + /// + public void SuppressAbility() + { + OverrideAbility = null; + } /// - public IAbility ActiveAbility + public IAbility? ActiveAbility { get { + if (AbilitySuppressed) + return null; if (OverrideAbility != null) return OverrideAbility; var ability = Form.GetAbility(AbilityIndex); @@ -1004,6 +1031,9 @@ public class PokemonImpl : ScriptSource, IPokemon Volatile.Clear(); WeightInKg = Form.Weight; HeightInMeters = Form.Height; + Types = Form.Types; + OverrideAbility = null; + AbilitySuppressed = false; } } } @@ -1032,6 +1062,12 @@ public class PokemonImpl : ScriptSource, IPokemon return true; } + /// + public void SetTypes(IReadOnlyList types) + { + _types = types.ToList(); + } + /// public SerializedPokemon Serialize() => new(this); diff --git a/PkmnLib.Static/Libraries/TypeLibrary.cs b/PkmnLib.Static/Libraries/TypeLibrary.cs index 9886bac..9123d36 100644 --- a/PkmnLib.Static/Libraries/TypeLibrary.cs +++ b/PkmnLib.Static/Libraries/TypeLibrary.cs @@ -28,6 +28,8 @@ public interface IReadOnlyTypeLibrary /// This is equivalent to running get_single_effectiveness on each defending type, and multiplying the results with each other. /// float GetEffectiveness(TypeIdentifier attacking, IReadOnlyList defending); + + IEnumerable<(TypeIdentifier type, float effectiveness)> GetAllEffectivenessFromAttacking(TypeIdentifier attacking); } /// @@ -70,6 +72,17 @@ public class TypeLibrary : IReadOnlyTypeLibrary defending.Aggregate(1, (current, type) => current * GetSingleEffectiveness(attacking, type)); + /// + public IEnumerable<(TypeIdentifier, float)> GetAllEffectivenessFromAttacking(TypeIdentifier attacking) + { + if (attacking.Value < 1 || attacking.Value > _effectiveness.Count) + throw new ArgumentOutOfRangeException(nameof(attacking)); + for (var i = 0; i < _effectiveness.Count; i++) + { + yield return (new TypeIdentifier((byte)(i + 1)), _effectiveness[attacking.Value - 1][i]); + } + } + /// /// Registers a new type in the library. /// diff --git a/PkmnLib.Static/Statistic.cs b/PkmnLib.Static/Statistic.cs index c21dd50..b6e4e5a 100644 --- a/PkmnLib.Static/Statistic.cs +++ b/PkmnLib.Static/Statistic.cs @@ -29,4 +29,16 @@ public enum Statistic : byte /// Speed determines the order that a Pokémon can act in battle. /// Speed, + + /// + /// Evasion determines the likelihood that a Pokémon will dodge an attack. + /// This is not part of base stats, but is a temporary stat boost. + /// + Evasion, + + /// + /// Accuracy determines the likelihood that a Pokémon will hit an attack. + /// This is not part of base stats, but is a temporary stat boost. + /// + Accuracy, } \ No newline at end of file diff --git a/PkmnLib.Static/StatisticSet.cs b/PkmnLib.Static/StatisticSet.cs index 1e69186..e6457f3 100644 --- a/PkmnLib.Static/StatisticSet.cs +++ b/PkmnLib.Static/StatisticSet.cs @@ -168,12 +168,24 @@ public record StatisticSet : ImmutableStatisticSet, IEnumerable, IDeepC Speed = Add(Speed, value); break; default: - throw new ArgumentException("Invalid statistic."); + SetUnknownStat(stat, Add(GetUnknownStat(stat), value)); + break; } return true; } + protected virtual T GetUnknownStat(Statistic stat) + { + throw new ArgumentException($"Invalid statistic {stat}"); + } + + protected virtual void SetUnknownStat(Statistic stat, T value) + { + throw new ArgumentException($"Invalid statistic {stat}"); + } + + /// /// Decreases a statistic in the set by a value. /// @@ -200,14 +212,15 @@ public record StatisticSet : ImmutableStatisticSet, IEnumerable, IDeepC Speed = Subtract(Speed, value); break; default: - throw new ArgumentException("Invalid statistic."); + SetUnknownStat(stat, Subtract(GetUnknownStat(stat), value)); + break; } return true; } /// - public IEnumerator GetEnumerator() + public virtual IEnumerator GetEnumerator() { yield return Hp; yield return Attack; @@ -332,6 +345,47 @@ public record StatBoostStatisticSet : ClampedStatisticSet sbyte speed) : base(hp, attack, defense, specialAttack, specialDefense, speed) { } + + /// + protected override sbyte GetUnknownStat(Statistic stat) + { + return stat switch + { + Statistic.Evasion => Evasion, + Statistic.Accuracy => Accuracy, + _ => throw new ArgumentException($"Invalid statistic {stat}"), + }; + } + + /// + /// + protected override void SetUnknownStat(Statistic stat, sbyte value) + { + switch (stat) + { + case Statistic.Evasion: + Evasion = value; + break; + case Statistic.Accuracy: + Accuracy = value; + break; + default: + throw new ArgumentException($"Invalid statistic {stat}"); + } + } + + /// + public override IEnumerator 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; + } } /// diff --git a/PkmnLib.Tests/Data/Moves.json b/PkmnLib.Tests/Data/Moves.json index f5b1225..a32881c 100755 --- a/PkmnLib.Tests/Data/Moves.json +++ b/PkmnLib.Tests/Data/Moves.json @@ -1424,7 +1424,10 @@ "category": "status", "flags": [ "snatch" - ] + ], + "effect": { + "name": "calm_mind" + } }, { "name": "camouflage", @@ -1437,7 +1440,10 @@ "category": "status", "flags": [ "snatch" - ] + ], + "effect": { + "name": "camouflage" + } }, { "name": "captivate", @@ -1452,7 +1458,10 @@ "protect", "reflectable", "mirror" - ] + ], + "effect": { + "name": "captivate" + } }, { "name": "catastropika", @@ -1476,7 +1485,10 @@ "priority": 0, "target": "Self", "category": "status", - "flags": [] + "flags": [], + "effect": { + "name": "celebrate" + } }, { "name": "charge", @@ -1489,7 +1501,10 @@ "category": "status", "flags": [ "snatch" - ] + ], + "effect": { + "name": "charge" + } }, { "name": "charge_beam", @@ -1503,7 +1518,14 @@ "flags": [ "protect", "mirror" - ] + ], + "effect": { + "name": "change_user_special_attack", + "chance": 70, + "parameters": { + "amount": 1 + } + } }, { "name": "charm", @@ -1518,7 +1540,13 @@ "protect", "reflectable", "mirror" - ] + ], + "effect": { + "name": "change_target_attack", + "parameters": { + "amount": -2 + } + } }, { "name": "chatter", @@ -1535,7 +1563,10 @@ "sound", "distance", "ignore-substitute" - ] + ], + "effect": { + "name": "confuse" + } }, { "name": "chip_away", @@ -1550,7 +1581,10 @@ "contact", "protect", "mirror" - ] + ], + "effect": { + "name": "chip_away" + } }, { "name": "circle_throw", @@ -1565,7 +1599,10 @@ "contact", "protect", "mirror" - ] + ], + "effect": { + "name": "circle_throw" + } }, { "name": "clamp", @@ -1580,7 +1617,10 @@ "contact", "protect", "mirror" - ] + ], + "effect": { + "name": "bind" + } }, { "name": "clanging_scales", @@ -1596,7 +1636,13 @@ "mirror", "sound", "ignore-substitute" - ] + ], + "effect": { + "name": "change_user_defense", + "parameters": { + "amount": -1 + } + } }, { "name": "clear_smog", @@ -1610,7 +1656,10 @@ "flags": [ "protect", "mirror" - ] + ], + "effect": { + "name": "reset_target_stats" + } }, { "name": "close_combat", @@ -1625,7 +1674,13 @@ "contact", "protect", "mirror" - ] + ], + "effect": { + "name": "change_user_defense", + "parameters": { + "amount": -1 + } + } }, { "name": "coil", @@ -1638,7 +1693,10 @@ "category": "status", "flags": [ "snatch" - ] + ], + "effect": { + "name": "coil" + } }, { "name": "comet_punch", @@ -1654,7 +1712,10 @@ "protect", "mirror", "punch" - ] + ], + "effect": { + "name": "2_5_hit_move" + } }, { "name": "confide", @@ -1670,7 +1731,13 @@ "mirror", "sound", "ignore-substitute" - ] + ], + "effect": { + "name": "change_target_special_attack", + "parameters": { + "amount": -1 + } + } }, { "name": "confuse_ray", @@ -1685,7 +1752,10 @@ "protect", "reflectable", "mirror" - ] + ], + "effect": { + "name": "confuse" + } }, { "name": "confusion", @@ -1699,7 +1769,11 @@ "flags": [ "protect", "mirror" - ] + ], + "effect": { + "name": "confuse", + "chance": 10 + } }, { "name": "constrict", @@ -1714,7 +1788,14 @@ "contact", "protect", "mirror" - ] + ], + "effect": { + "name": "change_target_speed", + "chance": 10, + "parameters": { + "amount": -1 + } + } }, { "name": "continental_crush__physical", @@ -1749,7 +1830,10 @@ "category": "status", "flags": [ "snatch" - ] + ], + "effect": { + "name": "conversion" + } }, { "name": "conversion_2", @@ -1762,7 +1846,10 @@ "category": "status", "flags": [ "ignore-substitute" - ] + ], + "effect": { + "name": "conversion_2" + } }, { "name": "copycat", @@ -1773,7 +1860,10 @@ "priority": 0, "target": "Self", "category": "status", - "flags": [] + "flags": [], + "effect": { + "name": "copycat" + } }, { "name": "core_enforcer", @@ -1787,7 +1877,10 @@ "flags": [ "protect", "mirror" - ] + ], + "effect": { + "name": "core_enforcer" + } }, { "name": "corkscrew_crash__physical", @@ -1822,7 +1915,10 @@ "category": "status", "flags": [ "snatch" - ] + ], + "effect": { + "name": "cosmic_power" + } }, { "name": "cotton_guard", @@ -1835,7 +1931,13 @@ "category": "status", "flags": [ "snatch" - ] + ], + "effect": { + "name": "change_user_defense", + "parameters": { + "amount": 3 + } + } }, { "name": "cotton_spore", @@ -1851,7 +1953,13 @@ "reflectable", "mirror", "powder" - ] + ], + "effect": { + "name": "change_target_speed", + "parameters": { + "amount": -2 + } + } }, { "name": "counter", diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/BulkUp.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/BulkUp.cs index 37832ce..dc79cc1 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/BulkUp.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/BulkUp.cs @@ -8,7 +8,8 @@ public class BulkUp : Script /// public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) { - move.User.ChangeStatBoost(Statistic.Attack, 1, true); - move.User.ChangeStatBoost(Statistic.Defense, 1, true); + EventBatchId eventBatchId = new(); + move.User.ChangeStatBoost(Statistic.Attack, 1, true, eventBatchId); + move.User.ChangeStatBoost(Statistic.Defense, 1, true, eventBatchId); } } \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/CalmMind.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/CalmMind.cs new file mode 100644 index 0000000..27688f2 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/CalmMind.cs @@ -0,0 +1,15 @@ +using PkmnLib.Static; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "calm_mind")] +public class CalmMind : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + EventBatchId eventBatchId = new(); + move.User.ChangeStatBoost(Statistic.SpecialAttack, 1, true, eventBatchId); + move.User.ChangeStatBoost(Statistic.SpecialDefense, 1, true, eventBatchId); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Camouflage.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Camouflage.cs new file mode 100644 index 0000000..a5d1721 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Camouflage.cs @@ -0,0 +1,7 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "camouflage")] +public class Camouflage : Script +{ + // FIXME: Implement this. How to get the terrain in a sane manner? +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Captivate.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Captivate.cs new file mode 100644 index 0000000..8843f2c --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Captivate.cs @@ -0,0 +1,25 @@ +using PkmnLib.Static; +using PkmnLib.Static.Species; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "captivate")] +public class Captivate : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + var user = move.User; + if (target.Gender == Gender.Genderless || target.Gender == user.Gender) + { + move.GetHitData(target, hit).Fail(); + return; + } + if (target.ActiveAbility?.Name == "oblivious") + { + move.GetHitData(target, hit).Fail(); + return; + } + target.ChangeStatBoost(Statistic.SpecialAttack, -2, false); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Celebrate.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Celebrate.cs new file mode 100644 index 0000000..ffeb81b --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Celebrate.cs @@ -0,0 +1,13 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "celebrate")] +public class Celebrate : Script +{ + /// + public override void PreventMoveSelection(IMoveChoice choice, ref bool prevent) + { + // This move is mostly useless, and it's not worth the effort to implement it. + // Prevent it from being selected. + prevent = true; + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ChangeUserStats.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ChangeUserStats.cs new file mode 100644 index 0000000..a6a0509 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ChangeUserStats.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using PkmnLib.Static; +using PkmnLib.Static.Utils; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +public abstract class ChangeUserStats : Script +{ + private readonly Statistic _stat; + private sbyte _amount; + + protected ChangeUserStats(Statistic stat) + { + _stat = stat; + } + + /// + public override void OnInitialize(IReadOnlyDictionary? parameters) + { + if (parameters == null) + { + throw new ArgumentNullException(nameof(parameters)); + } + + if (!parameters.TryGetValue("amount", out var amount) || amount == null) + { + throw new ArgumentException("Parameter 'amount' is required."); + } + + _amount = (sbyte)amount; + } + + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + move.User.ChangeStatBoost(_stat, _amount, true); + } +} + +[Script(ScriptCategory.Move, "change_user_attack")] +public class ChangeUserAttack : ChangeUserStats +{ + public ChangeUserAttack() : base(Statistic.Attack) + { + } +} + +[Script(ScriptCategory.Move, "change_user_defense")] +public class ChangeUserDefense : ChangeUserStats +{ + public ChangeUserDefense() : base(Statistic.Defense) + { + } +} + +[Script(ScriptCategory.Move, "change_user_special_attack")] +public class ChangeUserSpecialAttack : ChangeUserStats +{ + public ChangeUserSpecialAttack() : base(Statistic.SpecialAttack) + { + } +} + +[Script(ScriptCategory.Move, "change_user_special_defense")] +public class ChangeUserSpecialDefense : ChangeUserStats +{ + public ChangeUserSpecialDefense() : base(Statistic.SpecialDefense) + { + } +} + +[Script(ScriptCategory.Move, "change_user_speed")] +public class ChangeUserSpeed : ChangeUserStats +{ + public ChangeUserSpeed() : base(Statistic.Speed) + { + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Charge.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Charge.cs new file mode 100644 index 0000000..6ec9fce --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Charge.cs @@ -0,0 +1,15 @@ +using PkmnLib.Plugin.Gen7.Scripts.Pokemon; +using PkmnLib.Static; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "charge")] +public class Charge : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + move.User.ChangeStatBoost(Statistic.SpecialDefense, 1, true); + move.User.Volatile.Add(new ChargeEffect()); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ChipAway.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ChipAway.cs new file mode 100644 index 0000000..7b7ec1e --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ChipAway.cs @@ -0,0 +1,13 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "chip_away")] +public class ChipAway : Script +{ + /// + public override void BypassDefensiveStatBoosts(IExecutingMove move, IPokemon target, byte hit, ref bool bypass) + { + bypass = true; + } + + // TODO: bypass evasion stat. +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/CircleThrow.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/CircleThrow.cs new file mode 100644 index 0000000..24a92e5 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/CircleThrow.cs @@ -0,0 +1,7 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "circle_throw")] +public class CircleThrow : Script +{ + // TODO: Implement this. How to handle forced switch? +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/CloseCombat.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/CloseCombat.cs new file mode 100644 index 0000000..18662bf --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/CloseCombat.cs @@ -0,0 +1,15 @@ +using PkmnLib.Static; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "close_combat")] +public class CloseCombat : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + EventBatchId eventBatchId = new(); + move.User.ChangeStatBoost(Statistic.Defense, -1, true, eventBatchId); + move.User.ChangeStatBoost(Statistic.SpecialDefense, -1, true, eventBatchId); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Coil.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Coil.cs new file mode 100644 index 0000000..cc383a9 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Coil.cs @@ -0,0 +1,16 @@ +using PkmnLib.Static; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "coil")] +public class Coil : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + EventBatchId eventBatchId = new(); + move.User.ChangeStatBoost(Statistic.Attack, 1, true, eventBatchId); + move.User.ChangeStatBoost(Statistic.Defense, 1, true, eventBatchId); + move.User.ChangeStatBoost(Statistic.Accuracy, 1, true, eventBatchId); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Confuse.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Confuse.cs new file mode 100644 index 0000000..47a259b --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Confuse.cs @@ -0,0 +1,13 @@ +using PkmnLib.Plugin.Gen7.Scripts.Pokemon; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "confuse")] +public class Confuse : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + target.Volatile.Add(new Confusion()); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Conversion.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Conversion.cs new file mode 100644 index 0000000..2607654 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Conversion.cs @@ -0,0 +1,20 @@ +using System.Linq; +using PkmnLib.Static.Utils; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "conversion")] +public class Conversion : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + var moveType = move.User.Moves.WhereNotNull().FirstOrDefault()?.MoveData.MoveType; + if (moveType == null) + { + move.GetHitData(target, hit).Fail(); + return; + } + move.User.SetTypes([moveType.Value]); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Conversion2.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Conversion2.cs new file mode 100644 index 0000000..69f9c08 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Conversion2.cs @@ -0,0 +1,57 @@ +using System.Linq; +using PkmnLib.Static.Utils; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "conversion_2")] +public class Conversion2 : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + var previousTurnChoices = target.BattleData?.Battle.PreviousTurnChoices; + var nextExecutingChoice = target.BattleData?.Battle.ChoiceQueue?.Peek(); + var lastMoveByTarget = previousTurnChoices? + // The previous turn choices include the choices of the current turn, so we need to have special handling for + // the current turn + .Select((x, index) => + { + // All choices before the current turn are valid + if (index < previousTurnChoices.Count - 1) + return x; + // If there is no next choice, we're at the end of the list, so we can just return the whole list + if (nextExecutingChoice == null) + return x; + // Otherwise we determine where the next choice is and return everything before that + var indexOfNext = x.IndexOf(nextExecutingChoice); + if (indexOfNext == -1) + return x; + return x.Take(indexOfNext); + }) + .SelectMany(x => x) + // We only want the last move choice by the target + .OfType().FirstOrDefault(x => x.User == target); + if (lastMoveByTarget == null) + { + move.GetHitData(target, hit).Fail(); + return; + } + + var typeLibrary = move.User.BattleData!.Battle.Library.StaticLibrary.Types; + // Get all types against which the last move would be not very effective + var type = typeLibrary.GetAllEffectivenessFromAttacking(lastMoveByTarget.ChosenMove.MoveData.MoveType) + .Where(x => x.effectiveness < 1) + // Shuffle them randomly, but deterministically + .OrderBy(_ => move.User.BattleData.Battle.Random.GetInt()) + .ThenBy(x => x.type.Value) + // And grab the first one + .Select(x => x.type) + .FirstOrDefault(); + if (type == null) + { + move.GetHitData(target, hit).Fail(); + return; + } + move.User.SetTypes([type]); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Copycat.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Copycat.cs new file mode 100644 index 0000000..47a69eb --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Copycat.cs @@ -0,0 +1,24 @@ +using System.Linq; +using PkmnLib.Plugin.Gen7.Scripts.Utils; +using PkmnLib.Static.Utils; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "copycat")] +public class Copycat : Script +{ + /// + public override void ChangeMove(IMoveChoice choice, ref StringKey moveName) + { + var lastMove = choice.User.BattleData?.Battle.PreviousTurnChoices + .SelectMany(x => x) + .OfType() + .LastOrDefault(); + if (lastMove == null || !lastMove.ChosenMove.MoveData.CanCopyMove()) + { + choice.Fail(); + return; + } + moveName = lastMove.ChosenMove.MoveData.Name; + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/CoreEnforcer.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/CoreEnforcer.cs new file mode 100644 index 0000000..d035787 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/CoreEnforcer.cs @@ -0,0 +1,25 @@ +using System.Linq; +using PkmnLib.Static.Utils; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "core_enforcer")] +public class CoreEnforcer : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + var battleData = target.BattleData; + if (battleData == null) + return; + var turnChoices = battleData.Battle.PreviousTurnChoices.Last(); + var currentChoiceIndex = turnChoices.IndexOf(move.MoveChoice); + if (currentChoiceIndex == -1 || + !turnChoices.Take(currentChoiceIndex).Any(choice => choice is IMoveChoice or IItemChoice)) + { + move.GetHitData(target, hit).Fail(); + return; + } + target.SuppressAbility(); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/CosmicPower.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/CosmicPower.cs new file mode 100644 index 0000000..1ed972f --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/CosmicPower.cs @@ -0,0 +1,15 @@ +using PkmnLib.Static; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "cosmic_power")] +public class CosmicPower : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + EventBatchId eventBatchId = new(); + move.User.ChangeStatBoost(Statistic.Defense, 1, true, eventBatchId); + move.User.ChangeStatBoost(Statistic.SpecialDefense, 1, true, eventBatchId); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ResetTargetStats.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ResetTargetStats.cs new file mode 100644 index 0000000..a0d6dc4 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ResetTargetStats.cs @@ -0,0 +1,18 @@ +using System; +using PkmnLib.Static; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "reset_target_stats")] +public class ResetTargetStats : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + EventBatchId eventBatchId = new(); + foreach (Statistic stat in Enum.GetValues(typeof(Statistic))) + { + target.ChangeStatBoost(stat, (sbyte)-target.StatBoost.GetStatistic(stat), true, eventBatchId); + } + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/ChargeEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/ChargeEffect.cs new file mode 100644 index 0000000..ef0c135 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/ChargeEffect.cs @@ -0,0 +1,30 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon; + +[Script(ScriptCategory.Pokemon, "charge_effect")] +public class ChargeEffect : Script +{ + private bool _turnOfUse = true; + + /// + public override void ChangeDamageModifier(IExecutingMove move, IPokemon target, byte hit, ref float modifier) + { + var library = target.BattleData?.Battle.Library; + if (library == null) + return; + if (!library.StaticLibrary.Types.TryGetTypeIdentifier("electric", out var electricType)) + return; + if (move.UseMove.MoveType == electricType) + modifier *= 2; + } + + /// + public override void OnEndTurn(IBattle battle) + { + if (_turnOfUse) + { + _turnOfUse = false; + return; + } + RemoveSelf(); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/Confusion.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/Confusion.cs new file mode 100644 index 0000000..6e35836 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/Confusion.cs @@ -0,0 +1,7 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon; + +[Script(ScriptCategory.Pokemon, "confusion")] +public class Confusion : Script +{ + // TODO: Implement confusion +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Weather/Hail.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Weather/Hail.cs index f81cf64..ac37c7f 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Weather/Hail.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Weather/Hail.cs @@ -36,7 +36,7 @@ public class Hail : Script continue; if (pokemon.Types.Contains(iceType)) continue; - if (_hailIgnoreAbilities.Contains(pokemon.ActiveAbility.Name)) + if (pokemon.ActiveAbility != null && _hailIgnoreAbilities.Contains(pokemon.ActiveAbility.Name)) continue; var maxHealth = pokemon.BoostedStats.Hp;