diff --git a/PKHeX.Core/Legality/Encounters/Templates/GO/EncounterArea7g.cs b/PKHeX.Core/Legality/Encounters/Templates/GO/EncounterArea7g.cs index bcf196a1e..53f91ad79 100644 --- a/PKHeX.Core/Legality/Encounters/Templates/GO/EncounterArea7g.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/GO/EncounterArea7g.cs @@ -31,7 +31,7 @@ internal static EncounterArea7g[] GetArea(BinLinkerAccessor data) } private const int meta = 4; - private const int entrySize = (2 * sizeof(int)) + 2; + private const int entrySize = (2 * sizeof(ushort)) + 8; private static EncounterArea7g GetArea(ReadOnlySpan data) { @@ -61,9 +61,10 @@ private static EncounterSlot7GO ReadSlot(ReadOnlySpan entry, ushort specie var gender = (Gender)entry[5]; var type = (PogoType)entry[6]; //var ball = (PogoBallRestriction)entry[7]; - var minLevel = entry[8]; - var minIV = entry[9]; - return new EncounterSlot7GO(start, end, species, form, minLevel, minIV, shiny, gender, type); + var flags = (PogoFlags)entry[8]; + var minLevel = entry[9]; + var minIV = entry[10]; + return new EncounterSlot7GO(start, end, species, form, minLevel, minIV, shiny, gender, type, flags); } } diff --git a/PKHeX.Core/Legality/Encounters/Templates/GO/EncounterArea8g.cs b/PKHeX.Core/Legality/Encounters/Templates/GO/EncounterArea8g.cs index ed15f5b41..848232d99 100644 --- a/PKHeX.Core/Legality/Encounters/Templates/GO/EncounterArea8g.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/GO/EncounterArea8g.cs @@ -31,7 +31,7 @@ internal static EncounterArea8g[] GetArea(BinLinkerAccessor data) } private const int meta = 4; - private const int entrySize = (2 * sizeof(ushort)) + 6; + private const int entrySize = (2 * sizeof(ushort)) + 8; private static EncounterArea8g GetArea(ReadOnlySpan data) { @@ -61,8 +61,10 @@ private static EncounterSlot8GO ReadSlot(ReadOnlySpan entry, ushort specie var gender = (Gender)entry[5]; var type = (PogoType)entry[6]; var ball = (PogoBallRestriction)entry[7]; - var minLevel = entry[8]; - var minIV = entry[9]; - return new EncounterSlot8GO(start, end, species, form, minLevel, minIV, shiny, gender, type, ball, format); + + var flags = (PogoFlags)entry[8]; + var minLevel = entry[9]; + var minIV = entry[10]; + return new EncounterSlot8GO(start, end, species, form, minLevel, minIV, shiny, gender, type, ball, format, flags); } } diff --git a/PKHeX.Core/Legality/Encounters/Templates/GO/EncounterSlot7GO.cs b/PKHeX.Core/Legality/Encounters/Templates/GO/EncounterSlot7GO.cs index 31b17d232..beb7d0969 100644 --- a/PKHeX.Core/Legality/Encounters/Templates/GO/EncounterSlot7GO.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/GO/EncounterSlot7GO.cs @@ -6,7 +6,7 @@ namespace PKHeX.Core; /// Encounter Slot found in (GO Park). /// /// -public sealed record EncounterSlot7GO(ushort DayStart, ushort DayEnd, ushort Species, byte Form, byte LevelMin, byte MinimumIV, Shiny Shiny, Gender Gender, PogoType Type) +public sealed record EncounterSlot7GO(ushort DayStart, ushort DayEnd, ushort Species, byte Form, byte LevelMin, byte MinimumIV, Shiny Shiny, Gender Gender, PogoType Type, PogoFlags Flags) : IEncounterable, IEncounterMatch, IPogoSlot, IEncounterConvertible, IEncounterServerDate { public bool IsDateRestricted => true; @@ -21,6 +21,8 @@ public sealed record EncounterSlot7GO(ushort DayStart, ushort DayEnd, ushort Spe public GameVersion Version => GameVersion.GO; public ushort Location => Locations.GO7; public byte LevelMax => EncountersGO.MAX_LEVEL; + public bool IsLocalDayStart => Flags.HasFlag(PogoFlags.LocalDateStart); + public bool IsLocalDayEnd => Flags.HasFlag(PogoFlags.LocalDateEnd); public string Name => $"GO Encounter ({Version})"; public string LongName @@ -28,10 +30,8 @@ public string LongName get { var init = $"{Name} ({Type})"; - if (((IPogoDateRange)this).IsNoDateEither) - return init; - var start = PogoDateRangeExtensions.GetDateString(DayStart); - var end = PogoDateRangeExtensions.GetDateString(DayEnd); + var start = PogoDateRangeExtensions.GetDateString(DayStart, IsLocalDayStart ? 1 : 0, true); + var end = PogoDateRangeExtensions.GetDateString(DayEnd, IsLocalDayEnd ? -1 : 0); return $"{init}: {start}-{end}"; } } @@ -158,3 +158,10 @@ public EncounterMatchRating GetMatchRating(PKM pk) } #endregion } + +public enum PogoFlags : byte +{ + LocalDateStart = 1 << 0, + LocalDateEnd = 1 << 1, + FeaturedWildArea = 1 << 2, +} diff --git a/PKHeX.Core/Legality/Encounters/Templates/GO/EncounterSlot8GO.cs b/PKHeX.Core/Legality/Encounters/Templates/GO/EncounterSlot8GO.cs index 347ff0478..a51b5ea64 100644 --- a/PKHeX.Core/Legality/Encounters/Templates/GO/EncounterSlot8GO.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/GO/EncounterSlot8GO.cs @@ -7,7 +7,7 @@ namespace PKHeX.Core; /// Encounter Slot representing data transferred to HOME. /// /// -public sealed record EncounterSlot8GO(ushort DayStart, ushort DayEnd, ushort Species, byte Form, byte LevelMin, byte MinimumIV, Shiny Shiny, Gender Gender, PogoType Type, PogoBallRestriction BallRestrict, PogoImportFormat OriginFormat) +public sealed record EncounterSlot8GO(ushort DayStart, ushort DayEnd, ushort Species, byte Form, byte LevelMin, byte MinimumIV, Shiny Shiny, Gender Gender, PogoType Type, PogoBallRestriction BallRestrict, PogoImportFormat OriginFormat, PogoFlags Flags) : IEncounterable, IEncounterMatch, IEncounterConvertible, IPogoSlot, IFixedOTFriendship, IEncounterServerDate { public byte Generation => 8; @@ -20,6 +20,8 @@ public sealed record EncounterSlot8GO(ushort DayStart, ushort DayEnd, ushort Spe public GameVersion Version => GameVersion.GO; public ushort Location => Locations.GO8; public byte LevelMax => EncountersGO.MAX_LEVEL; + public bool IsLocalDayStart => Flags.HasFlag(PogoFlags.LocalDateStart); + public bool IsLocalDayEnd => Flags.HasFlag(PogoFlags.LocalDateEnd); public string Name => $"GO Encounter ({Version})"; public string LongName @@ -27,10 +29,8 @@ public string LongName get { var init = $"{Name} ({Type})"; - if (((IPogoDateRange)this).IsNoDateEither) - return init; - var start = PogoDateRangeExtensions.GetDateString(DayStart); - var end = PogoDateRangeExtensions.GetDateString(DayEnd); + var start = PogoDateRangeExtensions.GetDateString(DayStart, IsLocalDayStart ? 1 : 0, true); + var end = PogoDateRangeExtensions.GetDateString(DayEnd, IsLocalDayEnd ? -1 : 0); return $"{init}: {start}-{end}"; } } @@ -58,9 +58,9 @@ public bool IsBallValid(Ball ball, ushort currentSpecies, PKM pk) // GO does not natively produce Shedinja when evolving Nincada, and thus must be evolved in future games. if (currentSpecies == (int)Shedinja && currentSpecies != Species) return ball == Ball.Poke; - if (ball == Ball.Master) - return BallRestrict.IsMasterBallUsable && pk.MetDate >= new DateOnly(2023, 5, 21); - return BallRestrict.IsValidBall(ball); + if (ball == Ball.Master && pk.MetDate < new DateOnly(2023, 5, 21)) + return false; + return BallRestrict.IsValidBall(ball, Type, Flags); } private PKM GetBlank() => OriginFormat switch diff --git a/PKHeX.Core/Legality/Encounters/Templates/GO/IPogoDateRange.cs b/PKHeX.Core/Legality/Encounters/Templates/GO/IPogoDateRange.cs index 9f7da1710..2902d7ce3 100644 --- a/PKHeX.Core/Legality/Encounters/Templates/GO/IPogoDateRange.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/GO/IPogoDateRange.cs @@ -14,6 +14,9 @@ public interface IPogoDateRange /// If there is no end date (yet), we'll try to clamp to a date in the near-future to prevent it from being open-ended. ushort DayEnd { get; } + bool IsLocalDayStart { get; } + bool IsLocalDayEnd { get; } + internal const int FirstDay = 736150 - 1; // 2016-07-06 (Launch Day), -1 for time zones. internal const ushort DateNone = 0; internal bool IsNoDateEnd => DayEnd == DateNone; @@ -23,7 +26,7 @@ public interface IPogoDateRange public static class PogoDateRangeExtensions { - public static string GetDateString(ushort day) => day == IPogoDateRange.DateNone ? "X" : $"{GetDate(day):yyyy.MM.dd}"; + public static string GetDateString(ushort day, int shift, bool always = false) => day == IPogoDateRange.DateNone && !always ? "X" : $"{GetDate((ushort)(day + shift)):yyyy.MM.dd}"; private static DateOnly GetDate(ushort day) => DateOnly.FromDayNumber(GetDayNumber(day)); private static int GetDayNumber(ushort day) => IPogoDateRange.FirstDay + day; private static ushort GetDayRelative(in DateOnly date) => (ushort)(date.DayNumber - IPogoDateRange.FirstDay); @@ -47,13 +50,16 @@ public static bool IsWithinStartEnd(this IPogoDateRange time, DateOnly date) public static DateOnly GetRandomValidDate(this IPogoDateRange time) { if (time.IsNoDateStart) - return time.IsNoDateEnd ? GetCurrentDateLocal() : GetDate(time.DayEnd); + return time.IsNoDateEnd ? GetCurrentDateLocal() : GetDate((ushort)(time.DayEnd - (time.IsLocalDayEnd ? 1 : 0))); + var start = time.DayStart + (time.IsLocalDayStart ? 1 : 0); if (time.IsNoDateEnd) - return GetDate(time.DayStart); + return GetDate((ushort)start); - var delta = (time.DayEnd - time.DayStart) + 1; - var day = (ushort)Util.Rand.Next(delta); + // Both bounds present: apply local-day adjustments and pick a random day in the inclusive range. + var end = time.DayEnd - (time.IsLocalDayEnd ? 1 : 0); + var delta = end - start + 1; + var day = (ushort)(Util.Rand.Next(delta) + start); return GetDate(day); } } diff --git a/PKHeX.Core/Legality/Encounters/Templates/GO/PogoType.cs b/PKHeX.Core/Legality/Encounters/Templates/GO/PogoType.cs index 31321abd3..5a6dcd51d 100644 --- a/PKHeX.Core/Legality/Encounters/Templates/GO/PogoType.cs +++ b/PKHeX.Core/Legality/Encounters/Templates/GO/PogoType.cs @@ -66,6 +66,18 @@ public enum PogoType : byte public static class PogoExtensions { + private const Ball TransferSafariBall = Strange; + + extension(PogoType type) + { + public bool IsRaid => type is PogoType.Raid + or PogoType.RaidMythical + or PogoType.RaidUltraBeast + or PogoType.RaidShadow + or PogoType.RaidShadowMythical + or PogoType.RaidShadowUltraBeast; + } + extension(PogoBallRestriction ball) { public Ball GetFixedBall() => ball switch @@ -73,23 +85,31 @@ public static class PogoExtensions PogoBallRestriction.OnlyPoke => Poke, PogoBallRestriction.OnlyPremier => Premier, PogoBallRestriction.OnlyBeast => Beast, - PogoBallRestriction.OnlySafari => Safari, + PogoBallRestriction.OnlySafari => TransferSafariBall, _ => None, }; - public bool IsValidBall(Ball current) => ball switch + public bool IsValidBall(Ball current, PogoType type, PogoFlags flags) { - PogoBallRestriction.None => true, - PogoBallRestriction.StandardMaster => current is Poke or Great or Ultra or Master, - PogoBallRestriction.Standard => current is Poke or Great or Ultra, - PogoBallRestriction.OnlyPoke => current is Poke, - PogoBallRestriction.OnlyPremier => current is Premier, - PogoBallRestriction.OnlyBeast => current is Beast, - PogoBallRestriction.OnlySafari => current is Safari, - _ => false, - }; + if (current is Master) + return ball.IsMasterBallUsable(type); + if (current is TransferSafariBall) + return ball.IsSafariBallUsable(flags); - public bool IsMasterBallUsable => ball is PogoBallRestriction.StandardMaster; + return ball switch + { + PogoBallRestriction.None => true, + PogoBallRestriction.StandardMaster => current is Poke or Great or Ultra, + PogoBallRestriction.Standard => current is Poke or Great or Ultra, + PogoBallRestriction.OnlyPoke => current is Poke, + PogoBallRestriction.OnlyPremier => current is Premier, + PogoBallRestriction.OnlyBeast => current is Beast, + _ => false, + }; + } + + public bool IsMasterBallUsable(PogoType type) => ball is PogoBallRestriction.StandardMaster || type.IsRaid; + public bool IsSafariBallUsable(PogoFlags flags) => ball is PogoBallRestriction.OnlySafari || flags.HasFlag(PogoFlags.FeaturedWildArea); } } diff --git a/PKHeX.Core/Resources/legality/wild/encounter_go_home.pkl b/PKHeX.Core/Resources/legality/wild/encounter_go_home.pkl index 07c2341dc..550e62de8 100644 Binary files a/PKHeX.Core/Resources/legality/wild/encounter_go_home.pkl and b/PKHeX.Core/Resources/legality/wild/encounter_go_home.pkl differ diff --git a/PKHeX.Core/Resources/legality/wild/encounter_go_lgpe.pkl b/PKHeX.Core/Resources/legality/wild/encounter_go_lgpe.pkl index 4e005bb8c..d54a8434c 100644 Binary files a/PKHeX.Core/Resources/legality/wild/encounter_go_lgpe.pkl and b/PKHeX.Core/Resources/legality/wild/encounter_go_lgpe.pkl differ