2019-11-28 06:46:14 +00:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
2019-09-23 23:56:47 +00:00
|
|
|
|
using System.Linq;
|
|
|
|
|
|
|
|
|
|
namespace PKHeX.Core
|
|
|
|
|
{
|
2021-01-01 18:55:33 +00:00
|
|
|
|
/// <inheritdoc cref="EncounterArea" />
|
2019-09-23 23:56:47 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// <see cref="GameVersion.SWSH"/> encounter area
|
|
|
|
|
/// </summary>
|
2021-01-01 18:55:33 +00:00
|
|
|
|
public sealed record EncounterArea8 : EncounterArea
|
2019-09-23 23:56:47 +00:00
|
|
|
|
{
|
2020-12-22 01:48:08 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Slots from this area can cross over to another area, resulting in a different met location.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public bool PermitCrossover { get; init; }
|
|
|
|
|
|
2019-11-21 01:01:34 +00:00
|
|
|
|
/// <inheritdoc />
|
2019-11-19 16:38:18 +00:00
|
|
|
|
public override bool IsMatchLocation(int location)
|
|
|
|
|
{
|
|
|
|
|
if (Location == location)
|
|
|
|
|
return true;
|
|
|
|
|
|
2019-11-28 06:46:14 +00:00
|
|
|
|
if (!PermitCrossover)
|
|
|
|
|
return false;
|
|
|
|
|
|
2019-12-11 03:54:26 +00:00
|
|
|
|
// Get all other areas that the Location can bleed encounters to
|
|
|
|
|
if (!ConnectingArea8.TryGetValue(Location, out var others))
|
2019-11-19 16:38:18 +00:00
|
|
|
|
return false;
|
|
|
|
|
|
2019-12-11 03:54:26 +00:00
|
|
|
|
// Check if any of the other areas are the met location
|
|
|
|
|
return others.Contains((byte)location);
|
2019-11-19 16:38:18 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-08-21 23:35:49 +00:00
|
|
|
|
public override IEnumerable<EncounterSlot> GetMatchingSlots(PKM pkm, IReadOnlyList<EvoCriteria> chain)
|
2019-09-23 23:56:47 +00:00
|
|
|
|
{
|
2020-08-22 21:12:16 +00:00
|
|
|
|
// wild area gets boosted up to level 60 post-game
|
2020-08-22 21:52:17 +00:00
|
|
|
|
var met = pkm.Met_Level;
|
2020-10-24 18:16:01 +00:00
|
|
|
|
bool isBoosted = met == 60 && IsBoostedArea60(Location);
|
2020-08-22 21:52:17 +00:00
|
|
|
|
if (isBoosted)
|
|
|
|
|
return GetBoostedMatches(chain);
|
|
|
|
|
return GetUnboostedMatches(chain, met);
|
|
|
|
|
}
|
2020-08-22 21:12:16 +00:00
|
|
|
|
|
2020-08-22 21:52:17 +00:00
|
|
|
|
private IEnumerable<EncounterSlot> GetUnboostedMatches(IReadOnlyList<EvoCriteria> chain, int met)
|
|
|
|
|
{
|
2020-08-21 23:35:49 +00:00
|
|
|
|
foreach (var slot in Slots)
|
2019-11-16 01:34:18 +00:00
|
|
|
|
{
|
2020-08-21 23:35:49 +00:00
|
|
|
|
foreach (var evo in chain)
|
2019-11-16 01:34:18 +00:00
|
|
|
|
{
|
2020-08-21 23:35:49 +00:00
|
|
|
|
if (slot.Species != evo.Species)
|
|
|
|
|
continue;
|
2019-09-23 23:56:47 +00:00
|
|
|
|
|
2020-08-22 21:52:17 +00:00
|
|
|
|
if (!slot.IsLevelWithinRange(met))
|
|
|
|
|
break;
|
|
|
|
|
|
2020-12-11 04:42:30 +00:00
|
|
|
|
if (slot.Form != evo.Form && !FormInfo.WildChangeFormAfter.Contains(evo.Species))
|
2020-08-21 23:35:49 +00:00
|
|
|
|
break;
|
2019-09-23 23:56:47 +00:00
|
|
|
|
|
2020-08-22 21:52:17 +00:00
|
|
|
|
yield return slot;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private IEnumerable<EncounterSlot> GetBoostedMatches(IReadOnlyList<EvoCriteria> chain)
|
|
|
|
|
{
|
|
|
|
|
foreach (var slot in Slots)
|
|
|
|
|
{
|
|
|
|
|
foreach (var evo in chain)
|
|
|
|
|
{
|
|
|
|
|
if (slot.Species != evo.Species)
|
|
|
|
|
continue;
|
|
|
|
|
|
2020-11-06 05:17:13 +00:00
|
|
|
|
// Ignore max met level comparison; we already know it is permissible to boost to level 60.
|
|
|
|
|
if (slot.LevelMin > 60)
|
|
|
|
|
break; // Can't downlevel, only boost to 60.
|
2020-08-22 21:52:17 +00:00
|
|
|
|
|
2020-12-11 04:42:30 +00:00
|
|
|
|
if (slot.Form != evo.Form && !FormInfo.WildChangeFormAfter.Contains(evo.Species))
|
2020-08-21 23:35:49 +00:00
|
|
|
|
break;
|
2019-12-10 03:28:28 +00:00
|
|
|
|
|
2020-08-21 23:35:49 +00:00
|
|
|
|
yield return slot;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-11-17 23:50:41 +00:00
|
|
|
|
|
2020-12-29 08:37:59 +00:00
|
|
|
|
public static bool IsWildArea(int location) => IsWildArea8(location) || IsWildArea8Armor(location) || IsWildArea8Crown(location);
|
|
|
|
|
public static bool IsBoostedArea60(int location) => IsWildArea(location);
|
2020-10-24 18:16:01 +00:00
|
|
|
|
|
2020-12-29 08:37:59 +00:00
|
|
|
|
private static bool IsWildArea8(int location) => location is >= 122 and <= 154; // Rolling Fields -> Lake of Outrage
|
|
|
|
|
private static bool IsWildArea8Armor(int location) => location is >= 164 and <= 194; // Fields of Honor -> Honeycalm Island
|
|
|
|
|
private static bool IsWildArea8Crown(int location) => location is >= 204 and <= 234 and not 206; // Slippery Slope -> Dyna Tree Hill, skip Freezington
|
2019-11-19 16:38:18 +00:00
|
|
|
|
|
2020-06-24 12:34:26 +00:00
|
|
|
|
// Location, and areas that it can feed encounters to.
|
2019-11-21 01:01:34 +00:00
|
|
|
|
public static readonly IReadOnlyDictionary<int, IReadOnlyList<byte>> ConnectingArea8 = new Dictionary<int, IReadOnlyList<byte>>
|
2019-11-19 16:38:18 +00:00
|
|
|
|
{
|
2020-11-11 06:11:19 +00:00
|
|
|
|
// Route 3
|
|
|
|
|
// City of Motostoke
|
|
|
|
|
{28, new byte[] {20}},
|
|
|
|
|
|
2019-11-19 16:38:18 +00:00
|
|
|
|
// Rolling Fields
|
|
|
|
|
// Dappled Grove, East Lake Axewell, West Lake Axewell
|
|
|
|
|
// Also connects to South Lake Miloch but too much of a stretch
|
|
|
|
|
{122, new byte[] {124, 128, 130}},
|
|
|
|
|
|
|
|
|
|
// Dappled Grove
|
2019-11-28 22:00:55 +00:00
|
|
|
|
// Rolling Fields, Watchtower Ruins
|
2019-11-19 16:38:18 +00:00
|
|
|
|
{124, new byte[] {122, 126}},
|
|
|
|
|
|
|
|
|
|
// Watchtower Ruins
|
|
|
|
|
// Dappled Grove, West Lake Axewell
|
|
|
|
|
{126, new byte[] {124, 130}},
|
|
|
|
|
|
|
|
|
|
// East Lake Axewell
|
|
|
|
|
// Rolling Fields, West Lake Axewell, Axew's Eye, North Lake Miloch
|
|
|
|
|
{128, new byte[] {122, 130, 132, 138}},
|
|
|
|
|
|
|
|
|
|
// West Lake Axewell
|
|
|
|
|
// Rolling Fields, Watchtower Ruins, East Lake Axewell, Axew's Eye
|
|
|
|
|
{130, new byte[] {122, 126, 128, 132}},
|
|
|
|
|
|
|
|
|
|
// Axew's Eye
|
|
|
|
|
// East Lake Axewell, West Lake Axewell
|
|
|
|
|
{132, new byte[] {128, 130}},
|
|
|
|
|
|
|
|
|
|
// South Lake Miloch
|
|
|
|
|
// Giant's Seat, North Lake Miloch
|
|
|
|
|
{134, new byte[] {136, 138}},
|
|
|
|
|
|
|
|
|
|
// Giant's Seat
|
|
|
|
|
// South Lake Miloch, North Lake Miloch
|
|
|
|
|
{136, new byte[] {134, 138}},
|
|
|
|
|
|
|
|
|
|
// North Lake Miloch
|
|
|
|
|
// East Lake Axewell, South Lake Miloch, Giant's Seat
|
|
|
|
|
// Also connects to Motostoke Riverbank but too much of a stretch
|
|
|
|
|
{138, new byte[] {134, 136}},
|
|
|
|
|
|
|
|
|
|
// Motostoke Riverbank
|
|
|
|
|
// Bridge Field
|
|
|
|
|
{140, new byte[] {142}},
|
|
|
|
|
|
|
|
|
|
// Bridge Field
|
|
|
|
|
// Motostoke Riverbank, Stony Wilderness
|
|
|
|
|
{142, new byte[] {140, 144}},
|
|
|
|
|
|
|
|
|
|
// Stony Wilderness
|
|
|
|
|
// Bridge Field, Dusty Bowl, Giant's Mirror, Giant's Cap
|
|
|
|
|
{144, new byte[] {142, 146, 148, 152}},
|
|
|
|
|
|
|
|
|
|
// Dusty Bowl
|
|
|
|
|
// Stony Wilderness, Giant's Mirror, Hammerlocke Hills
|
|
|
|
|
{146, new byte[] {144, 148, 150}},
|
|
|
|
|
|
|
|
|
|
// Giant's Mirror
|
|
|
|
|
// Stony Wilderness, Dusty Bowl, Hammerlocke Hills
|
|
|
|
|
{148, new byte[] {144, 146, 148}},
|
|
|
|
|
|
|
|
|
|
// Hammerlocke Hills
|
|
|
|
|
// Dusty Bowl, Giant's Mirror, Giant's Cap
|
|
|
|
|
{150, new byte[] {146, 148, 152}},
|
|
|
|
|
|
|
|
|
|
// Giant's Cap
|
|
|
|
|
// Stony Wilderness, Giant's Cap
|
|
|
|
|
// Also connects to Lake of Outrage but too much of a stretch
|
|
|
|
|
{152, new byte[] {144, 150}},
|
|
|
|
|
|
|
|
|
|
// Lake of Outrage is just itself.
|
2020-06-24 12:34:26 +00:00
|
|
|
|
|
|
|
|
|
// Challenge Beach
|
|
|
|
|
// Soothing Wetlands, Courageous Cavern
|
|
|
|
|
{170, new byte[] {166, 176}},
|
|
|
|
|
|
|
|
|
|
// Challenge Road
|
|
|
|
|
// Brawler's Cave
|
|
|
|
|
{174, new byte[] {172}},
|
|
|
|
|
|
2020-06-27 04:31:23 +00:00
|
|
|
|
// Courageous Cavern
|
|
|
|
|
// Loop Lagoon
|
|
|
|
|
{176, new byte[] {178}},
|
|
|
|
|
|
2020-06-24 12:34:26 +00:00
|
|
|
|
// Warm-Up Tunnel
|
|
|
|
|
// Training Lowlands, Potbottom Desert
|
|
|
|
|
{182, new byte[] {180, 184}},
|
|
|
|
|
|
|
|
|
|
// Workout Sea
|
|
|
|
|
// Fields of Honor
|
|
|
|
|
{186, new byte[] {164}},
|
|
|
|
|
|
|
|
|
|
// Stepping-Stone Sea
|
|
|
|
|
// Fields of Honor
|
|
|
|
|
{188, new byte[] {170}},
|
|
|
|
|
|
2020-07-09 03:59:08 +00:00
|
|
|
|
// Insular Sea
|
|
|
|
|
// Honeycalm Sea
|
|
|
|
|
{190, new byte[] {192}},
|
|
|
|
|
|
2020-06-24 12:34:26 +00:00
|
|
|
|
// Honeycalm Sea
|
|
|
|
|
// Honeycalm Island
|
|
|
|
|
{192, new byte[] {194}},
|
2020-10-28 05:08:49 +00:00
|
|
|
|
|
|
|
|
|
// Frostpoint Field
|
|
|
|
|
// Freezington
|
|
|
|
|
{208, new byte[] {206}},
|
|
|
|
|
|
|
|
|
|
// Old Cemetery
|
|
|
|
|
// Giant’s Bed
|
|
|
|
|
{212, new byte[] {210}},
|
|
|
|
|
|
|
|
|
|
// Roaring-Sea Caves
|
|
|
|
|
// Giant’s Foot
|
|
|
|
|
{224, new byte[] {222}},
|
2020-11-01 05:29:08 +00:00
|
|
|
|
|
|
|
|
|
// Ballimere Lake
|
|
|
|
|
// Lakeside Cave
|
|
|
|
|
{230, new byte[] {232}},
|
2019-11-19 16:38:18 +00:00
|
|
|
|
};
|
2019-11-28 06:46:14 +00:00
|
|
|
|
|
2020-12-22 01:48:08 +00:00
|
|
|
|
public static EncounterArea8[] GetAreas(byte[][] input, GameVersion game, bool crossover = false)
|
2019-11-28 06:46:14 +00:00
|
|
|
|
{
|
2020-08-30 18:08:21 +00:00
|
|
|
|
var result = new EncounterArea8[input.Length];
|
|
|
|
|
for (int i = 0; i < input.Length; i++)
|
2020-12-22 01:48:08 +00:00
|
|
|
|
result[i] = new EncounterArea8(input[i], game) {PermitCrossover = crossover};
|
2020-08-30 18:08:21 +00:00
|
|
|
|
return result;
|
2019-11-28 06:46:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
2020-08-30 18:08:21 +00:00
|
|
|
|
private EncounterArea8(byte[] areaData, GameVersion game) : base(game)
|
2019-11-28 06:46:14 +00:00
|
|
|
|
{
|
|
|
|
|
Location = areaData[0];
|
2020-08-30 18:08:21 +00:00
|
|
|
|
Slots = ReadSlots(areaData, areaData[1]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private EncounterSlot[] ReadSlots(byte[] areaData, byte slotCount)
|
|
|
|
|
{
|
|
|
|
|
var slots = new EncounterSlot[slotCount];
|
2019-11-28 06:46:14 +00:00
|
|
|
|
|
|
|
|
|
int ctr = 0;
|
|
|
|
|
int ofs = 2;
|
|
|
|
|
do
|
|
|
|
|
{
|
2020-08-30 18:08:21 +00:00
|
|
|
|
var flags = (AreaWeather8) BitConverter.ToUInt16(areaData, ofs);
|
2019-11-28 06:46:14 +00:00
|
|
|
|
var min = areaData[ofs + 2];
|
|
|
|
|
var max = areaData[ofs + 3];
|
|
|
|
|
var count = areaData[ofs + 4];
|
|
|
|
|
// ofs+5 reserved
|
|
|
|
|
ofs += 6;
|
|
|
|
|
for (int i = 0; i < count; i++, ctr++, ofs += 2)
|
|
|
|
|
{
|
|
|
|
|
var specForm = BitConverter.ToUInt16(areaData, ofs);
|
2021-01-01 18:55:33 +00:00
|
|
|
|
var species = specForm & 0x7FF;
|
|
|
|
|
var form = specForm >> 11;
|
|
|
|
|
slots[ctr] = new EncounterSlot8(this, species, form, min, max, flags);
|
2019-11-28 06:46:14 +00:00
|
|
|
|
}
|
2020-08-30 18:08:21 +00:00
|
|
|
|
} while (ctr != slots.Length);
|
|
|
|
|
|
|
|
|
|
return slots;
|
2019-11-28 06:46:14 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-11-28 22:00:55 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Encounter Conditions for <see cref="GameVersion.SWSH"/>
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <remarks>Values above <see cref="All"/> are for Shaking/Fishing hidden encounters only.</remarks>
|
2019-11-28 06:46:14 +00:00
|
|
|
|
[Flags]
|
|
|
|
|
public enum AreaWeather8
|
|
|
|
|
{
|
|
|
|
|
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,
|
2019-11-28 20:56:26 +00:00
|
|
|
|
|
|
|
|
|
All = Normal | Overcast | Raining | Thunderstorm | Intense_Sun | Snowing | Snowstorm | Sandstorm | Heavy_Fog,
|
|
|
|
|
|
2019-11-28 06:46:14 +00:00
|
|
|
|
Shaking_Trees = 1 << 9,
|
|
|
|
|
Fishing = 1 << 10,
|
2019-11-28 20:56:26 +00:00
|
|
|
|
|
|
|
|
|
NotWeather = Shaking_Trees | Fishing,
|
2019-11-28 06:46:14 +00:00
|
|
|
|
}
|
2020-03-20 14:22:44 +00:00
|
|
|
|
}
|