diff --git a/PkmnLibRSharp/DynamicData/Libraries/BattleStatCalculator.cs b/PkmnLibRSharp/DynamicData/Libraries/BattleStatCalculator.cs index b1b30d4..2b6db89 100644 --- a/PkmnLibRSharp/DynamicData/Libraries/BattleStatCalculator.cs +++ b/PkmnLibRSharp/DynamicData/Libraries/BattleStatCalculator.cs @@ -6,7 +6,7 @@ namespace PkmnLibSharp.DynamicData.Libraries { public abstract class BattleStatCalculator : ExternPointer { - public BattleStatCalculator(IdentifiablePointer ptr, bool isOwner) : base(ptr, isOwner){} + protected BattleStatCalculator(IdentifiablePointer ptr, bool isOwner) : base(ptr, isOwner){} protected override object CreateCache() => new(); diff --git a/PkmnLibRSharp/DynamicData/Libraries/DynamicLibrary.cs b/PkmnLibRSharp/DynamicData/Libraries/DynamicLibrary.cs index 81585b1..b6e5269 100644 --- a/PkmnLibRSharp/DynamicData/Libraries/DynamicLibrary.cs +++ b/PkmnLibRSharp/DynamicData/Libraries/DynamicLibrary.cs @@ -7,18 +7,27 @@ namespace PkmnLibSharp.DynamicData.Libraries public class DynamicLibrary : ExternPointer { public class CacheData + { + public StaticData.Libraries.StaticData? StaticData { get; internal set; } + } + + internal DynamicLibrary(IdentifiablePointer ptr) : base(ptr, false) { } - - internal DynamicLibrary(IdentifiablePointer ptr) : base(ptr, false) {} public DynamicLibrary(StaticData.Libraries.StaticData staticData, BattleStatCalculator statCalculator, DamageLibrary damageLibrary, MiscLibrary miscLibrary, ScriptResolver scriptResolver) : base( - Interface.dynamic_library_new(staticData.Ptr, statCalculator.Ptr, damageLibrary.Ptr, miscLibrary.Ptr, - scriptResolver.Ptr), true) + Interface.dynamic_library_new(staticData.TakeOwnershipAndInvalidate(), + statCalculator.TakeOwnershipAndInvalidate(), damageLibrary.TakeOwnershipAndInvalidate(), + miscLibrary.TakeOwnershipAndInvalidate(), scriptResolver.TakeOwnershipAndInvalidate()), true) { } + public StaticData.Libraries.StaticData StaticData => + Cache.StaticData ??= + new StaticData.Libraries.StaticData(Interface.dynamic_library_get_static_data(Ptr), false); + + protected override CacheData CreateCache() => new(); protected override void Destructor() => Interface.dynamic_library_drop(Ptr); } diff --git a/PkmnLibRSharp/DynamicData/PokemonBuilder.cs b/PkmnLibRSharp/DynamicData/PokemonBuilder.cs new file mode 100644 index 0000000..c32dbdc --- /dev/null +++ b/PkmnLibRSharp/DynamicData/PokemonBuilder.cs @@ -0,0 +1,165 @@ +using System; +using System.Collections.Generic; +using PkmnLibSharp.DynamicData.Libraries; +using PkmnLibSharp.StaticData; +using PkmnLibSharp.Utils; + +namespace PkmnLibSharp.DynamicData +{ + public class PokemonBuilder + { + private static readonly Random DefaultRandom = new(); + + private readonly Random _random; + private readonly int _randomSeed; + + private readonly DynamicLibrary _library; + private readonly string _species; + private string _form = "default"; + private readonly LevelInt _level; + private bool? _forceShininess; + private Gender? _gender; + private uint? _identifier; + private string? _natureName; + private string? _ability; + private uint? _experience; + private readonly List<(string name, MoveLearnMethod method)> _moves = new(); + + private bool _hiddenAbility; + private byte _abilityIndex; + private byte _coloring; + + public PokemonBuilder(DynamicLibrary library, string species, LevelInt level) + { + _library = library; + _species = species; + _level = level; + _randomSeed = DefaultRandom.Next(); + _random = new Random(_randomSeed); + } + + public PokemonBuilder WithForm(string form) + { + _form = form; + return this; + } + + public PokemonBuilder ForceShiny(bool value) + { + _forceShininess = value; + return this; + } + + public PokemonBuilder WithGender(Gender gender) + { + _gender = gender; + return this; + } + + public PokemonBuilder WithIdentifier(uint identifier) + { + _identifier = identifier; + return this; + } + + public PokemonBuilder WithNature(string nature) + { + _natureName = nature; + return this; + } + + public PokemonBuilder WithAbility(string ability) + { + _ability = ability; + return this; + } + + public PokemonBuilder WithExperience(uint experience) + { + _experience = experience; + return this; + } + + public PokemonBuilder LearnMove(string moveName, MoveLearnMethod learnMethod) + { + _moves.Add((moveName, learnMethod)); + return this; + } + + protected virtual Pokemon Finalize(Species species, Form form, Item? heldItem) + { + var pokemon = new Pokemon(_library, species, form, _hiddenAbility, _abilityIndex, _level, + _identifier ?? (uint)_random.Next(), _gender!.Value, _coloring, _natureName!); + if (heldItem != null) + pokemon.SetHeldItem(heldItem); + foreach (var move in _moves) + { + pokemon.LearnMove(move.name, move.method); + } + + return pokemon; + } + + protected virtual void PopulateUninitialized(Species species, Form form, Random random) + { + _experience ??= _library.StaticData.GrowthRateLibrary.CalculateExperience(species.GrowthRate, _level); + _identifier ??= (uint)random.Next(); + _gender ??= species.GetRandomGender((ulong)_randomSeed); + if (_forceShininess.HasValue) + { + _coloring = _forceShininess.Value ? (byte)1 : (byte)0; + } + else if (random.Next((int)_library.StaticData.LibrarySettings.ShinyRate) == 0) + { + _coloring = 1; + } + + if (string.IsNullOrEmpty(_ability)) + { + _hiddenAbility = false; + _abilityIndex = (byte)_random.Next(0, form.Abilities.Count); + } + else + { + (_hiddenAbility, _abilityIndex) = GetAbilityIndex(species, form, _ability!); + } + + if (string.IsNullOrEmpty(_natureName)) + { + using var nature = _library.StaticData.NatureLibrary.GetRandomNature((ulong)_randomSeed); + _natureName = _library.StaticData.NatureLibrary.GetNatureName(nature); + } + } + + private static (bool, byte) GetAbilityIndex(Species species, Form form, string ability) + { + var i = form.Abilities.IndexOf(ability); + if (i != -1) + return (false, (byte)i); + i = form.HiddenAbilities.IndexOf(ability); + if (i == -1) + { + throw new Exception( + $"Invalid ability '{ability}' for Pokemon '{species.Name}' and forme '{form.Name}'."); + } + + return (true, (byte)i); + } + + public Pokemon Build() + { + if (!_library.StaticData.SpeciesLibrary.TryGetValue(_species, out var species)) + { + throw new Exception($"Species '{_species}' was not found."); + } + if (!species.TryGetForm(_form, out var form)) + { + throw new Exception($"Forme '{_form}' was not found on species '{_species}'"); + } + + PopulateUninitialized(species, form, _random); + + return Finalize(species, form, null); + } + } +} \ No newline at end of file diff --git a/PkmnLibRSharp/FFI/DynamicData/Libraries/DynamicLibrary.cs b/PkmnLibRSharp/FFI/DynamicData/Libraries/DynamicLibrary.cs index c1783c9..ee866bb 100644 --- a/PkmnLibRSharp/FFI/DynamicData/Libraries/DynamicLibrary.cs +++ b/PkmnLibRSharp/FFI/DynamicData/Libraries/DynamicLibrary.cs @@ -18,5 +18,11 @@ namespace PkmnLibSharp.FFI.DynamicData.Libraries [DllImport(Data.DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] internal static extern void dynamic_library_drop(IntPtr dynamicLibrary); + /// + /// The static data is the immutable storage data for this library. + /// + [DllImport(Data.DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] + internal static extern IdentifiablePointer dynamic_library_get_static_data(IntPtr dynamicLibrary); + } } \ No newline at end of file diff --git a/PkmnLibRSharp/FFI/StaticData/Libraries/LibrarySettings.cs b/PkmnLibRSharp/FFI/StaticData/Libraries/LibrarySettings.cs index e3f1475..d70a5b8 100644 --- a/PkmnLibRSharp/FFI/StaticData/Libraries/LibrarySettings.cs +++ b/PkmnLibRSharp/FFI/StaticData/Libraries/LibrarySettings.cs @@ -7,12 +7,16 @@ namespace PkmnLibSharp.FFI.StaticData.Libraries internal static class LibrarySettings { [DllImport(Data.DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - internal static extern IdentifiablePointer library_settings_new(LevelInt maxLevel); + internal static extern IdentifiablePointer library_settings_new(LevelInt maxLevel, uint shinyRate); [DllImport(Data.DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] internal static extern void library_settings_drop(IntPtr ptr); [DllImport(Data.DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] internal static extern LevelInt library_settings_maximum_level(IntPtr ptr); + + [DllImport(Data.DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] + internal static extern uint library_settings_shiny_rate(IntPtr ptr); + } } \ No newline at end of file diff --git a/PkmnLibRSharp/FFI/StaticData/Libraries/NatureLibrary.cs b/PkmnLibRSharp/FFI/StaticData/Libraries/NatureLibrary.cs index f8cc953..efce839 100644 --- a/PkmnLibRSharp/FFI/StaticData/Libraries/NatureLibrary.cs +++ b/PkmnLibRSharp/FFI/StaticData/Libraries/NatureLibrary.cs @@ -17,6 +17,9 @@ namespace PkmnLibSharp.FFI.StaticData.Libraries [DllImport(Data.DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] internal static extern IdentifiablePointer nature_library_get_nature(IntPtr ptr, IntPtr name); + [DllImport(Data.DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] + internal static extern IdentifiablePointer nature_library_get_random_nature(IntPtr ptr, ulong seed); + [DllImport(Data.DllName, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] internal static extern IntPtr nature_library_get_nature_name(IntPtr ptr, IntPtr nature); } diff --git a/PkmnLibRSharp/FFI/StaticData/Species.cs b/PkmnLibRSharp/FFI/StaticData/Species.cs index 85a26b3..1d05f86 100644 --- a/PkmnLibRSharp/FFI/StaticData/Species.cs +++ b/PkmnLibRSharp/FFI/StaticData/Species.cs @@ -1,5 +1,6 @@ using System; using System.Runtime.InteropServices; +using PkmnLibSharp.StaticData; namespace PkmnLibSharp.FFI.StaticData { @@ -33,5 +34,8 @@ namespace PkmnLibSharp.FFI.StaticData [DllImport(Data.DllName, CallingConvention = CallingConvention.Cdecl)] internal static extern IdentifiablePointer species_get_form(IntPtr ptr, IntPtr name); + [DllImport(Data.DllName, CallingConvention = CallingConvention.Cdecl)] + internal static extern Gender species_get_random_gender(IntPtr ptr, ulong seed); + } } \ No newline at end of file diff --git a/PkmnLibRSharp/PkmnLibRSharp.csproj b/PkmnLibRSharp/PkmnLibRSharp.csproj index 7634494..16470e8 100644 --- a/PkmnLibRSharp/PkmnLibRSharp.csproj +++ b/PkmnLibRSharp/PkmnLibRSharp.csproj @@ -1,10 +1,10 @@ - netstandard2.1 PkmnLibSharp 11 enable + netstandard2.0;netstandard2.1 diff --git a/PkmnLibRSharp/StaticData/GrowthRate.cs b/PkmnLibRSharp/StaticData/GrowthRate.cs index cc0580f..fc731ca 100644 --- a/PkmnLibRSharp/StaticData/GrowthRate.cs +++ b/PkmnLibRSharp/StaticData/GrowthRate.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.CompilerServices; using PkmnLibSharp.Utils; using Interface = PkmnLibSharp.FFI.StaticData.GrowthRate; @@ -22,9 +24,10 @@ namespace PkmnLibSharp.StaticData public uint CalculateExperience(LevelInt level) { + Debug.Assert(level >= 1); return Interface.growth_rate_calculate_experience(Ptr, level); } - + protected override void Destructor() { Interface.growth_rate_lookup_drop(Ptr); @@ -44,10 +47,126 @@ namespace PkmnLibSharp.StaticData { return new object(); } - + ~LookupGrowthRate() { Dispose(); } } + + /// + /// A collection of the growth rates that have been used since generation 5. + /// + public static class CommonGrowthRates + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint Power2(uint i) + { + return i * i; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static uint Power3(uint i) + { + return i * i * i; + } + + public static LookupGrowthRate Fast(LevelInt maxLevel) + { + var arr = new uint[maxLevel]; + arr[0] = 0; + for (uint i = 2; i < maxLevel + 1; i++) + { + arr[i - 1] = 4 * Power3(i) / 5; + } + + return new LookupGrowthRate(arr); + } + + public static LookupGrowthRate MediumFast(LevelInt maxLevel) + { + var arr = new uint[maxLevel]; + arr[0] = 0; + for (uint i = 2; i < maxLevel + 1; i++) + { + arr[i - 1] = Power3(i); + } + + return new LookupGrowthRate(arr); + } + + public static LookupGrowthRate MediumSlow(LevelInt maxLevel) + { + var arr = new uint[maxLevel]; + arr[0] = 0; + for (uint i = 2; i < maxLevel + 1; i++) + { + arr[i - 1] = 6 * Power3(i) / 5 - 15 * Power2(i) + 100 * i - 140; + } + + return new LookupGrowthRate(arr); + } + + public static LookupGrowthRate Slow(LevelInt maxLevel) + { + var arr = new uint[maxLevel]; + arr[0] = 0; + for (uint i = 2; i < maxLevel + 1; i++) + { + arr[i - 1] = 5 * Power3(i) / 4; + } + return new LookupGrowthRate(arr); + } + + public static LookupGrowthRate Erratic(LevelInt maxLevel) + { + var arr = new uint[maxLevel]; + arr[0] = 0; + uint i = 2; + for (; i < 50; i++) + { + arr[i - 1] = Power3(i) * (100 - i) / 50; + } + + for (; i < 68; i++) + { + arr[i - 1] = Power3(i) * (150 - i) / 100; + } + + for (; i < 98; i++) + { + arr[i - 1] = Power3(i) * (uint)((1911 - 10 * i) / 3.0) / 500; + } + + for (; i < maxLevel + 1; i++) + { + arr[i - 1] = Power3(i) * (160 - i) / 100; + } + + return new LookupGrowthRate(arr); + } + + public static LookupGrowthRate Fluctuating(LevelInt maxLevel) + { + var arr = new uint[maxLevel]; + arr[0] = 0; + uint i = 2; + for (; i < 15; i++) + { + arr[i - 1] = Power3(i) * (uint)((Math.Floor((i + 1) / 3.0) + 24) / 50.0); + } + + for (; i < 36; i++) + { + arr[i - 1] = Power3(i) * (i + 14) / 50; + } + + for (; i < maxLevel + 1; i++) + { + arr[i - 1] = Power3(i) * (i / 2 + 32) / 50; + } + + return new LookupGrowthRate(arr); + } + } } \ No newline at end of file diff --git a/PkmnLibRSharp/StaticData/Libraries/DataLibrary.cs b/PkmnLibRSharp/StaticData/Libraries/DataLibrary.cs index 41800c8..96f4d88 100644 --- a/PkmnLibRSharp/StaticData/Libraries/DataLibrary.cs +++ b/PkmnLibRSharp/StaticData/Libraries/DataLibrary.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using PkmnLibSharp.FFI; using PkmnLibSharp.Utils; @@ -29,7 +30,7 @@ namespace PkmnLibSharp.StaticData.Libraries return GetValueByKey(key) != null; } - public bool TryGetValue(string key, out T value) + public bool TryGetValue(string key, [NotNullWhen(true)] out T value) { if (Cache.ValueCache.TryGetValue(key, out value) && value != null) return true; diff --git a/PkmnLibRSharp/StaticData/Libraries/GrowthRateLibrary.cs b/PkmnLibRSharp/StaticData/Libraries/GrowthRateLibrary.cs index 81f4cf0..361414f 100644 --- a/PkmnLibRSharp/StaticData/Libraries/GrowthRateLibrary.cs +++ b/PkmnLibRSharp/StaticData/Libraries/GrowthRateLibrary.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using JetBrains.Annotations; using PkmnLibSharp.FFI; using PkmnLibSharp.Utils; @@ -21,8 +22,11 @@ namespace PkmnLibSharp.StaticData.Libraries Interface.growth_rate_library_calculate_level(Ptr, growthRate.ToPtr(), experience); [MustUseReturnValue] - public uint CalculateExperience(string growthRate, LevelInt level) => - Interface.growth_rate_library_calculate_experience(Ptr, growthRate.ToPtr(), level); + public uint CalculateExperience(string growthRate, LevelInt level) + { + Debug.Assert(level >= 1); + return Interface.growth_rate_library_calculate_experience(Ptr, growthRate.ToPtr(), level); + } public void AddGrowthRate(string name, GrowthRate growthRate) => Interface.growth_rate_library_add_growth_rate(Ptr, name.ToPtr(), growthRate.TakeOwnershipAndInvalidate()); diff --git a/PkmnLibRSharp/StaticData/Libraries/LibrarySettings.cs b/PkmnLibRSharp/StaticData/Libraries/LibrarySettings.cs index d95b930..5fd38dd 100644 --- a/PkmnLibRSharp/StaticData/Libraries/LibrarySettings.cs +++ b/PkmnLibRSharp/StaticData/Libraries/LibrarySettings.cs @@ -1,31 +1,53 @@ -using System; +using System.Diagnostics; using PkmnLibSharp.FFI; using PkmnLibSharp.Utils; using Interface = PkmnLibSharp.FFI.StaticData.Libraries.LibrarySettings; namespace PkmnLibSharp.StaticData.Libraries { + /// + /// This library holds several misc settings for the library. + /// public class LibrarySettings : ExternPointer { public class CacheData { public LevelInt? MaxLevel { get; internal set; } + public uint? ShinyRate { get; internal set; } } - public LibrarySettings(LevelInt maxLevel) : base(Interface.library_settings_new(maxLevel), true) + /// + /// The highest level a Pokemon can be. + /// + /// The chance of a Pokemon being shiny, as the denominator of a fraction, where the nominator + /// is 1. For example, if this is 1000, then the chance of a Pokemon being shiny is 1/1000. + /// + public LibrarySettings(LevelInt maxLevel, uint shinyRate) { + Debug.Assert(maxLevel >= 1); + Debug.Assert(shinyRate >= 1); + InitializePointer(Interface.library_settings_new(maxLevel, shinyRate), true); } internal LibrarySettings(IdentifiablePointer ptr, bool isOwner) : base(ptr, isOwner) { } + /// + /// The highest level a Pokemon can be. + /// public LevelInt MaxLevel => Cache.MaxLevel ??= Interface.library_settings_maximum_level(Ptr); + /// + /// The chance of a Pokemon being shiny, as the denominator of a fraction, where the nominator + /// is 1. For example, if this is 1000, then the chance of a Pokemon being shiny is 1/1000. + /// + public uint ShinyRate => Cache.ShinyRate ??= Interface.library_settings_shiny_rate(Ptr); + protected override CacheData CreateCache() => new(); protected override void Destructor() => Interface.library_settings_drop(Ptr); - + ~LibrarySettings() { Dispose(); diff --git a/PkmnLibRSharp/StaticData/Libraries/NatureLibrary.cs b/PkmnLibRSharp/StaticData/Libraries/NatureLibrary.cs index e25b435..3d5ce46 100644 --- a/PkmnLibRSharp/StaticData/Libraries/NatureLibrary.cs +++ b/PkmnLibRSharp/StaticData/Libraries/NatureLibrary.cs @@ -33,6 +33,12 @@ namespace PkmnLibSharp.StaticData.Libraries Cache.Natures.Add(name, nature); return nature; } + + public Nature GetRandomNature(ulong seed) + { + return new Nature(Interface.nature_library_get_random_nature(Ptr, seed)); + } + public string GetNatureName(Nature nature) { diff --git a/PkmnLibRSharp/StaticData/Libraries/StaticData.cs b/PkmnLibRSharp/StaticData/Libraries/StaticData.cs index 135e0ea..b9490af 100644 --- a/PkmnLibRSharp/StaticData/Libraries/StaticData.cs +++ b/PkmnLibRSharp/StaticData/Libraries/StaticData.cs @@ -1,4 +1,5 @@ using JetBrains.Annotations; +using PkmnLibSharp.FFI; using PkmnLibSharp.Utils; using Interface = PkmnLibSharp.FFI.StaticData.Libraries.StaticData; @@ -17,6 +18,8 @@ namespace PkmnLibSharp.StaticData.Libraries public NatureLibrary? NatureLibrary { get; internal set; } public AbilityLibrary? AbilityLibrary { get; internal set; } } + + internal StaticData(IdentifiablePointer ptr, bool isOwner) : base(ptr, isOwner){} public StaticData(LibrarySettings settings, SpeciesLibrary speciesLibrary, MoveLibrary moveLibrary, ItemLibrary itemLibrary, GrowthRateLibrary growthRateLibrary, TypeLibrary typeLibrary, @@ -42,16 +45,16 @@ namespace PkmnLibSharp.StaticData.Libraries Cache.ItemLibrary ??= new ItemLibrary(Interface.static_data_items(Ptr), false); public GrowthRateLibrary GrowthRateLibrary => - Cache.GrowthRateLibrary ??= new GrowthRateLibrary(Interface.static_data_items(Ptr), false); + Cache.GrowthRateLibrary ??= new GrowthRateLibrary(Interface.static_data_growth_rates(Ptr), false); public TypeLibrary TypeLibrary => - Cache.TypeLibrary ??= new TypeLibrary(Interface.static_data_items(Ptr), false); + Cache.TypeLibrary ??= new TypeLibrary(Interface.static_data_types(Ptr), false); public NatureLibrary NatureLibrary => - Cache.NatureLibrary ??= new NatureLibrary(Interface.static_data_items(Ptr), false); + Cache.NatureLibrary ??= new NatureLibrary(Interface.static_data_natures(Ptr), false); public AbilityLibrary AbilityLibrary => - Cache.AbilityLibrary ??= new AbilityLibrary(Interface.static_data_items(Ptr), false); + Cache.AbilityLibrary ??= new AbilityLibrary(Interface.static_data_abilities(Ptr), false); protected override CacheData CreateCache() => new(); diff --git a/PkmnLibRSharp/StaticData/Species.cs b/PkmnLibRSharp/StaticData/Species.cs index 8c5f7ae..0f2bb08 100644 --- a/PkmnLibRSharp/StaticData/Species.cs +++ b/PkmnLibRSharp/StaticData/Species.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using PkmnLibSharp.FFI; using PkmnLibSharp.Utils; @@ -37,8 +38,17 @@ namespace PkmnLibSharp.StaticData public float GenderRate => Cache.GenderRate ??= Interface.species_gender_rate(Ptr); public string GrowthRate => Cache.GrowthRate ??= Interface.species_growth_rate(Ptr).PtrString()!; public byte CaptureRate => Cache.CaptureRate ??= Interface.species_capture_rate(Ptr); + + public Form DefaultForm + { + get + { + TryGetForm("default", out var form); + return form!; + } + } - public bool TryGetForm(string formName, out Form? form) + public bool TryGetForm(string formName, [NotNullWhen(true)] out Form? form) { if (Cache.Forms.TryGetValue(formName, out form)) return true; @@ -57,6 +67,8 @@ namespace PkmnLibSharp.StaticData public void AddForm(Form form) => Interface.species_add_form(Ptr, form.Name.ToPtr(), form.TakeOwnershipAndInvalidate()); + public Gender GetRandomGender(ulong seed) => Interface.species_get_random_gender(Ptr, seed); + protected override CacheData CreateCache() => new CacheData(); protected override void Destructor() => Interface.species_drop(Ptr); diff --git a/PkmnLibRSharp/Utils/ExternPointer.cs b/PkmnLibRSharp/Utils/ExternPointer.cs index c7c6bdd..aa7d05b 100644 --- a/PkmnLibRSharp/Utils/ExternPointer.cs +++ b/PkmnLibRSharp/Utils/ExternPointer.cs @@ -94,5 +94,40 @@ namespace PkmnLibSharp.Utils _isInvalidated = true; GC.SuppressFinalize(this); } + + public override bool Equals(object obj) + { + if (obj is ExternPointer other) + { + return Identifier == other.Identifier; + } + return false; + } + + protected bool Equals(ExternPointer other) + { + if (Identifier != 0) + { + return Identifier == other.Identifier; + } + return Ptr == other.Ptr; + } + + public override int GetHashCode() + { + if (Identifier != 0) + return (int)Identifier; + return (int)Ptr; + } + + public static bool operator ==(ExternPointer? left, ExternPointer? right) + { + return Equals(left, right); + } + + public static bool operator !=(ExternPointer? left, ExternPointer? right) + { + return !Equals(left, right); + } } } \ No newline at end of file diff --git a/PkmnLibRSharp/Utils/MiscExtensions.cs b/PkmnLibRSharp/Utils/MiscExtensions.cs new file mode 100644 index 0000000..7a2877c --- /dev/null +++ b/PkmnLibRSharp/Utils/MiscExtensions.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; + +namespace PkmnLibSharp.Utils +{ + internal static class MiscExtensions + { + internal static int IndexOf(this IReadOnlyList list, T element) + { + for (int i = 0; i < list.Count; i++) + { + if (Equals(list[i], element)) + { + return i; + } + } + + return -1; + } + } +} \ No newline at end of file diff --git a/PkmnLibRSharp/Utils/NetStandardCompat.cs b/PkmnLibRSharp/Utils/NetStandardCompat.cs new file mode 100644 index 0000000..6e55499 --- /dev/null +++ b/PkmnLibRSharp/Utils/NetStandardCompat.cs @@ -0,0 +1,208 @@ +// https://github.com/dotnet/runtime/blob/527f9ae88a0ee216b44d556f9bdc84037fe0ebda/src/libraries/System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis/NullableAttributes.cs + +#pragma warning disable +#define INTERNAL_NULLABLE_ATTRIBUTES + +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Diagnostics.CodeAnalysis +{ +#if NETSTANDARD2_0 || NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2 || NET45 || NET451 || NET452 || NET46 || NET461 || NET462 || NET47 || NET471 || NET472 || NET48 + /// Specifies that null is allowed as an input even if the corresponding type disallows it. + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] +#if SYSTEM_PRIVATE_CORELIB + public +#else + internal +#endif + sealed class AllowNullAttribute : Attribute + { } + + /// Specifies that null is disallowed as an input even if the corresponding type allows it. + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)] +#if SYSTEM_PRIVATE_CORELIB + public +#else + internal +#endif + sealed class DisallowNullAttribute : Attribute + { } + + /// Specifies that an output may be null even if the corresponding type disallows it. + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] +#if SYSTEM_PRIVATE_CORELIB + public +#else + internal +#endif + sealed class MaybeNullAttribute : Attribute + { } + + /// Specifies that an output will not be null even if the corresponding type allows it. Specifies that an input argument was not null when the call returns. + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)] +#if SYSTEM_PRIVATE_CORELIB + public +#else + internal +#endif + sealed class NotNullAttribute : Attribute + { } + + /// Specifies that when a method returns , the parameter may be null even if the corresponding type disallows it. + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] +#if SYSTEM_PRIVATE_CORELIB + public +#else + internal +#endif + sealed class MaybeNullWhenAttribute : Attribute + { + /// Initializes the attribute with the specified return value condition. + /// + /// The return value condition. If the method returns this value, the associated parameter may be null. + /// + public MaybeNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; + + /// Gets the return value condition. + public bool ReturnValue { get; } + } + + /// Specifies that when a method returns , the parameter will not be null even if the corresponding type allows it. + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] +#if SYSTEM_PRIVATE_CORELIB + public +#else + internal +#endif + sealed class NotNullWhenAttribute : Attribute + { + /// Initializes the attribute with the specified return value condition. + /// + /// The return value condition. If the method returns this value, the associated parameter will not be null. + /// + public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; + + /// Gets the return value condition. + public bool ReturnValue { get; } + } + + /// Specifies that the output will be non-null if the named parameter is non-null. + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)] +#if SYSTEM_PRIVATE_CORELIB + public +#else + internal +#endif + sealed class NotNullIfNotNullAttribute : Attribute + { + /// Initializes the attribute with the associated parameter name. + /// + /// The associated parameter name. The output will be non-null if the argument to the parameter specified is non-null. + /// + public NotNullIfNotNullAttribute(string parameterName) => ParameterName = parameterName; + + /// Gets the associated parameter name. + public string ParameterName { get; } + } + + /// Applied to a method that will never return under any circumstance. + [AttributeUsage(AttributeTargets.Method, Inherited = false)] +#if SYSTEM_PRIVATE_CORELIB + public +#else + internal +#endif + sealed class DoesNotReturnAttribute : Attribute + { } + + /// Specifies that the method will not return if the associated Boolean parameter is passed the specified value. + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] +#if SYSTEM_PRIVATE_CORELIB + public +#else + internal +#endif + sealed class DoesNotReturnIfAttribute : Attribute + { + /// Initializes the attribute with the specified parameter value. + /// + /// The condition parameter value. Code after the method will be considered unreachable by diagnostics if the argument to + /// the associated parameter matches this value. + /// + public DoesNotReturnIfAttribute(bool parameterValue) => ParameterValue = parameterValue; + + /// Gets the condition parameter value. + public bool ParameterValue { get; } + } +#endif + +#if NETSTANDARD2_0 || NETCOREAPP2_0 || NETCOREAPP2_1 || NETCOREAPP2_2 || NETCOREAPP3_0 || NETCOREAPP3_1 || NET45 || NET451 || NET452 || NET46 || NET461 || NET462 || NET47 || NET471 || NET472 || NET48 + /// Specifies that the method or property will ensure that the listed field and property members have not-null values. + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] +#if SYSTEM_PRIVATE_CORELIB + public +#else + internal +#endif + sealed class MemberNotNullAttribute : Attribute + { + /// Initializes the attribute with a field or property member. + /// + /// The field or property member that is promised to be not-null. + /// + public MemberNotNullAttribute(string member) => Members = new[] { member }; + + /// Initializes the attribute with the list of field and property members. + /// + /// The list of field and property members that are promised to be not-null. + /// + public MemberNotNullAttribute(params string[] members) => Members = members; + + /// Gets field or property member names. + public string[] Members { get; } + } + + /// Specifies that the method or property will ensure that the listed field and property members have not-null values when returning with the specified return value condition. + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] +#if SYSTEM_PRIVATE_CORELIB + public +#else + internal +#endif + sealed class MemberNotNullWhenAttribute : Attribute + { + /// Initializes the attribute with the specified return value condition and a field or property member. + /// + /// The return value condition. If the method returns this value, the associated parameter will not be null. + /// + /// + /// The field or property member that is promised to be not-null. + /// + public MemberNotNullWhenAttribute(bool returnValue, string member) + { + ReturnValue = returnValue; + Members = new[] { member }; + } + + /// Initializes the attribute with the specified return value condition and list of field and property members. + /// + /// The return value condition. If the method returns this value, the associated parameter will not be null. + /// + /// + /// The list of field and property members that are promised to be not-null. + /// + public MemberNotNullWhenAttribute(bool returnValue, params string[] members) + { + ReturnValue = returnValue; + Members = members; + } + + /// Gets the return value condition. + public bool ReturnValue { get; } + + /// Gets field or property member names. + public string[] Members { get; } + } +#endif +} diff --git a/PkmnLibRSharp/libpkmn_lib.so b/PkmnLibRSharp/libpkmn_lib.so index 8361eca..b6b595f 100755 --- a/PkmnLibRSharp/libpkmn_lib.so +++ b/PkmnLibRSharp/libpkmn_lib.so @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:39cf9d7a787d19ba082f2a6fd036ef384292c76f0018077951e504e0f2738d6a -size 118266520 +oid sha256:ecda6513be44db7d7e36a51a6f2c8c4e1e61bc016d6db19e37bdec048a8cae27 +size 77909048 diff --git a/PkmnLibRSharpTests/DynamicData/PokemonTests.cs b/PkmnLibRSharpTests/DynamicData/PokemonTests.cs new file mode 100644 index 0000000..3159891 --- /dev/null +++ b/PkmnLibRSharpTests/DynamicData/PokemonTests.cs @@ -0,0 +1,130 @@ +using System; +using NUnit.Framework; +using PkmnLibSharp.DynamicData; +using PkmnLibSharp.DynamicData.Libraries; +using PkmnLibSharp.StaticData; +using PkmnLibSharp.StaticData.Libraries; + +namespace PkmnLibRSharpTests.DynamicData +{ + public class PokemonTests + { + [Test] + public void Pokemon_GetLibrary() + { + using var library = GetLibrary(); + using var pokemon = new PokemonBuilder(library, "testSpecies", 100).Build(); + Assert.AreEqual(library, pokemon.Library); + } + + [Test] + public void Pokemon_GetSpecies() + { + using var library = GetLibrary(); + using var pokemon = new PokemonBuilder(library, "testSpecies", 100).Build(); + Assert.AreEqual(library.StaticData.SpeciesLibrary["testSpecies"], pokemon.Species); + } + + [Test] + public void Pokemon_GetForm() + { + using var library = GetLibrary(); + using var pokemon = new PokemonBuilder(library, "testSpecies", 100).Build(); + Assert.AreEqual(library.StaticData.SpeciesLibrary["testSpecies"].DefaultForm, pokemon.Form); + } + + + [Test] + public void Pokemon_GetDisplaySpecies() + { + using var library = GetLibrary(); + using var pokemon = new PokemonBuilder(library, "testSpecies", 100).Build(); + Assert.AreEqual(library.StaticData.SpeciesLibrary["testSpecies"], pokemon.Species); + } + + [Test] + public void Pokemon_GetDisplayForm() + { + using var library = GetLibrary(); + using var pokemon = new PokemonBuilder(library, "testSpecies", 100).Build(); + Assert.AreEqual(library.StaticData.SpeciesLibrary["testSpecies"].DefaultForm, pokemon.Form); + } + + [Test] + public void Pokemon_GetLevel() + { + using var library = GetLibrary(); + using var pokemon = new PokemonBuilder(library, "testSpecies", 100).Build(); + Assert.AreEqual(100, pokemon.Level); + } + + [Test] + public void Pokemon_GetExperience() + { + using var library = GetLibrary(); + using var pokemon = new PokemonBuilder(library, "testSpecies", 100).Build(); + Assert.AreEqual(library.StaticData.GrowthRateLibrary.CalculateExperience("testGrowthrate", 100), + pokemon.Experience); + } + + [Test] + public void Pokemon_GetUniqueIdentifier() + { + using var library = GetLibrary(); + using var pokemon = new PokemonBuilder(library, "testSpecies", 100).WithIdentifier(1000).Build(); + Assert.AreEqual(1000, pokemon.UniqueIdentifier); + } + + [Test] + public void Pokemon_GetGender() + { + using var library = GetLibrary(); + using var pokemon = new PokemonBuilder(library, "testSpecies", 100).WithGender(Gender.Male).Build(); + Assert.AreEqual(Gender.Male, pokemon.Gender); + } + + [Test] + public void Pokemon_GetColoring() + { + using var library = GetLibrary(); + using var pokemon = new PokemonBuilder(library, "testSpecies", 100).ForceShiny(true).Build(); + Assert.AreEqual(1, pokemon.Coloring); + } + + private static DynamicLibrary GetLibrary() + { + using var settings = new LibrarySettings(100, 4096); + using var speciesLibrary = new SpeciesLibrary(0); + FillSpeciesLibrary(speciesLibrary); + using var moves = new MoveLibrary(0); + using var items = new ItemLibrary(0); + using var growthRates = new GrowthRateLibrary(0); + using var gr = CommonGrowthRates.Erratic(100); + growthRates.AddGrowthRate("testGrowthrate", gr); + using var types = new TypeLibrary(0); + using var natures = new NatureLibrary(0); + natures.LoadNature("testNature", Nature.NeutralNature()); + using var abilities = new AbilityLibrary(0); + using var library = new PkmnLibSharp.StaticData.Libraries.StaticData(settings, speciesLibrary, moves, items, + growthRates, types, natures, abilities); + + using var statCalc = new Gen7BattleStatCalculator(); + using var damageLib = new Gen7DamageLibrary(false); + using var miscLib = new Gen7MiscLibrary(); + using var scriptResolver = new WasmScriptResolver(); + + return new DynamicLibrary(library, statCalc, damageLib, miscLib, scriptResolver); + } + + private static void FillSpeciesLibrary(SpeciesLibrary speciesLibrary) + { + using var stats = new StaticStatisticSet(5, 10, 30, 20, 2, 0); + using var moves = new LearnableMoves(); + using var form = new Form("foobar", 0.2f, 5.8f, 300, new TypeIdentifier[] { new(1), new(2) }, stats, + new[] { "foo", "bar" }, new[] { "set" }, moves, Array.Empty()); + using var species = + new Species(10, "testSpecies", 0.2f, "testGrowthrate", 120, form, Array.Empty()); + speciesLibrary.Add(species.Name, species); + } + } +} \ No newline at end of file diff --git a/PkmnLibRSharpTests/StaticData/Libraries/CommonGrowthRateTests.cs b/PkmnLibRSharpTests/StaticData/Libraries/CommonGrowthRateTests.cs new file mode 100644 index 0000000..6bf8739 --- /dev/null +++ b/PkmnLibRSharpTests/StaticData/Libraries/CommonGrowthRateTests.cs @@ -0,0 +1,94 @@ +using NUnit.Framework; +using PkmnLibSharp.StaticData; + +namespace PkmnLibRSharpTests.StaticData.Libraries +{ + public class CommonGrowthRateTests + { + [TestCase(1, 0)] + [TestCase(20, 12_800)] + [TestCase(40, 76_800)] + [TestCase(60, 194_400)] + [TestCase(68, 257_834)] + [TestCase(70, 276_458)] + [TestCase(80, 378_880)] + [TestCase(90, 491_346)] + [TestCase(98, 583_539)] + [TestCase(100, 600_000)] + public void ErraticExperience(byte level, int experience) + { + using var growthRate = CommonGrowthRates.Erratic(100); + Assert.AreEqual(experience, growthRate.CalculateExperience(level)); + } + + [TestCase(1, 0)] + [TestCase(20, 6_400)] + [TestCase(40, 51_200)] + [TestCase(60, 172_800)] + [TestCase(70, 274_400)] + [TestCase(80, 409_600)] + [TestCase(90, 583_200)] + [TestCase(100, 800_000)] + public void FastExperience(byte level, int experience) + { + using var growthRate = CommonGrowthRates.Fast(100); + Assert.AreEqual(experience, growthRate.CalculateExperience(level)); + } + + [TestCase(1, 0)] + [TestCase(20, 8_000)] + [TestCase(40, 64_000)] + [TestCase(60, 216_000)] + [TestCase(70, 343_000)] + [TestCase(80, 512_000)] + [TestCase(90, 729_000)] + [TestCase(100, 1_000_000)] + public void MediumFastExperience(byte level, int experience) + { + using var growthRate = CommonGrowthRates.MediumFast(100); + Assert.AreEqual(experience, growthRate.CalculateExperience(level)); + } + + [TestCase(1, 0)] + [TestCase(20, 5_460)] + [TestCase(40, 56_660)] + [TestCase(60, 211_060)] + [TestCase(70, 344_960)] + [TestCase(80, 526_260)] + [TestCase(90, 762_160)] + [TestCase(100, 1_059_860)] + public void MediumSlowExperience(byte level, int experience) + { + using var growthRate = CommonGrowthRates.MediumSlow(100); + Assert.AreEqual(experience, growthRate.CalculateExperience(level)); + } + + [TestCase(1, 0)] + [TestCase(20, 10_000)] + [TestCase(40, 80_000)] + [TestCase(60, 270_000)] + [TestCase(70, 428_750)] + [TestCase(80, 640_000)] + [TestCase(90, 911_250)] + [TestCase(100, 1_250_000)] + public void SlowExperience(byte level, int experience) + { + using var growthRate = CommonGrowthRates.Slow(100); + Assert.AreEqual(experience, growthRate.CalculateExperience(level)); + } + + [TestCase(1, 0)] + [TestCase(20, 5_440)] + [TestCase(40, 66_560)] + [TestCase(60, 267_840)] + [TestCase(70, 459_620)] + [TestCase(80, 737_280)] + [TestCase(90, 1_122_660)] + [TestCase(100, 1_640_000)] + public void FluctuatingExperience(byte level, int experience) + { + using var growthRate = CommonGrowthRates.Fluctuating(100); + Assert.AreEqual(experience, growthRate.CalculateExperience(level)); + } + } +} \ No newline at end of file diff --git a/PkmnLibRSharpTests/StaticData/Libraries/LibrarySettingsTests.cs b/PkmnLibRSharpTests/StaticData/Libraries/LibrarySettingsTests.cs index e6c0e06..423428d 100644 --- a/PkmnLibRSharpTests/StaticData/Libraries/LibrarySettingsTests.cs +++ b/PkmnLibRSharpTests/StaticData/Libraries/LibrarySettingsTests.cs @@ -8,13 +8,13 @@ namespace PkmnLibRSharpTests.StaticData.Libraries [Test] public void CreateLibrarySettings() { - using var settings = new LibrarySettings(100); + using var settings = new LibrarySettings(100, 4096); } [Test] public void GetMaxLevel() { - using var settings = new LibrarySettings(100); + using var settings = new LibrarySettings(100, 4096); Assert.AreEqual(100, settings.MaxLevel); } } diff --git a/PkmnLibRSharpTests/StaticData/Libraries/StaticDataTests.cs b/PkmnLibRSharpTests/StaticData/Libraries/StaticDataTests.cs index affe3d3..1bde594 100644 --- a/PkmnLibRSharpTests/StaticData/Libraries/StaticDataTests.cs +++ b/PkmnLibRSharpTests/StaticData/Libraries/StaticDataTests.cs @@ -7,7 +7,7 @@ namespace PkmnLibRSharpTests.StaticData.Libraries { private PkmnLibSharp.StaticData.Libraries.StaticData Build() { - using var settings = new LibrarySettings(100); + using var settings = new LibrarySettings(100, 4096); using var species = new SpeciesLibrary(0); using var moves = new MoveLibrary(0); using var items = new ItemLibrary(0);