Style cleanup

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

View File

@ -47,7 +47,7 @@ public static class AbilityDataLoader
var objects = LoadAbilitiesData(stream);
if (objects == null)
throw new InvalidDataException("Ability data is empty.");
var abilities = objects.Select(x => DeserializeAbility(x.Key, x.Value));
foreach (var a in abilities)
library.Add(a);
@ -60,7 +60,7 @@ public static class AbilityDataLoader
var parameters = serialized.Parameters.ToDictionary(x => (StringKey)x.Key, x => x.Value.ToParameter());
StringKey? effectName = effect == null ? null! : new StringKey(effect);
var flags = serialized.Flags.Select(x => new StringKey(x)).ToImmutableHashSet();
var ability = new AbilityImpl(name, effectName, parameters, flags);

View File

@ -26,15 +26,15 @@ public static class ItemDataLoader
library.Add(i);
return library;
}
public delegate IItem ItemFactoryDelegate(SerializedItem serialized, StringKey name, ItemCategory type,
BattleItemCategory battleType, int price, ImmutableHashSet<StringKey> flags,
ISecondaryEffect? effect, ISecondaryEffect? battleTriggerEffect, byte flingPower);
BattleItemCategory battleType, int price, ImmutableHashSet<StringKey> flags, ISecondaryEffect? effect,
ISecondaryEffect? battleTriggerEffect, byte flingPower);
[PublicAPI]
public static ItemFactoryDelegate ItemConstructor { get; set; } = (_, name, type, battleType, price, flags, effect,
battleTriggerEffect, flingPower) =>
new ItemImpl(name, type, battleType, price, flags, effect, battleTriggerEffect, flingPower);
public static ItemFactoryDelegate ItemConstructor { get; set; } =
(_, name, type, battleType, price, flags, effect, battleTriggerEffect, flingPower) => new ItemImpl(name, type,
battleType, price, flags, effect, battleTriggerEffect, flingPower);
private static IItem DeserializeItem(SerializedItem serialized)
{

View File

@ -4,7 +4,7 @@ namespace PkmnLib.Dataloader;
internal static class JsonOptions
{
public static JsonSerializerOptions DefaultOptions => new JsonSerializerOptions()
public static JsonSerializerOptions DefaultOptions => new()
{
PropertyNameCaseInsensitive = true,
AllowTrailingCommas = true,

View File

@ -14,7 +14,6 @@ public class SerializedItem
public byte FlingPower { get; set; }
public SerializedMoveEffect? Effect { get; set; }
public SerializedMoveEffect? BattleEffect { get; set; }
[JsonExtensionData]
public Dictionary<string, JsonElement>? ExtensionData { get; set; }
[JsonExtensionData] public Dictionary<string, JsonElement>? ExtensionData { get; set; }
}

View File

@ -22,9 +22,8 @@ public class SerializedMove
public string Category { get; set; } = null!;
public string[] Flags { get; set; } = null!;
public SerializedMoveEffect? Effect { get; set; }
[JsonExtensionData]
public Dictionary<string, JsonElement>? ExtensionData { get; set; }
[JsonExtensionData] public Dictionary<string, JsonElement>? ExtensionData { get; set; }
}
public class SerializedMoveEffect

View File

@ -20,9 +20,8 @@ public class SerializedSpecies
public string[] Flags { get; set; } = [];
public Dictionary<string, SerializedForm> Formes { get; set; } = null!;
public SerializedEvolution[] Evolutions { get; set; } = [];
[JsonExtensionData]
public Dictionary<string, JsonElement>? ExtensionData { get; set; }
[JsonExtensionData] public Dictionary<string, JsonElement>? ExtensionData { get; set; }
}
public class SerializedForm
@ -38,9 +37,8 @@ public class SerializedForm
public bool IsMega { get; set; }
public SerializedMoves Moves { get; set; } = null!;
public string[] Flags { get; set; } = [];
[JsonExtensionData]
public Dictionary<string, JsonElement>? ExtensionData { get; set; }
[JsonExtensionData] public Dictionary<string, JsonElement>? ExtensionData { get; set; }
}
public class SerializedEvolution

View File

@ -26,11 +26,12 @@ public static class MoveDataLoader
return library;
}
public static Func<SerializedMove, StringKey, TypeIdentifier, MoveCategory, byte, byte, byte, MoveTarget, sbyte,
ISecondaryEffect?, IEnumerable<StringKey>, MoveDataImpl> MoveConstructor =
(serialized, name, moveType, category, basePower, accuracy, baseUsages, target, priority, secondaryEffect,
flags) => new MoveDataImpl(name, moveType, category, basePower, accuracy, baseUsages, target, priority,
secondaryEffect, flags);
public static
Func<SerializedMove, StringKey, TypeIdentifier, MoveCategory, byte, byte, byte, MoveTarget, sbyte,
ISecondaryEffect?, IEnumerable<StringKey>, MoveDataImpl> MoveConstructor =
(serialized, name, moveType, category, basePower, accuracy, baseUsages, target, priority, secondaryEffect,
flags) => new MoveDataImpl(name, moveType, category, basePower, accuracy, baseUsages, target, priority,
secondaryEffect, flags);
private static MoveDataImpl DeserializeMove(SerializedMove serialized, TypeLibrary typeLibrary)
{
@ -55,9 +56,8 @@ public static class MoveDataLoader
throw new InvalidDataException($"Target {target} is not a valid target.");
var secondaryEffect = effect.ParseEffect();
var move = MoveConstructor(serialized, serialized.Name, typeIdentifier, categoryEnum, power, accuracy, pp, targetEnum,
priority, secondaryEffect, flags.Select(x => (StringKey)x).ToImmutableHashSet());
var move = MoveConstructor(serialized, serialized.Name, typeIdentifier, categoryEnum, power, accuracy, pp,
targetEnum, priority, secondaryEffect, flags.Select(x => (StringKey)x).ToImmutableHashSet());
return move;
}
}

View File

@ -13,7 +13,7 @@ public static class NatureDataLoader
public static NatureLibrary LoadNatureLibrary(Stream stream)
{
var library = new NatureLibrary();
using var reader = new StreamReader(stream);
var header = reader.ReadLine();
if (header == null)
@ -31,7 +31,7 @@ public static class NatureDataLoader
var nature = values[0];
var increasedStat = values[1];
var decreasedStat = values[2];
var increasedModifier = 1.1f;
var decreasedModifier = 0.9f;
@ -46,7 +46,7 @@ public static class NatureDataLoader
decreasedStat = "Hp";
decreasedModifier = 1.0f;
}
if (!Enum.TryParse<Statistic>(increasedStat, out var increasedStatEnum))
throw new InvalidDataException($"Increased stat {increasedStat} is not a valid stat.");
if (!Enum.TryParse<Statistic>(decreasedStat, out var decreasedStatEnum))

View File

