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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -15,7 +15,7 @@ public class SpeciesChangeEvent : IEventData
Species = species; Species = species;
Form = form; Form = form;
} }
/// <inheritdoc /> /// <inheritdoc />
public EventBatchId BatchId { get; init; } 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. /// Calculate all the flat stats of a Pokemon, disregarding stat boosts.
/// </summary> /// </summary>
void CalculateFlatStats(IPokemon pokemon, StatisticSet<uint> stats); void CalculateFlatStats(IPokemon pokemon, StatisticSet<uint> stats);
/// <summary> /// <summary>
/// Calculate a single flat stat of a Pokemon, disregarding stat boosts. /// Calculate a single flat stat of a Pokemon, disregarding stat boosts.
/// </summary> /// </summary>
uint CalculateFlatStat(IPokemon pokemon, Statistic stat); uint CalculateFlatStat(IPokemon pokemon, Statistic stat);
/// <summary> /// <summary>
/// Calculate all the boosted stats of a Pokemon, including stat boosts. /// Calculate all the boosted stats of a Pokemon, including stat boosts.
/// </summary> /// </summary>
void CalculateBoostedStats(IPokemon pokemon, StatisticSet<uint> stats); void CalculateBoostedStats(IPokemon pokemon, StatisticSet<uint> stats);
/// <summary> /// <summary>
/// Calculate a single boosted stat of a Pokemon, including stat boosts. /// Calculate a single boosted stat of a Pokemon, including stat boosts.
/// </summary> /// </summary>
uint CalculateBoostedStat(IPokemon pokemon, Statistic stat); uint CalculateBoostedStat(IPokemon pokemon, Statistic stat);
/// <summary> /// <summary>
/// Calculates the accuracy for a move, taking into account any accuracy modifiers. /// Calculates the accuracy for a move, taking into account any accuracy modifiers.
/// </summary> /// </summary>

View File

