mirror of
https://github.com/kwsch/PKHeX.git
synced 2026-03-21 17:48:28 -05:00
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:
parent
b2d70295e9
commit
85f5950f28
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
129
PKHeX.Core/Legality/Encounters/Templates/Gen3/EncounterEgg3.cs
Normal file
129
PKHeX.Core/Legality/Encounters/Templates/Gen3/EncounterEgg3.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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!
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
138
PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterEgg4.cs
Normal file
138
PKHeX.Core/Legality/Encounters/Templates/Gen4/EncounterEgg4.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
112
PKHeX.Core/Legality/Encounters/Templates/Gen5/EncounterEgg5.cs
Normal file
112
PKHeX.Core/Legality/Encounters/Templates/Gen5/EncounterEgg5.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
107
PKHeX.Core/Legality/Encounters/Templates/Gen6/EncounterEgg6.cs
Normal file
107
PKHeX.Core/Legality/Encounters/Templates/Gen6/EncounterEgg6.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
104
PKHeX.Core/Legality/Encounters/Templates/Gen7/EncounterEgg7.cs
Normal file
104
PKHeX.Core/Legality/Encounters/Templates/Gen7/EncounterEgg7.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Breeding Egg Encounter Properties.
|
||||
/// </summary>
|
||||
public interface IEncounterEgg : IEncounterable
|
||||
{
|
||||
ILearnSource Learn { get; }
|
||||
bool CanHaveVoltTackle { get; }
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
279
PKHeX.Core/Legality/RNG/ClassicEra/Gen3/Daycare3.cs
Normal file
279
PKHeX.Core/Legality/RNG/ClassicEra/Gen3/Daycare3.cs
Normal 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.
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user