@ -20,10 +20,10 @@ public static class SpeciesDataLoader
var obj = JsonSerializer.Deserialize<JsonObject>(stream, JsonOptions.DefaultOptions);
if (obj == null)
throw new InvalidDataException("Species data is empty.");
return obj.Where(x => x.Key != "$schema")
.ToDictionary(x => x.Key, x => x.Value.Deserialize<SerializedSpecies>(JsonOptions.DefaultOptions));
return obj.Where(x => x.Key != "$schema").ToDictionary(x => x.Key,
x => x.Value.Deserialize<SerializedSpecies>(JsonOptions.DefaultOptions));
}
public static SpeciesLibrary LoadSpecies(Stream[] streams, IReadOnlyTypeLibrary typeLibrary)
{
var library = new SpeciesLibrary();
@ -35,7 +35,7 @@ public static class SpeciesDataLoader
library.Add(s);
return library;
}
public static SpeciesLibrary LoadSpecies(Stream stream, IReadOnlyTypeLibrary typeLibrary)
{
var library = new SpeciesLibrary();
@ -48,34 +48,37 @@ public static class SpeciesDataLoader
return library;
}
public static Func<SerializedSpecies, ushort, StringKey, float, StringKey, byte, byte, IReadOnlyDictionary<StringKey, IForm>,
IEnumerable<StringKey>, IReadOnlyList<IEvolution>, IEnumerable<StringKey>, SpeciesImpl> SpeciesConstructor =
(_, id, name, genderRate, growthRate, captureRate, baseHappiness, forms, flags, evolutionData,
eggGroups) =>
{
return new SpeciesImpl(id, name, genderRate, growthRate,
captureRate, baseHappiness, forms,
flags, evolutionData, eggGroups);
};
public static
Func<SerializedSpecies, ushort, StringKey, float, StringKey, byte, byte, IReadOnlyDictionary<StringKey, IForm>,
IEnumerable<StringKey>, IReadOnlyList<IEvolution>, IEnumerable<StringKey>, SpeciesImpl> SpeciesConstructor =
(_, id, name, genderRate, growthRate, captureRate, baseHappiness, forms, flags, evolutionData, eggGroups) =>
{
return new SpeciesImpl(id, name, genderRate, growthRate, captureRate, baseHappiness, forms, flags,
evolutionData, eggGroups);
};
private static SpeciesImpl DeserializeSpecies(SerializedSpecies serialized, IReadOnlyTypeLibrary typeLibrary)
{
var id = serialized.Id;
var genderRate = serialized.GenderRatio;
if (genderRate < -1.0 || genderRate > 100.0)
{
throw new InvalidDataException(
$"Gender rate for species {id} is invalid: {genderRate}. Must be between -1.0 and 100.0.");
}
if (serialized.EggCycles < 0)
{
throw new InvalidDataException(
$"Egg cycles for species {id} is invalid: {serialized.EggCycles}. Must be greater than or equal to 0.");
}
var forms = serialized.Formes.ToDictionary(x => (StringKey)x.Key,
x => DeserializeForm(x.Key, x.Value, typeLibrary));
var evolutions = serialized.Evolutions.Select(DeserializeEvolution).ToList();
var species = SpeciesConstructor(serialized, serialized.Id, serialized.Species, genderRate, serialized.GrowthRate,
serialized.CatchRate, serialized.BaseHappiness, forms,
var species = SpeciesConstructor(serialized, serialized.Id, serialized.Species, genderRate,
serialized.GrowthRate, serialized.CatchRate, serialized.BaseHappiness, forms,
serialized.Flags.Select(x => new StringKey(x)), evolutions, serialized.EggGroups.Select(x => (StringKey)x));
return species;
}
@ -85,11 +88,15 @@ public static class SpeciesDataLoader
if (form == null)
throw new ArgumentException("Form data is null.", nameof(form));
if (form.Height < 0.0)
{
throw new InvalidDataException(
$"Height for form {name} is invalid: {form.Height}. Must be greater than or equal to 0.0.");
}
if (form.Weight < 0.0)
{
throw new InvalidDataException(
$"Weight for form {name} is invalid: {form.Weight}. Must be greater than or equal to 0.0.");
}
var types = form.Types.Select(x =>
typeLibrary.TryGetTypeIdentifier(new StringKey(x), out var t)
? t
@ -105,25 +112,26 @@ public static class SpeciesDataLoader
{
var learnableMoves = new LearnableMovesImpl();
if (moves.LevelMoves != null)
{
foreach (var levelMove in moves.LevelMoves)
{
learnableMoves.AddLevelMove((byte)levelMove.Level, new StringKey(levelMove.Name));
}
}
if (moves.EggMoves != null)
{
foreach (var eggMove in moves.EggMoves)
{
learnableMoves.AddEggMove(new StringKey(eggMove));
}
}
return learnableMoves;
}
private static ImmutableStatisticSet<ushort> DeserializeStats(SerializedStats stats)
{
return new ImmutableStatisticSet<ushort>(stats.Hp, stats.Attack, stats.Defense, stats.SpecialAttack,
stats.SpecialDefense, stats.Speed);
}
private static ImmutableStatisticSet<ushort> DeserializeStats(SerializedStats stats) =>
new(stats.Hp, stats.Attack, stats.Defense, stats.SpecialAttack, stats.SpecialDefense, stats.Speed);
private static IEvolution DeserializeEvolution(SerializedEvolution evolution)
{
@ -223,8 +231,7 @@ public static class SpeciesDataLoader
{
Name = evolution.Data.AsObject()["type"]?.GetValue<string>() ??
throw new InvalidDataException("Type is null."),
Parameters = evolution.Data.AsObject()
.Where(x => x.Key != "type")
Parameters = evolution.Data.AsObject().Where(x => x.Key != "type")
.ToDictionary(x => new StringKey(x.Key), x => x.Value!.ToParameter()),
ToSpecies = evolution.Species,
},

View File

@ -42,8 +42,10 @@ public static class TypeDataLoader
{
var effectiveness = float.Parse(values[i]);
if (effectiveness < 0.0)
{
throw new InvalidDataException(
$"Effectiveness for {type} against {types[i]} is invalid: {effectiveness}. Must be greater than or equal to 0.0.");
}
library.SetEffectiveness(typeId, (TypeIdentifier)i, effectiveness);
}
}

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>

View File

@ -64,13 +64,14 @@ public class LookupGrowthRate : IGrowthRate
}
}
return (LevelInt)(_experienceTable.Length);
return (LevelInt)_experienceTable.Length;
}
/// <inheritdoc />
public uint CalculateExperience(LevelInt level)
{
if (level < 1) level = 1;
if (level < 1)
level = 1;
return level >= _experienceTable.Length ? _experienceTable[^1] : _experienceTable[level - 1];
}
}

View File

