PKHeX/PKHeX.Core/Legality/Areas/EncounterArea1.cs
Kurt 9166d0eb64
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 16:15:27 -07:00

74 lines
2.3 KiB
C#

using System;
using System.Collections.Generic;
namespace PKHeX.Core;
/// <inheritdoc cref="EncounterArea" />
/// <summary>
/// <see cref="GameVersion.RBY"/> encounter area
/// </summary>
public sealed record EncounterArea1 : EncounterArea
{
public readonly int Rate;
public readonly EncounterSlot1[] Slots;
protected override IReadOnlyList<EncounterSlot> Raw => Slots;
public static EncounterArea1[] GetAreas(BinLinkerAccessor input, GameVersion game)
{
var result = new EncounterArea1[input.Length];
for (int i = 0; i < result.Length; i++)
result[i] = new EncounterArea1(input[i], game);
return result;
}
private EncounterArea1(ReadOnlySpan<byte> data, GameVersion game) : base(game)
{
Location = data[0];
// 1 byte unused
Type = (SlotType)data[2];
Rate = data[3];
var next = data[4..];
int count = next.Length / 4;
var slots = new EncounterSlot1[count];
for (int i = 0; i < slots.Length; i++)
{
const int size = 4;
var entry = next.Slice(i * size, size);
byte max = entry[3];
byte min = entry[2];
byte slotNum = entry[1];
byte species = entry[0];
slots[i] = new EncounterSlot1(this, species, min, max, slotNum);
}
Slots = slots;
}
public override IEnumerable<EncounterSlot> GetMatchingSlots(PKM pk, EvoCriteria[] chain)
{
(bool useCatchRate, byte rate) = pk is PK1 pk1 ? (true, pk1.Catch_Rate) : (false, (byte)0);
foreach (var slot in Slots)
{
foreach (var evo in chain)
{
if (slot.Species != evo.Species)
continue;
if (slot.LevelMin > evo.LevelMax)
break;
if (slot.Form != evo.Form)
break;
if (useCatchRate)
{
var expect = (slot.Version == GameVersion.YW ? PersonalTable.Y : PersonalTable.RB)[slot.Species].CatchRate;
if (expect != rate && !(ParseSettings.AllowGen1Tradeback && GBRestrictions.IsTradebackCatchRate(rate)))
break;
}
yield return slot;
break;
}
}
}
}