Style cleanup

This commit is contained in:
2025-03-02 17:19:57 +01:00
parent c0bc905c46
commit 284ab3079c
175 changed files with 588 additions and 650 deletions

View File

@@ -15,8 +15,5 @@ public class PassTurnAI : PokemonAI
}
/// <inheritdoc />
public override ITurnChoice GetChoice(IBattle battle, IPokemon pokemon)
{
return new PassChoice(pokemon);
}
public override ITurnChoice GetChoice(IBattle battle, IPokemon pokemon) => new PassChoice(pokemon);
}

View File

@@ -11,7 +11,7 @@ namespace PkmnLib.Dynamic.AI;
public class RandomAI : PokemonAI
{
private IRandom _random;
/// <inheritdoc />
public RandomAI() : base("Random")
{
@@ -45,6 +45,4 @@ public class RandomAI : PokemonAI
}
return new PassChoice(pokemon);
}
}

View File

@@ -13,7 +13,7 @@ public class CaptureAttemptEvent : IEventData
public IPokemon Target { get; init; }
public CaptureResult Result { get; init; }
/// <inheritdoc />
public EventBatchId BatchId { get; init; }
}

View File

@@ -4,7 +4,7 @@ public class DialogEvent : IEventData
{
/// <inheritdoc />
public EventBatchId BatchId { get; init; } = new();
public DialogEvent(string message, Dictionary<string, object>? parameters = null)
{
Message = message;
@@ -12,6 +12,6 @@ public class DialogEvent : IEventData
}
public string Message { get; set; }
public Dictionary<string, object>? Parameters { get; set; }
}

View File

@@ -10,12 +10,11 @@ public class ExperienceGainEvent : IEventData
PreviousExperience = previousExperience;
NewExperience = newExperience;
}
public IPokemon Pokemon { get; set; }
public uint PreviousExperience { get; }
public uint NewExperience { get; }
/// <inheritdoc />
public EventBatchId BatchId { get; init; }
}

View File

@@ -17,7 +17,7 @@ public class FaintEvent : IEventData
/// The Pokemon that fainted.
/// </summary>
public IPokemon Pokemon { get; init; }
/// <inheritdoc />
public EventBatchId BatchId { get; init; } = new();
}

View File

@@ -13,7 +13,7 @@ public class FormChangeEvent : IEventData
Pokemon = pokemon;
Form = form;
}
/// <inheritdoc />
public EventBatchId BatchId { get; init; }
}

View File

@@ -14,7 +14,7 @@ public readonly record struct EventBatchId
{
Id = Guid.NewGuid();
}
/// <summary>
/// The unique identifier for this batch of events.
/// </summary>

View File

@@ -10,7 +10,7 @@ public class EventHook
/// The event handler that is called when the event is triggered.
/// </summary>
public event EventHandler<IEventData>? Handler;
/// <summary>
/// Triggers the event, calling the handler with the given data.
/// </summary>

View File

@@ -30,7 +30,6 @@ public class HealEvent : IEventData
/// </summary>
public uint NewHealth { get; }
/// <inheritdoc />
public EventBatchId BatchId { get; init; } = new();
}

View File

@@ -17,7 +17,7 @@ public class MoveMissEvent : IEventData
/// Data about the move that missed.
/// </summary>
public IExecutingMove ExecutingMove { get; }
/// <inheritdoc />
public EventBatchId BatchId { get; init; }
}

View File

@@ -17,7 +17,7 @@ public class MoveUseEvent : IEventData
{
ExecutingMove = executingMove;
}
/// <inheritdoc />
public EventBatchId BatchId { get; init; }
}

View File

@@ -15,7 +15,7 @@ public class SpeciesChangeEvent : IEventData
Species = species;
Form = form;
}
/// <inheritdoc />
public EventBatchId BatchId { get; init; }
}

View File

@@ -12,22 +12,22 @@ public interface IBattleStatCalculator
/// Calculate all the flat stats of a Pokemon, disregarding stat boosts.
/// </summary>
void CalculateFlatStats(IPokemon pokemon, StatisticSet<uint> stats);
/// <summary>
/// Calculate a single flat stat of a Pokemon, disregarding stat boosts.
/// </summary>
uint CalculateFlatStat(IPokemon pokemon, Statistic stat);
/// <summary>
/// Calculate all the boosted stats of a Pokemon, including stat boosts.
/// </summary>
void CalculateBoostedStats(IPokemon pokemon, StatisticSet<uint> stats);
/// <summary>
/// Calculate a single boosted stat of a Pokemon, including stat boosts.
/// </summary>
uint CalculateBoostedStat(IPokemon pokemon, Statistic stat);
/// <summary>
/// Calculates the accuracy for a move, taking into account any accuracy modifiers.
/// </summary>

View File

@@ -15,8 +15,8 @@ public record struct CaptureResult
public bool IsCaught { get; init; }
public int Shakes { get; init; }
public bool CriticalCapture { get; init; }
public static CaptureResult Failed => new CaptureResult(false, 0, false);
public static CaptureResult Failed => new(false, 0, false);
}
public interface ICaptureLibrary

View File

