Expose shiny potential value

Not really digging it currently as it doesn't cover multi-state like AlwaysStar-Or-Never, but that single edge case can be handled elsewhere
This commit is contained in:
Kurt 2022-01-05 16:46:23 -08:00 committed by Kurt
parent a08330ffd4
commit f83a9bf833
29 changed files with 182 additions and 58 deletions

View File

@ -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);

View File

@ -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() { }

View File

@ -7,7 +7,7 @@ namespace PKHeX.Core
/// Wild Encounter Slot data
/// </summary>
/// <remarks>Wild encounter slots are found as random encounters in-game.</remarks>
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
}
}
/// <summary>
/// Returns a required ball if the wild encounter can only be caught in certain scenarios.
/// </summary>
/// <returns><see cref="Ball.None"/> if unrestricted, otherwise, a specific ball value.</returns>
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);

View File

@ -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)
{

View File

@ -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)
{

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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();
}

View File

@ -5,7 +5,7 @@ namespace PKHeX.Core
/// <summary>
/// Contains details about an encounter that can be found in <see cref="GameVersion.GO"/>.
/// </summary>
public abstract record EncounterSlotGO : EncounterSlot, IPogoSlot, IFixedBall
public abstract record EncounterSlotGO : EncounterSlot, IPogoSlot
{
/// <inheritdoc/>
public int Start { get; }
@ -13,8 +13,7 @@ public abstract record EncounterSlotGO : EncounterSlot, IPogoSlot, IFixedBall
/// <inheritdoc/>
public int End { get; }
/// <inheritdoc/>
public Shiny Shiny { get; }
public override Shiny Shiny { get; }
/// <inheritdoc/>
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)
{

View File

@ -9,7 +9,7 @@ namespace PKHeX.Core
/// <remarks>
/// Static Encounters are fixed position encounters with properties that are not subject to Wild Encounter conditions.
/// </remarks>
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; }

View File

@ -10,7 +10,7 @@ namespace PKHeX.Core
/// <remarks>
/// Trade data is fixed level in all cases except for the first few generations of games.
/// </remarks>
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; }

View File

@ -4,7 +4,7 @@
/// Common Encounter Properties base interface.
/// <inheritdoc cref="IEncounterInfo"/>
/// </summary>
public interface IEncounterable : IEncounterInfo
public interface IEncounterable : IEncounterInfo, ILocation, IFixedAbilityNumber, IFixedBall, IShinyPotential
{
/// <summary>
/// Short name to describe the encounter data, usually just indicating which of the main component encounter types the data is.

View File

@ -0,0 +1,6 @@
namespace PKHeX.Core;
public interface IResultantShiny
{
Shiny CanBeShiny { get; }
}

View File

@ -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,
};
}
}

View File

@ -1047,13 +1047,13 @@ internal static class BallBreedLegality
/// <remarks>Not all things can hatch with a Poké Ball!</remarks>
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;
}
}
}

View File

@ -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);

View File

@ -8,7 +8,7 @@ namespace PKHeX.Core
/// <summary>
/// Mystery Gift Template File
/// </summary>
public abstract class MysteryGift : IEncounterable, IMoveset, IRelearn, ILocation, IFixedBall, IFixedAbilityNumber
public abstract class MysteryGift : IEncounterable, IMoveset, IRelearn
{
/// <summary>
/// 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<int> Relearn { get => Array.Empty<int>(); set { } }
public virtual int[] IVs { get => Array.Empty<int>(); 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 { } }

View File

@ -80,6 +80,7 @@ public override string CardTitle
public override IReadOnlyList<int> 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; }

View File

@ -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<int> finalIVs = stackalloc int[6];

View File

@ -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) { }

View File

@ -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
{

View File

@ -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));

View File

@ -32,7 +32,7 @@ public sealed class WC3 : MysteryGift, IRibbonSetEvent3, ILangNicknamedTemplate
public override bool IsEgg { get; set; }
public override IReadOnlyList<int> Moves { get; set; } = Array.Empty<int>();
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

View File

@ -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; }

View File

@ -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
{

View File

@ -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));

View File

@ -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; }

View File

@ -0,0 +1,9 @@
namespace PKHeX.Core;
/// <summary>
/// Interface that exposes a shiny potential state indicating what kinds of <see cref="Core.Shiny"/> can be expressed.
/// </summary>
public interface IShinyPotential
{
Shiny Shiny { get; }
}