diff --git a/PKHeX.Core/Legality/Encounters/EncounterMisc/EncounterEgg.cs b/PKHeX.Core/Legality/Encounters/EncounterMisc/EncounterEgg.cs
index 8914e53e0..874aa497a 100644
--- a/PKHeX.Core/Legality/Encounters/EncounterMisc/EncounterEgg.cs
+++ b/PKHeX.Core/Legality/Encounters/EncounterMisc/EncounterEgg.cs
@@ -14,6 +14,11 @@ public sealed record EncounterEgg(int Species, int Form, int Level, int Generati
public int LevelMin => Level;
public int LevelMax => Level;
public bool IsShiny => false;
+ public int Location => 0;
+ public int EggLocation => Locations.GetDaycareLocation(Generation, Version);
+ public Ball FixedBall => BallBreedLegality.GetDefaultBall(Version, Species);
+ public Shiny Shiny => Shiny.Random;
+ public int Ability => 0;
public bool CanHaveVoltTackle => Species is (int)Core.Species.Pichu && (Generation > 3 || Version is GameVersion.E);
@@ -34,7 +39,9 @@ public PKM ConvertToPKM(ITrainerInfo sav, EncounterCriteria criteria)
pk.Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, gen);
pk.CurrentLevel = Level;
pk.Version = (int)version;
- pk.Ball = (int)BallBreedLegality.GetDefaultBall(Version, Species);
+
+ var ball = FixedBall;
+ pk.Ball = ball is Ball.None ? (int)Ball.Poke : (int)ball;
pk.OT_Friendship = pk.PersonalInfo.BaseFriendship;
SetEncounterMoves(pk, version);
diff --git a/PKHeX.Core/Legality/Encounters/EncounterMisc/EncounterInvalid.cs b/PKHeX.Core/Legality/Encounters/EncounterMisc/EncounterInvalid.cs
index ab0c847cf..10502d352 100644
--- a/PKHeX.Core/Legality/Encounters/EncounterMisc/EncounterInvalid.cs
+++ b/PKHeX.Core/Legality/Encounters/EncounterMisc/EncounterInvalid.cs
@@ -17,9 +17,14 @@ public sealed record EncounterInvalid : IEncounterable
public int Generation { get; }
public GameVersion Version { get; }
public bool IsShiny => false;
+ public Shiny Shiny => Shiny.Never;
public string Name => "Invalid";
public string LongName => "Invalid";
+ public int Location => 0;
+ public int EggLocation => 0;
+ public int Ability => 0;
+ public Ball FixedBall => Ball.None;
private EncounterInvalid() { }
diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot.cs
index e12f9758d..f3dec9e3a 100644
--- a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot.cs
+++ b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot.cs
@@ -7,7 +7,7 @@ namespace PKHeX.Core
/// Wild Encounter Slot data
///
/// Wild encounter slots are found as random encounters in-game.
- public abstract record EncounterSlot(EncounterArea Area, int Species, int Form, int LevelMin, int LevelMax) : IEncounterable, ILocation, IEncounterMatch, IFixedAbilityNumber
+ public abstract record EncounterSlot(EncounterArea Area, int Species, int Form, int LevelMin, int LevelMax) : IEncounterable, IEncounterMatch
{
public abstract int Generation { get; }
public bool EggEncounter => false;
@@ -17,6 +17,8 @@ public abstract record EncounterSlot(EncounterArea Area, int Species, int Form,
public GameVersion Version => Area.Version;
public int Location => Area.Location;
public int EggLocation => 0;
+ public virtual Ball FixedBall => Ball.None;
+ public virtual Shiny Shiny => Shiny.Random;
public bool IsFixedLevel => LevelMin == LevelMax;
public bool IsRandomLevel => LevelMin != LevelMax;
@@ -68,12 +70,6 @@ public virtual string LongName
}
}
- ///
- /// Returns a required ball if the wild encounter can only be caught in certain scenarios.
- ///
- /// if unrestricted, otherwise, a specific ball value.
- public virtual Ball GetRequiredBallValue() => Ball.None;
-
public PKM ConvertToPKM(ITrainerInfo sav) => ConvertToPKM(sav, EncounterCriteria.Unrestricted);
public PKM ConvertToPKM(ITrainerInfo sav, EncounterCriteria criteria)
@@ -95,7 +91,7 @@ protected virtual void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria
pk.Version = (int)version;
pk.Nickname = SpeciesName.GetSpeciesNameGeneration(Species, lang, Generation);
- var ball = GetRequiredBallValue();
+ var ball = FixedBall;
pk.Ball = (int)(ball == Ball.None ? Ball.Poke : ball);
pk.Language = lang;
pk.Form = GetWildForm(pk, Form, sav);
diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot1.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot1.cs
index 18a5256e7..63cdd6c0a 100644
--- a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot1.cs
+++ b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot1.cs
@@ -8,6 +8,7 @@ public sealed record EncounterSlot1 : EncounterSlot, INumberedSlot
{
public override int Generation => 1;
public int SlotNumber { get; }
+ public override Ball FixedBall => Ball.Poke;
public EncounterSlot1(EncounterArea1 area, int species, int min, int max, int slot) : base(area, species, 0, min, max)
{
diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot2.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot2.cs
index 362f40580..63dc9db35 100644
--- a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot2.cs
+++ b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot2.cs
@@ -13,6 +13,7 @@ public sealed record EncounterSlot2 : EncounterSlot, INumberedSlot
{
public override int Generation => 2;
public int SlotNumber { get; }
+ public override Ball FixedBall => Ball.Poke;
public EncounterSlot2(EncounterArea2 area, int species, int min, int max, int slot) : base(area, species, species == 201 ? FormRandom : 0, min, max)
{
diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot3.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot3.cs
index ed476ad22..30b30a5e0 100644
--- a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot3.cs
+++ b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot3.cs
@@ -15,6 +15,7 @@ public record EncounterSlot3 : EncounterSlot, IMagnetStatic, INumberedSlot, ISlo
public SlotType Type => Area.Type;
public int SlotNumber { get; }
+ public override Ball FixedBall => Locations.IsSafariZoneLocation3(Location) ? Ball.Safari : Ball.None;
public EncounterSlot3(EncounterArea3 area, int species, int form, int min, int max, int slot, int mpi, int mpc, int sti, int stc) : base(area, species, form, min, max)
{
@@ -36,6 +37,5 @@ public override EncounterMatchRating GetMatchRating(PKM pkm)
private bool IsDeferredSafari3(bool IsSafariBall) => IsSafariBall != Locations.IsSafariZoneLocation3(Location);
- public override Ball GetRequiredBallValue() => Locations.IsSafariZoneLocation3(Location) ? Ball.Safari : Ball.None;
}
}
diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot4.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot4.cs
index ee4684cea..492c32682 100644
--- a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot4.cs
+++ b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot4.cs
@@ -16,6 +16,7 @@ public sealed record EncounterSlot4 : EncounterSlot, IMagnetStatic, INumberedSlo
public SlotType Type => Area.Type;
public int SlotNumber { get; }
+ public override Ball FixedBall => GetRequiredBallValue();
public bool CanUseRadar => !GameVersion.HGSS.Contains(Version) && GroundTile.HasFlag(GroundTilePermission.Grass);
public EncounterSlot4(EncounterArea4 area, int species, int form, int min, int max, int slot, int mpi, int mpc, int sti, int stc) : base(area, species, form, min, max)
@@ -44,7 +45,7 @@ public override EncounterMatchRating GetMatchRating(PKM pkm)
return base.GetMatchRating(pkm);
}
- public override Ball GetRequiredBallValue()
+ private Ball GetRequiredBallValue()
{
if (Type is SlotType.BugContest)
return Ball.Sport;
diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot7.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot7.cs
index e037fab1f..7976e0762 100644
--- a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot7.cs
+++ b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot7.cs
@@ -31,6 +31,6 @@ protected override void SetPINGA(PKM pk, EncounterCriteria criteria)
protected override HiddenAbilityPermission IsHiddenAbilitySlot() => IsSOS ? HiddenAbilityPermission.Possible : HiddenAbilityPermission.Never;
- public override Ball GetRequiredBallValue() => Location == Locations.Pelago7 ? Ball.Poke : Ball.None;
+ public override Ball FixedBall => Location == Locations.Pelago7 ? Ball.Poke : Ball.None;
}
}
diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot8b.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot8b.cs
index c76d010e9..e5b43d45f 100644
--- a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot8b.cs
+++ b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot8b.cs
@@ -13,6 +13,7 @@ public sealed record EncounterSlot8b : EncounterSlot
public bool IsUnderground => Area.Location is (>= 508 and <= 617);
public bool IsMarsh => Area.Location is (>= 219 and <= 224);
public readonly bool IsBCAT;
+ public override Ball FixedBall => IsMarsh ? Ball.Safari : Ball.None;
public EncounterSlot8b(EncounterArea area, int species, int form, int min, int max, bool isBCAT = false) : base(area, species, form, min, max)
{
@@ -34,10 +35,6 @@ protected override void SetFormatSpecificData(PKM pk)
if (GetBaseEggMove(out int move1))
pk.RelearnMove1 = move1;
}
- else if (IsMarsh)
- {
- pk.Ball = (int)Ball.Safari;
- }
pk.SetRandomEC();
}
diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/EncounterSlotGO.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/EncounterSlotGO.cs
index 95c173586..34309aed7 100644
--- a/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/EncounterSlotGO.cs
+++ b/PKHeX.Core/Legality/Encounters/EncounterSlot/GO/EncounterSlotGO.cs
@@ -5,7 +5,7 @@ namespace PKHeX.Core
///
/// Contains details about an encounter that can be found in .
///
- public abstract record EncounterSlotGO : EncounterSlot, IPogoSlot, IFixedBall
+ public abstract record EncounterSlotGO : EncounterSlot, IPogoSlot
{
///
public int Start { get; }
@@ -13,8 +13,7 @@ public abstract record EncounterSlotGO : EncounterSlot, IPogoSlot, IFixedBall
///
public int End { get; }
- ///
- public Shiny Shiny { get; }
+ public override Shiny Shiny { get; }
///
public PogoType Type { get; }
@@ -24,7 +23,7 @@ public abstract record EncounterSlotGO : EncounterSlot, IPogoSlot, IFixedBall
public override bool IsShiny => Shiny.IsShiny();
- public Ball FixedBall => Type.GetValidBall();
+ public override Ball FixedBall => Type.GetValidBall();
protected EncounterSlotGO(EncounterArea area, int species, int form, int start, int end, Shiny shiny, Gender gender, PogoType type) : base(area, species, form, type.GetMinLevel(), EncountersGO.MAX_LEVEL)
{
diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic.cs
index d077311df..542770238 100644
--- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic.cs
+++ b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic.cs
@@ -9,7 +9,7 @@ namespace PKHeX.Core
///
/// Static Encounters are fixed position encounters with properties that are not subject to Wild Encounter conditions.
///
- public abstract record EncounterStatic(GameVersion Version) : IEncounterable, IMoveset, ILocation, IEncounterMatch, IFixedBall, IFixedAbilityNumber
+ public abstract record EncounterStatic(GameVersion Version) : IEncounterable, IMoveset, IEncounterMatch
{
public int Species { get; init; }
public int Form { get; init; }
diff --git a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade.cs b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade.cs
index 09d516743..52aad583a 100644
--- a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade.cs
+++ b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade.cs
@@ -10,7 +10,7 @@ namespace PKHeX.Core
///
/// Trade data is fixed level in all cases except for the first few generations of games.
///
- public abstract record EncounterTrade(GameVersion Version) : IEncounterable, IMoveset, ILocation, IEncounterMatch, IFixedBall, IFixedAbilityNumber
+ public abstract record EncounterTrade(GameVersion Version) : IEncounterable, IMoveset, IEncounterMatch
{
public int Species { get; init; }
public int Form { get; init; }
diff --git a/PKHeX.Core/Legality/Encounters/IEncounterable.cs b/PKHeX.Core/Legality/Encounters/IEncounterable.cs
index 71418e547..ee86542a2 100644
--- a/PKHeX.Core/Legality/Encounters/IEncounterable.cs
+++ b/PKHeX.Core/Legality/Encounters/IEncounterable.cs
@@ -4,7 +4,7 @@
/// Common Encounter Properties base interface.
///
///
- public interface IEncounterable : IEncounterInfo
+ public interface IEncounterable : IEncounterInfo, ILocation, IFixedAbilityNumber, IFixedBall, IShinyPotential
{
///
/// Short name to describe the encounter data, usually just indicating which of the main component encounter types the data is.
diff --git a/PKHeX.Core/Legality/Structures/IResultantShiny.cs b/PKHeX.Core/Legality/Structures/IResultantShiny.cs
new file mode 100644
index 000000000..5c4122a83
--- /dev/null
+++ b/PKHeX.Core/Legality/Structures/IResultantShiny.cs
@@ -0,0 +1,6 @@
+namespace PKHeX.Core;
+
+public interface IResultantShiny
+{
+ Shiny CanBeShiny { get; }
+}
diff --git a/PKHeX.Core/Legality/Tables/Locations.cs b/PKHeX.Core/Legality/Tables/Locations.cs
index 33c19cefc..09e8ea678 100644
--- a/PKHeX.Core/Legality/Tables/Locations.cs
+++ b/PKHeX.Core/Legality/Tables/Locations.cs
@@ -153,5 +153,14 @@ public static bool IsEggLocationBred4(int loc, GameVersion ver)
GameVersion.BD or GameVersion.SP => Default8bNone,
_ => 0,
};
+
+ public static int GetDaycareLocation(int generation, GameVersion version) => generation switch
+ {
+ 1 or 2 or 3 => 0,
+ 4 => Daycare4,
+ 5 => Daycare5,
+ 8 when version is GameVersion.BD or GameVersion.SP => Daycare8b,
+ _ => Daycare5,
+ };
}
}
diff --git a/PKHeX.Core/Legality/Verifiers/Ball/BallBreedLegality.cs b/PKHeX.Core/Legality/Verifiers/Ball/BallBreedLegality.cs
index 4b4766848..77fb86f99 100644
--- a/PKHeX.Core/Legality/Verifiers/Ball/BallBreedLegality.cs
+++ b/PKHeX.Core/Legality/Verifiers/Ball/BallBreedLegality.cs
@@ -1047,13 +1047,13 @@ internal static class BallBreedLegality
/// Not all things can hatch with a Poké Ball!
public static Ball GetDefaultBall(GameVersion version, int species)
{
- if (GameVersion.BDSP.Contains(version))
+ if (version is GameVersion.BD or GameVersion.SP)
{
if (BanInheritedExceptSafari_BDSP.Contains(species))
return Ball.Safari;
}
- return Ball.Poke;
+ return Ball.None;
}
}
}
diff --git a/PKHeX.Core/Legality/Verifiers/Ball/BallVerifier.cs b/PKHeX.Core/Legality/Verifiers/Ball/BallVerifier.cs
index 16aa2d90d..94fd2393c 100644
--- a/PKHeX.Core/Legality/Verifiers/Ball/BallVerifier.cs
+++ b/PKHeX.Core/Legality/Verifiers/Ball/BallVerifier.cs
@@ -82,7 +82,7 @@ private CheckResult VerifyBallStatic(LegalityAnalysis data, EncounterStatic s)
private CheckResult VerifyBallWild(LegalityAnalysis data, EncounterSlot w)
{
- var req = w.GetRequiredBallValue();
+ var req = w.FixedBall;
if (req != None)
return VerifyBallEquals(data, (int) req);
diff --git a/PKHeX.Core/MysteryGifts/MysteryGift.cs b/PKHeX.Core/MysteryGifts/MysteryGift.cs
index bb226c762..80a703adc 100644
--- a/PKHeX.Core/MysteryGifts/MysteryGift.cs
+++ b/PKHeX.Core/MysteryGifts/MysteryGift.cs
@@ -8,7 +8,7 @@ namespace PKHeX.Core
///
/// Mystery Gift Template File
///
- public abstract class MysteryGift : IEncounterable, IMoveset, IRelearn, ILocation, IFixedBall, IFixedAbilityNumber
+ public abstract class MysteryGift : IEncounterable, IMoveset, IRelearn
{
///
/// Determines whether or not the given length of bytes is valid for a mystery gift.
@@ -133,6 +133,13 @@ public virtual GameVersion Version
public virtual IReadOnlyList Relearn { get => Array.Empty(); set { } }
public virtual int[] IVs { get => Array.Empty(); set { } }
public virtual bool IsShiny => false;
+
+ public virtual Shiny Shiny
+ {
+ get => Shiny.Never;
+ init => throw new InvalidOperationException();
+ }
+
public virtual bool IsEgg { get => false; set { } }
public virtual int HeldItem { get => -1; set { } }
public virtual int AbilityType { get => -1; set { } }
diff --git a/PKHeX.Core/MysteryGifts/PCD.cs b/PKHeX.Core/MysteryGifts/PCD.cs
index 9ec37d8ab..64d4871b9 100644
--- a/PKHeX.Core/MysteryGifts/PCD.cs
+++ b/PKHeX.Core/MysteryGifts/PCD.cs
@@ -80,6 +80,7 @@ public override string CardTitle
public override IReadOnlyList Moves { get => Gift.Moves; set => Gift.Moves = value; }
public override int HeldItem { get => Gift.HeldItem; set => Gift.HeldItem = value; }
public override bool IsShiny => Gift.IsShiny;
+ public override Shiny Shiny => Gift.Shiny;
public override bool IsEgg { get => Gift.IsEgg; set => Gift.IsEgg = value; }
public override int Gender { get => Gift.Gender; set => Gift.Gender = value; }
public override int Form { get => Gift.Form; set => Gift.Form = value; }
diff --git a/PKHeX.Core/MysteryGifts/PGF.cs b/PKHeX.Core/MysteryGifts/PGF.cs
index 3fb113996..f191e4d28 100644
--- a/PKHeX.Core/MysteryGifts/PGF.cs
+++ b/PKHeX.Core/MysteryGifts/PGF.cs
@@ -329,6 +329,13 @@ private void SetPID(PKM pk, int av)
pk.PID &= 0xFFFEFFFF;
}
+ public override Shiny Shiny => PIDType switch
+ {
+ 1 => Shiny.Random,
+ 2 => Shiny.Always,
+ _ => Shiny.Never,
+ };
+
private void SetIVs(PKM pk)
{
Span finalIVs = stackalloc int[6];
diff --git a/PKHeX.Core/MysteryGifts/PGT.cs b/PKHeX.Core/MysteryGifts/PGT.cs
index 888afbe00..08f17a200 100644
--- a/PKHeX.Core/MysteryGifts/PGT.cs
+++ b/PKHeX.Core/MysteryGifts/PGT.cs
@@ -47,6 +47,7 @@ private enum GiftType
public override string CardTitle { get => "Raw Gift (PGT)"; set { } }
public override int CardID { get => -1; set { } }
public override bool GiftUsed { get => false; set { } }
+ public override Shiny Shiny => IsEgg ? Shiny.Random : PK.PID == 1 ? Shiny.Never : IsShiny ? Shiny.Always : Shiny.Never;
public PGT() : this(new byte[Size]) { }
public PGT(byte[] data) : base(data) { }
diff --git a/PKHeX.Core/MysteryGifts/WB7.cs b/PKHeX.Core/MysteryGifts/WB7.cs
index 6f0a35315..1c83faa37 100644
--- a/PKHeX.Core/MysteryGifts/WB7.cs
+++ b/PKHeX.Core/MysteryGifts/WB7.cs
@@ -134,6 +134,7 @@ public override int Quantity
// Pokémon Properties
public override bool IsPokémon { get => CardType == 0; set { if (value) CardType = 0; } }
public override bool IsShiny => PIDType == Shiny.Always;
+ public override Shiny Shiny => PIDType;
public override int TID
{
diff --git a/PKHeX.Core/MysteryGifts/WB8.cs b/PKHeX.Core/MysteryGifts/WB8.cs
index f1948eaf1..526684779 100644
--- a/PKHeX.Core/MysteryGifts/WB8.cs
+++ b/PKHeX.Core/MysteryGifts/WB8.cs
@@ -85,27 +85,36 @@ public override int Quantity
// Pokémon Properties
public override bool IsPokémon { get => CardType == GiftType.Pokemon; set { if (value) CardType = GiftType.Pokemon; } }
- public override bool IsShiny
+ public override bool IsShiny => Shiny.IsShiny();
+
+ public override Shiny Shiny
{
get
{
var type = PIDType;
- if (type is Shiny.AlwaysStar or Shiny.AlwaysSquare)
- return true;
- if (type != Shiny.FixedValue)
- return false;
-
- // Player owned anti-shiny fixed PID
- if (TID == 0 && SID == 0)
- return false;
-
- var pid = PID;
- var psv = (int)((pid >> 16 ^ (pid & 0xFFFF)) >> 4);
- var tsv = (TID ^ SID) >> 4;
- return (psv ^ tsv) == 0;
+ if (type is not Shiny.FixedValue)
+ return type;
+ return GetShinyXor() switch
+ {
+ 0 => Shiny.AlwaysSquare,
+ <= 15 => Shiny.AlwaysStar,
+ _ => Shiny.Never,
+ };
}
}
+ private int GetShinyXor()
+ {
+ // Player owned anti-shiny fixed PID
+ if (TID == 0 && SID == 0)
+ return int.MaxValue;
+
+ var pid = PID;
+ var psv = (int)(pid >> 16 ^ (pid & 0xFFFF));
+ var tsv = (TID ^ SID);
+ return psv ^ tsv;
+ }
+
public override int TID
{
get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x20));
diff --git a/PKHeX.Core/MysteryGifts/WC3.cs b/PKHeX.Core/MysteryGifts/WC3.cs
index e181cfeae..9411d8dd0 100644
--- a/PKHeX.Core/MysteryGifts/WC3.cs
+++ b/PKHeX.Core/MysteryGifts/WC3.cs
@@ -32,7 +32,7 @@ public sealed class WC3 : MysteryGift, IRibbonSetEvent3, ILangNicknamedTemplate
public override bool IsEgg { get; set; }
public override IReadOnlyList Moves { get; set; } = Array.Empty();
public bool NotDistributed { get; init; }
- public Shiny Shiny { get; init; } = Shiny.Random;
+ public override Shiny Shiny { get; init; } = Shiny.Random;
public bool Fateful { get; init; } // Obedience Flag
// Mystery Gift Properties
diff --git a/PKHeX.Core/MysteryGifts/WC6.cs b/PKHeX.Core/MysteryGifts/WC6.cs
index de27f714e..b1da01b34 100644
--- a/PKHeX.Core/MysteryGifts/WC6.cs
+++ b/PKHeX.Core/MysteryGifts/WC6.cs
@@ -122,7 +122,36 @@ private uint Day
// Pokémon Properties
public override bool IsPokémon { get => CardType == 0; set { if (value) CardType = 0; } }
- public override bool IsShiny => PIDType == Shiny.Always;
+ public override bool IsShiny => Shiny.IsShiny();
+
+ public override Shiny Shiny
+ {
+ get
+ {
+ var type = PIDType;
+ if (type is not Shiny.FixedValue)
+ return type;
+ return GetShinyXor() switch
+ {
+ 0 => Shiny.AlwaysSquare,
+ <= 15 => Shiny.AlwaysStar,
+ _ => Shiny.Never,
+ };
+ }
+ }
+
+ private int GetShinyXor()
+ {
+ // Player owned anti-shiny fixed PID
+ if (TID == 0 && SID == 0)
+ return int.MaxValue;
+
+ var pid = PID;
+ var psv = (int)(pid >> 16 ^ (pid & 0xFFFF));
+ var tsv = (TID ^ SID);
+ return psv ^ tsv;
+ }
+
public override int TID { get => ReadUInt16LittleEndian(Data.AsSpan(0x68)); set => WriteUInt16LittleEndian(Data.AsSpan(0x68), (ushort)value); }
public override int SID { get => ReadUInt16LittleEndian(Data.AsSpan(0x6A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x6A), (ushort)value); }
public int OriginGame { get => Data[0x6C]; set => Data[0x6C] = (byte)value; }
diff --git a/PKHeX.Core/MysteryGifts/WC7.cs b/PKHeX.Core/MysteryGifts/WC7.cs
index a40b0cdcb..b6ccecaaa 100644
--- a/PKHeX.Core/MysteryGifts/WC7.cs
+++ b/PKHeX.Core/MysteryGifts/WC7.cs
@@ -137,7 +137,35 @@ public override int Quantity
// Pokémon Properties
public override bool IsPokémon { get => CardType == 0; set { if (value) CardType = 0; } }
- public override bool IsShiny => PIDType == Shiny.Always;
+ public override bool IsShiny => Shiny.IsShiny();
+
+ public override Shiny Shiny
+ {
+ get
+ {
+ var type = PIDType;
+ if (type is not Shiny.FixedValue)
+ return type;
+ return GetShinyXor() switch
+ {
+ 0 => Shiny.AlwaysSquare,
+ <= 15 => Shiny.AlwaysStar,
+ _ => Shiny.Never,
+ };
+ }
+ }
+
+ private int GetShinyXor()
+ {
+ // Player owned anti-shiny fixed PID
+ if (TID == 0 && SID == 0)
+ return int.MaxValue;
+
+ var pid = PID;
+ var psv = (int)(pid >> 16 ^ (pid & 0xFFFF));
+ var tsv = (TID ^ SID);
+ return psv ^ tsv;
+ }
public override int TID
{
diff --git a/PKHeX.Core/MysteryGifts/WC8.cs b/PKHeX.Core/MysteryGifts/WC8.cs
index 57fd57d34..42d20b11b 100644
--- a/PKHeX.Core/MysteryGifts/WC8.cs
+++ b/PKHeX.Core/MysteryGifts/WC8.cs
@@ -79,27 +79,36 @@ public override int Quantity
// Pokémon Properties
public override bool IsPokémon { get => CardType == GiftType.Pokemon; set { if (value) CardType = GiftType.Pokemon; } }
- public override bool IsShiny
+ public override bool IsShiny => Shiny.IsShiny();
+
+ public override Shiny Shiny
{
get
{
var type = PIDType;
- if (type is Shiny.AlwaysStar or Shiny.AlwaysSquare)
- return true;
- if (type != Shiny.FixedValue)
- return false;
-
- // Player owned anti-shiny fixed PID
- if (TID == 0 && SID == 0)
- return false;
-
- var pid = PID;
- var psv = (int)((pid >> 16 ^ (pid & 0xFFFF)) >> 4);
- var tsv = (TID ^ SID) >> 4;
- return (psv ^ tsv) == 0;
+ if (type is not Shiny.FixedValue)
+ return type;
+ return GetShinyXor() switch
+ {
+ 0 => Shiny.AlwaysSquare,
+ <= 15 => Shiny.AlwaysStar,
+ _ => Shiny.Never,
+ };
}
}
+ private int GetShinyXor()
+ {
+ // Player owned anti-shiny fixed PID
+ if (TID == 0 && SID == 0)
+ return int.MaxValue;
+
+ var pid = PID;
+ var psv = (int)(pid >> 16 ^ (pid & 0xFFFF));
+ var tsv = (TID ^ SID);
+ return psv ^ tsv;
+ }
+
public override int TID
{
get => ReadUInt16LittleEndian(Data.AsSpan(CardStart + 0x20));
diff --git a/PKHeX.Core/MysteryGifts/WR7.cs b/PKHeX.Core/MysteryGifts/WR7.cs
index 542993cb6..740624f48 100644
--- a/PKHeX.Core/MysteryGifts/WR7.cs
+++ b/PKHeX.Core/MysteryGifts/WR7.cs
@@ -104,6 +104,7 @@ public LanguageID LanguageReceived
public override bool IsMatchExact(PKM pkm, DexLevel evo) => false;
protected override bool IsMatchDeferred(PKM pkm) => false;
protected override bool IsMatchPartial(PKM pkm) => false;
+ public override Shiny Shiny => Shiny.Never;
public override int Location { get; set; }
public override int EggLocation { get; set; }
diff --git a/PKHeX.Core/PKM/Shared/IShinyPotential.cs b/PKHeX.Core/PKM/Shared/IShinyPotential.cs
new file mode 100644
index 000000000..bb771fd57
--- /dev/null
+++ b/PKHeX.Core/PKM/Shared/IShinyPotential.cs
@@ -0,0 +1,9 @@
+namespace PKHeX.Core;
+
+///
+/// Interface that exposes a shiny potential state indicating what kinds of can be expressed.
+///
+public interface IShinyPotential
+{
+ Shiny Shiny { get; }
+}