@@ -31,12 +31,12 @@ public interface IDynamicLibrary
/// calculators.
/// </summary>
IMiscLibrary MiscLibrary { get; }
/// <summary>
/// The capture library deals with the calculation of the capture rate of a Pokémon.
/// </summary>
ICaptureLibrary CaptureLibrary { get; }
/// <summary>
/// A holder of the script types that can be resolved by this library.
/// </summary>
@@ -66,12 +66,13 @@ public class DynamicLibraryImpl : IDynamicLibrary
if (registry.CaptureLibrary is null)
throw new InvalidOperationException("Capture library not found in plugins.");
var scriptResolver = new ScriptResolver(registry.ScriptTypes, registry.ItemScriptTypes);
return new DynamicLibraryImpl(staticLibrary, registry.BattleStatCalculator,
registry.DamageCalculator, registry.MiscLibrary, registry.CaptureLibrary, scriptResolver);
return new DynamicLibraryImpl(staticLibrary, registry.BattleStatCalculator, registry.DamageCalculator,
registry.MiscLibrary, registry.CaptureLibrary, scriptResolver);
}
private DynamicLibraryImpl(IStaticLibrary staticLibrary, IBattleStatCalculator statCalculator,
IDamageCalculator damageCalculator, IMiscLibrary miscLibrary, ICaptureLibrary captureLibrary, ScriptResolver scriptResolver)
IDamageCalculator damageCalculator, IMiscLibrary miscLibrary, ICaptureLibrary captureLibrary,
ScriptResolver scriptResolver)
{
StaticLibrary = staticLibrary;
StatCalculator = statCalculator;

View File

@@ -14,7 +14,7 @@ public interface IMiscLibrary
/// moves left, yet wants to make a move.
/// </summary>
ITurnChoice ReplacementChoice(IPokemon user, byte targetSide, byte targetPosition);
/// <summary>
/// Gets the current time of day for the battle.
/// </summary>

View File

@@ -94,7 +94,7 @@ public interface IBattle : IScriptSource, IDeepCloneable
void ValidateBattleState();
bool HasForcedTurn(IPokemon pokemon, [NotNullWhen(true)] out ITurnChoice? choice);
/// <summary>
/// Checks whether a choice is actually possible.
/// </summary>
@@ -111,22 +111,22 @@ public interface IBattle : IScriptSource, IDeepCloneable
void SetWeather(StringKey? weatherName);
public IScriptSet Volatile { get; }
/// <summary>
/// Gets the current weather of the battle. If no weather is present, this returns null.
/// </summary>
StringKey? WeatherName { get; }
void SetTerrain(StringKey? terrainName);
StringKey? TerrainName { get; }
/// <summary>
/// Gets the turn choices of the previous turn. This is a list of lists, where each list represents the choices
/// for a single turn. The outer list is ordered from oldest to newest turn.
/// </summary>
IReadOnlyList<IReadOnlyList<ITurnChoice>> PreviousTurnChoices { get; }
CaptureResult AttempCapture(byte sideIndex, byte position, IItem item);
}
@@ -247,10 +247,10 @@ public class BattleImpl : ScriptSource, IBattle
choice = null;
return false;
}
ITurnChoice? forcedChoice = null;
pokemon.RunScriptHook(
script => script.ForceTurnSelection(battleData.SideIndex, battleData.Position, ref forcedChoice));
pokemon.RunScriptHook(script =>
script.ForceTurnSelection(battleData.SideIndex, battleData.Position, ref forcedChoice));
choice = forcedChoice;
return choice != null;
}
@@ -262,7 +262,7 @@ public class BattleImpl : ScriptSource, IBattle
return false;
if (HasForcedTurn(choice.User, out var forcedChoice) && choice != forcedChoice)
return false;
if (choice is IMoveChoice moveChoice)
{
// TODO: Hook to change number of PP needed.
@@ -365,7 +365,7 @@ public class BattleImpl : ScriptSource, IBattle
/// <inheritdoc />
public StringKey? WeatherName => _weatherScript.Script?.Name;
private readonly ScriptContainer _terrainScript = new();
/// <inheritdoc />
@@ -386,7 +386,6 @@ public class BattleImpl : ScriptSource, IBattle
/// <inheritdoc />
public StringKey? TerrainName => _terrainScript.Script?.Name;
private readonly List<IReadOnlyList<ITurnChoice>> _previousTurnChoices = new();
/// <inheritdoc />
@@ -398,7 +397,7 @@ public class BattleImpl : ScriptSource, IBattle
var target = GetPokemon(sideIndex, position);
if (target is not { IsUsable: true })
return CaptureResult.Failed;
var attemptCapture = Library.CaptureLibrary.TryCapture(target, item, Random);
if (attemptCapture.IsCaught)
{

View File

@@ -23,7 +23,7 @@ public class BattleChoiceQueue : IDeepCloneable
{
_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.
@@ -40,18 +40,17 @@ public class BattleChoiceQueue : IDeepCloneable
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
@@ -72,7 +71,7 @@ public class BattleChoiceQueue : IDeepCloneable
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!);
}
@@ -99,8 +98,9 @@ public class BattleChoiceQueue : IDeepCloneable
_choices[_currentIndex] = choice;
return true;
}
internal IReadOnlyList<ITurnChoice?> GetChoices() => _choices;
public ITurnChoice? FirstOrDefault(Func<ITurnChoice, bool> predicate) => _choices.WhereNotNull().FirstOrDefault(predicate);
public ITurnChoice? FirstOrDefault(Func<ITurnChoice, bool> predicate) =>
_choices.WhereNotNull().FirstOrDefault(predicate);
}

View File

