PkmnLib.NET/PkmnLib.Dynamic/Models/BattleFlow/TurnRunner.cs

192 lines
6.7 KiB
C#
Raw Normal View History

using PkmnLib.Dynamic.Models.Choices;
using PkmnLib.Dynamic.ScriptHandling;
using PkmnLib.Static.Utils;
namespace PkmnLib.Dynamic.Models.BattleFlow;
2024-08-10 09:18:10 +00:00
/// <summary>
/// Helper class for handling the running of a turn in a battle.
/// </summary>
public static class TurnRunner
{
2024-08-10 09:18:10 +00:00
/// <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());
}
scripts.Clear();
side.GetOwnScripts(scripts);
scripts.RunScriptHook(x => x.OnEndTurn());
}
scripts.Clear();
battle.GetOwnScripts(scripts);
scripts.RunScriptHook(x => x.OnEndTurn());
}
}
private 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;
2025-01-10 10:11:50 +00:00
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();
}
2025-01-10 10:11:50 +00:00
private static void ExecuteItemChoice(IBattle battle, IItemChoice itemChoice)
{
var user = itemChoice.User;
var battleData = user.BattleData;
if (battleData == null)
return;
if (!battle.Library.ScriptResolver.TryResolveBattleItemScript(itemChoice.Item, out var itemScript))
{
return;
}
if (!itemScript.IsItemUsable)
return;
itemScript.OnInitialize(itemChoice.Item.BattleEffect!.Parameters);
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];
}
var requiresTarget = itemScript.RequiresTarget;
if (requiresTarget)
{
target ??= user;
itemScript.OnUseWithTarget(target);
}
else
{
itemScript.OnUse();
}
}
}