From 00fe08dcd4652fb13b1244d8701ec7437331e989 Mon Sep 17 00:00:00 2001 From: Deukhoofd Date: Sat, 1 Feb 2025 15:00:22 +0100 Subject: [PATCH] More moves implemented --- PkmnLib.Dynamic/Models/Battle.cs | 29 +- PkmnLib.Dynamic/Models/BattleChoiceQueue.cs | 4 + .../Models/BattleFlow/MoveTurnExecutor.cs | 6 +- PkmnLib.Dynamic/Models/Choices/MoveChoice.cs | 13 +- PkmnLib.Dynamic/ScriptHandling/Script.cs | 30 ++ .../ScriptHandling/ScriptCategory.cs | 21 +- .../{OutOfRange.cs => OutOfRangeException.cs} | 0 PkmnLib.Static/Utils/NumericHelpers.cs | 7 + PkmnLib.Tests/Data/Moves.json | 334 +++++++++++++++--- .../Libraries/Gen7BattleStatCalculator.cs | 4 + .../Scripts/MoveVolatile/ElectrifyEffect.cs | 17 + .../Scripts/Moves/BulkUp.cs | 15 - .../Scripts/Moves/CalmMind.cs | 15 - .../Moves/ChangeMultipleUserStatBoosts.cs | 43 +++ .../Scripts/Moves/ChangeUserStats.cs | 16 + .../Scripts/Moves/ChipAway.cs | 10 +- .../PkmnLib.Plugin.Gen7/Scripts/Moves/Coil.cs | 16 - .../Scripts/Moves/CosmicPower.cs | 15 - .../Scripts/Moves/Counter.cs | 41 +++ .../Scripts/Moves/Covet.cs | 15 + .../Scripts/Moves/CraftyShield.cs | 17 + .../Scripts/Moves/CrushGrip.cs | 13 + .../Scripts/Moves/Curse.cs | 34 ++ .../Scripts/Moves/DarkestLariat.cs | 13 + .../Scripts/Moves/Defog.cs | 32 ++ .../Scripts/Moves/DestinyBond.cs | 13 + .../PkmnLib.Plugin.Gen7/Scripts/Moves/Dig.cs | 29 ++ .../Scripts/Moves/Disable.cs | 28 ++ .../PkmnLib.Plugin.Gen7/Scripts/Moves/Dive.cs | 28 ++ .../Scripts/Moves/DoomDesire.cs | 31 ++ .../Moves/{CloseCombat.cs => DragonAscent.cs} | 10 +- .../Scripts/Moves/DragonRage.cs | 11 + .../Scripts/Moves/DreamEater.cs | 23 ++ .../Scripts/Moves/EchoedVoice.cs | 31 ++ .../Scripts/Moves/ElectricTerrain.cs | 12 + .../Scripts/Moves/Electrify.cs | 23 ++ .../Scripts/Moves/ElectroBall.cs | 23 ++ .../Scripts/Moves/ProtectionScript.cs | 7 +- .../Scripts/Pokemon/CounterHelperEffect.cs | 20 ++ .../Scripts/Pokemon/DestinyBondEffect.cs | 22 ++ .../Scripts/Pokemon/DigEffect.cs | 34 ++ .../Scripts/Pokemon/DisableEffect.cs | 21 ++ .../Scripts/Pokemon/DiveEffect.cs | 34 ++ .../Scripts/Pokemon/GhostCurseEffect.cs | 18 + .../Scripts/Pokemon/ProtectionEffectScript.cs | 2 +- .../Scripts/Side/CraftyShieldEffect.cs | 14 + .../Scripts/Side/DoomDesireEffect.cs | 63 ++++ .../Scripts/Side/EchoedVoiceData.cs | 14 + .../Scripts/Status/Sleep.cs | 7 + .../Scripts/Terrain/ElectricTerrain.cs | 7 + 50 files changed, 1146 insertions(+), 139 deletions(-) rename PkmnLib.Static/Utils/Errors/{OutOfRange.cs => OutOfRangeException.cs} (100%) create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/MoveVolatile/ElectrifyEffect.cs delete mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/BulkUp.cs delete mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/CalmMind.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ChangeMultipleUserStatBoosts.cs delete mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Coil.cs delete mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/CosmicPower.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Counter.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Covet.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/CraftyShield.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/CrushGrip.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Curse.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/DarkestLariat.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Defog.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/DestinyBond.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Dig.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Disable.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Dive.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/DoomDesire.cs rename Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/{CloseCombat.cs => DragonAscent.cs} (53%) create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/DragonRage.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/DreamEater.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/EchoedVoice.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ElectricTerrain.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Electrify.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ElectroBall.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/CounterHelperEffect.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/DestinyBondEffect.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/DigEffect.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/DisableEffect.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/DiveEffect.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/GhostCurseEffect.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Side/CraftyShieldEffect.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Side/DoomDesireEffect.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Side/EchoedVoiceData.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Status/Sleep.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Terrain/ElectricTerrain.cs diff --git a/PkmnLib.Dynamic/Models/Battle.cs b/PkmnLib.Dynamic/Models/Battle.cs index e9211bd..792c8f9 100644 --- a/PkmnLib.Dynamic/Models/Battle.cs +++ b/PkmnLib.Dynamic/Models/Battle.cs @@ -115,6 +115,10 @@ public interface IBattle : IScriptSource, IDeepCloneable /// StringKey? WeatherName { get; } + void SetTerrain(StringKey? terrainName); + + StringKey? TerrainName { get; } + /// /// Gets the turn choices of the previous turn. This is a list of lists, where each list represents the choices /// for a single turn. The outer list is ordered from oldest to newest turn. @@ -267,6 +271,8 @@ public class BattleImpl : ScriptSource, IBattle return false; var preventMove = false; choice.RunScriptHook(script => script.PreventMoveSelection(moveChoice, ref preventMove)); + if (preventMove) + return false; } return true; @@ -357,6 +363,26 @@ public class BattleImpl : ScriptSource, IBattle /// public StringKey? WeatherName => _weatherScript.Script?.Name; + + private readonly ScriptContainer _terrainScript = new(); + + /// + public void SetTerrain(StringKey? terrainName) + { + if (terrainName.HasValue) + { + if (!Library.ScriptResolver.TryResolve(ScriptCategory.Terrain, terrainName.Value, null, out var script)) + throw new InvalidOperationException($"Terrain script {terrainName} not found."); + _terrainScript.Set(script); + } + else + { + _terrainScript.Clear(); + } + } + + /// + public StringKey? TerrainName => _terrainScript.Script?.Name; private readonly List> _previousTurnChoices = new(); @@ -383,12 +409,13 @@ public class BattleImpl : ScriptSource, IBattle } /// - public override int ScriptCount => 2; + public override int ScriptCount => 3; /// public override void GetOwnScripts(List> scripts) { scripts.Add(_weatherScript); + scripts.Add(_terrainScript); scripts.Add(Volatile); } diff --git a/PkmnLib.Dynamic/Models/BattleChoiceQueue.cs b/PkmnLib.Dynamic/Models/BattleChoiceQueue.cs index 2e47495..fddae4d 100644 --- a/PkmnLib.Dynamic/Models/BattleChoiceQueue.cs +++ b/PkmnLib.Dynamic/Models/BattleChoiceQueue.cs @@ -16,6 +16,7 @@ public class BattleChoiceQueue : IDeepCloneable { private readonly ITurnChoice?[] _choices; private int _currentIndex; + public ITurnChoice? LastRanChoice { get; private set; } /// public BattleChoiceQueue(ITurnChoice[] choices) @@ -36,6 +37,7 @@ public class BattleChoiceQueue : IDeepCloneable var choice = _choices[_currentIndex]; _choices[_currentIndex] = null; _currentIndex++; + LastRanChoice = choice; return choice; } @@ -99,4 +101,6 @@ public class BattleChoiceQueue : IDeepCloneable } internal IReadOnlyList GetChoices() => _choices; + + public ITurnChoice? Where(Func predicate) => _choices.WhereNotNull().FirstOrDefault(predicate); } \ No newline at end of file diff --git a/PkmnLib.Dynamic/Models/BattleFlow/MoveTurnExecutor.cs b/PkmnLib.Dynamic/Models/BattleFlow/MoveTurnExecutor.cs index 9175c5c..0f01e9d 100644 --- a/PkmnLib.Dynamic/Models/BattleFlow/MoveTurnExecutor.cs +++ b/PkmnLib.Dynamic/Models/BattleFlow/MoveTurnExecutor.cs @@ -36,7 +36,8 @@ internal static class MoveTurnExecutor var targetType = moveData.Target; var targets = TargetResolver.ResolveTargets(battle, moveChoice.TargetSide, moveChoice.TargetPosition, targetType); - + moveChoice.RunScriptHook(x => x.ChangeTargets(moveChoice, ref targets)); + byte numberOfHits = 1; moveChoice.RunScriptHook(x => x.ChangeNumberOfHits(moveChoice, ref numberOfHits)); if (numberOfHits == 0) @@ -152,8 +153,11 @@ internal static class MoveTurnExecutor var blockIncomingHit = false; target.RunScriptHook(x => x.BlockIncomingHit(executingMove, target, hitIndex, ref blockIncomingHit)); + executingMove.RunScriptHook(x => x.BlockOutgoingHit(executingMove, target, hitIndex, ref blockIncomingHit)); if (blockIncomingHit) break; + if (executingMove.GetHitData(target, hitIndex).HasFailed) + break; if (useMove.Category == MoveCategory.Status) { var secondaryEffect = useMove.SecondaryEffect; diff --git a/PkmnLib.Dynamic/Models/Choices/MoveChoice.cs b/PkmnLib.Dynamic/Models/Choices/MoveChoice.cs index f4bd9c0..e5c7e3a 100644 --- a/PkmnLib.Dynamic/Models/Choices/MoveChoice.cs +++ b/PkmnLib.Dynamic/Models/Choices/MoveChoice.cs @@ -34,6 +34,8 @@ public interface IMoveChoice : ITurnChoice ScriptContainer Script { get; set; } Dictionary? AdditionalData { get; } + + IScriptSet Volatile { get; } } /// @@ -76,10 +78,17 @@ public class MoveChoice : TurnChoice, IMoveChoice public Dictionary? AdditionalData { get; } /// - public override int ScriptCount => 1 + User.ScriptCount; + public IScriptSet Volatile { get; } = new ScriptSet(); /// - public override void GetOwnScripts(List> scripts) => scripts.Add(Script); + public override int ScriptCount => 2 + User.ScriptCount; + + /// + public override void GetOwnScripts(List> scripts) + { + scripts.Add(Volatile); + scripts.Add(Script); + } /// public override void CollectScripts(List> scripts) diff --git a/PkmnLib.Dynamic/ScriptHandling/Script.cs b/PkmnLib.Dynamic/ScriptHandling/Script.cs index 1e76680..ba54df7 100644 --- a/PkmnLib.Dynamic/ScriptHandling/Script.cs +++ b/PkmnLib.Dynamic/ScriptHandling/Script.cs @@ -121,6 +121,13 @@ public abstract class Script : IDeepCloneable public virtual void ChangeMove(IMoveChoice choice, ref StringKey moveName) { } + + /// + /// Changes the targets of a move choice. This allows for changing the targets of a move before the move starts. + /// + public virtual void ChangeTargets(IMoveChoice moveChoice, ref IReadOnlyList targets) + { + } /// /// This function allows you to change a move into a multi-hit move. The number of hits set here @@ -260,6 +267,14 @@ public abstract class Script : IDeepCloneable public virtual void BypassDefensiveStatBoosts(IExecutingMove move, IPokemon target, byte hit, ref bool bypass) { } + + /// + /// This function allows a script to bypass evasion stat boosts for a move hit. + /// If this is true, the move will handle the evasion stat boosts as if the target has no positive stat boosts. + /// + public virtual void BypassEvasionStatBoosts(IExecutingMove move, IPokemon target, byte hitIndex, ref bool bypass) + { + } /// /// This function allows a script to bypass offensive stat boosts for a move hit. @@ -513,6 +528,21 @@ public abstract class Script : IDeepCloneable { } + public virtual void BlockOutgoingHit(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref bool block) + { + } + + /// + /// Custom triggers for scripts. This allows scripts to run custom events that are not part of the + /// standard battle flow. + /// + /// + /// The name of the event that is triggered. This should be unique for each different event. Overriding scripts + /// should validate the event name is one they should handle. + /// + /// + /// The parameters that are passed to the event. This can be null if no parameters are passed. + /// public virtual void CustomTrigger(StringKey eventName, IDictionary? parameters) { } diff --git a/PkmnLib.Dynamic/ScriptHandling/ScriptCategory.cs b/PkmnLib.Dynamic/ScriptHandling/ScriptCategory.cs index af38e36..0bf031c 100644 --- a/PkmnLib.Dynamic/ScriptHandling/ScriptCategory.cs +++ b/PkmnLib.Dynamic/ScriptHandling/ScriptCategory.cs @@ -16,42 +16,49 @@ public enum ScriptCategory /// and /// Move = 0, + + /// + /// A volatile script effect that is attached to a move choice. + /// + MoveVolatile = 1, /// /// An ability script. Scripts in this category are always abilities, and therefore always /// attached to a Pokemon. /// - Ability = 1, + Ability = 2, /// /// A non volatile status script. Scripts in this category are always non volatile statuses, and /// therefore always attached to a Pokemon. /// - Status = 2, + Status = 3, /// /// A volatile status script. Scripts in this category are always volatile status effects, and /// therefore always attached to a Pokemon. /// - Pokemon = 3, + Pokemon = 4, /// /// A script that can be attached to an entire side. /// - Side = 4, + Side = 5, /// /// A script that can be attached to the entire battle. /// - Battle = 5, + Battle = 6, /// /// A special script for weather, for use on battles. /// - Weather = 6, + Weather = 7, + + Terrain = 8, /// /// A special script for held items. As they're part of a held item, they're attached to a Pokemon. /// - ItemBattleTrigger = 7, + ItemBattleTrigger = 9, } \ No newline at end of file diff --git a/PkmnLib.Static/Utils/Errors/OutOfRange.cs b/PkmnLib.Static/Utils/Errors/OutOfRangeException.cs similarity index 100% rename from PkmnLib.Static/Utils/Errors/OutOfRange.cs rename to PkmnLib.Static/Utils/Errors/OutOfRangeException.cs diff --git a/PkmnLib.Static/Utils/NumericHelpers.cs b/PkmnLib.Static/Utils/NumericHelpers.cs index 9c78fd9..391c415 100644 --- a/PkmnLib.Static/Utils/NumericHelpers.cs +++ b/PkmnLib.Static/Utils/NumericHelpers.cs @@ -49,4 +49,11 @@ public static class NumericHelpers var result = value * multiplier; return result > short.MaxValue ? short.MaxValue : (short)result; } + + public static uint MultiplyOrMax(this uint value, uint multiplier) + { + var result = (ulong)value * multiplier; + return result > uint.MaxValue ? uint.MaxValue : (uint)result; + } + } \ No newline at end of file diff --git a/PkmnLib.Tests/Data/Moves.json b/PkmnLib.Tests/Data/Moves.json index a32881c..dbe9473 100755 --- a/PkmnLib.Tests/Data/Moves.json +++ b/PkmnLib.Tests/Data/Moves.json @@ -1337,7 +1337,11 @@ "snatch" ], "effect": { - "name": "bulk_up" + "name": "change_multiple_user_stat_boosts", + "parameters": { + "attack": 1, + "defense": 1 + } } }, { @@ -1426,7 +1430,11 @@ "snatch" ], "effect": { - "name": "calm_mind" + "name": "change_multiple_user_stat_boosts", + "parameters": { + "specialAttack": 1, + "specialDefense": 1 + } } }, { @@ -1676,9 +1684,10 @@ "mirror" ], "effect": { - "name": "change_user_defense", + "name": "change_multiple_user_stat_boosts", "parameters": { - "amount": -1 + "defense": -1, + "specialDefense": -1 } } }, @@ -1695,7 +1704,12 @@ "snatch" ], "effect": { - "name": "coil" + "name": "change_multiple_user_stat_boosts", + "parameters": { + "attack": 1, + "defense": 1, + "accuracy": 1 + } } }, { @@ -1917,7 +1931,11 @@ "snatch" ], "effect": { - "name": "cosmic_power" + "name": "change_multiple_user_stat_boosts", + "parameters": { + "defense": 1, + "specialDefense": 1 + } } }, { @@ -1973,7 +1991,10 @@ "flags": [ "contact", "protect" - ] + ], + "effect": { + "name": "counter" + } }, { "name": "covet", @@ -1988,7 +2009,10 @@ "contact", "protect", "mirror" - ] + ], + "effect": { + "name": "covet" + } }, { "name": "crabhammer", @@ -2014,7 +2038,10 @@ "priority": 3, "target": "AllAlly", "category": "status", - "flags": [] + "flags": [], + "effect": { + "name": "crafty_shield" + } }, { "name": "cross_chop", @@ -2044,7 +2071,14 @@ "contact", "protect", "mirror" - ] + ], + "effect": { + "name": "set_status", + "chance": 10, + "parameters": { + "status": "poisoned" + } + } }, { "name": "crunch", @@ -2060,7 +2094,14 @@ "protect", "mirror", "bite" - ] + ], + "effect": { + "name": "change_target_defense", + "chance": 20, + "parameters": { + "amount": -1 + } + } }, { "name": "crush_claw", @@ -2075,7 +2116,14 @@ "contact", "protect", "mirror" - ] + ], + "effect": { + "name": "change_target_defense", + "chance": 50, + "parameters": { + "amount": -1 + } + } }, { "name": "crush_grip", @@ -2090,7 +2138,10 @@ "contact", "protect", "mirror" - ] + ], + "effect": { + "name": "crush_grip" + } }, { "name": "curse", @@ -2103,7 +2154,10 @@ "category": "status", "flags": [ "ignore-substitute" - ] + ], + "effect": { + "name": "curse" + } }, { "name": "cut", @@ -2134,7 +2188,11 @@ "mirror", "distance", "pulse" - ] + ], + "effect": { + "name": "flinch", + "chance": 20 + } }, { "name": "dark_void", @@ -2149,7 +2207,13 @@ "protect", "reflectable", "mirror" - ] + ], + "effect": { + "name": "set_status", + "parameters": { + "status": "sleep" + } + } }, { "name": "darkest_lariat", @@ -2164,7 +2228,10 @@ "contact", "protect", "mirror" - ] + ], + "effect": { + "name": "darkest_lariat" + } }, { "name": "dazzling_gleam", @@ -2191,7 +2258,14 @@ "category": "status", "flags": [ "snatch" - ] + ], + "effect": { + "name": "change_multiple_user_stat_boosts", + "parameters": { + "defense": 1, + "specialDefense": 1 + } + } }, { "name": "defense_curl", @@ -2204,7 +2278,13 @@ "category": "status", "flags": [ "snatch" - ] + ], + "effect": { + "name": "change_user_defense", + "parameters": { + "amount": 1 + } + } }, { "name": "defog", @@ -2220,7 +2300,10 @@ "reflectable", "mirror", "ignore-substitute" - ] + ], + "effect": { + "name": "defog" + } }, { "name": "destiny_bond", @@ -2233,7 +2316,10 @@ "category": "status", "flags": [ "ignore-substitute" - ] + ], + "effect": { + "name": "destiny_bond" + } }, { "name": "detect", @@ -2244,7 +2330,10 @@ "priority": 4, "target": "Self", "category": "status", - "flags": [] + "flags": [], + "effect": { + "name": "protect" + } }, { "name": "devastating_drake__physical", @@ -2280,7 +2369,14 @@ "flags": [ "protect", "mirror" - ] + ], + "effect": { + "name": "raise_user_defense", + "chance": 50, + "parameters": { + "amount": 2 + } + } }, { "name": "dig", @@ -2297,7 +2393,10 @@ "protect", "mirror", "nonskybattle" - ] + ], + "effect": { + "name": "dig" + } }, { "name": "disable", @@ -2344,7 +2443,14 @@ "flags": [ "protect", "mirror" - ] + ], + "effect": { + "name": "set_status", + "chance": 30, + "parameters": { + "status": "paralyzed" + } + } }, { "name": "dive", @@ -2361,7 +2467,10 @@ "protect", "mirror", "nonskybattle" - ] + ], + "effect": { + "name": "dive" + } }, { "name": "dizzy_punch", @@ -2377,7 +2486,11 @@ "protect", "mirror", "punch" - ] + ], + "effect": { + "name": "confuse", + "chance": 20 + } }, { "name": "doom_desire", @@ -2388,7 +2501,10 @@ "priority": 0, "target": "Any", "category": "special", - "flags": [] + "flags": [], + "effect": { + "name": "doom_desire" + } }, { "name": "double_edge", @@ -2403,7 +2519,13 @@ "contact", "protect", "mirror" - ] + ], + "effect": { + "name": "recoil", + "parameters": { + "amount": 33.333 + } + } }, { "name": "double_hit", @@ -2418,7 +2540,10 @@ "contact", "protect", "mirror" - ] + ], + "effect": { + "name": "2_hit_move" + } }, { "name": "double_kick", @@ -2433,7 +2558,10 @@ "contact", "protect", "mirror" - ] + ], + "effect": { + "name": "2_hit_move" + } }, { "name": "double_slap", @@ -2448,7 +2576,10 @@ "contact", "protect", "mirror" - ] + ], + "effect": { + "name": "2_5_hit_move" + } }, { "name": "double_team", @@ -2461,7 +2592,13 @@ "category": "status", "flags": [ "snatch" - ] + ], + "effect": { + "name": "change_user_evasion", + "parameters": { + "amount": 1 + } + } }, { "name": "draco_meteor", @@ -2475,7 +2612,13 @@ "flags": [ "protect", "mirror" - ] + ], + "effect": { + "name": "change_user_special_attack", + "parameters": { + "amount": -2 + } + } }, { "name": "dragon_ascent", @@ -2491,7 +2634,14 @@ "protect", "mirror", "distance" - ] + ], + "effect": { + "name": "change_multiple_user_stat_boosts", + "parameters": { + "defense": 1, + "specialDefense": 1 + } + } }, { "name": "dragon_breath", @@ -2505,7 +2655,14 @@ "flags": [ "protect", "mirror" - ] + ], + "effect": { + "name": "set_status", + "chance": 30, + "parameters": { + "status": "paralyzed" + } + } }, { "name": "dragon_claw", @@ -2534,7 +2691,14 @@ "flags": [ "snatch", "dance" - ] + ], + "effect": { + "name": "change_multiple_user_stat_boosts", + "parameters": { + "attack": 1, + "speed": 1 + } + } }, { "name": "dragon_hammer", @@ -2579,7 +2743,10 @@ "flags": [ "protect", "mirror" - ] + ], + "effect": { + "name": "dragon_rage" + } }, { "name": "dragon_rush", @@ -2594,7 +2761,11 @@ "contact", "protect", "mirror" - ] + ], + "effect": { + "name": "flinch", + "chance": 20 + } }, { "name": "dragon_tail", @@ -2609,7 +2780,10 @@ "contact", "protect", "mirror" - ] + ], + "effect": { + "name": "circle_throw" + } }, { "name": "drain_punch", @@ -2626,7 +2800,13 @@ "mirror", "punch", "heal" - ] + ], + "effect": { + "name": "drain", + "parameters": { + "drain_modifier": 0.5 + } + } }, { "name": "draining_kiss", @@ -2642,7 +2822,13 @@ "protect", "mirror", "heal" - ] + ], + "effect": { + "name": "drain", + "parameters": { + "drain_modifier": 0.75 + } + } }, { "name": "dream_eater", @@ -2657,7 +2843,10 @@ "protect", "mirror", "heal" - ] + ], + "effect": { + "name": "dream_eater" + } }, { "name": "drill_peck", @@ -2703,7 +2892,10 @@ "contact", "protect", "mirror" - ] + ], + "effect": { + "name": "2_hit_move" + } }, { "name": "dynamic_punch", @@ -2719,7 +2911,10 @@ "protect", "mirror", "punch" - ] + ], + "effect": { + "name": "confuse" + } }, { "name": "earth_power", @@ -2734,7 +2929,14 @@ "protect", "mirror", "nonskybattle" - ] + ], + "effect": { + "name": "change_target_special_defense", + "chance": 10, + "parameters": { + "amount": -1 + } + } }, { "name": "earthquake", @@ -2748,7 +2950,9 @@ "flags": [ "protect", "mirror", - "nonskybattle" + "nonskybattle", + "hit_underground", + "effective_against_underground" ] }, { @@ -2765,7 +2969,10 @@ "mirror", "sound", "ignore-substitute" - ] + ], + "effect": { + "name": "echoed_voice" + } }, { "name": "eerie_impulse", @@ -2780,7 +2987,13 @@ "protect", "reflectable", "mirror" - ] + ], + "effect": { + "name": "change_target_special_attack", + "parameters": { + "amount": -2 + } + } }, { "name": "egg_bomb", @@ -2808,7 +3021,10 @@ "category": "status", "flags": [ "nonskybattle" - ] + ], + "effect": { + "name": "electric_terrain" + } }, { "name": "electrify", @@ -2822,7 +3038,10 @@ "flags": [ "protect", "mirror" - ] + ], + "effect": { + "name": "electrify" + } }, { "name": "electro_ball", @@ -2837,7 +3056,10 @@ "protect", "mirror", "ballistics" - ] + ], + "effect": { + "name": "electro_ball" + } }, { "name": "electroweb", @@ -2851,7 +3073,13 @@ "flags": [ "protect", "mirror" - ] + ], + "effect": { + "name": "change_target_speed", + "parameters": { + "amount": -1 + } + } }, { "name": "embargo", diff --git a/Plugins/PkmnLib.Plugin.Gen7/Libraries/Gen7BattleStatCalculator.cs b/Plugins/PkmnLib.Plugin.Gen7/Libraries/Gen7BattleStatCalculator.cs index 8f2aa3f..fd4cc40 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Libraries/Gen7BattleStatCalculator.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Libraries/Gen7BattleStatCalculator.cs @@ -55,6 +55,10 @@ public class Gen7BattleStatCalculator : IBattleStatCalculator executingMove.RunScriptHook(x => x.ChangeAccuracyModifier(executingMove, target, hitIndex, ref accuracyModifier)); var modifiedAccuracy = (int)(moveAccuracy * accuracyModifier); var targetEvasion = target.StatBoost.Evasion; + var ignoreEvasion = false; + executingMove.RunScriptHook(x => x.BypassEvasionStatBoosts(executingMove, target, hitIndex, ref ignoreEvasion)); + if (ignoreEvasion) + targetEvasion = 0; var userAccuracy = executingMove.User.StatBoost.Accuracy; var difference = targetEvasion - userAccuracy; var statModifier = difference switch diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/MoveVolatile/ElectrifyEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/MoveVolatile/ElectrifyEffect.cs new file mode 100644 index 0000000..d658555 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/MoveVolatile/ElectrifyEffect.cs @@ -0,0 +1,17 @@ +using PkmnLib.Static; + +namespace PkmnLib.Plugin.Gen7.Scripts.MoveVolatile; + +[Script(ScriptCategory.MoveVolatile, "electrify")] +public class ElectrifyEffect : Script { + /// + public override void ChangeMoveType(IExecutingMove move, IPokemon target, byte hit, ref TypeIdentifier moveType) + { + var battleData = target.BattleData; + if (battleData == null) + return; + if (!battleData.Battle.Library.StaticLibrary.Types.TryGetTypeIdentifier("electric", out var electricType)) + return; + moveType = electricType; + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/BulkUp.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/BulkUp.cs deleted file mode 100644 index dc79cc1..0000000 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/BulkUp.cs +++ /dev/null @@ -1,15 +0,0 @@ -using PkmnLib.Static; - -namespace PkmnLib.Plugin.Gen7.Scripts.Moves; - -[Script(ScriptCategory.Move, "bulk_up")] -public class BulkUp : 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); - } -} \ 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 deleted file mode 100644 index 27688f2..0000000 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/CalmMind.cs +++ /dev/null @@ -1,15 +0,0 @@ -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/ChangeMultipleUserStatBoosts.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ChangeMultipleUserStatBoosts.cs new file mode 100644 index 0000000..28f7676 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ChangeMultipleUserStatBoosts.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using PkmnLib.Static; +using PkmnLib.Static.Utils; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "change_multiple_user_stat_boosts")] +public class ChangeMultipleUserStatBoosts : Script +{ + private Dictionary _statBoosts = new(); + + /// + public override void OnInitialize(IReadOnlyDictionary? parameters) + { + if (parameters == null) + { + throw new ArgumentNullException(nameof(parameters)); + } + + foreach (var par in parameters) + { + if (!Enum.TryParse(par.Key, true, out var stat)) + continue; + if (par.Value is sbyte value) + { + _statBoosts[stat] = value; + } + } + } + + + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + EventBatchId batchId = new(); + foreach (var stat in _statBoosts!) + { + move.User.ChangeStatBoost(stat.Key, stat.Value, true, batchId); + } + } +} \ 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 index a6a0509..184e839 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ChangeUserStats.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ChangeUserStats.cs @@ -76,4 +76,20 @@ public class ChangeUserSpeed : ChangeUserStats public ChangeUserSpeed() : base(Statistic.Speed) { } +} + +[Script(ScriptCategory.Move, "change_user_accuracy")] +public class ChangeUserAccuracy : ChangeUserStats +{ + public ChangeUserAccuracy() : base(Statistic.Accuracy) + { + } +} + +[Script(ScriptCategory.Move, "change_user_evasion")] +public class ChangeUserEvasion : ChangeUserStats +{ + public ChangeUserEvasion() : base(Statistic.Evasion) + { + } } \ 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 index 7b7ec1e..aed619d 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ChipAway.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ChipAway.cs @@ -4,10 +4,10 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Moves; public class ChipAway : Script { /// - public override void BypassDefensiveStatBoosts(IExecutingMove move, IPokemon target, byte hit, ref bool bypass) - { + public override void BypassDefensiveStatBoosts(IExecutingMove move, IPokemon target, byte hit, ref bool bypass) => bypass = true; - } - - // TODO: bypass evasion stat. + + /// + public override void BypassEvasionStatBoosts(IExecutingMove move, IPokemon target, byte hitIndex, ref bool bypass) + => bypass = true; } \ 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 deleted file mode 100644 index cc383a9..0000000 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Coil.cs +++ /dev/null @@ -1,16 +0,0 @@ -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/CosmicPower.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/CosmicPower.cs deleted file mode 100644 index 1ed972f..0000000 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/CosmicPower.cs +++ /dev/null @@ -1,15 +0,0 @@ -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/Counter.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Counter.cs new file mode 100644 index 0000000..d94627f --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Counter.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using PkmnLib.Plugin.Gen7.Scripts.Pokemon; +using PkmnLib.Static.Utils; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "counter")] +public class Counter : Script +{ + /// + public override void OnBeforeTurnStart(ITurnChoice choice) + { + choice.User.Volatile.Add(new CounterHelperEffect()); + } + + /// + public override void ChangeTargets(IMoveChoice moveChoice, ref IReadOnlyList targets) + { + var counterHelper = moveChoice.User.Volatile.Get(); + var lastHitBy = counterHelper?.LastHitBy; + if (lastHitBy == null) + { + moveChoice.Fail(); + return; + } + targets = [lastHitBy]; + } + + /// + public override void ChangeDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage) + { + var counterHelper = move.User.Volatile.Get(); + if (counterHelper == null || counterHelper.LastHitBy == null || counterHelper.LastHitBy != target) + { + move.GetHitData(target, hit).Fail(); + return; + } + var lastDamage = counterHelper.LastDamage; + damage = lastDamage.MultiplyOrMax(2); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Covet.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Covet.cs new file mode 100644 index 0000000..a5fb91a --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Covet.cs @@ -0,0 +1,15 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "covet")] +public class Covet : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + if (target.HeldItem == null) + return; + if (move.User.HeldItem != null) + return; + _ = move.User.SetHeldItem(target.RemoveHeldItem()); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/CraftyShield.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/CraftyShield.cs new file mode 100644 index 0000000..6f3670b --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/CraftyShield.cs @@ -0,0 +1,17 @@ +using PkmnLib.Plugin.Gen7.Scripts.Side; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "crafty_shield")] +public class CraftyShield : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + var battleData = move.User.BattleData; + if (battleData == null) + return; + var side = battleData.Battle.Sides[battleData.SideIndex]; + side.VolatileScripts.Add(new CraftyShieldEffect()); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/CrushGrip.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/CrushGrip.cs new file mode 100644 index 0000000..b964bad --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/CrushGrip.cs @@ -0,0 +1,13 @@ +using System; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "crush_grip")] +public class CrushGrip : Script +{ + /// + public override void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref byte basePower) + { + basePower = Math.Max((byte)(120 * target.CurrentHealth / target.BoostedStats.Hp), (byte)1); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Curse.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Curse.cs new file mode 100644 index 0000000..ff62696 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Curse.cs @@ -0,0 +1,34 @@ +using System.Linq; +using PkmnLib.Plugin.Gen7.Scripts.Pokemon; +using PkmnLib.Static; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "curse")] +public class Curse : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + var battleData = move.User.BattleData; + if (battleData == null) + return; + var typeLibrary = battleData.Battle.Library.StaticLibrary.Types; + if (!typeLibrary.TryGetTypeIdentifier("ghost", out var ghostType)) + return; + if (move.User.Types.Contains(ghostType)) + { + move.User.Damage(move.User.CurrentHealth / 2, DamageSource.Misc); + if (move.User.CurrentHealth == 0) + return; + target.Volatile.Add(new GhostCurseEffect(target)); + } + else + { + EventBatchId batchId = new(); + move.User.ChangeStatBoost(Statistic.Speed, -1, true, batchId); + move.User.ChangeStatBoost(Statistic.Defense, 1, true, batchId); + move.User.ChangeStatBoost(Statistic.Attack, 1, true, batchId); + } + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/DarkestLariat.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/DarkestLariat.cs new file mode 100644 index 0000000..365fae6 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/DarkestLariat.cs @@ -0,0 +1,13 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "darkest_lariat")] +public class DarkestLariat : Script +{ + /// + public override void BypassDefensiveStatBoosts(IExecutingMove move, IPokemon target, byte hit, ref bool bypass) => + bypass = true; + + /// + public override void + BypassEvasionStatBoosts(IExecutingMove move, IPokemon target, byte hitIndex, ref bool bypass) => bypass = true; +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Defog.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Defog.cs new file mode 100644 index 0000000..7b252c7 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Defog.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using PkmnLib.Static.Utils; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "defog")] +public class Defog : Script +{ + public static HashSet DefoggedEffects = new() + { + "mist", + "light_screen", + "reflect", + "safe_guard", + "spikes", + "toxic_spikes", + "stealth_rock", + }; + + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + if (target.BattleData == null) + return; + + var targetSide = target.BattleData.Battle.Sides[target.BattleData.SideIndex]; + foreach (var effect in DefoggedEffects) + { + targetSide.VolatileScripts.Remove(effect); + } + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/DestinyBond.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/DestinyBond.cs new file mode 100644 index 0000000..03463ff --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/DestinyBond.cs @@ -0,0 +1,13 @@ +using PkmnLib.Plugin.Gen7.Scripts.Pokemon; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "destiny_bond")] +public class DestinyBond : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + move.User.Volatile.Add(new DestinyBondEffect()); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Dig.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Dig.cs new file mode 100644 index 0000000..99b5fd5 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Dig.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using PkmnLib.Plugin.Gen7.Scripts.Pokemon; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "dig")] +public class Dig : Script +{ + /// + public override void PreventMove(IExecutingMove move, ref bool prevent) + { + if (move.User.Volatile.Contains()) + return; + + move.User.Volatile.Add(new DigEffect(move.User)); + move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("dig_charge", new Dictionary() + { + { "user", move.User } + })); + prevent = true; + } + + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + move.User.Volatile.Remove(ScriptUtils.ResolveName()); + } + +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Disable.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Disable.cs new file mode 100644 index 0000000..f8e823d --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Disable.cs @@ -0,0 +1,28 @@ +using System.Linq; +using PkmnLib.Plugin.Gen7.Scripts.Pokemon; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "disable")] +public class Disable : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + var battleData = move.User.BattleData; + if (battleData == null) + return; + var choiceQueue = battleData.Battle.PreviousTurnChoices; + var lastMove = choiceQueue + .SelectMany(x => x) + .OfType() + .LastOrDefault(x => x.User == target); + if (lastMove == null) + { + move.GetHitData(target, hit).Fail(); + return; + } + var lastMoveName = lastMove.ChosenMove.MoveData.Name; + target.Volatile.Add(new DisableEffect(lastMoveName)); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Dive.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Dive.cs new file mode 100644 index 0000000..6f04d5f --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Dive.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using PkmnLib.Plugin.Gen7.Scripts.Pokemon; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "dive")] +public class Dive : Script +{ + /// + public override void PreventMove(IExecutingMove move, ref bool prevent) + { + if (move.User.Volatile.Contains()) + return; + + move.User.Volatile.Add(new DigEffect(move.User)); + move.User.BattleData?.Battle.EventHook.Invoke(new DialogEvent("dive_charge", new Dictionary() + { + { "user", move.User } + })); + prevent = true; + } + + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + move.User.Volatile.Remove(ScriptUtils.ResolveName()); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/DoomDesire.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/DoomDesire.cs new file mode 100644 index 0000000..5af8c31 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/DoomDesire.cs @@ -0,0 +1,31 @@ +using System.Linq; +using PkmnLib.Plugin.Gen7.Scripts.Side; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "doom_desire")] +public class DoomDesire : Script +{ + /// + public override void BlockOutgoingHit(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref bool block) + { + var battleData = target.BattleData; + if (battleData == null) + return; + var side = battleData.Battle.Sides[battleData.SideIndex]; + var effect = side.VolatileScripts.Get(); + if (effect != null && effect.HasTarget(battleData.Position)) + { + executingMove.GetHitData(target, hitIndex).Fail(); + return; + } + + if (effect == null) + { + effect = new DoomDesireEffect(side); + side.VolatileScripts.Add(effect); + } + effect.AddTarget(battleData.Position, executingMove.GetHitData(target, hitIndex).Damage); + block = true; + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/CloseCombat.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/DragonAscent.cs similarity index 53% rename from Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/CloseCombat.cs rename to Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/DragonAscent.cs index 18662bf..053e84d 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/CloseCombat.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/DragonAscent.cs @@ -2,14 +2,14 @@ using PkmnLib.Static; namespace PkmnLib.Plugin.Gen7.Scripts.Moves; -[Script(ScriptCategory.Move, "close_combat")] -public class CloseCombat : Script +[Script(ScriptCategory.Move, "dragon_ascent")] +public class DragonAscent : 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); + EventBatchId batchId = new(); + move.User.ChangeStatBoost(Statistic.Defense, -1, true, batchId); + move.User.ChangeStatBoost(Statistic.SpecialDefense, -1, true, batchId); } } \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/DragonRage.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/DragonRage.cs new file mode 100644 index 0000000..8d5d9d2 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/DragonRage.cs @@ -0,0 +1,11 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "dragon_rage")] +public class DragonRage : Script +{ + /// + public override void ChangeDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage) + { + damage = 40; + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/DreamEater.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/DreamEater.cs new file mode 100644 index 0000000..1a6faca --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/DreamEater.cs @@ -0,0 +1,23 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "dream_eater")] +public class DreamEater : Script +{ + /// + public override void BlockOutgoingHit(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref bool block) + { + if (!target.HasStatus("asleep")) + block = true; + } + + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + var user = move.User; + var damage = move.GetHitData(target, hit).Damage; + var healed = (uint)(damage * 0.5f); + if (move.User.HasHeldItem("big_root")) + healed = (uint)(healed * 1.3f); + user.Heal(healed, false); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/EchoedVoice.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/EchoedVoice.cs new file mode 100644 index 0000000..7e2cc9a --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/EchoedVoice.cs @@ -0,0 +1,31 @@ +using PkmnLib.Plugin.Gen7.Scripts.Side; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "echoed_voice")] +public class EchoedVoice : Script +{ + /// + public override void ChangeDamageModifier(IExecutingMove move, IPokemon target, byte hit, ref float modifier) + { + var battleData = move.User.BattleData; + if (battleData == null) + return; + var side = battleData.Battle.Sides[battleData.SideIndex]; + var echoedVoiceData = side.VolatileScripts.Get(); + if (echoedVoiceData == null) + return; + + modifier *= 2; + } + + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + var battleData = move.User.BattleData; + if (battleData == null) + return; + var side = battleData.Battle.Sides[battleData.SideIndex]; + side.VolatileScripts.Add(new EchoedVoiceData()); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ElectricTerrain.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ElectricTerrain.cs new file mode 100644 index 0000000..c98054d --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ElectricTerrain.cs @@ -0,0 +1,12 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "electric_terrain")] +public class ElectricTerrain : 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/Electrify.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Electrify.cs new file mode 100644 index 0000000..0b9ff76 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Electrify.cs @@ -0,0 +1,23 @@ +using PkmnLib.Plugin.Gen7.Scripts.MoveVolatile; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "electrify")] +public class Electrify : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + var choiceQueue = target.BattleData?.Battle.ChoiceQueue; + if (choiceQueue == null) + return; + + if (choiceQueue.Where(x => x is IMoveChoice moveChoice && moveChoice.User == target) is not IMoveChoice choice) + { + move.GetHitData(target, hit).Fail(); + return; + } + + choice.Volatile.Add(new ElectrifyEffect()); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ElectroBall.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ElectroBall.cs new file mode 100644 index 0000000..82c6b5f --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ElectroBall.cs @@ -0,0 +1,23 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "electro_ball")] +public class ElectroBall : Script +{ + /// + public override void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref byte basePower) + { + var user = move.User; + var targetSpeed = target.BoostedStats.Speed; + var userSpeed = user.BoostedStats.Speed; + + var ratio = (float) userSpeed / targetSpeed; + basePower = ratio switch + { + > 4 => 150, + > 3 => 120, + > 2 => 80, + > 1 => 60, + _ => 40 + }; + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ProtectionScript.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ProtectionScript.cs index 450c882..014fc27 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ProtectionScript.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ProtectionScript.cs @@ -3,10 +3,11 @@ using PkmnLib.Plugin.Gen7.Scripts.Pokemon; namespace PkmnLib.Plugin.Gen7.Scripts.Moves; -public abstract class ProtectionScript : Script +[Script(ScriptCategory.Move, "protect")] +public class ProtectionScript : Script { - protected abstract ProtectionEffectScript GetEffectScript(); - + protected virtual ProtectionEffectScript GetEffectScript() => new(); + /// public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) { diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/CounterHelperEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/CounterHelperEffect.cs new file mode 100644 index 0000000..32a6f6e --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/CounterHelperEffect.cs @@ -0,0 +1,20 @@ +using PkmnLib.Static.Moves; + +namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon; + +[Script(ScriptCategory.Pokemon, "counterhelper")] +public class CounterHelperEffect : Script +{ + public IPokemon? LastHitBy { get; private set; } + public uint LastDamage { get; private set; } + + /// + public override void OnIncomingHit(IExecutingMove move, IPokemon target, byte hit) + { + if (move.UseMove.Category == MoveCategory.Physical) + { + LastHitBy = move.User; + LastDamage = move.GetHitData(target, hit).Damage; + } + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/DestinyBondEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/DestinyBondEffect.cs new file mode 100644 index 0000000..4866b47 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/DestinyBondEffect.cs @@ -0,0 +1,22 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon; + +[Script(ScriptCategory.Pokemon, "destiny_bond")] +public class DestinyBondEffect : Script +{ + /// + public override void OnFaint(IPokemon pokemon, DamageSource source) + { + if (source == DamageSource.MoveDamage) + { + if (pokemon.BattleData?.Battle.ChoiceQueue?.LastRanChoice is not IMoveChoice lastChoice) + return; + lastChoice.User.Damage(lastChoice.User.BoostedStats.Hp * 10, DamageSource.Misc); + } + } + + /// + public override void OnBeforeMove(IExecutingMove move) + { + RemoveSelf(); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/DigEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/DigEffect.cs new file mode 100644 index 0000000..3721551 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/DigEffect.cs @@ -0,0 +1,34 @@ +using PkmnLib.Plugin.Gen7.Scripts.Utils; + +namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon; + +[Script(ScriptCategory.Pokemon, "dig")] +public class DigEffect : Script +{ + private readonly IPokemon _owner; + + public DigEffect(IPokemon owner) + { + _owner = owner; + } + /// + public override void ForceTurnSelection(byte sideIndex, byte position, ref ITurnChoice? choice) + { + var opposingSideIndex = (byte)(_owner.BattleData?.SideIndex == 0 ? 1 : 0); + choice = TurnChoiceHelper.CreateMoveChoice(_owner, "dig", opposingSideIndex, position); + } + + /// + public override void BlockIncomingHit(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref bool block) + { + if (!executingMove.UseMove.HasFlag("hit_underground")) + block = true; + } + + /// + public override void ChangeIncomingDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage) + { + if (!move.UseMove.HasFlag("effective_against_underground")) + damage *= 2; + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/DisableEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/DisableEffect.cs new file mode 100644 index 0000000..5475d9a --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/DisableEffect.cs @@ -0,0 +1,21 @@ +using PkmnLib.Static.Utils; + +namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon; + +[Script(ScriptCategory.Pokemon, "disable")] +public class DisableEffect : Script +{ + private readonly StringKey _move; + + public DisableEffect(StringKey move) + { + _move = move; + } + + /// + public override void PreventMoveSelection(IMoveChoice choice, ref bool prevent) + { + if (choice.ChosenMove.MoveData.Name == _move) + prevent = true; + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/DiveEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/DiveEffect.cs new file mode 100644 index 0000000..596375e --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/DiveEffect.cs @@ -0,0 +1,34 @@ +using PkmnLib.Plugin.Gen7.Scripts.Utils; + +namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon; + +[Script(ScriptCategory.Pokemon, "dive")] +public class DiveEffect : Script +{ + private readonly IPokemon _owner; + + public DiveEffect(IPokemon owner) + { + _owner = owner; + } + /// + public override void ForceTurnSelection(byte sideIndex, byte position, ref ITurnChoice? choice) + { + var opposingSideIndex = (byte)(_owner.BattleData?.SideIndex == 0 ? 1 : 0); + choice = TurnChoiceHelper.CreateMoveChoice(_owner, "dive", opposingSideIndex, position); + } + + /// + public override void BlockIncomingHit(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref bool block) + { + if (!executingMove.UseMove.HasFlag("hit_underwater")) + block = true; + } + + /// + public override void ChangeIncomingDamage(IExecutingMove move, IPokemon target, byte hit, ref uint damage) + { + if (!move.UseMove.HasFlag("effective_against_underwater")) + damage *= 2; + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/GhostCurseEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/GhostCurseEffect.cs new file mode 100644 index 0000000..688f911 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/GhostCurseEffect.cs @@ -0,0 +1,18 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon; + +[Script(ScriptCategory.Pokemon, "ghostcurse")] +public class GhostCurseEffect : Script +{ + private IPokemon _pokemon; + + public GhostCurseEffect(IPokemon pokemon) + { + _pokemon = pokemon; + } + + /// + public override void OnEndTurn(IBattle battle) + { + _pokemon.Damage(_pokemon.CurrentHealth / 4, DamageSource.Misc); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/ProtectionEffectScript.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/ProtectionEffectScript.cs index 84f7ed1..c67cb50 100644 --- a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/ProtectionEffectScript.cs +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/ProtectionEffectScript.cs @@ -1,6 +1,6 @@ namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon; -public abstract class ProtectionEffectScript : Script +public class ProtectionEffectScript : Script { /// public override void BlockIncomingHit(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref bool block) diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Side/CraftyShieldEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Side/CraftyShieldEffect.cs new file mode 100644 index 0000000..37474d3 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Side/CraftyShieldEffect.cs @@ -0,0 +1,14 @@ +using PkmnLib.Static.Moves; + +namespace PkmnLib.Plugin.Gen7.Scripts.Side; + +[Script(ScriptCategory.Side, "crafty_shield")] +public class CraftyShieldEffect : Script +{ + /// + public override void StopBeforeMove(IExecutingMove move, ref bool stop) + { + if (move.UseMove.Category == MoveCategory.Status) + stop = true; + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Side/DoomDesireEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Side/DoomDesireEffect.cs new file mode 100644 index 0000000..89f822f --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Side/DoomDesireEffect.cs @@ -0,0 +1,63 @@ +using System.Collections.Generic; + +namespace PkmnLib.Plugin.Gen7.Scripts.Side; + +[Script(ScriptCategory.Side, "doom_desire_effect")] +public class DoomDesireEffect : Script +{ + private class Target + { + public byte Position { get; } + public uint Damage { get; } + public int Turns { get; set; } + + public Target(byte position, uint damage) + { + Position = position; + Damage = damage; + Turns = 3; + } + } + + private readonly IBattleSide _side; + private List _targets = new(); + + public DoomDesireEffect(IBattleSide side) + { + _side = side; + } + + public void AddTarget(byte position, uint damage) + { + _targets.Add(new Target(position, damage)); + } + + public bool HasTarget(byte position) + { + return _targets.Exists(x => x.Position == position); + } + + /// + public override void OnEndTurn(IBattle battle) + { + var toRemove = new List(); + foreach (var v in _targets) + { + v.Turns--; + if (v.Turns == 0) + { + var pokemon = _side.Pokemon[v.Position]; + pokemon?.Damage(v.Damage, DamageSource.Misc); + toRemove.Add(v); + } + } + foreach (var v in toRemove) + { + _targets.Remove(v); + } + if (_targets.Count == 0) + { + RemoveSelf(); + } + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Side/EchoedVoiceData.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Side/EchoedVoiceData.cs new file mode 100644 index 0000000..a922ff7 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Side/EchoedVoiceData.cs @@ -0,0 +1,14 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Side; + +/// +/// Just here to indicate that a Pokemon on this side has used Echoed Voice. +/// +[Script(ScriptCategory.Side, "echoed_voice_data")] +public class EchoedVoiceData : Script +{ + /// + public override void OnEndTurn(IBattle battle) + { + RemoveSelf(); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Status/Sleep.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Status/Sleep.cs new file mode 100644 index 0000000..3af4dcd --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Status/Sleep.cs @@ -0,0 +1,7 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Status; + +[Script(ScriptCategory.Status, "sleep")] +public class Sleep : Script +{ + +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Terrain/ElectricTerrain.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Terrain/ElectricTerrain.cs new file mode 100644 index 0000000..7132eba --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Terrain/ElectricTerrain.cs @@ -0,0 +1,7 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Terrain; + +[Script(ScriptCategory.Terrain, "electric_terrain")] +public class ElectricTerrain : Script +{ + // TODO: Implement Electric Terrain +} \ No newline at end of file