Split EncounterEgg into derived classes (#4490)

Splits EncounterEgg into derived classes, allowing for fine-tuned control of each generation's egg generation & pattern matching.

Adds an interface to check if the encounter is a bred egg (useful for many scenarios when checking for move inheritance, in general).

Enhances the deferral rating for PIDIV matches in eggs based on global legality check settings.

Adds date/time indicators for Gen3/4 eggs and other Method 1 encounters.
This commit is contained in:
Kurt 2025-05-11 22:31:36 -05:00 committed by GitHub
parent b2d70295e9
commit 85f5950f28
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
85 changed files with 1858 additions and 457 deletions

View File

@ -85,7 +85,7 @@ public static void GetSuggestedRelearnMoves(this LegalityAnalysis legal, Span<us
if (moves[0] != 0)
return;
if (enc is MysteryGift or EncounterEgg)
if (enc is MysteryGift or IEncounterEgg)
return;
if (enc is EncounterSlot6AO {CanDexNav: true} dn)

View File

@ -63,9 +63,9 @@ private static void VerifyECShare(BulkAnalysis input, CombinedReference pr, Comb
// eggs/mystery gifts shouldn't share with wild encounters
var cenc = ca.Info.EncounterMatch;
bool eggMysteryCurrent = cenc is EncounterEgg or MysteryGift;
bool eggMysteryCurrent = cenc is IEncounterEgg or MysteryGift;
var penc = pa.Info.EncounterMatch;
bool eggMysteryPrevious = penc is EncounterEgg or MysteryGift;
bool eggMysteryPrevious = penc is IEncounterEgg or MysteryGift;
if (eggMysteryCurrent != eggMysteryPrevious)
{

View File

@ -64,9 +64,9 @@ private static void VerifyPIDShare(BulkAnalysis input, CombinedReference pr, Com
// eggs/mystery gifts shouldn't share with wild encounters
var cenc = ca.Info.EncounterMatch;
bool eggMysteryCurrent = cenc is EncounterEgg or MysteryGift;
bool eggMysteryCurrent = cenc is IEncounterEgg or MysteryGift;
var penc = pa.Info.EncounterMatch;
bool eggMysteryPrevious = penc is EncounterEgg or MysteryGift;
bool eggMysteryPrevious = penc is IEncounterEgg or MysteryGift;
if (eggMysteryCurrent != eggMysteryPrevious)
{

View File

@ -36,23 +36,16 @@ private static IEnumerable<IEncounterable> GetEncounters(PKM pk, EvoCriteria[] c
yield return enc.Encounter;
}
private const byte Generation = 2;
private const EntityContext Context = EntityContext.Gen2;
private const byte EggLevel = 5;
private const byte EggLevel = EncounterEgg2.Level;
private static EncounterEgg CreateEggEncounter(ushort species, byte form, GameVersion version)
{
if (FormInfo.IsBattleOnlyForm(species, form, Generation))
form = FormInfo.GetOutOfBattleForm(species, form, Generation);
return new EncounterEgg(species, form, EggLevel, Generation, version, Context);
}
private static EncounterEgg2 CreateEggEncounter(ushort species, GameVersion version) => new(species, version);
private static (ushort Species, byte Form) GetBaby(EvoCriteria lowest)
{
return EvolutionTree.Evolves2.GetBaseSpeciesForm(lowest.Species, lowest.Form);
}
public static bool TryGetEgg(ReadOnlySpan<EvoCriteria> chain, GameVersion version, [NotNullWhen(true)] out EncounterEgg? result)
public static bool TryGetEgg(ReadOnlySpan<EvoCriteria> chain, GameVersion version, [NotNullWhen(true)] out EncounterEgg2? result)
{
result = null;
var devolved = chain[^1];
@ -73,13 +66,13 @@ public static bool TryGetEgg(ReadOnlySpan<EvoCriteria> chain, GameVersion versio
if (!PersonalTable.C.IsPresentInGame(species, form))
return false;
result = CreateEggEncounter(species, form, version);
result = CreateEggEncounter(species, version);
return true;
}
// Depending on the game it was hatched (GS vs C), met data will be present.
// Since met data can't be used to infer which game it was created on, we yield both if possible.
public static bool TryGetEggCrystal(PKM pk, EncounterEgg egg, [NotNullWhen(true)] out EncounterEgg? crystal)
public static bool TryGetEggCrystal(PKM pk, EncounterEgg2 egg, [NotNullWhen(true)] out EncounterEgg2? crystal)
{
if (!ParseSettings.AllowGen2Crystal(pk))
{

View File

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using static PKHeX.Core.RandomCorrelationRating;
namespace PKHeX.Core;
@ -24,9 +25,12 @@ public IEnumerable<IEncounterable> GetEncounters(PKM pk, LegalInfo info)
private enum DeferralType
{
// Legal
None,
PIDIVDefer,
// Illegal
PIDIV,
Tile,
Ball,
SlotNumber,
}
@ -64,9 +68,13 @@ public IEnumerable<IEncounterable> GetEncounters(PKM pk, EvoCriteria[] chain, Le
foreach (var enc in iterator)
{
var e = enc.Encounter;
if (!IsTypeCompatible(e, pk, ref info.GetPIDIVRef()))
var typeCheck = IsTypeCompatible(e, pk, ref info.GetPIDIVRef());
if (typeCheck is not Match)
{
defer.Update(DeferralType.PIDIV, e);
var rating = typeCheck == NotIdeal
? DeferralType.PIDIVDefer
: DeferralType.PIDIV;
defer.Update(rating, e);
continue;
}
if (!IsBallCompatible(e, pk))
@ -102,7 +110,7 @@ public IEnumerable<IEncounterable> GetEncounters(PKM pk, EvoCriteria[] chain, Le
// Errors will be flagged later for those not manually handled below.
if (defer.Encounter is not { } lastResort)
yield break;
if (defer.Type is DeferralType.PIDIV && !(lastResort is EncounterEgg && ParseSettings.Settings.FramePattern.EggRandomAnyType3))
if (defer.Type is DeferralType.PIDIV)
info.ManualFlag = EncounterYieldFlag.InvalidPIDIV;
else if (defer.Type is DeferralType.SlotNumber)
info.ManualFlag = EncounterYieldFlag.InvalidFrame;
@ -115,33 +123,27 @@ public IEnumerable<IEncounterable> GetEncounters(PKM pk, EvoCriteria[] chain, Le
_ => pk.Ball is not (byte)Ball.Safari,
};
private static bool IsTypeCompatible(IEncounterTemplate enc, PKM pk, ref PIDIV pidiv)
private static RandomCorrelationRating IsTypeCompatible(IEncounterTemplate enc, PKM pk, ref PIDIV pidiv)
{
if (enc is IRandomCorrelationEvent3 revise)
return revise.IsCompatibleReviseReset(ref pidiv, pk);
var type = pidiv.Type;
if (enc is IRandomCorrelation r)
return r.IsCompatible(type, pk);
return type == PIDType.None;
return type is PIDType.None ? Match : Mismatch;
}
private const byte Generation = 3;
private const EntityContext Context = EntityContext.Gen3;
private const byte EggLevel = 5;
private const byte EggLevel = EncounterEgg3.Level;
private static EncounterEgg CreateEggEncounter(ushort species, byte form, GameVersion version)
{
if (FormInfo.IsBattleOnlyForm(species, form, Generation) || species is (int)Species.Castform)
form = FormInfo.GetOutOfBattleForm(species, form, Generation);
return new EncounterEgg(species, form, EggLevel, Generation, version, Context);
}
private static EncounterEgg3 CreateEggEncounter(ushort species, GameVersion version) => new(species, version);
private static (ushort Species, byte Form) GetBaby(EvoCriteria lowest)
{
return EvolutionTree.Evolves3.GetBaseSpeciesForm(lowest.Species, lowest.Form);
}
public static bool TryGetEgg(ReadOnlySpan<EvoCriteria> chain, GameVersion version, [NotNullWhen(true)] out EncounterEgg? result)
public static bool TryGetEgg(ReadOnlySpan<EvoCriteria> chain, GameVersion version, [NotNullWhen(true)] out EncounterEgg3? result)
{
result = null;
var devolved = chain[^1];
@ -163,13 +165,13 @@ public static bool TryGetEgg(ReadOnlySpan<EvoCriteria> chain, GameVersion versio
if (!PersonalTable.E.IsPresentInGame(species, form))
return false;
result = CreateEggEncounter(species, form, version);
result = CreateEggEncounter(species, version);
return true;
}
// Version is not updated when hatching an Egg in Gen3. Version is a clear indicator of the game it originated on.
public static bool TryGetSplit(EncounterEgg other, ReadOnlySpan<EvoCriteria> chain, [NotNullWhen(true)] out EncounterEgg? result)
public static bool TryGetSplit(EncounterEgg3 other, ReadOnlySpan<EvoCriteria> chain, [NotNullWhen(true)] out EncounterEgg3? result)
{
result = null;
// Check for split-breed
@ -183,7 +185,7 @@ public static bool TryGetSplit(EncounterEgg other, ReadOnlySpan<EvoCriteria> cha
if (!Breeding.IsSplitBreedNotBabySpecies3(devolved.Species))
return false;
result = other with { Species = devolved.Species, Form = devolved.Form };
result = other with { Species = devolved.Species };
return true;
}
}

View File

@ -48,9 +48,10 @@ public IEnumerable<IEncounterable> GetEncounters(PKM pk, EvoCriteria[] chain, Le
}
static bool IsTypeCompatible(IEncounterTemplate enc, PKM pk, PIDType type)
{
// boolean results only from this set of games (no correlation confusion to be concerned with)
if (enc is IRandomCorrelation r)
return r.IsCompatible(type, pk);
return type == PIDType.None;
return r.IsCompatible(type, pk) == RandomCorrelationRating.Match;
return type is PIDType.None;
}
if (IsTypeCompatible(z, pk, info.PIDIV.Type))

View File

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using static PKHeX.Core.RandomCorrelationRating;
namespace PKHeX.Core;
@ -29,7 +30,11 @@ public IEnumerable<IEncounterable> GetPossible(PKM pk, EvoCriteria[] chain, Game
private enum DeferralType
{
// Legal
None,
PIDIVDefer,
// Illegal
PIDIV,
Tile,
Ball,
@ -66,9 +71,14 @@ public IEnumerable<IEncounterable> GetEncounters(PKM pk, EvoCriteria[] chain, Le
defer.Update(DeferralType.Tile, e);
continue;
}
if (!IsTypeCompatible(e, pk, info.PIDIV.Type))
var typeCheck = IsTypeCompatible(e, pk, info.PIDIV.Type);
if (typeCheck is not Match)
{
defer.Update(DeferralType.PIDIV, e);
var rating = typeCheck == NotIdeal
? DeferralType.PIDIVDefer
: DeferralType.PIDIV;
defer.Update(rating, e);
continue;
}
if (!IsBallCompatible(e, pk))
@ -106,7 +116,7 @@ public IEnumerable<IEncounterable> GetEncounters(PKM pk, EvoCriteria[] chain, Le
// Errors will be flagged later for those not manually handled below.
if (defer.Encounter is not { } lastResort)
yield break;
if (defer.Type is DeferralType.PIDIV && !(lastResort is EncounterEgg && ParseSettings.Settings.FramePattern.EggRandomAnyType4))
if (defer.Type is DeferralType.PIDIV)
info.ManualFlag = EncounterYieldFlag.InvalidPIDIV;
else if (defer.Type is DeferralType.SlotNumber)
info.ManualFlag = EncounterYieldFlag.InvalidFrame;
@ -129,30 +139,24 @@ private static bool IsTileCompatible(IEncounterTemplate enc, PKM pk)
return t.GroundTile.Contains(e.GroundTile);
}
private static bool IsTypeCompatible(IEncounterTemplate enc, PKM pk, PIDType type)
private static RandomCorrelationRating IsTypeCompatible(IEncounterTemplate enc, PKM pk, PIDType type)
{
if (enc is IRandomCorrelation r)
return r.IsCompatible(type, pk);
return type == PIDType.None;
return type is PIDType.None ? Match : Mismatch;
}
private const byte Generation = 4;
private const EntityContext Context = EntityContext.Gen4;
private const byte EggLevel = 1;
private const byte EggLevel = EncounterEgg4.Level;
private static EncounterEgg CreateEggEncounter(ushort species, byte form, GameVersion version)
{
if (FormInfo.IsBattleOnlyForm(species, form, Generation) || species is (int)Species.Rotom or (int)Species.Castform)
form = FormInfo.GetOutOfBattleForm(species, form, Generation);
return new EncounterEgg(species, form, EggLevel, Generation, version, Context);
}
private static EncounterEgg4 CreateEggEncounter(ushort species, GameVersion version) => new(species, version);
private static (ushort Species, byte Form) GetBaby(EvoCriteria lowest)
{
return EvolutionTree.Evolves4.GetBaseSpeciesForm(lowest.Species, lowest.Form);
}
public static bool TryGetEgg(ReadOnlySpan<EvoCriteria> chain, GameVersion version, [NotNullWhen(true)] out EncounterEgg? result)
public static bool TryGetEgg(ReadOnlySpan<EvoCriteria> chain, GameVersion version, [NotNullWhen(true)] out EncounterEgg4? result)
{
result = null;
var devolved = chain[^1];
@ -174,13 +178,13 @@ public static bool TryGetEgg(ReadOnlySpan<EvoCriteria> chain, GameVersion versio
if (!PersonalTable.HGSS.IsPresentInGame(species, form))
return false;
result = CreateEggEncounter(species, form, version);
result = CreateEggEncounter(species, version);
return true;
}
// Version is not updated when hatching an Egg in Gen4. Version is a clear indicator of the game it originated on.
public static bool TryGetSplit(EncounterEgg other, ReadOnlySpan<EvoCriteria> chain, [NotNullWhen(true)] out EncounterEgg? result)
public static bool TryGetSplit(EncounterEgg4 other, ReadOnlySpan<EvoCriteria> chain, [NotNullWhen(true)] out EncounterEgg4? result)
{
result = null;
// Check for split-breed
@ -194,7 +198,7 @@ public static bool TryGetSplit(EncounterEgg other, ReadOnlySpan<EvoCriteria> cha
if (!Breeding.IsSplitBreedNotBabySpecies4(devolved.Species))
return false;
result = other with { Species = devolved.Species, Form = devolved.Form };
result = other with { Species = devolved.Species };
return true;
}
}

View File

@ -31,23 +31,17 @@ public IEnumerable<IEncounterable> GetEncounters(PKM pk, EvoCriteria[] chain, Le
yield return enc.Encounter;
}
private const byte Generation = 5;
private const EntityContext Context = EntityContext.Gen5;
private const byte EggLevel = EggStateLegality.EggMetLevel;
private static EncounterEgg CreateEggEncounter(ushort species, byte form, GameVersion version)
{
if (FormInfo.IsBattleOnlyForm(species, form, Generation) || species is (int)Species.Rotom or (int)Species.Castform)
form = FormInfo.GetOutOfBattleForm(species, form, Generation);
return new EncounterEgg(species, form, EggLevel, Generation, version, Context);
}
private static EncounterEgg5 CreateEggEncounter(ushort species, GameVersion version) => new(species, version);
private static (ushort Species, byte Form) GetBaby(EvoCriteria lowest)
{
return EvolutionTree.Evolves5.GetBaseSpeciesForm(lowest.Species, lowest.Form);
}
public static bool TryGetEgg(ReadOnlySpan<EvoCriteria> chain, GameVersion version, [NotNullWhen(true)] out EncounterEgg? result)
public static bool TryGetEgg(ReadOnlySpan<EvoCriteria> chain, GameVersion version, [NotNullWhen(true)] out EncounterEgg5? result)
{
result = null;
var devolved = chain[^1];
@ -69,14 +63,14 @@ public static bool TryGetEgg(ReadOnlySpan<EvoCriteria> chain, GameVersion versio
if (!PersonalTable.B2W2.IsPresentInGame(species, form))
return false;
result = CreateEggEncounter(species, form, version);
result = CreateEggEncounter(species, version);
return true;
}
// Both B/W and B2/W2 have the same egg move sets, so there is no point generating other-game pair encounters for traded eggs.
// When hatched, the entity's Version is updated to the OT's.
public static bool TryGetSplit(EncounterEgg other, ReadOnlySpan<EvoCriteria> chain, [NotNullWhen(true)] out EncounterEgg? result)
public static bool TryGetSplit(EncounterEgg5 other, ReadOnlySpan<EvoCriteria> chain, [NotNullWhen(true)] out EncounterEgg5? result)
{
result = null;
// Check for split-breed
@ -90,7 +84,7 @@ public static bool TryGetSplit(EncounterEgg other, ReadOnlySpan<EvoCriteria> cha
if (!Breeding.IsSplitBreedNotBabySpecies4(devolved.Species))
return false;
result = other with { Species = devolved.Species, Form = devolved.Form };
result = other with { Species = devolved.Species };
return true;
}
}

View File

@ -31,7 +31,7 @@ public IEnumerable<IEncounterable> GetEncounters(PKM pk, EvoCriteria[] chain, Le
private const byte Generation = 6;
private const EntityContext Context = EntityContext.Gen6;
private const byte EggLevel = EggStateLegality.EggMetLevel;
private const byte EggLevel = EncounterEgg6.Level;
private static GameVersion GetOtherGamePair(GameVersion version)
{
@ -43,11 +43,11 @@ private static GameVersion GetOtherGamePair(GameVersion version)
return version ^ (GameVersion)2;
}
private static EncounterEgg CreateEggEncounter(ushort species, byte form, GameVersion version)
private static EncounterEgg6 CreateEggEncounter(ushort species, byte form, GameVersion version)
{
if (FormInfo.IsBattleOnlyForm(species, form, Generation) || species is (int)Species.Rotom or (int)Species.Castform)
form = FormInfo.GetOutOfBattleForm(species, form, Generation);
return new EncounterEgg(species, form, EggLevel, Generation, version, Context);
return new EncounterEgg6(species, form, version);
}
private static (ushort Species, byte Form) GetBaby(EvoCriteria lowest)
@ -55,7 +55,7 @@ private static (ushort Species, byte Form) GetBaby(EvoCriteria lowest)
return EvolutionTree.Evolves6.GetBaseSpeciesForm(lowest.Species, lowest.Form);
}
public static bool TryGetEgg(ReadOnlySpan<EvoCriteria> chain, GameVersion version, [NotNullWhen(true)] out EncounterEgg? result)
public static bool TryGetEgg(ReadOnlySpan<EvoCriteria> chain, GameVersion version, [NotNullWhen(true)] out EncounterEgg6? result)
{
result = null;
var devolved = chain[^1];
@ -81,9 +81,9 @@ public static bool TryGetEgg(ReadOnlySpan<EvoCriteria> chain, GameVersion versio
return true;
}
public static EncounterEgg MutateEggTrade(EncounterEgg egg) => egg with { Version = GetOtherGamePair(egg.Version) };
public static EncounterEgg6 MutateEggTrade(EncounterEgg6 egg) => egg with { Version = GetOtherGamePair(egg.Version) };
public static bool TryGetSplit(EncounterEgg other, ReadOnlySpan<EvoCriteria> chain, [NotNullWhen(true)] out EncounterEgg? result)
public static bool TryGetSplit(EncounterEgg6 other, ReadOnlySpan<EvoCriteria> chain, [NotNullWhen(true)] out EncounterEgg6? result)
{
result = null;
// Check for split-breed

View File

@ -66,7 +66,7 @@ private static ushort GetVCSpecies(ReadOnlySpan<EvoCriteria> chain, PKM pk, usho
private const EntityContext Context = EntityContext.Gen7;
private const byte EggLevel = EggStateLegality.EggMetLevel;
public static bool TryGetEgg(ReadOnlySpan<EvoCriteria> chain, GameVersion version, [NotNullWhen(true)] out EncounterEgg? result)
public static bool TryGetEgg(ReadOnlySpan<EvoCriteria> chain, GameVersion version, [NotNullWhen(true)] out EncounterEgg7? result)
{
result = null;
var devolved = chain[^1];
@ -92,9 +92,9 @@ public static bool TryGetEgg(ReadOnlySpan<EvoCriteria> chain, GameVersion versio
return true;
}
public static EncounterEgg MutateEggTrade(EncounterEgg egg) => egg with { Version = GetOtherGamePair(egg.Version) };
public static EncounterEgg7 MutateEggTrade(EncounterEgg7 egg) => egg with { Version = GetOtherGamePair(egg.Version) };
public static bool TryGetSplit(EncounterEgg other, ReadOnlySpan<EvoCriteria> chain, [NotNullWhen(true)] out EncounterEgg? result)
public static bool TryGetSplit(EncounterEgg7 other, ReadOnlySpan<EvoCriteria> chain, [NotNullWhen(true)] out EncounterEgg7? result)
{
result = null;
// Check for split-breed
@ -124,11 +124,11 @@ private static GameVersion GetOtherGamePair(GameVersion version)
#pragma warning restore RCS1130 // Bitwise operation on enum without Flags attribute.
}
private static EncounterEgg CreateEggEncounter(ushort species, byte form, GameVersion version)
private static EncounterEgg7 CreateEggEncounter(ushort species, byte form, GameVersion version)
{
if (FormInfo.IsBattleOnlyForm(species, form, Generation) || species is (int)Species.Rotom or (int)Species.Castform)
form = FormInfo.GetOutOfBattleForm(species, form, Generation);
return new EncounterEgg(species, form, EggLevel, Generation, version, Context);
return new EncounterEgg7(species, form, version);
}
private static (ushort Species, byte Form) GetBaby(EvoCriteria lowest)

View File

@ -27,11 +27,11 @@ public IEnumerable<IEncounterable> GetEncounters(PKM pk, EvoCriteria[] chain, Le
private const EntityContext Context = EntityContext.Gen8;
private const byte EggLevel = 1;
private static EncounterEgg CreateEggEncounter(ushort species, byte form, GameVersion version)
private static EncounterEgg8 CreateEggEncounter(ushort species, byte form, GameVersion version)
{
if (FormInfo.IsBattleOnlyForm(species, form, Generation) || species is (int)Species.Rotom or (int)Species.Castform)
form = FormInfo.GetOutOfBattleForm(species, form, Generation);
return new EncounterEgg(species, form, EggLevel, Generation, version, Context);
return new EncounterEgg8(species, form, version);
}
private static (ushort Species, byte Form) GetBaby(EvoCriteria lowest)
@ -43,7 +43,7 @@ private static (ushort Species, byte Form) GetBaby(EvoCriteria lowest)
return (lowest.Species, lowest.Form);
}
public static bool TryGetEgg(ReadOnlySpan<EvoCriteria> chain, GameVersion version, [NotNullWhen(true)] out EncounterEgg? result)
public static bool TryGetEgg(ReadOnlySpan<EvoCriteria> chain, GameVersion version, [NotNullWhen(true)] out EncounterEgg8? result)
{
result = null;
var devolved = chain[^1];
@ -69,7 +69,7 @@ public static bool TryGetEgg(ReadOnlySpan<EvoCriteria> chain, GameVersion versio
return true;
}
public static bool TryGetSplit(EncounterEgg other, ReadOnlySpan<EvoCriteria> chain, [NotNullWhen(true)] out EncounterEgg? result)
public static bool TryGetSplit(EncounterEgg8 other, ReadOnlySpan<EvoCriteria> chain, [NotNullWhen(true)] out EncounterEgg8? result)
{
result = null;
// Check for split-breed

View File

@ -35,7 +35,7 @@ public IEnumerable<IEncounterable> GetEncountersSWSH(PKM pk, EvoCriteria[] chain
private const EntityContext Context = EntityContext.Gen8b;
private const byte EggLevel = 1;
public static bool TryGetEgg(ReadOnlySpan<EvoCriteria> chain, GameVersion version, [NotNullWhen(true)] out EncounterEgg? result)
public static bool TryGetEgg(ReadOnlySpan<EvoCriteria> chain, GameVersion version, [NotNullWhen(true)] out EncounterEgg8b? result)
{
result = null;
var devolved = chain[^1];
@ -61,7 +61,7 @@ public static bool TryGetEgg(ReadOnlySpan<EvoCriteria> chain, GameVersion versio
return true;
}
public static bool TryGetSplit(EncounterEgg other, ReadOnlySpan<EvoCriteria> chain, [NotNullWhen(true)] out EncounterEgg? result)
public static bool TryGetSplit(EncounterEgg8b other, ReadOnlySpan<EvoCriteria> chain, [NotNullWhen(true)] out EncounterEgg8b? result)
{
result = null;
// Check for split-breed
@ -79,11 +79,11 @@ public static bool TryGetSplit(EncounterEgg other, ReadOnlySpan<EvoCriteria> cha
return true;
}
private static EncounterEgg CreateEggEncounter(ushort species, byte form, GameVersion version)
private static EncounterEgg8b CreateEggEncounter(ushort species, byte form, GameVersion version)
{
if (FormInfo.IsBattleOnlyForm(species, form, Generation) || species is (int)Species.Rotom or (int)Species.Castform)
form = FormInfo.GetOutOfBattleForm(species, form, Generation);
return new EncounterEgg(species, form, EggLevel, Generation, version, Context);
return new EncounterEgg8b(species, form, version);
}
private static (ushort Species, byte Form) GetBaby(EvoCriteria lowest)

View File

@ -48,14 +48,14 @@ public IEnumerable<IEncounterable> GetEncounters(PKM pk, EvoCriteria[] chain, Le
private const EntityContext Context = EntityContext.Gen9;
private const byte EggLevel = 1;
public static bool TryGetEgg(PKM pk, EvoCriteria[] chain, GameVersion version, [NotNullWhen(true)] out EncounterEgg? result)
public static bool TryGetEgg(PKM pk, EvoCriteria[] chain, GameVersion version, [NotNullWhen(true)] out EncounterEgg9? result)
{
if (version == 0 && pk.IsEgg)
version = SL;
return TryGetEgg(chain, version, out result);
}
public static bool TryGetEgg(EvoCriteria[] chain, GameVersion version, [NotNullWhen(true)] out EncounterEgg? result)
public static bool TryGetEgg(EvoCriteria[] chain, GameVersion version, [NotNullWhen(true)] out EncounterEgg9? result)
{
result = null;
var devolved = chain[^1];
@ -82,13 +82,13 @@ public static bool TryGetEgg(EvoCriteria[] chain, GameVersion version, [NotNullW
return true;
}
private static EncounterEgg CreateEggEncounter(ushort species, byte form, GameVersion version)
private static EncounterEgg9 CreateEggEncounter(ushort species, byte form, GameVersion version)
{
if (species == (int)Species.Scatterbug)
form = Vivillon3DS.FancyFormID; // Fancy
else if (FormInfo.IsBattleOnlyForm(species, form, Generation) || species is (int)Species.Rotom or (int)Species.Castform)
form = FormInfo.GetOutOfBattleForm(species, form, Generation);
return new EncounterEgg(species, form, EggLevel, Generation, version, Context);
return new EncounterEgg9(species, form, version);
}
private static (ushort Species, byte Form) GetBaby(EvoCriteria lowest)

View File

@ -93,7 +93,7 @@ private static PeekEnumerator<IEncounterable> PickPreferredIterator(PKM pk, Peek
EncounterTrade1 => GBEncounterPriority.TradeEncounterG1,
EncounterTrade2 => GBEncounterPriority.TradeEncounterG2,
EncounterSlot1 or EncounterSlot2 => GBEncounterPriority.WildEncounter,
EncounterEgg => GBEncounterPriority.EggEncounter,
EncounterEgg2 => GBEncounterPriority.EggEncounter,
_ => GBEncounterPriority.StaticEncounter,
};

View File

@ -275,7 +275,7 @@ private static IEnumerable<IEncounterable> GetEggs(PKM pk, ReadOnlyMemory<ushort
var eggs = generator.GetPossible(pk, chain, version, Egg);
foreach (var egg in eggs)
{
if (needs.Length == 0 || HasAllNeededMovesEgg(needs.Span, egg))
if (needs.Length == 0 || HasAllNeededMovesEgg(needs.Span, (IEncounterEgg)egg))
yield return egg;
}
}
@ -414,13 +414,13 @@ private static int GetMoveMaskGen2(ReadOnlySpan<ushort> needs, IEncounterTemplat
return Moveset.BitOverlap(moves, needs);
}
private static int GetMoveMaskEgg(ReadOnlySpan<ushort> needs, IEncounterTemplate egg)
private static int GetMoveMaskEgg(ReadOnlySpan<ushort> needs, IEncounterEgg egg)
{
var source = GameData.GetLearnSource(egg.Version);
var source = egg.Learn;
var eggMoves = source.GetEggMoves(egg.Species, egg.Form);
int flags = Moveset.BitOverlap(eggMoves, needs);
var vt = needs.IndexOf((ushort)Move.VoltTackle);
if (vt != -1 && egg is EncounterEgg { CanHaveVoltTackle: true })
if (vt != -1 && egg.CanHaveVoltTackle)
flags |= 1 << vt;
else if (egg.Generation <= 2)
flags |= GetMoveMaskGen2(needs, egg);
@ -440,7 +440,7 @@ private static bool HasAllNeededMovesSlot(ReadOnlySpan<ushort> needs, IEncounter
return false;
}
private static bool HasAllNeededMovesEgg(ReadOnlySpan<ushort> needs, IEncounterTemplate egg)
private static bool HasAllNeededMovesEgg(ReadOnlySpan<ushort> needs, IEncounterEgg egg)
{
int flags = GetMoveMaskEgg(needs, egg);
return flags == (1 << needs.Length) - 1;

View File

@ -78,7 +78,7 @@ public bool MoveNext()
State = YieldState.BredSplit;
return SetCurrent(egg);
case YieldState.BredSplit:
if (!EncounterGenerator3.TryGetSplit((EncounterEgg)Current, Chain, out egg))
if (!EncounterGenerator3.TryGetSplit((EncounterEgg3)Current, Chain, out egg))
goto case YieldState.EventStart;
State = YieldState.EventStart;
return SetCurrent(egg);

View File

@ -72,7 +72,7 @@ public bool MoveNext()
return SetCurrent(egg);
case YieldState.BredSplit:
State = YieldState.EventStart;
if (EncounterGenerator4.TryGetSplit((EncounterEgg)Current, Chain, out egg))
if (EncounterGenerator4.TryGetSplit((EncounterEgg4)Current, Chain, out egg))
return SetCurrent(egg);
goto case YieldState.EventStart;

View File

@ -75,7 +75,7 @@ public bool MoveNext()
State = YieldState.BredSplit;
return SetCurrent(egg);
case YieldState.BredSplit:
if (!EncounterGenerator5.TryGetSplit((EncounterEgg)Current, Chain, out egg))
if (!EncounterGenerator5.TryGetSplit((EncounterEgg5)Current, Chain, out egg))
goto case YieldState.EventStart;
State = YieldState.EventStart;
return SetCurrent(egg);

View File

@ -69,16 +69,16 @@ public bool MoveNext()
return SetCurrent(egg);
case YieldState.BredTrade:
State = YieldState.BredSplit;
egg = EncounterGenerator6.MutateEggTrade((EncounterEgg)Current);
egg = EncounterGenerator6.MutateEggTrade((EncounterEgg6)Current);
return SetCurrent(egg);
case YieldState.BredSplit:
if (!EncounterGenerator6.TryGetSplit((EncounterEgg)Current, Chain, out egg))
if (!EncounterGenerator6.TryGetSplit((EncounterEgg6)Current, Chain, out egg))
goto case YieldState.EventStart;
State = YieldState.BredSplitTrade;
return SetCurrent(egg);
case YieldState.BredSplitTrade:
State = YieldState.EventStart;
egg = EncounterGenerator6.MutateEggTrade((EncounterEgg)Current);
egg = EncounterGenerator6.MutateEggTrade((EncounterEgg6)Current);
return SetCurrent(egg);
case YieldState.EventStart:

View File

@ -71,16 +71,16 @@ public bool MoveNext()
return SetCurrent(egg);
case YieldState.BredTrade:
State = YieldState.BredSplit;
egg = EncounterGenerator7.MutateEggTrade((EncounterEgg)Current);
egg = EncounterGenerator7.MutateEggTrade((EncounterEgg7)Current);
return SetCurrent(egg);
case YieldState.BredSplit:
if (!EncounterGenerator7.TryGetSplit((EncounterEgg)Current, Chain, out egg))
if (!EncounterGenerator7.TryGetSplit((EncounterEgg7)Current, Chain, out egg))
goto case YieldState.EventStart;
State = YieldState.BredSplitTrade;
return SetCurrent(egg);
case YieldState.BredSplitTrade:
State = YieldState.EventStart;
egg = EncounterGenerator7.MutateEggTrade((EncounterEgg)Current);
egg = EncounterGenerator7.MutateEggTrade((EncounterEgg7)Current);
return SetCurrent(egg);
case YieldState.EventStart:
@ -97,6 +97,8 @@ public bool MoveNext()
Index = 0; goto case YieldState.TradeStart;
case YieldState.TradeStart:
if (!Flags.HasFlag(EncounterTypeGroup.Trade))
goto case YieldState.StartCaptures;
if (Version is GameVersion.SN or GameVersion.MN)
{ State = YieldState.TradeSM; goto case YieldState.TradeSM; }
if (Version is GameVersion.US or GameVersion.UM)
@ -115,6 +117,8 @@ public bool MoveNext()
goto case YieldState.StaticStart;
case YieldState.StaticStart:
if (!Flags.HasFlag(EncounterTypeGroup.Static))
goto case YieldState.SlotStart;
if (Version == GameVersion.US)
{ State = YieldState.StaticUS; goto case YieldState.StaticUS; }
if (Version == GameVersion.UM)
@ -152,6 +156,8 @@ public bool MoveNext()
Index = 0; goto case YieldState.SlotStart;
case YieldState.SlotStart:
if (!Flags.HasFlag(EncounterTypeGroup.Slot))
goto case YieldState.SlotEnd;
if (Version == GameVersion.US)
{ State = YieldState.SlotUS; goto case YieldState.SlotUS; }
if (Version == GameVersion.UM)

View File

@ -67,7 +67,7 @@ public bool MoveNext()
State = YieldState.BredSplit;
return SetCurrent(egg);
case YieldState.BredSplit:
if (!EncounterGenerator8.TryGetSplit((EncounterEgg)Current, Chain, out egg))
if (!EncounterGenerator8.TryGetSplit((EncounterEgg8)Current, Chain, out egg))
goto case YieldState.EventStart;
State = YieldState.EventStart;
return SetCurrent(egg);

View File

@ -60,7 +60,7 @@ public bool MoveNext()
State = YieldState.BredSplit;
return SetCurrent(egg);
case YieldState.BredSplit:
if (!EncounterGenerator8b.TryGetSplit((EncounterEgg)Current, Chain, out egg))
if (!EncounterGenerator8b.TryGetSplit((EncounterEgg8b)Current, Chain, out egg))
goto case YieldState.EventStart;
State = YieldState.EventStart;
return SetCurrent(egg);

View File

@ -79,7 +79,7 @@ public bool MoveNext()
State = YieldState.BredSplit;
return SetCurrent(egg);
case YieldState.BredSplit:
if (!EncounterGenerator8b.TryGetSplit((EncounterEgg)Current.Encounter, Chain, out egg))
if (!EncounterGenerator8b.TryGetSplit((EncounterEgg8b)Current.Encounter, Chain, out egg))
goto case YieldState.TradeStart;
State = YieldState.End;
return SetCurrent(egg);

View File

@ -103,7 +103,7 @@ public bool MoveNext()
goto case YieldState.StaticStart;
case YieldState.BredCrystal:
State = YieldState.StaticStart;
if (EncounterGenerator2.TryGetEggCrystal(Entity, (EncounterEgg)Current.Encounter, out egg))
if (EncounterGenerator2.TryGetEggCrystal(Entity, (EncounterEgg2)Current.Encounter, out egg))
return SetCurrent(egg);
goto case YieldState.StaticStart;

View File

@ -242,7 +242,7 @@ public bool MoveNext()
return SetCurrent(egg);
case YieldState.BredSplit:
State = YieldState.Fallback;
if (!EncounterGenerator3.TryGetSplit((EncounterEgg)Current.Encounter, Chain, out egg))
if (!EncounterGenerator3.TryGetSplit((EncounterEgg3)Current.Encounter, Chain, out egg))
goto case YieldState.Fallback;
return SetCurrent(egg);

View File

@ -105,7 +105,7 @@ public bool MoveNext()
State = YieldState.BredSplit;
return SetCurrent(egg);
case YieldState.BredSplit:
if (!EncounterGenerator4.TryGetSplit((EncounterEgg)Current.Encounter, Chain, out egg))
if (!EncounterGenerator4.TryGetSplit((EncounterEgg4)Current.Encounter, Chain, out egg))
goto case YieldState.TradeStart;
State = YieldState.TradeStart;
return SetCurrent(egg);

View File

@ -100,7 +100,7 @@ public bool MoveNext()
case YieldState.BredSplit:
bool daycare = Entity.EggLocation == Locations.Daycare5;
State = daycare ? YieldState.End : YieldState.StartCaptures;
if (EncounterGenerator5.TryGetSplit((EncounterEgg)Current.Encounter, Chain, out egg))
if (EncounterGenerator5.TryGetSplit((EncounterEgg5)Current.Encounter, Chain, out egg))
return SetCurrent(egg);
if (daycare)
break; // no other encounters

View File

@ -95,20 +95,20 @@ public bool MoveNext()
State = YieldState.BredSplit;
if (Entity.EggLocation != Locations.LinkTrade6)
goto case YieldState.BredSplit;
egg = EncounterGenerator6.MutateEggTrade((EncounterEgg)Current.Encounter);
egg = EncounterGenerator6.MutateEggTrade((EncounterEgg6)Current.Encounter);
return SetCurrent(egg);
case YieldState.BredSplit:
if (Chain[^1].Species is (int)Species.Togepi or (int)Species.Wynaut)
goto case YieldState.StartCaptures;
State = YieldState.BredSplitTrade;
if (!EncounterGenerator6.TryGetSplit((EncounterEgg)Current.Encounter, Chain, out egg))
if (!EncounterGenerator6.TryGetSplit((EncounterEgg6)Current.Encounter, Chain, out egg))
break;
return SetCurrent(egg);
case YieldState.BredSplitTrade:
State = YieldState.StartCaptures;
if (Entity.EggLocation != Locations.LinkTrade6)
goto case YieldState.StartCaptures;
egg = EncounterGenerator6.MutateEggTrade((EncounterEgg)Current.Encounter);
egg = EncounterGenerator6.MutateEggTrade((EncounterEgg6)Current.Encounter);
return SetCurrent(egg);
case YieldState.TradeStart:

View File

@ -94,20 +94,20 @@ public bool MoveNext()
State = YieldState.BredSplit;
if (Entity.EggLocation != Locations.LinkTrade6)
goto case YieldState.BredSplit;
egg = EncounterGenerator7.MutateEggTrade((EncounterEgg)Current.Encounter);
egg = EncounterGenerator7.MutateEggTrade((EncounterEgg7)Current.Encounter);
return SetCurrent(egg);
case YieldState.BredSplit:
if (Chain[^1].Species == (int)Species.Eevee)
{ State = YieldState.StaticSharedUSUM; goto case YieldState.StaticSharedUSUM; }
State = YieldState.BredSplitTrade;
if (!EncounterGenerator7.TryGetSplit((EncounterEgg)Current.Encounter, Chain, out egg))
if (!EncounterGenerator7.TryGetSplit((EncounterEgg7)Current.Encounter, Chain, out egg))
break;
return SetCurrent(egg);
case YieldState.BredSplitTrade:
State = YieldState.End;
if (Entity.EggLocation != Locations.LinkTrade6)
break;
egg = EncounterGenerator7.MutateEggTrade((EncounterEgg)Current.Encounter);
egg = EncounterGenerator7.MutateEggTrade((EncounterEgg7)Current.Encounter);
return SetCurrent(egg);
case YieldState.TradeStart:

View File

@ -91,7 +91,7 @@ public bool MoveNext()
return SetCurrent(egg);
case YieldState.BredSplit:
State = YieldState.End;
if (EncounterGenerator8.TryGetSplit((EncounterEgg)Current.Encounter, Chain, out egg))
if (EncounterGenerator8.TryGetSplit((EncounterEgg8)Current.Encounter, Chain, out egg))
return SetCurrent(egg);
break;

View File

@ -83,7 +83,7 @@ public bool MoveNext()
return SetCurrent(egg);
case YieldState.BredSplit:
State = Entity.EggLocation == Locations.Daycare8b ? YieldState.End : YieldState.StartCaptures;
if (EncounterGenerator8b.TryGetSplit((EncounterEgg)Current.Encounter, Chain, out egg))
if (EncounterGenerator8b.TryGetSplit((EncounterEgg8b)Current.Encounter, Chain, out egg))
return SetCurrent(egg);
break;

View File

@ -0,0 +1,78 @@
namespace PKHeX.Core;
/// <summary>
/// Egg Encounter Data
/// </summary>
public sealed record EncounterEgg2(ushort Species, GameVersion Version) : IEncounterEgg
{
public byte Form => 0;
public string Name => "Egg";
public string LongName => Name;
public const byte Level = 5;
public bool CanHaveVoltTackle => false;
public byte Generation => 2;
public EntityContext Context => EntityContext.Gen2;
public bool IsEgg => true;
public byte LevelMin => Level;
public byte LevelMax => Level;
public bool IsShiny => false;
public ushort Location => 0;
public ushort EggLocation => 0;
public Ball FixedBall => Generation <= 5 ? Ball.Poke : Ball.None;
public Shiny Shiny => Shiny.Random;
public AbilityPermission Ability => AbilityPermission.Any12H;
PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria);
PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr);
public PK2 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted);
public PK2 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, Version);
var rnd = Util.Rand;
var pk = new PK2(language == (int)LanguageID.Japanese)
{
Species = Species,
CurrentLevel = Level,
TID16 = tr.TID16,
// Force Hatch
OriginalTrainerName = tr.OT,
OriginalTrainerFriendship = 120,
DV16 = criteria.IsSpecifiedIVsAll() ? criteria.GetCombinedDVs()
: EncounterUtil.GetRandomDVs(rnd, criteria.Shiny.IsShiny(), criteria.HiddenPowerType)
};
pk.SetNotNicknamed(language);
if (Version == GameVersion.C)
{
// Set met data for Crystal hatch.
pk.MetLocation = Locations.HatchLocationC;
pk.MetLevel = 1;
pk.MetTimeOfDay = rnd.Next(1, 4); // Morning | Day | Night
pk.OriginalTrainerGender = (byte)(tr.Gender & 1);
}
SetEncounterMoves(pk);
pk.HealPP();
return pk;
}
ILearnSource IEncounterEgg.Learn => Learn;
public ILearnSource<PersonalInfo2> Learn => Version is GameVersion.C
? LearnSource2C.Instance
: LearnSource2GS.Instance;
private void SetEncounterMoves(PK2 pk)
{
var learn = Learn.GetLearnset(Species, Form);
var initial = learn.GetBaseEggMoves(LevelMin);
pk.SetMoves(initial);
}
}

View File

@ -83,6 +83,8 @@ public PK2 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language);
var isJapanese = language == (int)LanguageID.Japanese;
var pi = PersonalTable.C[Species];
var rnd = Util.Rand;
var pk = new PK2(isJapanese)
{
Species = Species,
@ -90,15 +92,12 @@ public PK2 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
CurrentLevel = LevelMin,
OriginalTrainerFriendship = pi.BaseFriendship,
DV16 = criteria.IsSpecifiedIVsAll() ? criteria.GetCombinedDVs()
: EncounterUtil.GetRandomDVs(Util.Rand, criteria.Shiny.IsShiny(), criteria.HiddenPowerType),
: EncounterUtil.GetRandomDVs(rnd, criteria.Shiny.IsShiny(), criteria.HiddenPowerType),
Language = language,
OriginalTrainerName = tr.OT,
TID16 = tr.TID16,
};
pk.SetNotNicknamed(language);
if (criteria.Shiny.IsShiny())
pk.SetShiny();
if (Version == GameVersion.C)
{
@ -115,7 +114,7 @@ public PK2 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
if (!IsTreeAvailable(id))
{
// Get a random TID that satisfies this slot.
do { id = (ushort)Util.Rand.Next(); }
do { id = (ushort)rnd.Next(); }
while (!IsTreeAvailable(id));
pk.TID16 = id;
}

View File

@ -1,4 +1,5 @@
using System;
using static PKHeX.Core.RandomCorrelationRating;
namespace PKHeX.Core;
@ -155,7 +156,7 @@ private bool IsMatchPartial(PKM pk)
}
#endregion
public bool IsCompatible(PIDType type, PKM pk) => type is PIDType.CXD or PIDType.CXDAnti;
public RandomCorrelationRating IsCompatible(PIDType type, PKM pk) => type is PIDType.CXD or PIDType.CXDAnti ? Match : Mismatch;
public PIDType GetSuggestedCorrelation() => PIDType.CXD;

View File

@ -1,4 +1,5 @@
using System;
using static PKHeX.Core.RandomCorrelationRating;
namespace PKHeX.Core;
@ -180,11 +181,11 @@ private bool IsMatchLocation(PKM pk)
#endregion
public bool IsCompatible(PIDType type, PKM pk)
public RandomCorrelationRating IsCompatible(PIDType type, PKM pk)
{
if (IsEReader)
return true;
return type is PIDType.CXD;
return Match;
return type is PIDType.CXD ? Match : Mismatch;
}
public PIDType GetSuggestedCorrelation()

View File

@ -1,3 +1,5 @@
using static PKHeX.Core.RandomCorrelationRating;
namespace PKHeX.Core;
/// <summary>
@ -151,7 +153,7 @@ private bool IsMatchPartial(PKM pk)
}
#endregion
public bool IsCompatible(PIDType type, PKM pk) => type is PIDType.CXD_ColoStarter;
public RandomCorrelationRating IsCompatible(PIDType type, PKM pk) => type is PIDType.CXD_ColoStarter ? Match : Mismatch;
public PIDType GetSuggestedCorrelation() => PIDType.CXD_ColoStarter;
}

View File

@ -0,0 +1,129 @@
using System;
using static PKHeX.Core.RandomCorrelationRating;
namespace PKHeX.Core;
public sealed record EncounterEgg3(ushort Species, GameVersion Version) : IEncounterEgg, IRandomCorrelation
{
private byte Location => Version is GameVersion.FR or GameVersion.LG
? Locations.HatchLocationFRLG
: Locations.HatchLocationRSE;
public string Name => "Egg";
public string LongName => Name;
public const byte Level = 5;
public bool CanHaveVoltTackle => Species is (int)Core.Species.Pichu && Version is GameVersion.E;
public byte Form => 0; // No forms in Gen3
public byte Generation => 3;
public EntityContext Context => EntityContext.Gen3;
public bool IsShiny => false;
public byte LevelMin => Level;
public byte LevelMax => Level;
ushort ILocation.EggLocation => 0;
ushort ILocation.Location => Location;
public AbilityPermission Ability => AbilityPermission.Any12;
public Ball FixedBall => Ball.Poke;
public Shiny Shiny => Shiny.Random;
public bool IsEgg => true;
// Generation 3 has PID/IV correlations and RNG abuse; assume none.
public PIDType GetSuggestedCorrelation() => PIDType.None;
public RandomCorrelationRating IsCompatible(PIDType type, PKM pk)
{
if (type is PIDType.None)
return Match;
if (ParseSettings.Settings.FramePattern.EggRandomAnyType3)
return NotIdeal;
return Mismatch;
}
PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria);
PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr);
public PK3 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted);
public PK3 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, Version);
var pk = new PK3
{
Species = Species,
CurrentLevel = Level,
Version = Version,
Ball = (byte)FixedBall,
ID32 = tr.ID32,
OriginalTrainerGender = tr.Gender,
// Force Hatch
Language = language,
OriginalTrainerName = tr.OT,
Nickname = SpeciesName.GetSpeciesNameGeneration(Species, language, Generation),
OriginalTrainerFriendship = 120,
MetLevel = 0,
MetLocation = Location,
};
SetEncounterMoves(pk);
pk.HealPP();
if (criteria.IsSpecifiedIVsAny(out _))
criteria.SetRandomIVs(pk);
else
criteria.SetRandomIVs(pk, 3);
// Get a random PID that matches gender/nature/ability criteria
var pi = PersonalTable.E[Species];
var gr = pi.Gender;
var pid = GetRandomPID(criteria, gr);
pk.PID = pid;
pk.RefreshAbility((int)(pid % 2));
return pk;
}
private uint GetRandomPID(in EncounterCriteria criteria, byte gr)
{
var seed = Util.Rand32();
while (true)
{
// LCRNG is sufficiently random, especially with the nature of vBlanks potentially (super rarely) disjointing rand calls.
seed = LCRNG.Next(seed);
var pid = seed;
var gender = EntityGender.GetFromPIDAndRatio(pid, gr);
if (criteria.IsSpecifiedGender() && !criteria.IsSatisfiedGender(gender))
continue;
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature((Nature)(pid % 25)))
continue;
if (criteria.IsSpecifiedAbility() && !criteria.IsSatisfiedAbility((byte)(pid % 2)))
continue;
// For Nidoran and Volbeat/Illumise, match the bit correlation to be most permissive with move inheritance.
if (Breeding.IsGenderSpeciesDetermination(Species) && !Breeding.IsValidSpeciesBit34(pid, gender))
continue; // 50/50 chance!
if (!Daycare3.IsValidProcPID(pid, Version))
continue; // 0-value PID is invalid
return pid;
}
}
ILearnSource IEncounterEgg.Learn => Learn;
public ILearnSource<PersonalInfo3> Learn => Version switch
{
GameVersion.R or GameVersion.S => LearnSource3RS.Instance,
GameVersion.E => LearnSource3RS.Instance,
GameVersion.FR => LearnSource3FR.Instance,
GameVersion.LG => LearnSource3FR.Instance,
_ => throw new ArgumentOutOfRangeException(nameof(Version), Version, null),
};
private void SetEncounterMoves(PK3 pk)
{
var learn = Learn.GetLearnset(Species, Form);
var initial = learn.GetBaseEggMoves(LevelMin);
pk.SetMoves(initial);
}
}

View File

@ -1,5 +1,6 @@
using static PKHeX.Core.PIDType;
using static PKHeX.Core.SlotType3;
using static PKHeX.Core.RandomCorrelationRating;
namespace PKHeX.Core;
@ -130,11 +131,13 @@ public EncounterMatchRating GetMatchRating(PKM pk)
private bool IsDeferredSafari3(bool IsSafariBall) => IsSafariBall != Locations.IsSafariZoneLocation3(Location);
#endregion
public bool IsCompatible(PIDType type, PKM pk)
public RandomCorrelationRating IsCompatible(PIDType type, PKM pk)
{
if (Species != (int)Core.Species.Unown)
return type is (Method_1 or Method_2 or Method_3 or Method_4);
return type is (Method_1_Unown or Method_2_Unown or Method_3_Unown or Method_4_Unown);
var match = Species != (int)Core.Species.Unown
? type is Method_1 or Method_2 or Method_3 or Method_4
: type is Method_1_Unown or Method_2_Unown or Method_3_Unown or Method_4_Unown;
return match ? Match : Mismatch;
}
public PIDType GetSuggestedCorrelation() => Species == (int)Core.Species.Unown ? Method_1_Unown : Method_1;

View File

@ -1,4 +1,5 @@
using System;
using static PKHeX.Core.RandomCorrelationRating;
namespace PKHeX.Core;
@ -246,22 +247,22 @@ private bool IsMatchPartial(PKM pk)
}
#endregion
public bool IsCompatible(PIDType type, PKM pk)
public RandomCorrelationRating IsCompatible(PIDType type, PKM pk)
{
var version = pk.Version;
if (version is GameVersion.E)
return type is PIDType.Method_1;
return type is PIDType.Method_1 ? Match : Mismatch;
if (IsRoaming) // Glitched IVs
return IsRoamerPIDIV(type, pk);
return IsRoamerPIDIV(type, pk) ? Match : Mismatch;
if (type is PIDType.Method_1)
return true;
return Match;
// RS: Only Method 1, but RSBox s/w emulation can yield Method 4.
if (version is GameVersion.R or GameVersion.S)
return type is PIDType.Method_4;
return type is PIDType.Method_4 ? NotIdeal : Mismatch;
// FR/LG: Only Method 1, but Togepi gift can be Method 4 via PID modulo VBlank abuse
return type is PIDType.Method_4 && Species is (ushort)Core.Species.Togepi;
return type is PIDType.Method_4 && Species is (ushort)Core.Species.Togepi ? NotIdeal : Mismatch;
}
private static bool IsRoamerPIDIV(PIDType val, PKM pk)

View File

@ -2,6 +2,7 @@
using static PKHeX.Core.PIDType;
using static PKHeX.Core.CommonEvent3;
using static PKHeX.Core.CommonEvent3Checker;
using static PKHeX.Core.RandomCorrelationRating;
namespace PKHeX.Core;
@ -420,20 +421,20 @@ public EncounterMatchRating GetMatchRating(PKM pk)
public bool IsTrainerMatch(PKM pk, ReadOnlySpan<char> trainer, int language) => true; // checked in explicit match
public bool IsCompatible(PIDType type, PKM pk) => type == Method;
public RandomCorrelationRating IsCompatible(PIDType type, PKM pk) => type == Method ? Match : Mismatch;
public bool IsCompatibleReviseReset(ref PIDIV value, PKM pk)
public RandomCorrelationRating IsCompatibleReviseReset(ref PIDIV value, PKM pk)
{
var prev = value.Mutated; // if previously revised, use that instead.
var type = prev is 0 ? value.Type : prev;
if (type is BACD_EA or BACD_ES && !IsEgg)
return false;
return Mismatch;
if (OriginalTrainerGender is not (GiftGender3.RandAlgo or GiftGender3.Recipient) && (!IsEgg || pk.IsEgg) && !IsMatchGender(pk, value.OriginSeed))
return false;
return Mismatch;
return Method switch
bool result = Method switch
{
BACD_U => type is BACD,
BACD_R => IsRestrictedSimple(ref value, type),
@ -448,6 +449,10 @@ public bool IsCompatibleReviseReset(ref PIDIV value, PKM pk)
Method_2 => type is Method_2 or (Method_1 or Method_4), // via PID modulo VBlank abuse
_ => false,
};
if (result)
return Match;
return Mismatch;
}
private bool IsMatchGender(PKM pk, uint seed)

View File

@ -1,4 +1,5 @@
using System;
using static PKHeX.Core.RandomCorrelationRating;
namespace PKHeX.Core;
@ -143,22 +144,22 @@ public bool IsMatchExact(PKM pk, EvoCriteria evo)
return true;
}
public bool IsCompatible(PIDType type, PKM pk) => type is Method;
public RandomCorrelationRating IsCompatible(PIDType type, PKM pk) => type is Method ? Match : Mismatch;
public bool IsCompatibleReviseReset(ref PIDIV value, PKM pk)
public RandomCorrelationRating IsCompatibleReviseReset(ref PIDIV value, PKM pk)
{
var prev = value.Mutated; // if previously revised, use that instead.
var type = prev is 0 ? value.Type : prev;
if (type is not PIDType.BACD_AX)
return false;
return Mismatch;
var seed = value.OriginSeed;
var rand5 = LCRNG.Next5(seed) >> 16;
var expect = GetGender(rand5);
if (pk.OriginalTrainerGender != expect)
return false;
return Mismatch;
return true; // Table weight -> gift selection is a separate RNG, nothing to check!
return Match; // Table weight -> gift selection is a separate RNG, nothing to check!
}
private static uint GetGender(uint rand16) => CommonEvent3.GetGenderBit7(rand16);

View File

@ -1,5 +1,6 @@
using System;
using static System.Buffers.Binary.BinaryPrimitives;
using static PKHeX.Core.RandomCorrelationRating;
namespace PKHeX.Core;
@ -158,15 +159,15 @@ internal static EncounterGift3NY[] GetArray(ReadOnlySpan<byte> readOnlySpan)
return result;
}
public bool IsCompatible(PIDType type, PKM pk) => type is Method;
public RandomCorrelationRating IsCompatible(PIDType type, PKM pk) => type is Method ? Match : Mismatch;
public bool IsCompatibleReviseReset(ref PIDIV value, PKM pk)
public RandomCorrelationRating IsCompatibleReviseReset(ref PIDIV value, PKM pk)
{
var prev = value.Mutated; // if previously revised, use that instead.
var type = prev is 0 ? value.Type : prev;
if (type is not PIDType.BACD_AX)
return false;
return Mismatch;
return true; // Table weight -> gift selection is a separate RNG, nothing to check!
return Match; // Table weight -> gift selection is a separate RNG, nothing to check!
}
}

View File

@ -1,4 +1,5 @@
using System;
using static PKHeX.Core.RandomCorrelationRating;
namespace PKHeX.Core;
@ -151,6 +152,7 @@ private bool IsMatchLocation(PKM pk)
#endregion
public bool IsCompatible(PIDType type, PKM pk) => type is PIDType.CXD or PIDType.CXDAnti;
public RandomCorrelationRating IsCompatible(PIDType type, PKM pk) => type is PIDType.CXD or PIDType.CXDAnti ? Match : Mismatch;
public PIDType GetSuggestedCorrelation() => PIDType.CXD;
}

View File

@ -1,3 +1,5 @@
using static PKHeX.Core.RandomCorrelationRating;
namespace PKHeX.Core;
/// <summary>
@ -72,6 +74,6 @@ private void SetPINGA(XK3 pk, EncounterCriteria criteria, PersonalInfo3 pi)
public EncounterMatchRating GetMatchRating(PKM pk) => EncounterMatchRating.Match;
#endregion
public bool IsCompatible(PIDType type, PKM pk) => type == PIDType.PokeSpot;
public RandomCorrelationRating IsCompatible(PIDType type, PKM pk) => type is PIDType.PokeSpot ? Match : Mismatch;
public PIDType GetSuggestedCorrelation() => PIDType.PokeSpot;
}

View File

@ -1,3 +1,5 @@
using static PKHeX.Core.RandomCorrelationRating;
namespace PKHeX.Core;
/// <summary>
@ -138,11 +140,13 @@ private bool IsMatchPartial(PKM pk)
}
#endregion
public bool IsCompatible(PIDType type, PKM pk)
public RandomCorrelationRating IsCompatible(PIDType type, PKM pk)
{
if (type is PIDType.CXD)
return true;
return type is PIDType.CXDAnti && FatefulEncounter;
return Match;
if (type is PIDType.CXDAnti && FatefulEncounter)
return Match;
return Mismatch;
}
public PIDType GetSuggestedCorrelation() => PIDType.CXD;

View File

@ -1,4 +1,5 @@
using System;
using static PKHeX.Core.RandomCorrelationRating;
namespace PKHeX.Core;
@ -156,8 +157,9 @@ private bool IsMatchPartial(PKM pk)
}
#endregion
public bool IsCompatible(PIDType type, PKM pk) => type is PIDType.CXD;
public RandomCorrelationRating IsCompatible(PIDType type, PKM pk) => type is PIDType.CXD ? Match : Mismatch;
public PIDType GetSuggestedCorrelation() => PIDType.CXD;
public bool IsTrainerMatch(PKM pk, ReadOnlySpan<char> trainer, int language)
{
if ((uint)language >= TrainerNames.Length)

View File

@ -0,0 +1,138 @@
using System;
using static PKHeX.Core.RandomCorrelationRating;
namespace PKHeX.Core;
public sealed record EncounterEgg4(ushort Species, GameVersion Version) : IEncounterEgg, IRandomCorrelation
{
private ushort Location => GetHatchLocation(Version);
private static ushort GetHatchLocation(GameVersion version)
{
return version is GameVersion.HG or GameVersion.SS
? Locations.HatchLocationHGSS
: Locations.HatchLocationDPPt;
}
public string Name => "Egg";
public string LongName => Name;
public const byte Level = 1;
public bool CanHaveVoltTackle => Species is (int)Core.Species.Pichu;
public byte Form => 0; // No forms in Gen3
public byte Generation => 4;
public EntityContext Context => EntityContext.Gen4;
public bool IsShiny => false;
public byte LevelMin => Level;
public byte LevelMax => Level;
ushort ILocation.EggLocation => Locations.Daycare4;
ushort ILocation.Location => Location;
public AbilityPermission Ability => AbilityPermission.Any12;
public Ball FixedBall => Ball.Poke;
public Shiny Shiny => Shiny.Random;
public bool IsEgg => true;
// Generation 4 has PID/IV correlations and RNG abuse; assume none.
public PIDType GetSuggestedCorrelation() => PIDType.None;
public RandomCorrelationRating IsCompatible(PIDType type, PKM pk)
{
if (type is PIDType.None)
return Match;
if (ParseSettings.Settings.FramePattern.EggRandomAnyType4)
return NotIdeal;
return Mismatch;
}
PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria);
PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr);
public PK4 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted);
public PK4 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, Version);
var date = EncounterDate.GetDateNDS();
var pk = new PK4
{
Species = Species,
CurrentLevel = Level,
Version = Version,
Ball = (byte)FixedBall,
ID32 = tr.ID32,
OriginalTrainerGender = tr.Gender,
// Force Hatch
Language = language,
OriginalTrainerName = tr.OT,
Nickname = SpeciesName.GetSpeciesNameGeneration(Species, language, Generation),
OriginalTrainerFriendship = 120,
MetLevel = 0,
MetDate = date,
MetLocation = GetHatchLocation(tr.Version),
EggMetDate = date,
EggLocation = tr.Version == Version ? Locations.Daycare4 : Locations.LinkTrade4,
};
SetEncounterMoves(pk);
pk.HealPP();
if (criteria.IsSpecifiedIVsAny(out _))
criteria.SetRandomIVs(pk);
else
criteria.SetRandomIVs(pk, 3);
// Get a random PID that matches gender/nature/ability criteria
var pi = PersonalTable.HGSS[Species];
var gr = pi.Gender;
var pid = GetRandomPID(criteria, gr, out var gender);
pk.PID = pid;
pk.Gender = gender;
pk.RefreshAbility((int)(pid & 1));
return pk;
}
private uint GetRandomPID(in EncounterCriteria criteria, byte gr, out byte gender)
{
var seed = Util.Rand32();
while (true)
{
seed = LCRNG.Next(seed);
var pid = seed;
gender = EntityGender.GetFromPIDAndRatio(pid, gr);
if (criteria.IsSpecifiedGender() && !criteria.IsSatisfiedGender(gender))
continue;
if (criteria.IsSpecifiedNature() && !criteria.IsSatisfiedNature((Nature)(pid % 25)))
continue;
if (criteria.IsSpecifiedAbility() && !criteria.IsSatisfiedAbility((byte)(pid % 2)))
continue;
// For Nidoran and Volbeat/Illumise, match the bit correlation to be most permissive with move inheritance.
if (Breeding.IsGenderSpeciesDetermination(Species) && !Breeding.IsValidSpeciesBit34(pid, gender))
continue; // 50/50 chance!
// A 0-value PID is possible via Masuda Method even though a 0-value saved indicates "no egg available".
// PID is rolled forward upon picking up the egg.
// Not worth skipping 0-value PIDs. Too rare to be worth trying again, since it can be a valid PID.
return pid;
}
}
ILearnSource IEncounterEgg.Learn => Learn;
public ILearnSource<PersonalInfo4> Learn => Version switch
{
GameVersion.D or GameVersion.P => LearnSource4DP.Instance,
GameVersion.Pt => LearnSource4DP.Instance,
GameVersion.HG or GameVersion.SS => LearnSource4HGSS.Instance,
_ => throw new ArgumentOutOfRangeException(nameof(Version), Version, null),
};
private void SetEncounterMoves(PK4 pk)
{
var learn = Learn.GetLearnset(Species, Form);
var initial = learn.GetBaseEggMoves(LevelMin);
pk.SetMoves(initial);
}
}

View File

@ -1,4 +1,5 @@
using static PKHeX.Core.SlotType4;
using static PKHeX.Core.RandomCorrelationRating;
namespace PKHeX.Core;
@ -173,17 +174,17 @@ public EncounterMatchRating GetMatchRating(PKM pk)
private bool IsDeferredWurmple(PKM pk) => Species == (int)Core.Species.Wurmple && pk.Species != (int)Core.Species.Wurmple && !WurmpleUtil.IsWurmpleEvoValid(pk);
#endregion
public bool IsCompatible(PIDType type, PKM pk)
public RandomCorrelationRating IsCompatible(PIDType type, PKM pk)
{
if (type is PIDType.Method_1)
return true;
return Match;
// Chain shiny with Poké Radar is only possible in D/P/Pt, in grass.
// Safari Zone does not allow using the Poké Radar
if (type is PIDType.ChainShiny)
return pk.IsShiny && CanUseRadar;
return pk.IsShiny && CanUseRadar ? Match : Mismatch;
if (type is PIDType.CuteCharm)
return CuteCharm4.IsValid(this, pk);
return false;
return CuteCharm4.IsValid(this, pk) ? Match : Mismatch;
return Mismatch;
}
public PIDType GetSuggestedCorrelation() => PIDType.Method_1;

View File

@ -1,6 +1,7 @@
using System;
using System.Diagnostics.CodeAnalysis;
using static PKHeX.Core.GroundTileAllowed;
using static PKHeX.Core.RandomCorrelationRating;
namespace PKHeX.Core;
@ -383,17 +384,17 @@ public static bool IsMatchRoamerLocation([ConstantExpected] uint permit, ushort
#endregion
public bool IsCompatible(PIDType type, PKM pk)
public RandomCorrelationRating IsCompatible(PIDType type, PKM pk)
{
if (Species == (int)Core.Species.Pichu)
return type == PIDType.Pokewalker;
return type is PIDType.Pokewalker ? Match : Mismatch;
if (Shiny == Shiny.Always)
return type == PIDType.ChainShiny;
return type is PIDType.ChainShiny ? Match : Mismatch;
if (type is PIDType.Method_1)
return true;
return Match;
if (type is PIDType.CuteCharm)
return CuteCharm4.IsValid(this, pk);
return false;
return CuteCharm4.IsValid(this, pk) ? Match : Mismatch;
return Mismatch;
}
public PIDType GetSuggestedCorrelation()

View File

@ -1,5 +1,6 @@
using System;
using static System.Buffers.Binary.BinaryPrimitives;
using static PKHeX.Core.RandomCorrelationRating;
namespace PKHeX.Core;
@ -194,15 +195,15 @@ public EncounterMatchRating GetMatchRating(PKM pk)
private static bool IsMatchPartial(PKM pk) => pk.Ball != (byte)Ball.Poke || !IsMatchSeed(pk);
#endregion
public bool IsCompatible(PIDType type, PKM pk)
public RandomCorrelationRating IsCompatible(PIDType type, PKM pk)
{
if (type is PIDType.Pokewalker)
return true;
return Match;
// Pokewalker can sometimes be confused with CuteCharm due to the PID creation routine. Double check if it is okay.
if (type is PIDType.CuteCharm)
return CuteCharm4.IsCuteCharm(pk, pk.EncryptionConstant) && CuteCharm4.IsValid(this, pk);
return false;
return CuteCharm4.IsCuteCharm(pk, pk.EncryptionConstant) && CuteCharm4.IsValid(this, pk) ? Match : Mismatch;
return Mismatch;
}
public PIDType GetSuggestedCorrelation() => PIDType.Pokewalker;

View File

@ -0,0 +1,112 @@
using System;
using static PKHeX.Core.RandomCorrelationRating;
namespace PKHeX.Core;
public sealed record EncounterEgg5(ushort Species, GameVersion Version) : IEncounterEgg, IRandomCorrelation
{
private const ushort Location = Locations.HatchLocation5;
public string Name => "Egg";
public string LongName => Name;
public const byte Level = 1;
public bool CanHaveVoltTackle => Species is (int)Core.Species.Pichu;
public byte Form => 0;
public byte Generation => 5;
public EntityContext Context => EntityContext.Gen5;
public bool IsShiny => false;
public byte LevelMin => Level;
public byte LevelMax => Level;
ushort ILocation.EggLocation => Locations.Daycare5;
ushort ILocation.Location => Location;
public AbilityPermission Ability => AbilityBreedLegality.IsHiddenPossible5(Species) ? AbilityPermission.Any12H : AbilityPermission.Any12;
public Ball FixedBall => Ball.Poke;
public Shiny Shiny => Shiny.Random;
public bool IsEgg => true;
// Generation 5 has PID/IV correlations and RNG abuse; assume none.
public RandomCorrelationRating IsCompatible(PIDType type, PKM pk) => type is PIDType.None ? Match : Mismatch;
public PIDType GetSuggestedCorrelation() => PIDType.None;
PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria);
PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr);
public PK5 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted);
public PK5 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, Version);
var date = EncounterDate.GetDateNDS();
var pk = new PK5
{
Species = Species,
CurrentLevel = Level,
Version = Version,
Ball = (byte)FixedBall,
ID32 = tr.ID32,
OriginalTrainerGender = tr.Gender,
// Force Hatch
Language = language,
OriginalTrainerName = tr.OT,
Nickname = SpeciesName.GetSpeciesNameGeneration(Species, language, Generation),
OriginalTrainerFriendship = 120,
MetLevel = 1,
MetDate = date,
MetLocation = Location,
EggMetDate = date,
EggLocation = tr.Version == Version ? Locations.Daycare5 : Locations.LinkTrade5,
Nature = criteria.GetNature(),
};
SetEncounterMoves(pk);
pk.HealPP();
if (criteria.IsSpecifiedIVsAny(out _))
criteria.SetRandomIVs(pk);
else
criteria.SetRandomIVs(pk, 3);
// Get a random PID that matches gender/nature/ability criteria
var pi = PersonalTable.B2W2[Species];
var gr = pi.Gender;
var ability = criteria.GetAbilityFromNumber(Ability);
var pid = GetRandomPID(criteria, gr, out var gender);
pid = pid & 0xFFFEFFFF | (uint)(ability & 1) << 16; // 0x00000000 or 0x00010000
pk.PID = pid;
pk.Gender = gender;
pk.RefreshAbility(ability);
return pk;
}
private static uint GetRandomPID(in EncounterCriteria criteria, byte gr, out byte gender)
{
var seed = Util.Rand32();
while (true)
{
seed = LCRNG.Next(seed);
var pid = seed;
gender = EntityGender.GetFromPIDAndRatio(pid, gr);
if (criteria.IsSpecifiedGender() && !criteria.IsSatisfiedGender(gender))
continue;
return pid;
}
}
public ILearnSource Learn => Version switch
{
GameVersion.B or GameVersion.W => LearnSource5BW.Instance,
GameVersion.B2 or GameVersion.W2 => LearnSource5B2W2.Instance,
_ => throw new ArgumentOutOfRangeException(nameof(Version), Version, null),
};
private void SetEncounterMoves(PK5 pk)
{
var learn = Learn.GetLearnset(Species, Form);
var initial = learn.GetBaseEggMoves(LevelMin);
pk.SetMoves(initial);
}
}

View File

@ -0,0 +1,107 @@
using System;
namespace PKHeX.Core;
public sealed record EncounterEgg6(ushort Species, byte Form, GameVersion Version) : IEncounterEgg
{
private ushort Location => Version is GameVersion.AS or GameVersion.OR
? Locations.HatchLocation6AO
: Locations.HatchLocation6XY;
public string Name => "Egg";
public string LongName => Name;
public const byte Level = 1;
public bool CanHaveVoltTackle => Species is (int)Core.Species.Pichu;
public byte Generation => 6;
public EntityContext Context => EntityContext.Gen6;
public bool IsShiny => false;
public byte LevelMin => Level;
public byte LevelMax => Level;
ushort ILocation.EggLocation => Locations.Daycare5;
ushort ILocation.Location => Location;
public AbilityPermission Ability => AbilityBreedLegality.IsHiddenPossible6(Species, Form) ? AbilityPermission.Any12H : AbilityPermission.Any12;
public Ball FixedBall => Ball.None; // Inheritance allowed.
public Shiny Shiny => Shiny.Random;
public bool IsEgg => true;
PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria);
PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr);
public PK6 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted);
public PK6 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, Version);
var date = EncounterDate.GetDate3DS();
var pi = PersonalTable.AO[Species, Form];
var rnd = Util.Rand;
var geo = tr.GetRegionOrigin(language);
var pk = new PK6
{
Species = Species,
CurrentLevel = Level,
Version = Version,
Ball = (byte)Ball.Poke,
ID32 = tr.ID32,
OriginalTrainerGender = tr.Gender,
// Force Hatch
Language = language,
Nickname = SpeciesName.GetSpeciesNameGeneration(Species, language, Generation),
OriginalTrainerName = tr.OT,
OriginalTrainerFriendship = 120,
MetLevel = 1,
MetDate = date,
MetLocation = Location,
EggMetDate = date,
EggLocation = tr.Version == Version ? Locations.Daycare5 : Locations.LinkTrade6,
EncryptionConstant = rnd.Rand32(),
PID = rnd.Rand32(),
Nature = criteria.GetNature(),
Gender = criteria.GetGender(pi),
ConsoleRegion = geo.ConsoleRegion,
Country = geo.Country,
Region = geo.Region,
};
pk.StatNature = pk.Nature;
pk.SetHatchMemory6();
if (Species is (int)Core.Species.Scatterbug)
pk.Form = Vivillon3DS.GetPattern(pk.Country, pk.Region);
SetEncounterMoves(pk);
pk.HealPP();
pk.RelearnMove1 = pk.Move1;
pk.RelearnMove2 = pk.Move2;
pk.RelearnMove3 = pk.Move3;
pk.RelearnMove4 = pk.Move4;
if (criteria.IsSpecifiedIVsAny(out _))
criteria.SetRandomIVs(pk);
else
criteria.SetRandomIVs(pk, 3);
var ability = criteria.GetAbilityFromNumber(Ability);
pk.RefreshAbility(ability);
return pk;
}
public ILearnSource Learn => Version switch
{
GameVersion.X or GameVersion.Y => LearnSource6XY.Instance,
GameVersion.AS or GameVersion.OR => LearnSource6AO.Instance,
_ => throw new ArgumentOutOfRangeException(nameof(Version), Version, null),
};
private void SetEncounterMoves(PK6 pk)
{
var learn = Learn.GetLearnset(Species, Form);
var initial = learn.GetBaseEggMoves(LevelMin);
pk.SetMoves(initial);
}
}

