Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add StopCondition for specific amount of flawless IVs #120

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions SysBot.Pokemon/Helpers/TargetFlawlessIVsConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System;
using System.ComponentModel;
using System.Globalization;

namespace SysBot.Pokemon;

public class TargetFlawlessIVsConverter(Type type) : EnumConverter(type)

Check warning on line 7 in SysBot.Pokemon/Helpers/TargetFlawlessIVsConverter.cs

View workflow job for this annotation

GitHub Actions / build

Parameter 'Type type' is captured into the state of the enclosing type and its value is also passed to the base constructor. The value might be captured by the base class as well.

Check warning on line 7 in SysBot.Pokemon/Helpers/TargetFlawlessIVsConverter.cs

View workflow job for this annotation

GitHub Actions / build

Parameter 'Type type' is captured into the state of the enclosing type and its value is also passed to the base constructor. The value might be captured by the base class as well.
{
public override object? ConvertTo(ITypeDescriptorContext? context, CultureInfo? culture, object? value, Type destinationType)
{
if (value == null) return base.ConvertTo(context, culture, value, destinationType);

var name = Enum.GetName(type, value);
if (string.IsNullOrWhiteSpace(name))
return value.ToString();

var fieldInfo = type.GetField(name);
if (fieldInfo == null)
return value.ToString();

return Attribute.GetCustomAttribute(fieldInfo, typeof(DescriptionAttribute)) is DescriptionAttribute dna
? dna.Description
: value.ToString();
}

public override object? ConvertFrom(ITypeDescriptorContext? context, CultureInfo? culture, object value)
{
foreach (var fieldInfo in type.GetFields())
{
if (Attribute.GetCustomAttribute(fieldInfo, typeof(DescriptionAttribute)) is DescriptionAttribute dna && (string)value == dna.Description)
return Enum.Parse(type, fieldInfo.Name);
}

return Enum.Parse(type, (string)value);
}
}
76 changes: 70 additions & 6 deletions SysBot.Pokemon/Settings/StopConditionSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,16 @@ public class StopConditionSettings
[Category(StopConditions)]
public class SearchCondition
{
public override string ToString() => $"{(!IsEnabled ? $"{Nature}, condition is disabled" : $"{Nature}, {StopOnSpecies}, {TargetMinIVs} - {TargetMaxIVs}")}";
public override string ToString()
{
if (!IsEnabled) return $"{Nature}, condition is disabled";

var ivsStr = FlawlessIVs == TargetFlawlessIVsType.Disabled
? $"{TargetMinIVs} - {TargetMaxIVs}"
: $"Flawless IVs: {Convert(FlawlessIVs)}";

return $"{Nature}, {StopOnSpecies}, {ivsStr}";
}

[Category(StopConditions), DisplayName("1. Enabled")]
public bool IsEnabled { get; set; } = true;
Expand All @@ -58,10 +67,14 @@ public class SearchCondition
[Category(StopConditions), DisplayName("4. Gender")]
public TargetGenderType GenderTarget { get; set; } = TargetGenderType.Any;

[Category(StopConditions), DisplayName("5. Minimum accepted IVs")]
[Category(StopConditions), DisplayName("5. Minimum flawless IVs")]
[TypeConverter(typeof(TargetFlawlessIVsConverter))]
public TargetFlawlessIVsType FlawlessIVs { get; set; } = TargetFlawlessIVsType.Disabled;

[Category(StopConditions), DisplayName("6. Minimum accepted IVs")]
public string TargetMinIVs { get; set; } = "";

[Category(StopConditions), DisplayName("6. Maximum accepted IVs")]
[Category(StopConditions), DisplayName("7. Maximum accepted IVs")]
public string TargetMaxIVs { get; set; } = "";
}

Expand Down Expand Up @@ -112,13 +125,13 @@ public static bool EncounterFound<T>(T pk, StopConditionSettings settings, IRead
return true;

return settings.SearchConditions.Any(s =>
MatchIVs(pkIVsArr, s.TargetMinIVs, s.TargetMaxIVs) &&
(MatchIVs(pkIVsArr, s.TargetMinIVs, s.TargetMaxIVs, s.FlawlessIVs) || MatchFlawlessIVs(pkIVsArr, s.FlawlessIVs)) &&
(s.Nature == pk.Nature || s.Nature == Nature.Random) &&
(s.StopOnSpecies == (Species)pk.Species || s.StopOnSpecies == Species.None) &&
MatchGender(s.GenderTarget, (Gender)pk.Gender) &&
s.IsEnabled);
}

