From 1b54c78b07127850343635087733a4f96a87ffed Mon Sep 17 00:00:00 2001 From: Deukhoofd Date: Thu, 17 Apr 2025 10:22:24 +0200 Subject: [PATCH] More moves --- .../Models/BattleFlow/MoveTurnExecutor.cs | 1 + PkmnLib.Dynamic/Models/ExecutingMove.cs | 5 +++ PkmnLib.Dynamic/Models/Pokemon.cs | 26 ++++++++----- PkmnLib.Dynamic/ScriptHandling/Script.cs | 7 ++++ PkmnLib.Tests/Data/Moves.json | 38 ++++++++++++++++--- .../Scripts/Moves/Outrage.cs | 22 +++++++++++ .../Scripts/Moves/PainSplit.cs | 30 +++++++++++++++ .../Scripts/Moves/ParabolicCharge.cs | 22 +++++++++++ .../Scripts/Pokemon/OutrageEffect.cs | 37 ++++++++++++++++++ 9 files changed, 172 insertions(+), 16 deletions(-) create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Outrage.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/PainSplit.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ParabolicCharge.cs create mode 100644 Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/OutrageEffect.cs diff --git a/PkmnLib.Dynamic/Models/BattleFlow/MoveTurnExecutor.cs b/PkmnLib.Dynamic/Models/BattleFlow/MoveTurnExecutor.cs index ddc4543..e669abc 100644 --- a/PkmnLib.Dynamic/Models/BattleFlow/MoveTurnExecutor.cs +++ b/PkmnLib.Dynamic/Models/BattleFlow/MoveTurnExecutor.cs @@ -85,6 +85,7 @@ internal static class MoveTurnExecutor { ExecuteMoveChoiceForTarget(battle, executingMove, target); } + executingMove.RunScriptHook(x => x.OnAfterMove(executingMove)); } private static void ExecuteMoveChoiceForTarget(IBattle battle, IExecutingMove executingMove, IPokemon target) diff --git a/PkmnLib.Dynamic/Models/ExecutingMove.cs b/PkmnLib.Dynamic/Models/ExecutingMove.cs index be41e79..f0486e8 100644 --- a/PkmnLib.Dynamic/Models/ExecutingMove.cs +++ b/PkmnLib.Dynamic/Models/ExecutingMove.cs @@ -139,6 +139,8 @@ public interface IExecutingMove : IScriptSource /// The underlying move choice. /// IMoveChoice MoveChoice { get; } + + IReadOnlyList Hits { get; } } /// @@ -225,6 +227,9 @@ public class ExecutingMoveImpl : ScriptSource, IExecutingMove /// public IMoveChoice MoveChoice { get; } + /// + public IReadOnlyList Hits => _hits; + /// public override int ScriptCount => 2 + User.ScriptCount; diff --git a/PkmnLib.Dynamic/Models/Pokemon.cs b/PkmnLib.Dynamic/Models/Pokemon.cs index 6e5ee65..5d4a26e 100644 --- a/PkmnLib.Dynamic/Models/Pokemon.cs +++ b/PkmnLib.Dynamic/Models/Pokemon.cs @@ -315,7 +315,7 @@ public interface IPokemon : IScriptSource, IDeepCloneable /// /// Damages the Pokemon by a certain amount of damage, from a damage source. /// - void Damage(uint damage, DamageSource source, EventBatchId batchId = default); + void Damage(uint damage, DamageSource source, EventBatchId batchId = default, bool forceDamage = false); /// /// Forces the Pokémon to faint. @@ -326,7 +326,7 @@ public interface IPokemon : IScriptSource, IDeepCloneable /// Heals the Pokemon by a specific amount. Unless allow_revive is set to true, this will not /// heal if the Pokemon has 0 health. If the amount healed is 0, this will return false. /// - bool Heal(uint heal, bool allowRevive = false); + bool Heal(uint heal, bool allowRevive = false, EventBatchId batchId = default, bool forceHeal = false); /// /// Restores all PP of the Pokemon. @@ -943,12 +943,12 @@ public class PokemonImpl : ScriptSource, IPokemon public bool IsFainted => CurrentHealth == 0; /// - public void Damage(uint damage, DamageSource source, EventBatchId batchId) + public void Damage(uint damage, DamageSource source, EventBatchId batchId, bool forceDamage = false) { // If the Pokémon is already fainted, we don't need to do anything. if (IsFainted) return; - if (BattleData is not null) + if (BattleData is not null && !forceDamage) { var dmg = damage; this.RunScriptHook(script => script.ChangeIncomingDamage(this, source, ref dmg)); @@ -1013,7 +1013,7 @@ public class PokemonImpl : ScriptSource, IPokemon } /// - public bool Heal(uint heal, bool allowRevive) + public bool Heal(uint heal, bool allowRevive, EventBatchId batchId = default, bool forceHeal = false) { if (IsFainted && !allowRevive) return false; @@ -1023,13 +1023,19 @@ public class PokemonImpl : ScriptSource, IPokemon heal = maxAmount; if (heal == 0) return false; - var prevented = false; - this.RunScriptHook(x => x.PreventHeal(this, heal, allowRevive, ref prevented)); - if (prevented) - return false; + if (!forceHeal) + { + var prevented = false; + this.RunScriptHook(x => x.PreventHeal(this, heal, allowRevive, ref prevented)); + if (prevented) + return false; + } var newHealth = CurrentHealth + heal; - BattleData?.Battle.EventHook.Invoke(new HealEvent(this, CurrentHealth, newHealth)); + BattleData?.Battle.EventHook.Invoke(new HealEvent(this, CurrentHealth, newHealth) + { + BatchId = batchId, + }); CurrentHealth = newHealth; return true; } diff --git a/PkmnLib.Dynamic/ScriptHandling/Script.cs b/PkmnLib.Dynamic/ScriptHandling/Script.cs index d8da606..f32ebde 100644 --- a/PkmnLib.Dynamic/ScriptHandling/Script.cs +++ b/PkmnLib.Dynamic/ScriptHandling/Script.cs @@ -179,6 +179,13 @@ public abstract class Script : IDeepCloneable { } + /// + /// This function runs immediately after all targets have had their hits executed. + /// + public virtual void OnAfterMove(IExecutingMove move) + { + } + /// /// This function allows a script to prevent a move that is targeted at its owner. If set to true /// the move fails, and fail events get triggered. diff --git a/PkmnLib.Tests/Data/Moves.json b/PkmnLib.Tests/Data/Moves.json index 9d5349e..8420e70 100755 --- a/PkmnLib.Tests/Data/Moves.json +++ b/PkmnLib.Tests/Data/Moves.json @@ -7555,7 +7555,10 @@ "reflectable", "mirror", "ignore-substitute" - ] + ], + "effect": { + "name": "foresight" + } }, { "name": "ominous_wind", @@ -7569,7 +7572,14 @@ "flags": [ "protect", "mirror" - ] + ], + "effect": { + "name": "change_all_target_stats", + "chance": 10, + "parameters": { + "amount": 1 + } + } }, { "name": "origin_pulse", @@ -7584,6 +7594,7 @@ "protect", "mirror" ] + // No secondary effect }, { "name": "outrage", @@ -7598,7 +7609,10 @@ "contact", "protect", "mirror" - ] + ], + "effect": { + "name": "outrage" + } }, { "name": "overheat", @@ -7612,7 +7626,13 @@ "flags": [ "protect", "mirror" - ] + ], + "effect": { + "name": "change_user_special_attack", + "parameters": { + "amount": -2 + } + } }, { "name": "pain_split", @@ -7626,7 +7646,10 @@ "flags": [ "protect", "mirror" - ] + ], + "effect": { + "name": "pain_split" + } }, { "name": "parabolic_charge", @@ -7641,7 +7664,10 @@ "protect", "mirror", "heal" - ] + ], + "effect": { + "name": "parabolic_charge" + } }, { "name": "parting_shot", diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Outrage.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Outrage.cs new file mode 100644 index 0000000..0287f4c --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Outrage.cs @@ -0,0 +1,22 @@ +using PkmnLib.Plugin.Gen7.Scripts.Pokemon; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "outrage")] +public class Outrage : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + if (move.User.Volatile.Contains()) + return; + + var battleData = move.User.BattleData; + if (battleData == null) + return; + + var turns = battleData.Battle.Random.GetBool() ? 2 : 3; + move.User.Volatile.Add(new OutrageEffect(move.User, turns, move.MoveChoice.TargetSide, + move.MoveChoice.TargetPosition)); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/PainSplit.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/PainSplit.cs new file mode 100644 index 0000000..5a94133 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/PainSplit.cs @@ -0,0 +1,30 @@ +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "pain_split")] +public class PainSplit : Script +{ + /// + public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) + { + var userHp = move.User.CurrentHealth; + var targetHp = target.CurrentHealth; + var averageHp = (userHp + targetHp) / 2; + var evtBatch = new EventBatchId(); + if (userHp > averageHp) + { + move.User.Damage(userHp - averageHp, DamageSource.Misc, evtBatch, true); + } + else if (userHp < averageHp) + { + move.User.Heal(averageHp - userHp, false, evtBatch, true); + } + if (targetHp > averageHp) + { + target.Damage(targetHp - averageHp, DamageSource.Misc, evtBatch, true); + } + else if (targetHp < averageHp) + { + target.Heal(averageHp - targetHp, false, evtBatch, true); + } + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ParabolicCharge.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ParabolicCharge.cs new file mode 100644 index 0000000..bc9d4d4 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/ParabolicCharge.cs @@ -0,0 +1,22 @@ +using System.Linq; + +namespace PkmnLib.Plugin.Gen7.Scripts.Moves; + +[Script(ScriptCategory.Move, "parabolic_charge")] +public class ParabolicCharge : Script +{ + /// + public override void OnAfterMove(IExecutingMove move) + { + if (move.User.IsFainted) + return; + var totalDamage = move.Hits.Sum(x => x.Damage); + var halfDamage = totalDamage / 2; + if (halfDamage == 0) + return; + if (halfDamage > uint.MaxValue) + halfDamage = uint.MaxValue; + + move.User.Heal((uint)halfDamage); + } +} \ No newline at end of file diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/OutrageEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/OutrageEffect.cs new file mode 100644 index 0000000..7c31a44 --- /dev/null +++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/OutrageEffect.cs @@ -0,0 +1,37 @@ +using PkmnLib.Plugin.Gen7.Scripts.Utils; + +namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon; + +[Script(ScriptCategory.Pokemon, "outrage")] +public class OutrageEffect : Script +{ + private readonly IPokemon _owner; + private int _turns; + private readonly byte _targetSide; + private readonly byte _targetPosition; + + public OutrageEffect(IPokemon owner, int turns, byte targetSide, byte targetPosition) + { + _owner = owner; + _turns = turns; + _targetSide = targetSide; + _targetPosition = targetPosition; + } + + /// + public override void ForceTurnSelection(byte sideIndex, byte position, ref ITurnChoice? choice) + { + choice = TurnChoiceHelper.CreateMoveChoice(_owner, "outrage", _targetSide, _targetPosition); + } + + /// + public override void OnAfterHits(IExecutingMove move, IPokemon target) + { + _turns--; + if (_turns <= 0) + { + RemoveSelf(); + _owner.Volatile.Add(new Confusion()); + } + } +} \ No newline at end of file