View File

@ -0,0 +1,104 @@
using System;
namespace PKHeX.Core;
public sealed record EncounterEgg7(ushort Species, byte Form, GameVersion Version) : IEncounterEgg
{
private const ushort Location = Locations.HatchLocation7;
public string Name => "Egg";
public string LongName => Name;
public const byte Level = 1;
public bool CanHaveVoltTackle => Species is (int)Core.Species.Pichu;
public byte Generation => 7;
public EntityContext Context => EntityContext.Gen7;
public bool IsShiny => false;
public byte LevelMin => Level;
public byte LevelMax => Level;
ushort ILocation.EggLocation => Locations.Daycare5;
ushort ILocation.Location => Location;
public AbilityPermission Ability => AbilityBreedLegality.IsHiddenPossible7(Species, Form) ? AbilityPermission.Any12H : AbilityPermission.Any12;
public Ball FixedBall => Ball.None; // Inheritance allowed.
public Shiny Shiny => Shiny.Random;
public bool IsEgg => true;
PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria);
PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr);
public PK7 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted);
public PK7 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, Version);
var date = EncounterDate.GetDate3DS();
var pi = PersonalTable.USUM[Species, Form];
var rnd = Util.Rand;
var geo = tr.GetRegionOrigin(language);
var pk = new PK7
{
Species = Species,
CurrentLevel = Level,
Version = Version,
Ball = (byte)Ball.Poke,
ID32 = tr.ID32,
OriginalTrainerGender = tr.Gender,
// Force Hatch
Language = language,
Nickname = SpeciesName.GetSpeciesNameGeneration(Species, language, Generation),
OriginalTrainerName = tr.OT,
OriginalTrainerFriendship = 120,
MetLevel = 1,
MetDate = date,
MetLocation = Location,
EggMetDate = date,
EggLocation = tr.Version == Version ? Locations.Daycare5 : Locations.LinkTrade6,
EncryptionConstant = rnd.Rand32(),
PID = rnd.Rand32(),
Nature = criteria.GetNature(),
Gender = criteria.GetGender(pi),
ConsoleRegion = geo.ConsoleRegion,
Country = geo.Country,
Region = geo.Region,
};
pk.StatNature = pk.Nature;
if (Species is (int)Core.Species.Scatterbug)
pk.Form = Vivillon3DS.GetPattern(pk.Country, pk.Region);
SetEncounterMoves(pk);
pk.HealPP();
pk.RelearnMove1 = pk.Move1;
pk.RelearnMove2 = pk.Move2;
pk.RelearnMove3 = pk.Move3;
pk.RelearnMove4 = pk.Move4;
if (criteria.IsSpecifiedIVsAny(out _))
criteria.SetRandomIVs(pk);
else
criteria.SetRandomIVs(pk, 3);
var ability = criteria.GetAbilityFromNumber(Ability);
pk.RefreshAbility(ability);
return pk;
}
public ILearnSource Learn => Version switch
{
GameVersion.SN or GameVersion.MN => LearnSource7SM.Instance,
GameVersion.US or GameVersion.UM => LearnSource7USUM.Instance,
_ => throw new ArgumentOutOfRangeException(nameof(Version), Version, null),
};
private void SetEncounterMoves(PK7 pk)
{
var learn = Learn.GetLearnset(Species, Form);
var initial = learn.GetBaseEggMoves(LevelMin);
pk.SetMoves(initial);
}
}

