mirror of
https://github.com/kwsch/PKHeX.git
synced 2026-03-21 17:48:28 -05:00
Encounter Crossover/Wander tolerance (30f) (#3683)
* Update encounter_wild_paldea.pkl * Add weather/time mark checks * Set tolerance to 30f, swap crabrawler evos
This commit is contained in:
parent
d4835392a0
commit
3ef12c3ebe
|
|
@ -12,6 +12,8 @@ public sealed record EncounterArea9 : EncounterArea
|
|||
{
|
||||
public readonly EncounterSlot9[] Slots;
|
||||
|
||||
public ushort CrossFrom { get; init; }
|
||||
|
||||
protected override IReadOnlyList<EncounterSlot> Raw => Slots;
|
||||
|
||||
public static EncounterArea9[] GetAreas(BinLinkerAccessor input, GameVersion game)
|
||||
|
|
@ -25,12 +27,13 @@ public static EncounterArea9[] GetAreas(BinLinkerAccessor input, GameVersion gam
|
|||
private EncounterArea9(ReadOnlySpan<byte> areaData, GameVersion game) : base(game)
|
||||
{
|
||||
Location = areaData[0];
|
||||
Slots = ReadSlots(areaData[2..]);
|
||||
CrossFrom = areaData[2];
|
||||
Slots = ReadSlots(areaData[4..]);
|
||||
}
|
||||
|
||||
private EncounterSlot9[] ReadSlots(ReadOnlySpan<byte> areaData)
|
||||
{
|
||||
const int size = 6;
|
||||
const int size = 8;
|
||||
var result = new EncounterSlot9[areaData.Length / size];
|
||||
for (int i = 0; i < result.Length; i++)
|
||||
{
|
||||
|
|
@ -38,9 +41,12 @@ private EncounterSlot9[] ReadSlots(ReadOnlySpan<byte> areaData)
|
|||
var species = ReadUInt16LittleEndian(slot);
|
||||
var form = slot[2];
|
||||
var gender = (sbyte)slot[3];
|
||||
|
||||
var min = slot[4];
|
||||
var max = slot[5];
|
||||
result[i] = new EncounterSlot9(this, species, form, min, max, gender);
|
||||
var time = slot[6];
|
||||
|
||||
result[i] = new EncounterSlot9(this, species, form, min, max, gender, time);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
42
PKHeX.Core/Legality/Encounters/EncounterSlot/AreaWeather9.cs
Normal file
42
PKHeX.Core/Legality/Encounters/EncounterSlot/AreaWeather9.cs
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
using System;
|
||||
using static PKHeX.Core.RibbonIndex;
|
||||
using static PKHeX.Core.AreaWeather9;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Encounter Conditions for <see cref="GameVersion.SV"/>
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum AreaWeather9 : ushort
|
||||
{
|
||||
None,
|
||||
Normal = 1,
|
||||
Overcast = 1 << 1,
|
||||
Raining = 1 << 2,
|
||||
Thunderstorm = 1 << 3,
|
||||
Intense_Sun = 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,
|
||||
Snow = Normal | Overcast | Snowing | Snowstorm,
|
||||
Inside = Normal | Overcast,
|
||||
}
|
||||
|
||||
public static class AreaWeather9Extensions
|
||||
{
|
||||
public static bool IsMarkCompatible(this AreaWeather9 weather, RibbonIndex m) => m switch
|
||||
{
|
||||
MarkCloudy => (weather & Overcast) != 0,
|
||||
MarkRainy => (weather & Raining) != 0,
|
||||
MarkStormy => (weather & Thunderstorm) != 0,
|
||||
MarkSnowy => (weather & Snowing) != 0,
|
||||
MarkBlizzard => (weather & Snowstorm) != 0,
|
||||
MarkSandstorm => (weather & Sandstorm) != 0,
|
||||
_ => true,
|
||||
};
|
||||
}
|
||||
|
|
@ -1,3 +1,6 @@
|
|||
using System.Collections.Generic;
|
||||
using static PKHeX.Core.AreaWeather9;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -9,10 +12,12 @@ public sealed record EncounterSlot9 : EncounterSlot
|
|||
public override int Generation => 9;
|
||||
public override EntityContext Context => EntityContext.Gen9;
|
||||
public sbyte Gender { get; }
|
||||
public byte Time { get; } // disallow at time bit flag
|
||||
|
||||
public EncounterSlot9(EncounterArea9 area, ushort species, byte form, byte min, byte max, sbyte gender) : base(area, species, form, min, max)
|
||||
public EncounterSlot9(EncounterArea9 area, ushort species, byte form, byte min, byte max, sbyte gender, byte time) : base(area, species, form, min, max)
|
||||
{
|
||||
Gender = gender;
|
||||
Time = time;
|
||||
}
|
||||
|
||||
protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk)
|
||||
|
|
@ -33,4 +38,61 @@ protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteri
|
|||
pk.Nature = ToxtricityUtil.GetRandomNature(ref rand, Form);
|
||||
pk9.EncryptionConstant = Util.Rand32();
|
||||
}
|
||||
|
||||
private static int GetTime(RibbonIndex mark) => mark switch
|
||||
{
|
||||
RibbonIndex.MarkLunchtime => 0,
|
||||
RibbonIndex.MarkSleepyTime => 1,
|
||||
RibbonIndex.MarkDusk => 2,
|
||||
RibbonIndex.MarkDawn => 3,
|
||||
_ => 4,
|
||||
};
|
||||
|
||||
public bool CanSpawnAtTime(RibbonIndex mark) => (Time & (1 << GetTime(mark))) == 0;
|
||||
|
||||
public bool CanSpawnInWeather(RibbonIndex mark)
|
||||
{
|
||||
if (AreaWeather.TryGetValue((byte)Area.Location, out var areaWeather))
|
||||
return areaWeather.IsMarkCompatible(mark);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Location IDs matched with possible weather types.
|
||||
/// </summary>
|
||||
internal static readonly Dictionary<byte, AreaWeather9> AreaWeather = new()
|
||||
{
|
||||
{ 6, Standard }, // South Province (Area One)
|
||||
{ 10, Standard }, // Pokémon League
|
||||
{ 12, Standard }, // South Province (Area Two)
|
||||
{ 14, Standard }, // South Province (Area Four)
|
||||
{ 16, Standard }, // South Province (Area Six)
|
||||
{ 18, Standard }, // South Province (Area Five)
|
||||
{ 20, Standard }, // South Province (Area Three)
|
||||
{ 22, Standard }, // West Province (Area One)
|
||||
{ 24, Sand }, // Asado Desert
|
||||
{ 26, Standard }, // West Province (Area Two)
|
||||
{ 28, Standard }, // West Province (Area Three)
|
||||
{ 30, Standard }, // Tagtree Thicket
|
||||
{ 32, Standard }, // East Province (Area Three)
|
||||
{ 34, Standard }, // East Province (Area One)
|
||||
{ 36, Standard }, // East Province (Area Two)
|
||||
{ 38, Snow }, // Glaseado Mountain (1)
|
||||
{ 40, Standard }, // Casseroya Lake
|
||||
{ 44, Standard }, // North Province (Area Three)
|
||||
{ 46, Standard }, // North Province (Area One)
|
||||
{ 48, Standard }, // North Province (Area Two)
|
||||
{ 50, Standard }, // Great Crater of Paldea
|
||||
{ 56, Standard }, // South Paldean Sea
|
||||
{ 58, Standard }, // West Paldean Sea
|
||||
{ 60, Standard }, // East Paldean Sea
|
||||
{ 62, Standard }, // North Paldean Sea
|
||||
{ 64, Inside }, // Inlet Grotto
|
||||
{ 67, Inside }, // Alfornada Cavern
|
||||
{ 69, Standard | Inside | Snow | Snow },// Dalizapa Passage (Near Medali, Tunnels, Near Pokémon Center, Near Zapico)
|
||||
{ 70, Standard }, // Poco Path
|
||||
{ 80, Standard }, // Cabo Poco
|
||||
{ 109, Standard }, // Socarrat Trail
|
||||
{ 124, Inside }, // Area Zero (5)
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,8 @@ private static EncounterFixed9 ReadEncounter(ReadOnlySpan<byte> data) => new()
|
|||
Level = data[0x03],
|
||||
FlawlessIVCount = data[0x04],
|
||||
TeraType = (GemType)data[0x05],
|
||||
// 2 bytes reserved
|
||||
Gender = (sbyte)data[0x06],
|
||||
// 1 byte reserved
|
||||
Moves = new Moveset(
|
||||
BinaryPrimitives.ReadUInt16LittleEndian(data[0x08..]),
|
||||
BinaryPrimitives.ReadUInt16LittleEndian(data[0x0A..]),
|
||||
|
|
@ -57,6 +58,13 @@ protected override bool IsMatchLocation(PKM pk)
|
|||
return loc == Location0 || loc == Location1 || loc == Location2;
|
||||
}
|
||||
|
||||
protected override bool IsMatchForm(PKM pk, EvoCriteria evo)
|
||||
{
|
||||
if (Species is (int)Core.Species.Deerling or (int)Core.Species.Sawsbuck)
|
||||
return pk.Form <= 3;
|
||||
return base.IsMatchForm(pk, evo);
|
||||
}
|
||||
|
||||
public override bool IsMatchExact(PKM pk, EvoCriteria evo)
|
||||
{
|
||||
if (TeraType != GemType.Random && pk is ITeraType t && !Tera9RNG.IsMatchTeraType(TeraType, Species, Form, (byte)t.TeraTypeOriginal))
|
||||
|
|
|
|||
|
|
@ -62,22 +62,26 @@ private static IEnumerable<IEncounterable> GetEncounters(PKM pk, EvoCriteria[] c
|
|||
yield break;
|
||||
}
|
||||
|
||||
// Static Encounters can collide with wild encounters (close match); don't break if a Static Encounter is yielded.
|
||||
var encs = GetValidStaticEncounter(pk, chain, game);
|
||||
foreach (var z in encs)
|
||||
if (pk is not IRibbonIndex r || !r.HasEncounterMark())
|
||||
{
|
||||
var match = z.GetMatchRating(pk);
|
||||
if (match == Match)
|
||||
var encs = GetValidStaticEncounter(pk, chain, game);
|
||||
foreach (var z in encs)
|
||||
{
|
||||
yield return z;
|
||||
}
|
||||
else if (match < rating)
|
||||
{
|
||||
cache = z;
|
||||
rating = match;
|
||||
var match = z.GetMatchRating(pk);
|
||||
if (match == Match)
|
||||
{
|
||||
yield return z;
|
||||
}
|
||||
else if (match < rating)
|
||||
{
|
||||
cache = z;
|
||||
rating = match;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Wild encounters are more permissive than static encounters.
|
||||
// Can have encounter marks, can have varied scales/shiny states.
|
||||
foreach (var z in GetValidWildEncounters(pk, chain, game))
|
||||
{
|
||||
var match = z.GetMatchRating(pk);
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ private void VerifyMarksPresent(LegalityAnalysis data, IRibbonIndex m)
|
|||
return;
|
||||
}
|
||||
|
||||
bool result = MarkRules.IsMarkValid8(mark, data.Entity, data.EncounterMatch);
|
||||
bool result = MarkRules.IsEncounterMarkValid(mark, data.Entity, data.EncounterMatch);
|
||||
if (!result)
|
||||
{
|
||||
data.AddLine(GetInvalid(string.Format(LRibbonMarkingFInvalid_0, GetRibbonNameSafe(mark))));
|
||||
|
|
@ -103,7 +103,7 @@ private void VerifyShedinjaAffixed(LegalityAnalysis data, RibbonIndex affix, PKM
|
|||
var enc = data.EncounterOriginal;
|
||||
if (affix.IsEncounterMark())
|
||||
{
|
||||
if (!MarkRules.IsMarkValid8(affix, pk, enc))
|
||||
if (!MarkRules.IsEncounterMarkValid(affix, pk, enc))
|
||||
data.AddLine(GetInvalid(string.Format(LRibbonMarkingAffixedF_0, GetRibbonNameSafe(affix))));
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,11 +8,6 @@ namespace PKHeX.Core;
|
|||
/// <see cref="RibbonRules"/>
|
||||
public static class MarkRules
|
||||
{
|
||||
/// <summary>
|
||||
/// Checks if the ribbon index is one of the specific SW/SH encounter-only marks. These marks are granted when the encounter spawns in the wild.
|
||||
/// </summary>
|
||||
public static bool IsEncounterMark(this RibbonIndex m) => (byte)m is >= (int)MarkLunchtime and <= (int)MarkSlump;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if an encounter-only mark is possible to obtain for the encounter, if not lost via data manipulation.
|
||||
/// </summary>
|
||||
|
|
@ -35,39 +30,50 @@ public static bool IsEncounterMarkLost(LegalityAnalysis data)
|
|||
/// <summary>
|
||||
/// Checks if a SW/SH mark is valid.
|
||||
/// </summary>
|
||||
public static bool IsMarkValid8(RibbonIndex mark, PKM pk, IEncounterTemplate enc)
|
||||
public static bool IsEncounterMarkValid(RibbonIndex mark, PKM pk, IEncounterTemplate enc) => enc switch
|
||||
{
|
||||
return IsEncounterMarkAllowedAny(enc) && IsMarkAllowedSpecific(mark, pk, enc);
|
||||
}
|
||||
EncounterSlot8 or EncounterStatic8 { Gift: false, ScriptedNoMarks: false } => IsMarkAllowedSpecific8(mark, pk, enc),
|
||||
EncounterSlot9 s => IsMarkAllowedSpecific9(mark, s),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a specific encounter mark is disallowed.
|
||||
/// </summary>
|
||||
/// <returns>False if mark is disallowed based on specific conditions.</returns>
|
||||
public static bool IsMarkAllowedSpecific(RibbonIndex mark, PKM pk, IEncounterTemplate x) => mark switch
|
||||
public static bool IsMarkAllowedSpecific8(RibbonIndex mark, PKM pk, IEncounterTemplate x) => mark switch
|
||||
{
|
||||
MarkCurry when !IsMarkAllowedCurry(pk, x) => false,
|
||||
MarkFishing when !IsMarkAllowedFishing(x) => false,
|
||||
MarkMisty when x.Generation == 8 && pk.Met_Level < EncounterArea8.BoostLevel && EncounterArea8.IsBoostedArea60Fog(pk.Met_Location) => false,
|
||||
MarkDestiny => x is EncounterSlot9, // Capture on Birthday
|
||||
>= MarkCloudy and <= MarkMisty => IsWeatherPermitted(mark, x),
|
||||
>= MarkCloudy and <= MarkMisty => IsWeatherPermitted8(mark, x),
|
||||
_ => true,
|
||||
};
|
||||
|
||||
private static bool IsWeatherPermitted(RibbonIndex mark, IEncounterTemplate enc)
|
||||
/// <summary>
|
||||
/// 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
|
||||
{
|
||||
var permit = mark.GetWeather8();
|
||||
MarkCurry => false,
|
||||
MarkFishing => false,
|
||||
MarkDestiny => true, // Capture on Birthday
|
||||
>= MarkLunchtime and <= MarkDawn => x.CanSpawnAtTime(mark),
|
||||
>= MarkCloudy and <= MarkMisty => x.CanSpawnInWeather(mark),
|
||||
_ => true,
|
||||
};
|
||||
|
||||
// Encounter slots check location weather, while static encounters check weather per encounter.
|
||||
return enc switch
|
||||
{
|
||||
EncounterSlot8 w => IsSlotWeatherPermitted(permit, w),
|
||||
EncounterStatic8 s => s.Weather.HasFlag(permit),
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
// Encounter slots check location weather, while static encounters check weather per encounter.
|
||||
private static bool IsWeatherPermitted8(RibbonIndex mark, IEncounterTemplate enc) => enc switch
|
||||
{
|
||||
EncounterSlot8 w => IsSlotWeatherPermittedSWSH(mark.GetWeather8(), w),
|
||||
EncounterStatic8 s => s.Weather.HasFlag(mark.GetWeather8()),
|
||||
_ => false,
|
||||
};
|
||||
|
||||
private static bool IsSlotWeatherPermitted(AreaWeather8 permit, EncounterSlot8 s)
|
||||
private static bool IsSlotWeatherPermittedSWSH(AreaWeather8 permit, EncounterSlot8 s)
|
||||
{
|
||||
var location = s.Location;
|
||||
// If it's not in the main table, it can only have Normal weather.
|
||||
|
|
@ -85,18 +91,7 @@ private static bool IsSlotWeatherPermitted(AreaWeather8 permit, EncounterSlot8 s
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if any encounter-only mark is available for the <see cref="enc"/>.
|
||||
/// </summary>
|
||||
public static bool IsEncounterMarkAllowedAny(IEncounterTemplate enc) => enc.Generation >= 8 && enc switch
|
||||
{
|
||||
// Gen 8
|
||||
EncounterSlot8 or EncounterStatic8 { Gift: false, ScriptedNoMarks: false } => true,
|
||||
EncounterSlot9 => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a <see cref="RibbonIndex.MarkCurry"/> mark is valid.
|
||||
/// Checks if a <see cref="MarkCurry"/> mark is valid.
|
||||
/// </summary>
|
||||
public static bool IsMarkAllowedCurry(PKM pk, IEncounterTemplate enc)
|
||||
{
|
||||
|
|
@ -109,7 +104,7 @@ public static bool IsMarkAllowedCurry(PKM pk, IEncounterTemplate enc)
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a <see cref="RibbonIndex.MarkFishing"/> mark is valid.
|
||||
/// Checks if a <see cref="MarkFishing"/> mark is valid.
|
||||
/// </summary>
|
||||
public static bool IsMarkAllowedFishing(IEncounterTemplate enc)
|
||||
{
|
||||
|
|
|
|||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -129,7 +129,20 @@ public static class RibbonIndexExtensions
|
|||
{
|
||||
public static bool GetRibbonIndex(this IRibbonIndex x, RibbonIndex r) => x.GetRibbon((int)r);
|
||||
public static void SetRibbonIndex(this IRibbonIndex x, RibbonIndex r, bool value = true) => x.SetRibbon((int)r, value);
|
||||
public static bool IsMark(this RibbonIndex r) => r is >= MarkLunchtime and <= MarkSlump;
|
||||
public static bool IsEncounterMark(this RibbonIndex r) => r is >= MarkLunchtime and <= MarkSlump;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the ribbon index is one of the specific wild encounter-only marks. These marks are granted when the encounter spawns in the wild.
|
||||
/// </summary>
|
||||
public static bool HasEncounterMark(this IRibbonIndex m)
|
||||
{
|
||||
for (int i = (int)MarkLunchtime; i <= (int)MarkSlump; i++)
|
||||
{
|
||||
if (m.GetRibbon(i))
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static AreaWeather8 GetWeather8(this RibbonIndex x) => x switch
|
||||
{
|
||||
|
|
@ -147,7 +160,7 @@ public static class RibbonIndexExtensions
|
|||
private enum RibbonIndexGroup : byte
|
||||
{
|
||||
None,
|
||||
Mark,
|
||||
EncounterMark,
|
||||
CountMemory,
|
||||
Common3,
|
||||
Common4,
|
||||
|
|
@ -161,8 +174,8 @@ private enum RibbonIndexGroup : byte
|
|||
|
||||
private static RibbonIndexGroup GetGroup(this RibbonIndex r)
|
||||
{
|
||||
if (r.IsMark())
|
||||
return RibbonIndexGroup.Mark;
|
||||
if (r.IsEncounterMark())
|
||||
return RibbonIndexGroup.EncounterMark;
|
||||
return r switch
|
||||
{
|
||||
ChampionG3 => RibbonIndexGroup.Common3,
|
||||
|
|
@ -249,8 +262,8 @@ public static void Fix(this RibbonIndex r, RibbonVerifierArguments args, bool st
|
|||
var group = r.GetGroup();
|
||||
switch (group)
|
||||
{
|
||||
case RibbonIndexGroup.Mark:
|
||||
r.FixMark(pk, state);
|
||||
case RibbonIndexGroup.EncounterMark:
|
||||
r.FixEncounterMark(pk, state);
|
||||
return;
|
||||
case RibbonIndexGroup.CountMemory:
|
||||
if (pk is not IRibbonSetMemory6 m6)
|
||||
|
|
@ -365,7 +378,7 @@ public static void Fix(this RibbonIndex r, RibbonVerifierArguments args, bool st
|
|||
}
|
||||
}
|
||||
|
||||
private static void FixMark(this RibbonIndex r, PKM pk, bool state)
|
||||
private static void FixEncounterMark(this RibbonIndex r, PKM pk, bool state)
|
||||
{
|
||||
if (pk is not IRibbonSetMark8 m)
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -36,4 +36,21 @@ public class LegalityData
|
|||
t2.IsLevelUpRequired().Should().BeTrue();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EvolutionsOrderedSV()
|
||||
{
|
||||
// SV Crabrawler added a second, UseItem evolution method. Need to be sure it's before the more restrictive level-up method.
|
||||
var tree = EvolutionTree.Evolves9;
|
||||
var fEntries = typeof(EvolutionTree).GetFields(BindingFlags.NonPublic | BindingFlags.Instance).First(z => z.Name == "Entries");
|
||||
if (fEntries.GetValue(tree) is not IReadOnlyList<EvolutionMethod[]> entries)
|
||||
throw new ArgumentException(nameof(entries));
|
||||
var crab = entries[(int)Species.Crabrawler];
|
||||
|
||||
var t1 = crab[0].Method;
|
||||
var t2 = crab[1].Method;
|
||||
|
||||
t1.IsLevelUpRequired().Should().BeFalse();
|
||||
t2.IsLevelUpRequired().Should().BeTrue();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user