@ -105,10 +105,10 @@ public interface IItem : INamedValue
/// A set of arbitrary flags that can be set on the item.
/// </summary>
ImmutableHashSet<StringKey> Flags { get; }
ISecondaryEffect? Effect { get; }
ISecondaryEffect? BattleEffect { get; }
byte FlingPower { get; }
/// <summary>
@ -160,10 +160,6 @@ public class ItemImpl : IItem
/// <inheritdoc />
public byte FlingPower { get; }
/// <inheritdoc />
public bool HasFlag(string key)
{
return Flags.Contains(key);
}
public bool HasFlag(string key) => Flags.Contains(key);
}

View File

@ -7,13 +7,13 @@ namespace PkmnLib.Static.Libraries;
/// <summary>
/// A basic library for data types. Stores data both by name and by index.
/// </summary>
public abstract class DataLibrary<T> : IEnumerable<T>
where T : INamedValue
public abstract class DataLibrary<T> : IEnumerable<T> where T : INamedValue
{
/// <summary>
/// The underlying data storage.
/// </summary>
protected readonly Dictionary<StringKey, T> Data = new();
private readonly List<T> _values = [];
/// <summary>

View File

@ -12,24 +12,27 @@ public interface IReadOnlyGrowthRateLibrary : IEnumerable<IGrowthRate>
/// Tries to get a growth rate from the library. Returns false if the growth rate is not found.
/// </summary>
bool TryGet(StringKey key, [MaybeNullWhen(false)] out IGrowthRate value);
/// <summary>
/// Gets a random growth rate from the library.
/// </summary>
IGrowthRate GetRandom(IRandom random);
/// <summary>
/// Gets the amount of growth rates in the library.
/// </summary>
int Count { get; }
/// <summary>
/// Whether the library is empty.
/// </summary>
bool IsEmpty { get; }
/// <summary>
/// Calculates the experience for a given growth key name and a certain level.
/// </summary>
uint CalculateExperience(StringKey key, LevelInt level);
/// <summary>
/// Calculates the level for a given growth key name and a certain experience.
/// </summary>

View File

@ -6,7 +6,7 @@ namespace PkmnLib.Static.Libraries;
/// <summary>
/// The library for all items in the game.
/// </summary>
public interface IReadOnlyItemLibrary : IEnumerable<IItem>
public interface IReadOnlyItemLibrary : IEnumerable<IItem>
{
/// <summary>
/// Tries to get an item from the library. Returns false if the item is not found.

View File

@ -9,7 +9,7 @@ public record LibrarySettings
/// The maximum level a Pokémon can reach.
/// </summary>
public required LevelInt MaxLevel { get; init; }
/// <summary>
/// The chance of a Pokémon being shiny, as the denominator of a fraction, where the nominator
/// is 1. For example, if this is 1000, then the chance of a Pokémon being shiny is 1/1000.

View File

@ -13,14 +13,17 @@ public interface IReadOnlyMoveLibrary : IEnumerable<IMoveData>
/// Tries to get a move from the library. Returns false if the move is not found.
/// </summary>
bool TryGet(StringKey key, [MaybeNullWhen(false)] out IMoveData value);
/// <summary>
/// Gets a random move from the library.
/// </summary>
IMoveData GetRandom(IRandom random);
/// <summary>
/// The amount of moves in the library.
/// </summary>
int Count { get; }
/// <summary>
/// Whether the library is empty.
/// </summary>

View File

@ -13,7 +13,7 @@ public interface IReadOnlySpeciesLibrary : IEnumerable<ISpecies>
/// Tries to get a species from the library. Returns false if the species is not found.
/// </summary>
bool TryGet(StringKey key, [MaybeNullWhen(false)] out ISpecies value);
/// <summary>
/// Tried to get a species from the library by its national dex number. Returns false if the species is not found.
/// </summary>
@ -45,7 +45,7 @@ public interface IReadOnlySpeciesLibrary : IEnumerable<ISpecies>
public class SpeciesLibrary : DataLibrary<ISpecies>, IReadOnlySpeciesLibrary
{
private Dictionary<ISpecies, ISpecies> _preEvolutionCache = new();
/// <inheritdoc />
public bool TryGetById(int id, [MaybeNullWhen(false)] out ISpecies value)
{
@ -61,7 +61,7 @@ public class SpeciesLibrary : DataLibrary<ISpecies>, IReadOnlySpeciesLibrary
return preEvolution;
foreach (var s in this)
{
if (s.EvolutionData.All(e => e.ToSpecies != species.Name))
if (s.EvolutionData.All(e => e.ToSpecies != species.Name))
continue;
_preEvolutionCache[species] = s;
return s;

View File

@ -9,32 +9,37 @@ public interface IStaticLibrary
/// The miscellaneous settings for the library.
/// </summary>
LibrarySettings Settings { get; }
/// <summary>
/// All data for Pokémon species.
/// </summary>
IReadOnlySpeciesLibrary Species { get; }
/// <summary>
/// All data for Pokémon moves.
/// </summary>
IReadOnlyMoveLibrary Moves { get; }
/// <summary>
/// All data for Pokémon abilities.
/// </summary>
IReadOnlyAbilityLibrary Abilities { get; }
/// <summary>
/// All data for Pokémon types and their effectiveness.
/// </summary>
IReadOnlyTypeLibrary Types { get; }
/// <summary>
/// All data for Pokémon natures.
/// </summary>
IReadOnlyNatureLibrary Natures { get; }
/// <summary>
/// All data for Pokémon growth rates.
/// </summary>
IReadOnlyGrowthRateLibrary GrowthRates { get; }
/// <summary>
/// All data for Pokémon items.
/// </summary>
@ -45,7 +50,9 @@ public interface IStaticLibrary
public class StaticLibraryImpl : IStaticLibrary
{
/// <inheritdoc cref="StaticLibraryImpl" />
public StaticLibraryImpl(LibrarySettings settings, IReadOnlySpeciesLibrary species, IReadOnlyMoveLibrary moves, IReadOnlyAbilityLibrary abilities, IReadOnlyTypeLibrary types, IReadOnlyNatureLibrary natures, IReadOnlyGrowthRateLibrary growthRates, IReadOnlyItemLibrary items)
public StaticLibraryImpl(LibrarySettings settings, IReadOnlySpeciesLibrary species, IReadOnlyMoveLibrary moves,
IReadOnlyAbilityLibrary abilities, IReadOnlyTypeLibrary types, IReadOnlyNatureLibrary natures,
IReadOnlyGrowthRateLibrary growthRates, IReadOnlyItemLibrary items)
{
Settings = settings;
Species = species;

View File

@ -88,7 +88,7 @@ public class TypeLibrary : IReadOnlyTypeLibrary
/// </summary>
public TypeIdentifier RegisterType(StringKey name)
{
var id = new TypeIdentifier((byte)( _types.Count + 1));
var id = new TypeIdentifier((byte)(_types.Count + 1));
_types.Add(name, id);
_effectiveness.Add(Enumerable.Repeat(1.0f, _effectiveness.Count).ToList());
foreach (var list in _effectiveness)

View File

@ -11,12 +11,12 @@ public interface ISecondaryEffect
/// The chance in percentages that the effect triggers. When less than 0, the effect is always active.
/// </summary>
public float Chance { get; }
/// <summary>
/// The name of the effect.
/// </summary>
public StringKey Name { get; }
/// <summary>
/// Parameters for the effect.
/// </summary>

View File

@ -50,8 +50,7 @@ public class Nature(
Statistic increaseStat,
Statistic decreaseStat,
float increaseModifier,
float decreaseModifier)
: INature
float decreaseModifier) : INature
{
/// <inheritdoc />
public StringKey Name { get; } = name;
@ -79,8 +78,6 @@ public class Nature(
}
/// <inheritdoc />
public bool Equals(INature? other)
{
return other is not null && StringComparer.InvariantCultureIgnoreCase.Equals(Name, other.Name);
}
public bool Equals(INature? other) =>
other is not null && StringComparer.InvariantCultureIgnoreCase.Equals(Name, other.Name);
}

View File

@ -18,7 +18,7 @@ public interface IAbility : INamedValue
/// The parameters for the script effect of the ability.
/// </summary>
IReadOnlyDictionary<StringKey, object?> Parameters { get; }
bool HasFlag(StringKey key);
}
@ -26,7 +26,8 @@ public interface IAbility : INamedValue
public class AbilityImpl : IAbility
{
/// <inheritdoc cref="AbilityImpl" />
public AbilityImpl(StringKey name, StringKey? effect, IReadOnlyDictionary<StringKey, object?> parameters, ImmutableHashSet<StringKey> flags)
public AbilityImpl(StringKey name, StringKey? effect, IReadOnlyDictionary<StringKey, object?> parameters,
ImmutableHashSet<StringKey> flags)
{
Name = name;
Effect = effect;
@ -46,10 +47,7 @@ public class AbilityImpl : IAbility
public ImmutableHashSet<StringKey> Flags;
/// <inheritdoc />
public bool HasFlag(StringKey key)
{
return Flags.Contains(key);
}
public bool HasFlag(StringKey key) => Flags.Contains(key);
}
/// <summary>

View File

@ -95,8 +95,8 @@ public interface IForm : INamedValue
public class FormImpl : IForm
{
/// <inheritdoc cref="FormImpl" />
public FormImpl(StringKey name, float height, float weight, uint baseExperience,
IEnumerable<TypeIdentifier> types, ImmutableStatisticSet<ushort> baseStats, IEnumerable<StringKey> abilities,
public FormImpl(StringKey name, float height, float weight, uint baseExperience, IEnumerable<TypeIdentifier> types,
ImmutableStatisticSet<ushort> baseStats, IEnumerable<StringKey> abilities,
IEnumerable<StringKey> hiddenAbilities, ILearnableMoves moves, ImmutableHashSet<StringKey> flags)
{
Name = name;
@ -109,7 +109,7 @@ public class FormImpl : IForm
HiddenAbilities = [..hiddenAbilities];
Moves = moves;
Flags = flags;
if (Types.Count == 0)
throw new ArgumentException("A form must have at least one type.");
if (Abilities.Count == 0)
@ -163,20 +163,24 @@ public class FormImpl : IForm
for (var i = 0; i < Abilities.Count && i < 255; i++)
{
if (Abilities[i] == ability.Name)
{
return new AbilityIndex
{
IsHidden = false,
Index = (byte)i,
};
}
}
for (var i = 0; i < HiddenAbilities.Count && i < 255; i++)
{
if (HiddenAbilities[i] == ability.Name)
{
return new AbilityIndex
{
IsHidden = true,
Index = (byte)i,
};
}
}
return null;
}
@ -191,20 +195,11 @@ public class FormImpl : IForm
}
/// <inheritdoc />
public StringKey GetRandomAbility(IRandom rand)
{
return Abilities[rand.GetInt(Abilities.Count)];
}
public StringKey GetRandomAbility(IRandom rand) => Abilities[rand.GetInt(Abilities.Count)];
/// <inheritdoc />
public StringKey GetRandomHiddenAbility(IRandom rand)
{
return HiddenAbilities[rand.GetInt(HiddenAbilities.Count)];
}
public StringKey GetRandomHiddenAbility(IRandom rand) => HiddenAbilities[rand.GetInt(HiddenAbilities.Count)];
/// <inheritdoc />
public bool HasFlag(string key)
{
return Flags.Contains(key);
}
public bool HasFlag(string key) => Flags.Contains(key);
}

View File

@ -10,10 +10,12 @@ public enum Gender : byte
{
/// The Pokémon has no gender.
Genderless,
/// <summary>
/// The Pokémon is male.
/// </summary>
Male,
/// <summary>
/// The Pokémon is female.
/// </summary>

View File

@ -14,7 +14,7 @@ public interface ILearnableMoves
/// <param name="move">The move the Pokémon learns.</param>
/// <returns>Whether the move was added successfully.</returns>
void AddLevelMove(LevelInt level, StringKey move);
/// <summary>
/// Adds a new egg move the Pokémon can learn.
/// </summary>
@ -38,7 +38,7 @@ public interface ILearnableMoves
/// Gets a list of all moves a Pokémon can learn up to a specific level.
/// </summary>
IReadOnlyList<StringKey> GetLearnableMovesUpToLevel(LevelInt level);
/// <summary>
/// Gets all moves a Pokémon can learn by breeding.
/// </summary>
@ -50,9 +50,8 @@ public class LearnableMovesImpl : ILearnableMoves
{
private readonly Dictionary<LevelInt, List<StringKey>> _learnedByLevel = new();
private readonly HashSet<StringKey> _distinctLevelMoves = new();
private readonly List<StringKey> _eggMoves = new();
private readonly List<StringKey> _eggMoves = new();
/// <inheritdoc />
public void AddLevelMove(LevelInt level, StringKey move)
@ -81,10 +80,7 @@ public class LearnableMovesImpl : ILearnableMoves
}
/// <inheritdoc />
public IReadOnlyList<StringKey> GetDistinctLevelMoves()
{
return _distinctLevelMoves.ToList();
}
public IReadOnlyList<StringKey> GetDistinctLevelMoves() => _distinctLevelMoves.ToList();
/// <inheritdoc />
public IReadOnlyList<StringKey> GetLearnableMovesUpToLevel(LevelInt level)

View File

@ -50,7 +50,7 @@ public interface ISpecies : INamedValue
/// Gets a form by name.
/// </summary>
bool TryGetForm(StringKey id, [MaybeNullWhen(false)] out IForm form);
/// <summary>
/// Gets the form the Pokémon will have by default, if no other form is specified.
/// </summary>
@ -70,7 +70,7 @@ public interface ISpecies : INamedValue
/// The data regarding into which Pokémon this species can evolve, and how.
/// </summary>
IReadOnlyList<IEvolution> EvolutionData { get; }
/// <summary>
/// The egg groups the Pokémon belongs to.
/// </summary>
@ -124,14 +124,13 @@ public class SpeciesImpl : ISpecies
/// <inheritdoc />
public ImmutableHashSet<StringKey> Flags { get; }
/// <inheritdoc />
public IReadOnlyList<IEvolution> EvolutionData { get; }
/// <inheritdoc />
public ICollection<StringKey> EggGroups { get; }
/// <inheritdoc />
public bool TryGetForm(StringKey id, [MaybeNullWhen(false)] out IForm form) => Forms.TryGetValue(id, out form);

View File

@ -9,33 +9,38 @@ public enum Statistic : byte
/// Health Points determine how much damage a Pokémon can receive before fainting.
/// </summary>
Hp,
/// <summary>
/// Attack determines how much damage a Pokémon deals when using a physical attack.
/// </summary>
Attack,
/// <summary>
/// Defense determines how much damage a Pokémon receives when it is hit by a physical attack.
/// </summary>
Defense,
/// <summary>
/// Special Attack determines how much damage a Pokémon deals when using a special attack.
/// </summary>
SpecialAttack,
/// <summary>
/// Special Defense determines how much damage a Pokémon receives when it is hit by a special attack.
/// </summary>
SpecialDefense,
/// <summary>
/// Speed determines the order that a Pokémon can act in battle.
/// </summary>
Speed,
/// <summary>
/// Evasion determines the likelihood that a Pokémon will dodge an attack.
/// This is not part of base stats, but is a temporary stat boost.
/// </summary>
Evasion,
/// <summary>
/// Accuracy determines the likelihood that a Pokémon will hit an attack.
/// This is not part of base stats, but is a temporary stat boost.

View File

@ -8,8 +8,7 @@ namespace PkmnLib.Static;
/// A set of statistics that cannot be changed.
/// </summary>
/// <typeparam name="T">The size of the integer to be used</typeparam>
public record ImmutableStatisticSet<T>
where T : struct
public record ImmutableStatisticSet<T> where T : struct
{
/// <summary>
/// The health points stat value.
@ -51,7 +50,7 @@ public record ImmutableStatisticSet<T>
SpecialDefense = specialDefense;
Speed = speed;
}
public ImmutableStatisticSet(ImmutableStatisticSet<T> set)
{
Hp = set.Hp;
@ -84,8 +83,7 @@ public record ImmutableStatisticSet<T>
/// A set of statistics that can be changed.
/// </summary>
/// <typeparam name="T"></typeparam>
public record StatisticSet<T> : ImmutableStatisticSet<T>, IEnumerable<T>, IDeepCloneable
where T : struct
public record StatisticSet<T> : ImmutableStatisticSet<T>, IEnumerable<T>, IDeepCloneable where T : struct
{
/// <inheritdoc cref="StatisticSet{T}"/>
public StatisticSet() : base(default, default, default, default, default, default)
@ -97,7 +95,7 @@ public record StatisticSet<T> : ImmutableStatisticSet<T>, IEnumerable<T>, IDeepC
defense, specialAttack, specialDefense, speed)
{
}
public StatisticSet(StatisticSet<T> set) : base(set)
{
}
@ -175,16 +173,12 @@ public record StatisticSet<T> : ImmutableStatisticSet<T>, IEnumerable<T>, IDeepC
return true;
}
protected virtual T GetUnknownStat(Statistic stat)
{
throw new ArgumentException($"Invalid statistic {stat}");
}
protected virtual T GetUnknownStat(Statistic stat) => throw new ArgumentException($"Invalid statistic {stat}");
protected virtual void SetUnknownStat(Statistic stat, T value)
{
throw new ArgumentException($"Invalid statistic {stat}");
}
/// <summary>
/// Decreases a statistic in the set by a value.
@ -231,17 +225,13 @@ public record StatisticSet<T> : ImmutableStatisticSet<T>, IEnumerable<T>, IDeepC
}
/// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
/// <summary>
/// A set of statistics that can be changed, but are clamped to a minimum and maximum value.
/// </summary>
public abstract record ClampedStatisticSet<T> : StatisticSet<T>
where T : struct, IComparable<T>
public abstract record ClampedStatisticSet<T> : StatisticSet<T> where T : struct, IComparable<T>
{
/// <inheritdoc />
[SuppressMessage("ReSharper", "VirtualMemberCallInConstructor")]
@ -257,7 +247,7 @@ public abstract record ClampedStatisticSet<T> : StatisticSet<T>
SpecialDefense = Clamp(SpecialDefense, Min, Max);
Speed = Clamp(Speed, Min, Max);
}
protected ClampedStatisticSet(ClampedStatisticSet<T> set) : base(set)
{
}
@ -322,13 +312,15 @@ public record StatBoostStatisticSet : ClampedStatisticSet<sbyte>
protected override sbyte Max => 6;
private sbyte _evasion;
public sbyte Evasion
{
get => _evasion;
set => _evasion = Clamp(value, Min, Max);
}
private sbyte _accuracy;
public sbyte Accuracy
{
get => _accuracy;
@ -403,13 +395,13 @@ public record IndividualValueStatisticSet : ClampedStatisticSet<byte>
public IndividualValueStatisticSet() : base(0, 0, 0, 0, 0, 0)
{
}
/// <inheritdoc cref="IndividualValueStatisticSet"/>
public IndividualValueStatisticSet(byte hp, byte attack, byte defense, byte specialAttack, byte specialDefense,
byte speed) : base(hp, attack, defense, specialAttack, specialDefense, speed)
{
}
public IndividualValueStatisticSet(IndividualValueStatisticSet ivs) : base(ivs)
{
}
@ -436,7 +428,7 @@ public record EffortValueStatisticSet : ClampedStatisticSet<byte>
byte speed) : base(hp, attack, defense, specialAttack, specialDefense, speed)
{
}
public EffortValueStatisticSet(EffortValueStatisticSet evs) : base(evs)
{
}

View File

@ -25,11 +25,9 @@ public static class DeepCloneHandler
/// Recursive references will be handled correctly, and will only be cloned once, to prevent infinite loops and invalid
/// references.
/// </summary>
public static T DeepClone<T>(this T? obj, Dictionary<(Type, int), object>? objects = null) where T : IDeepCloneable
{
return (T)DeepClone((object?)obj, objects)!;
}
public static T DeepClone<T>(this T? obj, Dictionary<(Type, int), object>? objects = null)
where T : IDeepCloneable => (T)DeepClone((object?)obj, objects)!;
private static object? DeepClone(this object? obj, Dictionary<(Type, int), object>? objects = null)
{
if (obj == null)
@ -41,7 +39,7 @@ public static class DeepCloneHandler
// We use GetUninitializedObject to create an object without calling the constructor. This is necessary to prevent
// side effects from the constructor, and to not require a parameterless constructor.
var newObj = FormatterServices.GetUninitializedObject(type)!;
// If the objects dictionary is null, we create a new one. We use this dictionary to keep track of objects that have
// already been cloned, so we can re-use them instead of cloning them again. This is necessary to prevent infinite
// loops and invalid references.
@ -74,7 +72,7 @@ public static class DeepCloneHandler
// If the object is a value type or a string, we can just return it.
if (type.IsValueType || type == typeof(string))
return obj;
// If the object is marked as deep cloneable, we will clone it.
if (type.GetInterface(nameof(IDeepCloneable)) != null || ExternalDeepCloneTypes.Contains(type))
{
@ -91,8 +89,9 @@ public static class DeepCloneHandler
var array = (Array)obj;
var newArray = Array.CreateInstance(type.GetElementType()!, array.Length);
for (var i = 0; i < array.Length; i++)
newArray.SetValue(DeepCloneInternal(array.GetValue(i), type.GetElementType()!, objects),
i);
{
newArray.SetValue(DeepCloneInternal(array.GetValue(i), type.GetElementType()!, objects), i);
}
return newArray;
}
@ -115,9 +114,10 @@ public static class DeepCloneHandler
var dictionary = (IDictionary)obj;
var newDictionary = (IDictionary)Activator.CreateInstance(type);
foreach (DictionaryEntry entry in dictionary)
newDictionary.Add(
DeepCloneInternal(entry.Key, type.GetGenericArguments()[0], objects)!,
{
newDictionary.Add(DeepCloneInternal(entry.Key, type.GetGenericArguments()[0], objects)!,
DeepCloneInternal(entry.Value, type.GetGenericArguments()[1], objects));
}
return newDictionary;
}
}
@ -133,8 +133,7 @@ public static class DeepCloneHandler
/// This method is thread safe, and will only create the expressions once for each type. It returns compiled expressions for
/// each field in the type, so that we can get high performance deep cloning.
/// </remarks>
private static (Func<object, object?> getter, Action<object, object?> setter)[]
GetDeepCloneExpressions(Type type)
private static (Func<object, object?> getter, Action<object, object?> setter)[] GetDeepCloneExpressions(Type type)
{
// We use a lock here to prevent multiple threads from trying to create the expressions at the same time.
lock (DeepCloneExpressions)

View File

@ -2,7 +2,8 @@ namespace PkmnLib.Static.Utils;
public static class DictionaryHelpers
{
public static TValue GetOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, TValue defaultValue)
public static TValue GetOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key,
TValue defaultValue)
{
if (dictionary.TryGetValue(key, out var value))
return value;

View File

@ -10,7 +10,7 @@ public static class EnumerableHelpers
/// </summary>
public static IEnumerable<T> WhereNotNull<T>(this IEnumerable<T?> enumerable) where T : class =>
enumerable.Where(x => x is not null)!;
/// <summary>
/// Finds the index of a value in an enumerable. Returns -1 if the value is not found.
/// </summary>

View File

@ -1,4 +1,3 @@
namespace PkmnLib.Static.Utils.Errors;
/// <summary>

View File

@ -13,7 +13,7 @@ public static class NumericHelpers
var result = value * multiplier;
return result > byte.MaxValue ? byte.MaxValue : (byte)result;
}
/// <summary>
/// Multiplies two values. If this overflows, returns <see cref="byte.MaxValue"/>.
/// </summary>
@ -22,7 +22,7 @@ public static class NumericHelpers
var result = value * multiplier;
return result > byte.MaxValue ? byte.MaxValue : (byte)result;
}
/// <summary>
/// Multiplies two values. If this overflows, returns <see cref="sbyte.MaxValue"/>.
/// </summary>
@ -31,7 +31,7 @@ public static class NumericHelpers
var result = value * multiplier;
return result > sbyte.MaxValue ? sbyte.MaxValue : (sbyte)result;
}
/// <summary>
/// Multiplies two values. If this overflows, returns <see cref="ushort.MaxValue"/>.
/// </summary>
@ -40,7 +40,7 @@ public static class NumericHelpers
var result = value * multiplier;
return result > ushort.MaxValue ? ushort.MaxValue : (ushort)result;
}
/// <summary>
/// Multiplies two values. If this overflows, returns <see cref="short.MaxValue"/>.
/// </summary>
@ -49,11 +49,10 @@ public static class NumericHelpers
var result = value * multiplier;
return result > short.MaxValue ? short.MaxValue : (short)result;
}
public static uint MultiplyOrMax(this uint value, uint multiplier)
{
var result = (ulong)value * multiplier;
return result > uint.MaxValue ? uint.MaxValue : (uint)result;
}
}

View File

@ -21,19 +21,19 @@ public interface IRandom
/// <param name="max">The maximum value (exclusive).</param>
/// <returns>A random integer that is greater than or equal to 0 and less than max.</returns>
public int GetInt(int max);
/// <summary>
/// Get a random integer between 0 and <see cref="int.MaxValue"/>.
/// </summary>
/// <returns>A random integer that is greater than or equal to 0 and less than <see cref="int.MaxValue"/>.</returns>
public int GetInt();
/// <summary>
/// Get a random float that is greater than or equal to 0.0 and less than 1.0.
/// </summary>
/// <returns>A random float that is greater than or equal to 0.0 and less than 1.0.</returns>
public float GetFloat();
/// <summary>
/// Get a random float that is greater than or equal to min and less than max.
/// </summary>
@ -41,7 +41,7 @@ public interface IRandom
/// <param name="max">The maximum value (exclusive).</param>
/// <returns>A random float that is greater than or equal to min and less than max.</returns>
public float GetFloat(float min, float max);
/// <summary>
/// Get a random boolean. 50% chance of being true.
/// </summary>

View File

@ -22,14 +22,12 @@ public readonly record struct StringKey
/// Converts a <see cref="StringKey"/> to a <see cref="string"/>.
/// </summary>
public static implicit operator string(StringKey key) => key._key;
/// <summary>
/// Converts a <see cref="string"/> to a <see cref="StringKey"/>.
/// </summary>
public static implicit operator StringKey(string key)
{
return string.IsNullOrWhiteSpace(key) ? default : new StringKey(key);
}
public static implicit operator StringKey(string key) =>
string.IsNullOrWhiteSpace(key) ? default : new StringKey(key);
/// <inheritdoc cref="string.ToString()"/>
public override string ToString() => _key.ToLowerInvariant();

View File

@ -10,12 +10,9 @@ public class MoveDataTests
public record TestCaseData(IDynamicLibrary Library, IMoveData Move)
{
/// <inheritdoc />
public override string ToString()
{
return Move.Name + " has valid scripts";
}
public override string ToString() => Move.Name + " has valid scripts";
}
public static IEnumerable<TestCaseData> AllMovesHaveValidScriptsData()
{
var library = LibraryHelpers.LoadLibrary();
@ -27,9 +24,8 @@ public class MoveDataTests
yield return new TestCaseData(library, move);
}
}
[Test]
[MethodDataSource(nameof(AllMovesHaveValidScriptsData))]
[Test, MethodDataSource(nameof(AllMovesHaveValidScriptsData))]
public async Task AllMoveEffectsHaveValidScripts(TestCaseData test)
{
if (test.Move.SecondaryEffect == null)
@ -43,7 +39,8 @@ public class MoveDataTests
}
catch (Exception e)
{
throw new AggregateException($"Failed to resolve script for move {test.Move.Name} with effect {scriptName}", e);
throw new AggregateException($"Failed to resolve script for move {test.Move.Name} with effect {scriptName}",
e);
}
}
}

