HOME 2.0.0: Handle conversion behavior & restrictions (#3506)

* Revises legality checks to account for traveling between the three game islands (PLA/BDSP/SWSH)
* Adds conversion mechanisms between the three formats, as well as flexible conversion options to backfill missing data (thanks GameFreak/ILCA for opting for lossy conversion instead of updating the games).
* Adds API abstractions for HOME data storage format (EKH/PKH format 1, aka EH1/PH1).
* Revises some APIs for better usage:
  - `PKM` now exposes a `Context` to indicate the isolation context for legality purposes.
  - Some method signatures have changed to accept `Context` or `GameVersion` instead of a vague `int` for Generation.
  - Evolution History is now tracked in the Legality parse for specific contexts, rather than only per generation.
This commit is contained in:
Kurt 2022-05-30 21:43:52 -07:00 committed by GitHub
parent 5ae34854c7
commit 5bcccc6d92
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
183 changed files with 4189 additions and 863 deletions

View File

@ -43,7 +43,7 @@ public static class BatchMods
new ComplexSuggestion(PROP_RIBBONS, (_, value, info) => BatchModifications.SetSuggestedRibbons(info, value)),
new ComplexSuggestion(nameof(PKM.Met_Location), (_, _, info) => BatchModifications.SetSuggestedMetData(info)),
new ComplexSuggestion(nameof(PKM.CurrentLevel), (_, _, info) => BatchModifications.SetMinimumCurrentLevel(info)),
new ComplexSuggestion(PROP_CONTESTSTATS, p => p is IContestStatsMutable, (_, value, info) => BatchModifications.SetContestStats(info.Entity, info.Legality.EncounterMatch, value)),
new ComplexSuggestion(PROP_CONTESTSTATS, p => p is IContestStatsMutable, (_, value, info) => BatchModifications.SetContestStats(info.Entity, info.Legality, value)),
new ComplexSuggestion(PROP_MOVEMASTERY, (_, value, info) => BatchModifications.SetSuggestedMasteryData(info, value)),
};

View File

@ -105,14 +105,14 @@ public static ModifyResult SetEVs(PKM pk)
/// Sets the contests stats as requested.
/// </summary>
/// <param name="pk">Pokémon to modify.</param>
/// <param name="enc">Encounter matched to.</param>
/// <param name="la">Legality Information matched to.</param>
/// <param name="option">Option to apply with</param>
public static ModifyResult SetContestStats(PKM pk, IEncounterTemplate enc, string option)
public static ModifyResult SetContestStats(PKM pk, LegalityAnalysis la, string option)
{
if (option.Length != 0 && option[BatchEditing.CONST_SUGGEST.Length..] is not "0")
pk.SetMaxContestStats(enc);
pk.SetMaxContestStats(la.EncounterMatch, la.Info.EvoChainsAllGens);
else
pk.SetSuggestedContestStats(enc);
pk.SetSuggestedContestStats(la.EncounterMatch, la.Info.EvoChainsAllGens);
return ModifyResult.Modified;
}
}

View File

@ -0,0 +1,10 @@
namespace PKHeX.Core;
public static class LocationEdits
{
public static int GetNoneLocation(PKM pk) => pk switch
{
PB8 => Locations.Default8bNone,
_ => 0,
};
}

View File

@ -17,7 +17,22 @@ public static class Pokerus
/// </summary>
/// <param name="pk">Entity to check</param>
/// <returns>True if Pokérus exists in the game format, or can be transmitted to the entity via another game.</returns>
public static bool IsObtainable(PKM pk) => pk is not PA8; // don't care about PK1, not stored in the data.
public static bool IsObtainable(PKM pk) => pk switch
{
PA8 pa8 => HasVisitedBDSPorSWSH(pa8),
_ => true,
};
private static bool HasVisitedBDSPorSWSH(PA8 pk)
{
if (pk.IsUntraded)
return false;
if (PersonalTable.BDSP.IsPresentInGame(pk.Species, pk.Form))
return true;
if (PersonalTable.SWSH.IsPresentInGame(pk.Species, pk.Form))
return true;
return false;
}
/// <summary>
/// Checks if the Pokérus value for Strain is possible to have on the input entity.

View File

@ -91,9 +91,10 @@ public void ReadTemplateIfNoEntity(string path)
#region Utility
private static SaveFile GetBlank(PKM pk)
{
var ver = (GameVersion)pk.Version;
if (ver.GetGeneration() != pk.Format)
ver = GameUtil.GetVersion(pk.Format);
var ctx = pk.Context;
var ver = ctx.GetSingleGameVersion();
if (pk is { Format: 1, Japanese: true })
ver = GameVersion.BU;
return SaveUtil.GetBlankSAV(ver, pk.OT_Name, (LanguageID)pk.Language);
}

View File

@ -31,7 +31,7 @@ protected BoxManipBase(BoxManipType type, Func<SaveFile, bool> usable)
new BoxManipSort(BoxManipType.SortLevelReverse, EntitySorting.OrderByDescendingLevel),
new BoxManipSort(BoxManipType.SortDate, EntitySorting.OrderByDateObtained, s => s.Generation >= 4),
new BoxManipSort(BoxManipType.SortName, list => list.OrderBySpeciesName(GameInfo.Strings.Species)),
new BoxManipSort(BoxManipType.SortFavorite, list => list.OrderByCustom(pk => pk is PB7 {Favorite: true}), s => s is SAV7b),
new BoxManipSort(BoxManipType.SortFavorite, list => list.OrderByCustom(pk => pk is PB7 {Favorite: true}), s => s.BlankPKM is IFavorite),
new BoxManipSortComplex(BoxManipType.SortParty, (list, sav, start) => list.BubbleUp(sav, i => ((SAV7b)sav).Blocks.Storage.IsParty(i), start), s => s is SAV7b),
new BoxManipSort(BoxManipType.SortShiny, list => list.OrderByCustom(pk => !pk.IsShiny)),
new BoxManipSort(BoxManipType.SortRandom, list => list.OrderByCustom(_ => Util.Rand32())),

View File

@ -62,12 +62,12 @@ public enum GameVersion
Pt = 12,
/// <summary>
/// Pokémon Heart Gold (NDS)
/// Pokémon HeartGold (NDS)
/// </summary>
HG = 7,
/// <summary>
/// Pokémon Soul Silver (NDS)
/// Pokémon SoulSilver (NDS)
/// </summary>
SS = 8,
#endregion
@ -139,7 +139,7 @@ public enum GameVersion
#endregion
/// <summary>
/// Pokémon GO (GO -> Lets Go transfers)
/// Pokémon GO (GO -> Let's Go/HOME transfers)
/// </summary>
GO = 34,
@ -184,12 +184,12 @@ public enum GameVersion
#region Nintendo Switch
/// <summary>
/// Pokémon Let's Go Pikachu (NX)
/// Pokémon: Let's Go, Pikachu! (NX)
/// </summary>
GP = 42,
/// <summary>
/// Pokémon Let's Go Eevee (NX)
/// Pokémon: Let's Go, Eevee! (NX)
/// </summary>
GE = 43,
@ -204,6 +204,10 @@ public enum GameVersion
SH = 45,
// HOME = 46,
/// <summary>
/// Pokémon Legends: Arceus (NX)
/// </summary>
PLA = 47,
/// <summary>
@ -311,7 +315,7 @@ public enum GameVersion
DPPt,
/// <summary>
/// Pokémon Heart Gold &amp; Soul Silver [<see cref="SAV4"/>] identifier.
/// Pokémon HeartGold &amp; SoulSilver [<see cref="SAV4"/>] identifier.
/// </summary>
/// <see cref="HG"/>
/// <see cref="SS"/>

View File

