Begin work on outlining dynamic side

This commit is contained in:
2024-07-27 16:26:45 +02:00
parent 1b501dee7e
commit a251913ebd
44 changed files with 2150 additions and 19 deletions

View File

@@ -0,0 +1,6 @@
namespace PkmnLib.Dynamic.Models.Choices;
public interface IFleeChoice : ITurnChoice
{
}

View File

@@ -0,0 +1,6 @@
namespace PkmnLib.Dynamic.Models.Choices;
public interface IItemChoice : ITurnChoice
{
}

View File

@@ -0,0 +1,74 @@
using PkmnLib.Dynamic.ScriptHandling;
namespace PkmnLib.Dynamic.Models.Choices;
/// <summary>
/// The choice of a Pokémon to use a move.
/// </summary>
public interface IMoveChoice : ITurnChoice
{
/// <summary>
/// The move that is used.
/// </summary>
ILearnedMove UsedMove { get; }
/// <summary>
/// The side the move is targeted at.
/// </summary>
byte TargetSide { get; }
/// <summary>
/// The position the move is targeted at.
/// </summary>
byte TargetPosition { get; }
/// <summary>
/// The priority of the move.
/// </summary>
sbyte Priority { get; set; }
/// <summary>
/// The underlying script of the move.
/// </summary>
ScriptContainer Script { get; set; }
}
/// <inheritdoc cref="IMoveChoice"/>
public class MoveChoice : TurnChoice, IMoveChoice
{
/// <inheritdoc cref="MoveChoice"/>
public MoveChoice(IPokemon user, ILearnedMove usedMove, byte targetSide, byte targetPosition) : base(user)
{
UsedMove = usedMove;
TargetSide = targetSide;
TargetPosition = targetPosition;
}
/// <inheritdoc />
public ILearnedMove UsedMove { get; }
/// <inheritdoc />
public byte TargetSide { get; }
/// <inheritdoc />
public byte TargetPosition { get; }
/// <inheritdoc />
public sbyte Priority { get; set; }
/// <inheritdoc />
public ScriptContainer Script { get; set; } = new();
/// <inheritdoc />
public override int ScriptCount => 1 + User.ScriptCount;
/// <inheritdoc />
public override void GetOwnScripts(List<IEnumerable<ScriptContainer>> scripts) => scripts.Add(Script);
/// <inheritdoc />
public override void CollectScripts(List<IEnumerable<ScriptContainer>> scripts)
{
GetOwnScripts(scripts);
User.CollectScripts(scripts);
}
}

View File

@@ -0,0 +1,6 @@
namespace PkmnLib.Dynamic.Models.Choices;
public interface IPassChoice : ITurnChoice
{
}

View File

@@ -0,0 +1,6 @@
namespace PkmnLib.Dynamic.Models.Choices;
public interface ISwitchChoice : ITurnChoice
{
}

View File

@@ -0,0 +1,56 @@
using PkmnLib.Dynamic.ScriptHandling;
namespace PkmnLib.Dynamic.Models.Choices;
public interface ITurnChoice : IScriptSource
{
/// <summary>
/// The user of the turn choice
/// </summary>
IPokemon User { get; }
/// <summary>
/// The speed of the user at the beginning of the turn.
/// </summary>
uint Speed { get; set; }
/// <summary>
/// This random value is set at the beginning of the turn. It is used for tie breaking of the
/// turn order in a predictable way, regardless of implementation and hardware.
/// </summary>
uint RandomValue { get; set; }
/// <summary>
/// Whether the choice has failed. A failed choice will stop running, and execute special
/// fail handling during turn execution.
/// </summary>
bool HasFailed { get; }
/// <summary>
/// Fails the choice. This will prevent it from executing and run a specific fail handling during
/// execution. Note that this can not be undone.
/// </summary>
public void Fail();
}
public abstract class TurnChoice : ScriptSource, ITurnChoice
{
protected TurnChoice(IPokemon user)
{
User = user;
}
public IPokemon User { get; }
public uint Speed { get; set; }
public uint RandomValue { get; set; }
public bool HasFailed { get; private set; }
public void Fail()
{
HasFailed = true;
}
}

View File

@@ -0,0 +1,83 @@
namespace PkmnLib.Dynamic.Models.Choices;
/// <summary>
/// Comparer for turnchoices, to determine the order in which they should be executed.
/// </summary>
public class TurnChoiceComparer : IComparer<ITurnChoice>
{
private enum CompareValues
{
XEqualsY = 0,
XLessThanY = -1,
XGreaterThanY = 1
}
private CompareValues CompareForSameType(ITurnChoice x, ITurnChoice y)
{
// Higher speed goes first
var speedComparison = x.Speed.CompareTo(y.Speed);
if (speedComparison != 0)
return (CompareValues)speedComparison;
// If speed is equal, we use the random values we've given to each choice to tiebreak.
// This is to ensure that the order of choices is deterministic.
return (CompareValues)x.RandomValue.CompareTo(y.RandomValue);
}
private CompareValues CompareImpl(ITurnChoice? x, ITurnChoice? y)
{
// Deal with possible null values
switch (x)
{
case null when y is null:
return CompareValues.XEqualsY;
case null:
return CompareValues.XLessThanY;
}
if (y is null)
return CompareValues.XGreaterThanY;
switch (x)
{
case IMoveChoice moveX:
// Move choices go first
if (y is IMoveChoice moveY)
{
// Higher priority goes first
var priorityComparison = moveX.Priority.CompareTo(moveY.Priority);
if (priorityComparison != 0)
return (CompareValues)priorityComparison;
return CompareForSameType(moveX, moveY);
}
return CompareValues.XGreaterThanY;
case IItemChoice itemX:
// Item choices go second
return y switch
{
IMoveChoice => CompareValues.XLessThanY,
IItemChoice itemY => CompareForSameType(itemX, itemY),
_ => CompareValues.XGreaterThanY
};
case ISwitchChoice switchX:
// Switch choices go third
return y switch
{
IMoveChoice or IItemChoice => CompareValues.XLessThanY,
ISwitchChoice switchY => CompareForSameType(switchX, switchY),
_ => CompareValues.XGreaterThanY
};
case IPassChoice passX:
// Pass choices go last
return y switch
{
IMoveChoice or IItemChoice or ISwitchChoice => CompareValues.XLessThanY,
IPassChoice passY => CompareForSameType(passX, passY),
_ => CompareValues.XGreaterThanY
};
}
return CompareValues.XLessThanY;
}
/// <inheritdoc />
public int Compare(ITurnChoice? x, ITurnChoice? y) => (int) CompareImpl(x, y);
}