PkmnLib.NET/PkmnLib.Dynamic/Models/BattleChoiceQueue.cs

106 lines
3.7 KiB
C#

using PkmnLib.Dynamic.Models.Choices;
using PkmnLib.Dynamic.ScriptHandling;
using PkmnLib.Static.Utils;
namespace PkmnLib.Dynamic.Models;
/// <summary>
/// The ChoiceQueue is used to run choices one by one.
/// </summary>
/// <remarks>
/// It functions internally by holding a list of choices, and one by one setting items that have been returned to null,
/// It holds several helper functions to change the turn order while doing the execution. This is needed, as several
/// moves in Pokémon actively mess with this order.
/// </remarks>
public class BattleChoiceQueue : IDeepCloneable
{
private readonly ITurnChoice?[] _choices;
private int _currentIndex;
public ITurnChoice? LastRanChoice { get; private set; }
/// <inheritdoc cref="BattleChoiceQueue"/>
public BattleChoiceQueue(ITurnChoice[] choices)
{
_choices = choices;
}
/// <summary>
/// Dequeues the next turn choice to be executed. This gives back the choice and sets it to null in the queue. It
/// also increments the internal index.
/// </summary>
/// <returns></returns>
public ITurnChoice? Dequeue()
{
if (_currentIndex >= _choices.Length)
return null;
var choice = _choices[_currentIndex];
_choices[_currentIndex] = null;
_currentIndex++;
LastRanChoice = choice;
return choice;
}
/// <summary>
/// This reads what the next choice to execute will be, without modifying state.
/// </summary>
public ITurnChoice? Peek() => _currentIndex >= _choices.Length ? null : _choices[_currentIndex];
/// <summary>
/// Checks if there are any more choices to execute.
/// </summary>
public bool HasNext() => _currentIndex < _choices.Length;
/// <summary>
/// This resorts the yet to be executed choices. This can be useful for dealing with situations
/// such as Pokémon changing forms just after the very start of a turn, when turn order has
/// technically already been decided.
/// </summary>
public void Resort()
{
var length = _choices.Length;
var currentIndex = _currentIndex;
for (var i = currentIndex; i < length; i++)
{
var choice = _choices[i];
if (choice == null)
continue;
// Ensure that the speed is up to date
var speed = choice.User.BoostedStats.Speed;
choice.User.RunScriptHook(script => script.ChangeSpeed(choice, ref speed));
choice.Speed = speed;
}
// We only sort the choices that are left
Array.Sort(_choices, currentIndex, length - currentIndex, TurnChoiceComparer.Instance!);
}
/// <summary>
/// This moves the choice of a specific Pokémon up to the next choice to be executed.
/// </summary>
/// <returns>
/// Returns true if the Pokémon was found and moved, false otherwise.
/// </returns>
public bool MovePokemonChoiceNext(IPokemon pokemon)
{
var index = Array.FindIndex(_choices, _currentIndex, choice => choice?.User == pokemon);
if (index == -1)
return false;
if (index == _currentIndex)
return true;
var choice = _choices[index];
_choices[index] = null;
// Push all choices back
for (var i = index; i > _currentIndex; i--)
_choices[i] = _choices[i - 1];
// And insert the choice at the front
_choices[_currentIndex] = choice;
return true;
}
internal IReadOnlyList<ITurnChoice?> GetChoices() => _choices;
public ITurnChoice? FirstOrDefault(Func<ITurnChoice, bool> predicate) => _choices.WhereNotNull().FirstOrDefault(predicate);
}