mirror of
https://github.com/kwsch/PKHeX.git
synced 2026-03-21 17:48:28 -05:00
Support Gen5/3DS/Switch word filters (#4423)
* Support Gen5/3DS/Switch word filters - Separate 3DS/Switch word filters - Add/implement Gen5 word filter - Adjust behavior of DisableWordFilterPastGen * Implement halfwidth/fullwidth conversion Only applies to alphanumeric and kana
This commit is contained in:
parent
b119302d67
commit
6c3eec3314
|
|
@ -1,95 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Bad-word Filter class containing logic to check against unsavory regular expressions.
|
||||
/// </summary>
|
||||
public static class WordFilter
|
||||
{
|
||||
/// <summary>
|
||||
/// Regex patterns to check against
|
||||
/// </summary>
|
||||
/// <remarks>No need to keep the original pattern strings around; the <see cref="Regex"/> object retrieves this via <see cref="Regex.ToString()"/></remarks>
|
||||
private static readonly Regex[] Regexes = LoadPatterns(Util.GetStringResource("badwords"));
|
||||
|
||||
// if you're running this as a server and don't mind a few extra seconds of startup, add RegexOptions.Compiled for slightly better checking.
|
||||
private const RegexOptions Options = RegexOptions.CultureInvariant;
|
||||
|
||||
private static Regex[] LoadPatterns(ReadOnlySpan<char> patterns)
|
||||
{
|
||||
var lineCount = 1 + patterns.Count('\n');
|
||||
var result = new Regex[lineCount];
|
||||
int i = 0;
|
||||
foreach (var line in patterns.EnumerateLines())
|
||||
result[i++] = new Regex(line.ToString(), Options);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks to see if a phrase contains filtered content.
|
||||
/// </summary>
|
||||
/// <param name="message">Phrase to check for</param>
|
||||
/// <param name="regMatch">Matching regex that filters the phrase.</param>
|
||||
/// <returns>Boolean result if the message is filtered or not.</returns>
|
||||
public static bool TryMatch(ReadOnlySpan<char> message, [NotNullWhen(true)] out string? regMatch)
|
||||
{
|
||||
foreach (var regex in Regexes)
|
||||
{
|
||||
foreach (var _ in regex.EnumerateMatches(message))
|
||||
{
|
||||
regMatch = regex.ToString();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
regMatch = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Due to some messages repeating (Trainer names), keep a list of repeated values for faster lookup.
|
||||
/// </summary>
|
||||
private static readonly ConcurrentDictionary<string, string?>.AlternateLookup<ReadOnlySpan<char>> Lookup =
|
||||
new ConcurrentDictionary<string, string?>().GetAlternateLookup<ReadOnlySpan<char>>();
|
||||
|
||||
/// <summary>
|
||||
/// Checks to see if a phrase contains filtered content.
|
||||
/// </summary>
|
||||
/// <param name="message">Phrase to check for</param>
|
||||
/// <param name="regMatch">Matching regex that filters the phrase.</param>
|
||||
/// <returns>Boolean result if the message is filtered or not.</returns>
|
||||
public static bool IsFiltered(ReadOnlySpan<char> message, [NotNullWhen(true)] out string? regMatch)
|
||||
{
|
||||
if (message.IsWhiteSpace() || message.Length <= 1)
|
||||
{
|
||||
regMatch = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check dictionary
|
||||
if (Lookup.TryGetValue(message, out regMatch))
|
||||
return regMatch != null;
|
||||
|
||||
// Make the string lowercase invariant
|
||||
Span<char> lowercase = stackalloc char[message.Length];
|
||||
message.ToLowerInvariant(lowercase);
|
||||
|
||||
// not in dictionary, check patterns
|
||||
if (TryMatch(lowercase, out regMatch))
|
||||
{
|
||||
Lookup.TryAdd(message, regMatch);
|
||||
return true;
|
||||
}
|
||||
|
||||
// didn't match any pattern, cache result
|
||||
if ((Lookup.Dictionary.Count & ~MAX_COUNT) != 0)
|
||||
Lookup.Dictionary.Clear(); // reset
|
||||
Lookup.TryAdd(message, regMatch = null);
|
||||
return false;
|
||||
}
|
||||
|
||||
private const int MAX_COUNT = (1 << 17) - 1; // arbitrary cap for max dictionary size
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
using System;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Simplistic normalization of a string used by the Nintendo 3DS and Nintendo Switch games.
|
||||
/// </summary>
|
||||
public static class TextNormalizer
|
||||
{
|
||||
private const string Dakuten = "カキクケコサシスセソタチツテトハヒフヘホ"; // 'ウ' handled separately
|
||||
private const string Handakuten = "ハヒフヘホ";
|
||||
private const string FullwidthKana = "ヲァィゥェォャュョッーアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン";
|
||||
private const string SmallKana = "ァィゥェォッャュョヮ"; // 'ヵ', 'ヶ' handled separately
|
||||
|
||||
/// <summary>
|
||||
/// Normalize a string to a simplified form for checking against a bad-word list.
|
||||
/// </summary>
|
||||
/// <param name="input">Input string to normalize</param>
|
||||
/// <param name="output">Output buffer to write the normalized string</param>
|
||||
public static int Normalize(ReadOnlySpan<char> input, Span<char> output)
|
||||
{
|
||||
int ctr = 0;
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
var c = input[i];
|
||||
|
||||
// Skip spaces and halfwidth dakuten/handakuten
|
||||
if (c is ' ' or '\u3000' or '゙' or '゚')
|
||||
continue;
|
||||
|
||||
// Handle combining halfwidth dakuten/handakuten
|
||||
ushort ofs = 0;
|
||||
if (c is >= 'ヲ' and <= 'ン' && i + 1 < input.Length)
|
||||
{
|
||||
var d = input[i + 1];
|
||||
if (d == '゙' && Dakuten.Contains(c))
|
||||
ofs = 1;
|
||||
else if (d == '゚' && Handakuten.Contains(c))
|
||||
ofs = 2;
|
||||
else if (d == '゙' && c == 'ウ')
|
||||
ofs = 'ヴ' - 'ウ'; // 0x4E (78)
|
||||
}
|
||||
|
||||
// Fold characters treated identically
|
||||
c = char.ToLowerInvariant(c); // fold to lowercase
|
||||
c = (char)(c switch
|
||||
{
|
||||
>= 'ぁ' and <= 'ゖ' => c + 0x60, // shift hiragana to katakana
|
||||
>= '0' and <= '9' or >= 'a' and <= 'z' => c - 0xFEE0, // shift fullwidth numbers/letters to halfwidth
|
||||
>= 'ヲ' and <= 'ン' => FullwidthKana[c - 'ヲ'] + ofs, // shift halfwidth katakana to fullwidth
|
||||
_ => c,
|
||||
});
|
||||
|
||||
// Shift small kana to normal kana
|
||||
if (c is >= 'ァ' and <= 'ヶ')
|
||||
{
|
||||
if (SmallKana.Contains(c))
|
||||
c += (char)1;
|
||||
else if (c == 'ヵ')
|
||||
c = 'カ';
|
||||
else if (c == 'ヶ')
|
||||
c = 'ケ';
|
||||
}
|
||||
|
||||
output[ctr] = c;
|
||||
ctr++;
|
||||
}
|
||||
return ctr;
|
||||
}
|
||||
}
|
||||
116
PKHeX.Core/Legality/Restrictions/WordFilter/WordFilter.cs
Normal file
116
PKHeX.Core/Legality/Restrictions/WordFilter/WordFilter.cs
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Bad-word Filter class containing logic to check against unsavory regular expressions.
|
||||
/// </summary>
|
||||
public static class WordFilter
|
||||
{
|
||||
// if you're running this as a server and don't mind a few extra seconds of startup, add RegexOptions.Compiled for slightly better checking.
|
||||
private const RegexOptions Options = RegexOptions.CultureInvariant;
|
||||
|
||||
internal static Regex[] LoadPatterns(ReadOnlySpan<char> patterns)
|
||||
{
|
||||
// Make it lowercase invariant
|
||||
Span<char> lowercase = stackalloc char[patterns.Length];
|
||||
patterns.ToLowerInvariant(lowercase);
|
||||
|
||||
var lineCount = 1 + lowercase.Count('\n');
|
||||
var result = new Regex[lineCount];
|
||||
int i = 0;
|
||||
foreach (var line in lowercase.EnumerateLines())
|
||||
result[i++] = new Regex(line.ToString(), Options);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks to see if a phrase contains filtered content.
|
||||
/// </summary>
|
||||
/// <param name="message">Phrase to check</param>
|
||||
/// <param name="regexes">Console regex set to check against.</param>
|
||||
/// <param name="regMatch">Matching regex that filters the phrase.</param>
|
||||
/// <returns>Boolean result if the message is filtered or not.</returns>
|
||||
internal static bool TryMatch(ReadOnlySpan<char> message, ReadOnlySpan<Regex> regexes, [NotNullWhen(true)] out string? regMatch)
|
||||
{
|
||||
// Clean the string
|
||||
Span<char> clean = stackalloc char[message.Length];
|
||||
int ctr = TextNormalizer.Normalize(message, clean);
|
||||
if (ctr != clean.Length)
|
||||
clean = clean[..ctr];
|
||||
|
||||
foreach (var regex in regexes)
|
||||
{
|
||||
foreach (var _ in regex.EnumerateMatches(clean))
|
||||
{
|
||||
regMatch = regex.ToString();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
regMatch = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IsFiltered(ReadOnlySpan{char}, out string?, EntityContext, EntityContext)"/>
|
||||
public static bool IsFiltered(ReadOnlySpan<char> message, [NotNullWhen(true)] out string? regMatch,
|
||||
EntityContext current)
|
||||
=> IsFiltered(message, out regMatch, current, current);
|
||||
|
||||
/// <summary>
|
||||
/// Checks to see if a phrase contains filtered content.
|
||||
/// </summary>
|
||||
/// <param name="message">Phrase to check for</param>
|
||||
/// <param name="regMatch">Matching regex that filters the phrase.</param>
|
||||
/// <param name="current">Current context to check.</param>
|
||||
/// <param name="original">Earliest context to check.</param>
|
||||
/// <returns>Boolean result if the message is filtered or not.</returns>
|
||||
public static bool IsFiltered(ReadOnlySpan<char> message, [NotNullWhen(true)] out string? regMatch,
|
||||
EntityContext current, EntityContext original)
|
||||
{
|
||||
regMatch = null;
|
||||
if (message.IsWhiteSpace() || message.Length <= 1)
|
||||
return false;
|
||||
|
||||
// Only check against the single filter if requested
|
||||
if (ParseSettings.Settings.WordFilter.DisableWordFilterPastGen)
|
||||
return IsFilteredCurrentOnly(message, ref regMatch, current, original);
|
||||
|
||||
return IsFilteredLookBack(message, out regMatch, current, original);
|
||||
}
|
||||
|
||||
private static bool IsFilteredCurrentOnly(ReadOnlySpan<char> message, ref string? regMatch,
|
||||
EntityContext current, EntityContext original) => current switch
|
||||
{
|
||||
EntityContext.Gen5 => WordFilter5.IsFiltered(message, out regMatch),
|
||||
|
||||
EntityContext.Gen6 => WordFilter3DS.IsFilteredGen6(message, out regMatch),
|
||||
EntityContext.Gen7 when original is EntityContext.Gen6
|
||||
=> WordFilter3DS.IsFilteredGen6(message, out regMatch),
|
||||
|
||||
EntityContext.Gen7 => WordFilter3DS.IsFilteredGen7(message, out regMatch),
|
||||
_ => current.GetConsole() switch
|
||||
{
|
||||
GameConsole.NX => WordFilterNX.IsFiltered(message, out regMatch, original),
|
||||
_ => false,
|
||||
},
|
||||
};
|
||||
|
||||
private static bool IsFilteredLookBack(ReadOnlySpan<char> message, [NotNullWhen(true)] out string? regMatch,
|
||||
EntityContext current, EntityContext original)
|
||||
{
|
||||
// Switch 2 backwards transfer? Won't know for another couple years.
|
||||
if (WordFilterNX.IsFiltered(message, out regMatch, original))
|
||||
return true;
|
||||
|
||||
var generation = original.Generation();
|
||||
if (generation > 7 || original is EntityContext.Gen7b)
|
||||
return false;
|
||||
if (WordFilter3DS.IsFiltered(message, out regMatch, original))
|
||||
return true;
|
||||
|
||||
return generation == 5 && WordFilter5.IsFiltered(message, out regMatch);
|
||||
// no other word filters (none in Gen3 or Gen4)
|
||||
}
|
||||
}
|
||||
91
PKHeX.Core/Legality/Restrictions/WordFilter/WordFilter3DS.cs
Normal file
91
PKHeX.Core/Legality/Restrictions/WordFilter/WordFilter3DS.cs
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Word filter for 3DS games.
|
||||
/// </summary>
|
||||
public static class WordFilter3DS
|
||||
{
|
||||
private static readonly Regex[] Regexes = WordFilter.LoadPatterns(Util.GetStringResource("badwords_3ds"));
|
||||
|
||||
/// <summary>
|
||||
/// Regex patterns to check against
|
||||
/// </summary>
|
||||
/// <remarks>No need to keep the original pattern strings around; the <see cref="Regex"/> object retrieves this via <see cref="Regex.ToString()"/></remarks>
|
||||
private static readonly ConcurrentDictionary<string, string?>.AlternateLookup<ReadOnlySpan<char>> Lookup =
|
||||
new ConcurrentDictionary<string, string?>().GetAlternateLookup<ReadOnlySpan<char>>();
|
||||
|
||||
private const int MAX_COUNT = (1 << 17) - 1; // arbitrary cap for max dictionary size
|
||||
|
||||
/// <inheritdoc cref="IsFiltered"/>
|
||||
/// <remarks>Generation 6 is case-sensitive.</remarks>
|
||||
public static bool IsFilteredGen6(ReadOnlySpan<char> message, [NotNullWhen(true)] out string? regMatch)
|
||||
=> IsFiltered(message, out regMatch, EntityContext.Gen6);
|
||||
|
||||
/// <inheritdoc cref="IsFiltered"/>
|
||||
/// <remarks>Generation 7 is case-insensitive.</remarks>
|
||||
public static bool IsFilteredGen7(ReadOnlySpan<char> message, [NotNullWhen(true)] out string? regMatch)
|
||||
=> IsFiltered(message, out regMatch, EntityContext.Gen7);
|
||||
|
||||
/// <summary>
|
||||
/// Checks to see if a phrase contains filtered content.
|
||||
/// </summary>
|
||||
/// <param name="message">Phrase to check</param>
|
||||
/// <param name="regMatch">Matching regex that filters the phrase.</param>
|
||||
/// <param name="original">Earliest context to check.</param>
|
||||
/// <returns>Boolean result if the message is filtered or not.</returns>
|
||||
public static bool IsFiltered(ReadOnlySpan<char> message, [NotNullWhen(true)] out string? regMatch, EntityContext original)
|
||||
{
|
||||
regMatch = null;
|
||||
if (IsSpeciesName(message, original))
|
||||
return false;
|
||||
|
||||
// Check dictionary
|
||||
if (Lookup.TryGetValue(message, out regMatch))
|
||||
return regMatch != null;
|
||||
|
||||
// not in dictionary, check patterns
|
||||
if (WordFilter.TryMatch(message, Regexes, out regMatch))
|
||||
{
|
||||
Lookup.TryAdd(message, regMatch);
|
||||
return true;
|
||||
}
|
||||
|
||||
// didn't match any pattern, cache result
|
||||
if ((Lookup.Dictionary.Count & ~MAX_COUNT) != 0)
|
||||
Lookup.Dictionary.Clear(); // reset
|
||||
Lookup.TryAdd(message, regMatch = null);
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if the message is a species name
|
||||
/// </summary>
|
||||
/// <param name="message">Phrase to check</param>
|
||||
/// <param name="original">Earliest context to check.</param>
|
||||
public static bool IsSpeciesName(ReadOnlySpan<char> message, EntityContext original)
|
||||
{
|
||||
// Gen6 is case-sensitive, Gen7 is case-insensitive.
|
||||
if (original is EntityContext.Gen6) // Match case
|
||||
return IsSpeciesNameGen6(message);
|
||||
return IsSpeciesNameGen7(message);
|
||||
}
|
||||
|
||||
private static bool IsSpeciesNameGen7(ReadOnlySpan<char> message)
|
||||
{
|
||||
if (!SpeciesName.TryGetSpeciesAnyLanguageCaseInsensitive(message, out var s7, 7))
|
||||
return false;
|
||||
return s7 <= Legal.MaxSpeciesID_7_USUM;
|
||||
}
|
||||
|
||||
private static bool IsSpeciesNameGen6(ReadOnlySpan<char> message)
|
||||
{
|
||||
if (!SpeciesName.TryGetSpeciesAnyLanguage(message, out var s6, 6))
|
||||
return false;
|
||||
return s6 <= Legal.MaxSpeciesID_6;
|
||||
}
|
||||
}
|
||||
48
PKHeX.Core/Legality/Restrictions/WordFilter/WordFilter5.cs
Normal file
48
PKHeX.Core/Legality/Restrictions/WordFilter/WordFilter5.cs
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
public static class WordFilter5
|
||||
{
|
||||
private static readonly HashSet<string>.AlternateLookup<ReadOnlySpan<char>> Words =
|
||||
new HashSet<string>(Util.GetStringList("badwords_gen5"))
|
||||
.GetAlternateLookup<ReadOnlySpan<char>>();
|
||||
|
||||
/// <summary>
|
||||
/// Checks to see if a phrase contains filtered content.
|
||||
/// </summary>
|
||||
/// <param name="message">Phrase to check</param>
|
||||
/// <param name="match">Blocked word that filters the phrase.</param>
|
||||
/// <returns>Boolean result if the message is filtered or not.</returns>
|
||||
public static bool IsFiltered(ReadOnlySpan<char> message, [NotNullWhen(true)] out string? match)
|
||||
{
|
||||
Span<char> clean = stackalloc char[message.Length];
|
||||
Normalize(message, clean);
|
||||
return Words.TryGetValue(clean, out match);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Normalize a string to a simplified form for checking against a bad-word list.
|
||||
/// </summary>
|
||||
/// <param name="input">Input string to normalize</param>
|
||||
/// <param name="output">Output buffer to write the normalized string</param>
|
||||
public static void Normalize(ReadOnlySpan<char> input, Span<char> output)
|
||||
{
|
||||
Debug.Assert(input.Length == output.Length);
|
||||
for (int i = 0; i < input.Length; i++)
|
||||
{
|
||||
var c = input[i];
|
||||
c = char.ToUpperInvariant(c); // fold to uppercase
|
||||
c = (char)(c switch
|
||||
{
|
||||
>= 'ァ' and <= 'ヶ' => c - 0x60, // shift katakana to hiragana
|
||||
>= 'A' and <= 'Z' => c - 0xFEE0, // shift fullwidth letters to halfwidth
|
||||
_ => c,
|
||||
});
|
||||
output[i] = c;
|
||||
}
|
||||
}
|
||||
}
|
||||
65
PKHeX.Core/Legality/Restrictions/WordFilter/WordFilterNX.cs
Normal file
65
PKHeX.Core/Legality/Restrictions/WordFilter/WordFilterNX.cs
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace PKHeX.Core;
|
||||
|
||||
/// <summary>
|
||||
/// Word filter for Switch games.
|
||||
/// </summary>
|
||||
public static class WordFilterNX
|
||||
{
|
||||
/// <summary>
|
||||
/// Regex patterns to check against
|
||||
/// </summary>
|
||||
/// <remarks>No need to keep the original pattern strings around; the <see cref="Regex"/> object retrieves this via <see cref="Regex.ToString()"/></remarks>
|
||||
private static readonly Regex[] Regexes = WordFilter.LoadPatterns(Util.GetStringResource("badwords_switch"));
|
||||
|
||||
/// <summary>
|
||||
/// Due to some messages repeating (Trainer names), keep a list of repeated values for faster lookup.
|
||||
/// </summary>
|
||||
private static readonly ConcurrentDictionary<string, string?>.AlternateLookup<ReadOnlySpan<char>> Lookup =
|
||||
new ConcurrentDictionary<string, string?>().GetAlternateLookup<ReadOnlySpan<char>>();
|
||||
|
||||
private const int MAX_COUNT = (1 << 17) - 1; // arbitrary cap for max dictionary size
|
||||
|
||||
/// <summary>
|
||||
/// Checks to see if a phrase contains filtered content.
|
||||
/// </summary>
|
||||
/// <param name="message">Phrase to check</param>
|
||||
/// <param name="regMatch">Matching regex that filters the phrase.</param>
|
||||
/// <param name="original">Earliest context to check.</param>
|
||||
/// <returns>Boolean result if the message is filtered or not.</returns>
|
||||
public static bool IsFiltered(ReadOnlySpan<char> message, [NotNullWhen(true)] out string? regMatch, EntityContext original)
|
||||
{
|
||||
regMatch = null;
|
||||
if (IsSpeciesName(message, original))
|
||||
return false;
|
||||
|
||||
// Check dictionary
|
||||
if (Lookup.TryGetValue(message, out regMatch))
|
||||
return regMatch != null;
|
||||
|
||||
// not in dictionary, check patterns
|
||||
if (WordFilter.TryMatch(message, Regexes, out regMatch))
|
||||
{
|
||||
Lookup.TryAdd(message, regMatch);
|
||||
return true;
|
||||
}
|
||||
|
||||
// didn't match any pattern, cache result
|
||||
if ((Lookup.Dictionary.Count & ~MAX_COUNT) != 0)
|
||||
Lookup.Dictionary.Clear(); // reset
|
||||
Lookup.TryAdd(message, regMatch = null);
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsSpeciesName(ReadOnlySpan<char> message, EntityContext origin)
|
||||
{
|
||||
var gen = origin.Generation();
|
||||
if (!SpeciesName.TryGetSpeciesAnyLanguageCaseInsensitive(message, out var species, gen))
|
||||
return false;
|
||||
return species <= origin.GetSingleGameVersion().GetMaxSpeciesID();
|
||||
}
|
||||
}
|
||||
|
|
@ -5,11 +5,11 @@ namespace PKHeX.Core;
|
|||
[TypeConverter(typeof(ExpandableObjectConverter))]
|
||||
public sealed class WordFilterSettings
|
||||
{
|
||||
[LocalizedDescription("Checks player given Nicknames and Trainer Names for profanity. Bad words will be flagged using the 3DS console's regex lists.")]
|
||||
[LocalizedDescription("Checks player given Nicknames and Trainer Names for profanity. Bad words will be flagged using the appropriate console's lists.")]
|
||||
public bool CheckWordFilter { get; set; } = true;
|
||||
|
||||
[LocalizedDescription("Disables the Word Filter check for formats prior to 3DS-era.")]
|
||||
[LocalizedDescription("Disables retroactive Word Filter checks for earlier formats.")]
|
||||
public bool DisableWordFilterPastGen { get; set; }
|
||||
|
||||
public bool IsEnabled(int gen) => CheckWordFilter && (!DisableWordFilterPastGen || gen >= 6);
|
||||
public bool IsEnabled(int gen) => CheckWordFilter && (!DisableWordFilterPastGen || gen >= 5);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,7 +67,8 @@ public override void Verify(LegalityAnalysis data)
|
|||
// Non-nicknamed strings have already been checked.
|
||||
if (ParseSettings.Settings.WordFilter.IsEnabled(pk.Format) && pk.IsNicknamed)
|
||||
{
|
||||
if (WordFilter.IsFiltered(nickname, out var badPattern))
|
||||
var mostRecentNicknameContext = pk.Format >= 8 ? pk.Context : enc.Context;
|
||||
if (WordFilter.IsFiltered(nickname, out var badPattern, pk.Context, mostRecentNicknameContext))
|
||||
data.AddLine(GetInvalid($"Word Filter: {badPattern}"));
|
||||
if (TrainerNameVerifier.ContainsTooManyNumbers(nickname, data.Info.Generation))
|
||||
data.AddLine(GetInvalid("Word Filter: Too many numbers."));
|
||||
|
|
@ -164,11 +165,9 @@ private bool VerifyUnNicknamedEncounter(LegalityAnalysis data, PKM pk, ReadOnlyS
|
|||
return true;
|
||||
}
|
||||
}
|
||||
foreach (var language in Language.GetAvailableGameLanguages(pk.Format))
|
||||
if (SpeciesName.TryGetSpeciesAnyLanguage(nickname, out var species, pk.Format))
|
||||
{
|
||||
if (!SpeciesName.TryGetSpecies(nickname, language, out var species))
|
||||
continue;
|
||||
var msg = species == pk.Species && language != pk.Language ? LNickMatchNoOthersFail : LNickMatchLanguageFlag;
|
||||
var msg = species == pk.Species ? LNickMatchLanguageFlag : LNickMatchNoOthersFail;
|
||||
data.AddLine(Get(msg, ParseSettings.Settings.Nickname.NicknamedAnotherSpecies));
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ public override void Verify(LegalityAnalysis data)
|
|||
trainer = trainer[..len];
|
||||
if (trainer.Contains('\uffff') && pk is { Format: 4 })
|
||||
{
|
||||
data.AddLine(GetInvalid("Trainer Name: Unkown Character"));
|
||||
data.AddLine(GetInvalid("Trainer Name: Unknown Character"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -54,14 +54,14 @@ public override void Verify(LegalityAnalysis data)
|
|||
|
||||
if (ParseSettings.Settings.WordFilter.IsEnabled(pk.Format))
|
||||
{
|
||||
if (WordFilter.IsFiltered(trainer, out var badPattern))
|
||||
if (WordFilter.IsFiltered(trainer, out var badPattern, pk.Context, enc.Context))
|
||||
data.AddLine(GetInvalid($"Word Filter: {badPattern}"));
|
||||
if (ContainsTooManyNumbers(trainer, data.Info.Generation))
|
||||
data.AddLine(GetInvalid("Word Filter: Too many numbers."));
|
||||
|
||||
Span<char> ht = stackalloc char[pk.TrashCharCountTrainer];
|
||||
int nameLen = pk.LoadString(pk.HandlingTrainerTrash, ht);
|
||||
if (WordFilter.IsFiltered(ht[..nameLen], out badPattern))
|
||||
if (WordFilter.IsFiltered(ht[..nameLen], out badPattern, pk.Context)) // HT context is always the current context
|
||||
data.AddLine(GetInvalid($"Word Filter: {badPattern}"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,7 +52,10 @@ public static class SpeciesName
|
|||
/// </summary>
|
||||
private static readonly Dictionary<string, ushort>.AlternateLookup<ReadOnlySpan<char>>[] SpeciesDict = GetDictionary(SpeciesLang);
|
||||
|
||||
private static Dictionary<string, ushort>.AlternateLookup<ReadOnlySpan<char>>[] GetDictionary(string[][] names)
|
||||
/// <inheritdoc cref="SpeciesDict"/>
|
||||
private static readonly Dictionary<string, ushort>.AlternateLookup<ReadOnlySpan<char>>[] SpeciesDictLower = GetDictionary(SpeciesLang, true);
|
||||
|
||||
private static Dictionary<string, ushort>.AlternateLookup<ReadOnlySpan<char>>[] GetDictionary(string[][] names, bool lower = false)
|
||||
{
|
||||
var result = new Dictionary<string, ushort>.AlternateLookup<ReadOnlySpan<char>>[names.Length];
|
||||
for (int i = 0; i < result.Length; i++)
|
||||
|
|
@ -61,7 +64,10 @@ public static class SpeciesName
|
|||
var capacity = Math.Max(speciesList.Length - 1, 0);
|
||||
var dict = new Dictionary<string, ushort>(capacity);
|
||||
for (ushort species = 1; species < speciesList.Length; species++)
|
||||
dict[speciesList[species]] = species;
|
||||
{
|
||||
var key = speciesList[species];
|
||||
dict[lower ? key.ToLowerInvariant() : key] = species;
|
||||
}
|
||||
result[i] = dict.GetAlternateLookup<ReadOnlySpan<char>>();
|
||||
}
|
||||
return result;
|
||||
|
|
@ -323,9 +329,28 @@ public static bool TryGetSpecies(ReadOnlySpan<char> speciesName, int language, o
|
|||
return SpeciesDict[language].TryGetValue(speciesName, out species);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="TryGetSpecies(ReadOnlySpan{char}, int, out ushort)"/>
|
||||
public static bool TryGetSpecies(string speciesName, int language, out ushort species)
|
||||
public static bool TryGetSpeciesAnyLanguage(ReadOnlySpan<char> speciesName, out ushort species, byte generation = LatestGeneration)
|
||||
{
|
||||
return SpeciesDict[language].TryGetValue(speciesName, out species);
|
||||
foreach (var language in Language.GetAvailableGameLanguages(generation))
|
||||
{
|
||||
if (SpeciesDict[language].TryGetValue(speciesName, out species))
|
||||
return true;
|
||||
}
|
||||
species = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool TryGetSpeciesAnyLanguageCaseInsensitive(ReadOnlySpan<char> speciesName, out ushort species, byte generation = LatestGeneration)
|
||||
{
|
||||
Span<char> lowercase = stackalloc char[speciesName.Length];
|
||||
speciesName.ToLowerInvariant(lowercase);
|
||||
|
||||
foreach (var language in Language.GetAvailableGameLanguages(generation))
|
||||
{
|
||||
if (SpeciesDictLower[language].TryGetValue(lowercase, out species))
|
||||
return true;
|
||||
}
|
||||
species = 0;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
3406
PKHeX.Core/Resources/text/badwords/badwords_3ds.txt
Normal file
3406
PKHeX.Core/Resources/text/badwords/badwords_3ds.txt
Normal file
File diff suppressed because it is too large
Load Diff
362
PKHeX.Core/Resources/text/badwords/badwords_gen5.txt
Normal file
362
PKHeX.Core/Resources/text/badwords/badwords_gen5.txt
Normal file
|
|
@ -0,0 +1,362 @@
|
|||
9/11
|
||||
ABRUTI
|
||||
ABRUTIE
|
||||
ADHD
|
||||
AFFANCULO
|
||||
ANAL
|
||||
ANALPLUG
|
||||
ANALSEX
|
||||
ARSCH
|
||||
ARSCHLOCH
|
||||
ASS
|
||||
BAGASCIA
|
||||
BAISE
|
||||
BAISER
|
||||
BAISé
|
||||
BALDRACCA
|
||||
BASTARD
|
||||
BATARD
|
||||
BATTONA
|
||||
BITCH
|
||||
BITE
|
||||
BLOWJOB
|
||||
BOCCHINARA
|
||||
BOCCHINARO
|
||||
BOLLERA
|
||||
BOUGNOUL
|
||||
BRANLEUR
|
||||
BULLSHIT
|
||||
BURNE
|
||||
CABRON
|
||||
CABRONA
|
||||
CABRONAZO
|
||||
CABRóN
|
||||
CAPULLA
|
||||
CAPULLO
|
||||
CAZZI
|
||||
CAZZO
|
||||
CHIAVARE
|
||||
CHICHI
|
||||
CHIER
|
||||
CHINK
|
||||
CHOCHO
|
||||
CLIT
|
||||
COCK
|
||||
COCKSUCKER
|
||||
COCU
|
||||
COGLIONE
|
||||
COJON
|
||||
COJONES
|
||||
COJóN
|
||||
COMEPOLLAS
|
||||
CON
|
||||
CONNARD
|
||||
CONNASSE
|
||||
CONNE
|
||||
CONO
|
||||
COON
|
||||
COUILLE
|
||||
COUILLON
|
||||
COUILLONNE
|
||||
COñO
|
||||
CREVARD
|
||||
CUL
|
||||
CULATTONE
|
||||
CULO
|
||||
CUM
|
||||
CUMSHOT
|
||||
CUNT
|
||||
DAMN
|
||||
DICK
|
||||
DICKHEAD
|
||||
DILDO
|
||||
DIO BESTIA
|
||||
DIO CANE
|
||||
DIO PORCO
|
||||
DRECKSACK
|
||||
DRECKSAU
|
||||
DYKE
|
||||
ENCULE
|
||||
ENCULEE
|
||||
ENCULER
|
||||
ENCULé
|
||||
ENCULéE
|
||||
ENFOIRE
|
||||
ENFOIRé
|
||||
F.U.C.K.
|
||||
FAG
|
||||
FAGGOT
|
||||
FAGS
|
||||
FANCULO
|
||||
FCUK
|
||||
FICA
|
||||
FICKEN
|
||||
FICKFRESSE
|
||||
FIGA
|
||||
FION
|
||||
FOLLAR
|
||||
FOLLEN
|
||||
FOTTERE
|
||||
FOTZE
|
||||
FOUTRE
|
||||
FROCIO
|
||||
FUCK
|
||||
FUCKER
|
||||
FUCT
|
||||
FUK
|
||||
FURCIA
|
||||
GILIPOLLAS
|
||||
GOBSHITE
|
||||
GODDAMN
|
||||
GYPO
|
||||
HACKFRESSE
|
||||
HANDJOB
|
||||
HIJAPUTA
|
||||
HIJO PUTA
|
||||
HIJOPUTA
|
||||
HITLER
|
||||
HOLOCAUST
|
||||
HOMO
|
||||
HORE
|
||||
HOSTIA
|
||||
HURENSOHN
|
||||
INCULARE
|
||||
JESUSSUCKS
|
||||
JIZZ
|
||||
JIZZUM
|
||||
JODER
|
||||
JODETE
|
||||
JOPUTA
|
||||
JUDENSAU
|
||||
JóDETE
|
||||
KACKE
|
||||
KAFFIR
|
||||
KANACKE
|
||||
KIKE
|
||||
KUNT
|
||||
KZ
|
||||
LESBO
|
||||
MAMADA
|
||||
MAMON
|
||||
MAMONA
|
||||
MAMóN
|
||||
MARICA
|
||||
MARICON
|
||||
MARICONA
|
||||
MARICONAZO
|
||||
MARICóN
|
||||
MASTURBATE
|
||||
MERDE
|
||||
MIGNOTTA
|
||||
MINCHIA
|
||||
MISSGEBURT
|
||||
MOLEST
|
||||
NAZI
|
||||
NEGER
|
||||
NEGRE
|
||||
NEGRESSE
|
||||
NEGRO
|
||||
NIGGER
|
||||
NIQUE
|
||||
NIQUER
|
||||
NUTTE
|
||||
NèGRE
|
||||
NéGRESSE
|
||||
OJETE
|
||||
OSTIA
|
||||
PADOPHILER
|
||||
PAEDO
|
||||
PAEDOPHILE
|
||||
PAJILLERO
|
||||
PAKI
|
||||
PARTOUZE
|
||||
PD
|
||||
PECKER
|
||||
PEDE
|
||||
PEDO
|
||||
PEDOFILE
|
||||
PEDOPHILE
|
||||
PENDON
|
||||
PENDóN
|
||||
PENIS
|
||||
PETASSE
|
||||
PHUK
|
||||
PICHA
|
||||
PINE
|
||||
PISSER
|
||||
POLLA
|
||||
POLLON
|
||||
POLLóN
|
||||
POLVO
|
||||
POMPINARA
|
||||
POMPINO
|
||||
POOF
|
||||
PORCO DIO
|
||||
POTORRO
|
||||
POUFFE
|
||||
POUFFIASSE
|
||||
PUSSY
|
||||
PUTA
|
||||
PUTAIN
|
||||
PUTE
|
||||
PUTO
|
||||
PUTON
|
||||
PUTTANA
|
||||
PUTóN
|
||||
PäDOPHILER
|
||||
PéDé
|
||||
PéTASSE
|
||||
QUEER
|
||||
RAPE
|
||||
RAPED
|
||||
RAPES
|
||||
RAPIST
|
||||
RICCHIONE
|
||||
ROTTINCULO
|
||||
SA
|
||||
SALAUD
|
||||
SALOP
|
||||
SALOPARD
|
||||
SALOPE
|
||||
SAU
|
||||
SBORRA
|
||||
SCHEIßE
|
||||
SCHLAMPE
|
||||
SCHWANZ
|
||||
SCHWUCHTEL
|
||||
SCROTUM
|
||||
SEGAIOLO
|
||||
SEX
|
||||
SHIT
|
||||
SHIZ
|
||||
SIEG HEIL!
|
||||
SLAG
|
||||
SLUT
|
||||
SODOMIE
|
||||
SPASTI
|
||||
SPASTIC
|
||||
SPAZ
|
||||
SPERM
|
||||
SPUNK
|
||||
SS
|
||||
STRICHER
|
||||
SUCER
|
||||
TAPETTE
|
||||
TARE
|
||||
TARé
|
||||
TITS
|
||||
TORTILLERA
|
||||
TROIA
|
||||
TROIETTA
|
||||
TROIONA
|
||||
TROIONE
|
||||
TWAT
|
||||
VAFFANCULO
|
||||
VAGIN
|
||||
VAGINA
|
||||
VOLLIDIOT
|
||||
VULVA
|
||||
WANK
|
||||
WANKER
|
||||
WETBACK
|
||||
WHOR
|
||||
WHORE
|
||||
WICHSER
|
||||
WOG
|
||||
ZOB
|
||||
ZOCCOLA
|
||||
ZORRON
|
||||
ZORRóN
|
||||
あいえき
|
||||
いぬごろし
|
||||
いんぱい
|
||||
いんもう
|
||||
うんこ
|
||||
うんち
|
||||
おまんこ
|
||||
おめこ
|
||||
かたわ
|
||||
きちがい
|
||||
くろんぼ
|
||||
けとう
|
||||
ころす
|
||||
ごうかん
|
||||
さんごくじん
|
||||
しなじん
|
||||
しね
|
||||
せいえき
|
||||
せっくす
|
||||
ちゃんころ
|
||||
ちんこ
|
||||
ちんちん
|
||||
ちんば
|
||||
ちんぽ
|
||||
つんぼ
|
||||
とさつ
|
||||
どかた
|
||||
どもり
|
||||
にぐろ
|
||||
にんぴにん
|
||||
ひにん
|
||||
びっこ
|
||||
ふぇら
|
||||
ぶらく
|
||||
ぺにす
|
||||
まんこ
|
||||
めくら
|
||||
やらせろ
|
||||
やりまん
|
||||
りょうじょく
|
||||
れいぷ
|
||||
ろりこん
|
||||
ファック
|
||||
강간
|
||||
개새끼
|
||||
개지랄
|
||||
걸레같은년
|
||||
걸레년
|
||||
귀두
|
||||
내꺼빨아
|
||||
내꺼핥아
|
||||
니미랄
|
||||
딸딸이
|
||||
미친년
|
||||
미친놈
|
||||
병신
|
||||
보지
|
||||
부랄
|
||||
불알
|
||||
빠구리
|
||||
빠굴이
|
||||
사까시
|
||||
성감대
|
||||
성관계
|
||||
성폭행
|
||||
성행위
|
||||
섹스
|
||||
시팔년
|
||||
시팔놈
|
||||
쌍넘
|
||||
쌍년
|
||||
쌍놈
|
||||
쌍뇬
|
||||
씨발
|
||||
씨발넘
|
||||
씨발년
|
||||
씨발놈
|
||||
씨발뇬
|
||||
씹새끼
|
||||
엄창
|
||||
염병
|
||||
오르가즘
|
||||
왕자지
|
||||
유두
|
||||
자지
|
||||
잠지
|
||||
정액
|
||||
좆까
|
||||
창녀
|
||||
콘돔
|
||||
클리토리스
|
||||
페니스
|
||||
후장
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -170,7 +170,7 @@ LocalizedDescription.CheckWordFilter=Überprüft Spitznamen und Trainer Namen na
|
|||
LocalizedDescription.CurrentHandlerMismatch=Schweregrad mit dem der aktuelle Besitzer eines Pokémons in der Legalitäts Analyse geprüft wird.
|
||||
LocalizedDescription.DefaultBoxExportNamer=Selected File namer to use for box exports for the GUI, if multiple are available.
|
||||
LocalizedDescription.DisableScalingDpi=Disables the GUI scaling based on Dpi on program startup, falling back to font scaling.
|
||||
LocalizedDescription.DisableWordFilterPastGen=Disables the Word Filter check for formats prior to 3DS-era.
|
||||
LocalizedDescription.DisableWordFilterPastGen=Disables retroactive Word Filter checks for earlier formats.
|
||||
LocalizedDescription.ExportLegalityVerboseProperties=Display all properties of the encounter (auto-generated) when exporting a verbose report.
|
||||
LocalizedDescription.ExtraProperties=Extra entity properties to try and show in addition to the default properties displayed.
|
||||
LocalizedDescription.Female=Female gender color.
|
||||
|
|
|
|||
|
|
@ -166,11 +166,11 @@ LocalizedDescription.BAKEnabled=Automatic Save File Backups Enabled
|
|||
LocalizedDescription.BAKPrompt=Tracks if the "Create Backup" prompt has been issued to the user.
|
||||
LocalizedDescription.BoxExport=Settings to use for box exports.
|
||||
LocalizedDescription.CheckActiveHandler=Checks the last loaded player save file data and Current Handler state to determine if the Pokémon's Current Handler does not match the expected value.
|
||||
LocalizedDescription.CheckWordFilter=Checks player given Nicknames and Trainer Names for profanity. Bad words will be flagged using the 3DS console's regex lists.
|
||||
LocalizedDescription.CheckWordFilter=Checks player given Nicknames and Trainer Names for profanity. Bad words will be flagged using the appropriate console's lists.
|
||||
LocalizedDescription.CurrentHandlerMismatch=Severity to flag a Legality Check if Pokémon's Current Handler does not match the expected value.
|
||||
LocalizedDescription.DefaultBoxExportNamer=Selected File namer to use for box exports for the GUI, if multiple are available.
|
||||
LocalizedDescription.DisableScalingDpi=Disables the GUI scaling based on Dpi on program startup, falling back to font scaling.
|
||||
LocalizedDescription.DisableWordFilterPastGen=Disables the Word Filter check for formats prior to 3DS-era.
|
||||
LocalizedDescription.DisableWordFilterPastGen=Disables retroactive Word Filter checks for earlier formats.
|
||||
LocalizedDescription.ExportLegalityVerboseProperties=Display all properties of the encounter (auto-generated) when exporting a verbose report.
|
||||
LocalizedDescription.ExtraProperties=Extra entity properties to try and show in addition to the default properties displayed.
|
||||
LocalizedDescription.Female=Female gender color.
|
||||
|
|
|
|||
|
|
@ -166,11 +166,11 @@ LocalizedDescription.BAKEnabled=Sauvegarde automatique des fichiers activée
|
|||
LocalizedDescription.BAKPrompt=Tracks if the "Create Backup" prompt has been issued to the user.
|
||||
LocalizedDescription.BoxExport=Settings to use for box exports.
|
||||
LocalizedDescription.CheckActiveHandler=Checks the last loaded player save file data and Current Handler state to determine if the Pokémon's Current Handler does not match the expected value.
|
||||
LocalizedDescription.CheckWordFilter=Checks player given Nicknames and Trainer Names for profanity. Bad words will be flagged using the 3DS console's regex lists.
|
||||
LocalizedDescription.CheckWordFilter=Checks player given Nicknames and Trainer Names for profanity. Bad words will be flagged using the appropriate console's lists.
|
||||
LocalizedDescription.CurrentHandlerMismatch=Severity to flag a Legality Check if Pokémon's Current Handler does not match the expected value.
|
||||
LocalizedDescription.DefaultBoxExportNamer=Selected File namer to use for box exports for the GUI, if multiple are available.
|
||||
LocalizedDescription.DisableScalingDpi=Disables the GUI scaling based on Dpi on program startup, falling back to font scaling.
|
||||
LocalizedDescription.DisableWordFilterPastGen=Disables the Word Filter check for formats prior to 3DS-era.
|
||||
LocalizedDescription.DisableWordFilterPastGen=Disables retroactive Word Filter checks for earlier formats.
|
||||
LocalizedDescription.ExportLegalityVerboseProperties=Display all properties of the encounter (auto-generated) when exporting a verbose report.
|
||||
LocalizedDescription.ExtraProperties=Extra entity properties to try and show in addition to the default properties displayed.
|
||||
LocalizedDescription.Female=Female gender color.
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ LocalizedDescription.CheckWordFilter=Controlla la volgarità di Soprannomi e Nom
|
|||
LocalizedDescription.CurrentHandlerMismatch=Forza una segnalazione di legalità se l'Ultimo Allenatore non corrisponde al valore aspettato.
|
||||
LocalizedDescription.DefaultBoxExportNamer=Selected File namer to use for box exports for the GUI, if multiple are available.
|
||||
LocalizedDescription.DisableScalingDpi=Disables the GUI scaling based on Dpi on program startup, falling back to font scaling.
|
||||
LocalizedDescription.DisableWordFilterPastGen=Disables the Word Filter check for formats prior to 3DS-era.
|
||||
LocalizedDescription.DisableWordFilterPastGen=Disables retroactive Word Filter checks for earlier formats.
|
||||
LocalizedDescription.ExportLegalityVerboseProperties=Display all properties of the encounter (auto-generated) when exporting a verbose report.
|
||||
LocalizedDescription.ExtraProperties=Extra entity properties to try and show in addition to the default properties displayed.
|
||||
LocalizedDescription.Female=Female gender color.
|
||||
|
|
|
|||
|
|
@ -166,11 +166,11 @@ LocalizedDescription.BAKEnabled=세이브 파일 자동 백업 사용
|
|||
LocalizedDescription.BAKPrompt=Tracks if the "Create Backup" prompt has been issued to the user.
|
||||
LocalizedDescription.BoxExport=Settings to use for box exports.
|
||||
LocalizedDescription.CheckActiveHandler=Checks the last loaded player save file data and Current Handler state to determine if the Pokémon's Current Handler does not match the expected value.
|
||||
LocalizedDescription.CheckWordFilter=Checks player given Nicknames and Trainer Names for profanity. Bad words will be flagged using the 3DS console's regex lists.
|
||||
LocalizedDescription.CheckWordFilter=Checks player given Nicknames and Trainer Names for profanity. Bad words will be flagged using the appropriate console's lists.
|
||||
LocalizedDescription.CurrentHandlerMismatch=Severity to flag a Legality Check if Pokémon's Current Handler does not match the expected value.
|
||||
LocalizedDescription.DefaultBoxExportNamer=Selected File namer to use for box exports for the GUI, if multiple are available.
|
||||
LocalizedDescription.DisableScalingDpi=Disables the GUI scaling based on Dpi on program startup, falling back to font scaling.
|
||||
LocalizedDescription.DisableWordFilterPastGen=Disables the Word Filter check for formats prior to 3DS-era.
|
||||
LocalizedDescription.DisableWordFilterPastGen=Disables retroactive Word Filter checks for earlier formats.
|
||||
LocalizedDescription.ExportLegalityVerboseProperties=Display all properties of the encounter (auto-generated) when exporting a verbose report.
|
||||
LocalizedDescription.ExtraProperties=Extra entity properties to try and show in addition to the default properties displayed.
|
||||
LocalizedDescription.Female=Female gender color.
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ LocalizedDescription.CheckWordFilter=檢查昵稱和訓練家名稱是否存在
|
|||
LocalizedDescription.CurrentHandlerMismatch=如果寶可夢現時持有人與預期值不匹配,則使用高等級合法性檢查。
|
||||
LocalizedDescription.DefaultBoxExportNamer=Selected File namer to use for box exports for the GUI, if multiple are available.
|
||||
LocalizedDescription.DisableScalingDpi=Disables the GUI scaling based on Dpi on program startup, falling back to font scaling.
|
||||
LocalizedDescription.DisableWordFilterPastGen=Disables the Word Filter check for formats prior to 3DS-era.
|
||||
LocalizedDescription.DisableWordFilterPastGen=Disables retroactive Word Filter checks for earlier formats.
|
||||
LocalizedDescription.ExportLegalityVerboseProperties=Display all properties of the encounter (auto-generated) when exporting a verbose report.
|
||||
LocalizedDescription.ExtraProperties=Extra entity properties to try and show in addition to the default properties displayed.
|
||||
LocalizedDescription.Female=Female gender color.
|
||||
|
|
|
|||
|
|
@ -13,13 +13,78 @@ public class LegalityTest
|
|||
static LegalityTest() => TestUtil.InitializeLegality();
|
||||
|
||||
[Theory]
|
||||
[InlineData("censor")]
|
||||
[InlineData("buttnugget")]
|
||||
[InlineData("18넘")]
|
||||
[InlineData("inoffensive", false)]
|
||||
public void CensorsBadWords(string badword, bool value = true)
|
||||
[InlineData("Ass")]
|
||||
[InlineData("Ass")]
|
||||
[InlineData("9/11")]
|
||||
[InlineData("9/11", false)]
|
||||
[InlineData("baise")]
|
||||
[InlineData("baisé", false)]
|
||||
[InlineData("BAISÉ", false)]
|
||||
[InlineData("scheiße")]
|
||||
[InlineData("SCHEISSE", false)]
|
||||
[InlineData("RICCHIONE ")]
|
||||
[InlineData("RICCHIONE", false)]
|
||||
[InlineData("せっくす")]
|
||||
[InlineData("セックス")]
|
||||
[InlineData("ふぁっく", false)]
|
||||
[InlineData("ファック", false)]
|
||||
[InlineData("kofagrigus", false)]
|
||||
[InlineData("cofagrigus", false)]
|
||||
public void CensorsBadWordsGen5(string badword, bool value = true)
|
||||
{
|
||||
WordFilter.TryMatch(badword, out _).Should().Be(value, "the word should have been identified as a bad word");
|
||||
var result = WordFilter5.IsFiltered(badword, out _);
|
||||
result.Should().Be(value, $"the word {(value ? "should" : "should not")} have been identified as a bad word");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("kofagrigus")]
|
||||
[InlineData("cofagrigus")]
|
||||
[InlineData("Cofagrigus", false)]
|
||||
public void CensorsBadWordsGen6(string badword, bool value = true)
|
||||
{
|
||||
var result = WordFilter3DS.IsFilteredGen6(badword, out _);
|
||||
result.Should().Be(value, $"the word {(value ? "should" : "should not")} have been identified as a bad word");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("badword")]
|
||||
[InlineData("butt nuggets")]
|
||||
[InlineData("18년")]
|
||||
[InlineData("ふぁっく")]
|
||||
[InlineData("gcd")]
|
||||
[InlineData("P0RN")]
|
||||
[InlineData("gmail.com")]
|
||||
[InlineData("kofagrigus")]
|
||||
[InlineData("cofagrigus", false)]
|
||||
[InlineData("Cofagrigus", false)]
|
||||
[InlineData("inoffensive", false)]
|
||||
public void CensorsBadWordsGen7(string badword, bool value = true)
|
||||
{
|
||||
var result = WordFilter3DS.IsFilteredGen7(badword, out _);
|
||||
result.Should().Be(value, $"the word {(value ? "should" : "should not")} have been identified as a bad word");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("badword")]
|
||||
[InlineData("butt nuggets")]
|
||||
[InlineData("18넘")]
|
||||
[InlineData("ふぁっく")]
|
||||
[InlineData("ヴァギナ")]
|
||||
[InlineData("オッパイ")]
|
||||
[InlineData("ファッ゙ク")]
|
||||
[InlineData("ファヅク", false)]
|
||||
[InlineData("ファッグ", false)]
|
||||
[InlineData("sh!t")]
|
||||
[InlineData("sh!t", false)]
|
||||
[InlineData("abu$e")]
|
||||
[InlineData("kofagrigus")]
|
||||
[InlineData("cofagrigus", false)]
|
||||
[InlineData("Cofagrigus", false)]
|
||||
[InlineData("inoffensive", false)]
|
||||
public void CensorsBadWordsSwitch(string badword, bool value = true)
|
||||
{
|
||||
var result = WordFilterNX.IsFiltered(badword, out _, EntityContext.Gen9);
|
||||
result.Should().Be(value, $"the word {(value ? "should" : "should not")} have been identified as a bad word");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user