@@ -12,7 +12,7 @@ internal static class MoveTurnExecutor
{
var chosenMove = moveChoice.ChosenMove;
var moveData = chosenMove.MoveData;
var moveDataName = moveData.Name;
moveChoice.RunScriptHook(x => x.ChangeMove(moveChoice, ref moveDataName));
if (moveData.Name != moveDataName)
@@ -31,11 +31,11 @@ internal static class MoveTurnExecutor
moveChoice.Script.Set(script);
}
}
}
var targetType = moveData.Target;
var targets = TargetResolver.ResolveTargets(battle, moveChoice.TargetSide, moveChoice.TargetPosition, targetType);
var targets =
TargetResolver.ResolveTargets(battle, moveChoice.TargetSide, moveChoice.TargetPosition, targetType);
moveChoice.RunScriptHook(x => x.ChangeTargets(moveChoice, ref targets));
byte numberOfHits = 1;
@@ -46,7 +46,7 @@ internal static class MoveTurnExecutor
}
var executingMove = new ExecutingMoveImpl(targets, numberOfHits, chosenMove, moveData, moveChoice);
var prevented = false;
executingMove.RunScriptHook(x => x.PreventMove(executingMove, ref prevented));
if (prevented)
@@ -56,9 +56,9 @@ internal static class MoveTurnExecutor
// TODO: Modify the PP used by the move.
if (!executingMove.ChosenMove.TryUse(ppUsed))
return;
battle.EventHook.Invoke(new MoveUseEvent(executingMove));
var failed = false;
executingMove.RunScriptHook(x => x.FailMove(executingMove, ref failed));
if (failed)
@@ -66,12 +66,12 @@ internal static class MoveTurnExecutor
// TODO: fail handling
return;
}
var stopped = false;
executingMove.RunScriptHook(x => x.StopBeforeMove(executingMove, ref stopped));
if (stopped)
return;
executingMove.RunScriptHook(x => x.OnBeforeMove(executingMove));
foreach (var target in targets.WhereNotNull())
{
@@ -88,7 +88,7 @@ internal static class MoveTurnExecutor
// TODO: fail handling
return;
}
var isInvulnerable = false;
target.RunScriptHook(x => x.IsInvulnerableToMove(executingMove, target, ref isInvulnerable));
if (isInvulnerable)
@@ -96,7 +96,7 @@ internal static class MoveTurnExecutor
// TODO: event?
return;
}
var numberOfHits = executingMove.NumberOfHits;
var targetHitStat = executingMove.GetTargetIndex(target) * numberOfHits;
@@ -120,7 +120,7 @@ internal static class MoveTurnExecutor
var effectiveness = battle.Library.StaticLibrary.Types.GetEffectiveness(hitType, target.Types);
executingMove.RunScriptHook(x => x.ChangeEffectiveness(executingMove, target, hitIndex, ref effectiveness));
hitData.Effectiveness = effectiveness;
var blockCritical = false;
executingMove.RunScriptHook(x => x.BlockCriticalHit(executingMove, target, hitIndex, ref blockCritical));
target.RunScriptHook(x => x.BlockIncomingCriticalHit(executingMove, target, hitIndex, ref blockCritical));
@@ -129,10 +129,10 @@ internal static class MoveTurnExecutor
var critical = battle.Library.DamageCalculator.IsCritical(battle, executingMove, target, hitIndex);
hitData.IsCritical = critical;
}
var basePower = battle.Library.DamageCalculator.GetBasePower(executingMove, target, hitIndex, hitData);
hitData.BasePower = basePower;
hitData.Damage = battle.Library.DamageCalculator.GetDamage(executingMove, target, hitIndex, hitData);
var accuracy = useMove.Accuracy;
@@ -140,17 +140,17 @@ internal static class MoveTurnExecutor
// modifying it.
if (accuracy != 255)
{
accuracy = battle.Library.StatCalculator.CalculateModifiedAccuracy(executingMove, target,
hitIndex, accuracy);
accuracy = battle.Library.StatCalculator.CalculateModifiedAccuracy(executingMove, target, hitIndex,
accuracy);
}
if (accuracy < 100 && battle.Random.GetInt(100) >= accuracy)
{
executingMove.RunScriptHook(x => x.OnMoveMiss(executingMove, target));
battle.EventHook.Invoke(new MoveMissEvent(executingMove));
break;
}
var blockIncomingHit = false;
target.RunScriptHook(x => x.BlockIncomingHit(executingMove, target, hitIndex, ref blockIncomingHit));
executingMove.RunScriptHook(x => x.BlockOutgoingHit(executingMove, target, hitIndex, ref blockIncomingHit));
@@ -187,7 +187,7 @@ internal static class MoveTurnExecutor
BatchId = hitEventBatch,
});
target.Damage(damage, DamageSource.MoveDamage, hitEventBatch);
if (!target.IsFainted)
if (!target.IsFainted)
target.RunScriptHook(x => x.OnIncomingHit(executingMove, target, hitIndex));
else
executingMove.RunScriptHook(x => x.OnOpponentFaints(executingMove, target, hitIndex));
@@ -198,14 +198,16 @@ internal static class MoveTurnExecutor
if (secondaryEffect != null)
{
var preventSecondary = false;
target.RunScriptHook(x => x.PreventSecondaryEffect(executingMove, target, hitIndex, ref preventSecondary));
target.RunScriptHook(x =>
x.PreventSecondaryEffect(executingMove, target, hitIndex, ref preventSecondary));
if (!preventSecondary)
{
var chance = secondaryEffect.Chance;
if (chance < 0 || battle.Random.EffectChance(chance, executingMove, target, hitIndex))
{
executingMove.RunScriptHook(x => x.OnSecondaryEffect(executingMove, target, hitIndex));
executingMove.RunScriptHook(x =>
x.OnSecondaryEffect(executingMove, target, hitIndex));
}
}
}
@@ -213,7 +215,7 @@ internal static class MoveTurnExecutor
}
}
}
if (numberOfHits == 0)
{
target.RunScriptHook(x => x.OnMoveMiss(executingMove, target));
@@ -224,6 +226,5 @@ internal static class MoveTurnExecutor
{
executingMove.RunScriptHook(x => x.OnAfterHits(executingMove, target));
}
}
}

View File

@@ -15,8 +15,8 @@ public static class TargetResolver
return target switch
{
MoveTarget.Adjacent or MoveTarget.AdjacentAlly or MoveTarget.AdjacentAllySelf or MoveTarget.AdjacentOpponent
or MoveTarget.Any or MoveTarget.RandomOpponent
or MoveTarget.SelfUse => [battle.GetPokemon(side, position)],
or MoveTarget.Any or MoveTarget.RandomOpponent or MoveTarget.SelfUse =>
[battle.GetPokemon(side, position)],
MoveTarget.All => GetAllTargets(battle),
MoveTarget.AllAdjacentOpponent => GetAllAdjacentAndOpponent(battle, side, position),
MoveTarget.AllAdjacent => GetAllAdjacent(battle, side, position),
@@ -144,7 +144,8 @@ public static class TargetResolver
return
[
battle.GetPokemon(side, position), battle.GetPokemon(side, (byte)left), battle.GetPokemon(side, (byte)right),
battle.GetPokemon(side, position), battle.GetPokemon(side, (byte)left),
battle.GetPokemon(side, (byte)right),
];
}
}

View File