View File

@ -0,0 +1,93 @@
namespace PKHeX.Core;
public sealed record EncounterEgg8(ushort Species, byte Form, GameVersion Version) : IEncounterEgg
{
private const ushort Location = Locations.HatchLocation8;
public string Name => "Egg";
public string LongName => Name;
public const byte Level = 1;
public bool CanHaveVoltTackle => Species is (int)Core.Species.Pichu;
public byte Generation => 8;
public EntityContext Context => EntityContext.Gen8;
public bool IsShiny => false;
public byte LevelMin => Level;
public byte LevelMax => Level;
ushort ILocation.EggLocation => Locations.Daycare5;
ushort ILocation.Location => Location;
public AbilityPermission Ability => AbilityBreedLegality.IsHiddenPossibleHOME(Species) ? AbilityPermission.Any12H : AbilityPermission.Any12;
public Ball FixedBall => Ball.None; // Inheritance allowed.
public Shiny Shiny => Shiny.Random;
public bool IsEgg => true;
PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria);
PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr);
public PK8 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted);
public PK8 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, Version);
var date = EncounterDate.GetDateSwitch();
var pi = PersonalTable.SWSH[Species, Form];
var rnd = Util.Rand;
var pk = new PK8
{
Species = Species,
CurrentLevel = Level,
Version = Version,
Ball = (byte)Ball.Poke,
ID32 = tr.ID32,
OriginalTrainerGender = tr.Gender,
// Force Hatch
Language = language,
Nickname = SpeciesName.GetSpeciesNameGeneration(Species, language, Generation),
OriginalTrainerName = tr.OT,
OriginalTrainerFriendship = 100, // previously 120 in Gen2-7
MetLevel = 1,
MetDate = date,
MetLocation = Location,
EggMetDate = date,
EggLocation = tr.Version == Version ? Locations.Daycare5 : Locations.LinkTrade6,
EncryptionConstant = rnd.Rand32(),
PID = rnd.Rand32(),
Nature = criteria.GetNature(),
Gender = criteria.GetGender(pi),
};
pk.StatNature = pk.Nature;
SetEncounterMoves(pk);
pk.HealPP();
pk.RelearnMove1 = pk.Move1;
pk.RelearnMove2 = pk.Move2;
pk.RelearnMove3 = pk.Move3;
pk.RelearnMove4 = pk.Move4;
if (criteria.IsSpecifiedIVsAny(out _))
criteria.SetRandomIVs(pk);
else
criteria.SetRandomIVs(pk, 3);
pk.HeightScalar = PokeSizeUtil.GetRandomScalar(rnd);
pk.WeightScalar = PokeSizeUtil.GetRandomScalar(rnd);
var ability = criteria.GetAbilityFromNumber(Ability);
pk.RefreshAbility(ability);
return pk;
}
ILearnSource IEncounterEgg.Learn => Learn;
public ILearnSource<PersonalInfo8SWSH> Learn => LearnSource8SWSH.Instance;
private void SetEncounterMoves(PK8 pk)
{
var learn = Learn.GetLearnset(Species, Form);
var initial = learn.GetBaseEggMoves(LevelMin);
pk.SetMoves(initial);
}
}