View File

@ -28,7 +28,7 @@ public class SpeciesDataloaderTests
typeLibrary.RegisterType("Dark");
typeLibrary.RegisterType("Steel");
typeLibrary.RegisterType("Fairy");
var library = SpeciesDataLoader.LoadSpecies(file, typeLibrary);
await Assert.That(library).IsNotNull();
}

View File

@ -10,12 +10,12 @@ public class TypeDataloaderTests
await using var file = File.Open("Data/Types.csv", FileMode.Open, FileAccess.Read, FileShare.Read);
var library = TypeDataLoader.LoadTypeLibrary(file);
await Assert.That(library).IsNotNull();
var fire = library.TryGetTypeIdentifier("Fire", out var fireId);
await Assert.That(fire).IsTrue();
var grass = library.TryGetTypeIdentifier("Grass", out var grassId);
await Assert.That(grass).IsTrue();
var fireEffectiveness = library.GetSingleEffectiveness(fireId, grassId);
await Assert.That(fireEffectiveness).IsEqualTo(2.0f);
}

View File

@ -1,4 +1,3 @@
global using TUnit;
global using FluentAssertions;
global using LevelInt = byte;

View File

@ -26,8 +26,7 @@ public class IntegrationTestRunner
}
}
[Test]
[MethodDataSource(nameof(TestCases))]
[Test, MethodDataSource(nameof(TestCases))]
public async Task RunIntegrationTest(IntegrationTestModel test)
{
var library = LibraryHelpers.LoadLibrary();
@ -40,11 +39,10 @@ public class IntegrationTestRunner
var pokemon = x.Pokemon[index];
await Assert.That(library.StaticLibrary.Species.TryGet(pokemon.Species, out var species)).IsTrue();
var mon = new PokemonImpl(library, species!, species!.GetDefaultForm(), new AbilityIndex
{
IsHidden = false,
Index = 0,
},
pokemon.Level, 0, Gender.Genderless, 0, "hardy");
{
IsHidden = false,
Index = 0,
}, pokemon.Level, 0, Gender.Genderless, 0, "hardy");
foreach (var move in pokemon.Moves)
{
mon.LearnMove(move, MoveLearnMethod.Unknown, 255);
@ -56,7 +54,7 @@ public class IntegrationTestRunner
return new BattlePartyImpl(party, x.Indices.Select(y => new ResponsibleIndex(y[0], y[1])).ToArray());
}).ProcessOneAtATime().GetResultsAsync();
var battle = new BattleImpl(library, parties, test.BattleSetup.CanFlee, test.BattleSetup.NumberOfSides,
test.BattleSetup.PositionsPerSide, randomSeed: test.BattleSetup.Seed);
test.BattleSetup.PositionsPerSide, test.BattleSetup.Seed);
foreach (var action in test.Actions)
{

View File

@ -37,5 +37,5 @@ public static class LibraryHelpers
}),
]);
return dynamicLibrary;
}
}
}