@ -15,8 +15,8 @@ public record struct CaptureResult
public bool IsCaught { get; init; } public bool IsCaught { get; init; }
public int Shakes { get; init; } public int Shakes { get; init; }
public bool CriticalCapture { 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 public interface ICaptureLibrary

View File

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

View File

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

View File

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

View File

@ -23,7 +23,7 @@ public class BattleChoiceQueue : IDeepCloneable
{ {
_choices = choices; _choices = choices;
} }
/// <summary> /// <summary>
/// Dequeues the next turn choice to be executed. This gives back the choice and sets it to null in the queue. It /// 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. /// also increments the internal index.
@ -40,18 +40,17 @@ public class BattleChoiceQueue : IDeepCloneable
LastRanChoice = choice; LastRanChoice = choice;
return choice; return choice;
} }
/// <summary> /// <summary>
/// This reads what the next choice to execute will be, without modifying state. /// This reads what the next choice to execute will be, without modifying state.
/// </summary> /// </summary>
public ITurnChoice? Peek() => _currentIndex >= _choices.Length ? null : _choices[_currentIndex]; public ITurnChoice? Peek() => _currentIndex >= _choices.Length ? null : _choices[_currentIndex];
/// <summary> /// <summary>
/// Checks if there are any more choices to execute. /// Checks if there are any more choices to execute.
/// </summary> /// </summary>
public bool HasNext() => _currentIndex < _choices.Length; public bool HasNext() => _currentIndex < _choices.Length;
/// <summary> /// <summary>
/// This resorts the yet to be executed choices. This can be useful for dealing with situations /// 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 /// 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.User.RunScriptHook(script => script.ChangeSpeed(choice, ref speed));
choice.Speed = speed; choice.Speed = speed;
} }
// We only sort the choices that are left // We only sort the choices that are left
Array.Sort(_choices, currentIndex, length - currentIndex, TurnChoiceComparer.Instance!); Array.Sort(_choices, currentIndex, length - currentIndex, TurnChoiceComparer.Instance!);
} }
@ -99,8 +98,9 @@ public class BattleChoiceQueue : IDeepCloneable
_choices[_currentIndex] = choice; _choices[_currentIndex] = choice;
return true; return true;
} }
internal IReadOnlyList<ITurnChoice?> GetChoices() => _choices; 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 chosenMove = moveChoice.ChosenMove;
var moveData = chosenMove.MoveData; var moveData = chosenMove.MoveData;
var moveDataName = moveData.Name; var moveDataName = moveData.Name;
moveChoice.RunScriptHook(x => x.ChangeMove(moveChoice, ref moveDataName)); moveChoice.RunScriptHook(x => x.ChangeMove(moveChoice, ref moveDataName));
if (moveData.Name != moveDataName) if (moveData.Name != moveDataName)
@ -31,11 +31,11 @@ internal static class MoveTurnExecutor
moveChoice.Script.Set(script); moveChoice.Script.Set(script);
} }
} }
} }
var targetType = moveData.Target; 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)); moveChoice.RunScriptHook(x => x.ChangeTargets(moveChoice, ref targets));
byte numberOfHits = 1; byte numberOfHits = 1;
@ -46,7 +46,7 @@ internal static class MoveTurnExecutor
} }
var executingMove = new ExecutingMoveImpl(targets, numberOfHits, chosenMove, moveData, moveChoice); var executingMove = new ExecutingMoveImpl(targets, numberOfHits, chosenMove, moveData, moveChoice);
var prevented = false; var prevented = false;
executingMove.RunScriptHook(x => x.PreventMove(executingMove, ref prevented)); executingMove.RunScriptHook(x => x.PreventMove(executingMove, ref prevented));
if (prevented) if (prevented)
@ -56,9 +56,9 @@ internal static class MoveTurnExecutor
// TODO: Modify the PP used by the move. // TODO: Modify the PP used by the move.
if (!executingMove.ChosenMove.TryUse(ppUsed)) if (!executingMove.ChosenMove.TryUse(ppUsed))
return; return;
battle.EventHook.Invoke(new MoveUseEvent(executingMove)); battle.EventHook.Invoke(new MoveUseEvent(executingMove));
var failed = false; var failed = false;
executingMove.RunScriptHook(x => x.FailMove(executingMove, ref failed)); executingMove.RunScriptHook(x => x.FailMove(executingMove, ref failed));
if (failed) if (failed)
@ -66,12 +66,12 @@ internal static class MoveTurnExecutor
// TODO: fail handling // TODO: fail handling
return; return;
} }
var stopped = false; var stopped = false;
executingMove.RunScriptHook(x => x.StopBeforeMove(executingMove, ref stopped)); executingMove.RunScriptHook(x => x.StopBeforeMove(executingMove, ref stopped));
if (stopped) if (stopped)
return; return;
executingMove.RunScriptHook(x => x.OnBeforeMove(executingMove)); executingMove.RunScriptHook(x => x.OnBeforeMove(executingMove));
foreach (var target in targets.WhereNotNull()) foreach (var target in targets.WhereNotNull())
{ {
@ -88,7 +88,7 @@ internal static class MoveTurnExecutor
// TODO: fail handling // TODO: fail handling
return; return;
} }
var isInvulnerable = false; var isInvulnerable = false;
target.RunScriptHook(x => x.IsInvulnerableToMove(executingMove, target, ref isInvulnerable)); target.RunScriptHook(x => x.IsInvulnerableToMove(executingMove, target, ref isInvulnerable));
if (isInvulnerable) if (isInvulnerable)
@ -96,7 +96,7 @@ internal static class MoveTurnExecutor
// TODO: event? // TODO: event?
return; return;
} }
var numberOfHits = executingMove.NumberOfHits; var numberOfHits = executingMove.NumberOfHits;
var targetHitStat = executingMove.GetTargetIndex(target) * 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); var effectiveness = battle.Library.StaticLibrary.Types.GetEffectiveness(hitType, target.Types);
executingMove.RunScriptHook(x => x.ChangeEffectiveness(executingMove, target, hitIndex, ref effectiveness)); executingMove.RunScriptHook(x => x.ChangeEffectiveness(executingMove, target, hitIndex, ref effectiveness));
hitData.Effectiveness = effectiveness; hitData.Effectiveness = effectiveness;
var blockCritical = false; var blockCritical = false;
executingMove.RunScriptHook(x => x.BlockCriticalHit(executingMove, target, hitIndex, ref blockCritical)); executingMove.RunScriptHook(x => x.BlockCriticalHit(executingMove, target, hitIndex, ref blockCritical));
target.RunScriptHook(x => x.BlockIncomingCriticalHit(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); var critical = battle.Library.DamageCalculator.IsCritical(battle, executingMove, target, hitIndex);
hitData.IsCritical = critical; hitData.IsCritical = critical;
} }
var basePower = battle.Library.DamageCalculator.GetBasePower(executingMove, target, hitIndex, hitData); var basePower = battle.Library.DamageCalculator.GetBasePower(executingMove, target, hitIndex, hitData);
hitData.BasePower = basePower; hitData.BasePower = basePower;
hitData.Damage = battle.Library.DamageCalculator.GetDamage(executingMove, target, hitIndex, hitData); hitData.Damage = battle.Library.DamageCalculator.GetDamage(executingMove, target, hitIndex, hitData);
var accuracy = useMove.Accuracy; var accuracy = useMove.Accuracy;
@ -140,17 +140,17 @@ internal static class MoveTurnExecutor
// modifying it. // modifying it.
if (accuracy != 255) if (accuracy != 255)
{ {
accuracy = battle.Library.StatCalculator.CalculateModifiedAccuracy(executingMove, target, accuracy = battle.Library.StatCalculator.CalculateModifiedAccuracy(executingMove, target, hitIndex,
hitIndex, accuracy); accuracy);
} }
if (accuracy < 100 && battle.Random.GetInt(100) >= accuracy) if (accuracy < 100 && battle.Random.GetInt(100) >= accuracy)
{ {
executingMove.RunScriptHook(x => x.OnMoveMiss(executingMove, target)); executingMove.RunScriptHook(x => x.OnMoveMiss(executingMove, target));
battle.EventHook.Invoke(new MoveMissEvent(executingMove)); battle.EventHook.Invoke(new MoveMissEvent(executingMove));
break; break;
} }
var blockIncomingHit = false; var blockIncomingHit = false;
target.RunScriptHook(x => x.BlockIncomingHit(executingMove, target, hitIndex, ref blockIncomingHit)); target.RunScriptHook(x => x.BlockIncomingHit(executingMove, target, hitIndex, ref blockIncomingHit));
executingMove.RunScriptHook(x => x.BlockOutgoingHit(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, BatchId = hitEventBatch,
}); });
target.Damage(damage, DamageSource.MoveDamage, hitEventBatch); target.Damage(damage, DamageSource.MoveDamage, hitEventBatch);
if (!target.IsFainted) if (!target.IsFainted)
target.RunScriptHook(x => x.OnIncomingHit(executingMove, target, hitIndex)); target.RunScriptHook(x => x.OnIncomingHit(executingMove, target, hitIndex));
else else
executingMove.RunScriptHook(x => x.OnOpponentFaints(executingMove, target, hitIndex)); executingMove.RunScriptHook(x => x.OnOpponentFaints(executingMove, target, hitIndex));
@ -198,14 +198,16 @@ internal static class MoveTurnExecutor
if (secondaryEffect != null) if (secondaryEffect != null)
{ {
var preventSecondary = false; 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) if (!preventSecondary)
{ {
var chance = secondaryEffect.Chance; var chance = secondaryEffect.Chance;
if (chance < 0 || battle.Random.EffectChance(chance, executingMove, target, hitIndex)) 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) if (numberOfHits == 0)
{ {
target.RunScriptHook(x => x.OnMoveMiss(executingMove, target)); target.RunScriptHook(x => x.OnMoveMiss(executingMove, target));
@ -224,6 +226,5 @@ internal static class MoveTurnExecutor
{ {
executingMove.RunScriptHook(x => x.OnAfterHits(executingMove, target)); executingMove.RunScriptHook(x => x.OnAfterHits(executingMove, target));
} }
} }
} }

View File