View File

@ -0,0 +1,95 @@
namespace PKHeX.Core;
public sealed record EncounterEgg8b(ushort Species, byte Form, GameVersion Version) : IEncounterEgg
{
private const ushort Location = Locations.HatchLocation8b;
public string Name => "Egg";
public string LongName => Name;
public const byte Level = 1;
public bool CanHaveVoltTackle => Species is (int)Core.Species.Pichu;
public byte Generation => 8;
public EntityContext Context => EntityContext.Gen8b;
public bool IsShiny => false;
public byte LevelMin => Level;
public byte LevelMax => Level;
ushort ILocation.EggLocation => Locations.Daycare8b;
ushort ILocation.Location => Location;
public AbilityPermission Ability => AbilityBreedLegality.IsHiddenPossibleHOME(Species) ? AbilityPermission.Any12H : AbilityPermission.Any12;
public Ball FixedBall => Ball.None; // Inheritance allowed.
public Shiny Shiny => Shiny.Random;
public bool IsEgg => true;
PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria);
PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr);
public PB8 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted);
public PB8 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, Version);
var date = EncounterDate.GetDateSwitch();
var pi = PersonalTable.BDSP[Species, Form];
var rnd = Util.Rand;
var pk = new PB8
{
Species = Species,
CurrentLevel = Level,
Version = Version,
Ball = (byte)Ball.Poke,
TID16 = tr.TID16,
SID16 = tr.SID16,
OriginalTrainerGender = tr.Gender,
// Force Hatch
Language = language,
OriginalTrainerName = tr.OT,
Nickname = SpeciesName.GetSpeciesNameGeneration(Species, language, Generation),
OriginalTrainerFriendship = 100,
MetLevel = 1,
MetLocation = Location,
EggLocation = tr.Version == Version ? Locations.Daycare8b : Locations.LinkTrade6NPC,
MetDate = date,
EggMetDate = date,
EncryptionConstant = rnd.Rand32(),
PID = rnd.Rand32(),
Nature = criteria.GetNature(),
Gender = criteria.GetGender(pi),
};
pk.StatNature = pk.Nature;
SetEncounterMoves(pk);
pk.HealPP();
pk.RelearnMove1 = pk.Move1;
pk.RelearnMove2 = pk.Move2;
pk.RelearnMove3 = pk.Move3;
pk.RelearnMove4 = pk.Move4;
if (criteria.IsSpecifiedIVsAny(out _))
criteria.SetRandomIVs(pk);
else
criteria.SetRandomIVs(pk, 3);
pk.HeightScalar = PokeSizeUtil.GetRandomScalar(rnd);
pk.WeightScalar = PokeSizeUtil.GetRandomScalar(rnd);
var ability = criteria.GetAbilityFromNumber(Ability);
pk.RefreshAbility(ability);
return pk;
}
ILearnSource IEncounterEgg.Learn => Learn;
public ILearnSource<PersonalInfo8BDSP> Learn => LearnSource8BDSP.Instance;
private void SetEncounterMoves(PB8 pk)
{
var learn = Learn.GetLearnset(Species, Form);
var initial = learn.GetBaseEggMoves(LevelMin);
pk.SetMoves(initial);
}
}