View File

@ -7,11 +7,8 @@ using JsonSerializer = System.Text.Json.JsonSerializer;
namespace PkmnLib.Tests.Integration.Models;
[JsonDerivedType(typeof(SetPokemonAction), "setPokemon")]
[JsonDerivedType(typeof(SetMoveChoiceAction), "setMoveChoice")]
[JsonDerivedType(typeof(SetPassChoiceAction), "setPassChoice")]
[JsonDerivedType(typeof(AssertAction), "assert")]
[JsonDerivedType(typeof(SetPokemonAction), "setPokemon"), JsonDerivedType(typeof(SetMoveChoiceAction), "setMoveChoice"),
JsonDerivedType(typeof(SetPassChoiceAction), "setPassChoice"), JsonDerivedType(typeof(AssertAction), "assert")]
public abstract class IntegrationTestAction
{
public abstract Task Execute(IBattle battle);
@ -36,7 +33,6 @@ public class SetMoveChoiceAction : IntegrationTestAction
public string Move { get; set; } = null!;
public List<byte> Target { get; set; } = null!;
/// <inheritdoc />
public override async Task Execute(IBattle battle)
{
@ -73,7 +69,7 @@ public class AssertAction : IntegrationTestAction
{
var list = battle.Path(Value).ToList();
var value = list.Count == 1 ? list[0] : list;
var serialized = JsonSerializer.Serialize(value);
await Assert.That(serialized).IsEqualTo(Expected.ToJsonString());
}

View File

@ -8,10 +8,7 @@ public class IntegrationTestModel
public IntegrationTestAction[] Actions { get; set; } = null!;
/// <inheritdoc />
public override string ToString()
{
return Name;
}
public override string ToString() => Name;
}
public class IntegrationTestBattleSetup

View File

@ -11,8 +11,7 @@ namespace PkmnLib.Tests.Static;
public class DeepCloneTests
{
[SuppressMessage("ReSharper", "UnusedMember.Local")]
[SuppressMessage("ReSharper", "ValueParameterNotUsed")]
[SuppressMessage("ReSharper", "UnusedMember.Local"), SuppressMessage("ReSharper", "ValueParameterNotUsed")]
private class TestClass : IDeepCloneable
{
public int Value { get; set; }
@ -70,8 +69,7 @@ public class DeepCloneTests
var clone = obj.DeepClone();
await Assert.That(clone).IsNotEqualTo(obj);
var clonePrivateField =
clone.GetType().GetField("_privateField", BindingFlags.NonPublic | BindingFlags.Instance)!
.GetValue(clone);
clone.GetType().GetField("_privateField", BindingFlags.NonPublic | BindingFlags.Instance)!.GetValue(clone);
await Assert.That(clonePrivateField).IsEqualTo(1);
}
@ -93,28 +91,24 @@ public class DeepCloneTests
await Assert.That(library.StaticLibrary.Species.TryGet("bulbasaur", out var bulbasaur)).IsTrue();
await Assert.That(library.StaticLibrary.Species.TryGet("charmander", out var charmander)).IsTrue();
var party1 = new PokemonParty(6);
party1.SwapInto(new PokemonImpl(library, bulbasaur!,
bulbasaur!.GetDefaultForm(), new AbilityIndex
{
IsHidden = false,
Index = 0,
}, 50, 0,
Gender.Male, 0, "hardy"), 0);
party1.SwapInto(new PokemonImpl(library, bulbasaur!, bulbasaur!.GetDefaultForm(), new AbilityIndex
{
IsHidden = false,
Index = 0,
}, 50, 0, Gender.Male, 0, "hardy"), 0);
var party2 = new PokemonParty(6);
party2.SwapInto(new PokemonImpl(library, charmander!,
charmander!.GetDefaultForm(), new AbilityIndex
{
IsHidden = false,
Index = 0,
}, 50, 0,
Gender.Male, 0, "hardy"), 0);
party2.SwapInto(new PokemonImpl(library, charmander!, charmander!.GetDefaultForm(), new AbilityIndex
{
IsHidden = false,
Index = 0,
}, 50, 0, Gender.Male, 0, "hardy"), 0);
var parties = new[]
{
new BattlePartyImpl(party1, [new ResponsibleIndex(0, 0)]),
new BattlePartyImpl(party2, [new ResponsibleIndex(1, 0)]),
};
var battle = new BattleImpl(library, parties, false, 2, 3, randomSeed: 0);
var battle = new BattleImpl(library, parties, false, 2, 3, 0);
battle.Sides[0].SwapPokemon(0, party1[0]);
battle.Sides[1].SwapPokemon(0, party2[0]);
party1[0]!.ChangeStatBoost(Statistic.Defense, 2, true);

View File

@ -10,21 +10,23 @@ public class GrowthRateTests
Action act = () => _ = new LookupGrowthRate("Test", []);
act.Should().Throw<ArgumentException>().WithMessage("Experience table must have at least one entry.");
}
[Test]
public void NonZeroLookupGrowthRateTestShouldThrowArgumentException()
{
Action act = () => _ = new LookupGrowthRate("Test", [1]);
act.Should().Throw<ArgumentException>().WithMessage("Experience table must start at 0.");
}
[Test]
public void TooLargeLookupGrowthRateTestShouldThrowArgumentException()
{
Action act = () => _ = new LookupGrowthRate("Test", Enumerable.Range(0, LevelInt.MaxValue + 1).Select(i => (uint)i));
act.Should().Throw<ArgumentException>().WithMessage($"Experience table may have at most {LevelInt.MaxValue} entries.");
Action act = () =>
_ = new LookupGrowthRate("Test", Enumerable.Range(0, LevelInt.MaxValue + 1).Select(i => (uint)i));
act.Should().Throw<ArgumentException>()
.WithMessage($"Experience table may have at most {LevelInt.MaxValue} entries.");
}
[Test]
public void LookupGrowthRateTest()
{
@ -35,7 +37,7 @@ public class GrowthRateTests
growthRate.CalculateLevel(3).Should().Be(4);
growthRate.CalculateLevel(4).Should().Be(5);
growthRate.CalculateLevel(5).Should().Be(6);
growthRate.CalculateExperience(1).Should().Be(0);
growthRate.CalculateExperience(2).Should().Be(1);
growthRate.CalculateExperience(3).Should().Be(2);

View File

@ -15,8 +15,7 @@ public class StringKeyTests
yield return () => ("TeSt", "tesv", false);
}
[Test]
[MethodDataSource(nameof(StringKeyEqualityTestCases))]
[Test, MethodDataSource(nameof(StringKeyEqualityTestCases))]
public async Task StringKeyEqualityTest(string k1, string k2, bool expected)
{
var sk1 = new StringKey(k1);
@ -24,8 +23,7 @@ public class StringKeyTests
await Assert.That(sk1 == sk2).IsEqualTo(expected);
}
[Test]
[MethodDataSource(nameof(StringKeyEqualityTestCases))]
[Test, MethodDataSource(nameof(StringKeyEqualityTestCases))]
public async Task HashCodeEqualityTest(string k1, string k2, bool expected)
{
var sk1 = new StringKey(k1);
@ -33,8 +31,7 @@ public class StringKeyTests
await Assert.That(sk1.GetHashCode() == sk2.GetHashCode()).IsEqualTo(expected);
}
[Test]
[MethodDataSource(nameof(StringKeyEqualityTestCases))]
[Test, MethodDataSource(nameof(StringKeyEqualityTestCases))]
public async Task HashSetEqualityTest(string k1, string k2, bool expected)
{
var sk1 = new StringKey(k1);

View File

@ -27,21 +27,19 @@ public class DamageCalculatorTests
// Imagine a level 75 Glaceon
attacker.Setup(x => x.Level).Returns(75);
// with an effective Attack stat of 123
attacker.Setup(x => x.BoostedStats).Returns(new StatisticSet<uint>(
1, 123, 1, 1, 1, 1));
attacker.Setup(x => x.BoostedStats).Returns(new StatisticSet<uint>(1, 123, 1, 1, 1, 1));
// We use 10 as the Ice type
attacker.Setup(x => x.Types).Returns([new TypeIdentifier(10)]);
var defender = new Mock<IPokemon>();
// a Garchomp with an effective Defense stat of 163
defender.Setup(x => x.BoostedStats).Returns(new StatisticSet<uint>(
1, 1, 163, 1, 1, 1));
defender.Setup(x => x.BoostedStats).Returns(new StatisticSet<uint>(1, 1, 163, 1, 1, 1));
defender.Setup(x => x.GetScripts()).Returns(new ScriptIterator([]));
var useMove = new Mock<IMoveData>();
// Ice Fang (an Ice-type physical move with a power of 65)
useMove.Setup(x => x.Category).Returns(MoveCategory.Physical);
var damageCalculator = new Gen7DamageCalculator(false);
var executingMove = new Mock<IExecutingMove>();
executingMove.Setup(x => x.UseMove).Returns(useMove.Object);
@ -54,7 +52,7 @@ public class DamageCalculatorTests
hit.Setup(x => x.Type).Returns(new TypeIdentifier(10));
// has a double weakness to the move's Ice type
hit.Setup(x => x.Effectiveness).Returns(4.0f);
var damage = damageCalculator.GetDamage(executingMove.Object, defender.Object, 0, hit.Object);
// That means Ice Fang will do between 168 and 196 HP damage, depending on luck.
// Note that we are testing deterministic damage, so we expect the maximum damage.

View File

@ -23,7 +23,7 @@ public class AcrobaticsTests
// Assert
await Assert.That(basePower).IsEqualTo((byte)20);
}
[Test]
public async Task ChangeBasePower_UserHoldingItem_BasePowerUnchanged()
{
@ -59,5 +59,4 @@ public class AcrobaticsTests
// Assert
await Assert.That(basePower).IsEqualTo(byte.MaxValue);
}
}

Some files were not shown because too many files have changed in this diff Show More