PKHeX/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot8.cs

139 lines
5 KiB
C#
Raw Normal View History

using static PKHeX.Core.OverworldCorrelation8Requirement;
namespace PKHeX.Core;
/// <summary>
/// Encounter Slot found in <see cref="GameVersion.SWSH"/>.
/// </summary>
/// <inheritdoc cref="EncounterSlot"/>
public sealed record EncounterSlot8 : EncounterSlot, IOverworldCorrelation8
{
public readonly AreaWeather8 Weather;
public readonly AreaSlotType8 SlotType;
public override string LongName => $"{wild} [{SlotType}] - {Weather.ToString().Replace("_", string.Empty)}";
public override int Generation => 8;
Refactoring: Move Source (Legality) (#3560) Rewrites a good amount of legality APIs pertaining to: * Legal moves that can be learned * Evolution chains & cross-generation paths * Memory validation with forgotten moves In generation 8, there are 3 separate contexts an entity can exist in: SW/SH, BD/SP, and LA. Not every entity can cross between them, and not every entity from generation 7 can exist in generation 8 (Gogoat, etc). By creating class models representing the restrictions to cross each boundary, we are able to better track and validate data. The old implementation of validating moves was greedy: it would iterate for all generations and evolutions, and build a full list of every move that can be learned, storing it on the heap. Now, we check one game group at a time to see if the entity can learn a move that hasn't yet been validated. End result is an algorithm that requires 0 allocation, and a smaller/quicker search space. The old implementation of storing move parses was inefficient; for each move that was parsed, a new object is created and adjusted depending on the parse. Now, move parse results are `struct` and store the move parse contiguously in memory. End result is faster parsing and 0 memory allocation. * `PersonalTable` objects have been improved with new API methods to check if a species+form can exist in the game. * `IEncounterTemplate` objects have been improved to indicate the `EntityContext` they originate in (similar to `Generation`). * Some APIs have been extended to accept `Span<T>` instead of Array/IEnumerable
2022-08-03 23:15:27 +00:00
public override EntityContext Context => EntityContext.Gen8;
// Fishing are only from the hidden table (not symbol).
public bool CanEncounterViaFishing => SlotType.CanEncounterViaFishing(Weather);
public bool CanEncounterViaCurry
{
get
{
if (!SlotType.CanEncounterViaCurry())
return false;
if ((Weather & AreaWeather8.All) == 0)
return false;
if (EncounterArea8.IsWildArea(Location))
return false;
return true;
}
}
public EncounterSlot8(EncounterArea8 area, ushort species, byte form, byte min, byte max, AreaWeather8 weather, AreaSlotType8 slotType) : base(area, species, form, min, max)
{
Weather = weather;
SlotType = slotType;
}
protected override void ApplyDetails(ITrainerInfo sav, EncounterCriteria criteria, PKM pk)
{
bool symbol = ((EncounterArea8)Area).PermitCrossover;
var c = symbol ? EncounterCriteria.Unrestricted : criteria;
if (!symbol && Location is 30 or 54 && (Weather & AreaWeather8.Fishing) == 0)
((PK8)pk).RibbonMarkCurry = true;
base.ApplyDetails(sav, c, pk);
if (Weather is AreaWeather8.Heavy_Fog && EncounterArea8.IsBoostedArea60Fog(Location))
pk.CurrentLevel = pk.Met_Level = EncounterArea8.BoostLevel;
var req = GetRequirement(pk);
if (req != MustHave)
{
pk.SetRandomEC();
return;
}
// Don't bother honoring shiny state.
Overworld8RNG.ApplyDetails(pk, c, Shiny.Random);
}
public OverworldCorrelation8Requirement GetRequirement(PKM pk)
{
if (((EncounterArea8)Area).PermitCrossover)
return MustHave; // symbol walking overworld
bool curry = pk is IRibbonSetMark8 {RibbonMarkCurry: true} || (pk.Species == (int)Core.Species.Shedinja && pk is IRibbonSetAffixed { AffixedRibbon:(int)RibbonIndex.MarkCurry});
if (curry)
return MustNotHave;
// Tree encounters are generated via the global seed, not the u32
if ((Weather & AreaWeather8.Shaking_Trees) != 0)
{
// Some tree encounters are present in the regular encounters.
return Weather == AreaWeather8.Shaking_Trees
? MustNotHave
: CanBeEither;
}
2021-02-14 23:14:45 +00:00
return MustHave;
}
public bool IsOverworldCorrelationCorrect(PKM pk)
{
var flawless = GetFlawlessIVCount(pk.Met_Level);
return Overworld8RNG.ValidateOverworldEncounter(pk, flawless: flawless);
}
private int GetFlawlessIVCount(int met)
{
const int none = 0;
const int any023 = -1;
// Brilliant encounters are boosted to max level for the slot.
if (met < LevelMax)
return none;
var area = (EncounterArea8) Area;
if (area.PermitCrossover)
return any023; // Symbol
if ((Weather & AreaWeather8.Fishing) != 0)
return any023; // Fishing
return none; // Hidden
}
public override EncounterMatchRating GetMatchRating(PKM pk)
{
bool isHidden = pk.AbilityNumber == 4;
if (isHidden && this.IsPartialMatchHidden(pk.Species, Species))
return EncounterMatchRating.PartialMatch;
if (pk is IRibbonSetMark8 m)
2021-02-15 18:19:52 +00:00
{
if (m.RibbonMarkCurry && (Weather & AreaWeather8.All) == 0)
return EncounterMatchRating.DeferredErrors;
if (m.RibbonMarkFishing && (Weather & AreaWeather8.Fishing) == 0)
return EncounterMatchRating.DeferredErrors;
// Check if it has a mark and the weather does not permit the mark.
// Tree/Fishing slots should be deferred here and are checked later.
if (!Weather.IsMarkCompatible(m))
return EncounterMatchRating.DeferredErrors;
// Galar Mine hidden encounters can only be found via Curry or Fishing.
if (Location is (30 or 54) && SlotType is AreaSlotType8.HiddenMain && !m.RibbonMarkCurry && !SlotType.CanEncounterViaFishing(Weather))
return EncounterMatchRating.DeferredErrors;
2021-02-15 18:19:52 +00:00
}
var req = GetRequirement(pk);
return req switch
2021-02-14 23:14:45 +00:00
{
MustHave when !IsOverworldCorrelationCorrect(pk) => EncounterMatchRating.DeferredErrors,
MustNotHave when IsOverworldCorrelationCorrect(pk) => EncounterMatchRating.DeferredErrors,
_ => EncounterMatchRating.Match,
};
}
}