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;
|
2023-07-06 04:14:09 +00:00
|
|
|
|
using System.Collections.Generic;
|
2018-06-24 05:00:01 +00:00
|
|
|
|
using System.Linq;
|
2023-07-06 04:14:09 +00:00
|
|
|
|
using System.Runtime.InteropServices;
|
2018-06-24 05:00:01 +00:00
|
|
|
|
using static PKHeX.Core.LegalityCheckStrings;
|
2021-01-31 03:15:39 +00:00
|
|
|
|
using static PKHeX.Core.MemoryPermissions;
|
2022-08-25 03:32:40 +00:00
|
|
|
|
using static PKHeX.Core.EntityContext;
|
2018-06-24 05:00:01 +00:00
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
|
namespace PKHeX.Core;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Verifies the <see cref="IMemoryOT.OT_Memory"/>, <see cref="IMemoryHT.HT_Memory"/>, and associated values.
|
|
|
|
|
/// </summary>
|
|
|
|
|
public sealed class MemoryVerifier : Verifier
|
2018-06-24 05:00:01 +00:00
|
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
|
protected override CheckIdentifier Identifier => CheckIdentifier.Memory;
|
2018-06-24 05:00:01 +00:00
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
|
public override void Verify(LegalityAnalysis data)
|
|
|
|
|
{
|
|
|
|
|
var pk = data.Entity;
|
2023-07-06 04:14:09 +00:00
|
|
|
|
var sources = MemoryRules.GetPossibleSources(data.Info.EvoChainsAllGens);
|
|
|
|
|
if (sources == MemorySource.None)
|
2018-06-24 05:00:01 +00:00
|
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
|
VerifyOTMemoryIs(data, 0, 0, 0, 0);
|
|
|
|
|
VerifyHTMemoryNone(data, (ITrainerMemories)pk);
|
|
|
|
|
return;
|
2018-06-24 05:00:01 +00:00
|
|
|
|
}
|
2022-06-18 18:04:24 +00:00
|
|
|
|
VerifyOTMemory(data);
|
2023-07-06 04:14:09 +00:00
|
|
|
|
VerifyHTMemory(data, sources);
|
2022-06-18 18:04:24 +00:00
|
|
|
|
}
|
2018-07-27 02:34:27 +00:00
|
|
|
|
|
2023-07-06 04:14:09 +00:00
|
|
|
|
private void VerifyHTMemory(LegalityAnalysis data, MemorySource sources)
|
2022-06-18 18:04:24 +00:00
|
|
|
|
{
|
2023-07-06 04:14:09 +00:00
|
|
|
|
var pk = data.Entity;
|
|
|
|
|
sources = MemoryRules.ReviseSourcesHandler(pk, sources, data.EncounterMatch);
|
|
|
|
|
if (sources == MemorySource.None)
|
|
|
|
|
{
|
|
|
|
|
VerifyHTMemoryNone(data, (ITrainerMemories)pk);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
VerifyHTMemoryContextVisited(data, sources);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void VerifyHTMemoryContextVisited(LegalityAnalysis data, MemorySource sources)
|
|
|
|
|
{
|
|
|
|
|
// Memories aren't reset when imported into formats, so it could be from any context visited.
|
|
|
|
|
var results = data.Info.Parse;
|
|
|
|
|
var start = results.Count;
|
|
|
|
|
if (sources.HasFlag(MemorySource.Gen6))
|
|
|
|
|
{
|
|
|
|
|
results.RemoveRange(start, results.Count - start);
|
|
|
|
|
VerifyHTMemory(data, Gen6);
|
|
|
|
|
VerifyHTLanguage(data, MemorySource.Gen6);
|
|
|
|
|
if (ValidSet(results, start))
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (sources.HasFlag(MemorySource.Gen8))
|
|
|
|
|
{
|
|
|
|
|
results.RemoveRange(start, results.Count - start);
|
|
|
|
|
VerifyHTMemory(data, Gen8);
|
|
|
|
|
VerifyHTLanguage(data, MemorySource.Gen8);
|
|
|
|
|
if (ValidSet(results, start))
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (sources.HasFlag(MemorySource.Bank))
|
|
|
|
|
{
|
|
|
|
|
results.RemoveRange(start, results.Count - start);
|
|
|
|
|
VerifyHTMemoryTransferTo7(data, data.Entity, data.Info);
|
|
|
|
|
VerifyHTLanguage(data, MemorySource.Bank);
|
|
|
|
|
if (ValidSet(results, start))
|
|
|
|
|
return;
|
|
|
|
|
}
|
2023-08-19 18:13:20 +00:00
|
|
|
|
if (sources.HasFlag(MemorySource.Deleted))
|
2023-07-06 04:14:09 +00:00
|
|
|
|
{
|
|
|
|
|
results.RemoveRange(start, results.Count - start);
|
|
|
|
|
VerifyHTMemoryNone(data, (ITrainerMemories)data.Entity);
|
|
|
|
|
VerifyHTLanguage(data, MemorySource.Deleted);
|
|
|
|
|
}
|
2022-06-18 18:04:24 +00:00
|
|
|
|
}
|
2022-05-31 04:43:52 +00:00
|
|
|
|
|
2023-07-06 04:14:09 +00:00
|
|
|
|
private void VerifyHTLanguage(LegalityAnalysis data, MemorySource source)
|
|
|
|
|
{
|
|
|
|
|
var pk = data.Entity;
|
|
|
|
|
if (pk is not IHandlerLanguage h)
|
|
|
|
|
return;
|
|
|
|
|
if (!GetIsHTLanguageValid(data.EncounterMatch, pk, h.HT_Language, source))
|
|
|
|
|
data.AddLine(GetInvalid(LMemoryHTLanguage));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static bool GetIsHTLanguageValid(IEncounterTemplate enc, PKM pk, byte language, MemorySource source)
|
|
|
|
|
{
|
|
|
|
|
// Bounds check.
|
|
|
|
|
if (language > (int)LanguageID.ChineseT)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
// Gen6 and Bank don't have the HT language flag.
|
|
|
|
|
if (source is MemorySource.Gen6 or MemorySource.Bank)
|
|
|
|
|
return language == 0;
|
|
|
|
|
|
|
|
|
|
// Some encounters erroneously set the HT flag.
|
|
|
|
|
if (enc is EncounterStatic9 { GiftWithLanguage: true })
|
|
|
|
|
{
|
|
|
|
|
// Must be the SAV language or another-with-HT_Name.
|
|
|
|
|
if (language == 0)
|
|
|
|
|
return false;
|
|
|
|
|
if (pk.IsUntraded)
|
|
|
|
|
return language == pk.Language;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (pk.IsUntraded)
|
|
|
|
|
return language == 0;
|
|
|
|
|
|
|
|
|
|
// Can be anything within bounds.
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static bool ValidSet(List<CheckResult> results, int start)
|
|
|
|
|
{
|
|
|
|
|
var count = results.Count - start;
|
|
|
|
|
if (count == 0)
|
|
|
|
|
return true; // None added.
|
|
|
|
|
|
|
|
|
|
var span = CollectionsMarshal.AsSpan(results)[start..];
|
|
|
|
|
foreach (var result in span)
|
|
|
|
|
{
|
|
|
|
|
if (result.Valid)
|
|
|
|
|
continue;
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private CheckResult VerifyCommonMemory(PKM pk, int handler, LegalInfo info, MemoryContext mem)
|
2022-06-18 18:04:24 +00:00
|
|
|
|
{
|
|
|
|
|
var memory = MemoryVariableSet.Read((ITrainerMemories)pk, handler);
|
|
|
|
|
|
|
|
|
|
// Actionable HM moves
|
2023-01-22 04:02:33 +00:00
|
|
|
|
int hmIndex = MemoryContext6.MoveSpecificMemoryHM.IndexOf(memory.MemoryID);
|
2022-06-18 18:04:24 +00:00
|
|
|
|
if (hmIndex != -1)
|
2023-07-06 04:14:09 +00:00
|
|
|
|
return VerifyMemoryHM6(info, mem, memory, hmIndex);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
|
2022-08-25 03:32:40 +00:00
|
|
|
|
if (mem.IsInvalidGeneralLocationMemoryValue(memory.MemoryID, memory.Variable, info.EncounterMatch, pk))
|
2022-06-18 18:04:24 +00:00
|
|
|
|
return GetInvalid(string.Format(LMemoryArgBadLocation, memory.Handler));
|
|
|
|
|
|
2022-08-25 03:32:40 +00:00
|
|
|
|
if (mem.IsInvalidMiscMemory(memory.MemoryID, memory.Variable))
|
2022-06-18 18:04:24 +00:00
|
|
|
|
return GetInvalid(string.Format(LMemoryArgBadID, memory.Handler));
|
|
|
|
|
|
|
|
|
|
switch (memory.MemoryID)
|
|
|
|
|
{
|
|
|
|
|
case 19 when pk.Species is (int)Species.Urshifu && memory.Variable is not 34: // tall building is the only location for evolving Urshifu
|
|
|
|
|
case 19 when pk.Species is (int)Species.Runerigus && memory.Variable is not 72: // vast field is the only location for evolving Runerigus
|
2021-08-22 18:10:29 +00:00
|
|
|
|
return GetInvalid(string.Format(LMemoryArgBadLocation, memory.Handler));
|
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
|
// {0} saw {2} carrying {1} on its back. {4} that {3}.
|
2023-07-06 04:14:09 +00:00
|
|
|
|
case 21 when mem.Context != Gen6 || !PersonalTable.AO.GetFormEntry(memory.Variable, 0).GetIsLearnHM(2): // Fly
|
2023-04-15 08:58:37 +00:00
|
|
|
|
return BadSpeciesMove(memory.Handler);
|
2022-06-18 18:04:24 +00:00
|
|
|
|
|
|
|
|
|
// {0} used {2} at {1}’s instruction, but it had no effect. {4} that {3}.
|
|
|
|
|
// The Move Deleter that {0} met through {1} made it forget {2}. {4} that {3}.
|
2023-07-06 04:14:09 +00:00
|
|
|
|
case 16 or 48 when !CanKnowMove(pk, memory, mem.Context, info, memory.MemoryID == 16):
|
2023-04-15 08:58:37 +00:00
|
|
|
|
return BadSpeciesMove(memory.Handler);
|
2022-06-18 18:04:24 +00:00
|
|
|
|
|
2023-07-06 04:14:09 +00:00
|
|
|
|
case 49 when memory.Variable == 0 || !GetCanRelearnMove(pk, memory.Variable, mem.Context, info.EvoChainsAllGens, info.EncounterOriginal):
|
2023-04-15 08:58:37 +00:00
|
|
|
|
return BadSpeciesMove(memory.Handler);
|
2022-06-18 18:04:24 +00:00
|
|
|
|
|
|
|
|
|
// Dynamaxing
|
|
|
|
|
// {0} battled at {1}’s side against {2} that Dynamaxed. {4} that {3}.
|
|
|
|
|
case 71 when !GetCanDynamaxTrainer(memory.Variable, 8, handler == 0 ? (GameVersion)pk.Version : GameVersion.Any):
|
|
|
|
|
// {0} battled {2} and Dynamaxed upon {1}’s instruction. {4} that {3}.
|
|
|
|
|
case 72 when !PersonalTable.SWSH.IsSpeciesInGame(memory.Variable):
|
|
|
|
|
return GetInvalid(string.Format(LMemoryArgBadSpecies, memory.Handler));
|
|
|
|
|
|
|
|
|
|
// Move
|
|
|
|
|
// {0} studied about how to use {2} in a Box, thinking about {1}. {4} that {3}.
|
|
|
|
|
// {0} practiced its cool pose for the move {2} in a Box, wishing to be praised by {1}. {4} that {3}.
|
2023-07-06 04:14:09 +00:00
|
|
|
|
case 80 or 81 when !CanKnowMove(pk, memory, mem.Context, info):
|
2023-04-15 08:58:37 +00:00
|
|
|
|
return BadSpeciesMove(memory.Handler);
|
2022-06-18 18:04:24 +00:00
|
|
|
|
|
|
|
|
|
// Species
|
|
|
|
|
// With {1}, {0} went fishing, and they caught {2}. {4} that {3}.
|
2023-07-06 04:14:09 +00:00
|
|
|
|
case 7 when !GetCanFishSpecies(memory.Variable, mem.Context, handler == 0 ? (GameVersion)pk.Version : GameVersion.Any):
|
2022-06-18 18:04:24 +00:00
|
|
|
|
return GetInvalid(string.Format(LMemoryArgBadSpecies, memory.Handler));
|
|
|
|
|
|
|
|
|
|
// {0} saw {1} paying attention to {2}. {4} that {3}.
|
|
|
|
|
// {0} fought hard until it had to use Struggle when it battled at {1}’s side against {2}. {4} that {3}.
|
|
|
|
|
// {0} was taken to a Pokémon Nursery by {1} and left with {2}. {4} that {3}.
|
2023-07-06 04:14:09 +00:00
|
|
|
|
case 9 or 60 or 75 when mem.Context == Gen8 && !PersonalTable.SWSH.IsSpeciesInGame(memory.Variable):
|
2022-06-18 18:04:24 +00:00
|
|
|
|
return GetInvalid(string.Format(LMemoryArgBadSpecies, memory.Handler));
|
|
|
|
|
|
|
|
|
|
// {0} had a great chat about {1} with the {2} that it was in a Box with. {4} that {3}.
|
|
|
|
|
// {0} became good friends with the {2} in a Box, practiced moves with it, and talked about the day that {0} would be praised by {1}. {4} that {3}.
|
|
|
|
|
// {0} got in a fight with the {2} that it was in a Box with about {1}. {4} that {3}.
|
|
|
|
|
case 82 or 83 or 87 when !PersonalTable.SWSH.IsSpeciesInGame(memory.Variable):
|
|
|
|
|
return GetInvalid(string.Format(LMemoryArgBadSpecies, memory.Handler));
|
|
|
|
|
|
|
|
|
|
// {0} had a very hard training session with {1}. {4} that {3}.
|
2023-07-06 04:14:09 +00:00
|
|
|
|
case 53 when mem.Context == Gen8 && pk is IHyperTrain t && !t.IsHyperTrained():
|
2021-08-27 04:20:52 +00:00
|
|
|
|
return GetInvalid(string.Format(LMemoryArgBadID, memory.Handler));
|
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
|
// Item
|
|
|
|
|
// {0} went to a Pokémon Center with {1} to buy {2}. {4} that {3}.
|
2023-07-06 04:14:09 +00:00
|
|
|
|
case 5 when !CanBuyItem(mem.Context, memory.Variable, handler == 0 ? (GameVersion)pk.Version : GameVersion.Any):
|
2022-06-18 18:04:24 +00:00
|
|
|
|
// {1} used {2} when {0} was in trouble. {4} that {3}.
|
2023-07-06 04:14:09 +00:00
|
|
|
|
case 15 when !CanUseItem(mem.Context, memory.Variable, pk.Species):
|
2022-06-18 18:04:24 +00:00
|
|
|
|
// {0} saw {1} using {2}. {4} that {3}.
|
2023-07-06 04:14:09 +00:00
|
|
|
|
case 26 when !CanUseItemGeneric(mem.Context, memory.Variable):
|
2022-06-18 18:04:24 +00:00
|
|
|
|
// {0} planted {2} with {1} and imagined a big harvest. {4} that {3}.
|
2023-07-06 04:14:09 +00:00
|
|
|
|
case 34 when !CanPlantBerry(mem.Context, memory.Variable):
|
2022-06-18 18:04:24 +00:00
|
|
|
|
// {1} had {0} hold items like {2} to help it along. {4} that {3}.
|
2023-07-06 04:14:09 +00:00
|
|
|
|
case 40 when !CanHoldItem(mem.Context, memory.Variable):
|
2022-06-18 18:04:24 +00:00
|
|
|
|
// {0} was excited when {1} won prizes like {2} through Loto-ID. {4} that {3}.
|
2023-07-06 04:14:09 +00:00
|
|
|
|
case 51 when !CanWinLotoID(mem.Context, memory.Variable):
|
2022-06-18 18:04:24 +00:00
|
|
|
|
// {0} was worried if {1} was looking for the {2} that it was holding in a Box. {4} that {3}.
|
|
|
|
|
// When {0} was in a Box, it thought about the reason why {1} had it hold the {2}. {4} that {3}.
|
|
|
|
|
case 84 or 88 when !Legal.HeldItems_SWSH.Contains(memory.Variable) || pk.IsEgg:
|
|
|
|
|
return GetInvalid(string.Format(LMemoryArgBadItem, memory.Handler));
|
|
|
|
|
}
|
2018-06-24 05:00:01 +00:00
|
|
|
|
|
2022-08-25 03:32:40 +00:00
|
|
|
|
return VerifyCommonMemoryEtc(memory, mem);
|
2022-06-18 18:04:24 +00:00
|
|
|
|
}
|
2018-06-24 05:00:01 +00:00
|
|
|
|
|
2023-07-06 04:14:09 +00:00
|
|
|
|
private CheckResult VerifyMemoryHM6(LegalInfo info, MemoryContext mem, MemoryVariableSet memory, int hmIndex)
|
2023-04-15 08:58:37 +00:00
|
|
|
|
{
|
2023-07-06 04:14:09 +00:00
|
|
|
|
if (mem.Context != Gen6) // Gen8 has no HMs, so this memory can never exist.
|
2023-04-15 08:58:37 +00:00
|
|
|
|
return BadSpeciesMove(memory.Handler);
|
|
|
|
|
|
|
|
|
|
if (info.EncounterMatch.Species == (int)Species.Smeargle)
|
|
|
|
|
return VerifyCommonMemoryEtc(memory, mem);
|
|
|
|
|
|
|
|
|
|
// All AO hidden machine permissions are super-sets of Gen 3-5 games.
|
|
|
|
|
// Don't need to check the move history -- a learned HM in a prior game can still be learned in Gen6.
|
|
|
|
|
var pt = PersonalTable.AO;
|
2023-07-06 04:14:09 +00:00
|
|
|
|
foreach (ref readonly var evo in info.EvoChainsAllGens.Gen6.AsSpan())
|
2023-04-15 08:58:37 +00:00
|
|
|
|
{
|
|
|
|
|
var entry = pt[evo.Species];
|
|
|
|
|
var canLearn = entry.GetIsLearnHM(hmIndex);
|
|
|
|
|
if (canLearn)
|
|
|
|
|
return VerifyCommonMemoryEtc(memory, mem);
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return BadSpeciesMove(memory.Handler);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private CheckResult BadSpeciesMove(string handler) => GetInvalid(string.Format(LMemoryArgBadMove, handler));
|
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
|
private CheckResult VerifyCommonMemoryEtc(MemoryVariableSet memory, MemoryContext context)
|
|
|
|
|
{
|
|
|
|
|
if (!context.CanHaveIntensity(memory.MemoryID, memory.Intensity))
|
|
|
|
|
{
|
|
|
|
|
var min = context.GetMinimumIntensity(memory.MemoryID);
|
|
|
|
|
return GetInvalid(string.Format(LMemoryIndexIntensityMin, memory.Handler, min));
|
|
|
|
|
}
|
2020-12-21 22:22:24 +00:00
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
|
if (!context.CanHaveFeeling(memory.MemoryID, memory.Feeling, memory.Variable))
|
|
|
|
|
return GetInvalid(string.Format(LMemoryFeelInvalid, memory.Handler));
|
2018-06-24 05:00:01 +00:00
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
|
return GetValid(string.Format(LMemoryF_0_Valid, memory.Handler));
|
|
|
|
|
}
|
2018-06-24 05:00:01 +00:00
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Used for enforcing a fixed memory detail.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="data">Output storage</param>
|
|
|
|
|
/// <param name="m">Memory ID</param>
|
|
|
|
|
/// <param name="i">Intensity</param>
|
|
|
|
|
/// <param name="t">Text Variable</param>
|
|
|
|
|
/// <param name="f">Feeling</param>
|
|
|
|
|
private void VerifyOTMemoryIs(LegalityAnalysis data, byte m, byte i, ushort t, byte f)
|
|
|
|
|
{
|
|
|
|
|
var pk = (ITrainerMemories)data.Entity;
|
|
|
|
|
if (pk.OT_Memory != m)
|
|
|
|
|
data.AddLine(GetInvalid(string.Format(LMemoryIndexID, L_XOT, m)));
|
|
|
|
|
if (pk.OT_Intensity != i)
|
|
|
|
|
data.AddLine(GetInvalid(string.Format(LMemoryIndexIntensity, L_XOT, i)));
|
|
|
|
|
if (pk.OT_TextVar != t)
|
|
|
|
|
data.AddLine(GetInvalid(string.Format(LMemoryIndexVar, L_XOT, t)));
|
|
|
|
|
if (pk.OT_Feeling != f)
|
|
|
|
|
data.AddLine(GetInvalid(string.Format(LMemoryIndexFeel, L_XOT, f)));
|
|
|
|
|
}
|
2018-06-24 05:00:01 +00:00
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
|
private void VerifyHTMemoryNone(LegalityAnalysis data, ITrainerMemories pk)
|
|
|
|
|
{
|
|
|
|
|
if (pk.HT_Memory != 0 || pk.HT_TextVar != 0 || pk.HT_Intensity != 0 || pk.HT_Feeling != 0)
|
|
|
|
|
data.AddLine(GetInvalid(string.Format(LMemoryCleared, L_XHT)));
|
|
|
|
|
}
|
2021-08-22 23:41:57 +00:00
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
|
private void VerifyOTMemory(LegalityAnalysis data)
|
|
|
|
|
{
|
2023-07-06 04:14:09 +00:00
|
|
|
|
var enc = data.Info.EncounterMatch;
|
|
|
|
|
var context = enc.Context;
|
2022-06-18 18:04:24 +00:00
|
|
|
|
var pk = data.Entity;
|
|
|
|
|
var mem = (ITrainerMemories)pk;
|
2018-06-24 05:00:01 +00:00
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
|
// If the encounter has a memory from the OT that could never have it replaced, ensure it was not modified.
|
|
|
|
|
switch (data.EncounterMatch)
|
2018-06-24 05:00:01 +00:00
|
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
|
case WC6 {IsEgg: false} g when g.OTGender != 3:
|
|
|
|
|
VerifyOTMemoryIs(data, g.OT_Memory, g.OT_Intensity, g.OT_TextVar, g.OT_Feeling);
|
|
|
|
|
return;
|
|
|
|
|
case WC7 {IsEgg: false} g when g.OTGender != 3:
|
|
|
|
|
VerifyOTMemoryIs(data, g.OT_Memory, g.OT_Intensity, g.OT_TextVar, g.OT_Feeling);
|
|
|
|
|
return;
|
|
|
|
|
case WC8 {IsEgg: false} g when g.OTGender != 3:
|
|
|
|
|
VerifyOTMemoryIs(data, g.OT_Memory, g.OT_Intensity, g.OT_TextVar, g.OT_Feeling);
|
|
|
|
|
return;
|
2018-07-27 02:34:27 +00:00
|
|
|
|
|
2022-11-25 01:42:17 +00:00
|
|
|
|
case IMemoryOTReadOnly t and not MysteryGift: // Ignore Mystery Gift cases (covered above)
|
2022-06-18 18:04:24 +00:00
|
|
|
|
VerifyOTMemoryIs(data, t.OT_Memory, t.OT_Intensity, t.OT_TextVar, t.OT_Feeling);
|
|
|
|
|
return;
|
2019-12-08 07:36:39 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
|
var memory = mem.OT_Memory;
|
2018-06-24 05:00:01 +00:00
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
|
if (pk.IsEgg)
|
|
|
|
|
{
|
|
|
|
|
// Traded unhatched eggs in Gen8 have OT link trade memory applied erroneously.
|
|
|
|
|
// They can also have the box-inspect memory!
|
2022-08-25 03:32:40 +00:00
|
|
|
|
if (context != Gen8 || !((pk.Met_Location == Locations.LinkTrade6 && memory == 4) || memory == 85))
|
2018-06-24 05:00:01 +00:00
|
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
|
VerifyOTMemoryIs(data, 0, 0, 0, 0); // empty
|
|
|
|
|
return;
|
2018-06-24 05:00:01 +00:00
|
|
|
|
}
|
2022-06-18 18:04:24 +00:00
|
|
|
|
}
|
2022-08-25 03:32:40 +00:00
|
|
|
|
else if (!CanHaveMemoryForOT(pk, context, memory))
|
2022-06-18 18:04:24 +00:00
|
|
|
|
{
|
|
|
|
|
VerifyOTMemoryIs(data, 0, 0, 0, 0); // empty
|
|
|
|
|
return;
|
|
|
|
|
}
|
2018-06-24 05:00:01 +00:00
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
|
// Bounds checking
|
2022-08-25 03:32:40 +00:00
|
|
|
|
var mc = Memories.GetContext(context);
|
|
|
|
|
if (!mc.CanObtainMemoryOT((GameVersion)pk.Version, memory))
|
2022-06-18 18:04:24 +00:00
|
|
|
|
data.AddLine(GetInvalid(string.Format(LMemoryArgBadID, L_XOT)));
|
2019-12-08 07:36:39 +00:00
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
|
// Verify memory if specific to OT
|
|
|
|
|
switch (memory)
|
|
|
|
|
{
|
|
|
|
|
// No Memory
|
2023-01-22 04:02:33 +00:00
|
|
|
|
case 0: // SW/SH trades don't set HT memories immediately, which is hilarious.
|
2023-07-06 04:14:09 +00:00
|
|
|
|
data.AddLine(Get(LMemoryMissingOT, mc.Context == Gen8 ? Severity.Fishy : Severity.Invalid));
|
2022-06-18 18:04:24 +00:00
|
|
|
|
VerifyOTMemoryIs(data, 0, 0, 0, 0);
|
2019-12-12 03:37:51 +00:00
|
|
|
|
return;
|
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
|
// {0} hatched from an Egg and saw {1} for the first time at... {2}. {4} that {3}.
|
2023-07-06 04:14:09 +00:00
|
|
|
|
case 2 when !enc.EggEncounter:
|
2022-06-18 18:04:24 +00:00
|
|
|
|
data.AddLine(GetInvalid(string.Format(LMemoryArgBadHatch, L_XOT)));
|
|
|
|
|
break;
|
2018-06-24 05:00:01 +00:00
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
|
// {0} became {1}’s friend when it arrived via Link Trade at... {2}. {4} that {3}.
|
2023-12-04 04:13:20 +00:00
|
|
|
|
case 4 when mc.Context == Gen6: // Gen8 applies this memory erroneously
|
2022-06-18 18:04:24 +00:00
|
|
|
|
data.AddLine(GetInvalid(string.Format(LMemoryArgBadOTEgg, L_XOT)));
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// {0} went to the Pokémon Center in {2} with {1} and had its tired body healed there. {4} that {3}.
|
2022-08-25 03:32:40 +00:00
|
|
|
|
case 6 when !mc.HasPokeCenter((GameVersion)pk.Version, mem.OT_TextVar):
|
2022-06-18 18:04:24 +00:00
|
|
|
|
data.AddLine(GetInvalid(string.Format(LMemoryArgBadLocation, L_XOT)));
|
|
|
|
|
return;
|
2018-06-24 05:00:01 +00:00
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
|
// {0} was with {1} when {1} caught {2}. {4} that {3}.
|
|
|
|
|
case 14:
|
2023-07-06 04:14:09 +00:00
|
|
|
|
var result = GetCanBeCaptured(mem.OT_TextVar, mc.Context, (GameVersion)pk.Version) // Any Game in the Handling Trainer's generation
|
2022-06-18 18:04:24 +00:00
|
|
|
|
? GetValid(string.Format(LMemoryArgSpecies, L_XOT))
|
|
|
|
|
: GetInvalid(string.Format(LMemoryArgBadSpecies, L_XOT));
|
|
|
|
|
data.AddLine(result);
|
|
|
|
|
return;
|
2018-06-24 05:00:01 +00:00
|
|
|
|
}
|
2018-07-27 02:34:27 +00:00
|
|
|
|
|
2023-07-06 04:14:09 +00:00
|
|
|
|
data.AddLine(VerifyCommonMemory(pk, 0, data.Info, mc));
|
2022-06-18 18:04:24 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-08-25 03:32:40 +00:00
|
|
|
|
private static bool CanHaveMemoryForOT(PKM pk, EntityContext origin, int memory)
|
2022-06-18 18:04:24 +00:00
|
|
|
|
{
|
2022-08-25 03:32:40 +00:00
|
|
|
|
// Eggs cannot have memories
|
|
|
|
|
if (pk.IsEgg)
|
2019-12-12 03:37:51 +00:00
|
|
|
|
{
|
2022-08-25 03:32:40 +00:00
|
|
|
|
if (origin == Gen8) // Gen8 sets for traded eggs.
|
|
|
|
|
return pk.IsTradedEgg;
|
|
|
|
|
return false;
|
2019-12-12 03:37:51 +00:00
|
|
|
|
}
|
2022-08-25 03:32:40 +00:00
|
|
|
|
|
|
|
|
|
return origin switch
|
|
|
|
|
{
|
|
|
|
|
Gen1 or Gen2 or Gen7 => memory == 4, // VC transfers can only have Bank memory.
|
|
|
|
|
Gen6 => true,
|
|
|
|
|
Gen8 => !(pk.GO_HOME || pk.Met_Location == Locations.HOME8), // HOME does not set memories.
|
|
|
|
|
_ => false,
|
|
|
|
|
};
|
2022-06-18 18:04:24 +00:00
|
|
|
|
}
|
2019-12-12 03:37:51 +00:00
|
|
|
|
|
2023-07-06 04:14:09 +00:00
|
|
|
|
private void VerifyHTMemory(LegalityAnalysis data, EntityContext memoryGen)
|
2022-06-18 18:04:24 +00:00
|
|
|
|
{
|
|
|
|
|
var pk = data.Entity;
|
|
|
|
|
var mem = (ITrainerMemories)pk;
|
2019-12-08 07:36:39 +00:00
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
|
var memory = mem.HT_Memory;
|
2019-12-10 00:23:29 +00:00
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
|
if (pk.IsUntraded)
|
|
|
|
|
{
|
|
|
|
|
if (memory == 4 && WasTradedSWSHEgg(pk))
|
2019-12-08 07:36:39 +00:00
|
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
|
// Untraded link trade eggs in Gen8 have HT link trade memory applied erroneously.
|
|
|
|
|
// Verify the link trade memory later.
|
2019-12-08 07:36:39 +00:00
|
|
|
|
}
|
2022-06-18 18:04:24 +00:00
|
|
|
|
else
|
2018-06-24 05:00:01 +00:00
|
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
|
VerifyHTMemoryNone(data, mem);
|
2019-12-08 07:36:39 +00:00
|
|
|
|
return;
|
|
|
|
|
}
|
2022-06-18 18:04:24 +00:00
|
|
|
|
}
|
2018-06-24 05:00:01 +00:00
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
|
if (pk.Format == 7)
|
|
|
|
|
{
|
2023-07-06 04:14:09 +00:00
|
|
|
|
VerifyHTMemoryTransferTo7(data, pk, data.Info);
|
2022-06-18 18:04:24 +00:00
|
|
|
|
return;
|
2019-12-08 07:36:39 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
|
// Bounds checking
|
2023-07-06 04:14:09 +00:00
|
|
|
|
var mc = Memories.GetContext(memoryGen);
|
|
|
|
|
if (!mc.CanObtainMemoryHT((GameVersion)pk.Version, memory))
|
2022-06-18 18:04:24 +00:00
|
|
|
|
data.AddLine(GetInvalid(string.Format(LMemoryArgBadID, L_XHT)));
|
|
|
|
|
|
|
|
|
|
// Verify memory if specific to HT
|
|
|
|
|
switch (memory)
|
2019-12-08 07:36:39 +00:00
|
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
|
// No Memory
|
2023-01-22 04:02:33 +00:00
|
|
|
|
case 0: // SW/SH memory application has an off-by-one error: [0,99] + 1 <= chance --> don't apply
|
2023-07-06 04:14:09 +00:00
|
|
|
|
var severity = mc.Context switch
|
2022-12-04 08:03:47 +00:00
|
|
|
|
{
|
|
|
|
|
Gen8 when pk is not PK8 && !pk.SWSH => Severity.Valid,
|
|
|
|
|
Gen8 => ParseSettings.Gen8MemoryMissingHT,
|
|
|
|
|
_ => Severity.Invalid,
|
|
|
|
|
};
|
|
|
|
|
if (severity != Severity.Valid)
|
|
|
|
|
data.AddLine(Get(LMemoryMissingHT, severity));
|
2020-08-01 00:25:14 +00:00
|
|
|
|
VerifyHTMemoryNone(data, mem);
|
2019-12-08 07:36:39 +00:00
|
|
|
|
return;
|
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
|
// {0} met {1} at... {2}. {1} threw a Poké Ball at it, and they started to travel together. {4} that {3}.
|
|
|
|
|
case 1:
|
|
|
|
|
data.AddLine(GetInvalid(string.Format(LMemoryArgBadCatch, L_XHT)));
|
2019-12-08 07:36:39 +00:00
|
|
|
|
return;
|
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
|
// {0} hatched from an Egg and saw {1} for the first time at... {2}. {4} that {3}.
|
|
|
|
|
case 2:
|
|
|
|
|
data.AddLine(GetInvalid(string.Format(LMemoryArgBadHatch, L_XHT)));
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// {0} went to the Pokémon Center in {2} with {1} and had its tired body healed there. {4} that {3}.
|
2023-07-06 04:14:09 +00:00
|
|
|
|
case 6 when !mc.HasPokeCenter(GameVersion.Any, mem.HT_TextVar):
|
2022-06-18 18:04:24 +00:00
|
|
|
|
data.AddLine(GetInvalid(string.Format(LMemoryArgBadLocation, L_XHT)));
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// {0} was with {1} when {1} caught {2}. {4} that {3}.
|
|
|
|
|
case 14:
|
2023-07-06 04:14:09 +00:00
|
|
|
|
var result = GetCanBeCaptured(mem.HT_TextVar, mc.Context, GameVersion.Any) // Any Game in the Handling Trainer's generation
|
2022-06-18 18:04:24 +00:00
|
|
|
|
? GetValid(string.Format(LMemoryArgSpecies, L_XHT))
|
|
|
|
|
: GetInvalid(string.Format(LMemoryArgBadSpecies, L_XHT));
|
|
|
|
|
data.AddLine(result);
|
|
|
|
|
return;
|
2018-06-24 05:00:01 +00:00
|
|
|
|
}
|
2022-06-18 18:04:24 +00:00
|
|
|
|
|
2023-07-06 04:14:09 +00:00
|
|
|
|
var commonResult = VerifyCommonMemory(pk, 1, data.Info, mc);
|
2022-06-18 18:04:24 +00:00
|
|
|
|
data.AddLine(commonResult);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static bool WasTradedSWSHEgg(PKM pk) => pk.SWSH && (!pk.IsEgg ? pk.Egg_Location : pk.Met_Location) is Locations.LinkTrade6;
|
|
|
|
|
|
|
|
|
|
private void VerifyHTMemoryTransferTo7(LegalityAnalysis data, PKM pk, LegalInfo Info)
|
|
|
|
|
{
|
|
|
|
|
var mem = (ITrainerMemories)pk;
|
|
|
|
|
// Bank Transfer adds in the Link Trade Memory.
|
|
|
|
|
// Trading 7<->7 between games (not Bank) clears this data.
|
|
|
|
|
if (mem.HT_Memory == 0)
|
|
|
|
|
{
|
|
|
|
|
VerifyHTMemoryNone(data, mem);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Transfer 6->7 & withdraw to same HT => keeps past gen memory
|
|
|
|
|
// Don't require link trade memory for these past gen cases
|
|
|
|
|
int gen = Info.Generation;
|
|
|
|
|
if (gen is >= 3 and < 7 && pk.CurrentHandler == 1)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (mem.HT_Memory != 4)
|
|
|
|
|
data.AddLine(Severity.Invalid, LMemoryIndexLinkHT, CheckIdentifier.Memory);
|
|
|
|
|
if (mem.HT_TextVar != 0)
|
|
|
|
|
data.AddLine(Severity.Invalid, LMemoryIndexArgHT, CheckIdentifier.Memory);
|
|
|
|
|
if (mem.HT_Intensity != 1)
|
|
|
|
|
data.AddLine(Severity.Invalid, LMemoryIndexIntensityHT1, CheckIdentifier.Memory);
|
|
|
|
|
if (mem.HT_Feeling > 10)
|
|
|
|
|
data.AddLine(Severity.Invalid, LMemoryIndexFeelHT09, CheckIdentifier.Memory);
|
2018-06-24 05:00:01 +00:00
|
|
|
|
}
|
|
|
|
|
}
|