View File

@ -0,0 +1,96 @@
namespace PKHeX.Core;
public sealed record EncounterEgg9(ushort Species, byte Form, GameVersion Version) : IEncounterEgg
{
private const ushort Location = Locations.HatchLocation9;
public string Name => "Egg";
public string LongName => Name;
public const byte Level = 1;
public bool CanHaveVoltTackle => Species is (int)Core.Species.Pichu;
public byte Generation => 9;
public EntityContext Context => EntityContext.Gen9;
public bool IsShiny => false;
public byte LevelMin => Level;
public byte LevelMax => Level;
ushort ILocation.EggLocation => Locations.Picnic9;
ushort ILocation.Location => Location;
public AbilityPermission Ability => AbilityBreedLegality.IsHiddenPossibleHOME(Species) ? AbilityPermission.Any12H : AbilityPermission.Any12;
public Ball FixedBall => Ball.None; // Inheritance allowed.
public Shiny Shiny => Shiny.Random;
public bool IsEgg => true;
PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria) => ConvertToPKM(tr, criteria);
PKM IEncounterConvertible.ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr);
public PK9 ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted);
public PK9 ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, Version);
var date = EncounterDate.GetDateSwitch();
var pi = PersonalTable.SV[Species, Form];
var rnd = Util.Rand;
var pk = new PK9
{
Species = Species,
CurrentLevel = Level,
Version = Version,
Ball = (byte)Ball.Poke,
ID32 = tr.ID32,
OriginalTrainerGender = tr.Gender,
// Force Hatch
Language = language,
Nickname = SpeciesName.GetSpeciesNameGeneration(Species, language, Generation),
OriginalTrainerName = tr.OT,
OriginalTrainerFriendship = 100,
MetLevel = 1,
MetDate = date,
MetLocation = Location,
EggMetDate = date,
EggLocation = tr.Version == Version ? Locations.Daycare5 : Locations.LinkTrade6,
EncryptionConstant = rnd.Rand32(),
PID = rnd.Rand32(),
Nature = criteria.GetNature(),
Gender = criteria.GetGender(pi),
};
pk.StatNature = pk.Nature;
SetEncounterMoves(pk);
pk.HealPP();
pk.RelearnMove1 = pk.Move1;
pk.RelearnMove2 = pk.Move2;
pk.RelearnMove3 = pk.Move3;
pk.RelearnMove4 = pk.Move4;
if (criteria.IsSpecifiedIVsAny(out _))
criteria.SetRandomIVs(pk);
else
criteria.SetRandomIVs(pk, 3);
pk.HeightScalar = PokeSizeUtil.GetRandomScalar(rnd);
pk.WeightScalar = PokeSizeUtil.GetRandomScalar(rnd);
pk.Scale = PokeSizeUtil.GetRandomScalar(rnd);
var type = Tera9RNG.GetTeraTypeFromPersonal(Species, Form, rnd.Rand64());
pk.TeraTypeOriginal = (MoveType)type;
var ability = criteria.GetAbilityFromNumber(Ability);
pk.RefreshAbility(ability);
return pk;
}
ILearnSource IEncounterEgg.Learn => Learn;
public ILearnSource<PersonalInfo9SV> Learn => LearnSource9SV.Instance;
private void SetEncounterMoves(PK9 pk)
{
var learn = Learn.GetLearnset(Species, Form);
var initial = learn.GetBaseEggMoves(LevelMin);
pk.SetMoves(initial);
}
}

View File

@ -0,0 +1,10 @@
namespace PKHeX.Core;
/// <summary>
/// Breeding Egg Encounter Properties.
/// </summary>
public interface IEncounterEgg : IEncounterable
{
ILearnSource Learn { get; }
bool CanHaveVoltTackle { get; }
}

View File

@ -11,7 +11,7 @@ public interface IRandomCorrelation
/// <param name="type">Observed <see cref="PIDType"/> of the <see cref="pk"/></param>
/// <param name="pk">Entity to compare against for other details</param>
/// <returns>True if all details are compatible</returns>
bool IsCompatible(PIDType type, PKM pk);
RandomCorrelationRating IsCompatible(PIDType type, PKM pk);
/// <summary>
/// Gets the suggested <see cref="PIDType"/> for the encounter.
@ -30,5 +30,16 @@ public interface IRandomCorrelationEvent3 : IRandomCorrelation
/// <param name="value">Value to check and mutate</param>
/// <param name="pk">Entity to compare against</param>
/// <returns>True if Compatible. Revision of the ref value is not returned.</returns>
bool IsCompatibleReviseReset(ref PIDIV value, PKM pk);
RandomCorrelationRating IsCompatibleReviseReset(ref PIDIV value, PKM pk);
}
public enum RandomCorrelationRating
{
// Clear match, no issues.
Match,
// Weak match, could be better matched to another encounter.
NotIdeal,
// Invalid
Mismatch,
}

View File

@ -1,179 +0,0 @@
using System;
namespace PKHeX.Core;
/// <summary>
/// Egg Encounter Data
/// </summary>
public sealed record EncounterEgg(ushort Species, byte Form, byte Level, byte Generation, GameVersion Version, EntityContext Context) : IEncounterable
{
public string Name => "Egg";
public string LongName => "Egg";
public bool IsEgg => true;
public byte LevelMin => Level;
public byte LevelMax => Level;
public bool IsShiny => false;
public ushort Location => 0;
public ushort EggLocation => Locations.GetDaycareLocation(Generation, Version);
public Ball FixedBall => Generation <= 5 ? Ball.Poke : Ball.None;
public Shiny Shiny => Shiny.Random;
public AbilityPermission Ability => AbilityPermission.Any12H;
public bool CanHaveVoltTackle => Species is (int)Core.Species.Pichu && (Generation > 3 || Version is GameVersion.E);
public PKM ConvertToPKM(ITrainerInfo tr) => ConvertToPKM(tr, EncounterCriteria.Unrestricted);
public PKM ConvertToPKM(ITrainerInfo tr, EncounterCriteria criteria)
{
var generation = Generation;
var version = Version;
var pk = EntityBlank.GetBlank(generation, version);
tr.ApplyTo(pk);
int language = (int)Language.GetSafeLanguage(Generation, (LanguageID)tr.Language, version);
pk.Species = Species;
pk.Form = Form;
pk.Language = language;
pk.Nickname = SpeciesName.GetSpeciesNameGeneration(Species, language, generation);
pk.CurrentLevel = Level;
pk.Version = version;
var ball = FixedBall;
pk.Ball = ball is Ball.None ? (byte)Ball.Poke : (byte)ball;
pk.OriginalTrainerFriendship = EggStateLegality.GetEggHatchFriendship(Context);
SetEncounterMoves(pk, version);
pk.HealPP();
var rnd = Util.Rand;
SetPINGA(pk, criteria);
if (generation <= 2)
{
var pk2 = (PK2)pk;
if (version == GameVersion.C)
{
// Set met data for Crystal hatch.
pk2.MetLocation = Locations.HatchLocationC;
pk2.MetLevel = 1;
pk2.MetTimeOfDay = rnd.Next(1, 4); // Morning | Day | Night
}
else // G/S
{
// G/S can't set any data for Trainer Gender.
pk2.OriginalTrainerGender = 0;
}
// No other revisions needed.
return pk2;
}
SetMetData(pk);
if (generation >= 4)
pk.SetEggMetData(version, tr.Version);
if (generation < 6)
return pk;
if (pk is PK6 pk6)
pk6.SetHatchMemory6();
SetForm(pk, tr);
pk.SetRandomEC();
pk.RelearnMove1 = pk.Move1;
pk.RelearnMove2 = pk.Move2;
pk.RelearnMove3 = pk.Move3;
pk.RelearnMove4 = pk.Move4;
if (pk is IScaledSize s)
{
s.HeightScalar = PokeSizeUtil.GetRandomScalar(rnd);
s.WeightScalar = PokeSizeUtil.GetRandomScalar(rnd);
if (pk is IScaledSize3 s3)
s3.Scale = PokeSizeUtil.GetRandomScalar(rnd);
}
if (pk is ITeraType tera)
{
var type = Tera9RNG.GetTeraTypeFromPersonal(Species, Form, rnd.Rand64());
tera.TeraTypeOriginal = (MoveType)type;
}
return pk;
}
private void SetForm(PKM pk, ITrainerInfo sav)
{
switch (Species)
{
case (int)Core.Species.Minior:
pk.Form = (byte)Util.Rand.Next(7, 14);
break;
case (int)Core.Species.Scatterbug or (int)Core.Species.Spewpa or (int)Core.Species.Vivillon:
if (sav.Generation is 6 or 7 && sav is IRegionOriginReadOnly o)
pk.Form = Vivillon3DS.GetPattern(o.Country, o.Region);
// else keep original value
break;
}
}
private static void SetPINGA(PKM pk, EncounterCriteria criteria)
{
if (pk is PK2 pk2)
{
pk2.DV16 = criteria.IsSpecifiedIVsAll()
? criteria.GetCombinedDVs()
: EncounterUtil.GetRandomDVs(Util.Rand, criteria.Shiny.IsShiny(), criteria.HiddenPowerType);
return;
}
if (criteria.IsSpecifiedIVsAny(out _))
criteria.SetRandomIVs(pk);
else
criteria.SetRandomIVs(pk, 3);
var gender = criteria.GetGender(pk.PersonalInfo);
var nature = criteria.GetNature();
if (pk.Format <= 5)
{
pk.SetPIDGender(gender);
pk.Gender = gender;
pk.SetPIDNature(nature);
if (pk.Format is 3 or 4 && Breeding.IsGenderSpeciesDetermination(pk.Species))
{
while (!Breeding.IsValidSpeciesBit34(pk.EncryptionConstant, gender))
{
// Try again.
pk.SetPIDGender(gender);
pk.SetPIDNature(nature);
}
}
pk.RefreshAbility(pk.PIDAbility);
}
else
{
pk.PID = Util.Rand32();
pk.Nature = pk.StatNature = nature;
pk.Gender = gender;
pk.RefreshAbility(Util.Rand.Next(2));
}
}
private void SetMetData(PKM pk)
{
pk.MetLevel = EggStateLegality.GetEggLevelMet(Version, Generation);
pk.MetLocation = Math.Max((ushort)0, EggStateLegality.GetEggHatchLocation(Version, Generation));
if (pk is IObedienceLevel l)
l.ObedienceLevel = pk.MetLevel;
}
private void SetEncounterMoves(PKM pk, GameVersion version)
{
var ls = GameData.GetLearnSource(version);
var learn = ls.GetLearnset(Species, Form);
var initial = learn.GetBaseEggMoves(LevelMin);
pk.SetMoves(initial);
}
}

View File