@ -151,6 +151,13 @@ private static List<ComboItem> CreateGen8(GameStrings s)
Util.AddCBWithOffset(locations, s.metSWSH_30000, 30000, Locations8.Met3);
Util.AddCBWithOffset(locations, s.metSWSH_40000, 40000, Locations8.Met4);
Util.AddCBWithOffset(locations, s.metSWSH_60000, 60000, Locations8.Met6);
// Add in the BDSP+PLA magic met locations.
locations.Add(new ComboItem($"{s.EggName} (BD/SP)", Locations.HOME_SWSHBDSPEgg));
locations.Add(new ComboItem(s.gamelist[(int)BD], Locations.HOME_SWBD));
locations.Add(new ComboItem(s.gamelist[(int)SP], Locations.HOME_SHSP));
locations.Add(new ComboItem(s.gamelist[(int)PLA], Locations.HOME_SWLA));
return locations;
}
@ -162,13 +169,20 @@ private static List<ComboItem> CreateGen8a(GameStrings s)
Util.AddCBWithOffset(locations, s.metLA_30000, 30000, Locations8a.Met3);
Util.AddCBWithOffset(locations, s.metLA_40000, 40000, Locations8a.Met4);
Util.AddCBWithOffset(locations, s.metLA_60000, 60000, Locations8a.Met6);
// Add in the BDSP+PLA magic met locations.
locations.Add(new ComboItem($"{s.EggName} (BD/SP)", Locations.HOME_SWSHBDSPEgg));
locations.Add(new ComboItem(s.gamelist[(int)BD], Locations.HOME_SWBD));
locations.Add(new ComboItem(s.gamelist[(int)SP], Locations.HOME_SHSP));
locations.Add(new ComboItem(s.gamelist[(int)PLA], Locations.HOME_SWLA));
return locations;
}
private static List<ComboItem> CreateGen8b(GameStrings s)
{
// Manually add invalid (-1) location from SWSH as ID 65535
var locations = new List<ComboItem> { new(s.metSWSH_00000[0], unchecked((ushort)Locations.Default8bNone)) };
var locations = new List<ComboItem> { new(s.metSWSH_00000[0], Locations.Default8bNone) };
Util.AddCBWithOffset(locations, s.metBDSP_60000, 60000, Locations.Daycare5);
Util.AddCBWithOffset(locations, s.metBDSP_30000, 30000, Locations.LinkTrade6);
Util.AddCBWithOffset(locations, s.metBDSP_00000, 00000, Locations8b.Met0);
@ -193,29 +207,45 @@ public IReadOnlyList<ComboItem> GetLocationList(GameVersion version, int current
if (egg && version < W && currentGen >= 5)
return MetGen4;
var result = GetLocationListInternal(version, currentGen);
// Insert the BDSP none location if the format requires it.
if (currentGen == 8 && !BDSP.Contains(version))
{
var list = new List<ComboItem>(result.Count + 1);
list.AddRange(result);
list.Insert(1, new ComboItem($"{list[0].Text} (BD/SP)", Locations.Default8bNone));
result = list;
}
return result;
}
private IReadOnlyList<ComboItem> GetLocationListInternal(GameVersion version, int currentGen)
{
return version switch
{
CXD when currentGen == 3 => MetGen3CXD,
R or S when currentGen == 3 => Partition1(MetGen3, z => z <= 87), // Ferry
E when currentGen == 3 => Partition1(MetGen3, z => z is <= 87 or >= 197 and <= 212), // Trainer Hill
CXD when currentGen == 3 => MetGen3CXD,
R or S when currentGen == 3 => Partition1(MetGen3, z => z <= 87), // Ferry
E when currentGen == 3 => Partition1(MetGen3, z => z is <= 87 or >= 197 and <= 212), // Trainer Hill
FR or LG when currentGen == 3 => Partition1(MetGen3, z => z is > 87 and < 197), // Celadon Dept.
D or P when currentGen == 4 => Partition2(MetGen4, z => z <= 111, 4), // Battle Park
Pt when currentGen == 4 => Partition2(MetGen4, z => z <= 125, 4), // Rock Peak Ruins
D or P when currentGen == 4 => Partition2(MetGen4, z => z <= 111, 4), // Battle Park
Pt when currentGen == 4 => Partition2(MetGen4, z => z <= 125, 4), // Rock Peak Ruins
HG or SS when currentGen == 4 => Partition2(MetGen4, z => z is > 125 and < 234, 4), // Celadon Dept.
B or W => MetGen5,
B2 or W2 => Partition2(MetGen5, z => z <= 116), // Abyssal Ruins
X or Y => Partition2(MetGen6, z => z <= 168), // Unknown Dungeon
B or W => MetGen5,
B2 or W2 => Partition2(MetGen5, z => z <= 116), // Abyssal Ruins
X or Y => Partition2(MetGen6, z => z <= 168), // Unknown Dungeon
OR or AS => Partition2(MetGen6, z => z is > 168 and <= 354), // Secret Base
SN or MN => Partition2(MetGen7, z => z < 200), // Outer Cape
SN or MN => Partition2(MetGen7, z => z < 200), // Outer Cape
US or UM
or RD or BU or GN or YW
or GD or SI or C => Partition2(MetGen7, z => z < 234), // Dividing Peak Tunnel
or RD or BU or GN or YW
or GD or SI or C => Partition2(MetGen7, z => z < 234), // Dividing Peak Tunnel
GP or GE or GO => Partition2(MetGen7GG, z => z <= 54), // Pokémon League
SW or SH => Partition2(MetGen8, z => z < 400),
BD or SP => Partition2(MetGen8b, z => z < 628),
PLA => Partition2(MetGen8a, z => z < 512),
_ => GetLocationListModified(version, currentGen),
_ => new List<ComboItem>(GetLocationListModified(version, currentGen)),
};
static IReadOnlyList<ComboItem> Partition1(IReadOnlyList<ComboItem> list, Func<int, bool> criteria)
@ -224,7 +254,8 @@ static IReadOnlyList<ComboItem> Partition1(IReadOnlyList<ComboItem> list, Func<i
return GetOrderedList(list, result, criteria);
}
static IReadOnlyList<ComboItem> GetOrderedList(IReadOnlyList<ComboItem> list, ComboItem[] result, Func<int, bool> criteria, int start = 0)
static IReadOnlyList<ComboItem> GetOrderedList(IReadOnlyList<ComboItem> list, ComboItem[] result,
Func<int, bool> criteria, int start = 0)
{
// store values that match criteria at the next available position of the array
// store non-matches starting at the end. reverse before returning
@ -237,12 +268,14 @@ static IReadOnlyList<ComboItem> GetOrderedList(IReadOnlyList<ComboItem> list, Co
else
result[end--] = item;
}
// since the non-matches are reversed in order, we swap them back since we know where they end up at.
Array.Reverse(result, start, list.Count - start);
return result;
}
static IReadOnlyList<ComboItem> Partition2(IReadOnlyList<ComboItem> list, Func<int, bool> criteria, int keepFirst = 3)
static IReadOnlyList<ComboItem> Partition2(IReadOnlyList<ComboItem> list, Func<int, bool> criteria,
int keepFirst = 3)
{
var result = new ComboItem[list.Count];
for (int i = 0; i < keepFirst; i++)

View File

@ -100,10 +100,34 @@ public static class Locations
public const int BugCatchingContest4 = 207;
/// <summary>
/// -1
/// </summary>
public const int Default8bNone = -1;
public const int HOME_SHSP = 59998; // SP traded to (SW)SH
public const int HOME_SWBD = 59999; // BD traded to SW(SH)
public const int HOME_SWLA = 60000; // PLA traded to SW(SH)
public const int HOME_SWSHBDSPEgg = 65534; // -2 = 8bNone-1..
public const int Default8bNone = 65535;
public static int GetVersionSWSH(int ver) => (GameVersion)ver switch
{
GameVersion.PLA => (int)GameVersion.SW,
GameVersion.BD => (int)GameVersion.SW,
GameVersion.SP => (int)GameVersion.SH,
_ => ver,
};
public static int GetMetSWSH(int loc, int ver) => (GameVersion)ver switch
{
GameVersion.PLA => HOME_SWLA,
GameVersion.BD => HOME_SWBD,
GameVersion.SP => HOME_SHSP,
_ => loc,
};
public static bool IsValidMetBDSP(int loc, int ver) => loc switch
{
HOME_SHSP when ver == (int)GameVersion.SH => true,
HOME_SWBD when ver == (int)GameVersion.SW => true,
_ => false,
};
public static int TradedEggLocationNPC(int generation) => generation switch
{
@ -125,6 +149,7 @@ public static class Locations
public static bool IsPtHGSSLocation(int location) => location is > 111 and < 2000;
public static bool IsPtHGSSLocationEgg(int location) => location is > 2010 and < 3000;
public static bool IsEventLocation3(int location) => location is 255;
public static bool IsEventLocation4(int location) => location is >= 3000 and <= 3076;
public static bool IsEventLocation5(int location) => location is > 40000 and < 50000;
@ -146,14 +171,6 @@ public static bool IsEggLocationBred4(int loc, GameVersion ver)
public static bool IsEggLocationBred6(int loc) => loc is Daycare5 or LinkTrade6;
public static bool IsEggLocationBred8b(int loc) => loc is Daycare8b or LinkTrade6NPC;
public static bool IsNoneLocation(GameVersion ver, int location) => GetNoneLocation(ver) == (short)location;
public static int GetNoneLocation(GameVersion ver) => ver switch
{
GameVersion.BD or GameVersion.SP => Default8bNone,
_ => 0,
};
public static int GetDaycareLocation(int generation, GameVersion version) => generation switch
{
1 or 2 or 3 => 0,

View File

@ -35,19 +35,21 @@ internal static EncounterArea7g[] GetArea(BinLinkerAccessor data)
return areas;
}
private const int meta = 4;
private const int entrySize = (2 * sizeof(int)) + 2;
private static EncounterArea7g GetArea(ReadOnlySpan<byte> data)
{
var species = ReadUInt16LittleEndian(data);
byte form = (byte)(species >> 11);
species &= 0x3FF;
var form = data[2];
//var import = (EntityFormatDetected)data[3];
var result = new EncounterSlot7GO[(data.Length - 2) / entrySize];
data = data[meta..];
var result = new EncounterSlot7GO[data.Length / entrySize];
var area = new EncounterArea7g(species, form, result);
for (int i = 0; i < result.Length; i++)
{
var offset = (i * entrySize) + 2;
var offset = i * entrySize;
var entry = data.Slice(offset, entrySize);
result[i] = ReadSlot(entry, area, species, form);
}

View File

@ -35,29 +35,29 @@ internal static EncounterArea8g[] GetArea(BinLinkerAccessor data)
return areas;
}
private const int meta = 4;
private const int entrySize = (2 * sizeof(int)) + 2;
private static EncounterArea8g GetArea(ReadOnlySpan<byte> data)
{
var species = ReadUInt16LittleEndian(data);
byte form = (byte)(species >> 11);
species &= 0x3FF;
var form = data[2];
var import = (PogoImportFormat)data[3];
var group = GetGroup(species, form);
var result = new EncounterSlot8GO[(data.Length - 2) / entrySize];
data = data[meta..];
var result = new EncounterSlot8GO[data.Length / entrySize];
var area = new EncounterArea8g(species, form, result);
for (int i = 0; i < result.Length; i++)
{
var offset = (i * entrySize) + 2;
var offset = i * entrySize;
var entry = data.Slice(offset, entrySize);
result[i] = ReadSlot(entry, area, species, form, group);
result[i] = ReadSlot(entry, area, species, form, import);
}
return area;
}
private static EncounterSlot8GO ReadSlot(ReadOnlySpan<byte> entry, EncounterArea8g area, ushort species, byte form, GameVersion group)
private static EncounterSlot8GO ReadSlot(ReadOnlySpan<byte> entry, EncounterArea8g area, ushort species, byte form, PogoImportFormat format)
{
int start = ReadInt32LittleEndian(entry);
int end = ReadInt32LittleEndian(entry[4..]);
@ -65,32 +65,7 @@ private static EncounterSlot8GO ReadSlot(ReadOnlySpan<byte> entry, EncounterArea
var shiny = (Shiny)(sg & 0x3F);
var gender = (Gender)(sg >> 6);
var type = (PogoType)entry[9];
return new EncounterSlot8GO(area, species, form, start, end, shiny, gender, type, group);
}
private static GameVersion GetGroup(int species, int form)
{
// Transfer Rules:
// If it can exist in LGP/E, it uses LGP/E's move data for the initial moves.
// Else, if it can exist in SW/SH, it uses SW/SH's move data for the initial moves.
// Else, it must exist in US/UM, thus it uses US/UM's moves.
var pt8 = PersonalTable.SWSH;
var ptGG = PersonalTable.GG;
var pi8 = (PersonalInfoSWSH)pt8[species];
if (pi8.IsPresentInGame)
{
bool lgpe = (species is (<= 151 or 808 or 809)) && (form == 0 || ptGG[species].HasForm(form));
return lgpe ? GameVersion.GG : GameVersion.SWSH;
}
if (species <= Legal.MaxSpeciesID_7_USUM)
{
bool lgpe = species <= 151 && (form == 0 || ptGG[species].HasForm(form));
return lgpe ? GameVersion.GG : GameVersion.USUM;
}
throw new ArgumentOutOfRangeException(nameof(species));
return new EncounterSlot8GO(area, species, form, start, end, shiny, gender, type, format);
}
public override IEnumerable<EncounterSlot> GetMatchingSlots(PKM pkm, EvoCriteria[] chain)
@ -101,7 +76,7 @@ public override IEnumerable<EncounterSlot> GetMatchingSlots(PKM pkm, EvoCriteria
// Find the first chain that has slots defined.
// Since it is possible to evolve before transferring, we only need the highest evolution species possible.
// PoGoEncTool has already extrapolated the evolutions to separate encounters!
var sf = Array.Find(chain, z => z.Species == Species && (z.Form == Form || FormInfo.IsFormChangeable(Species, Form, z.Form, pkm.Format)));
var sf = FindCriteriaToIterate(pkm, chain);
if (sf == default)
yield break;
@ -133,5 +108,25 @@ public override IEnumerable<EncounterSlot> GetMatchingSlots(PKM pkm, EvoCriteria
if (deferredIV != null)
yield return deferredIV;
}
private EvoCriteria FindCriteriaToIterate(PKM pkm, EvoCriteria[] chain)
{
foreach (var evo in chain)
{
if (evo.Species != Species)
continue;
if (evo.Form == Form)
return evo;
// Check for form mismatches
if (FormInfo.IsFormChangeable(Species, Form, evo.Form, pkm.Format))
return evo;
if (Species == (int)Core.Species.Burmy)
return evo;
break;
}
return default;
}
}
}

View File

@ -141,8 +141,11 @@ public static bool CanHatchAsEgg(int species, int form, GameVersion game)
{
// Sanity check form for origin
var pt = GameData.GetPersonal(game);
if ((uint)species > pt.MaxSpeciesID)
return false;
var entry = pt.GetFormEntry(species, form);
if (entry is PersonalInfoSWSH { IsPresentInGame: false })
if (!entry.IsPresentInGame)
return false;
return form < entry.FormCount || (species == (int)Rotom && form <= 5);
}

View File

@ -110,6 +110,23 @@ internal static int GetMaxSpeciesOrigin(PKM pkm)
_ => -1,
};
internal static int GetMaxSpeciesOrigin(EntityContext context) => context switch
{
EntityContext.Gen1 => MaxSpeciesID_1,
EntityContext.Gen2 => MaxSpeciesID_2,
EntityContext.Gen3 => MaxSpeciesID_3,
EntityContext.Gen4 => MaxSpeciesID_4,
EntityContext.Gen5 => MaxSpeciesID_5,
EntityContext.Gen6 => MaxSpeciesID_6,
EntityContext.Gen7 => MaxSpeciesID_7_USUM,
EntityContext.Gen8 => MaxSpeciesID_8_R2,
EntityContext.Gen7b => MaxSpeciesID_7b,
EntityContext.Gen8a => MaxSpeciesID_8a,
EntityContext.Gen8b => MaxSpeciesID_8b,
_ => -1,
};
internal static int GetMaxSpeciesOrigin(int generation) => generation switch
{
1 => MaxSpeciesID_1,
@ -169,43 +186,93 @@ internal static int GetMaxSpeciesOrigin(PKM pkm)
internal static bool HasVisitedORAS(this PKM pkm, int species) => pkm.InhabitedGeneration(6, species) && (pkm.AO || !pkm.IsUntraded);
internal static bool HasVisitedUSUM(this PKM pkm, int species) => pkm.InhabitedGeneration(7, species) && (pkm.USUM || !pkm.IsUntraded);
internal static bool HasVisitedBDSP(this PKM pkm, int species)
internal static bool HasVisitedSWSH(this PKM pkm, EvoCriteria[] evos)
{
if (!pkm.InhabitedGeneration(8, species))
if (pkm.SWSH)
return true;
if (pkm.IsUntraded)
return false;
if (pkm.BDSP && pkm.Species is (int)Species.Spinda or (int)Species.Nincada)
return false;
var pt = PersonalTable.SWSH;
foreach (var evo in evos)
{
if (pt.IsPresentInGame(evo.Species, evo.Form))
return true;
}
return false;
}
internal static bool HasVisitedBDSP(this PKM pkm, EvoCriteria[] evos)
{
if (pkm.BDSP)
return true;
if (pkm.IsUntraded)
return false;
var pi = (PersonalInfoBDSP)PersonalTable.BDSP[species];
return pi.IsPresentInGame;
if (pkm.Species is (int)Species.Spinda or (int)Species.Nincada)
return false;
var pt = PersonalTable.BDSP;
foreach (var evo in evos)
{
if (pt.IsPresentInGame(evo.Species, evo.Form))
return true;
}
return false;
}
internal static bool HasVisitedLA(this PKM pkm, int species)
internal static bool HasVisitedLA(this PKM pkm, EvoCriteria[] evos)
{
if (!pkm.InhabitedGeneration(8, species))
return false;
if (pkm.LA)
return true;
if (pkm.IsUntraded)
return false;
var pi = (PersonalInfoLA)PersonalTable.LA[species];
return pi.IsPresentInGame;
var pt = PersonalTable.LA;
foreach (var evo in evos)
{
if (pt.IsPresentInGame(evo.Species, evo.Form))
return true;
}
return false;
}
/// <summary>
/// Indicates if the moveset is restricted to only the original version.
/// Checks if the moveset is restricted to only a specific version.
/// </summary>
/// <param name="pkm">Entity to check</param>
/// <returns></returns>
internal static bool IsMovesetRestricted(this PKM pkm)
internal static (bool IsRestricted, GameVersion Game) IsMovesetRestricted(this PKM pkm) => pkm switch
{
if (pkm.IsUntraded)
PB7 => (true, GameVersion.GP),
PA8 => (true, GameVersion.PLA),
PB8 => (true, GameVersion.BD),
PK8 when pkm.Version > (int)GameVersion.SH => (true, GameVersion.SH), // Permit past generation moves.
IBattleVersion { BattleVersion: not 0 } bv => (true, (GameVersion)bv.BattleVersion),
_ when pkm.IsUntraded => (true, (GameVersion)pkm.Version),
_ => (false, GameVersion.Any),
};
/// <summary>
/// Checks if the relearn moves should be wiped.
/// </summary>
/// <remarks>Already checked for generations &lt; 8.</remarks>
/// <param name="pkm">Entity to check</param>
internal static bool IsOriginalMovesetDeleted(this PKM pkm)
{
if (pkm is PA8 {LA: false} or PB8 {BDSP: false})
return true;
if (pkm.BDSP)
return true;
if (pkm.LA)
if (pkm.IsNative)
{
if (pkm is PK8 {LA: true} or PK8 {BDSP: true})
return true;
return false;
}
if (pkm is IBattleVersion { BattleVersion: not 0 })
return true;
return false;
}
@ -218,23 +285,6 @@ public static bool IsPPUpAvailable(PKM pkm)
return pkm is not PA8;
}
/// <summary>
/// Indicates if the moveset is restricted to only the original version.
/// </summary>
/// <param name="pkm">Entity to check</param>
/// <param name="gen">Generation the move check is for</param>
/// <returns></returns>
internal static bool IsMovesetRestricted(this PKM pkm, int gen)
{
if (pkm.IsMovesetRestricted())
return true;
return gen switch
{
7 when pkm.Version is (int)GameVersion.GO or (int)GameVersion.GP or (int)GameVersion.GE => true,
_ => false,
};
}
public static int GetMaxLengthOT(int generation, LanguageID language) => language switch
{
LanguageID.ChineseS or LanguageID.ChineseT => 6,
@ -270,5 +320,12 @@ public static bool GetIsFixedIVSequenceValidNoRand(ReadOnlySpan<int> IVs, PKM pk
}
return true;
}
public static bool IsMetAsEgg(PKM pkm) => pkm switch
{
PA8 or PK8 => pkm.Egg_Location is not 0 || (pkm.BDSP && pkm.Egg_Day is not 0),
PB8 pb8 => pb8.Egg_Location is not Locations.Default8bNone,
_ => pkm.Egg_Location is not 0,
};
}
}

View File

@ -41,11 +41,13 @@ private static bool IsValidDate(DateTime obtained, (DateTime Start, DateTime End
{
WC8 wc8 => Result(IsValidDateWC8(wc8.CardID, obtained)),
WA8 wa8 => Result(IsValidDateWA8(wa8.CardID, obtained)),
WB8 wb8 => Result(IsValidDateWB8(wb8.CardID, obtained)),
_ => throw new ArgumentOutOfRangeException(nameof(enc)),
};
public static bool IsValidDateWC8(int cardID, DateTime obtained) => WC8Gifts.TryGetValue(cardID, out var time) && IsValidDate(obtained, time);
public static bool IsValidDateWA8(int cardID, DateTime obtained) => WA8Gifts.TryGetValue(cardID, out var time) && IsValidDate(obtained, time);
public static bool IsValidDateWB8(int cardID, DateTime obtained) => WB8Gifts.TryGetValue(cardID, out var time) && IsValidDate(obtained, time);
/// <summary>
/// Minimum date the gift can be received.
@ -69,13 +71,31 @@ private static bool IsValidDate(DateTime obtained, (DateTime Start, DateTime End
{9014, new DateTime(2021, 06, 17)}, // Gigantamax Squirtle
};
private static readonly DateTime Never = DateTime.MaxValue;
/// <summary>
/// Minimum date the gift can be received.
/// </summary>
public static readonly Dictionary<int, (DateTime Start, DateTime End)> WA8Gifts = new()
{
{0138, (new(2022, 01, 27), new(2022, 11, 01))}, // Poké Center Happiny
{0138, (new(2022, 01, 27), new(2023, 02, 01))}, // Poké Center Happiny
{0301, (new(2022, 02, 04), new(2022, 02, 24))}, // プロポチャ Piplup
{0801, (new(2022, 02, 25), new(2022, 06, 01))}, // Teresa Roca Hisuian Growlithe
{1201, (new(2022, 05, 31), new(2022, 08, 01))}, // 전이마을 Regigigas
{1202, (new(2022, 05, 31), new(2022, 08, 01))}, // 빛나's Piplup
{9018, (new(2022, 05, 18), Never)}, // Hidden Ability Rowlet
{9019, (new(2022, 05, 18), Never)}, // Hidden Ability Cyndaquil
{9020, (new(2022, 05, 18), Never)}, // Hidden Ability Oshawott
};
/// <summary>
/// Minimum date the gift can be received.
/// </summary>
public static readonly Dictionary<int, (DateTime Start, DateTime End)> WB8Gifts = new()
{
{9015, (new(2022, 05, 18), Never)}, // Hidden Ability Turtwig
{9016, (new(2022, 05, 18), Never)}, // Hidden Ability Chimchar
{9017, (new(2022, 05, 18), Never)}, // Hidden Ability Piplup
};
}

View File

@ -74,12 +74,14 @@ public virtual string LongName
public PKM ConvertToPKM(ITrainerInfo sav, EncounterCriteria criteria)
{
var pk = EntityBlank.GetBlank(Generation, Version);
var pk = GetBlank();
sav.ApplyTo(pk);
ApplyDetails(sav, criteria, pk);
return pk;
}
protected virtual PKM GetBlank() => EntityBlank.GetBlank(Generation, Version);
protected virtual void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk)
{
var version = this.GetCompatibleVersion((GameVersion) sav.Game);

View File

@ -49,7 +49,7 @@ public override string GetConditionString(out bool valid)
private int[] GetDexNavMoves()
{
var et = EvolutionTree.GetEvolutionTree(6);
var et = EvolutionTree.Evolves6;
var sf = et.GetBaseSpeciesForm(Species, Form);
return MoveEgg.GetEggMoves(6, sf & 0x7FF, sf >> 11, Version);
}

View File

@ -14,6 +14,8 @@ public EncounterSlot7GO(EncounterArea7g area, int species, int form, int start,
{
}
protected override PKM GetBlank() => new PB7();
protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk)
{
base.ApplyDetails(sav, criteria, pk);
@ -27,6 +29,21 @@ protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteri
pb.ResetCP();
}
protected override void SetPINGA(PKM pk, EncounterCriteria criteria)
{
var pi = pk.PersonalInfo;
int gender = criteria.GetGender(-1, pi);
int nature = (int)criteria.GetNature(Nature.Random);
var ability = criteria.GetAbilityFromNumber(Ability);
pk.PID = Util.Rand32();
pk.Nature = pk.StatNature = nature;
pk.Gender = gender;
pk.RefreshAbility(ability);
pk.SetRandomIVsGO();
base.SetPINGA(pk, criteria);
}
protected override void SetEncounterMoves(PKM pk, GameVersion version, int level)
{
var moves = MoveLevelUp.GetEncounterMoves(pk, level, GameVersion.GG);

View File

@ -1,10 +1,12 @@
using System;
namespace PKHeX.Core
{
/// <summary>
/// Encounter Slot representing data transferred to <see cref="GameVersion.Gen8"/> (HOME).
/// <inheritdoc cref="EncounterSlotGO" />
/// </summary>
public sealed record EncounterSlot8GO : EncounterSlotGO
public sealed record EncounterSlot8GO : EncounterSlotGO, IFixedOTFriendship
{
public override int Generation => 8;
@ -13,14 +15,14 @@ public sealed record EncounterSlot8GO : EncounterSlotGO
/// </summary>
/// <remarks>
/// Future game releases might change this value.
/// With respect to date legality, new dates might be incompatible with initial <seealso cref="OriginGroup"/> values.
/// With respect to date legality, new dates might be incompatible with initial <seealso cref="OriginFormat"/> values.
/// </remarks>
public GameVersion OriginGroup { get; }
public PogoImportFormat OriginFormat { get; }
public EncounterSlot8GO(EncounterArea8g area, int species, int form, int start, int end, Shiny shiny, Gender gender, PogoType type, GameVersion originGroup)
public EncounterSlot8GO(EncounterArea8g area, int species, int form, int start, int end, Shiny shiny, Gender gender, PogoType type, PogoImportFormat originFormat)
: base(area, species, form, start, end, shiny, gender, type)
{
OriginGroup = originGroup;
OriginFormat = originFormat;
}
/// <summary>
@ -34,21 +36,84 @@ public bool IsBallValid(Ball ball, int currentSpecies)
return Type.IsBallValid(ball);
}
protected override PKM GetBlank() => OriginFormat switch
{
PogoImportFormat.PK7 => new PK8(),
PogoImportFormat.PB7 => new PB7(),
PogoImportFormat.PK8 => new PK8(),
PogoImportFormat.PA8 => new PA8(),
_ => throw new ArgumentOutOfRangeException(nameof(OriginFormat)),
};
private PersonalInfo GetPersonal() => OriginFormat switch
{
PogoImportFormat.PK7 => PersonalTable.USUM.GetFormEntry(Species, Form),
PogoImportFormat.PB7 => PersonalTable.GG.GetFormEntry(Species, Form),
PogoImportFormat.PK8 => PersonalTable.SWSH.GetFormEntry(Species, Form),
PogoImportFormat.PA8 => PersonalTable.LA.GetFormEntry(Species, Form),
_ => throw new ArgumentOutOfRangeException(nameof(OriginFormat)),
};
internal GameVersion OriginGroup => OriginFormat switch
{
PogoImportFormat.PK7 => GameVersion.USUM,
PogoImportFormat.PB7 => GameVersion.GG,
PogoImportFormat.PK8 => GameVersion.SWSH,
PogoImportFormat.PA8 => GameVersion.PLA,
_ => throw new ArgumentOutOfRangeException(nameof(OriginFormat)),
};
protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk)
{
var pk8 = (PK8)pk;
pk8.HT_Name = "PKHeX";
pk8.HT_Language = 2;
pk8.CurrentHandler = 1;
pk.HT_Name = "PKHeX";
pk.CurrentHandler = 1;
if (pk is IHandlerLanguage l)
l.HT_Language = 2;
base.ApplyDetails(sav, criteria, pk);
var ball = Type.GetValidBall();
if (ball != Ball.None)
pk.Ball = (int)ball;
pk8.SetRandomEC();
pk8.HeightScalar = PokeSizeUtil.GetRandomScalar();
pk8.WeightScalar = PokeSizeUtil.GetRandomScalar();
if (pk is IScaledSize s)
{
s.HeightScalar = PokeSizeUtil.GetRandomScalar();
s.WeightScalar = PokeSizeUtil.GetRandomScalar();
}
if (OriginFormat is PogoImportFormat.PA8)
{
var pa8 = (PA8)pk;
pa8.ResetHeight();
pa8.ResetWeight();
pa8.HeightScalarCopy = pa8.HeightScalar;
}
pk.OT_Friendship = OT_Friendship;
pk.SetRandomEC();
}
protected override void SetPINGA(PKM pk, EncounterCriteria criteria)
{
var pi = GetPersonal();
if (OriginFormat is PogoImportFormat.PK7)
pk.EXP = Experience.GetEXP(LevelMin, pi.EXPGrowth);
int gender = criteria.GetGender(-1, pi);
int nature = (int)criteria.GetNature(Nature.Random);
var ability = criteria.GetAbilityFromNumber(Ability);
pk.PID = Util.Rand32();
pk.Nature = pk.StatNature = nature;
pk.Gender = gender;
pk.AbilityNumber = 1 << ability;
var abilities = pi.Abilities;
if ((uint)ability < abilities.Count)
pk.Ability = abilities[ability];
pk.SetRandomIVsGO();
base.SetPINGA(pk, criteria);
}
protected override void SetEncounterMoves(PKM pk, GameVersion version, int level)
@ -65,6 +130,22 @@ public override EncounterMatchRating GetMatchRating(PKM pkm)
return base.GetMatchRating(pkm) == EncounterMatchRating.PartialMatch ? EncounterMatchRating.PartialMatch : EncounterMatchRating.Match;
}
public byte OT_Friendship => Species switch
{
(int)Core.Species.Timburr when Form == 0 => 70,
(int)Core.Species.Stunfisk when Form == 0 => 70,
(int)Core.Species.Hoopa when Form == 1 => 50,
_ => GetHOMEFriendship(),
};
private byte GetHOMEFriendship()
{
var fs = (byte)GetPersonal().BaseFriendship;
if (fs == 70)
return 50;
return fs;
}
private bool IsMatchPartial(PKM pk)
{
var stamp = GetTimeStamp(pk.Met_Year + 2000, pk.Met_Month, pk.Met_Day);
@ -76,19 +157,33 @@ private bool IsMatchPartial(PKM pk)
return true;
// Eevee & Glaceon have different base friendships. Make sure if it is invalid that we yield the other encounter before.
if (PersonalTable.SWSH.GetFormEntry(Species, Form).BaseFriendship != pk.OT_Friendship)
if (pk.OT_Friendship != OT_Friendship)
return true;
return Species switch
{
(int)Core.Species.Yamask when pk.Species != Species && Form == 1 => pk is IFormArgument { FormArgument: 0 },
(int)Core.Species.Milcery when pk.Species != Species => pk is IFormArgument { FormArgument: 0 },
(int)Core.Species.Qwilfish when pk.Species != Species && Form == 1 => pk is IFormArgument { FormArgument: 0 },
(int)Core.Species.Basculin when pk.Species != Species && Form == 2 => pk is IFormArgument { FormArgument: 0 },
(int)Core.Species.Stantler when pk.Species != Species => pk is IFormArgument { FormArgument: 0 },
(int)Core.Species.Runerigus => pk is IFormArgument { FormArgument: not 0 },
(int)Core.Species.Alcremie => pk is IFormArgument { FormArgument: not 0 },
(int)Core.Species.Wyrdeer => pk is IFormArgument { FormArgument: not 0 },
(int)Core.Species.Basculegion => pk is IFormArgument { FormArgument: not 0 },
(int)Core.Species.Overqwil => pk is IFormArgument { FormArgument: not 0 },
_ => false,
};
}
}
public enum PogoImportFormat : byte
{
PK7 = 0,
PB7 = 1,
PK8 = 2,
PA8 = 3,
}
}

View File

@ -153,7 +153,6 @@ public bool GetIVsValid(PKM pkm)
protected override void SetPINGA(PKM pk, EncounterCriteria criteria)
{
base.SetPINGA(pk, criteria);
switch (Shiny)
{
case Shiny.Random when !pk.IsShiny && criteria.Shiny.IsShiny():

View File

@ -39,7 +39,7 @@ public abstract record EncounterStatic(GameVersion Version) : IEncounterable, IM
public IReadOnlyList<int> Moves { get; init; } = Array.Empty<int>();
public IReadOnlyList<int> IVs { get; init; } = Array.Empty<int>();
public bool EggEncounter => EggLocation > 0;
public virtual bool EggEncounter => EggLocation != 0;
private const string _name = "Static Encounter";
public string Name => _name;
@ -107,9 +107,9 @@ protected virtual void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria
s.HeightScalar = PokeSizeUtil.GetRandomScalar();
s.WeightScalar = PokeSizeUtil.GetRandomScalar();
}
if (this is IGigantamax g && pk is IGigantamax pg)
if (this is IGigantamax g && pk is PK8 pg)
pg.CanGigantamax = g.CanGigantamax;
if (this is IDynamaxLevel d && pk is IDynamaxLevel pd)
if (this is IDynamaxLevel d && pk is PK8 pd)
pd.DynamaxLevel = d.DynamaxLevel;
}
@ -264,7 +264,11 @@ protected virtual bool IsMatchForm(PKM pkm, EvoCriteria evo)
}
// override me if the encounter type has any eggs
protected virtual bool IsMatchEggLocation(PKM pkm) => pkm.Egg_Location == 0;
protected virtual bool IsMatchEggLocation(PKM pkm)
{
var expect = pkm is PB8 ? Locations.Default8bNone : 0;
return pkm.Egg_Location == expect;
}
private bool IsMatchGender(PKM pkm)
{

View File

@ -22,7 +22,7 @@ protected override bool IsMatchEggLocation(PKM pkm)
{
if (pkm.Format == 3)
return !pkm.IsEgg || EggLocation == 0 || EggLocation == pkm.Met_Location;
return pkm.Egg_Location == 0;
return base.IsMatchEggLocation(pkm);
}
protected override bool IsMatchLevel(PKM pkm, EvoCriteria evo)

View File

@ -33,10 +33,10 @@ protected override bool IsMatchLocation(PKM pkm)
protected override bool IsMatchEggLocation(PKM pkm)
{
var eggloc = pkm.Egg_Location;
if (!EggEncounter)
return eggloc == 0;
return base.IsMatchEggLocation(pkm);
var eggloc = pkm.Egg_Location;
// Transferring 4->5 clears Pt/HG/SS location value and keeps Faraway Place
if (pkm is not G4PKM pk4)
{

View File

@ -29,10 +29,10 @@ protected sealed override bool IsMatchLocation(PKM pk)
protected override bool IsMatchEggLocation(PKM pkm)
{
var eggloc = pkm.Egg_Location;
if (!EggEncounter)
return eggloc == EggLocation;
return base.IsMatchEggLocation(pkm);
var eggloc = pkm.Egg_Location;
if (!pkm.IsEgg) // hatched
return eggloc == EggLocation || eggloc == Locations.LinkTrade5;

View File

@ -30,10 +30,10 @@ protected override bool IsMatchLocation(PKM pkm)
protected override bool IsMatchEggLocation(PKM pkm)
{
var eggloc = pkm.Egg_Location;
if (!EggEncounter)
return eggloc == EggLocation;
return base.IsMatchEggLocation(pkm);
var eggloc = pkm.Egg_Location;
if (!pkm.IsEgg) // hatched
return eggloc == EggLocation || eggloc == Locations.LinkTrade6;

View File

@ -23,10 +23,10 @@ protected override bool IsMatchLocation(PKM pkm)
protected override bool IsMatchEggLocation(PKM pkm)
{
var eggloc = pkm.Egg_Location;
if (!EggEncounter)
return eggloc == EggLocation;
return base.IsMatchEggLocation(pkm);
var eggloc = pkm.Egg_Location;
if (!pkm.IsEgg) // hatched
return eggloc == EggLocation || eggloc == Locations.LinkTrade6;

View File

@ -31,7 +31,7 @@ protected override bool IsMatchLevel(PKM pkm, EvoCriteria evo)
public override bool IsMatchExact(PKM pkm, EvoCriteria evo)
{
if (pkm is IDynamaxLevel d && d.DynamaxLevel < DynamaxLevel)
if (pkm is PK8 d && d.DynamaxLevel < DynamaxLevel)
return false;
if (pkm.Met_Level < EncounterArea8.BoostLevel && Weather is AreaWeather8.Heavy_Fog && EncounterArea8.IsBoostedArea60Fog(Location))
return false;

View File

@ -20,11 +20,11 @@ public abstract record EncounterStatic8Nest<T>(GameVersion Version) : EncounterS
public override bool IsMatchExact(PKM pkm, EvoCriteria evo)
{
if (pkm is IDynamaxLevel d && d.DynamaxLevel < DynamaxLevel)
if (pkm is PK8 d && d.DynamaxLevel < DynamaxLevel)
return false;
// Required Ability
if (Ability == AbilityPermission.OnlyHidden && pkm.AbilityNumber != 4)
if (Ability == OnlyHidden && pkm.AbilityNumber != 4)
return false; // H
if (Version != GameVersion.SWSH && pkm.Version != (int)Version && pkm.Met_Location != SharedNest)
@ -64,7 +64,7 @@ protected sealed override EncounterMatchRating IsMatchDeferred(PKM pkm)
protected override bool IsMatchPartial(PKM pkm)
{
if (pkm is IGigantamax g && g.CanGigantamax != CanGigantamax && !g.CanToggleGigantamax(pkm.Species, pkm.Form, Species, Form))
if (pkm is PK8 and IGigantamax g && g.CanGigantamax != CanGigantamax && !g.CanToggleGigantamax(pkm.Species, pkm.Form, Species, Form))
return true;
if (Species == (int)Core.Species.Alcremie && pkm is IFormArgument { FormArgument: not 0 })
return true;

View File

@ -83,6 +83,16 @@ public override bool IsMatchExact(PKM pkm, EvoCriteria evo)
return true;
}
protected override bool IsMatchLocation(PKM pkm)
{
if (pkm is PK8)
return pkm.Met_Location == Locations.HOME_SWLA;
if (pkm is PB8 { Version: (int)GameVersion.PLA, Met_Location: Locations.HOME_SWLA })
return true;
return base.IsMatchLocation(pkm);
}
public override EncounterMatchRating GetMatchRating(PKM pkm)
{
var result = GetMatchRatingInternal(pkm);

View File

@ -12,14 +12,14 @@ public sealed record EncounterStatic8b : EncounterStatic, IStaticCorrelation8b
public override int Generation => 8;
public bool Roaming { get; init; }
public override bool EggEncounter => EggLocation != Locations.Default8bNone;
public EncounterStatic8b(GameVersion game) : base(game)
{
EggLocation = Locations.Default8bNone;
}
public EncounterStatic8b(GameVersion game) : base(game) => EggLocation = Locations.Default8bNone;
protected override bool IsMatchLocation(PKM pkm)
{
if (pkm is PK8)
return Locations.IsValidMetBDSP(pkm.Met_Location, pkm.Version);
if (!Roaming)
return base.IsMatchLocation(pkm);
return IsRoamingLocation(pkm);
@ -47,17 +47,34 @@ public bool IsStaticCorrelationCorrect(PKM pk)
protected override bool IsMatchEggLocation(PKM pkm)
{
var eggloc = (short)pkm.Egg_Location;
if (pkm is not PB8)
{
if (!EggEncounter)
return pkm.Egg_Location == 0;
if (pkm is PK8)
{
if (EggLocation > 60000 && pkm.Egg_Location == Locations.HOME_SWSHBDSPEgg)
return true;
// >60000 can be reset to Link Trade (30001), then altered differently.
return Locations.IsValidMetBDSP(pkm.Egg_Location, pkm.Version);
}
// Hatched
return pkm.Egg_Location == EggLocation || pkm.Egg_Location == Locations.LinkTrade6NPC;
}
var eggloc = pkm.Egg_Location;
if (!EggEncounter)
return eggloc == (short)EggLocation;
return eggloc == EggLocation;
if (!pkm.IsEgg) // hatched
return eggloc == (short)EggLocation || eggloc == Locations.LinkTrade6NPC;
return eggloc == EggLocation || eggloc == Locations.LinkTrade6NPC;
// Unhatched:
if (eggloc != (short)EggLocation)
if (eggloc != EggLocation)
return false;
if ((short)pkm.Met_Location is not (Locations.Default8bNone or Locations.LinkTrade6NPC))
if (pkm.Met_Location is not (Locations.Default8bNone or Locations.LinkTrade6NPC))
return false;
return true;
}

View File

@ -203,7 +203,7 @@ public virtual bool IsMatchExact(PKM pkm, EvoCriteria evo)
return false;
if (OTGender != -1 && OTGender != pkm.OT_Gender)
return false;
if (EggLocation != pkm.Egg_Location)
if (!IsMatchEggLocation(pkm))
return false;
// if (z.Ability == 4 ^ pkm.AbilityNumber == 4) // defer to Ability
// continue;
@ -213,6 +213,14 @@ public virtual bool IsMatchExact(PKM pkm, EvoCriteria evo)
return true;
}
protected virtual bool IsMatchEggLocation(PKM pkm)
{
var expect = EggLocation;
if (pkm is PB8 && expect is 0)
expect = Locations.Default8bNone;
return pkm.Egg_Location == expect;
}
private bool IsMatchLevel(PKM pkm, EvoCriteria evo)
{
if (!pkm.HasOriginalMetLocation)

View File

@ -35,7 +35,7 @@ public EncounterTrade8(GameVersion game, int species, byte level, byte memory, u
public override bool IsMatchExact(PKM pkm, EvoCriteria evo)
{
if (pkm is IDynamaxLevel d && d.DynamaxLevel < DynamaxLevel)
if (pkm is PK8 d && d.DynamaxLevel < DynamaxLevel)
return false;
if (pkm.FlawlessIVCount < FlawlessIVCount)
return false;

View File

@ -9,7 +9,7 @@ public sealed record EncounterTrade8b : EncounterTrade, IContestStats, IScaledSi
public override int Generation => 8;
public override int Location => Locations.LinkTrade6NPC;
public EncounterTrade8b(GameVersion game) : base(game) => EggLocation = unchecked((ushort)Locations.Default8bNone);
public EncounterTrade8b(GameVersion game) : base(game) => EggLocation = Locations.Default8bNone;
public byte CNT_Cool => BaseContest;
public byte CNT_Beauty => BaseContest;
public byte CNT_Cute => BaseContest;
@ -38,6 +38,14 @@ public override bool IsMatchExact(PKM pkm, EvoCriteria evo)
return base.IsMatchExact(pkm, evo);
}
protected override bool IsMatchEggLocation(PKM pkm)
{
var expect = EggLocation;
if (pkm is not PB8 && expect == Locations.Default8bNone)
expect = 0;
return pkm.Egg_Location == expect;
}
protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk)
{
base.ApplyDetails(sav, criteria, pk);

View File

@ -79,8 +79,9 @@ private static IEnumerable<IEncounterable> GenerateRawEncounters3CXD(PKM pkm)
{
var chain = EncounterOrigin.GetOriginChain(pkm);
var game = (GameVersion)pkm.Version;
// Mystery Gifts
foreach (var z in GetValidGifts(pkm, chain))
foreach (var z in GetValidGifts(pkm, chain, game))
{
// Don't bother deferring matches.
var match = z.GetMatchRating(pkm);
@ -89,7 +90,7 @@ private static IEnumerable<IEncounterable> GenerateRawEncounters3CXD(PKM pkm)
}
// Trades
foreach (var z in GetValidEncounterTrades(pkm, chain))
foreach (var z in GetValidEncounterTrades(pkm, chain, game))
{
// Don't bother deferring matches.
var match = z.GetMatchRating(pkm);
@ -100,7 +101,7 @@ private static IEnumerable<IEncounterable> GenerateRawEncounters3CXD(PKM pkm)
IEncounterable? partial = null;
// Static Encounter
foreach (var z in GetValidStaticEncounter(pkm, chain))
foreach (var z in GetValidStaticEncounter(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
if (match == PartialMatch)
@ -110,7 +111,7 @@ private static IEnumerable<IEncounterable> GenerateRawEncounters3CXD(PKM pkm)
}
// Encounter Slots
foreach (var z in GetValidWildEncounters(pkm, chain))
foreach (var z in GetValidWildEncounters(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
if (match == PartialMatch)
@ -128,9 +129,10 @@ private static IEnumerable<IEncounterable> GenerateRawEncounters3CXD(PKM pkm)
private static IEnumerable<IEncounterable> GenerateRawEncounters3(PKM pkm, LegalInfo info)
{
var chain = EncounterOrigin.GetOriginChain(pkm);
var game = (GameVersion)pkm.Version;
// Mystery Gifts
foreach (var z in GetValidGifts(pkm, chain))
foreach (var z in GetValidGifts(pkm, chain, game))
{
// Don't bother deferring matches.
var match = z.GetMatchRating(pkm);
@ -139,7 +141,7 @@ private static IEnumerable<IEncounterable> GenerateRawEncounters3(PKM pkm, Legal
}
// Trades
foreach (var z in GetValidEncounterTrades(pkm, chain))
foreach (var z in GetValidEncounterTrades(pkm, chain, game))
{
// Don't bother deferring matches.
var match = z.GetMatchRating(pkm);
@ -155,7 +157,7 @@ private static IEnumerable<IEncounterable> GenerateRawEncounters3(PKM pkm, Legal
bool safari = pkm.Ball == 0x05; // never static encounters
if (!safari)
{
foreach (var z in GetValidStaticEncounter(pkm, chain))
foreach (var z in GetValidStaticEncounter(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
if (match == PartialMatch)
@ -167,7 +169,7 @@ private static IEnumerable<IEncounterable> GenerateRawEncounters3(PKM pkm, Legal
// Encounter Slots
var slots = FrameFinder.GetFrames(info.PIDIV, pkm).ToList();
foreach (var z in GetValidWildEncounters(pkm, chain))
foreach (var z in GetValidWildEncounters(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
if (match == PartialMatch)
@ -208,7 +210,7 @@ private static IEnumerable<IEncounterable> GenerateRawEncounters3(PKM pkm, Legal
partial = null;
foreach (var z in GetValidStaticEncounter(pkm, chain))
foreach (var z in GetValidStaticEncounter(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
if (match == PartialMatch)

View File

@ -42,19 +42,20 @@ public static IEnumerable<IEncounterable> GetEncounters(PKM pkm, LegalInfo info)
private static IEnumerable<IEncounterable> GenerateRawEncounters4(PKM pkm, LegalInfo info)
{
var chain = EncounterOrigin.GetOriginChain(pkm);
var game = (GameVersion)pkm.Version;
if (pkm.FatefulEncounter)
{
int ctr = 0;
foreach (var z in GetValidGifts(pkm, chain))
foreach (var z in GetValidGifts(pkm, chain, game))
{ yield return z; ++ctr; }
if (ctr != 0) yield break;
}
if (Locations.IsEggLocationBred4(pkm.Egg_Location, (GameVersion)pkm.Version))
if (Locations.IsEggLocationBred4(pkm.Egg_Location, game))
{
foreach (var z in GenerateEggs(pkm, 4))
yield return z;
}
foreach (var z in GetValidEncounterTrades(pkm, chain))
foreach (var z in GetValidEncounterTrades(pkm, chain, game))
yield return z;
IEncounterable? deferred = null;
@ -63,7 +64,7 @@ private static IEnumerable<IEncounterable> GenerateRawEncounters4(PKM pkm, Legal
bool safariSport = pkm.Ball is (int)Ball.Sport or (int)Ball.Safari; // never static encounters
if (!safariSport)
{
foreach (var z in GetValidStaticEncounter(pkm, chain))
foreach (var z in GetValidStaticEncounter(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
if (match == PartialMatch)
@ -74,7 +75,7 @@ private static IEnumerable<IEncounterable> GenerateRawEncounters4(PKM pkm, Legal
}
var slots = FrameFinder.GetFrames(info.PIDIV, pkm).ToList();
foreach (var slot in GetValidWildEncounters(pkm, chain))
foreach (var slot in GetValidWildEncounters(pkm, chain, game))
{
var z = (EncounterSlot4)slot;
var match = z.GetMatchRating(pkm);
@ -115,7 +116,7 @@ private static IEnumerable<IEncounterable> GenerateRawEncounters4(PKM pkm, Legal
if (!safariSport)
yield break;
foreach (var z in GetValidStaticEncounter(pkm, chain))
foreach (var z in GetValidStaticEncounter(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
if (match == PartialMatch)

View File

@ -16,9 +16,11 @@ public static IEnumerable<IEncounterable> GetEncounters(PKM pkm)
int ctr = 0;
var chain = EncounterOrigin.GetOriginChain(pkm);
var game = (GameVersion)pkm.Version;
if (pkm.FatefulEncounter)
{
foreach (var z in GetValidGifts(pkm, chain))
foreach (var z in GetValidGifts(pkm, chain, game))
{ yield return z; ++ctr; }
if (ctr != 0) yield break;
}
@ -33,7 +35,7 @@ public static IEnumerable<IEncounterable> GetEncounters(PKM pkm)
IEncounterable? deferred = null;
IEncounterable? partial = null;
foreach (var z in GetValidStaticEncounter(pkm, chain))
foreach (var z in GetValidStaticEncounter(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
switch (match)
@ -45,7 +47,7 @@ public static IEnumerable<IEncounterable> GetEncounters(PKM pkm)
}
if (ctr != 0) yield break;
foreach (var z in GetValidWildEncounters(pkm, chain))
foreach (var z in GetValidWildEncounters(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
switch (match)
@ -57,7 +59,7 @@ public static IEnumerable<IEncounterable> GetEncounters(PKM pkm)
}
if (ctr != 0) yield break;
foreach (var z in GetValidEncounterTrades(pkm, chain))
foreach (var z in GetValidEncounterTrades(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
switch (match)

View File

@ -16,13 +16,14 @@ public static IEnumerable<IEncounterable> GetEncounters(PKM pkm)
int ctr = 0;
var chain = EncounterOrigin.GetOriginChain(pkm);
var game = (GameVersion)pkm.Version;
IEncounterable? deferred = null;
IEncounterable? partial = null;
if (pkm.FatefulEncounter || pkm.Met_Location == Locations.LinkGift6)
{
foreach (var z in GetValidGifts(pkm, chain))
foreach (var z in GetValidGifts(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
switch (match)
@ -53,7 +54,7 @@ public static IEnumerable<IEncounterable> GetEncounters(PKM pkm)
if (ctr == 0) yield break;
}
foreach (var z in GetValidStaticEncounter(pkm, chain))
foreach (var z in GetValidStaticEncounter(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
switch (match)
@ -65,7 +66,7 @@ public static IEnumerable<IEncounterable> GetEncounters(PKM pkm)
}
if (ctr != 0) yield break;
foreach (var z in GetValidWildEncounters(pkm, chain))
foreach (var z in GetValidWildEncounters(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
switch (match)
@ -77,7 +78,7 @@ public static IEnumerable<IEncounterable> GetEncounters(PKM pkm)
}
if (ctr != 0) yield break;
foreach (var z in GetValidEncounterTrades(pkm, chain))
foreach (var z in GetValidEncounterTrades(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
switch (match)

View File

@ -28,7 +28,7 @@ internal static IEnumerable<IEncounterable> GetEncountersGO(PKM pkm, EvoCriteria
IEncounterable? partial = null;
int ctr = 0;
foreach (var z in GetValidWildEncounters(pkm, chain))
foreach (var z in GetValidWildEncounters(pkm, chain, GameVersion.GO))
{
var match = z.GetMatchRating(pkm);
switch (match)
@ -50,9 +50,11 @@ internal static IEnumerable<IEncounterable> GetEncountersGO(PKM pkm, EvoCriteria
private static IEnumerable<IEncounterable> GetEncountersGG(PKM pkm, EvoCriteria[] chain)
{
int ctr = 0;
var game = (GameVersion)pkm.Version;
if (pkm.FatefulEncounter)
{
foreach (var z in GetValidGifts(pkm, chain))
foreach (var z in GetValidGifts(pkm, chain, game))
{ yield return z; ++ctr; }
if (ctr != 0) yield break;
}
@ -60,7 +62,7 @@ private static IEnumerable<IEncounterable> GetEncountersGG(PKM pkm, EvoCriteria[
IEncounterable? deferred = null;
IEncounterable? partial = null;
foreach (var z in GetValidStaticEncounter(pkm, chain))
foreach (var z in GetValidStaticEncounter(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
switch (match)
@ -72,7 +74,7 @@ private static IEnumerable<IEncounterable> GetEncountersGG(PKM pkm, EvoCriteria[
}
if (ctr != 0) yield break;
foreach (var z in GetValidWildEncounters(pkm, chain))
foreach (var z in GetValidWildEncounters(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
switch (match)
@ -84,7 +86,7 @@ private static IEnumerable<IEncounterable> GetEncountersGG(PKM pkm, EvoCriteria[
}
if (ctr != 0) yield break;
foreach (var z in GetValidEncounterTrades(pkm, chain))
foreach (var z in GetValidEncounterTrades(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
switch (match)
@ -105,9 +107,11 @@ private static IEnumerable<IEncounterable> GetEncountersGG(PKM pkm, EvoCriteria[
private static IEnumerable<IEncounterable> GetEncountersMainline(PKM pkm, EvoCriteria[] chain)
{
int ctr = 0;
var game = (GameVersion)pkm.Version;
if (pkm.FatefulEncounter)
{
foreach (var z in GetValidGifts(pkm, chain))
foreach (var z in GetValidGifts(pkm, chain, game))
{ yield return z; ++ctr; }
if (ctr != 0) yield break;
}
@ -122,7 +126,7 @@ private static IEnumerable<IEncounterable> GetEncountersMainline(PKM pkm, EvoCri
IEncounterable? deferred = null;
IEncounterable? partial = null;
foreach (var z in GetValidStaticEncounter(pkm, chain))
foreach (var z in GetValidStaticEncounter(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
switch (match)
@ -134,7 +138,7 @@ private static IEnumerable<IEncounterable> GetEncountersMainline(PKM pkm, EvoCri
}
if (ctr != 0) yield break;
foreach (var z in GetValidWildEncounters(pkm, chain))
foreach (var z in GetValidWildEncounters(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
switch (match)
@ -146,7 +150,7 @@ private static IEnumerable<IEncounterable> GetEncountersMainline(PKM pkm, EvoCri
}
if (ctr != 0) yield break;
foreach (var z in GetValidEncounterTrades(pkm, chain))
foreach (var z in GetValidEncounterTrades(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
switch (match)

View File

@ -19,6 +19,9 @@ public static IEnumerable<IEncounterable> GetEncounters(PKM pkm)
(int)GameVersion.GO => EncounterGenerator7.GetEncountersGO(pkm, chain),
(int)GameVersion.PLA => EncounterGenerator8a.GetEncounters(pkm, chain),
(int)GameVersion.BD or (int)GameVersion.SP => EncounterGenerator8b.GetEncounters(pkm, chain),
(int)GameVersion.SW when pkm.Met_Location == Locations.HOME_SWLA => EncounterGenerator8a.GetEncounters(pkm, chain),
(int)GameVersion.SW when pkm.Met_Location == Locations.HOME_SWBD => EncounterGenerator8b.GetEncountersFuzzy(pkm, chain, GameVersion.BD),
(int)GameVersion.SH when pkm.Met_Location == Locations.HOME_SHSP => EncounterGenerator8b.GetEncountersFuzzy(pkm, chain, GameVersion.SP),
_ => GetEncountersMainline(pkm, chain),
};
}
@ -26,9 +29,11 @@ public static IEnumerable<IEncounterable> GetEncounters(PKM pkm)
private static IEnumerable<IEncounterable> GetEncountersMainline(PKM pkm, EvoCriteria[] chain)
{
int ctr = 0;
var game = (GameVersion)pkm.Version;
if (pkm.FatefulEncounter)
{
foreach (var z in GetValidGifts(pkm, chain))
foreach (var z in GetValidGifts(pkm, chain, game))
{ yield return z; ++ctr; }
if (ctr != 0) yield break;
}
@ -46,7 +51,7 @@ private static IEnumerable<IEncounterable> GetEncountersMainline(PKM pkm, EvoCri
// Trades
if (pkm.Met_Location == Locations.LinkTrade6NPC)
{
foreach (var z in GetValidEncounterTrades(pkm, chain))
foreach (var z in GetValidEncounterTrades(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
if (match == Match)
@ -66,7 +71,7 @@ private static IEnumerable<IEncounterable> GetEncountersMainline(PKM pkm, EvoCri
}
// Static Encounters can collide with wild encounters (close match); don't break if a Static Encounter is yielded.
var encs = GetValidStaticEncounter(pkm, chain);
var encs = GetValidStaticEncounter(pkm, chain, game);
foreach (var z in encs)
{
var match = z.GetMatchRating(pkm);
@ -81,7 +86,7 @@ private static IEnumerable<IEncounterable> GetEncountersMainline(PKM pkm, EvoCri
}
}
foreach (var z in GetValidWildEncounters(pkm, chain))
foreach (var z in GetValidWildEncounters(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
if (match == Match)

View File

@ -11,13 +11,15 @@ internal static class EncounterGenerator8a
{
public static IEnumerable<IEncounterable> GetEncounters(PKM pkm, EvoCriteria[] chain)
{
if (pkm is PK8 { SWSH: false })
yield break;
if (pkm.IsEgg)
yield break;
int ctr = 0;
if (pkm.FatefulEncounter)
{
foreach (var z in GetValidGifts(pkm, chain))
foreach (var z in GetValidGifts(pkm, chain, GameVersion.PLA))
{ yield return z; ++ctr; }
if (ctr != 0) yield break;
}
@ -26,7 +28,7 @@ public static IEnumerable<IEncounterable> GetEncounters(PKM pkm, EvoCriteria[] c
EncounterMatchRating rating = None;
// Static Encounters can collide with wild encounters (close match); don't break if a Static Encounter is yielded.
var encs = GetValidStaticEncounter(pkm, chain);
var encs = GetValidStaticEncounter(pkm, chain, GameVersion.PLA);
foreach (var z in encs)
{
var match = z.GetMatchRating(pkm);
@ -41,7 +43,7 @@ public static IEnumerable<IEncounterable> GetEncounters(PKM pkm, EvoCriteria[] c
}
}
foreach (var z in GetValidWildEncounters(pkm, chain))
foreach (var z in GetValidWildEncounters(pkm, chain, GameVersion.PLA))
{
var match = z.GetMatchRating(pkm);
if (match == Match)

View File

@ -13,10 +13,14 @@ internal static class EncounterGenerator8b
{
public static IEnumerable<IEncounterable> GetEncounters(PKM pkm, EvoCriteria[] chain)
{
if (pkm is PK8)
yield break;
int ctr = 0;
var game = (GameVersion)pkm.Version;
if (pkm.FatefulEncounter)
{
foreach (var z in GetValidGifts(pkm, chain))
foreach (var z in GetValidGifts(pkm, chain, game))
{ yield return z; ++ctr; }
if (ctr != 0) yield break;
}
@ -34,7 +38,7 @@ public static IEnumerable<IEncounterable> GetEncounters(PKM pkm, EvoCriteria[] c
// Trades
if (!pkm.IsEgg && pkm.Met_Location == Locations.LinkTrade6NPC)
{
foreach (var z in GetValidEncounterTrades(pkm, chain))
foreach (var z in GetValidEncounterTrades(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
if (match == Match)
@ -54,7 +58,7 @@ public static IEnumerable<IEncounterable> GetEncounters(PKM pkm, EvoCriteria[] c
}
// Static Encounters can collide with wild encounters (close match); don't break if a Static Encounter is yielded.
var encs = GetValidStaticEncounter(pkm, chain);
var encs = GetValidStaticEncounter(pkm, chain, game);
foreach (var z in encs)
{
var match = z.GetMatchRating(pkm);
@ -69,7 +73,83 @@ public static IEnumerable<IEncounterable> GetEncounters(PKM pkm, EvoCriteria[] c
}
}
foreach (var z in GetValidWildEncounters(pkm, chain))
foreach (var z in GetValidWildEncounters(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
if (match == Match)
{
yield return z;
}
else if (match < rating)
{
cache = z;
rating = match;
}
}
if (cache != null)
yield return cache;
}
public static IEnumerable<IEncounterable> GetEncountersFuzzy(PKM pkm, EvoCriteria[] chain, GameVersion game)
{
int ctr = 0;
if (pkm.FatefulEncounter)
{
foreach (var z in GetValidGifts(pkm, chain, game))
{ yield return z; ++ctr; }
if (ctr != 0) yield break;
}
if (pkm.Egg_Location == Locations.HOME_SWSHBDSPEgg && pkm.Met_Level == 1)
{
foreach (var z in GenerateEggs(pkm, 8))
{ yield return z; ++ctr; }
if (ctr == 0) yield break;
}
IEncounterable? cache = null;
EncounterMatchRating rating = None;
// Trades
if (!pkm.IsEgg)
{
foreach (var z in GetValidEncounterTrades(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
if (match == Match)
{
yield return z;
}
else if (match < rating)
{
cache = z;
rating = match;
}
}
if (cache != null)
yield return cache;
}
// Static Encounters can collide with wild encounters (close match); don't break if a Static Encounter is yielded.
var encs = GetValidStaticEncounter(pkm, chain, game);
foreach (var z in encs)
{
var match = z.GetMatchRating(pkm);
if (match == Match)
{
yield return z;
}
else if (match < rating)
{
cache = z;
rating = match;
}
}
foreach (var z in GetValidWildEncounters(pkm, chain, game))
{
var match = z.GetMatchRating(pkm);
if (match == Match)

View File

@ -173,18 +173,18 @@ private static string GetHintWhyNotFound(PKM pkm, int gen)
private static bool WasEventEgg(PKM pkm, int gen) => gen switch
{
// Event Egg, indistinguible from normal eggs after hatch
// Event Egg, indistinguishable from normal eggs after hatch
// can't tell after transfer
3 => pkm.Format == 3 && pkm.IsEgg && pkm.Met_Location == 255,
3 => pkm.Format == 3 && pkm.IsEgg && Locations.IsEventLocation3(pkm.Met_Location),
// Manaphy was the only generation 4 released event egg
_ => pkm.Egg_Location is not 0 && pkm.FatefulEncounter,
_ => pkm.FatefulEncounter && pkm.Egg_Day != 0,
};
private static bool WasEvent(PKM pkm, int gen) => pkm.FatefulEncounter || gen switch
{
3 => (pkm.Met_Location == 255 && pkm.Format == 3),
4 => (Locations.IsEventLocation4(pkm.Met_Location) && pkm.Format == 4),
3 => Locations.IsEventLocation3(pkm.Met_Location) && pkm.Format == 3,
4 => Locations.IsEventLocation4(pkm.Met_Location) && pkm.Format == 4,
>=5 => Locations.IsEventLocation5(pkm.Met_Location),
_ => false,
};

View File

@ -142,10 +142,10 @@ public static IEnumerable<IEncounterable> GenerateVersionEncounters(PKM pk, IEnu
if (pk.Species == 0) // can enter this method after failing to set a species ID that cannot exist in the format
return Array.Empty<IEncounterable>();
pk.Version = (int)version;
var format = pk.Format;
if (format is 2 && version is GameVersion.RD or GameVersion.GN or GameVersion.BU or GameVersion.YW)
format = 1; // try excluding baby pokemon from our evolution chain, for move learning purposes.
var et = EvolutionTree.GetEvolutionTree(pk, format);
var context = pk.Context;
if (context is EntityContext.Gen2 && version is GameVersion.RD or GameVersion.GN or GameVersion.BU or GameVersion.YW)
context = EntityContext.Gen1; // try excluding baby pokemon from our evolution chain, for move learning purposes.
var et = EvolutionTree.GetEvolutionTree(context);
var chain = et.GetValidPreEvolutions(pk, maxLevel: 100, skipChecks: true);
int[] needs = GetNeededMoves(pk, moves, chain);
@ -225,7 +225,7 @@ private static IEnumerable<IEncounterable> GetPossibleOfType(PKM pk, IReadOnlyLi
return type switch
{
EncounterOrder.Egg => GetEggs(pk, needs, chain, version),
EncounterOrder.Mystery => GetGifts(pk, needs, chain),
EncounterOrder.Mystery => GetGifts(pk, needs, chain, version),
EncounterOrder.Static => GetStatic(pk, needs, chain, version),
EncounterOrder.Trade => GetTrades(pk, needs, chain, version),
EncounterOrder.Slot => GetSlots(pk, needs, chain, version),
@ -274,11 +274,12 @@ private static IEnumerable<EncounterEgg> GetEggs(PKM pk, IReadOnlyCollection<int
/// <param name="pk">Rough Pokémon data which contains the requested species, gender, and form.</param>
/// <param name="needs">Moves which cannot be taught by the player.</param>
/// <param name="chain">Origin possible evolution chain</param>
/// <param name="version">Specific version to iterate for.</param>
/// <returns>A consumable <see cref="IEncounterable"/> list of possible encounters.</returns>
private static IEnumerable<MysteryGift> GetGifts(PKM pk, IReadOnlyCollection<int> needs, EvoCriteria[] chain)
private static IEnumerable<MysteryGift> GetGifts(PKM pk, IReadOnlyCollection<int> needs, EvoCriteria[] chain, GameVersion version)
{
var format = pk.Format;
var gifts = MysteryGiftGenerator.GetPossible(pk, chain);
var gifts = MysteryGiftGenerator.GetPossible(pk, chain, version);
foreach (var gift in gifts)
{
if (gift is WC3 {NotDistributed: true})

View File

@ -8,7 +8,7 @@ public static class EncounterEggGenerator
{
public static IEnumerable<EncounterEgg> GenerateEggs(PKM pkm, int generation, bool all = false)
{
var table = EvolutionTree.GetEvolutionTree(pkm, pkm.Format);
var table = EvolutionTree.GetEvolutionTree(pkm.Context);
int maxSpeciesOrigin = GetMaxSpeciesOrigin(generation);
var evos = table.GetValidPreEvolutions(pkm, maxLevel: 100, maxSpeciesOrigin: maxSpeciesOrigin, skipChecks: true);
return GenerateEggs(pkm, evos, generation, all);

View File

@ -9,7 +9,7 @@ internal static class EncounterEggGenerator2
{
public static IEnumerable<EncounterEgg> GenerateEggs(PKM pkm, bool all = false)
{
var table = EvolutionTree.GetEvolutionTree(pkm, 2);
var table = EvolutionTree.Evolves2;
int maxSpeciesOrigin = Legal.GetMaxSpeciesOrigin(2);
var evos = table.GetValidPreEvolutions(pkm, maxLevel: 100, maxSpeciesOrigin: maxSpeciesOrigin, skipChecks: true);
return GenerateEggs(pkm, evos, all);

View File

@ -45,7 +45,9 @@ public static IEnumerable<EncounterSlot> GetPossible(PKM pkm, EvoCriteria[] chai
private static IEnumerable<EncounterSlot> GetRawEncounterSlots(PKM pkm, EvoCriteria[] chain, GameVersion gameSource)
{
if (!Locations.IsNoneLocation(gameSource, pkm.Egg_Location) || pkm.IsEgg)
if (pkm.IsEgg)
yield break;
if (IsMetAsEgg(pkm))
yield break;
var possibleAreas = GetEncounterAreas(pkm, gameSource);
@ -62,6 +64,11 @@ public static IEnumerable<EncounterSlot> GetValidWildEncounters12(PKM pkm, EvoCr
return GetRawEncounterSlots(pkm, chain, gameSource);
}
public static IEnumerable<EncounterSlot> GetValidWildEncounters(PKM pkm, EvoCriteria[] chain, GameVersion gameSource)
{
return GetRawEncounterSlots(pkm, chain, gameSource);
}
public static IEnumerable<EncounterSlot> GetValidWildEncounters(PKM pkm, EvoCriteria[] chain)
{
var gameSource = (GameVersion)pkm.Version;

View File

@ -48,11 +48,6 @@ static IEnumerable<EncounterStatic> GetEvents(GameVersion g)
return table.Where(e => chain.Any(d => d.Species == e.Species));
}
public static IEnumerable<EncounterStatic> GetValidStaticEncounter(PKM pkm, EvoCriteria[] chain)
{
return GetValidStaticEncounter(pkm, chain, (GameVersion)pkm.Version);
}
public static IEnumerable<EncounterStatic> GetValidStaticEncounter(PKM pkm, EvoCriteria[] chain, GameVersion gameSource)
{
var table = GetEncounterStaticTable(pkm, gameSource);

View File

@ -42,7 +42,7 @@ public static IEnumerable<EncounterTradeGB> GetValidEncounterTradesVC(PKM pkm, E
}
}
public static IEnumerable<EncounterTrade> GetValidEncounterTrades(PKM pkm, EvoCriteria[] chain)
public static IEnumerable<EncounterTrade> GetValidEncounterTrades(PKM pkm, EvoCriteria[] chain, GameVersion game)
{
// Pre-filter for some language scenarios
int lang = pkm.Language;
@ -51,7 +51,6 @@ public static IEnumerable<EncounterTrade> GetValidEncounterTrades(PKM pkm, EvoCr
if (lang == (int)LanguageID.Hacked && !EncounterTrade5PID.IsValidMissingLanguage(pkm)) // Japanese trades in BW have no language ID
return Array.Empty<EncounterTrade>();
var game = (GameVersion)pkm.Version;
var table = GetTable(game);
return GetValidEncounterTrades(pkm, chain, table);
}

View File

@ -7,20 +7,20 @@ namespace PKHeX.Core
{
public static class MysteryGiftGenerator
{
public static IEnumerable<MysteryGift> GetPossible(PKM pkm, EvoCriteria[] chain)
public static IEnumerable<MysteryGift> GetPossible(PKM pkm, EvoCriteria[] chain, GameVersion game)
{
// Ranger Manaphy is a PGT and is not in the PCD[] for gen4. Check manually.
int gen = pkm.Generation;
if (gen == 4 && pkm.Species == (int) Species.Manaphy)
yield return RangerManaphy;
var table = GetTable(gen, pkm);
var table = GetTable(gen, game);
var possible = table.Where(wc => chain.Any(evo => evo.Species == wc.Species));
foreach (var enc in possible)
yield return enc;
}
public static IEnumerable<MysteryGift> GetValidGifts(PKM pkm, EvoCriteria[] chain)
public static IEnumerable<MysteryGift> GetValidGifts(PKM pkm, EvoCriteria[] chain, GameVersion game)
{
int gen = pkm.Generation;
if (pkm.IsEgg && pkm.Format != gen) // transferred
@ -28,18 +28,23 @@ public static IEnumerable<MysteryGift> GetValidGifts(PKM pkm, EvoCriteria[] chai
if (gen == 4) // check for Manaphy gift
return GetMatchingPCD(pkm, MGDB_G4, chain);
var table = GetTable(gen, pkm);
var table = GetTable(gen, game);
return GetMatchingGifts(pkm, table, chain);
}
private static IReadOnlyCollection<MysteryGift> GetTable(int generation, PKM pkm) => generation switch
private static IReadOnlyCollection<MysteryGift> GetTable(int generation, GameVersion game) => generation switch
{
3 => MGDB_G3,
4 => MGDB_G4,
5 => MGDB_G5,
6 => MGDB_G6,
7 => pkm.LGPE ? MGDB_G7GG : MGDB_G7,
8 => pkm.BDSP ? MGDB_G8B : pkm.LA ? MGDB_G8A : MGDB_G8,
7 => game is GameVersion.GP or GameVersion.GE ? MGDB_G7GG : MGDB_G7,
8 => game switch
{
GameVersion.BD or GameVersion.SP => MGDB_G8B,
GameVersion.PLA => MGDB_G8A,
_ => MGDB_G8,
},
_ => Array.Empty<MysteryGift>(),
};

View File

@ -103,7 +103,7 @@ public static int GetLowestLevel(PKM pkm, byte startLevel)
if (startLevel >= 100)
startLevel = 100;
var table = EvolutionTree.GetEvolutionTree(pkm, pkm.Format);
var table = EvolutionTree.GetEvolutionTree(pkm.Context);
int count = 1;
for (byte i = 100; i >= startLevel; i--)
{

View File

@ -17,7 +17,7 @@ public sealed class ValidEncounterMoves
private const int EmptyCount = PKX.Generation + 1; // one for each generation index (and 0th)
private static readonly IReadOnlyList<int>[] Empty = Enumerable.Repeat((IReadOnlyList<int>)new List<int>(), EmptyCount).ToArray();
public ValidEncounterMoves(PKM pkm, IEncounterTemplate encounter, EvoCriteria[][] chains)
public ValidEncounterMoves(PKM pkm, IEncounterTemplate encounter, EvolutionHistory chains)
{
var level = MoveList.GetValidMovesAllGens(pkm, chains, types: MoveSourceType.Encounter, RemoveTransferHM: false);

View File

@ -124,7 +124,9 @@ private static CheckResult VerifyEncounterEgg3Transfer(PKM pkm)
return new CheckResult(Severity.Invalid, LTransferEgg, CheckIdentifier.Encounter);
if (pkm.Met_Level < 5)
return new CheckResult(Severity.Invalid, LTransferEggMetLevel, CheckIdentifier.Encounter);
if (pkm.Egg_Location != 0)
var expectEgg = pkm is PB8 ? Locations.Default8bNone : 0;
if (pkm.Egg_Location != expectEgg)
return new CheckResult(Severity.Invalid, LEggLocationNone, CheckIdentifier.Encounter);
if (pkm.Format != 4)
@ -173,7 +175,7 @@ private static CheckResult VerifyEncounterEgg6(PKM pkm)
if (pkm.AO)
return VerifyEncounterEggLevelLoc(pkm, 1, Legal.ValidMet_AO);
if (pkm.Egg_Location == 318)
if (pkm.Egg_Location == Locations.HatchLocation6AO) // Battle Resort Daycare is only OR/AS.
return new CheckResult(Severity.Invalid, LEggMetLocationFail, CheckIdentifier.Encounter);
return VerifyEncounterEggLevelLoc(pkm, 1, Legal.ValidMet_XY);
@ -193,7 +195,11 @@ private static CheckResult VerifyEncounterEgg7(PKM pkm)
private static CheckResult VerifyEncounterEgg8(PKM pkm)
{
if (pkm.SWSH)
{
if (pkm.BDSP)
return VerifyEncounterEggLevelLoc(pkm, 1, (location, game) => location == (game == GameVersion.SW ? Locations.HOME_SWBD : Locations.HOME_SHSP));
return VerifyEncounterEggLevelLoc(pkm, 1, Legal.ValidMet_SWSH);
}
// no other games
return new CheckResult(Severity.Invalid, LEggLocationInvalid, CheckIdentifier.Encounter);
@ -223,7 +229,7 @@ private static CheckResult VerifyEncounterEggLevelLoc(PKM pkm, int eggLevel, Fun
: new CheckResult(Severity.Invalid, LEggLocationInvalid, CheckIdentifier.Encounter);
}
private static CheckResult VerifyUnhatchedEgg(PKM pkm, int tradeLoc, short noneLoc = 0)
private static CheckResult VerifyUnhatchedEgg(PKM pkm, int tradeLoc, int noneLoc = 0)
{
var eggLevel = pkm.Format < 5 ? 0 : 1;
if (pkm.Met_Level != eggLevel)
@ -234,7 +240,7 @@ private static CheckResult VerifyUnhatchedEgg(PKM pkm, int tradeLoc, short noneL
var met = pkm.Met_Location;
if (met == tradeLoc)
return new CheckResult(Severity.Valid, LEggLocationTrade, CheckIdentifier.Encounter);
return (short)met == noneLoc
return met == noneLoc
? new CheckResult(Severity.Valid, LEggUnhatched, CheckIdentifier.Encounter)
: new CheckResult(Severity.Invalid, LEggLocationNone, CheckIdentifier.Encounter);
}

View File

@ -78,7 +78,7 @@ private static void ParseMovesWasEggPreRelearn(PKM pkm, CheckMoveResult[] parse,
var TradebackPreevo = pkm.Format == 2 && e.Species > 151;
var NonTradebackLvlMoves = TradebackPreevo
? MoveList.GetExclusivePreEvolutionMoves(pkm, e.Species, info.EvoChainsAllGens[2], 2, e.Version).Where(m => m > Legal.MaxMoveID_1).ToArray()
? MoveList.GetExclusivePreEvolutionMoves(pkm, e.Species, info.EvoChainsAllGens.Gen2, 2, e.Version).Where(m => m > Legal.MaxMoveID_1).ToArray()
: Array.Empty<int>();
var Egg = MoveEgg.GetEggMoves(pkm.PersonalInfo, e.Species, e.Form, e.Version, e.Generation);
@ -238,9 +238,10 @@ private static void ParseMoves(PKM pkm, MoveParseSource source, LegalInfo info,
// Special considerations!
const int NoMinGeneration = 0;
int minGeneration = NoMinGeneration;
if (pkm is IBattleVersion {BattleVersion: not 0} v)
if (pkm.IsOriginalMovesetDeleted())
{
minGeneration = ((GameVersion) v.BattleVersion).GetGeneration();
var (_, resetGame) = pkm.IsMovesetRestricted();
minGeneration = resetGame.GetGeneration();
source.ResetSources();
}
@ -578,7 +579,7 @@ private static void ParseEvolutionsIncompatibleMoves(PKM pkm, CheckMoveResult[]
}
}
private static void ParseShedinjaEvolveMoves(PKM pkm, CheckMoveResult[] parse, IReadOnlyList<int> currentMoves, EvoCriteria[][] evos)
private static void ParseShedinjaEvolveMoves(PKM pkm, CheckMoveResult[] parse, IReadOnlyList<int> currentMoves, EvolutionHistory evos)
{
int shedinjaEvoMoveIndex = 0;
var format = pkm.Format;

View File

@ -27,7 +27,7 @@ public static CheckMoveResult[] VerifyRelearn(PKM pkm, IEncounterTemplate enc, C
};
}
public static bool ShouldNotHaveRelearnMoves(IGeneration enc, PKM pkm) => enc.Generation < 6 || pkm is IBattleVersion {BattleVersion: not 0};
public static bool ShouldNotHaveRelearnMoves(IGeneration enc, PKM pkm) => enc.Generation < 6 || pkm.IsOriginalMovesetDeleted();
private static CheckMoveResult[] VerifyRelearnSpecifiedMoveset(PKM pkm, IReadOnlyList<int> required, CheckMoveResult[] result)
{

View File

@ -6,9 +6,7 @@ namespace PKHeX.Core
{
public static class EvolutionChain
{
private static readonly EvoCriteria[] NONE = Array.Empty<EvoCriteria>();
internal static EvoCriteria[][] GetEvolutionChainsAllGens(PKM pkm, IEncounterTemplate enc)
internal static EvolutionHistory GetEvolutionChainsAllGens(PKM pkm, IEncounterTemplate enc)
{
var chain = GetEvolutionChain(pkm, enc, pkm.Species, (byte)pkm.CurrentLevel);
if (chain.Length == 0 || pkm.IsEgg || enc is EncounterInvalid)
@ -17,40 +15,36 @@ internal static EvoCriteria[][] GetEvolutionChainsAllGens(PKM pkm, IEncounterTem
return GetChainAll(pkm, enc, chain);
}
private static EvoCriteria[][] GetAllEmpty(int count)
private static EvolutionHistory GetChainSingle(PKM pkm, EvoCriteria[] fullChain)
{
var result = new EvoCriteria[count][];
for (int i = 0; i < result.Length; i++)
result[i] = NONE; // default no-evolutions
return result;
var count = Math.Max(2, pkm.Format) + 1;
return new EvolutionHistory(fullChain, count)
{
[pkm.Format] = fullChain,
};
}
private static EvoCriteria[][] GetChainSingle(PKM pkm, EvoCriteria[] fullChain)
private static EvolutionHistory GetChainAll(PKM pkm, IEncounterTemplate enc, EvoCriteria[] fullChain)
{
var chain = GetAllEmpty(Math.Max(2, pkm.Format) + 1);
chain[pkm.Format] = fullChain;
return chain;
}
private static EvoCriteria[][] GetChainAll(PKM pkm, IEncounterTemplate enc, EvoCriteria[] fullChain)
{
int maxgen = ParseSettings.AllowGen1Tradeback && pkm is PK1 ? 2 : pkm.Format;
var GensEvoChains = GetAllEmpty(maxgen + 1);
int maxgen = ParseSettings.AllowGen1Tradeback && pkm.Context == EntityContext.Gen1 ? 2 : pkm.Format;
var GensEvoChains = new EvolutionHistory(fullChain, maxgen + 1);
var head = 0; // inlined FIFO queue indexing
var mostEvolved = fullChain[head++];
var lvl = (byte)pkm.CurrentLevel;
var maxLevel = lvl;
int pkGen = enc.Generation;
// Iterate generations backwards
// Maximum level of an earlier generation (GenX) will never be greater than a later generation (GenX+Y).
int mingen = pkGen >= 3 ? pkGen : GBRestrictions.GetTradebackStatusInitial(pkm) == PotentialGBOrigin.Gen2Only ? 2 : 1;
int mingen = enc.Generation;
if (mingen is 1 or 2)
mingen = GBRestrictions.GetTradebackStatusInitial(pkm) == PotentialGBOrigin.Gen2Only ? 2 : 1;
bool noxfrDecremented = true;
for (int g = GensEvoChains.Length - 1; g >= mingen; g--)
{
if (pkGen <= 2 && g == 6)
if (g == 6 && enc.Generation < 3)
g = 2; // skip over 6543 as it never existed in these.
if (g <= 4 && pkm.Format > 2 && pkm.Format > g && !pkm.HasOriginalMetLocation)
@ -70,99 +64,124 @@ private static EvoCriteria[][] GetChainAll(PKM pkm, IEncounterTemplate enc, EvoC
if (head >= fullChain.Length)
{
if (g <= 2 && pkm.VC1)
GensEvoChains[pkm.Format] = NONE; // invalidate here since we haven't reached the regular invalidation
GensEvoChains.Invalidate(); // invalidate here since we haven't reached the regular invalidation
return GensEvoChains;
}
if (mostEvolved.RequiresLvlUp)
{
// This is a Gen3 pokemon in a Gen4 phase evolution that requires level up and then transferred to Gen5+
// We can deduce that it existed in Gen4 until met level,
// but if current level is met level we can also deduce it existed in Gen3 until maximum met level -1
if (g == 3 && pkm.Format > 4 && lvl == maxLevel)
lvl--;
ReviseMaxLevel(ref lvl, pkm, g, maxLevel);
// The same condition for Gen2 evolution of Gen1 pokemon, level of the pokemon in Gen1 games would be CurrentLevel -1 one level below Gen2 level
else if (g == 1 && pkm.Format == 2 && lvl == maxLevel)
lvl--;
}
mostEvolved = fullChain[head++];
}
// Alolan form evolutions, remove from gens 1-6 chains
if (HasAlolanForm(mostEvolved.Species))
if (g < 7 && HasAlolanForm(mostEvolved.Species) && pkm.Format >= 7 && mostEvolved.Form > 0)
{
if (g < 7 && pkm.Format >= 7 && mostEvolved.Form > 0)
{
if (head >= fullChain.Length)
break;
mostEvolved = fullChain[head++];
}
if (head >= fullChain.Length)
return GensEvoChains;
mostEvolved = fullChain[head++];
}
GensEvoChains[g] = GetEvolutionChain(pkm, enc, mostEvolved.Species, lvl);
ref var genChain = ref GensEvoChains[g];
if (genChain.Length == 0)
var tmp = GetEvolutionChain(pkm, enc, mostEvolved.Species, lvl);
if (tmp.Length == 0)
continue;
if (g > 2 && !pkm.HasOriginalMetLocation && g >= pkGen && noxfrDecremented)
GensEvoChains[g] = tmp;
if (g == 1)
{
bool isTransferred = HasMetLocationUpdatedTransfer(pkGen, g);
CleanGen1(pkm, enc, GensEvoChains);
continue;
}
if (g >= 3 && !pkm.HasOriginalMetLocation && g >= enc.Generation && noxfrDecremented)
{
bool isTransferred = HasMetLocationUpdatedTransfer(enc.Generation, g);
if (!isTransferred)
continue;
noxfrDecremented = g > (pkGen != 3 ? 4 : 5);
noxfrDecremented = g > (enc.Generation != 3 ? 4 : 5);
// Remove previous evolutions below transfer level
// For example a gen3 Charizard in format 7 with current level 36 and met level 36, thus could never be Charmander / Charmeleon in Gen5+.
// chain level for Charmander is 35, is below met level.
int minlvl = GetMinLevelGeneration(pkm, g);
ref var genChain = ref GensEvoChains[g];
int minIndex = Array.FindIndex(genChain, e => e.LevelMax >= minlvl);
if (minIndex != -1)
genChain = genChain.AsSpan(minIndex).ToArray();
}
else if (g == 1)
{
// Remove Gen7 pre-evolutions and chain break scenarios
if (pkm.VC1)
TrimVC1Transfer(pkm, GensEvoChains);
ref var lastGen = ref GensEvoChains[1];
var g1 = lastGen.AsSpan();
// Remove Gen2 post-evolutions (Scizor, Blissey...)
if (g1[0].Species > MaxSpeciesID_1)
{
if (g1.Length == 1)
{
lastGen = Array.Empty<EvoCriteria>();
continue; // done
}
g1 = g1[1..];
}
// Remove Gen2 pre-evolutions (Pichu, Cleffa...)
if (g1[^1].Species > MaxSpeciesID_1)
{
if (g1.Length == 1)
{
lastGen = Array.Empty<EvoCriteria>();
continue; // done
}
g1 = g1[..^1];
}
if (g1.Length != lastGen.Length)
lastGen = g1.ToArray();
// Update min level for the encounter to prevent certain level up moves.
if (g1.Length != 0)
{
ref var last = ref g1[^1];
last = last with { LevelMin = enc.LevelMin };
}
}
}
return GensEvoChains;
}
private static void ReviseMaxLevel(ref byte lvl, PKM pkm, int g, byte maxLevel)
{
// This is a Gen3 pokemon in a Gen4 phase evolution that requires level up and then transferred to Gen5+
// We can deduce that it existed in Gen4 until met level,
// but if current level is met level we can also deduce it existed in Gen3 until maximum met level -1
if (g == 3 && pkm.Format > 4 && lvl == maxLevel)
lvl--;
// The same condition for Gen2 evolution of Gen1 pokemon, level of the pokemon in Gen1 games would be CurrentLevel -1 one level below Gen2 level
else if (g == 1 && pkm.Format == 2 && lvl == maxLevel)
lvl--;
}
private static void CleanGen1(PKM pkm, IEncounterTemplate enc, EvolutionHistory chains)
{
// Remove Gen7 pre-evolutions and chain break scenarios
if (pkm.VC1)
{
var index = Array.FindLastIndex(chains.Gen7, z => z.Species <= MaxSpeciesID_1);
if (index == -1)
{
chains.Invalidate(); // needed a Gen1 species present; invalidate the chain.
return;
}
}
TrimSpeciesAbove(enc, MaxSpeciesID_1, ref chains.Gen1);
}
private static void TrimSpeciesAbove(IEncounterTemplate enc, int species, ref EvoCriteria[] chain)
{
var span = chain.AsSpan();
// Remove post-evolutions
if (span[0].Species > species)
{
if (span.Length == 1)
{
chain = Array.Empty<EvoCriteria>();
return;
}
span = span[1..];
}
// Remove pre-evolutions
if (span[^1].Species > species)
{
if (span.Length == 1)
{
chain = Array.Empty<EvoCriteria>();
return;
}
span = span[..^1];
}
if (span.Length != chain.Length)
chain = span.ToArray();
// Update min level for the encounter to prevent certain level up moves.
if (span.Length != 0)
{
ref var last = ref span[^1];
last = last with { LevelMin = enc.LevelMin };
}
}
private static bool HasMetLocationUpdatedTransfer(int originalGeneration, int currentGeneration) => originalGeneration switch
{
< 3 => currentGeneration >= 3,
@ -170,29 +189,20 @@ private static EvoCriteria[][] GetChainAll(PKM pkm, IEncounterTemplate enc, EvoC
_ => false,
};
private static void TrimVC1Transfer(PKM pkm, EvoCriteria[][] allChains)
{
var vc7 = allChains[7];
var gen1Index = Array.FindLastIndex(vc7, z => z.Species <= MaxSpeciesID_1);
if (gen1Index == -1)
allChains[pkm.Format] = NONE; // needed a Gen1 species present; invalidate the chain.
}
private static EvoCriteria[] GetEvolutionChain(PKM pkm, IEncounterTemplate enc, int mostEvolvedSpecies, byte maxlevel)
{
int min = enc.LevelMin;
if (pkm.HasOriginalMetLocation && pkm.Met_Level != 0)
min = pkm.Met_Level;
var chain = GetValidPreEvolutions(pkm, minLevel: min);
return TrimChain(chain, enc, mostEvolvedSpecies, maxlevel);
}
private static EvoCriteria[] TrimChain(EvoCriteria[] chain, IEncounterTemplate enc, int mostEvolvedSpecies, byte maxlevel)
{
if (enc.Species == mostEvolvedSpecies)
{
if (chain.Length == 1)
return chain;
var index = Array.FindLastIndex(chain, z => z.Species == enc.Species);
if (index == -1)
return Array.Empty<EvoCriteria>();
return new[] { chain[index] };
}
return TrimChainSingle(chain, enc);
// Evolution chain is in reverse order (devolution)
// Find the index of the minimum species to determine the end of the chain
@ -210,6 +220,11 @@ private static EvoCriteria[] GetEvolutionChain(PKM pkm, IEncounterTemplate enc,
CheckLastEncounterRemoval(enc, chain);
}
return TrimChainMore(chain, mostEvolvedSpecies, maxlevel);
}
private static EvoCriteria[] TrimChainMore(EvoCriteria[] chain, int mostEvolvedSpecies, byte maxlevel)
{
// maxspec is used to remove future geneneration evolutions, to gather evolution chain of a pokemon in previous generations
var maxSpeciesIndex = Array.FindIndex(chain, z => z.Species == mostEvolvedSpecies);
if (maxSpeciesIndex > 0)
@ -223,13 +238,28 @@ private static EvoCriteria[] GetEvolutionChain(PKM pkm, IEncounterTemplate enc,
chain = Array.FindAll(chain, z => z.LevelMin <= maxlevel);
// Reduce the evolution chain levels to max level to limit any later analysis/results.
SanitizeMaxLevel(chain, maxlevel);
return chain;
}
private static void SanitizeMaxLevel(EvoCriteria[] chain, byte maxlevel)
{
for (var i = 0; i < chain.Length; i++)
{
ref var c = ref chain[i];
c = c with { LevelMax = Math.Min(c.LevelMax, maxlevel) };
}
}
return chain;
private static EvoCriteria[] TrimChainSingle(EvoCriteria[] chain, IEncounterTemplate enc)
{
if (chain.Length == 1)
return chain;
var index = Array.FindLastIndex(chain, z => z.Species == enc.Species);
if (index == -1)
return Array.Empty<EvoCriteria>();
return new[] { chain[index] };
}
private static void CheckLastEncounterRemoval(IEncounterTemplate enc, EvoCriteria[] chain)
@ -263,8 +293,10 @@ internal static EvoCriteria[] GetValidPreEvolutions(PKM pkm, int maxspeciesorigi
if (maxspeciesorigin == -1 && pkm.InhabitedGeneration(2) && pkm.Format <= 2 && pkm.Generation == 1)
maxspeciesorigin = MaxSpeciesID_2;
int tree = Math.Max(2, pkm.Format);
var et = EvolutionTree.GetEvolutionTree(pkm, tree);
var context = pkm.Context;
if (context < EntityContext.Gen2)
context = EntityContext.Gen2;
var et = EvolutionTree.GetEvolutionTree(context);
return et.GetValidPreEvolutions(pkm, maxLevel: (byte)maxLevel, maxSpeciesOrigin: maxspeciesorigin, skipChecks: skipChecks, minLevel: (byte)minLevel);
}

View File

@ -0,0 +1,55 @@
using System;
namespace PKHeX.Core;
/// <summary>
/// Stores the possible evolution bounds for a parsed entity with respect to its origins and game traversal.
/// </summary>
public class EvolutionHistory
{
private static readonly EvoCriteria[] NONE = Array.Empty<EvoCriteria>();
public EvoCriteria[] Gen1 = NONE;
public EvoCriteria[] Gen2 = NONE;
public EvoCriteria[] Gen3 = NONE;
public EvoCriteria[] Gen4 = NONE;
public EvoCriteria[] Gen5 = NONE;
public EvoCriteria[] Gen6 = NONE;
public EvoCriteria[] Gen7 = NONE;
public EvoCriteria[] Gen7b = NONE;
public EvoCriteria[] Gen8 = NONE;
public EvoCriteria[] Gen8a => Gen8; // future: separate field instead of copy
public EvoCriteria[] Gen8b => Gen8; // future: separate field instead of copy
public readonly int Length;
public readonly EvoCriteria[] FullChain;
public EvolutionHistory(EvoCriteria[] fullChain, int count)
{
FullChain = fullChain;
Length = count;
}
public ref EvoCriteria[] this[int index]
{
get
{
if (index == 1) return ref Gen1;
if (index == 2) return ref Gen2;
if (index == 3) return ref Gen3;
if (index == 4) return ref Gen4;
if (index == 5) return ref Gen5;
if (index == 6) return ref Gen6;
if (index == 7) return ref Gen7;
if (index == 8) return ref Gen8;
throw new IndexOutOfRangeException(nameof(index));
}
}
internal void Invalidate() => this[Length - 1] = NONE;
public bool HasVisitedSWSH => Gen8.Length != 0;
public bool HasVisitedPLA => Gen8a.Length != 0;
public bool HasVisitedBDSP => Gen8b.Length != 0;
}

View File

@ -30,6 +30,8 @@ internal static class EvolutionLegality
(int)Species.PorygonZ,
(int)Species.Sylveon,
(int)Species.Kleavor,
};
private static readonly HashSet<int> FutureEvolutionsGen2 = new()
@ -53,6 +55,9 @@ internal static class EvolutionLegality
(int)Species.PorygonZ,
(int)Species.Sylveon,
(int)Species.Wyrdeer,
(int)Species.Ursaluna,
};
private static readonly HashSet<int> FutureEvolutionsGen3 = new()

View File

@ -14,17 +14,17 @@ namespace PKHeX.Core
/// </remarks>
public sealed class EvolutionTree
{
private static readonly EvolutionTree Evolves1 = new(GetResource("rby"), Gen1, PersonalTable.Y, MaxSpeciesID_1);
private static readonly EvolutionTree Evolves2 = new(GetResource("gsc"), Gen2, PersonalTable.C, MaxSpeciesID_2);
private static readonly EvolutionTree Evolves3 = new(GetResource("g3"), Gen3, PersonalTable.RS, MaxSpeciesID_3);
private static readonly EvolutionTree Evolves4 = new(GetResource("g4"), Gen4, PersonalTable.DP, MaxSpeciesID_4);
private static readonly EvolutionTree Evolves5 = new(GetResource("g5"), Gen5, PersonalTable.BW, MaxSpeciesID_5);
private static readonly EvolutionTree Evolves6 = new(GetReader("ao"), Gen6, PersonalTable.AO, MaxSpeciesID_6);
private static readonly EvolutionTree Evolves7 = new(GetReader("uu"), Gen7, PersonalTable.USUM, MaxSpeciesID_7_USUM);
private static readonly EvolutionTree Evolves7b = new(GetReader("gg"), Gen7, PersonalTable.GG, MaxSpeciesID_7b);
private static readonly EvolutionTree Evolves8 = new(GetReader("ss"), Gen8, PersonalTable.SWSH, MaxSpeciesID_8);
private static readonly EvolutionTree Evolves8a = new(GetReader("la"), Gen8, PersonalTable.LA, MaxSpeciesID_8a);
private static readonly EvolutionTree Evolves8b = new(GetReader("bs"), Gen8, PersonalTable.BDSP, MaxSpeciesID_8b);
public static readonly EvolutionTree Evolves1 = new(GetResource("rby"), Gen1, PersonalTable.Y, MaxSpeciesID_1);
public static readonly EvolutionTree Evolves2 = new(GetResource("gsc"), Gen2, PersonalTable.C, MaxSpeciesID_2);
public static readonly EvolutionTree Evolves3 = new(GetResource("g3"), Gen3, PersonalTable.RS, MaxSpeciesID_3);
public static readonly EvolutionTree Evolves4 = new(GetResource("g4"), Gen4, PersonalTable.DP, MaxSpeciesID_4);
public static readonly EvolutionTree Evolves5 = new(GetResource("g5"), Gen5, PersonalTable.BW, MaxSpeciesID_5);
public static readonly EvolutionTree Evolves6 = new(GetReader("ao"), Gen6, PersonalTable.AO, MaxSpeciesID_6);
public static readonly EvolutionTree Evolves7 = new(GetReader("uu"), Gen7, PersonalTable.USUM, MaxSpeciesID_7_USUM);
public static readonly EvolutionTree Evolves7b = new(GetReader("gg"), Gen7, PersonalTable.GG, MaxSpeciesID_7b);
public static readonly EvolutionTree Evolves8 = new(GetReader("ss"), Gen8, PersonalTable.SWSH, MaxSpeciesID_8);
public static readonly EvolutionTree Evolves8a = new(GetReader("la"), Gen8, PersonalTable.LA, MaxSpeciesID_8a);
public static readonly EvolutionTree Evolves8b = new(GetReader("bs"), Gen8, PersonalTable.BDSP, MaxSpeciesID_8b);
private static ReadOnlySpan<byte> GetResource(string resource) => Util.GetBinaryResource($"evos_{resource}.pkl");
private static BinLinkerAccessor GetReader(string resource) => BinLinkerAccessor.Get(GetResource(resource), resource);
@ -38,33 +38,20 @@ static EvolutionTree()
Evolves8b.FixEvoTreeBS();
}
public static EvolutionTree GetEvolutionTree(int generation) => generation switch
public static EvolutionTree GetEvolutionTree(EntityContext context) => context switch
{
1 => Evolves1,
2 => Evolves2,
3 => Evolves3,
4 => Evolves4,
5 => Evolves5,
6 => Evolves6,
7 => Evolves7,
_ => Evolves8,
};
public static EvolutionTree GetEvolutionTree(PKM pkm, int generation) => generation switch
{
1 => Evolves1,
2 => Evolves2,
3 => Evolves3,
4 => Evolves4,
5 => Evolves5,
6 => Evolves6,
7 => pkm.Version is (int)GO or (int)GP or (int)GE ? Evolves7b : Evolves7,
_ => pkm.Version switch
{
(int)PLA => Evolves8a,
(int)BD or (int)SP => Evolves8b,
_ => Evolves8,
},
EntityContext.Gen1 => Evolves1,
EntityContext.Gen2 => Evolves2,
EntityContext.Gen3 => Evolves3,
EntityContext.Gen4 => Evolves4,
EntityContext.Gen5 => Evolves5,
EntityContext.Gen6 => Evolves6,
EntityContext.Gen7 => Evolves7,
EntityContext.Gen8 => Evolves8,
EntityContext.Gen7b => Evolves7b,
EntityContext.Gen8a => Evolves8a,
EntityContext.Gen8b => Evolves8b,
_ => throw new ArgumentOutOfRangeException(nameof(context), context, null)
};
private readonly IReadOnlyList<EvolutionMethod[]> Entries;
@ -233,26 +220,11 @@ public EvoCriteria[] GetValidPreEvolutions(PKM pkm, byte maxLevel, int maxSpecie
{
if (maxSpeciesOrigin <= 0)
maxSpeciesOrigin = GetMaxSpeciesOrigin(pkm);
if (pkm.IsEgg && !skipChecks)
{
return new[]
{
new EvoCriteria{ Species = (ushort)pkm.Species, Form = (byte)pkm.Form, LevelMax = maxLevel, LevelMin = maxLevel },
};
}
// Shedinja's evolution case can be a little tricky; hard-code handling.
if (pkm.Species == (int)Species.Shedinja && maxLevel >= 20 && (!pkm.HasOriginalMetLocation || minLevel < maxLevel))
{
var min = Math.Max(minLevel, (byte)20);
return new[]
{
new EvoCriteria { Species = (ushort)Species.Shedinja, LevelMax = maxLevel, LevelMin = min, Method = EvolutionType.LevelUp },
new EvoCriteria { Species = (ushort)Species.Nincada, LevelMax = maxLevel, LevelMin = minLevel },
};
}
ushort species = (ushort)pkm.Species;
byte form = (byte)pkm.Form;
return GetExplicitLineage(pkm, maxLevel, skipChecks, maxSpeciesOrigin, minLevel);
return GetExplicitLineage(species, form, pkm, minLevel, maxLevel, maxSpeciesOrigin, skipChecks);
}
public bool IsSpeciesDerivedFrom(int species, int form, int otherSpecies, int otherForm, bool ignoreForm = true)
@ -348,16 +320,39 @@ public IEnumerable<int> GetEvolutions(int species, int form)
/// <summary>
/// Generates the reverse evolution path for the input <see cref="pkm"/>.
/// </summary>
/// <param name="species">Entity Species to begin the chain</param>
/// <param name="form">Entity Form to begin the chain</param>
/// <param name="pkm">Entity data</param>
/// <param name="maxLevel">Maximum level</param>
/// <param name="levelMin">Minimum level</param>
/// <param name="levelMax">Maximum level</param>
/// <param name="maxSpeciesID">Clamp for maximum species ID</param>
/// <param name="skipChecks">Skip the secondary checks that validate the evolution</param>
/// <param name="maxSpeciesOrigin">Clamp for maximum species ID</param>
/// <param name="minLevel">Minimum level</param>
private EvoCriteria[] GetExplicitLineage(PKM pkm, byte maxLevel, bool skipChecks, int maxSpeciesOrigin, byte minLevel)
private EvoCriteria[] GetExplicitLineage(ushort species, byte form, PKM pkm, byte levelMin, byte levelMax, int maxSpeciesID, bool skipChecks)
{
var species = pkm.Species;
var form = pkm.Form;
var lvl = maxLevel;
if (pkm.IsEgg && !skipChecks)
{
return new[]
{
new EvoCriteria{ Species = species, Form = form, LevelMax = levelMax, LevelMin = levelMax },
};
}
// Shedinja's evolution case can be a little tricky; hard-code handling.
if (species == (int)Species.Shedinja && levelMax >= 20 && (!pkm.HasOriginalMetLocation || levelMin < levelMax))
{
var min = Math.Max(levelMin, (byte)20);
return new[]
{
new EvoCriteria { Species = (ushort)Species.Shedinja, LevelMax = levelMax, LevelMin = min, Method = EvolutionType.LevelUp },
new EvoCriteria { Species = (ushort)Species.Nincada, LevelMax = levelMax, LevelMin = levelMin },
};
}
return GetLineage(species, form, pkm, levelMin, levelMax, maxSpeciesID, skipChecks);
}
private EvoCriteria[] GetLineage(int species, int form, PKM pkm, byte levelMin, byte levelMax, int maxSpeciesID, bool skipChecks)
{
var lvl = levelMax;
var first = new EvoCriteria { Species = (ushort)species, Form = (byte)form, LevelMax = lvl };
const int maxEvolutions = 3;
@ -366,7 +361,8 @@ private EvoCriteria[] GetExplicitLineage(PKM pkm, byte maxLevel, bool skipChecks
switch (species)
{
case (int)Species.Silvally: form = 0;
case (int)Species.Silvally:
form = 0;
break;
}
@ -376,9 +372,9 @@ private EvoCriteria[] GetExplicitLineage(PKM pkm, byte maxLevel, bool skipChecks
while (true)
{
var key = GetLookupKey(species, form);
bool oneValid = false;
var node = Lineage[key];
bool oneValid = false;
foreach (var link in node)
{
if (link.IsEvolutionBanned(pkm) && !skipChecks)
@ -388,17 +384,18 @@ private EvoCriteria[] GetExplicitLineage(PKM pkm, byte maxLevel, bool skipChecks
if (!evo.Valid(pkm, lvl, skipChecks))
continue;
if (evo.RequiresLevelUp && minLevel >= lvl)
if (evo.RequiresLevelUp && levelMin >= lvl)
break; // impossible evolution
oneValid = true;
UpdateMinValues(evos[..ctr], evo, minLevel);
UpdateMinValues(evos[..ctr], evo, levelMin);
species = link.Species;
form = link.Form;
evos[ctr++] = evo.GetEvoCriteria((ushort)species, (byte)form, lvl);
if (evo.RequiresLevelUp)
lvl--;
oneValid = true;
break;
}
if (!oneValid)
@ -407,11 +404,11 @@ private EvoCriteria[] GetExplicitLineage(PKM pkm, byte maxLevel, bool skipChecks
// Remove future gen pre-evolutions; no Munchlax from a Gen3 Snorlax, no Pichu from a Gen1-only Raichu, etc
ref var last = ref evos[ctr - 1];
if (last.Species > maxSpeciesOrigin)
if (last.Species > maxSpeciesID)
{
for (int i = 0; i < ctr; i++)
{
if (evos[i].Species > maxSpeciesOrigin)
if (evos[i].Species > maxSpeciesID)
continue;
ctr--;
break;
@ -421,9 +418,16 @@ private EvoCriteria[] GetExplicitLineage(PKM pkm, byte maxLevel, bool skipChecks
// Last species is the wild/hatched species, the minimum level is because it has not evolved from previous species
var result = evos[..ctr];
last = ref result[^1];
last = last with { LevelMin = minLevel, LevelUpRequired = 0 };
last = last with { LevelMin = levelMin, LevelUpRequired = 0 };
// Rectify minimum levels
RectifyMinimumLevels(result);
return result.ToArray();
}
private static void RectifyMinimumLevels(Span<EvoCriteria> result)
{
for (int i = result.Length - 2; i >= 0; i--)
{
ref var evo = ref result[i];
@ -431,8 +435,6 @@ private EvoCriteria[] GetExplicitLineage(PKM pkm, byte maxLevel, bool skipChecks
var min = (byte)Math.Max(prev.LevelMin + evo.LevelUpRequired, evo.LevelMin);
evo = evo with { LevelMin = min };
}
return result.ToArray();
}
private static void UpdateMinValues(Span<EvoCriteria> evos, EvolutionMethod evo, byte minLevel)

View File

@ -467,6 +467,7 @@ public static class LegalityCheckStrings
public static string LTransferFlagIllegal { get; set; } = "Flagged as illegal by the game (glitch abuse).";
public static string LTransferHTFlagRequired { get; set; } = "Current handler cannot be past gen OT for transferred specimen.";
public static string LTransferMet { get; set; } = "Invalid Met Location, expected Poké Transfer or Crown.";
public static string LTransferNotPossible { get; set; } = "Unable to transfer into current format from origin format.";
public static string LTransferMetLocation { get; set; } = "Invalid Transfer Met Location.";
public static string LTransferMove { get; set; } = "Incompatible transfer move.";
public static string LTransferMoveG4HM { get; set; } = "Defog and Whirlpool. One of the two moves should have been removed before transferred to Generation 5.";

View File

@ -192,7 +192,11 @@ public void SetEvolutionMoves(Span<int> moves, int ctr = 0)
public void SetLevelUpMoves(int startLevel, int endLevel, Span<int> moves, ReadOnlySpan<int> ignore, int ctr = 0)
{
int startIndex = Array.FindIndex(Levels, z => z >= startLevel);
if (startIndex == -1)
return; // No more remain
int endIndex = Array.FindIndex(Levels, z => z > endLevel);
if (endIndex == -1)
endIndex = Levels.Length;
for (int i = startIndex; i < endIndex; i++)
{
int move = Moves[i];

View File

@ -256,7 +256,7 @@ private void UpdateVCTransferInfo()
var enc = (Info.EncounterOriginalGB = EncounterMatch);
if (enc is EncounterInvalid)
return;
var vc = EncounterStaticGenerator.GetVCStaticTransferEncounter(pkm, enc, Info.EvoChainsAllGens[7]);
var vc = EncounterStaticGenerator.GetVCStaticTransferEncounter(pkm, enc, Info.EvoChainsAllGens.Gen7);
Info.EncounterMatch = vc;
foreach (var z in Transfer.VerifyVCEncounter(pkm, enc, vc, Info.Moves))

View File

@ -161,7 +161,7 @@ internal static int[] GetBaseEggMoves(PKM pkm, int species, int form, GameVersio
return Array.Empty<int>();
}
internal static IReadOnlyList<int>[] GetValidMovesAllGens(PKM pkm, EvoCriteria[][] evoChains, MoveSourceType types = MoveSourceType.ExternalSources, bool RemoveTransferHM = true)
internal static IReadOnlyList<int>[] GetValidMovesAllGens(PKM pkm, EvolutionHistory evoChains, MoveSourceType types = MoveSourceType.ExternalSources, bool RemoveTransferHM = true)
{
var result = new IReadOnlyList<int>[evoChains.Length];
for (int i = 0; i < result.Length; i++)
@ -181,9 +181,7 @@ internal static IReadOnlyList<int>[] GetValidMovesAllGens(PKM pkm, EvoCriteria[]
internal static IEnumerable<int> GetValidMoves(PKM pkm, EvoCriteria[] evoChain, int generation, MoveSourceType types = MoveSourceType.ExternalSources, bool RemoveTransferHM = true)
{
GameVersion version = (GameVersion)pkm.Version;
if (!pkm.IsMovesetRestricted(generation))
version = Any;
var (_, version) = pkm.IsMovesetRestricted();
return GetValidMoves(pkm, version, evoChain, generation, types: types, RemoveTransferHM: RemoveTransferHM);
}

View File

@ -6,7 +6,7 @@ namespace PKHeX.Core
{
public static class MoveListSuggest
{
private static int[] GetSuggestedMoves(PKM pkm, EvoCriteria[][] evoChains, MoveSourceType types, IEncounterTemplate enc)
private static int[] GetSuggestedMoves(PKM pkm, EvolutionHistory evoChains, MoveSourceType types, IEncounterTemplate enc)
{
if (pkm.IsEgg && pkm.Format <= 5) // pre relearn
return MoveList.GetBaseEggMoves(pkm, pkm.Species, 0, (GameVersion)pkm.Version, pkm.CurrentLevel);
@ -30,15 +30,13 @@ private static int[] GetSuggestedMoves(PKM pkm, EvoCriteria[][] evoChains, MoveS
return GetValidMoves(pkm, evoChains, types).Skip(1).ToArray(); // skip move 0
}
private static IEnumerable<int> GetValidMoves(PKM pkm, EvoCriteria[][] evoChains, MoveSourceType types = MoveSourceType.ExternalSources, bool RemoveTransferHM = true)
private static IEnumerable<int> GetValidMoves(PKM pkm, EvolutionHistory evoChains, MoveSourceType types = MoveSourceType.ExternalSources, bool RemoveTransferHM = true)
{
GameVersion version = (GameVersion)pkm.Version;
if (!pkm.IsMovesetRestricted())
version = GameVersion.Any;
var (_, version) = pkm.IsMovesetRestricted();
return GetValidMoves(pkm, version, evoChains, types: types, RemoveTransferHM: RemoveTransferHM);
}
private static IEnumerable<int> GetValidMoves(PKM pkm, GameVersion version, EvoCriteria[][] evoChains, MoveSourceType types = MoveSourceType.Reminder, bool RemoveTransferHM = true)
private static IEnumerable<int> GetValidMoves(PKM pkm, GameVersion version, EvolutionHistory evoChains, MoveSourceType types = MoveSourceType.Reminder, bool RemoveTransferHM = true)
{
var r = new List<int> { 0 };
if (types.HasFlagFast(MoveSourceType.RelearnMoves) && pkm.Format >= 6)

View File

@ -31,8 +31,9 @@ private static readonly LearnLookup
public static LearnVersion GetIsLevelUpMove(PKM pkm, int species, int form, int maxLevel, int generation, int move, int minlvlG1, int minlvlG2, GameVersion version = Any)
{
if (pkm.IsMovesetRestricted(generation))
version = (GameVersion)pkm.Version;
var restrict = pkm.IsMovesetRestricted();
if (restrict.IsRestricted)
version = restrict.Game;
return generation switch
{
@ -43,7 +44,7 @@ public static LearnVersion GetIsLevelUpMove(PKM pkm, int species, int form, int
4 => GetIsLevelUp4(species, form, move, maxLevel, version),
5 => GetIsLevelUp5(species, form, move, maxLevel, version),
6 => GetIsLevelUp6(species, form, move, maxLevel, version),
7 => GetIsLevelUp7(species, form, move, version), // move reminder can give any move 1-100
7 => GetIsLevelUp7(species, form, move, pkm.GG ? (GameVersion)pkm.Version : version), // move reminder can give any move 1-100
8 => GetIsLevelUp8(species, form, move, maxLevel, version),
_ => LearnNONE,
};
@ -267,8 +268,10 @@ private static LearnVersion GetIsLevelUp3Deoxys(int form, int move, int lvl, Gam
public static IEnumerable<int> GetMovesLevelUp(PKM pkm, int species, int form, int maxLevel, int minlvlG1, int minlvlG2, GameVersion version, bool MoveReminder, int generation)
{
if (pkm.IsMovesetRestricted(generation))
version = (GameVersion)pkm.Version;
var restrict = pkm.IsMovesetRestricted();
if (restrict.IsRestricted)
version = restrict.Game;
return generation switch
{
1 => GetMovesLevelUp1(species, form, maxLevel, minlvlG1, version),
@ -277,7 +280,7 @@ public static IEnumerable<int> GetMovesLevelUp(PKM pkm, int species, int form, i
4 => GetMovesLevelUp4(species, form, maxLevel, version),
5 => GetMovesLevelUp5(species, form, maxLevel, version),
6 => GetMovesLevelUp6(species, form, maxLevel, version),
7 => GetMovesLevelUp7(species, form, maxLevel, MoveReminder, version),
7 => GetMovesLevelUp7(species, form, maxLevel, MoveReminder, pkm.GG ? (GameVersion)pkm.Version : version),
8 => GetMovesLevelUp8(species, form, maxLevel, version),
_ => Array.Empty<int>(),
};

View File

@ -8,8 +8,10 @@ public static class MoveTechnicalMachine
{
public static GameVersion GetIsMachineMove(PKM pkm, int species, int form, int generation, int move, GameVersion ver = GameVersion.Any, bool RemoveTransfer = false)
{
if (pkm.IsMovesetRestricted(generation))
ver = (GameVersion) pkm.Version;
var (isRestricted, game) = pkm.IsMovesetRestricted();
if (isRestricted)
ver = game;
switch (generation)
{
case 1: return GetIsMachine1(species, move);
@ -30,8 +32,10 @@ public static GameVersion GetIsMachineMove(PKM pkm, int species, int form, int g
public static GameVersion GetIsRecordMove(PKM pkm, int species, int form, int generation, int move, GameVersion ver = GameVersion.Any, bool allowBit = false)
{
if (pkm.IsMovesetRestricted(generation))
ver = (GameVersion)pkm.Version;
var (isRestricted, game) = pkm.IsMovesetRestricted();
if (isRestricted)
ver = game;
return generation switch
{
8 => GetIsRecord8(pkm, species, move, form, ver, allowBit),
@ -223,8 +227,9 @@ private static GameVersion GetIsRecord8(PKM pkm, int species, int move, int form
public static IEnumerable<int> GetTMHM(PKM pkm, int species, int form, int generation, GameVersion ver = GameVersion.Any, bool RemoveTransfer = true)
{
var r = new List<int>();
if (pkm.IsMovesetRestricted(generation))
ver = (GameVersion)pkm.Version;
var (isRestricted, game) = pkm.IsMovesetRestricted();
if (isRestricted)
ver = game;
switch (generation)
{
@ -237,7 +242,7 @@ public static IEnumerable<int> GetTMHM(PKM pkm, int species, int form, int gener
case 4: AddMachine4(r, species, pkm.Format, RemoveTransfer, form); break;
case 5: AddMachine5(r, species, form); break;
case 6: AddMachine6(r, species, form, ver); break;
case 7: AddMachine7(r, species, form, ver); break;
case 7: AddMachine7(r, species, form, pkm.GG ? (GameVersion)pkm.Version : ver); break;
case 8: AddMachine8(r, species, form, ver); break;
}
return r.Distinct();

View File

@ -153,7 +153,7 @@ private static GameVersion GetIsTutor7(PKM pkm, int species, int form, bool spec
private static GameVersion GetIsTutor8(PKM pkm, int species, int form, bool specialTutors, int move)
{
if (pkm.LA)
if (pkm is PA8)
{
var pi = (PersonalInfoLA)PersonalTable.LA.GetFormEntry(species, form);
if (!pi.IsPresentInGame)
@ -278,7 +278,7 @@ private static void AddMovesTutor7(List<int> moves, int species, int form, PKM p
private static void AddMovesTutor8(List<int> moves, int species, int form, PKM pkm, bool specialTutors)
{
if (pkm.LA)
if (pkm is PA8)
{
var pi = (PersonalInfoLA)PersonalTable.LA.GetFormEntry(species, form);
if (!pi.IsPresentInGame)
@ -292,7 +292,7 @@ private static void AddMovesTutor8(List<int> moves, int species, int form, PKM p
}
return;
}
if (pkm.BDSP)
if (pkm is PB8)
{
var pi = (PersonalInfoBDSP)PersonalTable.BDSP.GetFormEntry(species, form);
if (!pi.IsPresentInGame)

View File

@ -19,16 +19,18 @@ internal static class EvolutionRestrictions
/// </summary>
private static readonly Dictionary<int, MoveEvolution> SpeciesEvolutionWithMove = new()
{
{(int)Eevee, new(0, 0)}, // FairyMoves
{(int)MimeJr, new(1, (int)Mimic)},
{(int)Bonsly, new(2, (int)Mimic)},
{(int)Aipom, new(3, (int)Mimic)},
{(int)Lickitung, new(4, (int)Rollout)},
{(int)Tangela, new(5, (int)AncientPower)},
{(int)Yanma, new(6, (int)AncientPower)},
{(int)Piloswine, new(7, (int)AncientPower)},
{(int)Steenee, new(8, (int)Stomp)},
{(int)Clobbopus, new(9, (int)Taunt)},
{(int)Eevee, new(00, 0)}, // FairyMoves
{(int)MimeJr, new(01, (int)Mimic)},
{(int)Bonsly, new(02, (int)Mimic)},
{(int)Aipom, new(03, (int)Mimic)},
{(int)Lickitung, new(04, (int)Rollout)},
{(int)Tangela, new(05, (int)AncientPower)},
{(int)Yanma, new(06, (int)AncientPower)},
{(int)Piloswine, new(07, (int)AncientPower)},
{(int)Steenee, new(08, (int)Stomp)},
{(int)Clobbopus, new(09, (int)Taunt)},
{(int)Stantler, new(10, (int)PsyshieldBash)},
{(int)Qwilfish, new(11, (int)BarbBarrage)},
};
private readonly record struct MoveEvolution(int ReferenceIndex, int Move);
@ -65,6 +67,7 @@ internal static class EvolutionRestrictions
(int)SpiritBreak,
(int)StrangeSteam,
(int)MistyExplosion,
(int)SpringtideStorm,
};
/// <summary>
@ -85,7 +88,6 @@ internal static class EvolutionRestrictions
new byte[] { 00, 00, 00, 00, 00, 00, 00, 00, 35 }, // Grapploct (Clobbopus with Taunt)
new byte[] { 00, 00, 00, 00, 00, 00, 00, 00, 00 }, // Wyrdeer (Stantler with AGILE Psyshield Bash)
new byte[] { 00, 00, 00, 00, 00, 00, 00, 00, 00 }, // Overqwil (Qwilfish with STRONG Barb Barrage)
new byte[] { 00, 00, 00, 00, 00, 00, 00, 00, 00 }, // Basculegion (Basculin-2 with Recoil Move)
};
private static readonly byte[] MinLevelEvolutionWithMove_8LA =
@ -102,7 +104,6 @@ internal static class EvolutionRestrictions
99, // Grapploct (Clobbopus with Taunt)
31, // Wyrdeer (Stantler with AGILE Psyshield Bash)
25, // Overqwil (Qwilfish with STRONG Barb Barrage)
34, // Basculegion (Basculin-2 with Recoil Move)
};
private static readonly bool[][] CanEggHatchWithEvolveMove =
@ -119,7 +120,6 @@ internal static class EvolutionRestrictions
new [] { false, false, false, false, false, false, false, false, true }, // Grapploct (Clobbopus with Taunt)
new [] { false, false, false, false, false, false, false, false, false }, // Wyrdeer (Stantler with AGILE Psyshield Bash)
new [] { false, false, false, false, false, false, false, false, false }, // Overqwil (Qwilfish with STRONG Barb Barrage)
new [] { false, false, false, false, false, false, false, false, false }, // Basculegion (Basculin-2 with Recoil Move)
};
/// <summary>
@ -170,13 +170,13 @@ public static bool IsValidEvolutionWithMove(PKM pkm, LegalInfo info)
}
// Current level must be at least the minimum post-evolution level.
var lvl = GetMinLevelKnowRequiredMove(pkm, gen, index);
var lvl = GetMinLevelKnowRequiredMove(pkm, gen, index, info.EvoChainsAllGens);
return pkm.CurrentLevel >= lvl;
}
private static int GetMinLevelKnowRequiredMove(PKM pkm, int gen, int index)
private static int GetMinLevelKnowRequiredMove(PKM pkm, int gen, int index, EvolutionHistory evos)
{
if (gen == 8 && pkm.LA) // No Level Up required, and different levels than mainline SW/SH.
if (gen == 8 && pkm.HasVisitedLA(evos.Gen8a)) // No Level Up required, and different levels than mainline SW/SH.
return MinLevelEvolutionWithMove_8LA[index];
var lvl = GetLevelLearnMove(pkm, gen, index);

View File

@ -396,7 +396,7 @@ internal static IEnumerable<GameVersion> GetGen1Versions(IEncounterTemplate enc)
private static bool GetCatchRateMatchesPreEvolution(PK1 pkm, int catch_rate)
{
// For species catch rate, discard any species that has no valid encounters and a different catch rate than their pre-evolutions
var table = EvolutionTree.GetEvolutionTree(1);
var table = EvolutionTree.Evolves1;
var chain = table.GetValidPreEvolutions(pkm, maxLevel: (byte)pkm.CurrentLevel);
foreach (var entry in chain)
{

View File

@ -23,7 +23,7 @@ public override IEnumerable<ushort> GetMemoryItemParams() => Legal.HeldItem_AO.C
public override bool CanObtainMemoryOT(GameVersion pkmVersion, byte memory) => pkmVersion switch
{
GameVersion.SW or GameVersion.SH => CanObtainMemorySWSH(memory),
_ => false,
_ => memory is 0,
};
public override bool CanObtainMemoryHT(GameVersion pkmVersion, byte memory) => CanObtainMemorySWSH(memory);

View File

@ -66,12 +66,6 @@ public static bool CanBuyItem(int generation, int item, GameVersion version = Ga
return context.CanBuyItem(item, version);
}
public static bool GetCanLearnMachineMove(PKM pkm, int move, int generation, GameVersion version = GameVersion.Any)
{
var evos = EvolutionChain.GetValidPreEvolutions(pkm);
return GetCanLearnMachineMove(pkm, evos, move, generation, version);
}
public static bool GetCanLearnMachineMove(PKM pkm, EvoCriteria[] evos, int move, int generation, GameVersion version = GameVersion.Any)
{
if (IsOtherFormMove(pkm, evos, move, generation, version, types: MoveSourceType.AllMachines))
@ -145,10 +139,10 @@ public static bool CanKnowMove(PKM pkm, MemoryVariableSet memory, int gen, Legal
return false;
}
private static bool IsSpecialEncounterMoveEggDeleted(PKM pkm, IEncounterable enc)
private static bool IsSpecialEncounterMoveEggDeleted(PKM pkm, IEncounterTemplate enc)
{
if (pkm is IBattleVersion { BattleVersion: not 0 }) // can hide Relearn moves (Gen6+ Eggs, or DexNav)
return enc is EncounterEgg { Generation: >= 6 } or EncounterSlot6AO { CanDexNav: true } or EncounterSlot8b { IsUnderground: true };
if (pkm.IsOriginalMovesetDeleted())
return true;
return enc is EncounterEgg { Generation: < 6 }; // egg moves that are no longer in the movepool
}
@ -159,7 +153,7 @@ public static bool GetCanRelearnMove(PKM pkm, int move, int generation, EvoCrite
return MoveList.GetValidMoves(pkm, version, evos, generation, types: MoveSourceType.Reminder).Contains(move);
}
private static bool GetCanKnowMove(PKM pkm, int move, int generation, EvoCriteria[][] evos, GameVersion version = GameVersion.Any)
private static bool GetCanKnowMove(PKM pkm, int move, int generation, EvolutionHistory evos, GameVersion version = GameVersion.Any)
{
if (pkm.Species == (int)Smeargle)
return Legal.IsValidSketch(move, generation);

View File

@ -55,8 +55,8 @@ private static CheckMoveResult[] GetArray()
private static readonly ValidEncounterMoves NONE = new();
public ValidEncounterMoves EncounterMoves { get; internal set; } = NONE;
public EvoCriteria[][] EvoChainsAllGens => _evochains ??= EvolutionChain.GetEvolutionChainsAllGens(pkm, EncounterMatch);
private EvoCriteria[][]? _evochains;
public EvolutionHistory EvoChainsAllGens => _evochains ??= EvolutionChain.GetEvolutionChainsAllGens(pkm, EncounterMatch);
private EvolutionHistory? _evochains;
/// <summary><see cref="RNG"/> related information that generated the <see cref="PKM.PID"/>/<see cref="PKM.IVs"/> value(s).</summary>
public PIDIV PIDIV

View File

@ -130,7 +130,7 @@ public static bool IsValidSketch(int move, int generation)
return false;
if (generation is 8) // can't Sketch unusable moves in BDSP, no Sketch in PLA
{
if (SignatureSketch_BDSP.Contains(move) || DummiedMoves_BDSP.Contains(move))
if (DummiedMoves_BDSP.Contains(move))
return false;
if (move > MaxMoveID_8)
return false;

View File

@ -7,7 +7,7 @@ public static partial class Legal
internal const int MaxSpeciesID_8b = MaxSpeciesID_4; // Arceus-493
internal const int MaxMoveID_8b = MaxMoveID_8_R2;
internal const int MaxItemID_8b = 1822; // DS Sounds
internal const int MaxBallID_8b = (int)Ball.Beast;
internal const int MaxBallID_8b = (int)Ball.LAOrigin;
internal const int MaxGameID_8b = (int)GameVersion.SP;
internal const int MaxAbilityID_8b = MaxAbilityID_8_R2;
@ -119,14 +119,6 @@ public static partial class Legal
(int)Move.DracoMeteor,
};
/// <summary>
/// Unavailable Sketch Moves that are only learnable once certain species are distributed / made accessible.
/// </summary>
public static readonly HashSet<int> SignatureSketch_BDSP = new()
{
(int)Move.PsychoBoost, // Deoxys
};
/// <summary>
/// Moves that are kill
/// </summary>

View File

@ -58,8 +58,8 @@ private CheckResult VerifyAbility(LegalityAnalysis data)
if (abilities[0] == abilities[1] && num != 1)
{
// Check if any pre-evolution could have it flipped.
var evos = data.Info.EvoChainsAllGens[6];
var pt = GameData.GetPersonal(GameUtil.GetVersion(format));
var evos = data.Info.EvoChainsAllGens.Gen6;
var pt = GameData.GetPersonal(pkm.Context.GetSingleGameVersion());
if (!GetWasDual(evos, pt, pkm))
return INVALID;
}
@ -68,7 +68,8 @@ private CheckResult VerifyAbility(LegalityAnalysis data)
if (format >= 8) // Ability Patch
{
if (pkm.AbilityNumber == 4 && !pkm.LA)
var evos = data.Info.EvoChainsAllGens;
if (pkm.AbilityNumber == 4 && IsAccessibleAbilityPatch(pkm, evos))
{
if (CanAbilityPatch(format, abilities, pkm.Species))
return GetValid(LAbilityPatchUsed);
@ -163,7 +164,7 @@ private CheckResult VerifyFixedAbility(LegalityAnalysis data, IReadOnlyList<int>
var enc = data.Info.EncounterMatch;
if (enc.Generation >= 6)
{
if (IsAbilityCapsuleModified(pkm, abilities, encounterAbility))
if (IsAbilityCapsuleModified(pkm, abilities, encounterAbility, data.Info.EvoChainsAllGens))
return GetValid(LAbilityCapsuleUsed);
if (pkm.AbilityNumber != 1 << encounterAbility.GetSingleValue())
return INVALID;
@ -202,7 +203,7 @@ private CheckResult VerifyFixedAbility(LegalityAnalysis data, IReadOnlyList<int>
if (state == AbilityState.CanMismatch || encounterAbility == 0)
return CheckMatch(pkm, abilities, enc.Generation, AbilityState.MustMatch, enc);
if (IsAbilityCapsuleModified(pkm, abilities, encounterAbility))
if (IsAbilityCapsuleModified(pkm, abilities, encounterAbility, data.Info.EvoChainsAllGens))
return GetValid(LAbilityCapsuleUsed);
return INVALID;
@ -231,7 +232,7 @@ private AbilityState VerifyAbilityPreCapsule(LegalityAnalysis data, IReadOnlyLis
return AbilityState.MustMatch;
// If the species could not exist in Gen3, must match.
var g3 = info.EvoChainsAllGens[3];
var g3 = info.EvoChainsAllGens.Gen3;
if (g3.Length == 0)
return AbilityState.MustMatch;
@ -251,7 +252,7 @@ private AbilityState VerifyAbilityGen3Transfer(LegalityAnalysis data, IReadOnlyL
return AbilityState.MustMatch;
var chain = data.Info.EvoChainsAllGens;
bool evolved45 = chain[4].Length > 1 || (pkm.Format == 5 && chain[5].Length > 1);
bool evolved45 = chain.Gen4.Length > 1 || (pkm.Format == 5 && chain.Gen5.Length > 1);
if (evolved45)
{
if (pkm.Ability == pers.Ability1) // Could evolve in Gen4/5 and have a Gen3 only ability
@ -445,15 +446,27 @@ private CheckResult GetPIDAbilityMatch(PKM pkm, IReadOnlyList<int> abilities)
return VALID;
}
// Ability Capsule can change between 1/2
private static bool IsAbilityCapsuleModified(PKM pkm, IReadOnlyList<int> abilities, AbilityPermission encounterAbility)
private static bool IsAccessibleAbilityPatch(PKM pkm, EvolutionHistory evosAll)
{
return pkm.HasVisitedSWSH(evosAll.Gen8) || pkm.HasVisitedBDSP(evosAll.Gen8b);
}
private static bool IsAccessibleAbilityCapsule(PKM pkm, EvolutionHistory evosAll)
{
if (evosAll.Gen6.Length > 0 || evosAll.Gen7.Length > 0)
return true;
return pkm.HasVisitedSWSH(evosAll.Gen8) || pkm.HasVisitedBDSP(evosAll.Gen8b);
}
// Ability Capsule can change between 1/2
private static bool IsAbilityCapsuleModified(PKM pkm, IReadOnlyList<int> abilities, AbilityPermission encounterAbility, EvolutionHistory evos)
{
if (!IsAccessibleAbilityCapsule(pkm, evos))
return false; // Not available.
if (!CanAbilityCapsule(pkm.Format, abilities))
return false;
if (pkm.AbilityNumber == 4)
return false; // Cannot alter to hidden ability.
if (pkm.LA)
return false; // Not available.
if (encounterAbility == AbilityPermission.OnlyHidden)
return false; // Cannot alter from hidden ability.
return true;

View File

@ -984,60 +984,6 @@ internal static class BallBreedLegality
(int)Flabébé + (3 << 11), // Flabébé-Blue
};
/// <summary>
/// All egg species that can inherit a Safari Ball when bred in BD/SP.
/// </summary>
internal static readonly HashSet<int> InheritSafari_BDSP = new()
{
(int)Ekans,
(int)Azurill,
(int)Barboach,
(int)Bidoof,
(int)Budew,
(int)Carnivine,
(int)Carvanha,
(int)Croagunk,
(int)Exeggcute,
(int)Gulpin,
(int)Hoothoot,
(int)Kangaskhan,
(int)Magikarp,
(int)Marill,
(int)Paras,
(int)Psyduck,
(int)Roselia,
(int)Shroomish,
(int)Skorupi,
(int)Starly,
(int)Wooper,
(int)Yanma,
};
internal static readonly HashSet<int> BanInheritedExceptSafari_BDSP = new()
{
(int)Exeggcute,
(int)Kangaskhan,
(int)Yanma,
(int)Shroomish,
(int)Gulpin,
(int)Carnivine,
};
internal static readonly HashSet<int> BanInheritedBall_BDSP = new()
{
// Gen1 Fossils
(int)Aerodactyl, (int)Omanyte, (int)Kabuto,
// Gen3 Fossils
(int)Lileep, (int)Anorith,
// Gen4 Fossils
(int)Cranidos, (int)Shieldon,
// Riolu Egg from Riley
(int)Riolu,
(int)Phione,
};
/// <summary>
/// Gets a legal <see cref="Ball"/> value for a bred egg encounter.
/// </summary>
@ -1045,14 +991,9 @@ internal static class BallBreedLegality
/// <param name="species">Species the egg contained.</param>
/// <returns>Valid ball to hatch with.</returns>
/// <remarks>Not all things can hatch with a Poké Ball!</remarks>
#pragma warning disable RCS1163, IDE0060 // Unused parameter.
public static Ball GetDefaultBall(GameVersion version, int species)
{
if (version is GameVersion.BD or GameVersion.SP)
{
if (BanInheritedExceptSafari_BDSP.Contains(species))
return Ball.Safari;
}
return Ball.None;
}
}

View File

@ -20,11 +20,21 @@ public override void Verify(LegalityAnalysis data)
data.AddLine(result);
}
private static int IsReplacedBall(IVersion enc, PKM pk) => pk switch
{
PK8 when enc.Version == GameVersion.PLA => (int)Poke,
_ => (int)None,
};
private CheckResult VerifyBall(LegalityAnalysis data)
{
var Info = data.Info;
var enc = Info.EncounterMatch;
var ball = IsReplacedBall(enc, data.pkm);
if (ball != 0)
return VerifyBallEquals(data, ball);
// Fixed ball cases -- can be only one ball ever
switch (enc)
{
@ -49,7 +59,7 @@ private CheckResult VerifyBall(LegalityAnalysis data)
// The special move verifier has a similar check!
if (pkm.HGSS && pkm.Ball == (int)Sport) // Can evolve in DP to retain the HG/SS ball -- not able to be captured in any other ball
return VerifyBallEquals(data, (int)Sport);
if (Info.Generation != 3 || Info.EvoChainsAllGens[3].Length != 2)
if (Info.Generation != 3 || Info.EvoChainsAllGens.Gen3.Length != 2)
return VerifyBallEquals(data, (int)Poke); // Pokeball Only
}
@ -264,20 +274,81 @@ private CheckResult VerifyBallEggGen7(LegalityAnalysis data)
private CheckResult VerifyBallEggGen8BDSP(LegalityAnalysis data)
{
int species = data.EncounterMatch.Species;
if (BallBreedLegality.BanInheritedBall_BDSP.Contains(species))
if (species == (int)Species.Phione)
return VerifyBallEquals(data, (int)Poke);
if (BallBreedLegality.BanInheritedExceptSafari_BDSP.Contains(species))
return VerifyBallEquals(data, (int)Safari);
if (data.pkm.Ball == (int)Safari)
if (species is (int)Species.Cranidos or (int)Species.Shieldon)
return VerifyBallEquals(data, BallUseLegality.DreamWorldBalls);
var pkm = data.pkm;
Ball ball = (Ball)pkm.Ball;
var balls = BallUseLegality.GetWildBalls(8, GameVersion.BDSP);
if (balls.Contains((int)ball))
return GetValid(LBallSpeciesPass);
if (species is (int)Species.Spinda)
return GetInvalid(LBallSpecies); // Can't enter or exit, needs to adhere to wild balls.
// Cross-game inheritance
if (IsGalarCatchAndBreed(species))
{
if (BallBreedLegality.InheritSafari_BDSP.Contains(species))
if (BallUseLegality.WildPokeballs8.Contains(pkm.Ball))
return GetValid(LBallSpeciesPass);
}
if (ball == Safari)
{
if (!(BallBreedLegality.Inherit_Safari.Contains(species) || BallBreedLegality.Inherit_SafariMale.Contains(species)))
return GetInvalid(LBallSpecies);
if (BallBreedLegality.Ban_SafariBallHidden_7.Contains(species) && IsHiddenAndNotPossible(pkm))
return GetInvalid(LBallAbility);
return GetValid(LBallSpeciesPass);
}
if (ball.IsApricornBall()) // Apricorn Ball
{
if (!BallBreedLegality.Inherit_Apricorn7.Contains(species))
return GetInvalid(LBallSpecies);
if (BallBreedLegality.Ban_NoHidden8Apricorn.Contains(species | pkm.Form << 11) && IsHiddenAndNotPossible(pkm)) // lineage is 3->2->origin
return GetInvalid(LBallAbility);
return GetValid(LBallSpeciesPass);
}
if (ball == Sport) // Sport Ball
{
if (!BallBreedLegality.Inherit_Sport.Contains(species))
return GetInvalid(LBallSpecies);
if ((species is (int)Species.Volbeat or (int)Species.Illumise) && IsHiddenAndNotPossible(pkm)) // Volbeat/Illumise
return GetInvalid(LBallAbility);
return GetValid(LBallSpeciesPass);
}
if (ball == Dream) // Dream Ball
{
if (BallBreedLegality.Inherit_Dream.Contains(species) || BallBreedLegality.Inherit_DreamMale.Contains(species))
return GetValid(LBallSpeciesPass);
return GetInvalid(LBallSpecies);
}
if (ball is >= Dusk and <= Quick) // Dusk Heal Quick
{
if (!BallBreedLegality.Ban_Gen4Ball_7.Contains(species))
return GetValid(LBallSpeciesPass);
return GetInvalid(LBallSpecies);
}
if (ball is >= Ultra and <= Premier) // Don't worry, Safari was already checked.
{
if (!BallBreedLegality.Ban_Gen3Ball_7.Contains(species))
return GetValid(LBallSpeciesPass);
return GetInvalid(LBallSpecies);
}
var balls = BallUseLegality.GetWildBalls(8, GameVersion.BDSP);
return VerifyBallEquals(data, balls);
if (ball == Beast)
{
if (BallBreedLegality.PastGenAlolanScans.Contains(species))
return GetValid(LBallSpeciesPass);
}
if (ball > Beast)
return GetInvalid(LBallUnavailable);
return GetInvalid(LBallEncMismatch);
}
private CheckResult VerifyBallEggGen8(LegalityAnalysis data)

View File

@ -14,7 +14,7 @@ public override void Verify(LegalityAnalysis data)
var pkm = data.pkm;
if (data.EncounterMatch is EncounterStatic3 s3)
VerifyCXDStarterCorrelation(data, s3);
else if (pkm.Egg_Location != 0) // can't obtain eggs in CXD
else if (pkm.Egg_Location != 0 && pkm is not PB8 {Egg_Location: Locations.Default8bNone}) // can't obtain eggs in CXD
data.AddLine(GetInvalid(LEncInvalid, CheckIdentifier.Encounter)); // invalid encounter
if (pkm.OT_Gender == 1)

View File

@ -25,7 +25,7 @@ public override void Verify(LegalityAnalysis data)
// In generations 3,4 and BDSP, blocks/poffins have a feel(sheen) equal to sheen=sum(stats)/5, with +/- 10% for a favored stat.
// In generation 6 (ORAS), they don't award any sheen, so any value is legal.
var correlation = GetContestStatRestriction(pkm, data.Info.Generation);
var correlation = GetContestStatRestriction(pkm, data.Info.Generation, data.Info.EvoChainsAllGens);
if (correlation == None)
{
// We're only here because we have contest stat values. We aren't permitted to have any, so flag it.
@ -41,7 +41,7 @@ public override void Verify(LegalityAnalysis data)
else if (correlation == CorrelateSheen)
{
bool gen3 = data.Info.Generation == 3;
bool bdsp = pkm.HasVisitedBDSP(data.Info.EncounterOriginal.Species);
bool bdsp = pkm.HasVisitedBDSP(data.Info.EvoChainsAllGens.Gen8b);
var method = gen3 ? ContestStatGrantingSheen.Gen3 :
bdsp ? ContestStatGrantingSheen.Gen8b : ContestStatGrantingSheen.Gen4;

View File

@ -66,7 +66,7 @@ private CheckResult VerifyForm(LegalityAnalysis data)
break;
case Unown when Info.Generation == 2 && form >= 26:
return GetInvalid(string.Format(LFormInvalidRange, "Z", form == 26 ? "!" : "?"));
case Dialga or Palkia or Giratina or Arceus when form > 0 && pkm.LA: // can change forms with key items
case Dialga or Palkia or Giratina or Arceus when form > 0 && pkm is PA8: // can change forms with key items
break;
case Giratina when form == 1 ^ pkm.HeldItem == 112: // Giratina, Origin form only with Griseous Orb
return GetInvalid(LFormItemInvalid);
@ -308,12 +308,14 @@ private CheckResult VerifyFormArgument(LegalityAnalysis data, IFormArgument f)
Qwilfish when pkm.Form is 1 => arg switch
{
not 0 when pkm.IsEgg => GetInvalid(LFormArgumentNotAllowed),
not 0 when pkm.CurrentLevel < 25 => GetInvalid(LFormArgumentHigh),
> 9_999 => GetInvalid(LFormArgumentHigh),
_ => GetValid(LFormArgumentValid),
},
Stantler when pkm is PA8 => arg switch
Stantler when pkm is PA8 || pkm.HasVisitedLA(data.Info.EvoChainsAllGens.Gen8a) => arg switch
{
not 0 when pkm.IsEgg => GetInvalid(LFormArgumentNotAllowed),
not 0 when pkm.CurrentLevel < 31 => GetInvalid(LFormArgumentHigh),
> 9_999 => GetInvalid(LFormArgumentHigh),
_ => GetValid(LFormArgumentValid),
},

View File

@ -1,5 +1,4 @@
using System;
using System.Linq;
namespace PKHeX.Core
{
@ -76,18 +75,14 @@ private void VerifyOTFriendship(LegalityAnalysis data, bool neverOT, int origin,
{
if (origin < 0)
return;
if (origin <= 2)
{
// Verify the original friendship value since it cannot change from the value it was assigned in the original generation.
// Since some evolutions have different base friendship values, check all possible evolutions for a match.
// If none match, then it is not a valid OT friendship.
const int vc = 7; // VC transfers use SM personal info
var evos = data.Info.EvoChainsAllGens[vc];
var fs = pkm.OT_Friendship;
if (evos.All(z => GetBaseFriendship(vc, z.Species, z.Form) != fs))
data.AddLine(GetInvalid(LegalityCheckStrings.LMemoryStatFriendshipOTBaseEvent));
VerifyOTFriendshipVC12(data, pkm);
return;
}
else if (neverOT)
if (neverOT)
{
// Verify the original friendship value since it cannot change from the value it was assigned in the original generation.
// If none match, then it is not a valid OT friendship.
@ -98,6 +93,30 @@ private void VerifyOTFriendship(LegalityAnalysis data, bool neverOT, int origin,
}
}
private void VerifyOTFriendshipVC12(LegalityAnalysis data, PKM pkm)
{
// Verify the original friendship value since it cannot change from the value it was assigned in the original generation.
// Since some evolutions have different base friendship values, check all possible evolutions for a match.
// If none match, then it is not a valid OT friendship.
// VC transfers use SM personal info
var any = IsMatchFriendship(data.Info.EvoChainsAllGens.Gen7, PersonalTable.USUM, pkm.OT_Friendship);
if (!any)
data.AddLine(GetInvalid(LegalityCheckStrings.LMemoryStatFriendshipOTBaseEvent));
}
private static bool IsMatchFriendship(EvoCriteria[] evos, PersonalTable pt, int fs)
{
foreach (var z in evos)
{
if (!pt.IsPresentInGame(z.Species, z.Form))
continue;
var entry = pt.GetFormEntry(z.Species, z.Form);
if (entry.BaseFriendship == fs)
return true;
}
return false;
}
private void VerifyOTAffection(LegalityAnalysis data, bool neverOT, int origin, PKM pkm)
{
if (pkm is not IAffection a)
@ -209,13 +228,10 @@ public static bool GetCanOTHandle(IEncounterTemplate enc, PKM pkm, int generatio
private static int GetBaseFriendship(int generation, int species, int form) => generation switch
{
1 => PersonalTable.USUM[species].BaseFriendship,
2 => PersonalTable.USUM[species].BaseFriendship,
6 => PersonalTable.AO[species].BaseFriendship,
7 => PersonalTable.USUM[species].BaseFriendship,
8 => PersonalTable.SWSH.GetFormEntry(species, form).BaseFriendship,
_ => throw new IndexOutOfRangeException(),
_ => throw new ArgumentOutOfRangeException(nameof(generation)),
};
}
}

View File

@ -14,12 +14,9 @@ public sealed class LegendsArceusVerifier : Verifier
public override void Verify(LegalityAnalysis data)
{
var pk = data.pkm;
if (!pk.LA || pk is not PA8 pa)
if (pk is not PA8 pa)
return;
CheckLearnset(data, pa);
CheckMastery(data, pa);
if (pa.IsNoble)
data.AddLine(GetInvalid(LStatNobleInvalid));
if (pa.IsAlpha != data.EncounterMatch is IAlpha { IsAlpha: true })
@ -27,6 +24,9 @@ public override void Verify(LegalityAnalysis data)
CheckScalars(data, pa);
CheckGanbaru(data, pa);
CheckLearnset(data, pa);
CheckMastery(data, pa);
}
private static void CheckGanbaru(LegalityAnalysis data, PA8 pa)
@ -67,7 +67,7 @@ private static void CheckLearnset(LegalityAnalysis data, PA8 pa)
// Get the bare minimum moveset.
Span<int> expect = stackalloc int[4];
var minMoveCount = LoadBareMinimumMoveset(data.EncounterMatch, data.Info.EvoChainsAllGens[8], pa, expect);
var minMoveCount = LoadBareMinimumMoveset(data.EncounterMatch, data.Info.EvoChainsAllGens, pa, expect);
// Flag move slots that are empty.
for (int i = moveCount; i < minMoveCount; i++)
@ -81,7 +81,7 @@ private static void CheckLearnset(LegalityAnalysis data, PA8 pa)
/// <summary>
/// Gets the expected minimum count of moves, and modifies the input <see cref="moves"/> with the bare minimum move IDs.
/// </summary>
private static int LoadBareMinimumMoveset(ISpeciesForm enc, EvoCriteria[] evos, PA8 pa, Span<int> moves)
private static int LoadBareMinimumMoveset(ISpeciesForm enc, EvolutionHistory h, PA8 pa, Span<int> moves)
{
// Get any encounter moves
var pt = PersonalTable.LA;
@ -100,6 +100,10 @@ private static int LoadBareMinimumMoveset(ISpeciesForm enc, EvoCriteria[] evos,
Span<int> purchased = stackalloc int[purchasedCount];
LoadPurchasedMoves(pa, purchased);
// If it can be leveled up in other games, level it up in other games.
if (pa.HasVisitedSWSH(h.Gen8) || pa.HasVisitedBDSP(h.Gen8b))
return count;
// Level up to current level
var level = pa.CurrentLevel;
moveset.SetLevelUpMoves(pa.Met_Level, level, moves, purchased, count);
@ -108,6 +112,7 @@ private static int LoadBareMinimumMoveset(ISpeciesForm enc, EvoCriteria[] evos,
return 4;
// Evolve and try
var evos = h.Gen8a;
for (int i = 0; i < evos.Length - 1; i++)
{
var evo = evos[i];
@ -223,7 +228,7 @@ private static bool CanLearnMoveByLevelUp(LegalityAnalysis data, PA8 pa, int i,
// Changing forms do not have separate tutor permissions, so we don't need to bother with form changes.
// Level up movepools can grant moves for mastery at lower levels for earlier evolutions... find the minimum.
int level = 101;
foreach (var evo in data.Info.EvoChainsAllGens[8])
foreach (var evo in data.Info.EvoChainsAllGens.Gen8a)
{
var pt = PersonalTable.LA;
var index = pt.GetFormIndex(evo.Species, evo.Form);

View File

@ -44,14 +44,14 @@ private void VerifyMarksPresent(LegalityAnalysis data, IRibbonIndex m)
if (hasOne)
{
data.AddLine(GetInvalid(string.Format(LRibbonMarkingFInvalid_0, mark)));
data.AddLine(GetInvalid(string.Format(LRibbonMarkingFInvalid_0, GetRibbonNameSafe(mark))));
return;
}
bool result = IsMarkValid(mark, data.pkm, data.EncounterMatch);
if (!result)
{
data.AddLine(GetInvalid(string.Format(LRibbonMarkingFInvalid_0, mark)));
data.AddLine(GetInvalid(string.Format(LRibbonMarkingFInvalid_0, GetRibbonNameSafe(mark))));
return;
}
@ -59,6 +59,14 @@ private void VerifyMarksPresent(LegalityAnalysis data, IRibbonIndex m)
}
}
private static string GetRibbonNameSafe(RibbonIndex index)
{
if (index >= RibbonIndex.MAX_COUNT)
return index.ToString();
var expect = $"Ribbon{index}";
return RibbonStrings.GetName(expect);
}
public static bool IsMarkValid(RibbonIndex mark, PKM pk, IEncounterTemplate enc)
{
return IsMarkAllowedAny(enc) && IsMarkAllowedSpecific(mark, pk, enc);
@ -128,28 +136,31 @@ public static bool IsMarkAllowedFishing(IEncounterTemplate enc)
private void VerifyAffixedRibbonMark(LegalityAnalysis data, IRibbonIndex m)
{
if (m is not PK8 pk8)
if (m is not IRibbonSetAffixed a)
return;
var affix = pk8.AffixedRibbon;
var affix = a.AffixedRibbon;
if (affix == -1) // None
return;
if ((byte)affix > (int)RibbonIndex.MarkSlump)
if ((byte)affix > (int)RibbonIndex.MarkSlump) // SW/SH cannot affix anything higher.
{
data.AddLine(GetInvalid(string.Format(LRibbonMarkingAffixedF_0, affix)));
data.AddLine(GetInvalid(string.Format(LRibbonMarkingAffixedF_0, GetRibbonNameSafe((RibbonIndex)affix))));
return;
}
if (pk8.Species == (int)Species.Shedinja && data.EncounterOriginal.Species is not (int)Species.Shedinja)
if (m is not PKM pk)
return;
if (pk.Species == (int)Species.Shedinja && data.EncounterOriginal.Species is not (int)Species.Shedinja)
{
VerifyShedinjaAffixed(data, affix, pk8);
VerifyShedinjaAffixed(data, affix, pk, m);
return;
}
EnsureHasRibbon(data, pk8, affix);
EnsureHasRibbon(data, m, affix);
}
private void VerifyShedinjaAffixed(LegalityAnalysis data, sbyte affix, PK8 pk8)
private void VerifyShedinjaAffixed(LegalityAnalysis data, sbyte affix, PKM pk, IRibbonIndex r)
{
// Does not copy ribbons or marks, but retains the Affixed Ribbon value.
// Try re-verifying to see if it could have had the Ribbon/Mark.
@ -157,48 +168,47 @@ private void VerifyShedinjaAffixed(LegalityAnalysis data, sbyte affix, PK8 pk8)
var enc = data.EncounterOriginal;
if ((byte) affix >= (int) RibbonIndex.MarkLunchtime)
{
if (!IsMarkValid((RibbonIndex)affix, pk8, enc))
data.AddLine(GetInvalid(string.Format(LRibbonMarkingAffixedF_0, (RibbonIndex) affix)));
if (!IsMarkValid((RibbonIndex)affix, pk, enc))
data.AddLine(GetInvalid(string.Format(LRibbonMarkingAffixedF_0, GetRibbonNameSafe((RibbonIndex)affix))));
return;
}
if (enc.Generation <= 4 && (pk8.Ball != (int)Ball.Poke || IsMoveSetEvolvedShedinja(pk8)))
if (enc.Generation <= 4 && (pk.Ball != (int)Ball.Poke || IsMoveSetEvolvedShedinja(pk)))
{
// Evolved in a prior generation.
EnsureHasRibbon(data, pk8, affix);
EnsureHasRibbon(data, r, affix);
return;
}
var clone = pk8.Clone();
var clone = pk.Clone();
clone.Species = (int) Species.Nincada;
((IRibbonIndex) clone).SetRibbon(affix);
var parse = RibbonVerifier.GetRibbonResults(clone, data.Info.EvoChainsAllGens, enc);
var expect = $"Ribbon{(RibbonIndex) affix}";
var name = RibbonStrings.GetName(expect);
var name = GetRibbonNameSafe((RibbonIndex)affix);
bool invalid = parse.FirstOrDefault(z => z.Name == name)?.Invalid == true;
var severity = invalid ? Severity.Invalid : Severity.Fishy;
data.AddLine(Get(string.Format(LRibbonMarkingAffixedF_0, affix), severity));
data.AddLine(Get(string.Format(LRibbonMarkingAffixedF_0, name), severity));
}
private static bool IsMoveSetEvolvedShedinja(PK8 pk8)
private static bool IsMoveSetEvolvedShedinja(PKM pk)
{
// Check for gen3/4 exclusive moves that are Ninjask glitch only.
if (pk8.HasMove((int) Move.Screech))
if (pk.HasMove((int) Move.Screech))
return true;
if (pk8.HasMove((int) Move.SwordsDance))
if (pk.HasMove((int) Move.SwordsDance))
return true;
if (pk8.HasMove((int) Move.Slash))
if (pk.HasMove((int) Move.Slash))
return true;
if (pk8.HasMove((int) Move.BatonPass))
if (pk.HasMove((int) Move.BatonPass))
return true;
return pk8.HasMove((int)Move.Agility) && !pk8.GetMoveRecordFlag(12); // TR12 (Agility)
return pk.HasMove((int)Move.Agility) && pk is PK8 pk8 && !pk8.GetMoveRecordFlag(12); // TR12 (Agility)
}
private void EnsureHasRibbon(LegalityAnalysis data, IRibbonIndex pk8, sbyte affix)
private void EnsureHasRibbon(LegalityAnalysis data, IRibbonIndex m, sbyte affix)
{
var hasRibbon = pk8.GetRibbonIndex((RibbonIndex) affix);
var hasRibbon = m.GetRibbonIndex((RibbonIndex) affix);
if (!hasRibbon)
data.AddLine(GetInvalid(string.Format(LRibbonMarkingAffixedF_0, (RibbonIndex) affix)));
data.AddLine(GetInvalid(string.Format(LRibbonMarkingAffixedF_0, GetRibbonNameSafe((RibbonIndex) affix))));
}
}
}

View File

@ -15,7 +15,7 @@ public sealed class MemoryVerifier : Verifier
public override void Verify(LegalityAnalysis data)
{
var pkm = data.pkm;
if (pkm.BDSP || pkm.LA)
if (ShouldHaveNoMemory(data, pkm))
{
VerifyOTMemoryIs(data, 0, 0, 0, 0);
VerifyHTMemoryNone(data, (ITrainerMemories)pkm);
@ -25,6 +25,13 @@ public override void Verify(LegalityAnalysis data)
VerifyHTMemory(data);
}
private static bool ShouldHaveNoMemory(LegalityAnalysis data, PKM pkm)
{
if (pkm.BDSP || pkm.LA)
return !pkm.HasVisitedSWSH(data.Info.EvoChainsAllGens.Gen8);
return false;
}
private CheckResult VerifyCommonMemory(PKM pkm, int handler, int gen, LegalInfo info, MemoryContext context)
{
var memory = MemoryVariableSet.Read((ITrainerMemories)pkm, handler);
@ -56,7 +63,7 @@ private CheckResult VerifyCommonMemory(PKM pkm, int handler, int gen, LegalInfo
return GetInvalid(string.Format(LMemoryArgBadLocation, memory.Handler));
// {0} saw {2} carrying {1} on its back. {4} that {3}.
case 21 when gen != 6 || !GetCanLearnMachineMove(new PK6 {Species = memory.Variable, EXP = Experience.GetEXP(100, PersonalTable.XY.GetFormIndex(memory.Variable, 0))}, (int)Move.Fly, 6):
case 21 when gen != 6 || !PersonalTable.AO.GetFormEntry(memory.Variable, 0).TMHM[101]: // Fly
return GetInvalid(string.Format(LMemoryArgBadMove, memory.Handler));
// {0} used {2} at {1}s instruction, but it had no effect. {4} that {3}.
@ -71,7 +78,7 @@ private CheckResult VerifyCommonMemory(PKM pkm, int handler, int gen, LegalInfo
// {0} battled at {1}s side against {2} that Dynamaxed. {4} that {3}.
case 71 when !GetCanDynamaxTrainer(memory.Variable, 8, handler == 0 ? (GameVersion)pkm.Version : GameVersion.Any):
// {0} battled {2} and Dynamaxed upon {1}s instruction. {4} that {3}.
case 72 when !((PersonalInfoSWSH)PersonalTable.SWSH[memory.Variable]).IsPresentInGame:
case 72 when !PersonalTable.SWSH.IsSpeciesInGame(memory.Variable):
return GetInvalid(string.Format(LMemoryArgBadSpecies, memory.Handler));
// Move
@ -88,13 +95,13 @@ private CheckResult VerifyCommonMemory(PKM pkm, int handler, int gen, LegalInfo
// {0} saw {1} paying attention to {2}. {4} that {3}.
// {0} fought hard until it had to use Struggle when it battled at {1}s side against {2}. {4} that {3}.
// {0} was taken to a Pokémon Nursery by {1} and left with {2}. {4} that {3}.
case 9 or 60 or 75 when gen == 8 && !((PersonalInfoSWSH)PersonalTable.SWSH[memory.Variable]).IsPresentInGame:
case 9 or 60 or 75 when gen == 8 && !PersonalTable.SWSH.IsSpeciesInGame(memory.Variable):
return GetInvalid(string.Format(LMemoryArgBadSpecies, memory.Handler));
// {0} had a great chat about {1} with the {2} that it was in a Box with. {4} that {3}.
// {0} became good friends with the {2} in a Box, practiced moves with it, and talked about the day that {0} would be praised by {1}. {4} that {3}.
// {0} got in a fight with the {2} that it was in a Box with about {1}. {4} that {3}.
case 82 or 83 or 87 when !((PersonalInfoSWSH)PersonalTable.SWSH[memory.Variable]).IsPresentInGame:
case 82 or 83 or 87 when !PersonalTable.SWSH.IsSpeciesInGame(memory.Variable):
return GetInvalid(string.Format(LMemoryArgBadSpecies, memory.Handler));
// {0} had a very hard training session with {1}. {4} that {3}.
@ -201,7 +208,7 @@ private void VerifyOTMemory(LegalityAnalysis data)
return;
}
}
else if (!CanHaveMemoryForOT(pkm, memoryGen, memory))
else if (!CanHaveMemoryForOT(pkm, memoryGen, memory, Info.EvoChainsAllGens))
{
VerifyOTMemoryIs(data, 0, 0, 0, 0); // empty
return;
@ -248,7 +255,7 @@ private void VerifyOTMemory(LegalityAnalysis data)
data.AddLine(VerifyCommonMemory(pkm, 0, Info.Generation, Info, context));
}
private static bool CanHaveMemoryForOT(PKM pkm, int origin, int memory)
private static bool CanHaveMemoryForOT(PKM pkm, int origin, int memory, EvolutionHistory evos)
{
switch (origin)
{
@ -259,8 +266,8 @@ private static bool CanHaveMemoryForOT(PKM pkm, int origin, int memory)
case 7 when pkm.GG: // LGPE does not set memories.
case 8 when pkm.GO_HOME: // HOME does not set memories.
case 8 when pkm.Met_Location == Locations.HOME8: // HOME does not set memories.
case 8 when pkm.BDSP: // BDSP does not set memories.
case 8 when pkm.LA: // LA does not set memories.
case 8 when pkm.BDSP && !pkm.HasVisitedSWSH(evos.Gen8): // BDSP does not set memories.
case 8 when pkm.LA && !pkm.HasVisitedSWSH(evos.Gen8): // LA does not set memories.
return false;
// Eggs cannot have memories

View File

@ -29,12 +29,12 @@ public static class ContestStatInfo
/// </remarks>
private const int BestSheenStat8b = 120;
public static void SetSuggestedContestStats(this PKM pk, IEncounterTemplate enc)
public static void SetSuggestedContestStats(this PKM pk, IEncounterTemplate enc, EvolutionHistory h)
{
if (pk is not IContestStatsMutable s)
return;
var restrict = GetContestStatRestriction(pk, pk.Generation);
var restrict = GetContestStatRestriction(pk, pk.Generation, h);
var baseStat = GetReferenceTemplate(enc);
if (restrict == None || pk.Species is not (int)Species.Milotic)
baseStat.CopyContestStatsTo(s); // reset
@ -42,25 +42,25 @@ public static void SetSuggestedContestStats(this PKM pk, IEncounterTemplate enc)
s.SetAllContestStatsTo(MaxContestStat, restrict == NoSheen ? baseStat.CNT_Sheen : MaxContestStat);
}
public static void SetMaxContestStats(this PKM pk, IEncounterTemplate enc)
public static void SetMaxContestStats(this PKM pk, IEncounterTemplate enc, EvolutionHistory h)
{
if (pk is not IContestStatsMutable s)
return;
var restrict = GetContestStatRestriction(pk, enc.Generation);
var restrict = GetContestStatRestriction(pk, enc.Generation, h);
var baseStat = GetReferenceTemplate(enc);
if (restrict == None)
return;
s.SetAllContestStatsTo(MaxContestStat, restrict == NoSheen ? baseStat.CNT_Sheen : MaxContestStat);
}
public static ContestStatGranting GetContestStatRestriction(PKM pk, int origin) => origin switch
public static ContestStatGranting GetContestStatRestriction(PKM pk, int origin, EvolutionHistory h) => origin switch
{
3 => pk.Format < 6 ? CorrelateSheen : Mixed,
4 => pk.Format < 6 ? CorrelateSheen : Mixed,
5 => pk.Format >= 6 ? NoSheen : None, // ORAS Contests
6 => pk.AO || !pk.IsUntraded ? NoSheen : None,
8 => pk.BDSP ? CorrelateSheen : None, // BDSP Contests
8 => pk.HasVisitedBDSP(h.Gen8b) ? CorrelateSheen : None, // BDSP Contests
_ => None,
};

View File

@ -73,6 +73,14 @@ public override void Verify(LegalityAnalysis data)
if (enc is IEncounterServerDate { IsDateRestricted: true } serverGift)
{
var date = new DateTime(pkm.Met_Year + 2000, pkm.Met_Month, pkm.Met_Day);
// HOME Gifts for Sinnoh/Hisui starters were forced JPN until May 20, 2022 (UTC).
if (enc is WB8 { CardID: 9015 or 9016 or 9017 } or WA8 { CardID: 9018 or 9019 or 9020 })
{
if (date < new DateTime(2022, 5, 20) && pkm.Language != (int)LanguageID.Japanese)
data.AddLine(GetInvalid(LDateOutsideDistributionWindow));
}
var result = serverGift.IsValidDate(date);
if (result == EncounterServerDateCheck.Invalid)
data.AddLine(GetInvalid(LDateOutsideDistributionWindow));
@ -369,8 +377,8 @@ private static void VerifyReceivability(LegalityAnalysis data, MysteryGift g)
case WC6 wc6 when !wc6.CanBeReceivedByVersion(pkm.Version) && !pkm.WasTradedEgg:
case WC7 wc7 when !wc7.CanBeReceivedByVersion(pkm.Version) && !pkm.WasTradedEgg:
case WC8 wc8 when !wc8.CanBeReceivedByVersion(pkm.Version):
case WB8 wb8 when !wb8.CanBeReceivedByVersion(pkm.Version):
case WA8 wa8 when !wa8.CanBeReceivedByVersion(pkm.Version):
case WB8 wb8 when !wb8.CanBeReceivedByVersion(pkm.Version, pkm):
case WA8 wa8 when !wa8.CanBeReceivedByVersion(pkm.Version, pkm):
data.AddLine(GetInvalid(LEncGiftVersionNotDistributed, GameOrigin));
return;
case WC6 wc6 when wc6.RestrictLanguage != 0 && pkm.Language != wc6.RestrictLanguage:
@ -423,9 +431,9 @@ private static void VerifyFullness(LegalityAnalysis data, PKM pkm)
if (pkm.IsEgg)
{
if (pkm.Fullness != 0)
data.AddLine(GetInvalid(string.Format(LMemoryStatFullness, 0), Encounter));
data.AddLine(GetInvalid(string.Format(LMemoryStatFullness, "0"), Encounter));
if (pkm.Enjoyment != 0)
data.AddLine(GetInvalid(string.Format(LMemoryStatEnjoyment, 0), Encounter));
data.AddLine(GetInvalid(string.Format(LMemoryStatEnjoyment, "0"), Encounter));
return;
}
@ -433,8 +441,11 @@ private static void VerifyFullness(LegalityAnalysis data, PKM pkm)
{
if (pkm.Fullness > 245) // Exiting camp is -10
data.AddLine(GetInvalid(string.Format(LMemoryStatFullness, "<=245"), Encounter));
else if (pkm.Fullness is not 0 && pkm is not PK8)
data.AddLine(GetInvalid(string.Format(LMemoryStatFullness, "0"), Encounter));
if (pkm.Enjoyment != 0)
data.AddLine(GetInvalid(string.Format(LMemoryStatEnjoyment, 0), Encounter));
data.AddLine(GetInvalid(string.Format(LMemoryStatEnjoyment, "0"), Encounter));
return;
}
@ -448,7 +459,7 @@ private static void VerifyFullness(LegalityAnalysis data, PKM pkm)
return; // evolved
if (Unfeedable.Contains(pkm.Species))
data.AddLine(GetInvalid(string.Format(LMemoryStatFullness, 0), Encounter));
data.AddLine(GetInvalid(string.Format(LMemoryStatFullness, "0"), Encounter));
}
private static readonly HashSet<int> Unfeedable = new()
@ -488,7 +499,7 @@ private static void VerifyAbsoluteSizes(LegalityAnalysis data, IScaledSizeValue
private void VerifySWSHStats(LegalityAnalysis data, PK8 pk8)
{
if (pk8.Favorite)
if (pk8.Favorite && !pk8.GG)
data.AddLine(GetInvalid(LFavoriteMarkingUnavailable, Encounter));
var social = pk8.Sociability;
@ -504,12 +515,8 @@ private void VerifySWSHStats(LegalityAnalysis data, PK8 pk8)
VerifyStatNature(data, pk8);
var bv = pk8.BattleVersion;
if (bv != 0)
{
if ((bv != (int)GameVersion.SW && bv != (int)GameVersion.SH) || pk8.SWSH)
data.AddLine(GetInvalid(LStatBattleVersionInvalid));
}
if (!pk8.IsBattleVersionValid(data.Info.EvoChainsAllGens))
data.AddLine(GetInvalid(LStatBattleVersionInvalid));
var enc = data.EncounterMatch;
bool originGMax = enc is IGigantamax {CanGigantamax: true};
@ -556,12 +563,15 @@ private void VerifyPLAStats(LegalityAnalysis data, PA8 pa8)
{
VerifyAbsoluteSizes(data, pa8);
if (pa8.Favorite)
if (pa8.Favorite && !pa8.GG)
data.AddLine(GetInvalid(LFavoriteMarkingUnavailable, Encounter));
var affix = pa8.AffixedRibbon;
if (affix != -1) // None
data.AddLine(GetInvalid(string.Format(LRibbonMarkingAffixedF_0, affix)));
if (!pa8.HasVisitedSWSH(data.Info.EvoChainsAllGens.Gen8))
{
var affix = pa8.AffixedRibbon;
if (affix != -1) // None
data.AddLine(GetInvalid(string.Format(LRibbonMarkingAffixedF_0, affix)));
}
var social = pa8.Sociability;
if (social != 0)
@ -569,8 +579,7 @@ private void VerifyPLAStats(LegalityAnalysis data, PA8 pa8)
VerifyStatNature(data, pa8);
var bv = pa8.BattleVersion;
if (bv != 0)
if (!pa8.IsBattleVersionValid(data.Info.EvoChainsAllGens))
data.AddLine(GetInvalid(LStatBattleVersionInvalid));
if (pa8.CanGigantamax)
@ -588,12 +597,15 @@ private void VerifyPLAStats(LegalityAnalysis data, PA8 pa8)
private void VerifyBDSPStats(LegalityAnalysis data, PB8 pb8)
{
if (pb8.Favorite)
if (pb8.Favorite && !pb8.GG)
data.AddLine(GetInvalid(LFavoriteMarkingUnavailable, Encounter));
var affix = pb8.AffixedRibbon;
if (affix != -1) // None
data.AddLine(GetInvalid(string.Format(LRibbonMarkingAffixedF_0, affix)));
if (!pb8.HasVisitedSWSH(data.Info.EvoChainsAllGens.Gen8))
{
var affix = pb8.AffixedRibbon;
if (affix != -1) // None
data.AddLine(GetInvalid(string.Format(LRibbonMarkingAffixedF_0, affix)));
}
var social = pb8.Sociability;
if (social != 0)
@ -601,11 +613,14 @@ private void VerifyBDSPStats(LegalityAnalysis data, PB8 pb8)
if (pb8.IsDprIllegal)
data.AddLine(GetInvalid(LTransferFlagIllegal));
if (pb8.Species is (int)Species.Spinda or (int)Species.Nincada && !pb8.BDSP)
data.AddLine(GetInvalid(LTransferNotPossible));
if (pb8.Species is (int)Species.Spinda && pb8.Tracker != 0)
data.AddLine(GetInvalid(LTransferTrackerShouldBeZero));
VerifyStatNature(data, pb8);
var bv = pb8.BattleVersion;
if (bv != 0)
if (!pb8.IsBattleVersionValid(data.Info.EvoChainsAllGens))
data.AddLine(GetInvalid(LStatBattleVersionInvalid));
if (pb8.CanGigantamax)

View File

@ -91,7 +91,7 @@ private void VerifyFixedNicknameEncounter(LegalityAnalysis data, ILangNicknamedT
if (n is WC3 && pkm.Format != 3)
{
// Gen3 gifts transferred to Generation 4 from another language can set the nickname flag.
var evos = data.Info.EvoChainsAllGens[3];
var evos = data.Info.EvoChainsAllGens.Gen3;
bool matchAny = evos.Any(evo => !SpeciesName.IsNicknamedAnyLanguage(evo.Species, nickname, 3));
if (matchAny)
return;

View File

@ -133,6 +133,13 @@ private static void VerifyEC(LegalityAnalysis data)
// Gen1-2, Gen6+ should have PID != EC
if (pkm.PID == pkm.EncryptionConstant)
{
// Check for edge cases
var enc = Info.EncounterMatch;
if (enc is WA8 {IsEquivalentFixedECPID: true})
return;
if (enc is WB8 {IsEquivalentFixedECPID: true})
return;
data.AddLine(GetInvalid(LPIDEqualsEC, CheckIdentifier.EC)); // better to flag than 1:2^32 odds since RNG is not feasible to yield match
return;
}

View File

@ -38,7 +38,7 @@ public override void Verify(LegalityAnalysis data)
}
}
private static List<string> GetIncorrectRibbons(PKM pkm, EvoCriteria[][] evos, IEncounterTemplate enc)
private static List<string> GetIncorrectRibbons(PKM pkm, EvolutionHistory evos, IEncounterTemplate enc)
{
List<string> missingRibbons = new();
List<string> invalidRibbons = new();
@ -75,14 +75,14 @@ private static bool GetIncorrectRibbonsEgg(PKM pkm, IEncounterTemplate enc)
return false;
}
internal static IEnumerable<RibbonResult> GetRibbonResults(PKM pkm, EvoCriteria[][] evos, IEncounterTemplate enc)
internal static IEnumerable<RibbonResult> GetRibbonResults(PKM pkm, EvolutionHistory evos, IEncounterTemplate enc)
{
return GetInvalidRibbons(pkm, evos, enc)
.Concat(GetInvalidRibbonsEvent1(pkm, enc))
.Concat(GetInvalidRibbonsEvent2(pkm, enc));
}
private static IEnumerable<RibbonResult> GetInvalidRibbons(PKM pkm, EvoCriteria[][] evos, IEncounterTemplate enc)
private static IEnumerable<RibbonResult> GetInvalidRibbons(PKM pkm, EvolutionHistory evos, IEncounterTemplate enc)
{
// is a part of Event4, but O3 doesn't have the others
if (pkm is IRibbonSetOnly3 {RibbonWorld: true})
@ -133,7 +133,7 @@ private static IEnumerable<RibbonResult> GetInvalidRibbons(PKM pkm, EvoCriteria[
var iterate = GetInvalidRibbons4Any(pkm, evos, s4, gen);
if (!inhabited4)
{
if (pkm.BDSP) // Allow Sinnoh Champion. ILCA reused the Gen4 ribbon for the remake.
if (pkm.HasVisitedBDSP(evos.Gen8b)) // Allow Sinnoh Champion. ILCA reused the Gen4 ribbon for the remake.
iterate = iterate.Concat(GetInvalidRibbonsNoneSkipIndex(s4.RibbonBitsOnly(), s4.RibbonNamesOnly(), 1));
else
iterate = iterate.Concat(GetInvalidRibbonsNone(s4.RibbonBitsOnly(), s4.RibbonNamesOnly()));
@ -148,7 +148,7 @@ private static IEnumerable<RibbonResult> GetInvalidRibbons(PKM pkm, EvoCriteria[
var iterate = inhabited6
? GetInvalidRibbons6Any(pkm, s6, gen, enc)
: pkm.Format >= 8
? GetInvalidRibbons6AnyG8(pkm, s6)
? GetInvalidRibbons6AnyG8(pkm, s6, evos)
: GetInvalidRibbonsNone(s6.RibbonBits(), s6.RibbonNamesBool());
foreach (var z in iterate)
yield return z;
@ -187,7 +187,7 @@ private static IEnumerable<RibbonResult> GetInvalidRibbons(PKM pkm, EvoCriteria[
if (pkm is IRibbonSetCommon8 s8)
{
bool inhabited8 = gen <= 8;
var iterate = inhabited8 ? GetInvalidRibbons8Any(pkm, s8, enc) : GetInvalidRibbonsNone(s8.RibbonBits(), s8.RibbonNames());
var iterate = inhabited8 ? GetInvalidRibbons8Any(pkm, s8, enc, evos) : GetInvalidRibbonsNone(s8.RibbonBits(), s8.RibbonNames());
foreach (var z in iterate)
yield return z;
}
@ -207,14 +207,14 @@ private static IEnumerable<RibbonResult> GetMissingContestRibbons(IReadOnlyList<
}
}
private static IEnumerable<RibbonResult> GetInvalidRibbons4Any(PKM pkm, EvoCriteria[][] evos, IRibbonSetCommon4 s4, int gen)
private static IEnumerable<RibbonResult> GetInvalidRibbons4Any(PKM pkm, EvolutionHistory evos, IRibbonSetCommon4 s4, int gen)
{
if (s4.RibbonRecord)
yield return new RibbonResult(nameof(s4.RibbonRecord)); // Unobtainable
if (s4.RibbonFootprint && !CanHaveFootprintRibbon(pkm, evos, gen))
yield return new RibbonResult(nameof(s4.RibbonFootprint));
bool visitBDSP = pkm.BDSP;
bool visitBDSP = pkm.HasVisitedBDSP(evos.Gen8b);
bool gen34 = gen is 3 or 4;
bool not6 = pkm.Format < 6 || gen is > 6 or < 3;
bool noDaily = !gen34 && not6 && !visitBDSP;
@ -287,9 +287,9 @@ private static IEnumerable<RibbonResult> GetInvalidRibbons6Any(PKM pkm, IRibbonS
yield return result;
}
private static IEnumerable<RibbonResult> GetInvalidRibbons6AnyG8(PKM pkm, IRibbonSetCommon6 s6)
private static IEnumerable<RibbonResult> GetInvalidRibbons6AnyG8(PKM pkm, IRibbonSetCommon6 s6, EvolutionHistory evos)
{
if (!pkm.BDSP)
if (!pkm.HasVisitedBDSP(evos.Gen8b))
{
var none = GetInvalidRibbonsNone(s6.RibbonBits(), s6.RibbonNamesBool());
foreach (var x in none)
@ -417,14 +417,21 @@ private static IEnumerable<RibbonResult> GetInvalidRibbons7Any(PKM pkm, IRibbonS
}
}
private static IEnumerable<RibbonResult> GetInvalidRibbons8Any(PKM pkm, IRibbonSetCommon8 s8, IEncounterTemplate enc)
private static IEnumerable<RibbonResult> GetInvalidRibbons8Any(PKM pkm, IRibbonSetCommon8 s8, IEncounterTemplate enc, EvolutionHistory evos)
{
if (!pkm.InhabitedGeneration(8) || !((PersonalInfoSWSH)PersonalTable.SWSH[pkm.Species]).IsPresentInGame || pkm.BDSP)
bool swsh = pkm.HasVisitedSWSH(evos.Gen8);
bool bdsp = pkm.HasVisitedBDSP(evos.Gen8b);
bool pla = pkm.HasVisitedLA(evos.Gen8a);
if (!swsh && !bdsp)
{
if (s8.RibbonTowerMaster)
yield return new RibbonResult(nameof(s8.RibbonTowerMaster));
}
if (!swsh)
{
if (s8.RibbonChampionGalar)
yield return new RibbonResult(nameof(s8.RibbonChampionGalar));
if (s8.RibbonTowerMaster && !(pkm.SWSH || pkm.BDSP) && pkm.IsUntraded)
yield return new RibbonResult(nameof(s8.RibbonTowerMaster));
if (s8.RibbonMasterRank)
yield return new RibbonResult(nameof(s8.RibbonMasterRank));
}
@ -440,15 +447,10 @@ private static IEnumerable<RibbonResult> GetInvalidRibbons8Any(PKM pkm, IRibbonS
// Legends cannot compete in Ranked, thus cannot reach Master Rank and obtain the ribbon.
// Past gen Pokemon can get the ribbon only if they've been reset.
if (s8.RibbonMasterRank && !CanParticipateInRankedSWSH(pkm, enc))
if (s8.RibbonMasterRank && !CanParticipateInRankedSWSH(pkm, enc, evos))
yield return new RibbonResult(nameof(s8.RibbonMasterRank));
if (s8.RibbonTowerMaster)
{
if (!(pkm.SWSH || pkm.BDSP) && pkm.IsUntraded)
yield return new RibbonResult(nameof(s8.RibbonTowerMaster));
}
else
if (!s8.RibbonTowerMaster)
{
// If the Tower Master ribbon is not present but a memory hint implies it should...
// This memory can also be applied in Gen6/7 via defeating the Chatelaines, where legends are disallowed.
@ -461,22 +463,26 @@ private static IEnumerable<RibbonResult> GetInvalidRibbons8Any(PKM pkm, IRibbonS
}
}
// can be expanded upon if SWSH gets updated with the new ribbon when HOME has BDSP support
if (s8.RibbonTwinklingStar && (pkm is IRibbonSetCommon6 {RibbonContestStar:false} || !pkm.BDSP))
if (s8.RibbonTwinklingStar && (!bdsp || pkm is IRibbonSetCommon6 {RibbonContestStar:false}))
{
yield return new RibbonResult(nameof(s8.RibbonTwinklingStar));
}
// received when capturing photos with Pokémon in the Photography Studio
if (s8.RibbonPioneer && !pkm.LA)
if (s8.RibbonPioneer && !pla)
{
yield return new RibbonResult(nameof(s8.RibbonPioneer));
}
}
private static bool CanParticipateInRankedSWSH(PKM pkm, IEncounterTemplate enc)
private static bool CanParticipateInRankedSWSH(PKM pkm, IEncounterTemplate enc, EvolutionHistory evos)
{
if (!pkm.SWSH && pkm is IBattleVersion {BattleVersion: 0})
bool exist = enc.Generation switch
{
< 8 => pkm is IBattleVersion { BattleVersion: (int)GameVersion.SW or (int)GameVersion.SH },
_ => pkm.HasVisitedSWSH(evos.Gen8),
};
if (!exist)
return false;
// Clamp to permitted species
@ -490,14 +496,14 @@ private static bool CanParticipateInRankedSWSH(PKM pkm, IEncounterTemplate enc)
if (Legal.Mythicals.Contains(species))
return false;
if (enc.Version == GameVersion.GO) // Capture date is global time, and not console changeable.
if (enc.Version == GameVersion.GO || enc is IEncounterServerDate { IsDateRestricted: true }) // Capture date is global time, and not console changeable.
{
if (pkm.MetDate > new DateTime(2022, 9, 1)) // Series 12 end date
return false;
}
}
var pi = (PersonalInfoSWSH)PersonalTable.SWSH[species];
return pi.IsPresentInGame;
return PersonalTable.SWSH.IsPresentInGame(species, pkm.Form);
}
private static IEnumerable<RibbonResult> GetInvalidRibbonsEvent1(PKM pkm, IEncounterTemplate enc)
@ -582,7 +588,7 @@ private static bool IsAllowedBattleFrontier(int species, int form, int gen)
return IsAllowedBattleFrontier(species);
}
private static bool CanHaveFootprintRibbon(PKM pkm, EvoCriteria[][] evos, int gen)
private static bool CanHaveFootprintRibbon(PKM pkm, EvolutionHistory evos, int gen)
{
if (gen <= 4) // Friendship Check unnecessary - can decrease after obtaining ribbon.
return true;
@ -595,15 +601,16 @@ private static bool CanHaveFootprintRibbon(PKM pkm, EvoCriteria[][] evos, int ge
return true;
// Gen8-BDSP: Variable by species Footprint
if (pkm.BDSP)
var bdspEvos = evos.Gen8b;
if (pkm.HasVisitedBDSP(bdspEvos))
{
if (evos[8].Any(z => z.Species <= Legal.MaxSpeciesID_4 && !HasFootprintBDSP[z.Species]))
if (bdspEvos.Any(z => PersonalTable.BDSP.IsPresentInGame(z.Species, z.Form) && !HasFootprintBDSP[z.Species]))
return true; // no footprint
if (pkm.CurrentLevel - pkm.Met_Level >= 30)
return true; // traveled well
}
// Gen8: Can't obtain
// Otherwise: Can't obtain
return false;
}

View File

@ -138,8 +138,7 @@ public void VerifyTransferLegalityG8(LegalityAnalysis data)
// PK8
int species = pkm.Species;
var pi = (PersonalInfoSWSH)PersonalTable.SWSH.GetFormEntry(species, pkm.Form);
if (!pi.IsPresentInGame || pkm.BDSP || pkm.LA) // Can't transfer
if (!PersonalTable.SWSH.IsPresentInGame(species, pkm.Form)) // Can't transfer
{
data.AddLine(GetInvalid(LTransferBad));
return;
@ -150,7 +149,7 @@ public void VerifyTransferLegalityG8(LegalityAnalysis data)
{
VerifyHOMETracker(data, pkm);
}
else if (enc.Generation < 8 && pkm.Format >= 8)
else if (pkm.Format >= 8 && !pkm.IsNative)
{
if (enc is EncounterStatic7 {IsTotem: true} s)
{
@ -160,19 +159,18 @@ public void VerifyTransferLegalityG8(LegalityAnalysis data)
data.AddLine(GetInvalid(LTransferBad));
}
VerifyHOMETransfer(data, pkm);
if (enc.Generation < 8)
VerifyHOMETransfer(data, pkm);
VerifyHOMETracker(data, pkm);
}
}
private void VerifyTransferLegalityG8a(LegalityAnalysis data, PA8 pk)
{
// Tracker value is set via Transfer across HOME.
// No HOME access yet.
if (pk is IHomeTrack { Tracker: not 0 })
data.AddLine(GetInvalid(LTransferTrackerShouldBeZero));
if (!pk.IsNative)
VerifyHOMETracker(data, pk);
var pi = (PersonalInfoLA)PersonalTable.LA.GetFormEntry(pk.Species, pk.Form);
if (!pi.IsPresentInGame || !pk.LA) // Can't transfer
if (!PersonalTable.LA.IsPresentInGame(pk.Species, pk.Form)) // Can't transfer
data.AddLine(GetInvalid(LTransferBad));
}
@ -180,12 +178,10 @@ private void VerifyTransferLegalityG8a(LegalityAnalysis data, PA8 pk)
private void VerifyTransferLegalityG8b(LegalityAnalysis data, PB8 pk)
{
// Tracker value is set via Transfer across HOME.
// No HOME access yet.
if (pk is IHomeTrack { Tracker: not 0 })
data.AddLine(GetInvalid(LTransferTrackerShouldBeZero));
if (!pk.IsNative)
VerifyHOMETracker(data, pk);
var pi = (PersonalInfoBDSP)PersonalTable.BDSP.GetFormEntry(pk.Species, pk.Form);
if (!pi.IsPresentInGame || !pk.BDSP) // Can't transfer
if (!PersonalTable.BDSP.IsPresentInGame(pk.Species, pk.Form)) // Can't transfer
data.AddLine(GetInvalid(LTransferBad));
}
@ -219,7 +215,9 @@ public IEnumerable<CheckResult> VerifyVCEncounter(PKM pkm, IEncounterTemplate en
{
if (pkm.Met_Location != transfer.Location)
yield return GetInvalid(LTransferMetLocation);
if (pkm.Egg_Location != transfer.EggLocation)
var expecteEgg = pkm is PB8 ? Locations.Default8bNone : transfer.EggLocation;
if (pkm.Egg_Location != expecteEgg)
yield return GetInvalid(LEggLocationNone);
// Flag Moves that cannot be transferred

View File

@ -161,6 +161,12 @@ public virtual Shiny Shiny
public virtual bool EggEncounter => IsEgg;
public abstract int EggLocation { get; set; }
protected virtual bool IsMatchEggLocation(PKM pk)
{
var expect = EggEncounter ? EggLocation : pk is PB8 ? Locations.Default8bNone : 0;
return pk.Egg_Location == expect;
}
public Ball FixedBall => (Ball)Ball;
public int TrainerID7 => (int)((uint)(TID | (SID << 16)) % 1000000);

View File

@ -141,6 +141,8 @@ public override bool IsMatchExact(PKM pkm, EvoCriteria evo)
{
// met location: deferred to general transfer check
if (wc.CurrentLevel > pkm.Met_Level) return false;
if (!IsMatchEggLocation(pkm))
return false;
}
else
{

View File

@ -372,7 +372,7 @@ public override bool IsMatchExact(PKM pkm, EvoCriteria evo)
if (OriginGame != 0 && OriginGame != pkm.Version) return false;
if (Language != 0 && Language != pkm.Language) return false;
if (EggLocation != pkm.Egg_Location) return false;
if (!IsMatchEggLocation(pkm)) return false;
if (MetLocation != pkm.Met_Location) return false;
}
else

View File

@ -26,8 +26,9 @@ public enum GiftType : byte
public WA8() : this(new byte[Size]) { }
public WA8(byte[] data) : base(data) { }
public bool CanBeReceivedByVersion(int v) => v is (int) GameVersion.PLA;
public bool CanBeReceivedByVersion(int v, PKM pk) => v is (int) GameVersion.PLA || (pk is PK8 && v is (int)GameVersion.SW);
public bool IsDateRestricted => true;
public bool IsEquivalentFixedECPID => EncryptionConstant != 0 && PIDType == ShinyType8.FixedValue && PID == EncryptionConstant;
// General Card Properties
public override int CardID
@ -403,7 +404,7 @@ public override PKM ConvertToPKM(ITrainerInfo sav, EncounterCriteria criteria)
Species = Species,
Form = Form,
CurrentLevel = currentLevel,
Ball = Ball != 0 ? Ball : 4, // Default is Pokeball
Ball = Ball != 0 ? Ball : (int)Core.Ball.LAPoke, // Default is Pokeball
Met_Level = metLevel,
HeldItem = HeldItem,
@ -441,15 +442,22 @@ public override PKM ConvertToPKM(ITrainerInfo sav, EncounterCriteria criteria)
EV_SPA = EV_SPA,
EV_SPD = EV_SPD,
CanGigantamax = CanGigantamax,
DynamaxLevel = DynamaxLevel,
GV_HP = GV_HP,
GV_ATK = GV_ATK,
GV_DEF = GV_DEF,
GV_SPE = GV_SPE,
GV_SPA = GV_SPA,
GV_SPD = GV_SPD,
//CanGigantamax = CanGigantamax,
//DynamaxLevel = DynamaxLevel,
Met_Location = MetLocation,
Egg_Location = EggLocation,
};
pk.SetMaximumPPCurrent();
if ((sav.Generation > Generation && OriginGame == 0) || !CanBeReceivedByVersion(pk.Version))
if ((sav.Generation > Generation && OriginGame == 0) || !CanBeReceivedByVersion(pk.Version, pk))
pk.Version = (int)GameVersion.PLA;
if (OTGender >= 2)
@ -578,7 +586,7 @@ private void SetIVs(PKM pk)
public override bool IsMatchExact(PKM pkm, EvoCriteria evo)
{
if (pkm.Egg_Location == 0) // Not Egg
if (!IsEgg)
{
if (OTGender < 2)
{
@ -623,8 +631,17 @@ public override bool IsMatchExact(PKM pkm, EvoCriteria evo)
else
{
if (!Shiny.IsValid(pkm)) return false;
if (EggLocation != pkm.Egg_Location) return false;
if (MetLocation != pkm.Met_Location) return false;
if (!IsMatchEggLocation(pkm)) return false;
if (pkm is PK8)
{
if (pkm.Met_Location != Locations.HOME_SWLA)
return false;
}
else
{
if (MetLocation != pkm.Met_Location)
return false;
}
}
if (MetLevel != 0 && MetLevel != pkm.Met_Level) return false;
@ -636,13 +653,15 @@ public override bool IsMatchExact(PKM pkm, EvoCriteria evo)
var expectedBall = (Ball == 0 ? poke : Ball);
if (expectedBall < poke) // Not even Cherish balls are safe! They get set to the proto-Poké ball.
expectedBall = poke;
if (pkm is PK8)
expectedBall = (int)Core.Ball.Poke; // Transferred to SWSH -> Regular Poké ball
if (expectedBall != pkm.Ball)
return false;
if (pkm is IGigantamax g && g.CanGigantamax != CanGigantamax && !g.CanToggleGigantamax(pkm.Species, pkm.Form, Species, Form))
if (pkm is IDynamaxLevel dl && dl.DynamaxLevel < DynamaxLevel)
return false;
if (pkm is not IDynamaxLevel dl || dl.DynamaxLevel < DynamaxLevel)
if (pkm is IGanbaru b && b.IsGanbaruValuesBelow(this))
return false;
// PID Types 0 and 1 do not use the fixed PID value.

View File

@ -531,7 +531,7 @@ public bool CanBeAnyLanguage()
public override bool IsMatchExact(PKM pkm, EvoCriteria evo)
{
if (pkm.Egg_Location == 0) // Not Egg
if (!IsEgg)
{
if (OTGender != 3)
{
@ -544,6 +544,7 @@ public override bool IsMatchExact(PKM pkm, EvoCriteria evo)
if (OriginGame != 0 && OriginGame != pkm.Version) return false;
if (EncryptionConstant != 0 && EncryptionConstant != pkm.EncryptionConstant) return false;
if (!IsMatchEggLocation(pkm)) return false;
if (!CanBeAnyLanguage() && !CanHaveLanguage(pkm.Language))
return false;
}
@ -569,7 +570,7 @@ public override bool IsMatchExact(PKM pkm, EvoCriteria evo)
else
{
if (!Shiny.IsValid(pkm)) return false;
if (EggLocation != pkm.Egg_Location) return false;
if (!IsMatchEggLocation(pkm)) return false;
if (MetLocation != pkm.Met_Location) return false;
}

View File

@ -8,7 +8,7 @@ namespace PKHeX.Core
/// <summary>
/// Generation 8b Mystery Gift Template File
/// </summary>
public sealed class WB8 : DataMysteryGift, ILangNick, INature, IRibbonIndex, IContestStats, ILangNicknamedTemplate,
public sealed class WB8 : DataMysteryGift, ILangNick, INature, IRibbonIndex, IContestStats, ILangNicknamedTemplate, IEncounterServerDate,
IRibbonSetEvent3, IRibbonSetEvent4, IRibbonSetCommon3, IRibbonSetCommon4, IRibbonSetCommon6, IRibbonSetCommon7, IRibbonSetCommon8, IRibbonSetMark8
{
public const int Size = 0x2DC;
@ -16,6 +16,9 @@ public sealed class WB8 : DataMysteryGift, ILangNick, INature, IRibbonIndex, ICo
public override int Generation => 8;
public bool IsDateRestricted => IsHOMEGift;
public bool IsEquivalentFixedECPID => EncryptionConstant != 0 && PIDType == ShinyType8.FixedValue && PID == EncryptionConstant;
public enum GiftType : byte
{
None = 0,
@ -32,7 +35,7 @@ public enum GiftType : byte
// TODO: public byte RestrictVersion?
public bool CanBeReceivedByVersion(int v) => v is (int) GameVersion.BD or (int) GameVersion.SP;
public bool CanBeReceivedByVersion(int v, PKM pk) => v is (int) GameVersion.BD or (int) GameVersion.SP || (pk is PK8 && Locations.IsValidMetBDSP(pk.Met_Location, pk.Version));
// General Card Properties
@ -182,7 +185,7 @@ public override int HeldItem
public int MetLevel { get => Data[CardStart + 0x291]; set => Data[CardStart + 0x291] = (byte)value; }
// Ribbons 0x24C-0x26C
// Ribbons 0x292-0x2B2
private const int RibbonBytesOffset = 0x292;
private const int RibbonBytesCount = 0x20;
private const int RibbonByteNone = 0xFF; // signed -1
@ -366,6 +369,8 @@ private static int GetOTOffset(int language)
return 0x150 + (index * 0x20);
}
public bool IsHOMEGift => CardID >= 9000;
public bool CanHandleOT(int language) => !GetHasOT(language);
public override GameVersion Version
@ -447,12 +452,12 @@ public override PKM ConvertToPKM(ITrainerInfo sav, EncounterCriteria criteria)
}
pk.SetMaximumPPCurrent();
if ((sav.Generation > Generation && OriginGame == 0) || !CanBeReceivedByVersion(pk.Version))
if ((sav.Generation > Generation && OriginGame == 0) || !CanBeReceivedByVersion(pk.Version, pk))
{
// give random valid game
var rnd = Util.Rand;
do { pk.Version = (int)GameVersion.BD + rnd.Next(2); }
while (!CanBeReceivedByVersion(pk.Version));
while (!CanBeReceivedByVersion(pk.Version, pk));
}
if (pk.TID == 0 && pk.SID == 0)
@ -581,7 +586,7 @@ private void SetIVs(PKM pk)
public override bool IsMatchExact(PKM pkm, EvoCriteria evo)
{
if ((short)pkm.Egg_Location == Locations.Default8bNone) // Not Egg
if (!IsEgg)
{
if (OTGender < 2)
{
@ -627,8 +632,17 @@ public override bool IsMatchExact(PKM pkm, EvoCriteria evo)
else
{
if (!Shiny.IsValid(pkm)) return false;
if (EggLocation != pkm.Egg_Location) return false;
if (MetLocation != pkm.Met_Location) return false;
if (!IsMatchEggLocation(pkm)) return false;
if (pkm is PK8)
{
if (!Locations.IsValidMetBDSP(pkm.Met_Location, pkm.Version))
return false;
}
else
{
if (MetLocation != pkm.Met_Location)
return false;
}
}
if (MetLevel != 0 && MetLevel != pkm.Met_Level) return false;
@ -646,6 +660,12 @@ public override bool IsMatchExact(PKM pkm, EvoCriteria evo)
return pkm.PID == GetPID(pkm, type);
}
protected override bool IsMatchEggLocation(PKM pk)
{
var expect = pk is PB8 ? Locations.Default8bNone : 0;
return pk.Egg_Location == expect;
}
protected override bool IsMatchDeferred(PKM pkm) => Species != pkm.Species;
protected override bool IsMatchPartial(PKM pkm) => false; // no version compatibility checks yet.

View File

@ -520,7 +520,7 @@ private void SetIVs(PKM pk)
public override bool IsMatchExact(PKM pkm, EvoCriteria evo)
{
if (pkm.Egg_Location == 0) // Not Egg
if (!IsEgg)
{
if (OTGender != 3)
{
@ -559,7 +559,7 @@ public override bool IsMatchExact(PKM pkm, EvoCriteria evo)
}
else
{
if (EggLocation != pkm.Egg_Location) return false;
if (!IsMatchEggLocation(pkm)) return false;
if (MetLocation != pkm.Met_Location) return false;
}

View File

@ -556,7 +556,7 @@ public bool IsAshGreninjaWC7(PKM pkm)
public override bool IsMatchExact(PKM pkm, EvoCriteria evo)
{
if (pkm.Egg_Location == 0) // Not Egg
if (!IsEgg)
{
if (OTGender != 3)
{
@ -591,7 +591,7 @@ public override bool IsMatchExact(PKM pkm, EvoCriteria evo)
else
{
if (!Shiny.IsValid(pkm)) return false;
if (EggLocation != pkm.Egg_Location) return false;
if (!IsMatchEggLocation(pkm)) return false;
if (MetLocation != pkm.Met_Location) return false;
}

View File

@ -581,7 +581,7 @@ private void SetIVs(PKM pk)
public override bool IsMatchExact(PKM pkm, EvoCriteria evo)
{
if (pkm.Egg_Location == 0) // Not Egg
if (!IsEgg)
{
if (OTGender < 2)
{
@ -646,7 +646,7 @@ public override bool IsMatchExact(PKM pkm, EvoCriteria evo)
else
{
if (!Shiny.IsValid(pkm)) return false;
if (EggLocation != pkm.Egg_Location) return false;
if (!IsMatchEggLocation(pkm)) return false;
if (MetLocation != pkm.Met_Location) return false;
}
@ -656,7 +656,7 @@ public override bool IsMatchExact(PKM pkm, EvoCriteria evo)
if (Nature != -1 && pkm.Nature != Nature) return false;
if (Gender != 3 && Gender != pkm.Gender) return false;
if (pkm is IGigantamax g && g.CanGigantamax != CanGigantamax && !g.CanToggleGigantamax(pkm.Species, pkm.Form, Species, Form))
if (pkm is PK8 and IGigantamax g && g.CanGigantamax != CanGigantamax && !g.CanToggleGigantamax(pkm.Species, pkm.Form, Species, Form))
return false;
if (pkm is not IDynamaxLevel dl || dl.DynamaxLevel < DynamaxLevel)

View File

@ -20,7 +20,7 @@ public sealed class BK4 : G4PKM
public override int SIZE_PARTY => PokeCrypto.SIZE_4STORED;
public override int SIZE_STORED => PokeCrypto.SIZE_4STORED;
public override int Format => 4;
public override EntityContext Context => EntityContext.Gen4;
public override PersonalInfo PersonalInfo => PersonalTable.HGSS[Species];
public override byte[] DecryptedBoxData => EncryptedBoxData;
@ -66,7 +66,7 @@ public override uint EXP
public override int OT_Friendship { get => Data[0x14]; set => Data[0x14] = (byte)value; }
public override int Ability { get => Data[0x15]; set => Data[0x15] = (byte)value; }
public override int MarkValue { get => Data[0x16]; protected set => Data[0x16] = (byte)value; }
public override int MarkValue { get => Data[0x16]; set => Data[0x16] = (byte)value; }
public override int Language { get => Data[0x17]; set => Data[0x17] = (byte)value; }
public override int EV_HP { get => Data[0x18]; set => Data[0x18] = (byte)value; }
public override int EV_ATK { get => Data[0x19]; set => Data[0x19] = (byte)value; }

View File

@ -22,7 +22,7 @@ public sealed class CK3 : G3PKM, IShadowPKM
public override int SIZE_PARTY => PokeCrypto.SIZE_3CSTORED;
public override int SIZE_STORED => PokeCrypto.SIZE_3CSTORED;
public override int Format => 3;
public override EntityContext Context => EntityContext.Gen3;
public override PersonalInfo PersonalInfo => PersonalTable.RS[Species];
public CK3(byte[] data) : base(data) { }
public CK3() : this(new byte[PokeCrypto.SIZE_3CSTORED]) { }
@ -173,7 +173,7 @@ public sealed class CK3 : G3PKM, IShadowPKM
public override bool AbilityBit { get => Data[0xCC] == 1; set => Data[0xCC] = value ? (byte)1 : (byte)0; }
public override bool Valid { get => Data[0xCD] == 0; set => Data[0xCD] = !value ? (byte)1 : (byte)0; }
public override int MarkValue { get => SwapBits(Data[0xCF], 1, 2); protected set => Data[0xCF] = (byte)SwapBits(value, 1, 2); }
public override int MarkValue { get => SwapBits(Data[0xCF], 1, 2); set => Data[0xCF] = (byte)SwapBits(value, 1, 2); }
public override int PKRS_Days { get => Math.Max((sbyte)Data[0xD0], (sbyte)0); set => Data[0xD0] = (byte)(value == 0 ? 0xFF : value & 0xF); }
public int PartySlot { get => Data[0xD7]; set => Data[0xD7] = (byte)value; } // or not; only really used while in party?

View File

@ -4,14 +4,15 @@
namespace PKHeX.Core
{
/// <summary> Generation 8 <see cref="PKM"/> format. </summary>
public abstract class G8PKM : PKM, ISanityChecksum,
public abstract class G8PKM : PKM, ISanityChecksum, IMoveReset,
IRibbonSetEvent3, IRibbonSetEvent4, IRibbonSetCommon3, IRibbonSetCommon4, IRibbonSetCommon6, IRibbonSetCommon7, IRibbonSetCommon8, IRibbonSetMark8, IRibbonSetAffixed, ITechRecord8, ISociability,
IContestStats, IContestStatsMutable, IHyperTrain, IScaledSize, IGigantamax, IFavorite, IDynamaxLevel, IRibbonIndex, IHandlerLanguage, IFormArgument, IHomeTrack, IBattleVersion, ITrainerMemories
{
public sealed override int Format => 8;
protected G8PKM() : base(PokeCrypto.SIZE_8PARTY) { }
protected G8PKM(byte[] data) : base(DecryptParty(data)) { }
public abstract void ResetMoves();
private static byte[] DecryptParty(byte[] data)
{
PokeCrypto.DecryptIfEncrypted8(ref data);
@ -124,7 +125,7 @@ public void FixRelearn()
public bool Favorite { get => (Data[0x16] & 8) != 0; set => Data[0x16] = (byte)((Data[0x16] & ~8) | ((value ? 1 : 0) << 3)); } // unused, was in LGPE but not in SWSH
public bool CanGigantamax { get => (Data[0x16] & 16) != 0; set => Data[0x16] = (byte)((Data[0x16] & ~16) | (value ? 16 : 0)); }
// 0x17 alignment unused
public override int MarkValue { get => ReadUInt16LittleEndian(Data.AsSpan(0x18)); protected set => WriteUInt16LittleEndian(Data.AsSpan(0x18), (ushort)value); }
public override int MarkValue { get => ReadUInt16LittleEndian(Data.AsSpan(0x18)); set => WriteUInt16LittleEndian(Data.AsSpan(0x18), (ushort)value); }
// 0x1A alignment unused
// 0x1B alignment unused
public override uint PID { get => ReadUInt32LittleEndian(Data.AsSpan(0x1C)); set => WriteUInt32LittleEndian(Data.AsSpan(0x1C), value); }
@ -411,6 +412,8 @@ public void SetPokeJobFlag(int index, bool value)
public bool GetPokeJobFlagAny() => Array.FindIndex(Data, 0xCE, 14, z => z != 0) >= 0;
public void ClearPokeJobFlags() => Data.AsSpan(0xCE, 14).Clear();
public override byte Fullness { get => Data[0xDC]; set => Data[0xDC] = value; }
public override byte Enjoyment { get => Data[0xDD]; set => Data[0xDD] = value; }
public override int Version { get => Data[0xDE]; set => Data[0xDE] = (byte)value; }
@ -478,6 +481,8 @@ public void SetMoveRecordFlag(int index, bool value)
public bool GetMoveRecordFlagAny() => Array.FindIndex(Data, 0x127, 14, z => z != 0) >= 0;
public void ClearMoveRecordFlags() => Data.AsSpan(0x127, 14).Clear();
// Why did you mis-align this field, GameFreak?
public ulong Tracker
{
@ -532,5 +537,227 @@ public int GetRibbonByte(int index)
index -= 64;
return 0x40 + (index >> 3);
}
protected T ConvertTo<T>() where T : G8PKM, new()
{
var pk = new T();
Data.AsSpan().CopyTo(pk.Data);
pk.Move1_PPUps = pk.Move2_PPUps = pk.Move3_PPUps = pk.Move4_PPUps = 0;
pk.RelearnMove1 = pk.RelearnMove2 = pk.RelearnMove3 = pk.RelearnMove4 = 0;
pk.ClearMoveRecordFlags();
pk.ClearPokeJobFlags();
pk.CanGigantamax = false;
pk.DynamaxLevel = 0;
pk.Sociability = 0;
pk.Fullness = 0;
pk.Data[0x52] = 0;
pk.ResetMoves();
pk.ResetPartyStats();
pk.RefreshChecksum();
return pk;
}
public virtual PA8 ConvertToPA8()
{
var pk = new PA8
{
EncryptionConstant = EncryptionConstant,
PID = PID,
Species = Species,
Form = Form,
FormArgument = FormArgument,
Gender = Gender,
Nature = Nature,
StatNature = StatNature,
TID = TID,
SID = SID,
EXP = EXP,
Ability = Ability,
AbilityNumber = AbilityNumber,
Language = Language,
Version = Version,
IV_HP = IV_HP,
IV_ATK = IV_ATK,
IV_DEF = IV_DEF,
IV_SPE = IV_SPE,
IV_SPA = IV_SPA,
IV_SPD = IV_SPD,
IsEgg = IsEgg,
EV_HP = EV_HP,
EV_ATK = EV_ATK,
EV_DEF = EV_DEF,
EV_SPE = EV_SPE,
EV_SPA = EV_SPA,
EV_SPD = EV_SPD,
OT_Gender = OT_Gender,
OT_Friendship = OT_Friendship,
OT_Intensity = OT_Intensity,
OT_Memory = OT_Memory,
OT_TextVar = OT_TextVar,
OT_Feeling = OT_Feeling,
Egg_Year = Egg_Year,
Egg_Month = Egg_Month,
Egg_Day = Egg_Day,
Met_Year = Met_Year,
Met_Month = Met_Month,
Met_Day = Met_Day,
Ball = Ball,
Egg_Location = Egg_Location,
Met_Location = Met_Location,
Met_Level = Met_Level,
Tracker = Tracker,
IsNicknamed = IsNicknamed,
CurrentHandler = CurrentHandler,
HT_Gender = HT_Gender,
HT_Language = HT_Language,
HT_Friendship = HT_Friendship,
HT_Intensity = HT_Intensity,
HT_Memory = HT_Memory,
HT_Feeling = HT_Feeling,
HT_TextVar = HT_TextVar,
FatefulEncounter = FatefulEncounter,
CNT_Cool = CNT_Cool,
CNT_Beauty = CNT_Beauty,
CNT_Cute = CNT_Cute,
CNT_Smart = CNT_Smart,
CNT_Tough = CNT_Tough,
CNT_Sheen = CNT_Sheen,
RibbonChampionKalos = RibbonChampionKalos,
RibbonChampionG3 = RibbonChampionG3,
RibbonChampionSinnoh = RibbonChampionSinnoh,
RibbonBestFriends = RibbonBestFriends,
RibbonTraining = RibbonTraining,
RibbonBattlerSkillful = RibbonBattlerSkillful,
RibbonBattlerExpert = RibbonBattlerExpert,
RibbonEffort = RibbonEffort,
RibbonAlert = RibbonAlert,
RibbonShock = RibbonShock,
RibbonDowncast = RibbonDowncast,
RibbonCareless = RibbonCareless,
RibbonRelax = RibbonRelax,
RibbonSnooze = RibbonSnooze,
RibbonSmile = RibbonSmile,
RibbonGorgeous = RibbonGorgeous,
RibbonRoyal = RibbonRoyal,
RibbonGorgeousRoyal = RibbonGorgeousRoyal,
RibbonArtist = RibbonArtist,
RibbonFootprint = RibbonFootprint,
RibbonRecord = RibbonRecord,
RibbonLegend = RibbonLegend,
RibbonCountry = RibbonCountry,
RibbonNational = RibbonNational,
RibbonEarth = RibbonEarth,
RibbonWorld = RibbonWorld,
RibbonClassic = RibbonClassic,
RibbonPremier = RibbonPremier,
RibbonEvent = RibbonEvent,
RibbonBirthday = RibbonBirthday,
RibbonSpecial = RibbonSpecial,
RibbonSouvenir = RibbonSouvenir,
RibbonWishing = RibbonWishing,
RibbonChampionBattle = RibbonChampionBattle,
RibbonChampionRegional = RibbonChampionRegional,
RibbonChampionNational = RibbonChampionNational,
RibbonChampionWorld = RibbonChampionWorld,
HasContestMemoryRibbon = HasContestMemoryRibbon,
HasBattleMemoryRibbon = HasBattleMemoryRibbon,
RibbonChampionG6Hoenn = RibbonChampionG6Hoenn,
RibbonContestStar = RibbonContestStar,
RibbonMasterCoolness = RibbonMasterCoolness,
RibbonMasterBeauty = RibbonMasterBeauty,
RibbonMasterCuteness = RibbonMasterCuteness,
RibbonMasterCleverness = RibbonMasterCleverness,
RibbonMasterToughness = RibbonMasterToughness,
RibbonChampionAlola = RibbonChampionAlola,
RibbonBattleRoyale = RibbonBattleRoyale,
RibbonBattleTreeGreat = RibbonBattleTreeGreat,
RibbonBattleTreeMaster = RibbonBattleTreeMaster,
RibbonChampionGalar = RibbonChampionGalar,
RibbonTowerMaster = RibbonTowerMaster,
RibbonMasterRank = RibbonMasterRank,
RibbonMarkLunchtime = RibbonMarkLunchtime,
RibbonMarkSleepyTime = RibbonMarkSleepyTime,
RibbonMarkDusk = RibbonMarkDusk,
RibbonMarkDawn = RibbonMarkDawn,
RibbonMarkCloudy = RibbonMarkCloudy,
RibbonMarkRainy = RibbonMarkRainy,
RibbonMarkStormy = RibbonMarkStormy,
RibbonMarkSnowy = RibbonMarkSnowy,
RibbonMarkBlizzard = RibbonMarkBlizzard,
RibbonMarkDry = RibbonMarkDry,
RibbonMarkSandstorm = RibbonMarkSandstorm,
RibbonCountMemoryContest = RibbonCountMemoryContest,
RibbonCountMemoryBattle = RibbonCountMemoryBattle,
RibbonMarkMisty = RibbonMarkMisty,
RibbonMarkDestiny = RibbonMarkDestiny,
RibbonMarkFishing = RibbonMarkFishing,
RibbonMarkCurry = RibbonMarkCurry,
RibbonMarkUncommon = RibbonMarkUncommon,
RibbonMarkRare = RibbonMarkRare,
RibbonMarkRowdy = RibbonMarkRowdy,
RibbonMarkAbsentMinded = RibbonMarkAbsentMinded,
RibbonMarkJittery = RibbonMarkJittery,
RibbonMarkExcited = RibbonMarkExcited,
RibbonMarkCharismatic = RibbonMarkCharismatic,
RibbonMarkCalmness = RibbonMarkCalmness,
RibbonMarkIntense = RibbonMarkIntense,
RibbonMarkZonedOut = RibbonMarkZonedOut,
RibbonMarkJoyful = RibbonMarkJoyful,
RibbonMarkAngry = RibbonMarkAngry,
RibbonMarkSmiley = RibbonMarkSmiley,
RibbonMarkTeary = RibbonMarkTeary,
RibbonMarkUpbeat = RibbonMarkUpbeat,
RibbonMarkPeeved = RibbonMarkPeeved,
RibbonMarkIntellectual = RibbonMarkIntellectual,
RibbonMarkFerocious = RibbonMarkFerocious,
RibbonMarkCrafty = RibbonMarkCrafty,
RibbonMarkScowling = RibbonMarkScowling,
RibbonMarkKindly = RibbonMarkKindly,
RibbonMarkFlustered = RibbonMarkFlustered,
RibbonMarkPumpedUp = RibbonMarkPumpedUp,
RibbonMarkZeroEnergy = RibbonMarkZeroEnergy,
RibbonMarkPrideful = RibbonMarkPrideful,
RibbonMarkUnsure = RibbonMarkUnsure,
RibbonMarkHumble = RibbonMarkHumble,
RibbonMarkThorny = RibbonMarkThorny,
RibbonMarkVigor = RibbonMarkVigor,
RibbonMarkSlump = RibbonMarkSlump,
RibbonPioneer = RibbonPioneer,
RibbonTwinklingStar = RibbonTwinklingStar,
AffixedRibbon = AffixedRibbon,
HyperTrainFlags = HyperTrainFlags,
BattleVersion = BattleVersion,
PKRS_Days = PKRS_Days,
PKRS_Strain = PKRS_Strain,
HeightScalar = HeightScalar,
WeightScalar = WeightScalar,
Favorite = Favorite,
};
Nickname_Trash.CopyTo(pk.Nickname_Trash);
OT_Trash.CopyTo(pk.OT_Trash);
HT_Trash.CopyTo(pk.HT_Trash);
pk.SanitizeImport();
pk.ResetMoves();
pk.ResetPartyStats();
pk.RefreshChecksum();
return pk;
}
}
}

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