mirror of
https://github.com/kwsch/PKHeX
synced 2024-11-27 06:20:25 +00:00
f632aedd15
We implement simple state machine iterators to iterate through every split type encounter array, and more finely control the path we iterate through. And, by using generics, we can have the compiler generate optimized code to avoid virtual calls. In addition to this, we shift away from the big-5 encounter types and not inherit from an abstract class. This allows for creating a PK* of a specific type and directly writing properties (no virtual calls). Plus we can now fine-tune each encounter type to call specific code, and not have to worry about future game encounter types bothering the generation routines.
177 lines
5.3 KiB
C#
177 lines
5.3 KiB
C#
using System;
|
||
using static PKHeX.Core.LegalityCheckStrings;
|
||
|
||
namespace PKHeX.Core;
|
||
|
||
/// <summary>
|
||
/// Verifies the <see cref="PKM.OT_Name"/>.
|
||
/// </summary>
|
||
public sealed class TrainerNameVerifier : Verifier
|
||
{
|
||
protected override CheckIdentifier Identifier => CheckIdentifier.Trainer;
|
||
|
||
private static readonly string[] SuspiciousOTNames =
|
||
{
|
||
"PKHeX",
|
||
"PKHeX",
|
||
};
|
||
|
||
public override void Verify(LegalityAnalysis data)
|
||
{
|
||
var pk = data.Entity;
|
||
var enc = data.EncounterMatch;
|
||
if (!IsPlayerOriginalTrainer(enc))
|
||
return; // already verified
|
||
|
||
var ot = pk.OT_Name;
|
||
if (ot.Length == 0)
|
||
data.AddLine(GetInvalid(LOTShort));
|
||
|
||
if (IsOTNameSuspicious(ot))
|
||
{
|
||
data.AddLine(Get(LOTSuspicious, Severity.Fishy));
|
||
}
|
||
|
||
if (pk.VC)
|
||
{
|
||
VerifyOTG1(data);
|
||
}
|
||
else if (ot.Length > Legal.GetMaxLengthOT(data.Info.Generation, (LanguageID)pk.Language))
|
||
{
|
||
if (!IsEdgeCaseLength(pk, data.EncounterOriginal, ot))
|
||
data.AddLine(Get(LOTLong, Severity.Invalid));
|
||
}
|
||
|
||
if (ParseSettings.CheckWordFilter)
|
||
{
|
||
if (WordFilter.IsFiltered(ot, out var badPattern))
|
||
data.AddLine(GetInvalid($"Word Filter: {badPattern}"));
|
||
if (ContainsTooManyNumbers(ot, data.Info.Generation))
|
||
data.AddLine(GetInvalid("Word Filter: Too many numbers."));
|
||
|
||
if (WordFilter.IsFiltered(pk.HT_Name, out badPattern))
|
||
data.AddLine(GetInvalid($"Word Filter: {badPattern}"));
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Checks if any player (human) was the original OT.
|
||
/// </summary>
|
||
internal static bool IsPlayerOriginalTrainer(IEncounterable enc) => enc switch
|
||
{
|
||
IFixedTrainer { IsFixedTrainer: true } => false,
|
||
MysteryGift { IsEgg: false } => false,
|
||
EncounterStatic5N => false,
|
||
_ => true,
|
||
};
|
||
|
||
public static bool IsEdgeCaseLength(PKM pk, IEncounterTemplate e, string ot)
|
||
{
|
||
if (e.EggEncounter)
|
||
{
|
||
if (e is WC3 wc3 && pk.IsEgg && wc3.OT_Name == ot)
|
||
return true; // Fixed OT Mystery Gift Egg
|
||
bool eggEdge = pk.IsEgg ? pk.IsTradedEgg || pk.Format == 3 : pk.WasTradedEgg;
|
||
if (!eggEdge)
|
||
return false;
|
||
var len = Legal.GetMaxLengthOT(e.Generation, LanguageID.English); // max case
|
||
return ot.Length <= len;
|
||
}
|
||
|
||
if (e is IFixedTrainer { IsFixedTrainer: true })
|
||
return true; // already verified
|
||
|
||
if (e is MysteryGift mg && mg.OT_Name.Length == ot.Length)
|
||
return true; // Mattle Ho-Oh
|
||
return false;
|
||
}
|
||
|
||
public void VerifyOTG1(LegalityAnalysis data)
|
||
{
|
||
var pk = data.Entity;
|
||
string tr = pk.OT_Name;
|
||
|
||
if (tr.Length == 0)
|
||
{
|
||
if (pk is SK2 {TID16: 0, IsRental: true})
|
||
{
|
||
data.AddLine(Get(LOTShort, Severity.Fishy));
|
||
}
|
||
else
|
||
{
|
||
data.AddLine(GetInvalid(LOTShort));
|
||
return;
|
||
}
|
||
}
|
||
|
||
VerifyG1OTWithinBounds(data, tr);
|
||
|
||
if (pk.OT_Gender == 1)
|
||
{
|
||
if (pk is ICaughtData2 {CaughtData:0} or { Format: > 2, VC1: true } || data is {EncounterOriginal: {Generation:1} or EncounterGift2 {IsGift:true}})
|
||
data.AddLine(GetInvalid(LG1OTGender));
|
||
}
|
||
}
|
||
|
||
private void VerifyG1OTWithinBounds(LegalityAnalysis data, ReadOnlySpan<char> str)
|
||
{
|
||
if (StringConverter12.GetIsG1English(str))
|
||
{
|
||
if (str.Length > 7 && data.EncounterOriginal is not IFixedTrainer { IsFixedTrainer: true }) // OT already verified; GER shuckle has 8 chars
|
||
data.AddLine(GetInvalid(LOTLong));
|
||
}
|
||
else if (StringConverter12.GetIsG1Japanese(str))
|
||
{
|
||
if (str.Length > 5)
|
||
data.AddLine(GetInvalid(LOTLong));
|
||
}
|
||
else if (data.Entity.Korean && StringConverter2KOR.GetIsG2Korean(str))
|
||
{
|
||
if (str.Length > 5)
|
||
data.AddLine(GetInvalid(LOTLong));
|
||
}
|
||
else if (data.EncounterOriginal is not EncounterTrade2) // OT already verified; SPA Shuckle/Voltorb transferred from French can yield 2 inaccessible chars
|
||
{
|
||
data.AddLine(GetInvalid(LG1CharOT));
|
||
}
|
||
}
|
||
|
||
private static bool IsOTNameSuspicious(string name)
|
||
{
|
||
foreach (var s in SuspiciousOTNames)
|
||
{
|
||
if (s.StartsWith(name, StringComparison.InvariantCultureIgnoreCase))
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
public static bool ContainsTooManyNumbers(string str, int originalGeneration)
|
||
{
|
||
if (originalGeneration <= 3)
|
||
return false; // no limit from these generations
|
||
int max = originalGeneration < 6 ? 4 : 5;
|
||
if (str.Length <= max)
|
||
return false;
|
||
int count = GetNumberCount(str);
|
||
return count > max;
|
||
}
|
||
|
||
private static int GetNumberCount(string str)
|
||
{
|
||
static bool IsNumber(char c)
|
||
{
|
||
if ('0' <= c)
|
||
return c <= '9';
|
||
return (uint)(c - '0') <= 9;
|
||
}
|
||
|
||
int ctr = 0;
|
||
foreach (var c in str)
|
||
{
|
||
if (IsNumber(c))
|
||
++ctr;
|
||
}
|
||
return ctr;
|
||
}
|
||
}
|