@ -21,13 +21,13 @@ public static class EncounterVerifier
private static CheckResult VerifyEncounter(PKM pk, IEncounterTemplate enc) => enc switch
{
EncounterEgg e => VerifyEncounterEgg(pk, e.Generation),
EncounterShadow3Colo { IsEReader: true } when pk.Language != (int)LanguageID.Japanese => GetInvalid(LG3EReader),
EncounterStatic3 { Species: (int)Species.Mew } when pk.Language != (int)LanguageID.Japanese => GetInvalid(LEncUnreleasedEMewJP),
EncounterStatic3 { Species: (int)Species.Deoxys, Location: 200 } when pk.Language == (int)LanguageID.Japanese => GetInvalid(LEncUnreleased),
EncounterStatic4 { IsRoaming: true } when pk is G4PKM { MetLocation: 193, GroundTile: GroundTileType.Water } => GetInvalid(LG4InvalidTileR45Surf),
MysteryGift g => VerifyEncounterEvent(pk, g),
{ IsEgg: true } when !pk.IsEgg => VerifyEncounterEgg(pk, enc.Generation),
IEncounterEgg e when pk.IsEgg => VerifyEncounterEggUnhatched(pk, e.Context),
{ IsEgg: true } when !pk.IsEgg => VerifyEncounterEggHatched(pk, enc.Context),
EncounterInvalid => GetInvalid(LEncInvalid),
_ => GetValid(string.Empty), // todo: refactor
};
@ -35,7 +35,7 @@ public static class EncounterVerifier
private static CheckResult VerifyEncounterG12(PKM pk, IEncounterTemplate enc)
{
if (enc.IsEgg)
return VerifyEncounterEgg(pk, 2);
return pk.IsEgg ? VerifyUnhatchedEgg2(pk) : VerifyEncounterEgg2(pk);
return enc switch
{
@ -57,17 +57,31 @@ private static CheckResult VerifyEncounterG12(PKM pk, IEncounterTemplate enc)
};
// Eggs
private static CheckResult VerifyEncounterEgg(PKM pk, byte generation) => generation switch
private static CheckResult VerifyEncounterEggUnhatched(PKM pk, EntityContext context) => context switch
{
2 => pk.IsEgg ? VerifyUnhatchedEgg2(pk) : VerifyEncounterEgg2(pk),
3 => pk.IsEgg ? VerifyUnhatchedEgg3(pk) : VerifyEncounterEgg3(pk),
4 => pk.IsEgg ? VerifyUnhatchedEgg(pk, Locations.LinkTrade4) : VerifyEncounterEgg4(pk),
5 => pk.IsEgg ? VerifyUnhatchedEgg(pk, Locations.LinkTrade5) : VerifyEncounterEgg5(pk),
6 => pk.IsEgg ? VerifyUnhatchedEgg(pk, Locations.LinkTrade6) : VerifyEncounterEgg6(pk),
7 => pk.IsEgg ? VerifyUnhatchedEgg(pk, Locations.LinkTrade6) : VerifyEncounterEgg7(pk),
8 when GameVersion.BDSP.Contains(pk.Version) => pk.IsEgg ? VerifyUnhatchedEgg(pk, Locations.LinkTrade6NPC, Locations.Default8bNone) : VerifyEncounterEgg8BDSP(pk),
8 => pk.IsEgg ? VerifyUnhatchedEgg(pk, Locations.LinkTrade6) : VerifyEncounterEgg8(pk),
9 => pk.IsEgg ? VerifyUnhatchedEgg(pk, Locations.LinkTrade6) : VerifyEncounterEgg9(pk),
EntityContext.Gen2 => VerifyUnhatchedEgg2(pk),
EntityContext.Gen3 => VerifyUnhatchedEgg3(pk),
EntityContext.Gen4 => VerifyUnhatchedEgg(pk, Locations.LinkTrade4),
EntityContext.Gen5 => VerifyUnhatchedEgg(pk, Locations.LinkTrade5),
EntityContext.Gen6 => VerifyUnhatchedEgg(pk, Locations.LinkTrade6),
EntityContext.Gen7 => VerifyUnhatchedEgg(pk, Locations.LinkTrade6),
EntityContext.Gen8b=> VerifyUnhatchedEgg(pk, Locations.LinkTrade6NPC, Locations.Default8bNone),
EntityContext.Gen8 => VerifyUnhatchedEgg(pk, Locations.LinkTrade6),
EntityContext.Gen9 => VerifyUnhatchedEgg(pk, Locations.LinkTrade6),
_ => GetInvalid(LEggLocationInvalid),
};
private static CheckResult VerifyEncounterEggHatched(PKM pk, EntityContext context) => context switch
{
EntityContext.Gen2 => VerifyEncounterEgg2(pk),
EntityContext.Gen3 => VerifyEncounterEgg3(pk),
EntityContext.Gen4 => VerifyEncounterEgg4(pk),
EntityContext.Gen5 => VerifyEncounterEgg5(pk),
EntityContext.Gen6 => VerifyEncounterEgg6(pk),
EntityContext.Gen7 => VerifyEncounterEgg7(pk),
EntityContext.Gen8b=> VerifyEncounterEgg8BDSP(pk),
EntityContext.Gen8 => VerifyEncounterEgg8(pk),
EntityContext.Gen9 => VerifyEncounterEgg9(pk),
_ => GetInvalid(LEggLocationInvalid),
};
@ -335,7 +349,7 @@ private static CheckResult VerifyEncounterEvent(PKM pk, MysteryGift gift)
}
if (!pk.IsEgg && gift.IsEgg) // hatched
{
var hatchCheck = VerifyEncounterEgg(pk, gift.Generation);
var hatchCheck = VerifyEncounterEggHatched(pk, gift.Context);
if (!hatchCheck.Valid)
return hatchCheck;
}

View File