@@ -16,9 +16,11 @@ public static class TurnRunner
{
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.
@@ -30,7 +32,7 @@ public static class TurnRunner
{
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.
@@ -41,7 +43,7 @@ public static class TurnRunner
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.
@@ -122,7 +124,6 @@ public static class TurnRunner
userSide.SwapPokemon(battleData.Position, fleeChoice.SwitchTo);
}
private static void ExecuteFleeChoice(IBattle battle, IFleeChoice fleeChoice)
{
var user = fleeChoice.User;
@@ -131,7 +132,7 @@ public static class TurnRunner
return;
if (!battle.CanFlee)
return;
var preventFlee = false;
fleeChoice.RunScriptHook(script => script.PreventSelfRunAway(fleeChoice, ref preventFlee));
if (preventFlee)
@@ -148,10 +149,10 @@ public static class TurnRunner
return;
}
}
if (!battle.Library.MiscLibrary.CanFlee(battle, fleeChoice))
return;
var userSide = battle.Sides[battleData.SideIndex];
userSide.MarkAsFled();
battle.ValidateBattleState();
@@ -171,5 +172,4 @@ public static class TurnRunner
}
itemChoice.Item.RunItemScript(battle.Library.ScriptResolver, target ?? user);
}
}

View File

@@ -10,22 +10,22 @@ public record struct BattleResult
ConclusiveResult = conclusiveResult;
WinningSide = winningSide;
}
/// <summary>
/// An inconclusive battle result. This means no side has won.
/// </summary>
public static BattleResult Inconclusive => new(false, null);
/// <summary>
/// A conclusive battle result. This means one side has won.
/// </summary>
public static BattleResult Conclusive(byte winningSide) => new(true, winningSide);
/// <summary>
/// Whether the battle has a conclusive result. If false, no side has won.
/// </summary>
public bool ConclusiveResult { get; }
/// <summary>
/// The side that won the battle. If null, no side has won.
/// </summary>

View File

@@ -14,48 +14,48 @@ public interface IBattleSide : IScriptSource, IDeepCloneable
/// The index of the side on the battle.
/// </summary>
byte Index { get; }
/// <summary>
/// The number of Pokémon that can be on the side.
/// </summary>
byte NumberOfPositions { get; }
/// <summary>
/// A list of Pokémon currently on the battlefield.
/// </summary>
IReadOnlyList<IPokemon?> Pokemon { get; }
/// <summary>
/// The currently set choices for all Pokémon on the battlefield. Cleared when the turn starts.
/// </summary>
IReadOnlyList<ITurnChoice?> SetChoices { get; }
/// <summary>
/// Whether every Pokémon on this side has its choices
/// </summary>
bool AllChoicesSet { get; }
/// <summary>
/// The slots on the side that can still be filled. Once all slots are set to false, this side
/// has lost the battle.
/// </summary>
IReadOnlyList<bool> FillablePositions { get; }
/// <summary>
/// A reference to the battle this side is in.
/// </summary>
IBattle Battle { get; }
/// <summary>
/// Whether this side has fled.
/// </summary>
bool HasFledBattle { get; }
/// <summary>
/// The volatile scripts that are attached to the side.
/// </summary>
IScriptSet VolatileScripts { get; }
/// <summary>
/// Returns true if there are slots that need to be filled with a new pokemon, that have parties
/// responsible for them. Returns false if all slots are filled with usable pokemon, or slots are
@@ -67,7 +67,7 @@ public interface IBattleSide : IScriptSource, IDeepCloneable
/// Sets a choice for a Pokémon on this side.
/// </summary>
void SetChoice(byte position, ITurnChoice choice);
/// <summary>
/// Resets all choices on this side.
/// </summary>
@@ -110,12 +110,12 @@ public interface IBattleSide : IScriptSource, IDeepCloneable
/// Checks whether the side has been defeated.
/// </summary>
bool IsDefeated();
/// <summary>
/// The number of times this side has attempted to flee.
/// </summary>
uint FleeAttempts { get; }
/// <summary>
/// Registers a flee attempt for this side.
/// </summary>
@@ -150,7 +150,7 @@ public class BattleSideImpl : ScriptSource, IBattleSide
Battle = battle;
VolatileScripts = new ScriptSet();
}
/// <inheritdoc />
public byte Index { get; }
@@ -158,6 +158,7 @@ public class BattleSideImpl : ScriptSource, IBattleSide
public byte NumberOfPositions { get; }
private readonly IPokemon?[] _pokemon;
/// <inheritdoc />
public IReadOnlyList<IPokemon?> Pokemon => _pokemon;
@@ -170,6 +171,7 @@ public class BattleSideImpl : ScriptSource, IBattleSide
public bool AllChoicesSet => _setChoices.All(choice => choice is not null);
private readonly bool[] _fillablePositions;
/// <inheritdoc />
public IReadOnlyList<bool> FillablePositions => _fillablePositions;
@@ -223,7 +225,7 @@ public class BattleSideImpl : ScriptSource, IBattleSide
pokemon.RunScriptHook(script => script.OnRemove());
pokemon.SetOnBattlefield(false);
}
_pokemon[index] = null;
}
@@ -259,7 +261,7 @@ public class BattleSideImpl : ScriptSource, IBattleSide
{
Battle.EventHook.Invoke(new SwitchEvent(Index, position, null));
}
return oldPokemon;
}

View File

@@ -7,7 +7,6 @@ namespace PkmnLib.Dynamic.Models.Choices;
/// </summary>
public interface IFleeChoice : ITurnChoice
{
}
/// <inheritdoc cref="IFleeChoice"/>
@@ -22,7 +21,9 @@ public class FleeTurnChoice : TurnChoice, IFleeChoice
public override int ScriptCount => User.ScriptCount;
/// <inheritdoc />
public override void GetOwnScripts(List<IEnumerable<ScriptContainer>> scripts) { }
public override void GetOwnScripts(List<IEnumerable<ScriptContainer>> scripts)
{
}
/// <inheritdoc />
public override void CollectScripts(List<IEnumerable<ScriptContainer>> scripts) => User.CollectScripts(scripts);

View File

@@ -12,7 +12,7 @@ public interface IItemChoice : ITurnChoice
/// The item that is used.
/// </summary>
public IItem Item { get; }
/// <summary>
/// The side the move is targeted at.
/// </summary>

View File

