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
|
|
|
using System;
|
2022-01-03 05:35:59 +00:00
|
|
|
using System.Collections.Generic;
|
2019-09-13 06:20:52 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
namespace PKHeX.Core;
|
|
|
|
|
|
|
|
/// <inheritdoc cref="EncounterArea" />
|
|
|
|
/// <summary>
|
|
|
|
/// <see cref="GameVersion.RBY"/> encounter area
|
|
|
|
/// </summary>
|
|
|
|
public sealed record EncounterArea1 : EncounterArea
|
2019-09-13 06:20:52 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
public readonly int Rate;
|
|
|
|
public readonly EncounterSlot1[] Slots;
|
|
|
|
|
2023-01-22 04:02:33 +00:00
|
|
|
protected override IReadOnlyList<EncounterSlot1> Raw => Slots;
|
2022-06-18 18:04:24 +00:00
|
|
|
|
|
|
|
public static EncounterArea1[] GetAreas(BinLinkerAccessor input, GameVersion game)
|
2019-09-13 06:20:52 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
var result = new EncounterArea1[input.Length];
|
|
|
|
for (int i = 0; i < result.Length; i++)
|
|
|
|
result[i] = new EncounterArea1(input[i], game);
|
|
|
|
return result;
|
|
|
|
}
|
2021-06-30 03:58:06 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
private EncounterArea1(ReadOnlySpan<byte> data, GameVersion game) : base(game)
|
|
|
|
{
|
|
|
|
Location = data[0];
|
|
|
|
// 1 byte unused
|
|
|
|
Type = (SlotType)data[2];
|
|
|
|
Rate = data[3];
|
2019-09-13 06:20:52 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
var next = data[4..];
|
|
|
|
int count = next.Length / 4;
|
|
|
|
var slots = new EncounterSlot1[count];
|
|
|
|
for (int i = 0; i < slots.Length; i++)
|
2019-09-13 06:20:52 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
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);
|
2019-09-13 06:20:52 +00:00
|
|
|
}
|
2022-06-18 18:04:24 +00:00
|
|
|
Slots = slots;
|
|
|
|
}
|
2019-09-13 06:20:52 +00:00
|
|
|
|
2023-01-22 04:02:33 +00:00
|
|
|
public override IEnumerable<EncounterSlot1> GetMatchingSlots(PKM pk, EvoCriteria[] chain)
|
2022-06-18 18:04:24 +00:00
|
|
|
{
|
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
|
|
|
(bool useCatchRate, byte rate) = pk is PK1 pk1 ? (true, pk1.Catch_Rate) : (false, (byte)0);
|
2022-06-18 18:04:24 +00:00
|
|
|
foreach (var slot in Slots)
|
2019-09-13 06:20:52 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
foreach (var evo in chain)
|
2019-09-13 06:20:52 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
if (slot.Species != evo.Species)
|
|
|
|
continue;
|
2020-07-19 18:32:40 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
if (slot.LevelMin > evo.LevelMax)
|
|
|
|
break;
|
|
|
|
if (slot.Form != evo.Form)
|
|
|
|
break;
|
2020-07-19 18:32:40 +00:00
|
|
|
|
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
|
|
|
if (useCatchRate)
|
2022-06-18 18:04:24 +00:00
|
|
|
{
|
|
|
|
var expect = (slot.Version == GameVersion.YW ? PersonalTable.Y : PersonalTable.RB)[slot.Species].CatchRate;
|
|
|
|
if (expect != rate && !(ParseSettings.AllowGen1Tradeback && GBRestrictions.IsTradebackCatchRate(rate)))
|
2020-08-21 23:35:49 +00:00
|
|
|
break;
|
|
|
|
}
|
2022-06-18 18:04:24 +00:00
|
|
|
yield return slot;
|
|
|
|
break;
|
2020-08-21 23:35:49 +00:00
|
|
|
}
|
2019-09-13 06:20:52 +00:00
|
|
|
}
|
|
|
|
}
|
2020-07-19 18:32:40 +00:00
|
|
|
}
|