mirror of
https://github.com/kwsch/PKHeX
synced 2024-11-27 14:30:56 +00:00
ccf87242c1
struct implementing interface is boxed when passed to method that accepts interface (not generic method). Removes IDexLevel (no other inheritors but EvoCriteria) and uses the primitive the data is stored (array, not IReadOnlyList) for slightly better perf.
136 lines
4.5 KiB
C#
136 lines
4.5 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
|
|
namespace PKHeX.Core
|
|
{
|
|
/// <inheritdoc cref="EncounterArea" />
|
|
/// <summary>
|
|
/// <see cref="GameVersion.GSC"/> encounter area
|
|
/// </summary>
|
|
public sealed record EncounterArea2 : EncounterArea
|
|
{
|
|
private static readonly byte[] BCC_SlotRates = { 20, 20, 10, 10, 05, 05, 10, 10, 05, 05 };
|
|
private static readonly byte[] RatesGrass = { 30, 30, 20, 10, 5, 4, 1 };
|
|
private static readonly byte[] RatesSurf = { 60, 30, 10 };
|
|
|
|
internal readonly EncounterTime Time;
|
|
public readonly byte Rate;
|
|
public readonly IReadOnlyList<byte> Rates;
|
|
public readonly EncounterSlot2[] Slots;
|
|
|
|
protected override IReadOnlyList<EncounterSlot> Raw => Slots;
|
|
|
|
public static EncounterArea2[] GetAreas(BinLinkerAccessor input, GameVersion game)
|
|
{
|
|
var result = new EncounterArea2[input.Length];
|
|
for (int i = 0; i < result.Length; i++)
|
|
result[i] = new EncounterArea2(input[i], game);
|
|
return result;
|
|
}
|
|
|
|
private EncounterArea2(ReadOnlySpan<byte> data, GameVersion game) : base(game)
|
|
{
|
|
Location = data[0];
|
|
Time = (EncounterTime)data[1];
|
|
var type = (Type = (SlotType)data[2]) & (SlotType)0xF;
|
|
Rate = data[3];
|
|
|
|
var next = data[4..];
|
|
if (type is > SlotType.Surf and not SlotType.BugContest) // Not Grass/Surf
|
|
{
|
|
const int size = 5;
|
|
int count = next.Length / size;
|
|
Rates = next[..count].ToArray();
|
|
Slots = ReadSlots(next[count..], count);
|
|
}
|
|
else
|
|
{
|
|
const int size = 4;
|
|
int count = next.Length / size;
|
|
Rates = type switch
|
|
{
|
|
SlotType.BugContest => BCC_SlotRates,
|
|
SlotType.Grass => RatesGrass,
|
|
_ => RatesSurf,
|
|
};
|
|
Slots = ReadSlots(next, count);
|
|
}
|
|
}
|
|
|
|
private EncounterSlot2[] ReadSlots(ReadOnlySpan<byte> data, int count)
|
|
{
|
|
const int size = 4;
|
|
var slots = new EncounterSlot2[count];
|
|
for (int i = 0; i < slots.Length; i++)
|
|
{
|
|
var entry = data.Slice(i * size, size);
|
|
byte max = entry[3];
|
|
byte min = entry[2];
|
|
byte slotNum = entry[1];
|
|
byte species = entry[0];
|
|
slots[i] = new EncounterSlot2(this, species, min, max, slotNum);
|
|
}
|
|
return slots;
|
|
}
|
|
|
|
public override IEnumerable<EncounterSlot> GetMatchingSlots(PKM pkm, EvoCriteria[] chain)
|
|
{
|
|
if (pkm is not ICaughtData2 {CaughtData: not 0} pk2)
|
|
return GetSlotsFuzzy(chain);
|
|
|
|
if (pk2.Met_Location != Location)
|
|
return Array.Empty<EncounterSlot>();
|
|
return GetSlotsSpecificLevelTime(chain, pk2.Met_TimeOfDay, pk2.Met_Level);
|
|
}
|
|
|
|
private IEnumerable<EncounterSlot2> GetSlotsSpecificLevelTime(EvoCriteria[] chain, int time, int lvl)
|
|
{
|
|
foreach (var slot in Slots)
|
|
{
|
|
foreach (var evo in chain)
|
|
{
|
|
if (slot.Species != evo.Species)
|
|
continue;
|
|
|
|
if (slot.Form != evo.Form)
|
|
{
|
|
if (slot.Species != (int)Species.Unown || evo.Form >= 26) // Don't yield !? forms
|
|
break;
|
|
}
|
|
|
|
if (!slot.IsLevelWithinRange(lvl))
|
|
break;
|
|
|
|
if (!Time.Contains(time))
|
|
break;
|
|
|
|
yield return slot;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private IEnumerable<EncounterSlot2> GetSlotsFuzzy(EvoCriteria[] chain)
|
|
{
|
|
foreach (var slot in Slots)
|
|
{
|
|
foreach (var evo in chain)
|
|
{
|
|
if (slot.Species != evo.Species)
|
|
continue;
|
|
|
|
if (slot.Form != evo.Form)
|
|
{
|
|
if (slot.Species != (int) Species.Unown || evo.Form >= 26) // Don't yield !? forms
|
|
break;
|
|
}
|
|
if (slot.LevelMin > evo.LevelMax)
|
|
break;
|
|
|
|
yield return slot;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|