diff --git a/PkmnLib.Dynamic/Models/BattleChoiceQueue.cs b/PkmnLib.Dynamic/Models/BattleChoiceQueue.cs
index 5813643..7ea9b9f 100644
--- a/PkmnLib.Dynamic/Models/BattleChoiceQueue.cs
+++ b/PkmnLib.Dynamic/Models/BattleChoiceQueue.cs
@@ -99,8 +99,39 @@ public class BattleChoiceQueue : IDeepCloneable
return true;
}
+ ///
+ /// This moves the choice of a specific Pokémon to the end of the queue, making it the last choice to be executed.
+ ///
+ ///
+ /// Returns true if the Pokémon was found and moved, false otherwise.
+ ///
+ public bool MovePokemonChoiceLast(IPokemon pokemon)
+ {
+ var index = Array.FindIndex(_choices, _currentIndex, choice => choice?.User == pokemon);
+ if (index == -1)
+ return false;
+ var choice = _choices[index];
+ _choices[index] = null;
+ // Put all choices after the index of the choice forward
+ for (var i = index; i < _choices.Length - 1; i++)
+ _choices[i] = _choices[i + 1];
+ // And insert the choice at the end
+ _choices[^1] = choice;
+ return true;
+ }
+
internal IReadOnlyList GetChoices() => _choices;
public ITurnChoice? FirstOrDefault(Func predicate) =>
- _choices.WhereNotNull().FirstOrDefault(predicate);
+ _choices.Skip(_currentIndex).WhereNotNull().FirstOrDefault(predicate);
+
+ public void Remove(ITurnChoice choice)
+ {
+ var index = Array.FindIndex(_choices, _currentIndex, x => x == choice);
+ if (index == -1)
+ return;
+ _choices[index] = null;
+ for (var i = index; i > _currentIndex; i--)
+ _choices[i] = _choices[i - 1];
+ }
}
\ No newline at end of file
diff --git a/PkmnLib.Dynamic/Models/BattleFlow/MoveTurnExecutor.cs b/PkmnLib.Dynamic/Models/BattleFlow/MoveTurnExecutor.cs
index a41c91a..1cea3ca 100644
--- a/PkmnLib.Dynamic/Models/BattleFlow/MoveTurnExecutor.cs
+++ b/PkmnLib.Dynamic/Models/BattleFlow/MoveTurnExecutor.cs
@@ -45,6 +45,8 @@ internal static class MoveTurnExecutor
var targets =
TargetResolver.ResolveTargets(battle, moveChoice.TargetSide, moveChoice.TargetPosition, targetType);
moveChoice.RunScriptHook(x => x.ChangeTargets(moveChoice, ref targets));
+ var targetSide = battle.Sides[moveChoice.TargetSide];
+ targetSide.RunScriptHook(x => x.ChangeIncomingTargets(moveChoice, ref targets));
byte numberOfHits = 1;
moveChoice.RunScriptHook(x => x.ChangeNumberOfHits(moveChoice, ref numberOfHits));
diff --git a/PkmnLib.Dynamic/ScriptHandling/Script.cs b/PkmnLib.Dynamic/ScriptHandling/Script.cs
index 700b095..95816a1 100644
--- a/PkmnLib.Dynamic/ScriptHandling/Script.cs
+++ b/PkmnLib.Dynamic/ScriptHandling/Script.cs
@@ -140,6 +140,10 @@ public abstract class Script : IDeepCloneable
{
}
+ public virtual void ChangeIncomingTargets(IMoveChoice moveChoice, ref IReadOnlyList targets)
+ {
+ }
+
///
/// This function allows you to change a move into a multi-hit move. The number of hits set here
/// gets used as the number of hits. If set to 0, this will behave as if the move missed on its
diff --git a/PkmnLib.Tests/Data/Moves.jsonc b/PkmnLib.Tests/Data/Moves.jsonc
index 4e9347c..7a8d07b 100755
--- a/PkmnLib.Tests/Data/Moves.jsonc
+++ b/PkmnLib.Tests/Data/Moves.jsonc
@@ -8533,7 +8533,10 @@
"contact",
"protect",
"mirror"
- ]
+ ],
+ "effect": {
+ "name": "pursuit"
+ }
},
{
"name": "quash",
@@ -8547,7 +8550,10 @@
"flags": [
"protect",
"mirror"
- ]
+ ],
+ "effect": {
+ "name": "quash"
+ }
},
{
"name": "quick_attack",
@@ -8563,6 +8569,7 @@
"protect",
"mirror"
]
+ // No secondary effect
},
{
"name": "quick_guard",
@@ -8575,7 +8582,10 @@
"category": "status",
"flags": [
"snatch"
- ]
+ ],
+ "effect": {
+ "name": "quick_guard"
+ }
},
{
"name": "quiver_dance",
@@ -8589,7 +8599,15 @@
"flags": [
"snatch",
"dance"
- ]
+ ],
+ "effect": {
+ "name": "change_multiple_user_stat_boosts",
+ "parameters": {
+ "specialAttack": 1,
+ "specialDefense": 1,
+ "speed": 1
+ }
+ }
},
{
"name": "rage",
@@ -8604,7 +8622,10 @@
"contact",
"protect",
"mirror"
- ]
+ ],
+ "effect": {
+ "name": "rage"
+ }
},
{
"name": "rage_powder",
@@ -8617,7 +8638,10 @@
"category": "status",
"flags": [
"powder"
- ]
+ ],
+ "effect": {
+ "name": "rage_powder"
+ }
},
{
"name": "rain_dance",
@@ -8628,7 +8652,10 @@
"priority": 0,
"target": "All",
"category": "status",
- "flags": []
+ "flags": [],
+ "effect": {
+ "name": "rain_dance"
+ }
},
{
"name": "rapid_spin",
@@ -8643,7 +8670,10 @@
"contact",
"protect",
"mirror"
- ]
+ ],
+ "effect": {
+ "name": "rapid_spin"
+ }
},
{
"name": "razor_leaf",
@@ -8657,7 +8687,10 @@
"flags": [
"protect",
"mirror"
- ]
+ ],
+ "effect": {
+ "name": "increased_critical_stage"
+ }
},
{
"name": "razor_shell",
@@ -8672,7 +8705,14 @@
"contact",
"protect",
"mirror"
- ]
+ ],
+ "effect": {
+ "name": "change_target_defense",
+ "chance": 50,
+ "parameters": {
+ "amount": -1
+ }
+ }
},
{
"name": "razor_wind",
@@ -8687,7 +8727,10 @@
"charge",
"protect",
"mirror"
- ]
+ ],
+ "effect": {
+ "name": "razor_wind"
+ }
},
{
"name": "recover",
@@ -8701,7 +8744,13 @@
"flags": [
"snatch",
"heal"
- ]
+ ],
+ "effect": {
+ "name": "heal_percent",
+ "parameters": {
+ "healPercent": 0.5
+ }
+ }
},
{
"name": "recycle",
diff --git a/PkmnLib.Tests/Dynamic/ChoiceQueueTests.cs b/PkmnLib.Tests/Dynamic/ChoiceQueueTests.cs
new file mode 100644
index 0000000..79bab6f
--- /dev/null
+++ b/PkmnLib.Tests/Dynamic/ChoiceQueueTests.cs
@@ -0,0 +1,107 @@
+using Moq;
+using PkmnLib.Dynamic.Models;
+using PkmnLib.Dynamic.Models.Choices;
+
+namespace PkmnLib.Tests.Dynamic;
+
+public class ChoiceQueueTests
+{
+ [Test]
+ public async Task ChoiceQueue_MovePokemonChoiceNext()
+ {
+ var pokemon1 = new Mock();
+ var pokemon2 = new Mock();
+ var pokemon3 = new Mock();
+ var pokemon4 = new Mock();
+
+ var choice1 = new Mock();
+ choice1.Setup(c => c.User).Returns(pokemon1.Object);
+ var choice2 = new Mock();
+ choice2.Setup(c => c.User).Returns(pokemon2.Object);
+ var choice3 = new Mock();
+ choice3.Setup(c => c.User).Returns(pokemon3.Object);
+ var choice4 = new Mock();
+ choice4.Setup(c => c.User).Returns(pokemon4.Object);
+
+ var queue = new BattleChoiceQueue([choice1.Object, choice2.Object, choice3.Object, choice4.Object]);
+ var result = queue.MovePokemonChoiceNext(pokemon3.Object);
+ await Assert.That(result).IsTrue();
+ await Assert.That(queue.Dequeue()).IsEqualTo(choice3.Object);
+ }
+
+ [Test]
+ public async Task ChoiceQueue_MovePokemonChoiceNextFailsIfAlreadyExecuted()
+ {
+ var pokemon1 = new Mock();
+ var pokemon2 = new Mock();
+ var pokemon3 = new Mock();
+ var pokemon4 = new Mock();
+
+ var choice1 = new Mock();
+ choice1.Setup(c => c.User).Returns(pokemon1.Object);
+ var choice2 = new Mock();
+ choice2.Setup(c => c.User).Returns(pokemon2.Object);
+ var choice3 = new Mock();
+ choice3.Setup(c => c.User).Returns(pokemon3.Object);
+ var choice4 = new Mock();
+ choice4.Setup(c => c.User).Returns(pokemon4.Object);
+
+ var queue = new BattleChoiceQueue([choice1.Object, choice2.Object, choice3.Object, choice4.Object]);
+ queue.Dequeue();
+ var result = queue.MovePokemonChoiceNext(pokemon1.Object);
+ await Assert.That(result).IsFalse();
+ await Assert.That(queue.Dequeue()).IsEqualTo(choice2.Object);
+ }
+
+ [Test]
+ public async Task ChoiceQueue_MovePokemonChoiceLast()
+ {
+ var pokemon1 = new Mock();
+ var pokemon2 = new Mock();
+ var pokemon3 = new Mock();
+ var pokemon4 = new Mock();
+
+ var choice1 = new Mock();
+ choice1.Setup(c => c.User).Returns(pokemon1.Object);
+ var choice2 = new Mock();
+ choice2.Setup(c => c.User).Returns(pokemon2.Object);
+ var choice3 = new Mock();
+ choice3.Setup(c => c.User).Returns(pokemon3.Object);
+ var choice4 = new Mock();
+ choice4.Setup(c => c.User).Returns(pokemon4.Object);
+
+ var queue = new BattleChoiceQueue([choice1.Object, choice2.Object, choice3.Object, choice4.Object]);
+ var result = queue.MovePokemonChoiceLast(pokemon2.Object);
+ await Assert.That(result).IsTrue();
+ await Assert.That(queue.Dequeue()).IsEqualTo(choice1.Object);
+ await Assert.That(queue.Dequeue()).IsEqualTo(choice3.Object);
+ await Assert.That(queue.Dequeue()).IsEqualTo(choice4.Object);
+ await Assert.That(queue.Dequeue()).IsEqualTo(choice2.Object);
+ }
+
+ [Test]
+ public async Task ChoiceQueue_MovePokemonChoiceLastFailsIfAlreadyExecuted()
+ {
+ var pokemon1 = new Mock();
+ var pokemon2 = new Mock();
+ var pokemon3 = new Mock();
+ var pokemon4 = new Mock();
+
+ var choice1 = new Mock();
+ choice1.Setup(c => c.User).Returns(pokemon1.Object);
+ var choice2 = new Mock();
+ choice2.Setup(c => c.User).Returns(pokemon2.Object);
+ var choice3 = new Mock();
+ choice3.Setup(c => c.User).Returns(pokemon3.Object);
+ var choice4 = new Mock();
+ choice4.Setup(c => c.User).Returns(pokemon4.Object);
+
+ var queue = new BattleChoiceQueue([choice1.Object, choice2.Object, choice3.Object, choice4.Object]);
+ queue.Dequeue();
+ var result = queue.MovePokemonChoiceLast(pokemon1.Object);
+ await Assert.That(result).IsFalse();
+ await Assert.That(queue.Dequeue()).IsEqualTo(choice2.Object);
+ await Assert.That(queue.Dequeue()).IsEqualTo(choice3.Object);
+ await Assert.That(queue.Dequeue()).IsEqualTo(choice4.Object);
+ }
+}
\ No newline at end of file
diff --git a/PkmnLib.Tests/PkmnLib.Tests.csproj b/PkmnLib.Tests/PkmnLib.Tests.csproj
index 77e7a21..c9ed51a 100644
--- a/PkmnLib.Tests/PkmnLib.Tests.csproj
+++ b/PkmnLib.Tests/PkmnLib.Tests.csproj
@@ -10,18 +10,19 @@
-
+
+
-
-
+
+
-
+
@@ -42,7 +43,7 @@
-
-
+
+
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Pursuit.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Pursuit.cs
new file mode 100644
index 0000000..6f0cf1d
--- /dev/null
+++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Pursuit.cs
@@ -0,0 +1,21 @@
+using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
+
+namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
+
+[Script(ScriptCategory.Move, "pursuit")]
+public class Pursuit : Script
+{
+ ///
+ public override void OnBeforeTurnStart(ITurnChoice choice)
+ {
+ if (choice is IMoveChoice moveChoice)
+ choice.User.Volatile.Add(new PursuitEffect(moveChoice));
+ }
+
+ ///
+ public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit) =>
+ move.User.Volatile.Remove();
+
+ ///
+ public override void OnAfterMove(IExecutingMove move) => move.User.Volatile.Remove();
+}
\ No newline at end of file
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Quash.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Quash.cs
new file mode 100644
index 0000000..92d581e
--- /dev/null
+++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Quash.cs
@@ -0,0 +1,18 @@
+namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
+
+[Script(ScriptCategory.Move, "quash")]
+public class Quash : Script
+{
+ ///
+ public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
+ {
+ var battleData = move.User.BattleData;
+ if (battleData == null)
+ return;
+
+ if (battleData.Battle.ChoiceQueue?.MovePokemonChoiceLast(target) == false)
+ {
+ move.GetHitData(target, hit).Fail();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/QuickGuard.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/QuickGuard.cs
new file mode 100644
index 0000000..5e7ae31
--- /dev/null
+++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/QuickGuard.cs
@@ -0,0 +1,13 @@
+using PkmnLib.Plugin.Gen7.Scripts.Side;
+
+namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
+
+[Script(ScriptCategory.Move, "quick_guard")]
+public class QuickGuard : Script
+{
+ ///
+ public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
+ {
+ move.User.BattleData?.BattleSide.VolatileScripts.Add(new QuickGuardEffect());
+ }
+}
\ No newline at end of file
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Rage.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Rage.cs
new file mode 100644
index 0000000..87c070a
--- /dev/null
+++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/Rage.cs
@@ -0,0 +1,13 @@
+using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
+
+namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
+
+[Script(ScriptCategory.Move, "rage")]
+public class Rage : Script
+{
+ ///
+ public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
+ {
+ move.User.Volatile.Add(new RageEffect());
+ }
+}
\ No newline at end of file
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/RagePowder.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/RagePowder.cs
new file mode 100644
index 0000000..6dc1ae4
--- /dev/null
+++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/RagePowder.cs
@@ -0,0 +1,18 @@
+using PkmnLib.Plugin.Gen7.Scripts.Side;
+
+namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
+
+[Script(ScriptCategory.Move, "rage_powder")]
+public class RagePowder : Script
+{
+ ///
+ public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
+ {
+ var battleData = move.User.BattleData;
+ if (battleData == null)
+ return;
+
+ var effect = battleData.BattleSide.VolatileScripts.Add(new RagePowderEffect(move.User));
+ ((RagePowderEffect)effect.Script!).User = move.User;
+ }
+}
\ No newline at end of file
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/RainDance.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/RainDance.cs
new file mode 100644
index 0000000..3ee2fa5
--- /dev/null
+++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/RainDance.cs
@@ -0,0 +1,16 @@
+using PkmnLib.Plugin.Gen7.Scripts.Weather;
+
+namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
+
+[Script(ScriptCategory.Move, "rain_dance")]
+public class RainDance : Script
+{
+ ///
+ public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
+ {
+ var battleData = move.User.BattleData;
+ if (battleData == null)
+ return;
+ battleData.Battle.SetWeather(ScriptUtils.ResolveName(), 5);
+ }
+}
\ No newline at end of file
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/RapidSpin.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/RapidSpin.cs
new file mode 100644
index 0000000..d3502eb
--- /dev/null
+++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/RapidSpin.cs
@@ -0,0 +1,23 @@
+using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
+
+namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
+
+[Script(ScriptCategory.Move, "rapid_spin")]
+public class RapidSpin : Script
+{
+ ///
+ public override void OnSecondaryEffect(IExecutingMove move, IPokemon target, byte hit)
+ {
+ move.User.Volatile.Remove();
+ move.User.Volatile.Remove();
+ move.User.Volatile.Remove();
+ move.User.Volatile.Remove();
+ // TODO: Sand Tomb effect removal
+ // TODO: Whirlpool effect removal
+ // TODO: Wrap effect removal
+
+ // TODO: Remove Spikes
+ // TODO: Remove Toxic Spikes
+ // TODO: Remove Stealth Rock
+ }
+}
\ No newline at end of file
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/RazorWind.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/RazorWind.cs
new file mode 100644
index 0000000..cd55db5
--- /dev/null
+++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Moves/RazorWind.cs
@@ -0,0 +1,24 @@
+using PkmnLib.Plugin.Gen7.Scripts.Pokemon;
+
+namespace PkmnLib.Plugin.Gen7.Scripts.Moves;
+
+[Script(ScriptCategory.Move, "razor_wind")]
+public class RazorWind : Script
+{
+ ///
+ public override void PreventMove(IExecutingMove move, ref bool prevent)
+ {
+ var chargeMoveEffect = move.User.Volatile.Get();
+ if (chargeMoveEffect != null && chargeMoveEffect.MoveName == move.UseMove.Name)
+ return;
+ prevent = true;
+ move.User.Volatile.Add(new ChargeMoveEffect(move.UseMove.Name, move.User, move.MoveChoice.TargetSide,
+ move.MoveChoice.TargetPosition));
+ }
+
+ ///
+ public override void ChangeCriticalStage(IExecutingMove move, IPokemon target, byte hit, ref byte stage)
+ {
+ stage += 1;
+ }
+}
\ No newline at end of file
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/ChargeMoveEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/ChargeMoveEffect.cs
new file mode 100644
index 0000000..89a3d1c
--- /dev/null
+++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/ChargeMoveEffect.cs
@@ -0,0 +1,27 @@
+using PkmnLib.Plugin.Gen7.Scripts.Utils;
+using PkmnLib.Static.Utils;
+
+namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
+
+[Script(ScriptCategory.Pokemon, "charge_move_effect")]
+public class ChargeMoveEffect : Script
+{
+ public readonly StringKey MoveName;
+ private readonly IPokemon _user;
+ private readonly byte _targetSide;
+ private readonly byte _targetPosition;
+
+ public ChargeMoveEffect(StringKey moveName, IPokemon user, byte targetSide, byte targetPosition)
+ {
+ MoveName = moveName;
+ _user = user;
+ _targetSide = targetSide;
+ _targetPosition = targetPosition;
+ }
+
+ ///
+ public override void ForceTurnSelection(byte sideIndex, byte position, ref ITurnChoice? choice)
+ {
+ choice = TurnChoiceHelper.CreateMoveChoice(_user, MoveName, _targetSide, _targetPosition);
+ }
+}
\ No newline at end of file
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/PursuitEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/PursuitEffect.cs
new file mode 100644
index 0000000..bcb6699
--- /dev/null
+++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/PursuitEffect.cs
@@ -0,0 +1,52 @@
+using PkmnLib.Dynamic.Models.BattleFlow;
+using PkmnLib.Static.Utils;
+
+namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
+
+[Script(ScriptCategory.Pokemon, "pursuit")]
+public class PursuitEffect : Script
+{
+ private readonly IMoveChoice _choice;
+
+ public PursuitEffect(IMoveChoice choice)
+ {
+ _choice = choice;
+ }
+
+ ///
+ public override void OnSwitchOut(IPokemon oldPokemon, byte position)
+ {
+ var battleData = oldPokemon.BattleData;
+ if (battleData == null)
+ return;
+ if (battleData.Battle.HasEnded)
+ return;
+
+ if (battleData.Position != _choice.TargetPosition || battleData.SideIndex != _choice.TargetSide)
+ return;
+ if (!_choice.User.IsUsable)
+ return;
+ if (_choice.User.BattleData?.IsOnBattlefield != true)
+ return;
+
+ var choiceQueue = battleData.Battle.ChoiceQueue;
+
+ var choice = choiceQueue?.FirstOrDefault(x => x == _choice);
+ if (choice == null)
+ return;
+ choiceQueue!.Remove(choice);
+ _choice.Volatile.Add(new PursuitDoublePowerEffect());
+ RemoveSelf();
+ TurnRunner.ExecuteChoice(battleData.Battle, _choice);
+ }
+
+ [Script(ScriptCategory.Pokemon, "pursuit_double_power")]
+ private class PursuitDoublePowerEffect : Script
+ {
+ ///
+ public override void ChangeBasePower(IExecutingMove move, IPokemon target, byte hit, ref byte basePower)
+ {
+ basePower = basePower.MultiplyOrMax(2);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/RageEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/RageEffect.cs
new file mode 100644
index 0000000..a57f565
--- /dev/null
+++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Pokemon/RageEffect.cs
@@ -0,0 +1,19 @@
+using PkmnLib.Static;
+
+namespace PkmnLib.Plugin.Gen7.Scripts.Pokemon;
+
+[Script(ScriptCategory.Pokemon, "rage")]
+public class RageEffect : Script
+{
+ ///
+ public override void OnIncomingHit(IExecutingMove move, IPokemon target, byte hit)
+ {
+ move.User.ChangeStatBoost(Statistic.Attack, 1, true);
+ }
+
+ ///
+ public override void OnEndTurn(IBattle battle)
+ {
+ RemoveSelf();
+ }
+}
\ No newline at end of file
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Side/QuickGuardEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Side/QuickGuardEffect.cs
new file mode 100644
index 0000000..42e2892
--- /dev/null
+++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Side/QuickGuardEffect.cs
@@ -0,0 +1,18 @@
+namespace PkmnLib.Plugin.Gen7.Scripts.Side;
+
+public class QuickGuardEffect : Script
+{
+ ///
+ ///
+ public override void IsInvulnerableToMove(IExecutingMove move, IPokemon target, ref bool invulnerable)
+ {
+ if (move.UseMove.Priority > 0)
+ invulnerable = true;
+ }
+
+ ///
+ public override void OnEndTurn(IBattle battle)
+ {
+ RemoveSelf();
+ }
+}
\ No newline at end of file
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Scripts/Side/RagePowderEffect.cs b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Side/RagePowderEffect.cs
new file mode 100644
index 0000000..dcd7238
--- /dev/null
+++ b/Plugins/PkmnLib.Plugin.Gen7/Scripts/Side/RagePowderEffect.cs
@@ -0,0 +1,30 @@
+using System.Collections.Generic;
+
+namespace PkmnLib.Plugin.Gen7.Scripts.Side;
+
+[Script(ScriptCategory.Side, "rage_powder")]
+public class RagePowderEffect : Script
+{
+ public IPokemon User { get; set; }
+
+ public RagePowderEffect(IPokemon user)
+ {
+ User = user;
+ }
+
+ ///
+ public override void ChangeIncomingTargets(IMoveChoice moveChoice, ref IReadOnlyList targets)
+ {
+ // Ignore multi-hit moves
+ if (targets.Count != 1)
+ return;
+
+ targets = [User];
+ }
+
+ ///
+ public override void OnEndTurn(IBattle battle)
+ {
+ RemoveSelf();
+ }
+}
\ No newline at end of file