private static bool MatchGender(TargetGenderType target, Gender result)
{
return target switch
Expand All @@ -131,8 +144,28 @@ private static bool MatchGender(TargetGenderType target, Gender result)
};
}

private static bool MatchIVs(IReadOnlyList<int> pkIVs, string targetMinIVsStr, string targetMaxIVsStr)
private static bool MatchFlawlessIVs(IReadOnlyList<int> pkIVs, TargetFlawlessIVsType targetFlawlessIVs)
{
var count = pkIVs.Count(iv => iv == 31);

return targetFlawlessIVs switch
{
TargetFlawlessIVsType.Disabled => false,
TargetFlawlessIVsType._0 => count >= 0,
TargetFlawlessIVsType._1 => count >= 1,
TargetFlawlessIVsType._2 => count >= 2,
TargetFlawlessIVsType._3 => count >= 3,
TargetFlawlessIVsType._4 => count >= 4,
TargetFlawlessIVsType._5 => count >= 5,
TargetFlawlessIVsType._6 => count == 6,
_ => throw new ArgumentOutOfRangeException(nameof(targetFlawlessIVs), targetFlawlessIVs, null)
};
}

private static bool MatchIVs(IReadOnlyList<int> pkIVs, string targetMinIVsStr, string targetMaxIVsStr, TargetFlawlessIVsType targetFlawlessIVs)
{
if (targetFlawlessIVs != TargetFlawlessIVsType.Disabled) return false;

var targetMinIVs = ReadTargetIVs(targetMinIVsStr, true);
var targetMaxIVs = ReadTargetIVs(targetMaxIVsStr, false);

Expand Down Expand Up @@ -215,6 +248,25 @@ public static string GetMarkName(IRibbonIndex pk)
}
return "";
}

// Quite ugly solution to display DescriptionAttribute
private static string Convert<T>(T value) where T : Enum
{
var k = typeof(T);
var g = k.Name;

var name = Enum.GetName(typeof(T), value);
if (string.IsNullOrWhiteSpace(name))
return value.ToString();

var fieldInfo = typeof(T).GetField(name);
if (fieldInfo == null)
return value.ToString();

return Attribute.GetCustomAttribute(fieldInfo, typeof(DescriptionAttribute)) is DescriptionAttribute dna
? dna.Description
: value.ToString();
}
}

public enum TargetShinyType
Expand All @@ -233,3 +285,15 @@ public enum TargetGenderType
Female, // Match female only
Genderless, // Match genderless only
}

public enum TargetFlawlessIVsType
{
Disabled,
[Description("0")] _0,
[Description("1")] _1,
[Description("2")] _2,
[Description("3")] _3,
[Description("4")] _4,
[Description("5")] _5,
[Description("6")] _6,
}
47 changes: 3 additions & 44 deletions SysBot.Tests/GenerateTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using PKHeX.Core;
using SysBot.Pokemon;
using Xunit;
using LegalitySettings = SysBot.Pokemon.LegalitySettings;

namespace SysBot.Tests;

Expand Down Expand Up @@ -63,48 +64,7 @@ public void TestAbilityTwitch(string set, int abilNumber)
}
}

[Theory]
[InlineData(InvalidSpec)]
public void ShouldNotGenerate(string set)
{
_ = AutoLegalityWrapper.GetTrainerInfo<PK8>();
var s = ShowdownUtil.ConvertToShowdown(set);
s.Should().BeNull();
}

[Theory]
[InlineData(Torkoal2, 2)]
[InlineData(Charizard4, 4)]
public void TestAbility(string set, int abilNumber)
{
var sav = AutoLegalityWrapper.GetTrainerInfo<PK8>();
for (int i = 0; i < 10; i++)
{
var s = new ShowdownSet(set);
var template = AutoLegalityWrapper.GetTemplate(s);
var pk = sav.GetLegal(template, out _);
pk.AbilityNumber.Should().Be(abilNumber);
}
}

