diff --git a/PkmnLib.Dynamic/Libraries/MiscLibrary.cs b/PkmnLib.Dynamic/Libraries/MiscLibrary.cs
index 8971efe..c08f971 100644
--- a/PkmnLib.Dynamic/Libraries/MiscLibrary.cs
+++ b/PkmnLib.Dynamic/Libraries/MiscLibrary.cs
@@ -19,4 +19,6 @@ public interface IMiscLibrary
/// Gets the current time of day for the battle.
///
TimeOfDay GetTimeOfDay();
+
+ bool CanFlee(IBattle battle, IFleeChoice fleeChoice);
}
\ No newline at end of file
diff --git a/PkmnLib.Dynamic/Models/BattleFlow/TurnRunner.cs b/PkmnLib.Dynamic/Models/BattleFlow/TurnRunner.cs
index ca8cf58..b0d6290 100644
--- a/PkmnLib.Dynamic/Models/BattleFlow/TurnRunner.cs
+++ b/PkmnLib.Dynamic/Models/BattleFlow/TurnRunner.cs
@@ -84,7 +84,75 @@ public static class TurnRunner
case IMoveChoice moveChoice:
MoveTurnExecutor.ExecuteMoveChoice(battle, moveChoice);
break;
- // TODO: Implement other choice types
+ case ISwitchChoice switchChoice:
+ ExecuteSwitchChoice(battle, switchChoice);
+ break;
+ case IFleeChoice fleeChoice:
+ ExecuteFleeChoice(battle, fleeChoice);
+ break;
+ // TODO: Implement item choice types
}
}
+
+ private static void ExecuteSwitchChoice(IBattle battle, ISwitchChoice fleeChoice)
+ {
+ var user = fleeChoice.User;
+ var battleData = user.BattleData;
+ if (battleData == null)
+ return;
+ var preventSwitch = false;
+ fleeChoice.RunScriptHook(script => script.PreventSelfSwitch(fleeChoice, ref preventSwitch));
+ if (preventSwitch)
+ return;
+ foreach (var side in battle.Sides)
+ {
+ if (side.Index == battleData.SideIndex)
+ continue;
+ foreach (var pokemon in side.Pokemon.WhereNotNull())
+ {
+ pokemon.RunScriptHook(script => script.PreventOpponentSwitch(fleeChoice, ref preventSwitch));
+ if (preventSwitch)
+ return;
+ }
+ }
+ user.Volatile.Clear();
+ var userSide = battle.Sides[battleData.SideIndex];
+ userSide.SwapPokemon(battleData.Position, fleeChoice.SwitchTo);
+ }
+
+
+ private static void ExecuteFleeChoice(IBattle battle, IFleeChoice fleeChoice)
+ {
+ var user = fleeChoice.User;
+ var battleData = user.BattleData;
+ if (battleData == null)
+ return;
+ if (!battle.CanFlee)
+ return;
+
+ var preventFlee = false;
+ fleeChoice.RunScriptHook(script => script.PreventSelfRunAway(fleeChoice, ref preventFlee));
+ if (preventFlee)
+ return;
+
+ foreach (var side in battle.Sides)
+ {
+ if (side.Index == battleData.SideIndex)
+ continue;
+ foreach (var pokemon in side.Pokemon.WhereNotNull())
+ {
+ pokemon.RunScriptHook(script => script.PreventOpponentRunAway(fleeChoice, ref preventFlee));
+ if (preventFlee)
+ return;
+ }
+ }
+
+ if (!battle.Library.MiscLibrary.CanFlee(battle, fleeChoice))
+ return;
+
+ var userSide = battle.Sides[battleData.SideIndex];
+ userSide.MarkAsFled();
+ battle.ValidateBattleState();
+ }
+
}
\ No newline at end of file
diff --git a/PkmnLib.Dynamic/Models/BattleSide.cs b/PkmnLib.Dynamic/Models/BattleSide.cs
index a0da422..dd6cb03 100644
--- a/PkmnLib.Dynamic/Models/BattleSide.cs
+++ b/PkmnLib.Dynamic/Models/BattleSide.cs
@@ -110,6 +110,16 @@ public interface IBattleSide : IScriptSource, IDeepCloneable
/// Checks whether the side has been defeated.
///
bool IsDefeated();
+
+ ///
+ /// The number of times this side has attempted to flee.
+ ///
+ uint FleeAttempts { get; }
+
+ ///
+ /// Registers a flee attempt for this side.
+ ///
+ void RegisterFleeAttempt();
///
/// Mark the side as fled.
@@ -267,6 +277,15 @@ public class BattleSideImpl : ScriptSource, IBattleSide
return _fillablePositions.All(fillable => !fillable);
}
+ ///
+ public uint FleeAttempts { get; private set; }
+
+ ///
+ public void RegisterFleeAttempt()
+ {
+ FleeAttempts++;
+ }
+
///
public void MarkAsFled() => HasFledBattle = true;
diff --git a/PkmnLib.Dynamic/Models/Choices/SwitchChoice.cs b/PkmnLib.Dynamic/Models/Choices/SwitchChoice.cs
index c0e2181..02bfdad 100644
--- a/PkmnLib.Dynamic/Models/Choices/SwitchChoice.cs
+++ b/PkmnLib.Dynamic/Models/Choices/SwitchChoice.cs
@@ -7,7 +7,10 @@ namespace PkmnLib.Dynamic.Models.Choices;
///
public interface ISwitchChoice : ITurnChoice
{
-
+ ///
+ /// The Pokémon to switch to.
+ ///
+ IPokemon SwitchTo { get; }
}
///
@@ -18,6 +21,7 @@ public class SwitchChoice : TurnChoice, ISwitchChoice
SwitchTo = switchTo;
}
+ ///
public IPokemon SwitchTo { get; }
///
diff --git a/Plugins/PkmnLib.Plugin.Gen7/Libraries/Gen7MiscLibrary.cs b/Plugins/PkmnLib.Plugin.Gen7/Libraries/Gen7MiscLibrary.cs
index 072a650..5733d87 100644
--- a/Plugins/PkmnLib.Plugin.Gen7/Libraries/Gen7MiscLibrary.cs
+++ b/Plugins/PkmnLib.Plugin.Gen7/Libraries/Gen7MiscLibrary.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using System.Linq;
using PkmnLib.Dynamic;
using PkmnLib.Dynamic.Libraries;
using PkmnLib.Dynamic.Models;
@@ -35,4 +36,31 @@ public class Gen7MiscLibrary : IMiscLibrary
_ => TimeOfDay.Night,
};
}
+
+ ///
+ public bool CanFlee(IBattle battle, IFleeChoice fleeChoice)
+ {
+ var user = fleeChoice.User;
+ var battleData = user.BattleData;
+ if (battleData == null)
+ return false;
+ var opponentSide = battle.Sides[battleData.SideIndex == 0 ? 1 : 0];
+ var opponent = opponentSide.Pokemon.FirstOrDefault(x => x is not null);
+ if (opponent == null)
+ return true;
+
+ var userSpeed = user.FlatStats.Speed;
+ var opponentSpeed = opponent.FlatStats.Speed;
+ // If the player's active Pokémon's Speed is greater than or equal to the wild Pokémon's Speed, fleeing will
+ // always succeed
+ if (userSpeed >= opponentSpeed)
+ return true;
+
+ var userSide = battle.Sides[battleData.SideIndex];
+
+ userSide.RegisterFleeAttempt();
+ var fleeChance = ((userSpeed * 32) / (opponentSpeed / 4) + (30 * userSide.FleeAttempts)) / 256;
+ var random = battle.Random.GetInt(0, 100);
+ return random < fleeChance;
+ }
}
\ No newline at end of file