@ -15,8 +15,8 @@ public static class TargetResolver
return target switch return target switch
{ {
MoveTarget.Adjacent or MoveTarget.AdjacentAlly or MoveTarget.AdjacentAllySelf or MoveTarget.AdjacentOpponent MoveTarget.Adjacent or MoveTarget.AdjacentAlly or MoveTarget.AdjacentAllySelf or MoveTarget.AdjacentOpponent
or MoveTarget.Any or MoveTarget.RandomOpponent or MoveTarget.Any or MoveTarget.RandomOpponent or MoveTarget.SelfUse =>
or MoveTarget.SelfUse => [battle.GetPokemon(side, position)], [battle.GetPokemon(side, position)],
MoveTarget.All => GetAllTargets(battle), MoveTarget.All => GetAllTargets(battle),
MoveTarget.AllAdjacentOpponent => GetAllAdjacentAndOpponent(battle, side, position), MoveTarget.AllAdjacentOpponent => GetAllAdjacentAndOpponent(battle, side, position),
MoveTarget.AllAdjacent => GetAllAdjacent(battle, side, position), MoveTarget.AllAdjacent => GetAllAdjacent(battle, side, position),
@ -144,7 +144,8 @@ public static class TargetResolver
return 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; var queue = battle.ChoiceQueue;
if (queue == null) if (queue == null)
{
throw new ArgumentNullException(nameof(battle.ChoiceQueue), throw new ArgumentNullException(nameof(battle.ChoiceQueue),
"The battle's choice queue must be set before running a turn."); "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 // 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. // choices, and put them in the correct order.
@ -30,7 +32,7 @@ public static class TurnRunner
{ {
choice.RunScriptHook(script => script.OnBeforeTurnStart(choice)); choice.RunScriptHook(script => script.OnBeforeTurnStart(choice));
} }
// Now we can properly begin executing choices. // 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 // One by one dequeue the turns, and run them. If the battle has ended we do not want to
// continue running. // continue running.
@ -41,7 +43,7 @@ public static class TurnRunner
continue; continue;
ExecuteChoice(battle, next); ExecuteChoice(battle, next);
} }
// If the battle is not ended, we have arrived at the normal end of a turn. and thus want // 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. // to run the end turn scripts.
@ -122,7 +124,6 @@ public static class TurnRunner
userSide.SwapPokemon(battleData.Position, fleeChoice.SwitchTo); userSide.SwapPokemon(battleData.Position, fleeChoice.SwitchTo);
} }
private static void ExecuteFleeChoice(IBattle battle, IFleeChoice fleeChoice) private static void ExecuteFleeChoice(IBattle battle, IFleeChoice fleeChoice)
{ {
var user = fleeChoice.User; var user = fleeChoice.User;
@ -131,7 +132,7 @@ public static class TurnRunner
return; return;
if (!battle.CanFlee) if (!battle.CanFlee)
return; return;
var preventFlee = false; var preventFlee = false;
fleeChoice.RunScriptHook(script => script.PreventSelfRunAway(fleeChoice, ref preventFlee)); fleeChoice.RunScriptHook(script => script.PreventSelfRunAway(fleeChoice, ref preventFlee));
if (preventFlee) if (preventFlee)
@ -148,10 +149,10 @@ public static class TurnRunner
return; return;
} }
} }
if (!battle.Library.MiscLibrary.CanFlee(battle, fleeChoice)) if (!battle.Library.MiscLibrary.CanFlee(battle, fleeChoice))
return; return;
var userSide = battle.Sides[battleData.SideIndex]; var userSide = battle.Sides[battleData.SideIndex];
userSide.MarkAsFled(); userSide.MarkAsFled();
battle.ValidateBattleState(); battle.ValidateBattleState();
@ -171,5 +172,4 @@ public static class TurnRunner
} }
itemChoice.Item.RunItemScript(battle.Library.ScriptResolver, target ?? user); itemChoice.Item.RunItemScript(battle.Library.ScriptResolver, target ?? user);
} }
} }

View File

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

View File

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

View File

@ -7,7 +7,6 @@ namespace PkmnLib.Dynamic.Models.Choices;
/// </summary> /// </summary>
public interface IFleeChoice : ITurnChoice public interface IFleeChoice : ITurnChoice
{ {
} }
/// <inheritdoc cref="IFleeChoice"/> /// <inheritdoc cref="IFleeChoice"/>
@ -22,7 +21,9 @@ public class FleeTurnChoice : TurnChoice, IFleeChoice
public override int ScriptCount => User.ScriptCount; public override int ScriptCount => User.ScriptCount;
/// <inheritdoc /> /// <inheritdoc />
public override void GetOwnScripts(List<IEnumerable<ScriptContainer>> scripts) { } public override void GetOwnScripts(List<IEnumerable<ScriptContainer>> scripts)
{
}
/// <inheritdoc /> /// <inheritdoc />
public override void CollectScripts(List<IEnumerable<ScriptContainer>> scripts) => User.CollectScripts(scripts); 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. /// The item that is used.
/// </summary> /// </summary>
public IItem Item { get; } public IItem Item { get; }
/// <summary> /// <summary>
/// The side the move is targeted at. /// The side the move is targeted at.
/// </summary> /// </summary>

View File

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

View File

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

View File

