This commit is contained in:
179
PkmnLib.Dynamic/BattleFlow/TurnRunner.cs
Normal file
179
PkmnLib.Dynamic/BattleFlow/TurnRunner.cs
Normal file
@@ -0,0 +1,179 @@
|
||||
using PkmnLib.Dynamic.Models;
|
||||
using PkmnLib.Dynamic.Models.Choices;
|
||||
using PkmnLib.Dynamic.ScriptHandling;
|
||||
using PkmnLib.Static.Utils;
|
||||
|
||||
namespace PkmnLib.Dynamic.BattleFlow;
|
||||
|
||||
/// <summary>
|
||||
/// Helper class for handling the running of a turn in a battle.
|
||||
/// </summary>
|
||||
public static class TurnRunner
|
||||
{
|
||||
/// <summary>
|
||||
/// Runs a single turn in a battle to completion.
|
||||
/// </summary>
|
||||
public static void RunTurn(this IBattle battle)
|
||||
{
|
||||
var queue = battle.ChoiceQueue;
|
||||
if (queue == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(battle.ChoiceQueue),
|
||||
"The battle's choice queue must be set before running a turn.");
|
||||
}
|
||||
|
||||
// We are now at the very beginning of a turn. We have assigned speeds and priorities to all
|
||||
// choices, and put them in the correct order.
|
||||
|
||||
// The first thing to do is to run the on_before_turn script hook on every choice. This script hook
|
||||
// is primarily intended to be used to reset variables on a script (for example scripts that need
|
||||
// to check whether a Pokémon was hit this turn. By resetting here, and setting a variable to true
|
||||
// they can then know this later on.)
|
||||
foreach (var choice in queue.GetChoices().WhereNotNull())
|
||||
{
|
||||
choice.RunScriptHook(script => script.OnBeforeTurnStart(choice));
|
||||
}
|
||||
|
||||
// Now we can properly begin executing choices.
|
||||
// One by one dequeue the turns, and run them. If the battle has ended we do not want to
|
||||
// continue running.
|
||||
while (queue.HasNext() && !battle.HasEnded)
|
||||
{
|
||||
var next = queue.Dequeue();
|
||||
if (next == null)
|
||||
continue;
|
||||
ExecuteChoice(battle, next);
|
||||
}
|
||||
|
||||
// If the battle is not ended, we have arrived at the normal end of a turn. and thus want
|
||||
// to run the end turn scripts.
|
||||
|
||||
// As we want all scripts to run exactly once, including those on the sides and battles,
|
||||
// we can't just use the default script hook on each Pokémon. Instead, manually call
|
||||
// the script functions on every script.
|
||||
if (!battle.HasEnded)
|
||||
{
|
||||
var scripts = new List<IEnumerable<ScriptContainer>>(10);
|
||||
foreach (var side in battle.Sides)
|
||||
{
|
||||
foreach (var pokemon in side.Pokemon.WhereNotNull())
|
||||
{
|
||||
scripts.Clear();
|
||||
pokemon.GetOwnScripts(scripts);
|
||||
scripts.RunScriptHook(x => x.OnEndTurn(battle));
|
||||
}
|
||||
scripts.Clear();
|
||||
side.GetOwnScripts(scripts);
|
||||
scripts.RunScriptHook(x => x.OnEndTurn(battle));
|
||||
}
|
||||
scripts.Clear();
|
||||
battle.GetOwnScripts(scripts);
|
||||
scripts.RunScriptHook(x => x.OnEndTurn(battle));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs a single choice in a battle.
|
||||
/// </summary>
|
||||
public static void ExecuteChoice(IBattle battle, ITurnChoice choice)
|
||||
{
|
||||
if (choice is IPassChoice)
|
||||
return;
|
||||
if (battle.HasEnded)
|
||||
return;
|
||||
if (!choice.User.IsUsable)
|
||||
return;
|
||||
if (choice.User.BattleData?.IsOnBattlefield != true)
|
||||
return;
|
||||
switch (choice)
|
||||
{
|
||||
case IMoveChoice moveChoice:
|
||||
MoveTurnExecutor.ExecuteMoveChoice(battle, moveChoice);
|
||||
break;
|
||||
case ISwitchChoice switchChoice:
|
||||
ExecuteSwitchChoice(battle, switchChoice);
|
||||
break;
|
||||
case IFleeChoice fleeChoice:
|
||||
ExecuteFleeChoice(battle, fleeChoice);
|
||||
break;
|
||||
case IItemChoice itemChoice:
|
||||
ExecuteItemChoice(battle, itemChoice);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
private static void ExecuteItemChoice(IBattle battle, IItemChoice itemChoice)
|
||||
{
|
||||
var user = itemChoice.User;
|
||||
var battleData = user.BattleData;
|
||||
if (battleData == null)
|
||||
return;
|
||||
IPokemon? target = null;
|
||||
if (itemChoice is { TargetSide: not null, TargetPosition: not null })
|
||||
{
|
||||
var side = battle.Sides[itemChoice.TargetSide.Value];
|
||||
target = side.Pokemon[itemChoice.TargetPosition.Value];
|
||||
}
|
||||
itemChoice.Item.RunItemScript(battle.Library.ScriptResolver, target ?? user);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user