mirror of
https://github.com/kwsch/PKHeX.git
synced 2026-04-24 15:47:15 -05:00
Add Misty Mark recognition & weather bleed for slots (#4519)
Closes #4036 Weather now handled upstream in pkNX, with bleed applied to individual spawn points, serialized to consumable legality binary. Misty marks thanks to @Lusamine and her discord helpers -- also utilized https://github.com/kwsch/MistyMarkVisualize to help visualize and filter entity dumps => spawn-position.
This commit is contained in:
parent
607901dda1
commit
7864907f81
|
|
@ -8,18 +8,17 @@ namespace PKHeX.Core;
|
|||
/// Encounter Conditions for <see cref="GameVersion.SV"/>
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum AreaWeather9 : ushort
|
||||
public enum AreaWeather9 : byte
|
||||
{
|
||||
None,
|
||||
Normal = 1,
|
||||
Overcast = 1 << 1,
|
||||
Raining = 1 << 2,
|
||||
Thunderstorm = 1 << 3,
|
||||
Intense_Sun = 1 << 4,
|
||||
Mist = 1 << 4,
|
||||
Snowing = 1 << 5,
|
||||
Snowstorm = 1 << 6,
|
||||
Sandstorm = 1 << 7,
|
||||
Heavy_Fog = 1 << 8,
|
||||
|
||||
Standard = Normal | Overcast | Raining | Thunderstorm,
|
||||
Sand = Normal | Overcast | Raining | Sandstorm,
|
||||
|
|
@ -40,6 +39,7 @@ public static class AreaWeather9Extensions
|
|||
MarkSnowy => (weather & Snowing) != 0,
|
||||
MarkBlizzard => (weather & Snowstorm) != 0,
|
||||
MarkSandstorm => (weather & Sandstorm) != 0,
|
||||
MarkMisty => (weather & Mist) != 0,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,8 +48,9 @@ private EncounterSlot9[] ReadSlots(ReadOnlySpan<byte> areaData)
|
|||
var min = slot[4];
|
||||
var max = slot[5];
|
||||
var time = slot[6];
|
||||
var weather = (AreaWeather9)slot[7];
|
||||
|
||||
result[i] = new EncounterSlot9(this, species, form, min, max, gender, time);
|
||||
result[i] = new EncounterSlot9(this, species, form, min, max, gender, time, weather);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ public sealed record EncounterOutbreak9
|
|||
public required byte LevelMax { get; init; }
|
||||
public required byte Gender { get; init; }
|
||||
public required RibbonIndex Ribbon { get; init; }
|
||||
public AreaWeather9 Weather { get; init; }
|
||||
public required byte MetBase { get; init; }
|
||||
public required bool IsForcedScaleRange { get; init; }
|
||||
public required byte ScaleMin { get; init; }
|
||||
|
|
@ -33,7 +34,7 @@ public sealed record EncounterOutbreak9
|
|||
public required bool IsShiny { get; init; }
|
||||
public required UInt128 MetFlags { get; init; }
|
||||
|
||||
private const int SIZE = 0x14 + 8;
|
||||
private const int SIZE = 0xC + 16;
|
||||
|
||||
public static EncounterOutbreak9[] GetArray(ReadOnlySpan<byte> data)
|
||||
{
|
||||
|
|
@ -53,13 +54,15 @@ private static EncounterOutbreak9 ReadEncounter(ReadOnlySpan<byte> data) => new(
|
|||
LevelMin = data[0x04],
|
||||
LevelMax = data[0x05],
|
||||
Ribbon = (RibbonIndex)data[0x06],
|
||||
MetBase = data[0x07],
|
||||
Weather = (AreaWeather9)data[0x07],
|
||||
|
||||
IsForcedScaleRange = data[0x08] != 0,
|
||||
ScaleMin = data[0x09],
|
||||
ScaleMax = data[0x0A],
|
||||
IsShiny = data[0x0B] != 0,
|
||||
MetFlags = ReadUInt128LittleEndian(data[0x0C..]),
|
||||
|
||||
MetBase = data[0x0C],
|
||||
MetFlags = ReadUInt128LittleEndian(data[0x0C..]) >> 8,
|
||||
};
|
||||
|
||||
public string Name => "Distribution Outbreak Encounter";
|
||||
|
|
@ -226,6 +229,12 @@ private EncounterMatchRating IsMatchDeferred(PKM pk)
|
|||
}
|
||||
}
|
||||
|
||||
if (pk is IRibbonSetMark8 m)
|
||||
{
|
||||
if (m.HasWeatherMark(out var weather) && !CanSpawnInWeather(weather))
|
||||
return EncounterMatchRating.DeferredErrors;
|
||||
}
|
||||
|
||||
return EncounterMatchRating.Match;
|
||||
}
|
||||
|
||||
|
|
@ -237,4 +246,6 @@ private bool IsMatchPartial(PKM pk)
|
|||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public bool CanSpawnInWeather(RibbonIndex mark) => Weather.IsMarkCompatible(mark);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
using static PKHeX.Core.AreaWeather9;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Encounter Slot found in <see cref="GameVersion.SV"/>.
|
||||
/// </summary>
|
||||
public sealed record EncounterSlot9(EncounterArea9 Parent, ushort Species, byte Form, byte LevelMin, byte LevelMax, byte Gender, byte Time)
|
||||
public sealed record EncounterSlot9(EncounterArea9 Parent, ushort Species, byte Form, byte LevelMin, byte LevelMax, byte Gender, byte Time, AreaWeather9 Weather)
|
||||
: IEncounterable, IEncounterMatch, IEncounterConvertible<PK9>, IEncounterFormRandom, IFixedGender
|
||||
{
|
||||
public byte Generation => 9;
|
||||
|
|
@ -33,92 +31,7 @@ public sealed record EncounterSlot9(EncounterArea9 Parent, ushort Species, byte
|
|||
};
|
||||
|
||||
public bool CanSpawnAtTime(RibbonIndex mark) => (Time & (1 << GetTime(mark))) == 0;
|
||||
|
||||
public bool CanSpawnInWeather(RibbonIndex mark)
|
||||
{
|
||||
var loc = (byte)Location;
|
||||
return CanSpawnInWeather(mark, loc);
|
||||
}
|
||||
|
||||
public static bool CanSpawnInWeather(RibbonIndex mark, byte loc)
|
||||
{
|
||||
var weather = GetWeather(loc);
|
||||
return weather.IsMarkCompatible(mark);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Location IDs matched with possible weather types. Unlisted locations may only have Normal weather.
|
||||
/// </summary>
|
||||
public static AreaWeather9 GetWeather(byte location) => location switch
|
||||
{
|
||||
006 => Standard, // South Province (Area One)
|
||||
010 => Standard, // Pokémon League
|
||||
012 => Standard, // South Province (Area Two)
|
||||
014 => Standard, // South Province (Area Four)
|
||||
016 => Standard, // South Province (Area Six)
|
||||
018 => Standard, // South Province (Area Five)
|
||||
020 => Standard, // South Province (Area Three)
|
||||
022 => Standard, // West Province (Area One)
|
||||
024 => Sand, // Asado Desert
|
||||
026 => Standard, // West Province (Area Two)
|
||||
028 => Standard, // West Province (Area Three)
|
||||
030 => Standard, // Tagtree Thicket
|
||||
032 => Standard, // East Province (Area Three)
|
||||
034 => Standard, // East Province (Area One)
|
||||
036 => Standard, // East Province (Area Two)
|
||||
038 => Snow, // Glaseado Mountain (1)
|
||||
040 => Standard, // Casseroya Lake
|
||||
044 => Standard, // North Province (Area Three)
|
||||
046 => Standard, // North Province (Area One)
|
||||
048 => Standard, // North Province (Area Two)
|
||||
050 => Standard, // Great Crater of Paldea
|
||||
056 => Standard, // South Paldean Sea
|
||||
058 => Standard, // West Paldean Sea
|
||||
060 => Standard, // East Paldean Sea
|
||||
062 => Standard, // North Paldean Sea
|
||||
064 => Inside, // Inlet Grotto
|
||||
067 => Inside, // Alfornada Cavern
|
||||
069 => Standard | Inside | Snow | Snow,// Dalizapa Passage (Near Medali, Tunnels, Near Pokémon Center, Near Zapico)
|
||||
070 => Standard, // Poco Path
|
||||
080 => Standard, // Cabo Poco
|
||||
109 => Standard, // Socarrat Trail
|
||||
124 => Inside, // Area Zero (5)
|
||||
|
||||
132 => Standard, // Kitakami Road
|
||||
134 => Standard, // Mossui Town
|
||||
136 => Standard, // Apple Hills
|
||||
138 => Standard, // Loyalty Plaza
|
||||
140 => Standard, // Reveler’s Road
|
||||
142 => Standard, // Kitakami Hall
|
||||
144 => Standard, // Oni Mountain
|
||||
146 => Standard, // Dreaded Den
|
||||
148 => Standard, // Oni’s Maw
|
||||
150 => Standard, // Oni Mountain
|
||||
152 => Standard, // Crystal Pool
|
||||
154 => Standard, // Crystal Pool
|
||||
156 => Standard, // Wistful Fields
|
||||
158 => Standard, // Mossfell Confluence
|
||||
160 => Standard, // Fellhorn Gorge
|
||||
162 => Standard, // Paradise Barrens
|
||||
164 => Standard, // Kitakami Wilds
|
||||
166 => Standard, // Timeless Woods
|
||||
168 => Standard, // Infernal Pass
|
||||
170 => Standard, // Chilling Waterhead
|
||||
|
||||
174 => Standard, // Savanna Biome
|
||||
176 => Standard, // Coastal Biome
|
||||
178 => Standard, // Canyon Biome
|
||||
180 => Snow, // Polar Biome
|
||||
182 => Standard, // Central Plaza
|
||||
184 => Standard, // Savanna Plaza
|
||||
186 => Standard, // Coastal Plaza
|
||||
188 => Standard, // Canyon Plaza
|
||||
190 => Standard, // Polar Plaza
|
||||
192 => Inside, // Chargestone Cavern
|
||||
194 => Inside, // Torchlit Labyrinth
|
||||
|
||||
_ => None,
|
||||
};
|
||||
public bool CanSpawnInWeather(RibbonIndex mark) => Weather.IsMarkCompatible(mark);
|
||||
|
||||
#region Generating
|
||||
|
||||
|
|
@ -240,10 +153,7 @@ public EncounterMatchRating GetMatchRating(PKM pk)
|
|||
// Some encounters can cross over into non-snow, and their encounter match might not cross back over to snow.
|
||||
// Imagine a venn diagram, one circle is Desert, the other is Snow. The met location is in the middle, so both satisfy.
|
||||
// But if we pick the Desert circle, it's wrong, and we need to defer to the other.
|
||||
// Might need to add other deferral cases or maybe defer everything with a crossover location.
|
||||
if (m.RibbonMarkSnowy && !CanSpawnInWeather(RibbonIndex.MarkSnowy))
|
||||
return EncounterMatchRating.DeferredErrors;
|
||||
if (m.RibbonMarkBlizzard && !CanSpawnInWeather(RibbonIndex.MarkBlizzard))
|
||||
if (m.HasWeatherMark(out var weather) && !CanSpawnInWeather(weather))
|
||||
return EncounterMatchRating.DeferredErrors;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ public static bool IsEncounterMarkLost(IEncounterTemplate enc, PKM pk)
|
|||
EncounterSlot8 or EncounterStatic8 { Gift: false, ScriptedNoMarks: false } => IsMarkAllowedSpecific8(mark, pk, enc),
|
||||
EncounterSlot9 s => IsMarkAllowedSpecific9(mark, s),
|
||||
EncounterStatic9 s => IsMarkAllowedSpecific9(mark, s),
|
||||
EncounterOutbreak9 o when o.Ribbon == mark || IsMarkAllowedSpecific9(mark, pk) => true, // not guaranteed ribbon/mark
|
||||
EncounterOutbreak9 o when o.Ribbon == mark || IsMarkAllowedSpecific9(mark, o) => true, // not guaranteed ribbon/mark
|
||||
WC9 wc9 => wc9.GetRibbonIndex(mark),
|
||||
_ => false,
|
||||
};
|
||||
|
|
@ -44,13 +44,13 @@ public static bool IsEncounterMarkLost(IEncounterTemplate enc, PKM pk)
|
|||
/// Checks if a specific encounter mark is disallowed.
|
||||
/// </summary>
|
||||
/// <returns>False if mark is disallowed based on specific conditions.</returns>
|
||||
public static bool IsMarkAllowedSpecific8(RibbonIndex mark, PKM pk, IEncounterTemplate x) => mark switch
|
||||
public static bool IsMarkAllowedSpecific8(RibbonIndex mark, PKM pk, IEncounterTemplate enc) => mark switch
|
||||
{
|
||||
MarkCurry when !IsMarkAllowedCurry(pk, x) => false,
|
||||
MarkFishing when !IsMarkAllowedFishing(x) => false,
|
||||
MarkMisty when x.Generation == 8 && pk.MetLevel < EncounterArea8.BoostLevel && EncounterArea8.IsBoostedArea60Fog(pk.MetLocation) => false,
|
||||
MarkDestiny => x is EncounterSlot9, // Capture on Birthday
|
||||
>= MarkCloudy and <= MarkMisty => IsWeatherPermitted8(mark, x),
|
||||
MarkCurry when !IsMarkAllowedCurry(pk, enc) => false,
|
||||
MarkFishing when !IsMarkAllowedFishing(enc) => false,
|
||||
MarkMisty when enc.Generation == 8 && pk.MetLevel < EncounterArea8.BoostLevel && EncounterArea8.IsBoostedArea60Fog(pk.MetLocation) => false,
|
||||
MarkDestiny => enc is EncounterSlot9, // Capture on Birthday
|
||||
>= MarkCloudy and <= MarkMisty => IsWeatherPermitted8(mark, enc),
|
||||
_ => true,
|
||||
};
|
||||
|
||||
|
|
@ -58,13 +58,13 @@ public static bool IsEncounterMarkLost(IEncounterTemplate enc, PKM pk)
|
|||
/// Checks if a specific encounter mark is disallowed.
|
||||
/// </summary>
|
||||
/// <returns>False if mark is disallowed based on specific conditions.</returns>
|
||||
public static bool IsMarkAllowedSpecific9(RibbonIndex mark, EncounterSlot9 x) => mark switch
|
||||
public static bool IsMarkAllowedSpecific9(RibbonIndex mark, EncounterSlot9 enc) => mark switch
|
||||
{
|
||||
MarkCurry => false,
|
||||
MarkFishing => false,
|
||||
MarkDestiny => true, // Capture on Birthday
|
||||
>= MarkLunchtime and <= MarkDawn => x.CanSpawnAtTime(mark),
|
||||
>= MarkCloudy and <= MarkMisty => x.CanSpawnInWeather(mark),
|
||||
>= MarkLunchtime and <= MarkDawn => enc.CanSpawnAtTime(mark),
|
||||
>= MarkCloudy and <= MarkMisty => enc.CanSpawnInWeather(mark),
|
||||
_ => true,
|
||||
};
|
||||
|
||||
|
|
@ -73,13 +73,13 @@ public static bool IsEncounterMarkLost(IEncounterTemplate enc, PKM pk)
|
|||
/// </summary>
|
||||
/// <returns>False if mark is disallowed based on specific conditions.</returns>
|
||||
/// <remarks>ONLY USE FOR <see cref="EncounterOutbreak9"/></remarks>
|
||||
public static bool IsMarkAllowedSpecific9(RibbonIndex mark, PKM pk) => mark switch
|
||||
public static bool IsMarkAllowedSpecific9(RibbonIndex mark, EncounterOutbreak9 enc) => mark switch
|
||||
{
|
||||
MarkCurry => false,
|
||||
MarkFishing => false,
|
||||
MarkDestiny => true, // Capture on Birthday
|
||||
>= MarkLunchtime and <= MarkDawn => true, // no time restrictions
|
||||
>= MarkCloudy and <= MarkMisty => pk is PK8 || EncounterSlot9.CanSpawnInWeather(mark, (byte)pk.MetLocation),
|
||||
>= MarkCloudy and <= MarkMisty => enc.CanSpawnInWeather(mark),
|
||||
_ => true,
|
||||
};
|
||||
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue
Block a user