@@ -32,9 +32,9 @@ public interface IMoveChoice : ITurnChoice
/// The underlying script of the move.
/// </summary>
ScriptContainer Script { get; set; }
Dictionary<StringKey, object?>? AdditionalData { get; }
IScriptSet Volatile { get; }
}
@@ -47,7 +47,7 @@ public class MoveChoice : TurnChoice, IMoveChoice
ChosenMove = usedMove;
TargetSide = targetSide;
TargetPosition = targetPosition;
var secondaryEffect = usedMove.MoveData.SecondaryEffect;
if (secondaryEffect != null)
{

View File

@@ -7,7 +7,6 @@ namespace PkmnLib.Dynamic.Models.Choices;
/// </summary>
public interface IPassChoice : ITurnChoice
{
}
public class PassChoice : TurnChoice, IPassChoice

View File

@@ -7,7 +7,7 @@ public class TurnChoiceComparer : IComparer<ITurnChoice>
{
/// <inheritdoc cref="TurnChoiceComparer"/>
public static TurnChoiceComparer Instance { get; } = new();
private enum CompareValues
{
XEqualsY = 0,
@@ -25,7 +25,7 @@ public class TurnChoiceComparer : IComparer<ITurnChoice>
// This is to ensure that the order of choices is deterministic.
return (CompareValues)x.RandomValue.CompareTo(y.RandomValue);
}
private static CompareValues CompareImpl(ITurnChoice? x, ITurnChoice? y)
{
// Deal with possible null values
@@ -77,10 +77,10 @@ public class TurnChoiceComparer : IComparer<ITurnChoice>
_ => CompareValues.XGreaterThanY,
};
}
return CompareValues.XLessThanY;
}
/// <inheritdoc />
public int Compare(ITurnChoice? x, ITurnChoice? y) => (int) CompareImpl(x, y);
public int Compare(ITurnChoice? x, ITurnChoice? y) => (int)CompareImpl(x, y);
}

View File

@@ -19,13 +19,13 @@ public enum DamageSource
/// The damage is done because of struggling.
/// </summary>
Struggle = 2,
/// <summary>
/// The damage is done because of a form change.
/// This happens when the form of a Pokemon changes, and it has less max HP than it had before.
/// </summary>
FormChange = 3,
/// <summary>
/// The damage is done because of the weather.
/// </summary>

View File

@@ -129,12 +129,12 @@ public interface IExecutingMove : IScriptSource
/// Gets a hit based on its raw index.
/// </summary>
IHitData GetDataFromRawIndex(int index);
/// <summary>
/// Gets the targets of this move.
/// </summary>
IReadOnlyList<IPokemon?> Targets { get; }
/// <summary>
/// The underlying move choice.
/// </summary>

View File