@ -124,17 +124,50 @@ private static void AddEncounterInfoPIDIV(List<string> lines, LegalInfo info)
lines.Add(msgType);
if (pidiv.NoSeed)
{
if (type is PIDType.Pokewalker)
if (enc is EncounterStatic4Pokewalker)
{
if (type is not PIDType.Pokewalker)
return;
var line = GetLinePokewalkerSeed(info);
lines.Add(line);
}
else if (enc is PCD { Gift.PK.PID: <= 1 }) // tick rand
else if (enc is PCD pcd)
{
var ticks = ARNG.Prev(info.Entity.EncryptionConstant);
var line = string.Format(L_FOriginSeed_0, ticks.ToString("X8"));
line += $" [{ticks / 524_288f:F2}]"; // seconds?
lines.Add(line);
var gift = pcd.Gift;
if (gift is { HasPID: false }) // tick rand
{
var ticks = ARNG.Prev(info.Entity.EncryptionConstant);
var line = string.Format(L_FOriginSeed_0, ticks.ToString("X8"));
line += $" [{ticks / 524_288f:F2}]"; // seconds?
lines.Add(line);
}
if (gift is { HasIVs: false })
{
var pk = info.Entity;
Span<int> ivs = stackalloc int[6];
pk.GetIVs(ivs);
var date = pk.MetDate ?? new DateOnly(2000, 1, 1);
var initial = ClassicEraRNG.SeekInitialSeedForIVs(ivs, (uint)date.Year, (uint)date.Month, (uint)date.Day, out var origin);
var components = ClassicEraRNG.DecomposeSeed(initial, (uint)date.Year, (uint)date.Month, (uint)date.Day);
AppendInitialDateTime(lines, initial, origin, components);
if (components.IsInvalid())
lines.Add("INVALID");
}
}
else if (enc is EncounterEgg3)
{
if (Daycare3.TryGetOriginSeed(info.Entity, out var day3))
{
var line = string.Format(L_FOriginSeed_0, day3.Origin.ToString("X8"));
lines.Add(line);
lines.Add($"Initial: 0x{day3.Initial:X8}, Frame: {day3.Advances + 1}"); // frames are 1-indexed
var sb = new StringBuilder();
AppendFrameTimeStamp(day3.Advances, sb);
lines.Add($"Time: {sb}");
}
}
return;
}
@ -160,6 +193,8 @@ private static void AddEncounterInfoPIDIV(List<string> lines, LegalInfo info)
}
if (enc is EncounterSlot3 or EncounterStatic3)
AppendDetailsFrame3(info, lines);
else if (enc is EncounterSlot4 or EncounterStatic4)
AppendDetailsDate4(info, lines);
}
private static string GetLinePokewalkerSeed(LegalInfo info)
@ -195,17 +230,47 @@ private static string GetLineSlot34(LegalInfo info, PIDIV pidiv, IEncounterSlot3
return line;
}
private static void AppendDetailsDate4(LegalInfo info, List<string> lines)
{
var pidiv = info.PIDIV;
if (pidiv.Type is not (PIDType.Method_1 or PIDType.ChainShiny))
return;
// Try to determine date/time
var enc = info.EncounterOriginal;
var seed = enc is EncounterSlot4 && info.FrameMatches ? pidiv.EncounterSeed : pidiv.OriginSeed;
// Assume the met date is the same as the encounter date.
var entity = info.Entity;
var date = entity.MetDate ?? new DateOnly(2000, 1, 1);
var initialSeed = ClassicEraRNG.SeekInitialSeed((uint)date.Year, (uint)date.Month, (uint)date.Day, seed);
AppendInitialDateTime(lines, initialSeed, seed, date);
}
private static void AppendInitialDateTime(List<string> lines, uint initialSeed, uint origin, DateOnly date)
{
var decompose = ClassicEraRNG.DecomposeSeed(initialSeed, (uint)date.Year, (uint)date.Month, (uint)date.Day);
AppendInitialDateTime(lines, initialSeed, origin, decompose);
}
private static void AppendInitialDateTime(List<string> lines, uint initialSeed, uint origin, InitialSeedComponents4 decompose)
{
var advances = LCRNG.GetDistance(initialSeed, origin);
lines.Add($"{decompose.Year+2000:0000}-{decompose.Month:00}-{decompose.Day:00} @ {decompose.Hour:00}:{decompose.Minute:00}:{decompose.Second:00} - {decompose.Delay}");
lines.Add($"Initial: 0x{initialSeed:X8}, Frame: {advances + 1}"); // frames are 1-indexed
}
private static void AppendDetailsFrame3(LegalInfo info, List<string> lines)
{
var pidiv = info.PIDIV;
var pk = info.Entity;
var enc = info.EncounterOriginal;
var seed = enc is EncounterSlot3 && info.FrameMatches ? pidiv.EncounterSeed : pidiv.OriginSeed;
var (initialSeed, frame) = GetInitialSeed(seed, pk.Version);
lines.Add($"Initial: 0x{initialSeed:X8}, Frame: {frame + 1}"); // frames are 1-indexed
var (initialSeed, advances) = GetInitialSeed3(seed, pk.Version);
lines.Add($"Initial: 0x{initialSeed:X8}, Frame: {advances + 1}"); // frames are 1-indexed
var sb = new StringBuilder();
AppendFrameTimeStamp(frame, sb);
AppendFrameTimeStamp(advances, sb);
lines.Add($"Time: {sb}");
// Try appending the TID frame if it originates from Emerald.
@ -213,12 +278,12 @@ private static void AppendDetailsFrame3(LegalInfo info, List<string> lines)
return;
// don't bother ignoring postgame-only. E4 resets the seed, but it's annoying to check.
var tidSeed = pk.TID16; // start of game
var tidFrame = LCRNG.GetDistance(tidSeed, seed);
if (tidFrame >= frame)
var tidAdvances = LCRNG.GetDistance(tidSeed, seed);
if (tidAdvances >= advances)
return; // only show if it makes sense to
lines.Add($"New Game: 0x{tidSeed:X8}, Frame: {tidFrame + 1}"); // frames are 1-indexed
lines.Add($"New Game: 0x{tidSeed:X8}, Frame: {tidAdvances + 1}"); // frames are 1-indexed
sb.Clear();
AppendFrameTimeStamp(tidFrame, sb);
AppendFrameTimeStamp(tidAdvances, sb);
lines.Add($"Time: {sb}");
}
@ -236,7 +301,7 @@ private static void AppendFrameTimeStamp(uint frame, StringBuilder sb)
sb.Append($" (days: {(int)time.TotalDays})");
}
private static (uint Seed, uint Frame) GetInitialSeed(uint seed, GameVersion game)
private static (uint Seed, uint Advances) GetInitialSeed3(uint seed, GameVersion game)
{
if (game is GameVersion.E) // Always 0 seed.
return (0, LCRNG.GetDistance(0, seed));
@ -251,9 +316,9 @@ private static (uint Seed, uint Frame) GetInitialSeed(uint seed, GameVersion gam
if (game is GameVersion.R or GameVersion.S)
{
const uint drySeed = 0x05A0;
var dryFrame = LCRNG.GetDistance(drySeed, seed);
if (dryFrame < ushort.MaxValue << 2)
return (drySeed, dryFrame);
var advances = LCRNG.GetDistance(drySeed, seed);
if (advances < ushort.MaxValue << 2)
return (drySeed, advances);
}
return (nearest16, ctr);
}

View File

@ -35,7 +35,7 @@ public sealed class LearnGroup2 : ILearnGroup
Check(result, current, pk, evos[i], i, option, types);
}
if (enc is EncounterEgg { Generation: Generation } egg)
if (enc is EncounterEgg2 egg)
CheckEncounterMoves(result, current, egg);
bool vc1 = pk.VC1;
@ -82,7 +82,7 @@ private static void GetEncounterMoves(PKM pk, IEncounterTemplate enc, Span<ushor
LearnSource2GS.GetEncounterMoves(enc, moves);
}
private static void CheckEncounterMoves(Span<MoveResult> result, ReadOnlySpan<ushort> current, EncounterEgg egg)
private static void CheckEncounterMoves(Span<MoveResult> result, ReadOnlySpan<ushort> current, EncounterEgg2 egg)
{
ILearnSource inst = egg.Version == GameVersion.C ? LearnSource2C.Instance : LearnSource2GS.Instance;
var eggMoves = inst.GetEggMoves(egg.Species, egg.Form);

View File

@ -21,7 +21,7 @@ public sealed class LearnGroup3 : ILearnGroup
for (var i = 0; i < evos.Length; i++)
Check(result, current, pk, evos[i], i, types);
if (types.HasFlag(MoveSourceType.Encounter) && enc is EncounterEgg { Generation: Generation } egg)
if (types.HasFlag(MoveSourceType.Encounter) && enc is EncounterEgg3 { Generation: Generation } egg)
CheckEncounterMoves(result, current, egg);
if (types.HasFlag(MoveSourceType.LevelUp) && enc.Species is (int)Species.Nincada && evos is [{ Species: (int)Species.Shedinja }, _])
@ -65,7 +65,7 @@ private static void CheckNincadaMoves(Span<MoveResult> result, ReadOnlySpan<usho
}
}
private static void CheckEncounterMoves(Span<MoveResult> result, ReadOnlySpan<ushort> current, EncounterEgg egg)
private static void CheckEncounterMoves(Span<MoveResult> result, ReadOnlySpan<ushort> current, EncounterEgg3 egg)
{
ILearnSource inst = egg.Version switch
{

View File

@ -21,7 +21,7 @@ public sealed class LearnGroup4 : ILearnGroup
for (var i = 0; i < evos.Length; i++)
Check(result, current, pk, evos[i], i);
if (types.HasFlag(MoveSourceType.Encounter) && enc is EncounterEgg { Generation: Generation } egg)
if (types.HasFlag(MoveSourceType.Encounter) && enc is EncounterEgg4 egg)
CheckEncounterMoves(result, current, egg);
if (types.HasFlag(MoveSourceType.LevelUp) && enc.Species is (int)Species.Nincada && evos is [{ Species: (int)Species.Shedinja }, _])
@ -57,7 +57,7 @@ private static void CheckNincadaMoves(Span<MoveResult> result, ReadOnlySpan<usho
}
}
private static void CheckEncounterMoves(Span<MoveResult> result, ReadOnlySpan<ushort> current, EncounterEgg egg)
private static void CheckEncounterMoves(Span<MoveResult> result, ReadOnlySpan<ushort> current, EncounterEgg4 egg)
{
ILearnSource inst = egg.Version switch
{

View File

@ -21,13 +21,13 @@ public sealed class LearnGroup5 : ILearnGroup
for (var i = 0; i < evos.Length; i++)
Check(result, current, pk, evos[i], i, types, option);
if (types.HasFlag(MoveSourceType.Encounter) && enc is EncounterEgg { Generation: Generation } egg)
if (types.HasFlag(MoveSourceType.Encounter) && enc is EncounterEgg5 egg)
CheckEncounterMoves(result, current, egg);
return MoveResult.AllParsed(result);
}
private static void CheckEncounterMoves(Span<MoveResult> result, ReadOnlySpan<ushort> current, EncounterEgg egg)
private static void CheckEncounterMoves(Span<MoveResult> result, ReadOnlySpan<ushort> current, EncounterEgg5 egg)
{
ILearnSource inst = egg.Version > GameVersion.B ? LearnSource5B2W2.Instance : LearnSource5BW.Instance;
var eggMoves = inst.GetEggMoves(egg.Species, egg.Form);

View File

@ -24,7 +24,7 @@ public sealed class LearnGroup6 : ILearnGroup
if (option.IsPast() && types.HasFlag(MoveSourceType.Encounter))
{
if (enc is EncounterEgg { Generation: Generation } egg)
if (enc is EncounterEgg6 egg)
CheckEncounterMoves(result, current, egg);
else if (enc is EncounterSlot6AO { CanDexNav: true } dexnav && pk.IsOriginalMovesetDeleted())
CheckDexNavMoves(result, current, dexnav);
@ -33,7 +33,7 @@ public sealed class LearnGroup6 : ILearnGroup
return MoveResult.AllParsed(result);
}
private static void CheckEncounterMoves(Span<MoveResult> result, ReadOnlySpan<ushort> current, EncounterEgg egg)
private static void CheckEncounterMoves(Span<MoveResult> result, ReadOnlySpan<ushort> current, EncounterEgg6 egg)
{
ILearnSource inst = egg.Version > GameVersion.Y ? LearnSource6AO.Instance : LearnSource6XY.Instance;
var eggMoves = inst.GetEggMoves(egg.Species, egg.Form);

View File

@ -29,13 +29,13 @@ public sealed class LearnGroup7 : ILearnGroup
for (var i = 0; i < evos.Length; i++)
Check(result, current, pk, evos[i], i, types, option, mode);
if (option.IsPast() && types.HasFlag(MoveSourceType.Encounter) && enc is EncounterEgg { Generation: Generation } egg)
if (option.IsPast() && types.HasFlag(MoveSourceType.Encounter) && enc is EncounterEgg7 egg)
CheckEncounterMoves(result, current, egg);
return MoveResult.AllParsed(result);
}
private static void CheckEncounterMoves(Span<MoveResult> result, ReadOnlySpan<ushort> current, EncounterEgg egg)
private static void CheckEncounterMoves(Span<MoveResult> result, ReadOnlySpan<ushort> current, EncounterEgg7 egg)
{
ILearnSource inst = egg.Version > GameVersion.MN ? LearnSource7USUM.Instance : LearnSource7SM.Instance;
var eggMoves = inst.GetEggMoves(egg.Species, egg.Form);

View File

@ -46,7 +46,7 @@ public sealed class LearnGroup8 : ILearnGroup
CheckSharedMoves(result, current, evos[0]);
if (option.IsPast() && types.HasFlag(MoveSourceType.Encounter) && pk.IsOriginalMovesetDeleted() && enc is EncounterEgg { Generation: Generation } egg)
if (option.IsPast() && types.HasFlag(MoveSourceType.Encounter) && pk.IsOriginalMovesetDeleted() && enc is EncounterEgg8 egg)
CheckEncounterMoves(result, current, egg);
if (MoveResult.AllParsed(result))
@ -76,7 +76,7 @@ private static void CheckSharedMoves(Span<MoveResult> result, ReadOnlySpan<ushor
}
}
private static void CheckEncounterMoves(Span<MoveResult> result, ReadOnlySpan<ushort> current, EncounterEgg egg)
private static void CheckEncounterMoves(Span<MoveResult> result, ReadOnlySpan<ushort> current, EncounterEgg8 egg)
{
var game = LearnSource8SWSH.Instance;
var eggMoves = game.GetEggMoves(egg.Species, egg.Form);

View File

@ -27,7 +27,7 @@ public sealed class LearnGroup9 : ILearnGroup
CheckSharedMoves(result, current, evos[0]);
if (option.IsPast() && types.HasFlag(MoveSourceType.Encounter) && pk.IsOriginalMovesetDeleted() && enc is EncounterEgg { Generation: Generation } egg)
if (option.IsPast() && types.HasFlag(MoveSourceType.Encounter) && pk.IsOriginalMovesetDeleted() && enc is EncounterEgg9 egg)
CheckEncounterMoves(result, current, egg);
if (MoveResult.AllParsed(result))
@ -57,7 +57,7 @@ private static void CheckSharedMoves(Span<MoveResult> result, ReadOnlySpan<ushor
}
}
private static void CheckEncounterMoves(Span<MoveResult> result, ReadOnlySpan<ushort> current, EncounterEgg egg)
private static void CheckEncounterMoves(Span<MoveResult> result, ReadOnlySpan<ushort> current, EncounterEgg9 egg)
{
var game = LearnSource9SV.Instance;
var eggMoves = game.GetEggMoves(egg.Species, egg.Form);

View File

@ -17,7 +17,7 @@ public static void Verify(Span<MoveResult> result, ReadOnlySpan<ushort> current,
private static void VerifyPre3DS(Span<MoveResult> result, ReadOnlySpan<ushort> current, IEncounterTemplate enc)
{
if (enc is EncounterEgg e)
if (enc is IEncounterEgg e)
LearnVerifierRelearn.VerifyEggMoveset(e, result, current);
else
VerifyFromEncounter(result, current, enc);
@ -74,7 +74,7 @@ private static void VerifyMovesInitial(Span<MoveResult> result, ReadOnlySpan<ush
private static void VerifyFromRelearn(Span<MoveResult> result, ReadOnlySpan<ushort> current, IEncounterTemplate enc, PKM pk)
{
if (enc is EncounterEgg)
if (enc is IEncounterEgg)
VerifyMatchesRelearn(result, current, pk);
else if (enc is IMoveset { Moves: { HasMoves: true } x })
VerifyMovesInitial(result, current, x, GameData.GetLearnSource(enc.Version).Environment);

View File

@ -14,7 +14,7 @@ public static void Verify(Span<MoveResult> result, IEncounterTemplate enc, PKM p
VerifyRelearnNone(pk, result);
else if (enc is IRelearn {Relearn: {HasMoves: true} x})
VerifyRelearnSpecifiedMoveset(pk, x, result);
else if (enc is EncounterEgg e)
else if (enc is IEncounterEgg e)
VerifyEggMoveset(e, result, pk);
else if (enc is EncounterSlot6AO { CanDexNav: true } z && pk.RelearnMove1 != 0)
VerifyRelearnDexNav(pk, result, z);
@ -75,14 +75,14 @@ private static void VerifyRelearnNone(PKM pk, Span<MoveResult> result)
result[0] = ParseExpect(pk.RelearnMove1);
}
private static void VerifyEggMoveset(EncounterEgg e, Span<MoveResult> result, PKM pk)
private static void VerifyEggMoveset(IEncounterEgg e, Span<MoveResult> result, PKM pk)
{
Span<ushort> moves = stackalloc ushort[4];
pk.GetRelearnMoves(moves);
VerifyEggMoveset(e, result, moves);
}
internal static void VerifyEggMoveset(EncounterEgg e, Span<MoveResult> result, ReadOnlySpan<ushort> moves)
internal static void VerifyEggMoveset(IEncounterEgg e, Span<MoveResult> result, ReadOnlySpan<ushort> moves)
{
var gen = e.Generation;
Span<byte> origins = stackalloc byte[moves.Length];
@ -94,7 +94,7 @@ internal static void VerifyEggMoveset(EncounterEgg e, Span<MoveResult> result, R
if (moves[i] == 0)
result[i] = MoveResult.Empty;
else
result[i] = new(EggSourceUtil.GetSource(origins[i], gen), GameData.GetLearnSource(e.Version).Environment);
result[i] = new(EggSourceUtil.GetSource(origins[i], gen), e.Learn.Environment);
}
}
else
@ -111,7 +111,7 @@ internal static void VerifyEggMoveset(EncounterEgg e, Span<MoveResult> result, R
else if (current == 0)
result[i] = MoveResult.Empty;
else
result[i] = new(EggSourceUtil.GetSource(origins[i], gen), GameData.GetLearnSource(e.Version).Environment);
result[i] = new(EggSourceUtil.GetSource(origins[i], gen), e.Learn.Environment);
}
}

View File

@ -132,7 +132,7 @@ private static void GetSuggestedRelearnInternal(this IEncounterTemplate enc, PKM
{
if (enc is IRelearn { Relearn: { HasMoves: true } r })
r.CopyTo(moves);
else if (enc is EncounterEgg or EncounterInvalid { IsEgg: true })
else if (enc is IEncounterEgg or EncounterInvalid { IsEgg: true })
GetSuggestedRelearnEgg(enc, pk, moves);
}
@ -157,7 +157,7 @@ public static void GetSuggestedRelearnMovesFromEncounter(this LegalityAnalysis a
if (LearnVerifierRelearn.ShouldNotHaveRelearnMoves(enc, pk))
return;
if (enc is EncounterEgg or EncounterInvalid {IsEgg: true})
if (enc is IEncounterEgg or EncounterInvalid {IsEgg: true})
enc.GetSuggestedRelearnEgg(info.Moves, pk, moves);
else
enc.GetSuggestedRelearnInternal(pk, moves);

View File

@ -31,7 +31,7 @@ public static class MoveBreed
/// Gets the expected moves the egg should come with, using an input of requested <see cref="moves"/> that are requested to be in the output.
/// </summary>
/// <param name="moves">Moves requested to be in the expected moves result</param>
/// <param name="enc">Encounter detail interface wrapper; should always be <see cref="EncounterEgg"/>.</param>
/// <param name="enc">Encounter detail interface wrapper; should always be <see cref="IEncounterEgg"/> or <see cref="EncounterInvalid"/>.</param>
/// <param name="result">Result moves that are valid</param>
/// <remarks>Validates the requested moves first prior to trying a more expensive computation.</remarks>
/// <returns>True if the <see cref="result"/> is valid using the input <see cref="moves"/>. If not valid, the <see cref="result"/> will be base egg moves, probably valid.</returns>

View File

@ -1,3 +1,6 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace PKHeX.Core;
/// <summary>
@ -89,4 +92,208 @@ public static uint GetSequentialIVs(ref uint seed)
var rand2 = LCRNG.Next15(ref seed);
return (rand2 << 15) | rand1;
}
/// <summary>
/// Creates an initial seed from the given components.
/// </summary>
/// <param name="year">Year component (2000-2099).</param>
/// <param name="month">Month component (1-12).</param>
/// <param name="day">Day component (1-31).</param>
/// <param name="hour">Hour component (0-23).</param>
/// <param name="minute">Minute component (0-59).</param>
/// <param name="second">Seconds component (0-59).</param>
/// <param name="delay">Delay timer component.</param>
/// <remarks>
/// No sanity checking if the Month/Day/Year are valid.
/// </remarks>
public static uint GetInitialSeed(uint year, uint month, uint day, uint hour, uint minute, uint second, uint delay)
{
byte ab = (byte)(month * day + minute + second);
byte cd = (byte)hour;
return (uint)(((ab << 24) | (cd << 16))) + delay + year - 2000u;
}
/// <summary>
/// Finds the initial seed for a given date and time.
/// </summary>
/// <param name="year">Year component (2000-2099).</param>
/// <param name="month">Month component (1-12).</param>
/// <param name="day">Day component (1-31).</param>
/// <param name="seed">Origin seed to look backwards for the initial seed.</param>
/// <remarks>
/// No sanity checking if the Month/Day/Year are valid.
/// </remarks>
public static uint SeekInitialSeed(uint year, uint month, uint day, uint seed)
{
while (true)
{
if (IsInitialSeed(year, month, day, seed))
break;
seed = LCRNG.Prev(seed);
}
var decompose = DecomposeSeed(seed, year, month, day);
// Check one step previous, just in case that delay is better.
var prevSeed = LCRNG.Prev(seed);
while (true)
{
if (IsInitialSeed(year, month, day, prevSeed))
break;
prevSeed = LCRNG.Prev(prevSeed);
}
var distance = LCRNG.GetDistance(prevSeed, seed);
if (distance > 5000) // arbitrary limit, most won't need this many advances to RNG.
return seed; // don't go too far back
var prevDecompose = DecomposeSeed(prevSeed, year, month, day);
// Check if the previous seed has a better delay
if (prevDecompose.Delay < decompose.Delay)
return prevSeed;
return seed;
}
/// <summary>
/// Checks if a seed is an initial seed for the given date and time.
/// </summary>
/// <param name="year">Year component (2000-2099).</param>
/// <param name="month">Month component (1-12).</param>
/// <param name="day">Day component (1-31).</param>
/// <param name="seed">Initial seed to check.</param>
/// <returns><c>true</c> if the seed is an initial seed for the given date and time; otherwise, <c>false</c>.</returns>
public static bool IsInitialSeed(uint year, uint month, uint day, uint seed)
{
// Check component: hour
var hour = (byte)(seed >> 16 & 0xFF);
if (hour > 23)
return false;
// Check component: everything else but delay/year using modular arithmetic to handle overflow
const uint maxBonusMinSec = 59 + 59; // min + sec
var top = (byte)(seed >> 24);
var topMin = (byte)(month * day);
// Calculate the difference modulo 256. If it exceeds maxBonusMinSec, it's out of range.
if ((byte)(top - topMin) > maxBonusMinSec)
return false;
// Check component: delay/year
// Should be a plausible delay; even though delay can overflow, it would take at least half an hour of waiting to launch the game to do so.
const uint baseDelay = 400; // hg/ss
var minDelay = baseDelay + (year - 2000u);
const uint maxDelay = 6000;
var delayComponent = (ushort)(seed - minDelay);
// Check if the delay is within the plausible range
if (delayComponent > maxDelay)
return false;
return true;
}
/// <summary>
/// Decomposes a seed into its datetime initial seed components.
/// </summary>
/// <param name="seed">Initial seed to decompose.</param>
/// <param name="year">Year of the initial seed.</param>
/// <param name="month">Month of the initial seed.</param>
/// <param name="day">Day of the initial seed.</param>
/// <exception cref="ArgumentOutOfRangeException"> if any component is out of range and is not an Initial Seed.</exception>
public static InitialSeedComponents4 DecomposeSeed(uint seed, uint year, uint month, uint day)
{
// Check component: hour
var hour = (byte)(seed >> 16 & 0xFF);
ArgumentOutOfRangeException.ThrowIfGreaterThan(hour, 23, nameof(hour));
// Check component: everything else but delay/year using modular arithmetic to handle overflow
const uint maxBonusMinSec = 59 + 59; // min + sec
var top = (byte)(seed >> 24);
var topMin = (byte)(month * day);
// Calculate the difference modulo 256. If it exceeds maxBonusMinSec, it's out of range.
var delta = (byte)(top - topMin);
ArgumentOutOfRangeException.ThrowIfGreaterThan(delta, maxBonusMinSec, nameof(top));
var yearComponent = (byte)(year - 2000u);
var delay = (ushort)((ushort)seed - yearComponent);
// Minute and seconds: prefer a seconds value at least 7, at most 15 if possible.
const byte minSec = 7;
const byte maxSec = 15;
byte min; byte sec;
if (delta < 59 + maxSec)
{
sec = delta >= minSec ? (byte)(delta - minSec) : delta;
min = (byte)(delta - sec);
}
else
{
// need a higher seconds
min = 59;
sec = (byte)(delta - 59);
}
return new InitialSeedComponents4
{
Year = yearComponent,
Month = (byte)month,
Day = (byte)day,
Hour = hour,
Delay = delay,
Minute = min,
Second = sec,
};
}
/// <summary>
/// Finds the initial seed for a given set of IVs in Generation 4.
/// </summary>
/// <param name="ivs">IVs to use for the search.</param>
/// <param name="year">Year of the initial seed.</param>
/// <param name="month">Month of the initial seed.</param>
/// <param name="day">Day of the initial seed.</param>
/// <param name="origin">Seed that originated the IVs.</param>
/// <returns>Initial datetime seed.</returns>
public static uint SeekInitialSeedForIVs(ReadOnlySpan<int> ivs, uint year, uint month, uint day, out uint origin)
{
origin = 0;
uint bestDistance = uint.MaxValue;
Span<uint> seeds = stackalloc uint[LCRNG.MaxCountSeedsIV];
var count = LCRNGReversal.GetSeedsIVs(seeds, (uint)ivs[0], (uint)ivs[1], (uint)ivs[2], (uint)ivs[4], (uint)ivs[5], (uint)ivs[3]);
if (count == 0)
return 0; // shouldn't happen; IVs should always find seeds.
seeds = seeds[..count];
uint best = 0;
foreach (var seed in seeds)
{
var init = SeekInitialSeed(year, month, day, seed);
var distance = LCRNG.GetDistance(init, seed);
if (distance > bestDistance)
continue;
bestDistance = distance;
best = init;
origin = seed;
}
return best;
}
}
/// <summary>
/// Stores the components of an initial seed from Generation 4.
/// </summary>
public readonly record struct InitialSeedComponents4
{
[Range(0, 99)] public required byte Year { get; init; }
[Range(1, 12)] public required byte Month { get; init; }
[Range(1, 31)] public required byte Day { get; init; }
[Range(0, 23)] public required byte Hour { get; init; }
[Range(0, 59)] public required byte Minute { get; init; }
[Range(0, 59)] public required byte Second { get; init; }
public required ushort Delay { get; init; } // essentially XXX-65535, but can overflow. Not that anyone waits the 30+ minutes to do that since other initial seeds are more efficient.
public uint ToSeed() => ClassicEraRNG.GetInitialSeed(Year, Month, Day, Hour, Minute, Second, Delay);
public bool IsInvalid() => Month == 0;
}

View File

@ -0,0 +1,279 @@
using System;
namespace PKHeX.Core;
/// <summary>
/// Generation 3 Daycare RNG correlation logic.
/// </summary>
public static class Daycare3
{
/// <summary>
/// Checks if the PID is possible to obtain, in isolation.
/// </summary>
/// <param name="pid">Obtained PID.</param>
/// <param name="version">Version the egg was obtained in.</param>
/// <returns><c>true</c> if the PID is valid, <c>false</c> otherwise.</returns>
public static bool IsValidProcPID(uint pid, GameVersion version)
{
// Gen3 Eggs don't have a zero-value pending PID value.
// For R/S/FR/LG (not Emerald)
// LoveCheck: Rand() * 100 / 65535 < compatibility*
// PID: The game stores the lower 16 bits via (Rand() & 0xFFFE) + 1
// 0-value for lower 16 bits is never set by egg proc
if (version is not GameVersion.E)
return (pid & 0xFFFF) != 0;
// For Emerald, the PID is generated according to the following pattern:
// LoveCheck: Rand() * 100 / 65535 < compatibility*
// PID: The game stores the full 32-bit value to lock in nature; previous games only store the lower 16 bits.
// Everstone (inherit nature): 2400 attempts with rand() << 16 | rand() && pid != 0 -- assume never needing 2400 attempts even with vBlank in the mix.
// Otherwise (random nature): rand() << 16 | (rand() & 0xFFFE) + 1
// The PID will never be 0.
if (pid == 0)
return false;
// The LoveCheck is not always immediately before the PID generation (Everstone).
// Considering vBlank interrupts, it's essentially random enough to get any PID (besides 0).
return true;
}
/// <inheritdoc cref="TryGetOriginSeed(ReadOnlySpan{int}, uint, GameVersion, out Daycare3Origin)"/>
public static bool TryGetOriginSeed(PKM pk, out Daycare3Origin origin)
{
Span<int> actual = stackalloc int[6];
pk.GetIVs(actual);
if (pk.Version is GameVersion.E)
return TryGetOriginSeedEmerald(actual, out origin);
return TryGetOriginSeed(actual, pk.EncryptionConstant, out origin);
}
/// <summary>
/// Searches for a <see cref="origin"/> state that the player picked up the egg.
/// </summary>
/// <param name="ivs">IVs to check for.</param>
/// <param name="pid">Obtained PID; only high 16 bits needed.</param>
/// <param name="version">Version of the game.</param>
/// <param name="origin">Origin info when receiving the egg</param>
/// <returns><c>true</c> if a valid origin was found, <c>false</c> otherwise.</returns>
public static bool TryGetOriginSeed(ReadOnlySpan<int> ivs, uint pid, GameVersion version, out Daycare3Origin origin)
{
if (version is GameVersion.E)
return TryGetOriginSeedEmerald(ivs, out origin);
return TryGetOriginSeed(ivs, pid, out origin);
}
/// <inheritdoc cref="TryGetOriginSeed(ReadOnlySpan{int}, uint, GameVersion, out Daycare3Origin)"/>
public static bool TryGetOriginSeedEmerald(ReadOnlySpan<int> ivs, out Daycare3Origin origin)
{
// Frame pattern:
// PID is already decided. Simply IVs.
// IVs low (overwritten by inheritance later)
// IVs high (overwritten by inheritance later)
// **vBlank
// Inheritance
Span<int> tmp = stackalloc int[6];
// Search forward from Emerald's initial 0 seed.
// Once we find a result, that's the lowest (best) result possible.
uint frame = 0xBE34A09C; // 60 frames after initial seed (0x0000_0000); arbitrary minimum frame.
// Assume the most-frequent vBlank placement in the calculation sequence.
// (after IVs)
while (true)
{
var seed = frame = LCRNG.Next(frame);
var iv1 = LCRNG.Next16(ref seed);
var iv2 = LCRNG.Next16(ref seed);
Fill(tmp, iv1, iv2);
var countMatching = GetCountMatch(ivs, tmp);
if (countMatching < 3)
continue;
_ = LCRNG.Next16(ref seed); // vBlank, setting IVs is slow
// Determine inherited IVs
ApplyInheritanceEmerald(seed, tmp);
// Don't care which parent passes the IVs.
// Done.
// Check if the IVs are valid
var count = GetCountMatchInherit(ivs, tmp);
if (count != 6)
continue;
frame = LCRNG.Prev4(frame); // unroll once, and account for interaction lag (vBlank)
var advances = LCRNG.GetDistance(0, frame);
origin = new Daycare3Origin(frame, advances, 0);
return true;
}
}
/// <remarks>
/// Usable by all versions (R/S/FR/LG), excluding Emerald.
/// </remarks>
/// <inheritdoc cref="TryGetOriginSeed(ReadOnlySpan{int}, uint, GameVersion, out Daycare3Origin)"/>
public static bool TryGetOriginSeed(ReadOnlySpan<int> ivs, uint pid, out Daycare3Origin origin)
{
// Frame pattern:
// PID high
// **vBlank
// IVs low (overwritten by inheritance later)
// IVs high (overwritten by inheritance later)
// **vBlank
// Inheritance
Span<int> tmp = stackalloc int[6];
origin = default;
// PID is the first Rand() call. We don't know the lower bits of the seed, so try all.
// The seed that produces the IV pattern with the lowest amount of advances from a 16-bit seed is our result.
// For Ruby Sapphire RNG abused eggs, ideally we discover 0x05A0 is the initial seed.
var high = pid & 0xFFFF0000;
// Assume the most-frequent vBlank placement in the calculation sequence.
// (after PID, after IVs)
for (uint i = 0; i < 0x10000; i++)
{
var seed = high | i; // PID high; unknown lower portion of seed.
_ = LCRNG.Next16(ref seed); // vBlank, set PID and all misc PKM properties before IVs
var iv1 = LCRNG.Next16(ref seed);
var iv2 = LCRNG.Next16(ref seed);
Fill(tmp, iv1, iv2);
var countMatching = GetCountMatch(ivs, tmp);
if (countMatching < 3)
continue;
_ = LCRNG.Next16(ref seed); // vBlank, setting IVs is slow
// Determine inherited IVs
ApplyInheritance(seed, tmp);
// Don't care which parent passes the IVs.
// Done.
// Check if the IVs are valid
var count = GetCountMatchInherit(ivs, tmp);
if (count != 6)
continue;
var generate = LCRNG.Prev4(high | i); // unroll once, and account for interaction lag (vBlank)
UpdateIfBetter(ref origin, generate);
}
return origin.Pattern != Daycare3Correlation.None;
}
private static void ApplyInheritance(uint seed, Span<int> tmp)
{
Span<int> statIndexes = stackalloc int[6];
for (int i = 0; i < 6; i++)
statIndexes[i] = i;
for (int i = 0; i < 3; i++)
{
var index = (int)LCRNG.Next16(ref seed) % (6 - i);
var inherit = statIndexes[index];
for (int j = index + 1; j < 6; j++)
statIndexes[j - 1] = statIndexes[j];
tmp[inherit] = -1; // Inherit this stat
}
}
private static void ApplyInheritanceEmerald(uint seed, Span<int> tmp)
{
// Game Bug: Instead of removing the IV that was just picked, this
// removes position 0 (HP) then position 1 (DEF), then position 2.
Span<int> statIndexes = stackalloc int[6];
for (int i = 0; i < 6; i++)
statIndexes[i] = i;
for (int i = 0; i < 3; i++)
{
var index = (int)LCRNG.Next16(ref seed) % (6 - i);
var inherit = statIndexes[index];
for (int j = /* index */ i + 1; j < 6; j++)
statIndexes[j - 1] = statIndexes[j];
tmp[inherit] = -1; // Inherit this stat
}
}
private static void Fill(Span<int> tmp, uint iv1, uint iv2)
{
tmp[0] = (byte)(iv1 & 0x1F);
tmp[1] = (byte)((iv1 >> 5) & 0x1F);
tmp[2] = (byte)((iv1 >> 10) & 0x1F);
tmp[3] = (byte)(iv2 & 0x1F);
tmp[4] = (byte)((iv2 >> 5) & 0x1F);
tmp[5] = (byte)((iv2 >> 10) & 0x1F);
}
private static int GetCountMatch(ReadOnlySpan<int> actual, ReadOnlySpan<int> tmp)
{
int count = 0;
for (int i = 0; i < actual.Length; i++)
{
if (actual[i] == tmp[i])
count++;
}
return count;
}
private static int GetCountMatchInherit(ReadOnlySpan<int> actual, ReadOnlySpan<int> tmp)
{
int count = 0;
for (int i = 0; i < actual.Length; i++)
{
var iv = tmp[i];
if (iv == -1 || actual[i] == iv)
count++;
}
return count;
}
private static void UpdateIfBetter(ref Daycare3Origin best, uint origin, Daycare3Correlation pattern = Daycare3Correlation.Regular)
{
// Determine initial seed
var seed = LCRNG.Prev9(origin); // arbitrary un-hittable frames
while (seed >> 16 != 0)
seed = LCRNG.Prev(seed);
var advances = LCRNG.GetDistance(seed, origin);
// Check if the seed is better
if (best.Advances >= advances || best.Pattern is Daycare3Correlation.None)
best = new Daycare3Origin(origin, advances, (ushort)seed, pattern);
}
}
/// <summary>
/// Stores initial seed information for Gen3 daycare seeds.
/// </summary>
/// <param name="Origin">Seed that originates the egg on pickup from Daycare.</param>
/// <param name="Advances">Advances from the initial seed to the origin seed.</param>
/// <param name="Initial">Initial seed from the start of the game.</param>
/// <param name="Pattern">Generation pattern of the egg.</param>
public readonly record struct Daycare3Origin(uint Origin, uint Advances, ushort Initial, Daycare3Correlation Pattern = Daycare3Correlation.Regular);
public enum Daycare3Correlation
{
/// <summary>
/// None detected, usually just a sentinel value.
/// </summary>
None,
/// <summary>
/// Standard vBlank pattern.
/// </summary>
Regular,
// Other patterns may be added in the future if other examples necessitate it.
}

View File

@ -123,7 +123,7 @@ private static bool IsSpecialEncounterMoveEggDeleted(PKM pk, IEncounterTemplate
{
if (pk.IsOriginalMovesetDeleted())
return true;
return enc is EncounterEgg { Generation: < 6 }; // egg moves that are no longer in the movepool
return enc is IEncounterEgg { Generation: < 6 }; // egg moves that are no longer in the movepool
}
public static bool GetCanRelearnMove(PKM pk, ushort move, EntityContext context, EvolutionHistory history, IEncounterTemplate enc)

View File

@ -322,7 +322,7 @@ private CheckResult VerifyAbility5(LegalityAnalysis data, IEncounterTemplate enc
// Eggs and Encounter Slots are not yet checked for Hidden Ability potential.
return enc switch
{
EncounterEgg e when pk.AbilityNumber == 4 && !AbilityBreedLegality.IsHiddenPossible5(e.Species) => GetInvalid(LAbilityHiddenUnavailable),
EncounterEgg5 egg when pk.AbilityNumber == 4 && !egg.Ability.CanBeHidden() => GetInvalid(LAbilityHiddenUnavailable),
_ => CheckMatch(data.Entity, abilities, 5, pk.Format == 5 ? AbilityState.MustMatch : AbilityState.CanMismatch, enc),
};
}
@ -335,7 +335,7 @@ private CheckResult VerifyAbility6(LegalityAnalysis data, IEncounterTemplate enc
return enc switch
{
EncounterEgg egg when !AbilityBreedLegality.IsHiddenPossible6(egg.Species, egg.Form) => GetInvalid(LAbilityHiddenUnavailable),
EncounterEgg6 egg when !egg.Ability.CanBeHidden() => GetInvalid(LAbilityHiddenUnavailable),
_ => VALID,
};
}
@ -348,7 +348,7 @@ private CheckResult VerifyAbility7(LegalityAnalysis data, IEncounterTemplate enc
return enc switch
{
EncounterEgg egg when !AbilityBreedLegality.IsHiddenPossible7(egg.Species, egg.Form) => GetInvalid(LAbilityHiddenUnavailable),
EncounterEgg7 egg when !egg.Ability.CanBeHidden() => GetInvalid(LAbilityHiddenUnavailable),
_ => VALID,
};
}
@ -361,7 +361,7 @@ private CheckResult VerifyAbility8BDSP(LegalityAnalysis data, IEncounterTemplate
return enc switch
{
EncounterEgg egg when !AbilityBreedLegality.IsHiddenPossibleHOME(egg.Species) => GetInvalid(LAbilityHiddenUnavailable),
EncounterEgg8b egg when !egg.Ability.CanBeHidden() => GetInvalid(LAbilityHiddenUnavailable),
_ => VALID,
};
}

View File

@ -75,7 +75,7 @@ public static BallVerificationResult VerifyBall(IEncounterTemplate enc, Ball cur
return VerifyBallEquals(current, ball);
// Capturing with Heavy Ball is impossible in Sun/Moon for specific species.
if (current is Heavy && enc is not EncounterEgg && pk is { SM: true } && BallUseLegality.IsAlolanCaptureNoHeavyBall(enc.Species))
if (current is Heavy && enc is not EncounterEgg7 && pk is { SM: true } && BallUseLegality.IsAlolanCaptureNoHeavyBall(enc.Species))
return BadCaptureHeavy; // Heavy Ball, can inherit if from egg (US/UM fixed catch rate calc)
return enc switch
@ -86,13 +86,13 @@ public static BallVerificationResult VerifyBall(IEncounterTemplate enc, Ball cur
EncounterSlot8 when pk is IRibbonSetMark8 { RibbonMarkCurry: true } or IRibbonSetAffixed { AffixedRibbon: (sbyte)RibbonIndex.MarkCurry }
=> GetResult(current is Poke or Great or Ultra),
EncounterEgg => VerifyBallEgg(enc, current, pk), // Inheritance rules can vary.
IEncounterEgg egg => VerifyBallEgg(egg, current, pk), // Inheritance rules can vary.
EncounterStatic5Entree => VerifyBallEquals(current, BallUseLegality.DreamWorldBalls),
_ => VerifyBallEquals(current, BallUseLegality.GetWildBalls(enc.Generation, enc.Version)),
};
}
private static BallVerificationResult VerifyBallEgg(IEncounterTemplate enc, Ball ball, PKM pk)
private static BallVerificationResult VerifyBallEgg(IEncounterEgg enc, Ball ball, PKM pk)
{
if (enc.Generation < 6) // No inheriting Balls
return VerifyBallEquals(ball, Poke); // Must be Poké Ball -- no ball inheritance.
@ -105,17 +105,17 @@ private static BallVerificationResult VerifyBallEgg(IEncounterTemplate enc, Ball
};
}
private static BallVerificationResult VerifyBallInherited(IEncounterTemplate enc, Ball ball, PKM pk) => enc.Context switch
private static BallVerificationResult VerifyBallInherited(IEncounterEgg egg, Ball ball, PKM pk) => egg switch
{
EntityContext.Gen6 => VerifyBallEggGen6(enc, ball, pk), // Gen6 Inheritance Rules
EntityContext.Gen7 => VerifyBallEggGen7(enc, ball, pk), // Gen7 Inheritance Rules
EntityContext.Gen8 => VerifyBallEggGen8(enc, ball),
EntityContext.Gen8b => VerifyBallEggGen8BDSP(enc, ball),
EntityContext.Gen9 => VerifyBallEggGen9(enc, ball),
EncounterEgg6 e6 => VerifyBallEggGen6(e6, ball, pk), // Gen6 Inheritance Rules
EncounterEgg7 e7 => VerifyBallEggGen7(e7, ball, pk), // Gen7 Inheritance Rules
EncounterEgg8 e8 => VerifyBallEggGen8(e8, ball),
EncounterEgg8b b => VerifyBallEggGen8BDSP(b, ball),
EncounterEgg9 e9 => VerifyBallEggGen9(e9, ball),
_ => BadEncounter,
};
private static BallVerificationResult VerifyBallEggGen6(IEncounterTemplate enc, Ball ball, PKM pk)
private static BallVerificationResult VerifyBallEggGen6(EncounterEgg6 enc, Ball ball, PKM pk)
{
if (ball > Dream)
return BadOutOfRange;
@ -124,7 +124,7 @@ private static BallVerificationResult VerifyBallEggGen6(IEncounterTemplate enc,
return GetResult(result);
}
private static BallVerificationResult VerifyBallEggGen7(IEncounterTemplate enc, Ball ball, PKM pk)
private static BallVerificationResult VerifyBallEggGen7(EncounterEgg7 enc, Ball ball, PKM pk)
{
if (ball > Beast)
return BadOutOfRange;
@ -133,7 +133,7 @@ private static BallVerificationResult VerifyBallEggGen7(IEncounterTemplate enc,
return GetResult(result);
}
private static BallVerificationResult VerifyBallEggGen8BDSP(IEncounterTemplate enc, Ball ball)
private static BallVerificationResult VerifyBallEggGen8BDSP(EncounterEgg8b enc, Ball ball)
{
if (ball > Beast)
return BadOutOfRange;
@ -146,7 +146,7 @@ private static BallVerificationResult VerifyBallEggGen8BDSP(IEncounterTemplate e
return GetResult(result);
}
private static BallVerificationResult VerifyBallEggGen8(IEncounterTemplate enc, Ball ball)
private static BallVerificationResult VerifyBallEggGen8(EncounterEgg8 enc, Ball ball)
{
if (ball > Beast)
return BadOutOfRange;
@ -155,7 +155,7 @@ private static BallVerificationResult VerifyBallEggGen8(IEncounterTemplate enc,
return GetResult(result);
}
private static BallVerificationResult VerifyBallEggGen9(IEncounterTemplate enc, Ball ball)
private static BallVerificationResult VerifyBallEggGen9(EncounterEgg9 enc, Ball ball)
{
if (ball > Beast)
return BadOutOfRange;

View File

@ -62,7 +62,7 @@ private CheckResult VerifyForm(LegalityAnalysis data)
return GetInvalid(LFormBattle);
case Pikachu when enc.Generation >= 7: // Cap
var expectForm = enc is EncounterInvalid or EncounterEgg ? 0 : enc.Form;
var expectForm = enc is EncounterInvalid or IEncounterEgg ? 0 : enc.Form;
if (form != expectForm)
{
bool gift = enc is MysteryGift g && g.Form != form;
@ -108,7 +108,7 @@ private CheckResult VerifyForm(LegalityAnalysis data)
case Scatterbug or Spewpa or Vivillon when enc.Context is EntityContext.Gen9:
if (form > 18 && enc.Form != form) // Pokéball
return GetInvalid(LFormVivillonEventPre);
if (form != 18 && enc is EncounterEgg) // Fancy
if (form != 18 && enc is IEncounterEgg) // Fancy
return GetInvalid(LFormVivillonNonNative);
break;
case Scatterbug or Spewpa:

View File

@ -253,7 +253,7 @@ private void VerifySVStats(LegalityAnalysis data, PK9 pk9)
}
var enc = data.EncounterOriginal;
if (enc is EncounterEgg { Context: EntityContext.Gen9 } g)
if (enc is EncounterEgg9 g)
{
if (!Tera9RNG.IsMatchTeraTypePersonalEgg(g.Species, g.Form, (byte)pk9.TeraTypeOriginal))
data.AddLine(GetInvalid(LTeraTypeMismatch));
@ -279,9 +279,9 @@ private void VerifySVStats(LegalityAnalysis data, PK9 pk9)
if (!Locations9.IsAccessiblePreDLC(pk9.MetLocation))
{
if (enc is { Species: (int)Species.Larvesta, Form: 0 } and not EncounterEgg)
if (enc is { Species: (int)Species.Larvesta, Form: 0 } and not EncounterEgg9)
DisallowLevelUpMove(24, (ushort)Move.BugBite, pk9, data);
else if (enc is { Species: (int)Species.Zorua, Form: 1 } and not EncounterEgg)
else if (enc is { Species: (int)Species.Zorua, Form: 1 } and not EncounterEgg9)
DisallowLevelUpMove(28, (ushort)Move.Spite, pk9, data);
else
return;
@ -500,7 +500,7 @@ private static void VerifyMiscEggCommon(LegalityAnalysis data)
if (!EggStateLegality.GetIsEggHatchCyclesValid(pk, enc))
data.AddLine(GetInvalid(LEggHatchCycles, Egg));
if (pk.Format >= 6 && enc is EncounterEgg && !MovesMatchRelearn(pk))
if (pk.Format >= 6 && enc is IEncounterEgg && !MovesMatchRelearn(pk))
{
const int moveCount = 4;
var sb = new StringBuilder(64);

View File

@ -26,15 +26,15 @@ public override void Verify(LegalityAnalysis data)
data.AddLine(Get(LPIDZero, Severity.Fishy));
if (!pk.Nature.IsFixed()) // out of range
data.AddLine(GetInvalid(LPIDNatureMismatch));
if (data.Info.EncounterMatch is EncounterEgg egg)
if (data.Info.EncounterMatch is IEncounterEgg egg)
VerifyEggPID(data, pk, egg);
VerifyShiny(data);
}
private static void VerifyEggPID(LegalityAnalysis data, PKM pk, EncounterEgg egg)
private static void VerifyEggPID(LegalityAnalysis data, PKM pk, IEncounterEgg egg)
{
if (egg.Generation is 4 && pk.EncryptionConstant == 0)
if (egg is EncounterEgg4)
{
// Gen4 Eggs are "egg available" based on the stored PID value in the save file.
// If this value is 0 or is generated as 0 (possible), the game will see "false" and no egg is available.
@ -42,19 +42,30 @@ private static void VerifyEggPID(LegalityAnalysis data, PKM pk, EncounterEgg egg
// However, With Masuda Method, the egg PID is re-rolled with the ARNG (until shiny, at most 4 times) upon receipt.
// None of the un-rolled states share the same shiny-xor as PID=0, you can re-roll into an all-zero PID.
// Flag it as fishy, because more often than not, it is hacked rather than a legitimately obtained egg.
data.AddLine(Get(LPIDEncryptZero, Severity.Fishy, CheckIdentifier.EC));
return;
}
if (pk.EncryptionConstant == 0)
data.AddLine(Get(LPIDEncryptZero, Severity.Fishy, CheckIdentifier.EC));
if (egg.Generation is 3 or 4 && Breeding.IsGenderSpeciesDetermination(egg.Species))
{
var gender = pk.Gender;
if (!Breeding.IsValidSpeciesBit34(pk.EncryptionConstant, gender)) // 50/50 chance!
{
if (gender == 1 || IsEggBitRequiredMale34(data.Info.Moves))
data.AddLine(GetInvalid(LPIDGenderMismatch, CheckIdentifier.EC));
}
if (Breeding.IsGenderSpeciesDetermination(egg.Species))
VerifyEggGender8000(data, pk);
}
else if (egg is EncounterEgg3)
{
if (!Daycare3.IsValidProcPID(pk.EncryptionConstant, egg.Version))
data.AddLine(Get(LPIDEncryptZero, Severity.Invalid, CheckIdentifier.EC));
if (Breeding.IsGenderSpeciesDetermination(egg.Species))
VerifyEggGender8000(data, pk);
// PID and IVs+Inheritance randomness is sufficiently random; any permutation of vBlank correlations is possible.
}
}
private static void VerifyEggGender8000(LegalityAnalysis data, PKM pk)
{
var gender = pk.Gender;
if (Breeding.IsValidSpeciesBit34(pk.EncryptionConstant, gender))
return; // 50/50 chance!
if (gender == 1 || IsEggBitRequiredMale34(data.Info.Moves))
data.AddLine(GetInvalid(LPIDGenderMismatch, CheckIdentifier.EC));
}
private void VerifyShiny(LegalityAnalysis data)

View File

@ -102,7 +102,7 @@ private void VerifyTrashBytesHOME(LegalityAnalysis data, PKM pk)
VerifyTrashNickname(data, pk.NicknameTrash);
var enc = data.Info.EncounterMatch;
if (enc is EncounterEgg && pk.WasTradedEgg)
if (enc is IEncounterEgg && pk.WasTradedEgg)
{
// Allow Traded eggs to have a single layer of OT trash bytes.
VerifyTrashSingle(data, pk.OriginalTrainerTrash, OriginalTrainer);

View File

@ -101,7 +101,7 @@ public override string CardTitle
public override ushort Location { get => (ushort)(IsEgg ? 0 : Gift.EggLocation + 3000); set { } }
public override ushort EggLocation { get => (ushort)(IsEgg ? Gift.EggLocation + 3000 : 0); set { } }
public bool IsCompatible(PIDType type, PKM pk) => Gift.IsCompatible(type, pk);
public RandomCorrelationRating IsCompatible(PIDType type, PKM pk) => Gift.IsCompatible(type, pk);
public PIDType GetSuggestedCorrelation() => Gift.GetSuggestedCorrelation();
public bool GiftEquals(PGT pgt)

View File

@ -1,6 +1,7 @@
using System;
using static System.Buffers.Binary.BinaryPrimitives;
using static PKHeX.Core.GiftType4;
using static PKHeX.Core.RandomCorrelationRating;
namespace PKHeX.Core;
@ -317,6 +318,9 @@ private static void SetDefaultManaphyEggDetails(PK4 pk4, ITrainerInfo trainer)
pk4.EggMetDate = pk4.MetDate = EncounterDate.GetDateNDS();
}
public bool HasPID => PK.PID > 1; // 0=Random, 1=Random (Anti-Shiny). 0 was never used in any Gen4 gift (all non-shiny).
public bool HasIVs => (PK.IV32 & 0x3FFF_FFFFu) != 0; // ignore Nickname/Egg flag bits
private static void SetPINGA(PK4 pk4, PersonalInfo4 pi, EncounterCriteria criteria)
{
// Ability is forced already, can't force anything
@ -449,13 +453,13 @@ public static bool IsRangerManaphy(PKM pk)
public bool RibbonChampionWorld { get => PK.RibbonChampionWorld; set => PK.RibbonChampionWorld = value; }
public bool RibbonSouvenir { get => PK.RibbonSouvenir; set => PK.RibbonSouvenir = value; }
public bool IsCompatible(PIDType type, PKM pk)
public RandomCorrelationRating IsCompatible(PIDType type, PKM pk)
{
if (IsManaphyEgg)
return IsG4ManaphyPIDValid(type, pk);
return IsG4ManaphyPIDValid(type, pk) ? Match : Mismatch;
if (PK.PID != 1 && type == PIDType.G5MGShiny)
return true;
return type == PIDType.None;
return Match;
return type is PIDType.None ? Match : Mismatch;
}
public PIDType GetSuggestedCorrelation()

View File

@ -32,7 +32,7 @@ public void SimulatorGetEncounters()
var first = encounters.FirstOrDefault();
Assert.NotNull(first);
var egg = (EncounterEgg)first;
var egg = (EncounterEgg7)first;
var info = new SimpleTrainerInfo(GameVersion.SN);
var pk = egg.ConvertToPKM(info);
Assert.True(pk.Species != set.Species);