[Theory]
[InlineData(Torkoal2, 2)]
[InlineData(Charizard4, 4)]
public void TestAbilityTwitch(string set, int abilNumber)
{
var sav = AutoLegalityWrapper.GetTrainerInfo<PK8>();
for (int i = 0; i < 10; i++)
{
var twitch = set.Replace("\r\n", " ").Replace("\n", " ");
var s = ShowdownUtil.ConvertToShowdown(twitch);
var template = s == null ? null : AutoLegalityWrapper.GetTemplate(s);
var pk = template == null ? null : sav.GetLegal(template, out _);
pk.Should().NotBeNull();
pk!.AbilityNumber.Should().Be(abilNumber);
}
}

private const string Gengar =
private const string Gengar =
@"Gengar-Gmax @ Life Orb
Ability: Cursed Body
Shiny: Yes
Expand Down Expand Up @@ -159,7 +119,6 @@ Timid Nature
- Solar Beam
- Beat Up";

private const string InvalidSpec =
private const string InvalidSpec =
"(Pikachu)";
}
}
Binary file not shown.
Binary file not shown.
67 changes: 67 additions & 0 deletions SysBot.Tests/StopConditionsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using PKHeX.Core;
using SysBot.Pokemon;
using Xunit;

namespace SysBot.Tests;

public class StopConditionsTests
{
[Theory]
[InlineData(TargetFlawlessIVsType.Any, true)]
[InlineData(TargetFlawlessIVsType._0, true)]
[InlineData(TargetFlawlessIVsType._1, true)]
[InlineData(TargetFlawlessIVsType._2, true)]
[InlineData(TargetFlawlessIVsType._3, true)]
[InlineData(TargetFlawlessIVsType._4, true)]
[InlineData(TargetFlawlessIVsType._5, false)]
[InlineData(TargetFlawlessIVsType._6, false)]
public async Task TestEncounterFound_Scorbunny_FlawlessIVs(TargetFlawlessIVsType targetFlawlessIVs, bool expected)
{
// Arrange
var bytes = await GetResource("0813 - Scorbunny - 4F320450C78B.pk9");

var pk9 = new PK9(bytes);
var sc = new StopConditionSettings { SearchConditions = [new() { FlawlessIVs = targetFlawlessIVs }] };

// Act
var result = StopConditionSettings.EncounterFound(pk9, sc, null);

// Assert
Assert.Equal(expected, result);
}

[Theory]
[InlineData("x/x/x/x/x/x", "x/x/x/x/x/x", true)]
[InlineData("x/31/31/31/x/31", "x/31/31/31/x/31", true)]
[InlineData("31/x/x/x/x/x", "31/x/x/x/x/x", false)]
public async Task TestEncounterFound_Scorbunny_MatchIVs(string targetMinIVs, string targetMaxIVs, bool expected)
{
// Arrange
var bytes = await GetResource("0813 - Scorbunny - 4F320450C78B.pk9");

var pk9 = new PK9(bytes);
var sc = new StopConditionSettings { SearchConditions = [new() { TargetMinIVs = targetMinIVs, TargetMaxIVs = targetMaxIVs, FlawlessIVs = TargetFlawlessIVsType.Any }] };

// Act
var result = StopConditionSettings.EncounterFound(pk9, sc, null);

// Assert
Assert.Equal(expected, result);
}

private static async Task<byte[]> GetResource(string file)
{
var info = Assembly.GetExecutingAssembly().GetName();

await using var stream = Assembly
.GetExecutingAssembly()
.GetManifestResourceStream($"{info.Name}.Resources.{file}")!;

using var memoryStream = new MemoryStream();
await stream.CopyToAsync(memoryStream);
return memoryStream.ToArray();
}
}
10 changes: 9 additions & 1 deletion SysBot.Tests/SysBot.Tests.csproj
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<None Remove="Resources\*" />
</ItemGroup>

<ItemGroup>
<EmbeddedResource Include="Resources\*" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="FluentAssertions" Version="6.12.2" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
Expand Down
Loading