@@ -54,7 +54,7 @@ public interface ILearnedMove : IDeepCloneable
/// The maximal power points for this move.
/// </summary>
byte MaxPp { get; }
/// <summary>
/// The current power points for this move.
/// </summary>
@@ -86,7 +86,7 @@ public interface ILearnedMove : IDeepCloneable
public class LearnedMoveImpl : ILearnedMove
{
private byte _maxPpModification = 0;
/// <inheritdoc cref="LearnedMoveImpl" />
public LearnedMoveImpl(IMoveData moveData, MoveLearnMethod learnMethod)
{
@@ -95,8 +95,7 @@ public class LearnedMoveImpl : ILearnedMove
CurrentPp = MaxPp;
}
public LearnedMoveImpl(IMoveData moveData, MoveLearnMethod learnMethod, byte pp)
: this(moveData, learnMethod)
public LearnedMoveImpl(IMoveData moveData, MoveLearnMethod learnMethod, byte pp) : this(moveData, learnMethod)
{
CurrentPp = pp;
}
@@ -109,12 +108,12 @@ public class LearnedMoveImpl : ILearnedMove
/// <inheritdoc />
public MoveLearnMethod LearnMethod { get; }
/// <summary>
/// The available power points for this move.
/// </summary>
public byte CurrentPp { get; private set; }
/// <summary>
/// Try to use the move. This subtracts the amount of PP from the current PP. If the amount requested is
/// higher than the current PP, this will return false, and the PP will not be reduced.
@@ -123,7 +122,7 @@ public class LearnedMoveImpl : ILearnedMove
{
if (CurrentPp < amount)
return false;
CurrentPp -= amount;
return true;
}

View File

@@ -50,7 +50,7 @@ public interface IPokemon : IScriptSource, IDeepCloneable
/// The amount of experience of the Pokemon.
/// </summary>
uint Experience { get; }
/// <summary>
/// Increases the experience of the Pokemon. Returns whether any experience was gained.
/// </summary>
@@ -71,7 +71,7 @@ public interface IPokemon : IScriptSource, IDeepCloneable
/// currently not used, and can be used for other implementations.
/// </summary>
byte Coloring { get; }
/// <summary>
/// Whether the Pokemon is shiny.
/// </summary>
@@ -98,7 +98,7 @@ public interface IPokemon : IScriptSource, IDeepCloneable
/// <param name="weightInKg">The new weight in kilograms</param>
/// <returns></returns>
public bool ChangeWeightInKgBy(float weightInKg);
/// <summary>
/// The height of the Pokémon in meters.
/// </summary>
@@ -124,7 +124,7 @@ public interface IPokemon : IScriptSource, IDeepCloneable
/// The stats of the Pokemon including the stat boosts
/// </summary>
StatisticSet<uint> BoostedStats { get; }
/// <summary>
/// The maximum health of the Pokemon.
/// </summary>
@@ -171,12 +171,12 @@ public interface IPokemon : IScriptSource, IDeepCloneable
/// are null.
/// </summary>
IReadOnlyList<ILearnedMove?> Moves { get; }
/// <summary>
/// Checks whether the Pokemon has a specific move in its current moveset.
/// </summary>
bool HasMove(StringKey moveName);
/// <summary>
/// Swaps two moves of the Pokemon.
/// </summary>
@@ -201,7 +201,7 @@ public interface IPokemon : IScriptSource, IDeepCloneable
/// Whether or not this Pokemon was caught this battle.
/// </summary>
bool IsCaught { get; }
public void MarkAsCaught();
/// <summary>
@@ -240,7 +240,7 @@ public interface IPokemon : IScriptSource, IDeepCloneable
/// </summary>
[MustUseReturnValue]
IItem? RemoveHeldItem();
/// <summary>
/// Removes the held item from the Pokemon for the duration of the battle. Returns the previously held item.
/// </summary>
@@ -249,7 +249,7 @@ public interface IPokemon : IScriptSource, IDeepCloneable
/// restored after the battle.
/// </remarks>
IItem? RemoveHeldItemForBattle();
/// <summary>
/// Restores the held item of a Pokémon if it was temporarily removed.
/// </summary>
@@ -273,7 +273,7 @@ public interface IPokemon : IScriptSource, IDeepCloneable
/// Suppresses the ability of the Pokémon.
/// </summary>
public void SuppressAbility();
/// <summary>
/// Returns the currently active ability.
/// </summary>
@@ -322,7 +322,7 @@ public interface IPokemon : IScriptSource, IDeepCloneable
/// heal if the Pokemon has 0 health. If the amount healed is 0, this will return false.
/// </summary>
bool Heal(uint heal, bool allowRevive = false);
/// <summary>
/// Restores all PP of the Pokemon.
/// </summary>
@@ -337,10 +337,12 @@ public interface IPokemon : IScriptSource, IDeepCloneable
/// Checks whether the Pokémon has a specific non-volatile status.
/// </summary>
bool HasStatus(StringKey status);
/// <summary>
/// Adds a non-volatile status to the Pokemon.
/// </summary>
void SetStatus(StringKey status);
/// <summary>
/// Removes the current non-volatile status from the Pokemon.
/// </summary>
@@ -371,23 +373,23 @@ public interface IPokemon : IScriptSource, IDeepCloneable
/// Marks a Pokemon as seen in the battle.
/// </summary>
void MarkOpponentAsSeen(IPokemon pokemon);
/// <summary>
/// Removes a type from the Pokémon. Returns whether the type was removed.
/// </summary>
bool RemoveType(TypeIdentifier type);
/// <summary>
/// Adds a type to the Pokémon. Returns whether the type was added. It will not add the type if
/// the Pokémon already has it.
/// </summary>
bool AddType(TypeIdentifier type);
/// <summary>
/// Replace the types of the Pokémon with the provided types.
/// </summary>
void SetTypes(IReadOnlyList<TypeIdentifier> types);
void ChangeAbility(IAbility ability);
/// <summary>
@@ -431,17 +433,17 @@ public interface IPokemonBattleData : IDeepCloneable
/// Adds an opponent to the list of seen opponents.
/// </summary>
void MarkOpponentAsSeen(IPokemon opponent);
/// <summary>
/// A list of items the Pokémon has consumed this battle.
/// </summary>
IReadOnlyList<IItem> ConsumedItems { get; }
/// <summary>
/// Marks an item as consumed.
/// </summary>
void MarkItemAsConsumed(IItem itemName);
uint SwitchInTurn { get; internal set; }
}
@@ -569,7 +571,7 @@ public class PokemonImpl : ScriptSource, IPokemon
{
BatchId = batchId,
});
var newLevel = Library.StaticLibrary.GrowthRates.CalculateLevel(Species.GrowthRate, Experience);
if (newLevel > Level)
{
@@ -579,7 +581,7 @@ public class PokemonImpl : ScriptSource, IPokemon
{
BatchId = batchId,
});
if (newLevel >= maxLevel)
{
Experience = Library.StaticLibrary.GrowthRates.CalculateExperience(Species.GrowthRate, maxLevel);
@@ -597,7 +599,7 @@ public class PokemonImpl : ScriptSource, IPokemon
/// <inheritdoc />
public byte Coloring { get; }
/// <inheritdoc />
public bool IsShiny => Coloring == 1;
@@ -686,7 +688,11 @@ public class PokemonImpl : ScriptSource, IPokemon
private List<TypeIdentifier> _types = new();
/// <inheritdoc />
public IReadOnlyList<TypeIdentifier> Types { get => _types; private set => _types = value.ToList(); }
public IReadOnlyList<TypeIdentifier> Types
{
get => _types;
private set => _types = value.ToList();
}
/// <inheritdoc />
public bool IsEgg { get; private set; }
@@ -730,7 +736,7 @@ public class PokemonImpl : ScriptSource, IPokemon
HeldItem = null;
return previous;
}
private IItem? _stolenHeldItem;
/// <inheritdoc />
@@ -753,7 +759,7 @@ public class PokemonImpl : ScriptSource, IPokemon
return false;
if (!Library.ScriptResolver.TryResolveBattleItemScript(HeldItem, out _))
return false;
if (BattleData != null)
{
var prevented = false;
@@ -761,7 +767,7 @@ public class PokemonImpl : ScriptSource, IPokemon
if (prevented)
return false;
}
// TODO: actually consume the item
throw new NotImplementedException();
}
@@ -798,12 +804,12 @@ public class PokemonImpl : ScriptSource, IPokemon
RecalculateBoostedStats();
return true;
}
/// <summary>
/// Whether the ability of the Pokémon is suppressed.
/// </summary>
public bool AbilitySuppressed { get; private set; }
/// <inheritdoc />
public void SuppressAbility()
{
@@ -933,7 +939,7 @@ public class PokemonImpl : ScriptSource, IPokemon
this.RunScriptHook(script => script.ChangeIncomingDamage(this, source, ref dmg));
damage = dmg;
}
// If the damage is more than the current health, we cap it at the current health, to prevent
// underflow.
if (damage >= CurrentHealth)
@@ -989,7 +995,7 @@ public class PokemonImpl : ScriptSource, IPokemon
{
if (IsFainted && !allowRevive)
return false;
var maxAmount = this.BoostedStats.Hp - CurrentHealth;
var maxAmount = BoostedStats.Hp - CurrentHealth;
if (heal > maxAmount)
heal = maxAmount;
if (heal == 0)
@@ -1019,7 +1025,8 @@ public class PokemonImpl : ScriptSource, IPokemon
{
for (byte i = 0; i < Moves.Count; i++)
{
if (Moves[i] is not null) continue;
if (Moves[i] is not null)
continue;
index = i;
break;
}

View File

@@ -10,7 +10,7 @@ public interface IPokemonParty : IReadOnlyList<IPokemon?>, IDeepCloneable
{
event EventHandler<(IPokemon?, int index)>? OnSwapInto;
event EventHandler<(int index1, int index2)>? OnSwap;
/// <summary>
/// Sets the Pokemon at an index to a Pokemon, returning the old Pokemon.
/// </summary>
@@ -28,7 +28,7 @@ public interface IPokemonParty : IReadOnlyList<IPokemon?>, IDeepCloneable
/// This will return false if all Pokemon are fainted, or eggs, etc.
/// </remarks>
bool HasUsablePokemon();
/// <summary>
/// Packs the party so that all Pokémon are at the front, and the empty slots are at the back.
/// </summary>
@@ -46,7 +46,6 @@ public class PokemonParty : IPokemonParty
_pokemon = new IPokemon[size];
}
/// <inheritdoc />
public event EventHandler<(IPokemon?, int index)>? OnSwapInto;
@@ -73,10 +72,9 @@ public class PokemonParty : IPokemonParty
OnSwap?.Invoke(this, (index1, index2));
}
/// <inheritdoc />
public bool HasUsablePokemon() => _pokemon.Any(p => p is { IsUsable: true });
/// <inheritdoc />
public IEnumerator<IPokemon?> GetEnumerator() => ((IEnumerable<IPokemon?>)_pokemon).GetEnumerator();
@@ -95,11 +93,11 @@ public class PokemonParty : IPokemonParty
// Pack the party so that all Pokémon are at the front.
for (var i = 0; i < _pokemon.Length; i++)
{
if (_pokemon[i] != null)
if (_pokemon[i] != null)
continue;
for (var j = i + 1; j < _pokemon.Length; j++)
{
if (_pokemon[j] == null)
if (_pokemon[j] == null)
continue;
Swap(i, j);
break;

View File

@@ -134,7 +134,7 @@ public record SerializedStats
public SerializedStats()
{
}
public SerializedStats(ImmutableStatisticSet<byte> stats)
{
Hp = stats.Hp;
@@ -144,7 +144,7 @@ public record SerializedStats
SpecialDefense = stats.SpecialDefense;
Speed = stats.Speed;
}
public SerializedStats(long hp, long attack, long defense, long specialAttack, long specialDefense, long speed)
{
Hp = hp;

View File

@@ -12,8 +12,7 @@ public abstract class ItemScript : IDeepCloneable
}
protected IItem Item { get; private set; }
/// <summary>
/// Initializes the script with the given parameters for a specific item
/// </summary>

View File

@@ -11,14 +11,14 @@ public abstract class PokeballScript : ItemScript
}
public abstract byte GetCatchRate(IPokemon target);
/// <inheritdoc />
public override void OnUseWithTarget(IPokemon target)
{
var battleData = target.BattleData;
if (battleData == null)
return;
battleData.Battle.AttempCapture(battleData.SideIndex, battleData.Position, Item);
}
}

View File

@@ -3,8 +3,7 @@ using PkmnLib.Static.Utils;
namespace PkmnLib.Dynamic.ScriptHandling.Registry;
[AttributeUsage(AttributeTargets.Class)]
[MeansImplicitUse]
[AttributeUsage(AttributeTargets.Class), MeansImplicitUse]
public class ItemScriptAttribute : Attribute
{
/// <summary>

View File

@@ -11,11 +11,11 @@ public abstract class Plugin
protected Plugin()
{
}
protected Plugin(PluginConfiguration configuration)
{
}
/// <summary>
/// The name of the plugin. Mostly used for debugging purposes.
/// </summary>

View File

@@ -6,8 +6,7 @@ namespace PkmnLib.Dynamic.ScriptHandling.Registry;
/// <summary>
/// Helper attribute to register scripts through reflection.
/// </summary>
[AttributeUsage(AttributeTargets.Class, Inherited = false)]
[MeansImplicitUse]
[AttributeUsage(AttributeTargets.Class, Inherited = false), MeansImplicitUse]
public class ScriptAttribute : Attribute
{
/// <summary>

View File

@@ -64,7 +64,7 @@ public class ScriptRegistry
// This is more performant than using Activator.CreateInstance.
_scriptTypes[(category, name)] = Expression.Lambda<Func<Script>>(Expression.New(constructor)).Compile();
}
/// <summary>
/// Register an item script type with the given name.
/// </summary>
@@ -83,32 +83,32 @@ public class ScriptRegistry
// This is more performant than using Activator.CreateInstance.
var parameterExpression = Expression.Parameter(typeof(IItem), "item");
var newExpression = Expression.New(constructor, parameterExpression);
_itemScriptTypes[name] = Expression.Lambda<Func<IItem, ItemScript>>(newExpression, parameterExpression).Compile();
_itemScriptTypes[name] =
Expression.Lambda<Func<IItem, ItemScript>>(newExpression, parameterExpression).Compile();
}
/// <summary>
/// Register a battle stat calculator.
/// </summary>
public void RegisterBattleStatCalculator<T>(T battleStatCalculator)
where T : IBattleStatCalculator => _battleStatCalculator = battleStatCalculator;
public void RegisterBattleStatCalculator<T>(T battleStatCalculator) where T : IBattleStatCalculator =>
_battleStatCalculator = battleStatCalculator;
/// <summary>
/// Register a damage calculator.
/// </summary>
public void RegisterDamageCalculator<T>(T damageCalculator)
where T : IDamageCalculator => _damageCalculator = damageCalculator;
public void RegisterDamageCalculator<T>(T damageCalculator) where T : IDamageCalculator =>
_damageCalculator = damageCalculator;
/// <summary>
/// Register a misc library.
/// </summary>
public void RegisterMiscLibrary<T>(T miscLibrary) where T : IMiscLibrary
=> _miscLibrary = miscLibrary;
public void RegisterMiscLibrary<T>(T miscLibrary) where T : IMiscLibrary => _miscLibrary = miscLibrary;
/// <summary>
/// Register a capture library.
/// </summary>
public void RegisterCaptureLibrary<T>(T captureLibrary) where T : ICaptureLibrary
=> _captureLibrary = captureLibrary;
public void RegisterCaptureLibrary<T>(T captureLibrary) where T : ICaptureLibrary =>
_captureLibrary = captureLibrary;
internal IReadOnlyDictionary<(ScriptCategory category, StringKey name), Func<Script>> ScriptTypes => _scriptTypes;
internal IReadOnlyDictionary<StringKey, Func<IItem, ItemScript>> ItemScriptTypes => _itemScriptTypes;

View File

@@ -9,14 +9,14 @@ namespace PkmnLib.Dynamic.ScriptHandling.Registry;
public static class ScriptUtils
{
private static readonly Dictionary<Type, StringKey> NameCache = new();
/// <summary>
/// Resolve name from the <see cref="ScriptAttribute"/> of the given script.
/// </summary>
public static StringKey ResolveName(this Script script) => ResolveName(script.GetType());
public static StringKey ResolveName<T>() where T : Script => ResolveName(typeof(T));
/// <summary>
/// Resolve name from the <see cref="ScriptAttribute"/> of the given type.
/// </summary>

View File

@@ -14,7 +14,7 @@ namespace PkmnLib.Dynamic.ScriptHandling;
public abstract class Script : IDeepCloneable
{
internal event Action<Script>? OnRemoveEvent;
private int _suppressCount;
public void RemoveSelf()
@@ -76,14 +76,14 @@ public abstract class Script : IDeepCloneable
public virtual void OnInitialize(IReadOnlyDictionary<StringKey, object?>? parameters)
{
}
/// <summary>
/// Override to customize whether the move can be selected at all.
/// </summary>
public virtual void PreventMoveSelection(IMoveChoice choice, ref bool prevent)
{
}
public virtual void ForceTurnSelection(byte sideIndex, byte position, ref ITurnChoice? choice)
{
}
@@ -121,7 +121,7 @@ public abstract class Script : IDeepCloneable
public virtual void ChangeMove(IMoveChoice choice, ref StringKey moveName)
{
}
/// <summary>
/// Changes the targets of a move choice. This allows for changing the targets of a move before the move starts.
/// </summary>
@@ -267,7 +267,7 @@ public abstract class Script : IDeepCloneable
public virtual void BypassDefensiveStatBoosts(IExecutingMove move, IPokemon target, byte hit, ref bool bypass)
{
}
/// <summary>
/// This function allows a script to bypass evasion stat boosts for a move hit.
/// If this is true, the move will handle the evasion stat boosts as if the target has no positive stat boosts.
@@ -527,11 +527,11 @@ public abstract class Script : IDeepCloneable
public virtual void BlockIncomingHit(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref bool block)
{
}
public virtual void BlockOutgoingHit(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref bool block)
{
}
/// <summary>
/// Custom triggers for scripts. This allows scripts to run custom events that are not part of the
/// standard battle flow.
@@ -555,7 +555,8 @@ public abstract class Script : IDeepCloneable
{
}
public virtual void ChangeAccuracy(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref int modifiedAccuracy)
public virtual void ChangeAccuracy(IExecutingMove executingMove, IPokemon target, byte hitIndex,
ref int modifiedAccuracy)
{
}
}

View File

@@ -16,7 +16,7 @@ public enum ScriptCategory
/// <see cref="IMoveChoice"/> and <see cref="IExecutingMove"/>
/// </summary>
Move = 0,
/// <summary>
/// A volatile script effect that is attached to a move choice.
/// </summary>
@@ -54,7 +54,7 @@ public enum ScriptCategory
/// A special script for weather, for use on battles.
/// </summary>
Weather = 7,
Terrain = 8,
/// <summary>

View File

@@ -38,11 +38,7 @@ public class ScriptContainer : IEnumerable<ScriptContainer>, IDeepCloneable
yield return this;
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
/// <summary>
/// Assigns a new script to this container. If there was a script already, it is removed.
@@ -65,7 +61,7 @@ public class ScriptContainer : IEnumerable<ScriptContainer>, IDeepCloneable
}
Script = null;
}
public void ClearWithoutRemoving()
{
Script = null;

View File

@@ -66,5 +66,4 @@ public static class ScriptExecution
itemScript.OnUse();
}
}
}

