PKHeX/PKHeX.Core/Legality/Restrictions/Vivillon3DS.cs
Kurt 9f249ef4d6 Add bounds check for API purposes
chris doing things funkily, might as well range check ourselves before blindly accessing the array

Use a const value for vivillon max wild form value
2021-08-19 18:59:24 -07:00

286 lines
12 KiB
C#

using System;
using System.Linq;
using static PKHeX.Core.Region3DSFlags;
namespace PKHeX.Core
{
/// <summary>
/// Provides information for Vivillon origins with respect to the 3DS game data.
/// </summary>
public static class Vivillon3DS
{
private sealed class CountryTable
{
public readonly byte BaseForm;
public readonly byte CountryID;
public readonly FormSubregionTable[] SubRegionForms;
internal CountryTable(byte country, byte form, params FormSubregionTable[] subs)
{
BaseForm = form;
CountryID = country;
SubRegionForms = subs;
}
}
private sealed class FormSubregionTable
{
public readonly byte Form;
public readonly byte[] Regions;
internal FormSubregionTable(byte form, byte[] regions)
{
Form = form;
Regions = regions;
}
}
/// <summary>
/// List of valid regions as bitflags indexed by Vivillon form.
/// </summary>
private static readonly Region3DSFlags[] VivillonRegionTable =
{
/* 0 Icy Snow */ Americas | Europe,
/* 1 Polar */ Americas | Europe | China,
/* 2 Tundra */ Japan | Europe,
/* 3 Continental */ Americas | Europe | China | Korea | Taiwan,
/* 4 Garden */ Europe,
/* 5 Elegant */ Japan,
/* 6 Meadow */ Europe,
/* 7 Modern */ Americas,
/* 8 Marine */ Americas | Europe,
/* 9 Archipelago */ Americas | Europe,
/*10 High Plains */ Americas | Europe | China,
/*11 Sandstorm */ Americas | Europe,
/*12 River */ Europe,
/*13 Monsoon */ Japan | Europe | China | Taiwan,
/*14 Savanna */ Americas,
/*15 Sun */ Americas | Europe,
/*16 Ocean */ Americas | Europe,
/*17 Jungle */ Americas | Europe,
};
public static Region3DSFlags GetConsoleRegionFlag(int consoleRegion) => (Region3DSFlags)(1 << consoleRegion);
/// <summary>
/// List of valid countries for each Vivillon form.
/// </summary>
private static readonly byte[][] VivillonCountryTable =
{
/* 0 Icy Snow */ new byte[] {018,076,096,100,107},
/* 1 Polar */ new byte[] {010,018,020,049,076,096,100,107,160},
/* 2 Tundra */ new byte[] {001,074,081,096},
/* 3 Continental */ new byte[] {010,067,073,074,075,077,078,084,087,094,096,097,100,107,128,136,144,160,169},
/* 4 Garden */ new byte[] {065,082,095,110,125},
/* 5 Elegant */ new byte[] {001},
/* 6 Meadow */ new byte[] {066,077,078,083,086,088,105,108,122,127},
/* 7 Modern */ new byte[] {018,049,186},
/* 8 Marine */ new byte[] {020,064,066,068,070,071,073,077,078,079,080,083,089,090,091,098,099,100,101,102,103,105,109,123,124,126,184,185},
/* 9 Archipelago */ new byte[] {008,009,011,012,013,017,021,023,024,028,029,032,034,035,036,037,038,043,044,045,047,048,049,051,052,077,085,104},
/*10 High Plains */ new byte[] {018,036,049,100,109,113,160},
/*11 Sandstorm */ new byte[] {072,109,118,119,120,121,168,174},
/*12 River */ new byte[] {065,069,085,093,104,105,114,115,116,117},
/*13 Monsoon */ new byte[] {001,128,160,169},
/*14 Savanna */ new byte[] {010,015,016,041,042,050},
/*15 Sun */ new byte[] {014,019,026,030,033,036,039,065,085,092,104,106,111,112},
/*16 Ocean */ new byte[] {049,077},
/*17 Jungle */ new byte[] {016,021,022,025,027,031,040,042,046,052,077,153,156,169},
};
/// <summary>
/// List of valid subregions for countries that can have multiple Vivillon forms.
/// </summary>
/// <remarks>BaseForm is the form for no selected subregion.</remarks>
private static readonly CountryTable[] RegionFormTable =
{
new(001, 05, // Japan: Elegant
new FormSubregionTable(02, new byte[] {03,04}),
new FormSubregionTable(13, new byte[] {48})),
new(010, 14, // Argentina: Savanna
new FormSubregionTable(01, new byte[] {21,24}),
new FormSubregionTable(03, new byte[] {06,12,14,16,17,19,20})),
new(016, 14, // Brazil: Savanna
new FormSubregionTable(17, new byte[] {03,05,06,21,22})),
new(018, 01, // Canada: Polar
new FormSubregionTable(00, new byte[] {12,13,14}),
new FormSubregionTable(07, new byte[] {05}),
new FormSubregionTable(10, new byte[] {04})),
new(020, 08, // Chile: Marine
new FormSubregionTable(01, new byte[] {12})),
new(021, 17, // Colombia: Jungle
new FormSubregionTable(09, new byte[] {07,19,20})),
new(036, 15, // Mexico: Sun
new FormSubregionTable(10, new byte[] {03,04,05,08,09,11,12,15,19,20,23,25,26,27,29,33})),
new(042, 14, // Peru: Savanna
new FormSubregionTable(17, new byte[] {03,08,12,15,16,17,21,23,25,26})),
new(049, 07, // USA: Modern
new FormSubregionTable(01, new byte[] {03,09,21,23,24,32,33,36,40,41,48,50}),
new FormSubregionTable(09, new byte[] {53}),
new FormSubregionTable(10, new byte[] {06,07,08,15,28,34,35,39,46,49}),
new FormSubregionTable(16, new byte[] {13})),
new(052, 09, // Venezuela: Archipelago
new FormSubregionTable(17, new byte[] {03,04,05,07,08,09,10,11,13,15,17,19,21})),
new(065, 12, // Australia: River
new FormSubregionTable(04, new byte[] {07}),
new FormSubregionTable(15, new byte[] {04})),
new(066, 08, // Austria: Marine
new FormSubregionTable(06, new byte[] {10})),
new(073, 03, // Czech Republic: Continental
new FormSubregionTable(08, new byte[] {04,05,13,14,15})),
new(074, 03, // Denmark: Continental
new FormSubregionTable(02, new byte[] {18,24})),
new(076, 00, // Finland: Icy Snow
new FormSubregionTable(01, new byte[] {27})),
new(077, 06, // France: Meadow
new FormSubregionTable(03, new byte[] {18}),
new FormSubregionTable(08, new byte[] {04,06,08,19}),
new FormSubregionTable(09, new byte[] {24,25}),
new FormSubregionTable(16, new byte[] {27}),
new FormSubregionTable(17, new byte[] {26})),
new(078, 03, // Germany: Continental
new FormSubregionTable(06, new byte[] {04,12,13}),
new FormSubregionTable(08, new byte[] {05})),
new(083, 08, // Italy: Marine
new FormSubregionTable(06, new byte[] {03,04,05,06})),
new(085, 12, // Lesotho: River
new FormSubregionTable(09, new byte[] {10}),
new FormSubregionTable(15, new byte[] {06,07,08,09})),
new(096, 03, // Norway: Continental
new FormSubregionTable(00, new byte[] {11,26}),
new FormSubregionTable(01, new byte[] {12,15,16,17,20,22}),
new FormSubregionTable(02, new byte[] {13,14,19})),
new(100, 03, // Russia: Continental
new FormSubregionTable(00, new byte[] {14,22,34,38,40,52,53,66,88}),
new FormSubregionTable(01, new byte[] {11,12,13,16,19,21,23,26,27,32,35,36,37,39,41,43,44,48,49,50,54,55,56,57,58,60,61,62,67,68,70,74,75,76,77,80,81,82,83,84,90,91}),
new FormSubregionTable(08, new byte[] {42,64}),
new FormSubregionTable(10, new byte[] {10,15,20,24,25,28,30,33,71,73,85})),
new(104, 12, // South Africa: River
new FormSubregionTable(15, new byte[] {06,09}),
new FormSubregionTable(09, new byte[] {03,05})),
new(105, 08, // Spain: Marine
new FormSubregionTable(06, new byte[] {11}),
new FormSubregionTable(12, new byte[] {07})),
new(107, 03, // Sweden: Continental
new FormSubregionTable(00, new byte[] {10,11}),
new FormSubregionTable(01, new byte[] {09,13,17})),
new(109, 11, // Turkey: Sandstorm
new FormSubregionTable(08, new byte[] {03,04,05,17,20,23,24,26,27,28,30,33,34,36,41,42,44,47,48,50,52,55,57,60,62,63,65,66,69,71,73,75,80,83}),
new FormSubregionTable(10, new byte[] {53,54,70,79})),
new(128, 13, // Taiwan: Monsoon
new FormSubregionTable(03, new byte[] {24,25,26})),
new(160, 03, // China: Continental
new FormSubregionTable(01, new byte[] {13,19,20}),
new FormSubregionTable(10, new byte[] {30,32}),
new FormSubregionTable(13, new byte[] {10,26,29,33})),
new(169, 13, // India: Monsoon
new FormSubregionTable(03, new byte[] {06,09,10,21,36}),
new FormSubregionTable(17, new byte[] {12})),
};
public const int MaxWildFormID = 17; // 0-17 valid form indexes
/// <summary>
/// Compares the Vivillon pattern against its console region to determine if the pattern is legal.
/// </summary>
public static bool IsPatternValid(int form, int consoleRegion)
{
if ((uint)form > MaxWildFormID)
return false;
var permit = GetConsoleRegionFlag(consoleRegion);
return VivillonRegionTable[form].HasFlag(permit);
}
/// <summary>
/// Compares the Vivillon pattern against its country and subregion to determine if the pattern could have been natively obtained.
/// </summary>
/// <param name="form">Alternate Form Pattern</param>
/// <param name="country">Country ID</param>
/// <param name="region">Subregion ID</param>
/// <returns>True if valid</returns>
public static bool IsPatternNative(int form, byte country, byte region)
{
if ((uint)form > MaxWildFormID)
return false;
if (!VivillonCountryTable[form].Contains(country))
return false; // Country mismatch
var ct = Array.Find(RegionFormTable, t => t.CountryID == country);
if (ct == null) // empty = one form for country
return true; // No subregion table, already checked if Country can have this form
if (ct.BaseForm == form)
return !ct.SubRegionForms.Any(e => e.Regions.Contains(region)); //true if Mainform not in other specific region
return ct.SubRegionForms.Any(e => e.Form == form && e.Regions.Contains(region));
}
/// <summary>
/// Gets a compatible Vivillon pattern based on its country and subregion.
/// </summary>
/// <param name="country">Country ID</param>
/// <param name="region">Subregion ID</param>
public static int GetPattern(byte country, byte region)
{
var ct = Array.Find(RegionFormTable, t => t.CountryID == country);
if (ct == null) // empty = no forms referenced
return GetPattern(country);
foreach (var sub in ct.SubRegionForms)
{
if (sub.Regions.Contains(region))
return sub.Form;
}
return ct.BaseForm;
}
private static int GetPattern(byte country)
{
var form = Array.FindIndex(VivillonCountryTable, z => z.Contains(country));
return Math.Max(0, form);
}
}
/// <summary>
/// Console Region Flags for the 3DS.
/// </summary>
/// <remarks>Not to be confused with <see cref="Region3DSIndex"/>.</remarks>
[Flags]
public enum Region3DSFlags : ushort
{
None,
Japan = 1,
Americas = 1 << 1,
Europe = 1 << 2,
China = 1 << 4,
Korea = 1 << 5,
Taiwan = 1 << 6,
}
}