@ -7,7 +7,7 @@ public class TurnChoiceComparer : IComparer<ITurnChoice>
{ {
/// <inheritdoc cref="TurnChoiceComparer"/> /// <inheritdoc cref="TurnChoiceComparer"/>
public static TurnChoiceComparer Instance { get; } = new(); public static TurnChoiceComparer Instance { get; } = new();
private enum CompareValues private enum CompareValues
{ {
XEqualsY = 0, XEqualsY = 0,
@ -25,7 +25,7 @@ public class TurnChoiceComparer : IComparer<ITurnChoice>
// This is to ensure that the order of choices is deterministic. // This is to ensure that the order of choices is deterministic.
return (CompareValues)x.RandomValue.CompareTo(y.RandomValue); return (CompareValues)x.RandomValue.CompareTo(y.RandomValue);
} }
private static CompareValues CompareImpl(ITurnChoice? x, ITurnChoice? y) private static CompareValues CompareImpl(ITurnChoice? x, ITurnChoice? y)
{ {
// Deal with possible null values // Deal with possible null values
@ -77,10 +77,10 @@ public class TurnChoiceComparer : IComparer<ITurnChoice>
_ => CompareValues.XGreaterThanY, _ => CompareValues.XGreaterThanY,
}; };
} }
return CompareValues.XLessThanY; return CompareValues.XLessThanY;
} }
/// <inheritdoc /> /// <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. /// The damage is done because of struggling.
/// </summary> /// </summary>
Struggle = 2, Struggle = 2,
/// <summary> /// <summary>
/// The damage is done because of a form change. /// 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. /// This happens when the form of a Pokemon changes, and it has less max HP than it had before.
/// </summary> /// </summary>
FormChange = 3, FormChange = 3,
/// <summary> /// <summary>
/// The damage is done because of the weather. /// The damage is done because of the weather.
/// </summary> /// </summary>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -14,7 +14,7 @@ namespace PkmnLib.Dynamic.ScriptHandling;
public abstract class Script : IDeepCloneable public abstract class Script : IDeepCloneable
{ {
internal event Action<Script>? OnRemoveEvent; internal event Action<Script>? OnRemoveEvent;
private int _suppressCount; private int _suppressCount;
public void RemoveSelf() public void RemoveSelf()
@ -76,14 +76,14 @@ public abstract class Script : IDeepCloneable
public virtual void OnInitialize(IReadOnlyDictionary<StringKey, object?>? parameters) public virtual void OnInitialize(IReadOnlyDictionary<StringKey, object?>? parameters)
{ {
} }
/// <summary> /// <summary>
/// Override to customize whether the move can be selected at all. /// Override to customize whether the move can be selected at all.
/// </summary> /// </summary>
public virtual void PreventMoveSelection(IMoveChoice choice, ref bool prevent) public virtual void PreventMoveSelection(IMoveChoice choice, ref bool prevent)
{ {
} }
public virtual void ForceTurnSelection(byte sideIndex, byte position, ref ITurnChoice? choice) 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) public virtual void ChangeMove(IMoveChoice choice, ref StringKey moveName)
{ {
} }
/// <summary> /// <summary>
/// Changes the targets of a move choice. This allows for changing the targets of a move before the move starts. /// Changes the targets of a move choice. This allows for changing the targets of a move before the move starts.
/// </summary> /// </summary>
@ -267,7 +267,7 @@ public abstract class Script : IDeepCloneable
public virtual void BypassDefensiveStatBoosts(IExecutingMove move, IPokemon target, byte hit, ref bool bypass) public virtual void BypassDefensiveStatBoosts(IExecutingMove move, IPokemon target, byte hit, ref bool bypass)
{ {
} }
/// <summary> /// <summary>
/// This function allows a script to bypass evasion stat boosts for a move hit. /// 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. /// 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 BlockIncomingHit(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref bool block)
{ {
} }
public virtual void BlockOutgoingHit(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref bool block) public virtual void BlockOutgoingHit(IExecutingMove executingMove, IPokemon target, byte hitIndex, ref bool block)
{ {
} }
/// <summary> /// <summary>
/// Custom triggers for scripts. This allows scripts to run custom events that are not part of the /// Custom triggers for scripts. This allows scripts to run custom events that are not part of the
/// standard battle flow. /// 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"/> /// <see cref="IMoveChoice"/> and <see cref="IExecutingMove"/>
/// </summary> /// </summary>
Move = 0, Move = 0,
/// <summary> /// <summary>
/// A volatile script effect that is attached to a move choice. /// A volatile script effect that is attached to a move choice.
/// </summary> /// </summary>
@ -54,7 +54,7 @@ public enum ScriptCategory
/// A special script for weather, for use on battles. /// A special script for weather, for use on battles.
/// </summary> /// </summary>
Weather = 7, Weather = 7,
Terrain = 8, Terrain = 8,
/// <summary> /// <summary>

View File

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

View File

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

View File

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

View File

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

View File

@ -27,7 +27,7 @@ public interface IScriptSet : IEnumerable<ScriptContainer>
/// Gets a script from the set using its unique name. /// Gets a script from the set using its unique name.
/// </summary> /// </summary>
ScriptContainer? Get(StringKey scriptKey); ScriptContainer? Get(StringKey scriptKey);
/// <summary> /// <summary>
/// Gets a script from the set using its type. /// Gets a script from the set using its type.
/// </summary> /// </summary>
@ -155,8 +155,5 @@ public class ScriptSet : IScriptSet
/// <inheritdoc /> /// <inheritdoc />
public IEnumerable<StringKey> GetScriptNames() => public IEnumerable<StringKey> GetScriptNames() =>
_scripts _scripts.Select(x => x.Script).WhereNotNull().Select(s => s.Name);
.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. /// Gets an iterator over all scripts that are relevant for this source.
/// </summary> /// </summary>
ScriptIterator GetScripts(); ScriptIterator GetScripts();
/// <summary> /// <summary>
/// The number of scripts that are expected to be relevant for this source. This generally is /// 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. /// 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. /// may not be the same as the system time.
/// </summary> /// </summary>
public static Func<DateTimeOffset> DateTimeProvider { get; set; } = () => DateTimeOffset.Now; public static Func<DateTimeOffset> DateTimeProvider { get; set; } = () => DateTimeOffset.Now;
/// <summary> /// <summary>
/// Get the current date and time. /// Get the current date and time.
/// </summary> /// </summary>

View File

@ -64,13 +64,14 @@ public class LookupGrowthRate : IGrowthRate
} }
} }
return (LevelInt)(_experienceTable.Length); return (LevelInt)_experienceTable.Length;
} }
/// <inheritdoc /> /// <inheritdoc />
public uint CalculateExperience(LevelInt level) public uint CalculateExperience(LevelInt level)
{ {
if (level < 1) level = 1; if (level < 1)
level = 1;
return level >= _experienceTable.Length ? _experienceTable[^1] : _experienceTable[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. /// A set of arbitrary flags that can be set on the item.
/// </summary> /// </summary>
ImmutableHashSet<StringKey> Flags { get; } ImmutableHashSet<StringKey> Flags { get; }
ISecondaryEffect? Effect { get; } ISecondaryEffect? Effect { get; }
ISecondaryEffect? BattleEffect { get; } ISecondaryEffect? BattleEffect { get; }
byte FlingPower { get; } byte FlingPower { get; }
/// <summary> /// <summary>
@ -160,10 +160,6 @@ public class ItemImpl : IItem
/// <inheritdoc /> /// <inheritdoc />
public byte FlingPower { get; } public byte FlingPower { get; }
/// <inheritdoc /> /// <inheritdoc />
public bool HasFlag(string key) public bool HasFlag(string key) => Flags.Contains(key);
{
return Flags.Contains(key);
}
} }

View File

@ -7,13 +7,13 @@ namespace PkmnLib.Static.Libraries;
/// <summary> /// <summary>
/// A basic library for data types. Stores data both by name and by index. /// A basic library for data types. Stores data both by name and by index.
/// </summary> /// </summary>
public abstract class DataLibrary<T> : IEnumerable<T> public abstract class DataLibrary<T> : IEnumerable<T> where T : INamedValue
where T : INamedValue
{ {
/// <summary> /// <summary>
/// The underlying data storage. /// The underlying data storage.
/// </summary> /// </summary>
protected readonly Dictionary<StringKey, T> Data = new(); protected readonly Dictionary<StringKey, T> Data = new();
private readonly List<T> _values = []; private readonly List<T> _values = [];
/// <summary> /// <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. /// Tries to get a growth rate from the library. Returns false if the growth rate is not found.
/// </summary> /// </summary>
bool TryGet(StringKey key, [MaybeNullWhen(false)] out IGrowthRate value); bool TryGet(StringKey key, [MaybeNullWhen(false)] out IGrowthRate value);
/// <summary> /// <summary>
/// Gets a random growth rate from the library. /// Gets a random growth rate from the library.
/// </summary> /// </summary>
IGrowthRate GetRandom(IRandom random); IGrowthRate GetRandom(IRandom random);
/// <summary> /// <summary>
/// Gets the amount of growth rates in the library. /// Gets the amount of growth rates in the library.
/// </summary> /// </summary>
int Count { get; } int Count { get; }
/// <summary> /// <summary>
/// Whether the library is empty. /// Whether the library is empty.
/// </summary> /// </summary>
bool IsEmpty { get; } bool IsEmpty { get; }
/// <summary> /// <summary>
/// Calculates the experience for a given growth key name and a certain level. /// Calculates the experience for a given growth key name and a certain level.
/// </summary> /// </summary>
uint CalculateExperience(StringKey key, LevelInt level); uint CalculateExperience(StringKey key, LevelInt level);
/// <summary> /// <summary>
/// Calculates the level for a given growth key name and a certain experience. /// Calculates the level for a given growth key name and a certain experience.
/// </summary> /// </summary>

View File

@ -6,7 +6,7 @@ namespace PkmnLib.Static.Libraries;
/// <summary> /// <summary>
/// The library for all items in the game. /// The library for all items in the game.
/// </summary> /// </summary>
public interface IReadOnlyItemLibrary : IEnumerable<IItem> public interface IReadOnlyItemLibrary : IEnumerable<IItem>
{ {
/// <summary> /// <summary>
/// Tries to get an item from the library. Returns false if the item is not found. /// 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. /// The maximum level a Pokémon can reach.
/// </summary> /// </summary>
public required LevelInt MaxLevel { get; init; } public required LevelInt MaxLevel { get; init; }
/// <summary> /// <summary>
/// The chance of a Pokémon being shiny, as the denominator of a fraction, where the nominator /// 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. /// 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. /// Tries to get a move from the library. Returns false if the move is not found.
/// </summary> /// </summary>
bool TryGet(StringKey key, [MaybeNullWhen(false)] out IMoveData value); bool TryGet(StringKey key, [MaybeNullWhen(false)] out IMoveData value);
/// <summary> /// <summary>
/// Gets a random move from the library. /// Gets a random move from the library.
/// </summary> /// </summary>
IMoveData GetRandom(IRandom random); IMoveData GetRandom(IRandom random);
/// <summary> /// <summary>
/// The amount of moves in the library. /// The amount of moves in the library.
/// </summary> /// </summary>
int Count { get; } int Count { get; }
/// <summary> /// <summary>
/// Whether the library is empty. /// Whether the library is empty.
/// </summary> /// </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. /// Tries to get a species from the library. Returns false if the species is not found.
/// </summary> /// </summary>
bool TryGet(StringKey key, [MaybeNullWhen(false)] out ISpecies value); bool TryGet(StringKey key, [MaybeNullWhen(false)] out ISpecies value);
/// <summary> /// <summary>
/// Tried to get a species from the library by its national dex number. Returns false if the species is not found. /// Tried to get a species from the library by its national dex number. Returns false if the species is not found.
/// </summary> /// </summary>
@ -45,7 +45,7 @@ public interface IReadOnlySpeciesLibrary : IEnumerable<ISpecies>
public class SpeciesLibrary : DataLibrary<ISpecies>, IReadOnlySpeciesLibrary public class SpeciesLibrary : DataLibrary<ISpecies>, IReadOnlySpeciesLibrary
{ {
private Dictionary<ISpecies, ISpecies> _preEvolutionCache = new(); private Dictionary<ISpecies, ISpecies> _preEvolutionCache = new();
/// <inheritdoc /> /// <inheritdoc />
public bool TryGetById(int id, [MaybeNullWhen(false)] out ISpecies value) public bool TryGetById(int id, [MaybeNullWhen(false)] out ISpecies value)
{ {
@ -61,7 +61,7 @@ public class SpeciesLibrary : DataLibrary<ISpecies>, IReadOnlySpeciesLibrary
return preEvolution; return preEvolution;
foreach (var s in this) foreach (var s in this)
{ {
if (s.EvolutionData.All(e => e.ToSpecies != species.Name)) if (s.EvolutionData.All(e => e.ToSpecies != species.Name))
continue; continue;
_preEvolutionCache[species] = s; _preEvolutionCache[species] = s;
return s; return s;

View File

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

View File

@ -88,7 +88,7 @@ public class TypeLibrary : IReadOnlyTypeLibrary
/// </summary> /// </summary>
public TypeIdentifier RegisterType(StringKey name) 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); _types.Add(name, id);
_effectiveness.Add(Enumerable.Repeat(1.0f, _effectiveness.Count).ToList()); _effectiveness.Add(Enumerable.Repeat(1.0f, _effectiveness.Count).ToList());
foreach (var list in _effectiveness) 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. /// The chance in percentages that the effect triggers. When less than 0, the effect is always active.
/// </summary> /// </summary>
public float Chance { get; } public float Chance { get; }
/// <summary> /// <summary>
/// The name of the effect. /// The name of the effect.
/// </summary> /// </summary>
public StringKey Name { get; } public StringKey Name { get; }
/// <summary> /// <summary>
/// Parameters for the effect. /// Parameters for the effect.
/// </summary> /// </summary>

View File

@ -50,8 +50,7 @@ public class Nature(
Statistic increaseStat, Statistic increaseStat,
Statistic decreaseStat, Statistic decreaseStat,
float increaseModifier, float increaseModifier,
float decreaseModifier) float decreaseModifier) : INature
: INature
{ {
/// <inheritdoc /> /// <inheritdoc />
public StringKey Name { get; } = name; public StringKey Name { get; } = name;
@ -79,8 +78,6 @@ public class Nature(
} }
/// <inheritdoc /> /// <inheritdoc />
public bool Equals(INature? other) public bool Equals(INature? other) =>
{ other is not null && StringComparer.InvariantCultureIgnoreCase.Equals(Name, other.Name);
return 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. /// The parameters for the script effect of the ability.
/// </summary> /// </summary>
IReadOnlyDictionary<StringKey, object?> Parameters { get; } IReadOnlyDictionary<StringKey, object?> Parameters { get; }
bool HasFlag(StringKey key); bool HasFlag(StringKey key);
} }
@ -26,7 +26,8 @@ public interface IAbility : INamedValue
public class AbilityImpl : IAbility public class AbilityImpl : IAbility
{ {
/// <inheritdoc cref="AbilityImpl" /> /// <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; Name = name;
Effect = effect; Effect = effect;
@ -46,10 +47,7 @@ public class AbilityImpl : IAbility
public ImmutableHashSet<StringKey> Flags; public ImmutableHashSet<StringKey> Flags;
/// <inheritdoc /> /// <inheritdoc />
public bool HasFlag(StringKey key) public bool HasFlag(StringKey key) => Flags.Contains(key);
{
return Flags.Contains(key);
}
} }
/// <summary> /// <summary>

View File

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

View File

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

View File

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

View File

@ -50,7 +50,7 @@ public interface ISpecies : INamedValue
/// Gets a form by name. /// Gets a form by name.
/// </summary> /// </summary>
bool TryGetForm(StringKey id, [MaybeNullWhen(false)] out IForm form); bool TryGetForm(StringKey id, [MaybeNullWhen(false)] out IForm form);
/// <summary> /// <summary>
/// Gets the form the Pokémon will have by default, if no other form is specified. /// Gets the form the Pokémon will have by default, if no other form is specified.
/// </summary> /// </summary>
@ -70,7 +70,7 @@ public interface ISpecies : INamedValue
/// The data regarding into which Pokémon this species can evolve, and how. /// The data regarding into which Pokémon this species can evolve, and how.
/// </summary> /// </summary>
IReadOnlyList<IEvolution> EvolutionData { get; } IReadOnlyList<IEvolution> EvolutionData { get; }
/// <summary> /// <summary>
/// The egg groups the Pokémon belongs to. /// The egg groups the Pokémon belongs to.
/// </summary> /// </summary>
@ -124,14 +124,13 @@ public class SpeciesImpl : ISpecies
/// <inheritdoc /> /// <inheritdoc />
public ImmutableHashSet<StringKey> Flags { get; } public ImmutableHashSet<StringKey> Flags { get; }
/// <inheritdoc /> /// <inheritdoc />
public IReadOnlyList<IEvolution> EvolutionData { get; } public IReadOnlyList<IEvolution> EvolutionData { get; }
/// <inheritdoc /> /// <inheritdoc />
public ICollection<StringKey> EggGroups { get; } public ICollection<StringKey> EggGroups { get; }
/// <inheritdoc /> /// <inheritdoc />
public bool TryGetForm(StringKey id, [MaybeNullWhen(false)] out IForm form) => Forms.TryGetValue(id, out form); 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. /// Health Points determine how much damage a Pokémon can receive before fainting.
/// </summary> /// </summary>
Hp, Hp,
/// <summary> /// <summary>
/// Attack determines how much damage a Pokémon deals when using a physical attack. /// Attack determines how much damage a Pokémon deals when using a physical attack.
/// </summary> /// </summary>
Attack, Attack,
/// <summary> /// <summary>
/// Defense determines how much damage a Pokémon receives when it is hit by a physical attack. /// Defense determines how much damage a Pokémon receives when it is hit by a physical attack.
/// </summary> /// </summary>
Defense, Defense,
/// <summary> /// <summary>
/// Special Attack determines how much damage a Pokémon deals when using a special attack. /// Special Attack determines how much damage a Pokémon deals when using a special attack.
/// </summary> /// </summary>
SpecialAttack, SpecialAttack,
/// <summary> /// <summary>
/// Special Defense determines how much damage a Pokémon receives when it is hit by a special attack. /// Special Defense determines how much damage a Pokémon receives when it is hit by a special attack.
/// </summary> /// </summary>
SpecialDefense, SpecialDefense,
/// <summary> /// <summary>
/// Speed determines the order that a Pokémon can act in battle. /// Speed determines the order that a Pokémon can act in battle.
/// </summary> /// </summary>
Speed, Speed,
/// <summary> /// <summary>
/// Evasion determines the likelihood that a Pokémon will dodge an attack. /// 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. /// This is not part of base stats, but is a temporary stat boost.
/// </summary> /// </summary>
Evasion, Evasion,
/// <summary> /// <summary>
/// Accuracy determines the likelihood that a Pokémon will hit an attack. /// 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. /// 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. /// A set of statistics that cannot be changed.
/// </summary> /// </summary>
/// <typeparam name="T">The size of the integer to be used</typeparam> /// <typeparam name="T">The size of the integer to be used</typeparam>
public record ImmutableStatisticSet<T> public record ImmutableStatisticSet<T> where T : struct
where T : struct
{ {
/// <summary> /// <summary>
/// The health points stat value. /// The health points stat value.
@ -51,7 +50,7 @@ public record ImmutableStatisticSet<T>
SpecialDefense = specialDefense; SpecialDefense = specialDefense;
Speed = speed; Speed = speed;
} }
public ImmutableStatisticSet(ImmutableStatisticSet<T> set) public ImmutableStatisticSet(ImmutableStatisticSet<T> set)
{ {
Hp = set.Hp; Hp = set.Hp;
@ -84,8 +83,7 @@ public record ImmutableStatisticSet<T>
/// A set of statistics that can be changed. /// A set of statistics that can be changed.
/// </summary> /// </summary>
/// <typeparam name="T"></typeparam> /// <typeparam name="T"></typeparam>
public record StatisticSet<T> : ImmutableStatisticSet<T>, IEnumerable<T>, IDeepCloneable public record StatisticSet<T> : ImmutableStatisticSet<T>, IEnumerable<T>, IDeepCloneable where T : struct
where T : struct
{ {
/// <inheritdoc cref="StatisticSet{T}"/> /// <inheritdoc cref="StatisticSet{T}"/>
public StatisticSet() : base(default, default, default, default, default, default) 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) defense, specialAttack, specialDefense, speed)
{ {
} }
public StatisticSet(StatisticSet<T> set) : base(set) public StatisticSet(StatisticSet<T> set) : base(set)
{ {
} }
@ -175,16 +173,12 @@ public record StatisticSet<T> : ImmutableStatisticSet<T>, IEnumerable<T>, IDeepC
return true; return true;
} }
protected virtual T GetUnknownStat(Statistic stat) protected virtual T GetUnknownStat(Statistic stat) => throw new ArgumentException($"Invalid statistic {stat}");
{
throw new ArgumentException($"Invalid statistic {stat}");
}
protected virtual void SetUnknownStat(Statistic stat, T value) protected virtual void SetUnknownStat(Statistic stat, T value)
{ {
throw new ArgumentException($"Invalid statistic {stat}"); throw new ArgumentException($"Invalid statistic {stat}");
} }
/// <summary> /// <summary>
/// Decreases a statistic in the set by a value. /// Decreases a statistic in the set by a value.
@ -231,17 +225,13 @@ public record StatisticSet<T> : ImmutableStatisticSet<T>, IEnumerable<T>, IDeepC
} }
/// <inheritdoc /> /// <inheritdoc />
IEnumerator IEnumerable.GetEnumerator() IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
{
return GetEnumerator();
}
} }
/// <summary> /// <summary>
/// A set of statistics that can be changed, but are clamped to a minimum and maximum value. /// A set of statistics that can be changed, but are clamped to a minimum and maximum value.
/// </summary> /// </summary>
public abstract record ClampedStatisticSet<T> : StatisticSet<T> public abstract record ClampedStatisticSet<T> : StatisticSet<T> where T : struct, IComparable<T>
where T : struct, IComparable<T>
{ {
/// <inheritdoc /> /// <inheritdoc />
[SuppressMessage("ReSharper", "VirtualMemberCallInConstructor")] [SuppressMessage("ReSharper", "VirtualMemberCallInConstructor")]
@ -257,7 +247,7 @@ public abstract record ClampedStatisticSet<T> : StatisticSet<T>
SpecialDefense = Clamp(SpecialDefense, Min, Max); SpecialDefense = Clamp(SpecialDefense, Min, Max);
Speed = Clamp(Speed, Min, Max); Speed = Clamp(Speed, Min, Max);
} }
protected ClampedStatisticSet(ClampedStatisticSet<T> set) : base(set) protected ClampedStatisticSet(ClampedStatisticSet<T> set) : base(set)
{ {
} }
@ -322,13 +312,15 @@ public record StatBoostStatisticSet : ClampedStatisticSet<sbyte>
protected override sbyte Max => 6; protected override sbyte Max => 6;
private sbyte _evasion; private sbyte _evasion;
public sbyte Evasion public sbyte Evasion
{ {
get => _evasion; get => _evasion;
set => _evasion = Clamp(value, Min, Max); set => _evasion = Clamp(value, Min, Max);
} }
private sbyte _accuracy; private sbyte _accuracy;
public sbyte Accuracy public sbyte Accuracy
{ {
get => _accuracy; get => _accuracy;
@ -403,13 +395,13 @@ public record IndividualValueStatisticSet : ClampedStatisticSet<byte>
public IndividualValueStatisticSet() : base(0, 0, 0, 0, 0, 0) public IndividualValueStatisticSet() : base(0, 0, 0, 0, 0, 0)
{ {
} }
/// <inheritdoc cref="IndividualValueStatisticSet"/> /// <inheritdoc cref="IndividualValueStatisticSet"/>
public IndividualValueStatisticSet(byte hp, byte attack, byte defense, byte specialAttack, byte specialDefense, public IndividualValueStatisticSet(byte hp, byte attack, byte defense, byte specialAttack, byte specialDefense,
byte speed) : base(hp, attack, defense, specialAttack, specialDefense, speed) byte speed) : base(hp, attack, defense, specialAttack, specialDefense, speed)
{ {
} }
public IndividualValueStatisticSet(IndividualValueStatisticSet ivs) : base(ivs) public IndividualValueStatisticSet(IndividualValueStatisticSet ivs) : base(ivs)
{ {
} }
@ -436,7 +428,7 @@ public record EffortValueStatisticSet : ClampedStatisticSet<byte>
byte speed) : base(hp, attack, defense, specialAttack, specialDefense, speed) byte speed) : base(hp, attack, defense, specialAttack, specialDefense, speed)
{ {
} }
public EffortValueStatisticSet(EffortValueStatisticSet evs) : base(evs) 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 /// Recursive references will be handled correctly, and will only be cloned once, to prevent infinite loops and invalid
/// references. /// references.
/// </summary> /// </summary>
public static T DeepClone<T>(this T? obj, Dictionary<(Type, int), object>? objects = null) where T : IDeepCloneable public static T DeepClone<T>(this T? obj, Dictionary<(Type, int), object>? objects = null)
{ where T : IDeepCloneable => (T)DeepClone((object?)obj, objects)!;
return (T)DeepClone((object?)obj, objects)!;
}
private static object? DeepClone(this object? obj, Dictionary<(Type, int), object>? objects = null) private static object? DeepClone(this object? obj, Dictionary<(Type, int), object>? objects = null)
{ {
if (obj == 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 // 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. // side effects from the constructor, and to not require a parameterless constructor.
var newObj = FormatterServices.GetUninitializedObject(type)!; 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 // 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 // already been cloned, so we can re-use them instead of cloning them again. This is necessary to prevent infinite
// loops and invalid references. // 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 the object is a value type or a string, we can just return it.
if (type.IsValueType || type == typeof(string)) if (type.IsValueType || type == typeof(string))
return obj; return obj;
// If the object is marked as deep cloneable, we will clone it. // If the object is marked as deep cloneable, we will clone it.
if (type.GetInterface(nameof(IDeepCloneable)) != null || ExternalDeepCloneTypes.Contains(type)) if (type.GetInterface(nameof(IDeepCloneable)) != null || ExternalDeepCloneTypes.Contains(type))
{ {
@ -91,8 +89,9 @@ public static class DeepCloneHandler
var array = (Array)obj; var array = (Array)obj;
var newArray = Array.CreateInstance(type.GetElementType()!, array.Length); var newArray = Array.CreateInstance(type.GetElementType()!, array.Length);
for (var i = 0; i < array.Length; i++) 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; return newArray;
} }
@ -115,9 +114,10 @@ public static class DeepCloneHandler
var dictionary = (IDictionary)obj; var dictionary = (IDictionary)obj;
var newDictionary = (IDictionary)Activator.CreateInstance(type); var newDictionary = (IDictionary)Activator.CreateInstance(type);
foreach (DictionaryEntry entry in dictionary) 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)); DeepCloneInternal(entry.Value, type.GetGenericArguments()[1], objects));
}
return newDictionary; 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 /// 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. /// each field in the type, so that we can get high performance deep cloning.
/// </remarks> /// </remarks>
private static (Func<object, object?> getter, Action<object, object?> setter)[] private static (Func<object, object?> getter, Action<object, object?> setter)[] GetDeepCloneExpressions(Type type)
GetDeepCloneExpressions(Type type)
{ {
// We use a lock here to prevent multiple threads from trying to create the expressions at the same time. // We use a lock here to prevent multiple threads from trying to create the expressions at the same time.
lock (DeepCloneExpressions) lock (DeepCloneExpressions)

View File

@ -2,7 +2,8 @@ namespace PkmnLib.Static.Utils;
public static class DictionaryHelpers 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)) if (dictionary.TryGetValue(key, out var value))
return value; return value;

View File

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

View File

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

View File

@ -13,7 +13,7 @@ public static class NumericHelpers
var result = value * multiplier; var result = value * multiplier;
return result > byte.MaxValue ? byte.MaxValue : (byte)result; return result > byte.MaxValue ? byte.MaxValue : (byte)result;
} }
/// <summary> /// <summary>
/// Multiplies two values. If this overflows, returns <see cref="byte.MaxValue"/>. /// Multiplies two values. If this overflows, returns <see cref="byte.MaxValue"/>.
/// </summary> /// </summary>
@ -22,7 +22,7 @@ public static class NumericHelpers
var result = value * multiplier; var result = value * multiplier;
return result > byte.MaxValue ? byte.MaxValue : (byte)result; return result > byte.MaxValue ? byte.MaxValue : (byte)result;
} }
/// <summary> /// <summary>
/// Multiplies two values. If this overflows, returns <see cref="sbyte.MaxValue"/>. /// Multiplies two values. If this overflows, returns <see cref="sbyte.MaxValue"/>.
/// </summary> /// </summary>
@ -31,7 +31,7 @@ public static class NumericHelpers
var result = value * multiplier; var result = value * multiplier;
return result > sbyte.MaxValue ? sbyte.MaxValue : (sbyte)result; return result > sbyte.MaxValue ? sbyte.MaxValue : (sbyte)result;
} }
/// <summary> /// <summary>
/// Multiplies two values. If this overflows, returns <see cref="ushort.MaxValue"/>. /// Multiplies two values. If this overflows, returns <see cref="ushort.MaxValue"/>.
/// </summary> /// </summary>
@ -40,7 +40,7 @@ public static class NumericHelpers
var result = value * multiplier; var result = value * multiplier;
return result > ushort.MaxValue ? ushort.MaxValue : (ushort)result; return result > ushort.MaxValue ? ushort.MaxValue : (ushort)result;
} }
/// <summary> /// <summary>
/// Multiplies two values. If this overflows, returns <see cref="short.MaxValue"/>. /// Multiplies two values. If this overflows, returns <see cref="short.MaxValue"/>.
/// </summary> /// </summary>
@ -49,11 +49,10 @@ public static class NumericHelpers
var result = value * multiplier; var result = value * multiplier;
return result > short.MaxValue ? short.MaxValue : (short)result; return result > short.MaxValue ? short.MaxValue : (short)result;
} }
public static uint MultiplyOrMax(this uint value, uint multiplier) public static uint MultiplyOrMax(this uint value, uint multiplier)
{ {
var result = (ulong)value * multiplier; var result = (ulong)value * multiplier;
return result > uint.MaxValue ? uint.MaxValue : (uint)result; 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> /// <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> /// <returns>A random integer that is greater than or equal to 0 and less than max.</returns>
public int GetInt(int max); public int GetInt(int max);
/// <summary> /// <summary>
/// Get a random integer between 0 and <see cref="int.MaxValue"/>. /// Get a random integer between 0 and <see cref="int.MaxValue"/>.
/// </summary> /// </summary>
/// <returns>A random integer that is greater than or equal to 0 and less than <see cref="int.MaxValue"/>.</returns> /// <returns>A random integer that is greater than or equal to 0 and less than <see cref="int.MaxValue"/>.</returns>
public int GetInt(); public int GetInt();
/// <summary> /// <summary>
/// Get a random float that is greater than or equal to 0.0 and less than 1.0. /// Get a random float that is greater than or equal to 0.0 and less than 1.0.
/// </summary> /// </summary>
/// <returns>A random float that is greater than or equal to 0.0 and less than 1.0.</returns> /// <returns>A random float that is greater than or equal to 0.0 and less than 1.0.</returns>
public float GetFloat(); public float GetFloat();
/// <summary> /// <summary>
/// Get a random float that is greater than or equal to min and less than max. /// Get a random float that is greater than or equal to min and less than max.
/// </summary> /// </summary>
@ -41,7 +41,7 @@ public interface IRandom
/// <param name="max">The maximum value (exclusive).</param> /// <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> /// <returns>A random float that is greater than or equal to min and less than max.</returns>
public float GetFloat(float min, float max); public float GetFloat(float min, float max);
/// <summary> /// <summary>
/// Get a random boolean. 50% chance of being true. /// Get a random boolean. 50% chance of being true.
/// </summary> /// </summary>

View File

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

View File

@ -10,12 +10,9 @@ public class MoveDataTests
public record TestCaseData(IDynamicLibrary Library, IMoveData Move) public record TestCaseData(IDynamicLibrary Library, IMoveData Move)
{ {
/// <inheritdoc /> /// <inheritdoc />
public override string ToString() public override string ToString() => Move.Name + " has valid scripts";
{
return Move.Name + " has valid scripts";
}
} }
public static IEnumerable<TestCaseData> AllMovesHaveValidScriptsData() public static IEnumerable<TestCaseData> AllMovesHaveValidScriptsData()
{ {
var library = LibraryHelpers.LoadLibrary(); var library = LibraryHelpers.LoadLibrary();
@ -27,9 +24,8 @@ public class MoveDataTests
yield return new TestCaseData(library, move); yield return new TestCaseData(library, move);
} }
} }
[Test] [Test, MethodDataSource(nameof(AllMovesHaveValidScriptsData))]
[MethodDataSource(nameof(AllMovesHaveValidScriptsData))]
public async Task AllMoveEffectsHaveValidScripts(TestCaseData test) public async Task AllMoveEffectsHaveValidScripts(TestCaseData test)
{ {
if (test.Move.SecondaryEffect == null) if (test.Move.SecondaryEffect == null)
@ -43,7 +39,8 @@ public class MoveDataTests
} }
catch (Exception e) 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("Dark");
typeLibrary.RegisterType("Steel"); typeLibrary.RegisterType("Steel");
typeLibrary.RegisterType("Fairy"); typeLibrary.RegisterType("Fairy");
var library = SpeciesDataLoader.LoadSpecies(file, typeLibrary); var library = SpeciesDataLoader.LoadSpecies(file, typeLibrary);
await Assert.That(library).IsNotNull(); 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); await using var file = File.Open("Data/Types.csv", FileMode.Open, FileAccess.Read, FileShare.Read);
var library = TypeDataLoader.LoadTypeLibrary(file); var library = TypeDataLoader.LoadTypeLibrary(file);
await Assert.That(library).IsNotNull(); await Assert.That(library).IsNotNull();
var fire = library.TryGetTypeIdentifier("Fire", out var fireId); var fire = library.TryGetTypeIdentifier("Fire", out var fireId);
await Assert.That(fire).IsTrue(); await Assert.That(fire).IsTrue();
var grass = library.TryGetTypeIdentifier("Grass", out var grassId); var grass = library.TryGetTypeIdentifier("Grass", out var grassId);
await Assert.That(grass).IsTrue(); await Assert.That(grass).IsTrue();
var fireEffectiveness = library.GetSingleEffectiveness(fireId, grassId); var fireEffectiveness = library.GetSingleEffectiveness(fireId, grassId);
await Assert.That(fireEffectiveness).IsEqualTo(2.0f); await Assert.That(fireEffectiveness).IsEqualTo(2.0f);
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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