View File

@@ -44,16 +44,10 @@ public class ScriptIterator : IEnumerable<ScriptContainer>
}
}
}
/// <inheritdoc />
public IEnumerator<ScriptContainer> GetEnumerator()
{
return GetAsEnumerable().GetEnumerator();
}
/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public IEnumerator<ScriptContainer> GetEnumerator() => GetAsEnumerable().GetEnumerator();
/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

View File

@@ -38,7 +38,7 @@ public class ScriptResolver
{
script.OnInitialize(parameters);
}
return true;
}
@@ -51,7 +51,7 @@ public class ScriptResolver
{
return true;
}
var effect = item.BattleEffect;
if (effect == null)
{

View File

@@ -27,7 +27,7 @@ public interface IScriptSet : IEnumerable<ScriptContainer>
/// Gets a script from the set using its unique name.
/// </summary>
ScriptContainer? Get(StringKey scriptKey);
/// <summary>
/// Gets a script from the set using its type.
/// </summary>
@@ -155,8 +155,5 @@ public class ScriptSet : IScriptSet
/// <inheritdoc />
public IEnumerable<StringKey> GetScriptNames() =>
_scripts
.Select(x => x.Script)
.WhereNotNull()
.Select(s => s.Name);
_scripts.Select(x => x.Script).WhereNotNull().Select(s => s.Name);
}

View File

@@ -9,7 +9,7 @@ public interface IScriptSource
/// Gets an iterator over all scripts that are relevant for this source.
/// </summary>
ScriptIterator GetScripts();
/// <summary>
/// The number of scripts that are expected to be relevant for this source. This generally is
/// The number of its own scripts + the number of scripts for any parents.

View File

@@ -10,7 +10,7 @@ public static class StaticHelpers
/// may not be the same as the system time.
/// </summary>
public static Func<DateTimeOffset> DateTimeProvider { get; set; } = () => DateTimeOffset.Now;
/// <summary>
/// Get the current date and time.
/// </summary>