fix: Fixes and optimizes NameVerification and ProfanityProtection (#2153)

### Summary

Significantly improves the performance of NameVerification & ProfanityProtection:

```cs
| Method         | Mean      | Error     | StdDev    |
|--------------- |----------:|----------:|----------:|
| ValidateName   | 728.34 ns | 13.244 ns | 11.059 ns |
| ValidateNameSV |  26.62 ns |  0.233 ns |  0.207 ns |
```
This commit is contained in:
Kamron Batman 2025-04-12 02:26:10 -07:00 committed by GitHub
parent 0ce2a62a76
commit a94814a7ef
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 624 additions and 440 deletions

View file

@ -0,0 +1,137 @@
using System.Buffers;
using Server.Misc;
using Xunit;
namespace Server.Tests;
public class NameVerificationTests
{
[Theory]
[InlineData("John")]
[InlineData("Mary Ann")]
[InlineData("Bob-Jones")]
[InlineData("O'Malley")]
public void ValidatePlayerName_ValidNames_ReturnsTrue(string name)
{
Assert.True(NameVerification.ValidatePlayerName(name));
}
[Theory]
[InlineData("Rex")]
[InlineData("MrWhiskers")]
[InlineData("Fido")]
public void ValidatePetName_ValidNames_ReturnsTrue(string name)
{
Assert.True(NameVerification.ValidatePetName(name));
}
[Theory]
[InlineData("Mrs-Smith")]
[InlineData("Mr. Whiskers")]
[InlineData("Dog123")]
public void ValidatePetName_InvalidNames_ReturnsFalse(string name)
{
Assert.False(NameVerification.ValidatePetName(name));
}
[Theory]
[InlineData("Blacksmith12")]
[InlineData("Baker")]
[InlineData("Innkeeper")]
public void ValidateVendorName_ValidNames_ReturnsTrue(string name)
{
Assert.True(NameVerification.ValidateVendorName(name));
}
[Theory]
[InlineData("Blacksmith 123")]
[InlineData("Baker-Smith")]
[InlineData("*Innkeeper*")]
public void ValidateVendorName_ValidNames_ReturnsFalse(string name)
{
Assert.False(NameVerification.ValidateVendorName(name));
}
[Fact]
public void Validate_MinimumLengthName_ReturnsTrue()
{
Assert.True(NameVerification.Validate("Ab", 2, 16, true, false));
}
[Fact]
public void Validate_MaximumLengthName_ReturnsTrue()
{
Assert.True(NameVerification.Validate("AbcdefghijklmnopqrsT", 1, 20, true, true));
}
[Fact]
public void Validate_MaxExceptions_ReturnsTrue()
{
var exceptions = SearchValues.Create(' ', '-', '.');
Assert.True(NameVerification.Validate("A-B.C D", 2, 16, true, false, false, 3, exceptions));
}
[Fact]
public void Validate_BoundaryOfDisallowedWord_ReturnsTrueWhenNotActuallyDisallowed()
{
// "ass" is in disallowed, but "class" has proper boundaries
Assert.True(NameVerification.ValidatePlayerName("Class"));
}
// Negative Tests
[Fact]
public void Validate_EmptyName_ReturnsFalse()
{
Assert.False(NameVerification.Validate("", 1, 20, true, true));
}
[Fact]
public void Validate_TooShortName_ReturnsFalse()
{
Assert.False(NameVerification.Validate("A", 2, 16, true, false));
}
[Fact]
public void Validate_TooLongName_ReturnsFalse()
{
Assert.False(NameVerification.Validate("AbcdefghijklmnopqrstuvwxyzABCDEF", 2, 16, true, false));
}
[Theory]
[InlineData("ass")]
[InlineData("GodDamn Fine")]
[InlineData("Fuck")]
public void Validate_DisallowedWords_ReturnsFalse(string name)
{
Assert.False(NameVerification.ValidatePlayerName(name));
}
[Theory]
[InlineData("GMJohn")]
[InlineData("LordBob")]
[InlineData("SeerMagic")]
public void Validate_DisallowedPrefixes_ReturnsFalse(string name)
{
Assert.False(NameVerification.ValidatePlayerName(name));
}
[Fact]
public void Validate_TooManyExceptions_ReturnsFalse()
{
var exceptions = SearchValues.Create(' ', '-', '.');
Assert.False(NameVerification.Validate("A-B.C D-E", 2, 16, true, false, false, 3, exceptions));
}
[Fact]
public void Validate_ExceptionAtStartWhenNotAllowed_ReturnsFalse()
{
var exceptions = SearchValues.Create(' ', '-', '.');
Assert.False(NameVerification.Validate("-John", 2, 16, true, false, true, 1, exceptions));
}
[Fact]
public void Validate_DisallowedCharacters_ReturnsFalse()
{
Assert.False(NameVerification.Validate("John123", 2, 16, true, false));
}
}

View file

@ -0,0 +1,53 @@
using Server.Misc;
using Xunit;
namespace Server.Tests;
public class ProfanityProtectionTests
{
[Theory]
[InlineData("Hello world")]
[InlineData("This is a normal conversation")]
[InlineData("I would like to trade with you")]
public void Speech_WithoutProfanity_PassesValidation(string speech)
{
Assert.False(ProfanityProtection.ContainsProfanity(speech));
}
[Fact]
public void Speech_Empty_PassesValidation()
{
Assert.False(ProfanityProtection.ContainsProfanity(""));
}
[Theory]
[InlineData("I'm going to class tomorrow")] // contains "ass" but in "class"
[InlineData("This assignment is hard")] // contains "ass" but in "assignment"
[InlineData("That's a nice cocktail")] // contains "cock" but in "cocktail"
public void Speech_WithWordsThatLookLikeProfanity_PassesValidation(string speech)
{
Assert.False(ProfanityProtection.ContainsProfanity(speech));
}
[Theory]
[InlineData("This is ass")]
[InlineData("What the fuck")]
[InlineData("You're a bitch")]
public void Speech_WithProfanity_FailsValidation(string speech)
{
Assert.True(ProfanityProtection.ContainsProfanity(speech));
}
[Theory]
[InlineData("ass")] // standalone profanity
[InlineData("an ass joke")] // profanity with word boundaries
[InlineData("ass.")] // profanity followed by punctuation
public void ContainsDisallowedWord_DetectsProfanityWithBoundaries(string speech)
{
Assert.True(NameVerification.ContainsDisallowedWord(
speech,
ProfanityProtection.Disallowed,
ProfanityProtection.DisallowedSearchValues
));
}
}

View file

@ -418,7 +418,7 @@ public static partial class CharacterCreation
{
name = name.Trim();
if (!NameVerification.Validate(name, 2, 16, true, false, true, 1, NameVerification.SpaceDashPeriodQuote))
if (!NameVerification.ValidatePlayerName(name))
{
name = "Generic Player";
}

View file

@ -1,3 +1,4 @@
using System.Runtime.CompilerServices;
using Server.Gumps;
using Server.Misc;
using Server.Mobiles;
@ -78,55 +79,20 @@ namespace Server.Guilds
!(m.Deleted || g.Disbanded || m is not PlayerMobile ||
m.AccessLevel < AccessLevel.GameMaster && !g.IsMember(m));
public static bool CheckProfanity(string s, int maxLength = 50)
{
// return NameVerification.Validate( s, 1, 50, true, true, false, int.MaxValue, ProfanityProtection.Exceptions, ProfanityProtection.Disallowed, ProfanityProtection.StartDisallowed ); //What am I doing wrong, this still allows chars like the <3 symbol... 3 AM. someone change this to use this
// With testing on OSI, Guild stuff seems to follow a 'simpler' method of profanity protection
if (s.Length < 1 || s.Length > maxLength)
{
return false;
}
var exceptions = ProfanityProtection.Exceptions;
s = s.ToLower();
for (var i = 0; i < s.Length; ++i)
{
var c = s[i];
if (c is < 'a' or > 'z' && c is < '0' or > '9')
{
var except = false;
for (var j = 0; !except && j < exceptions.Length; j++)
{
if (c == exceptions[j])
{
except = true;
}
}
if (!except)
{
return false;
}
}
}
var disallowed = ProfanityProtection.Disallowed;
for (var i = 0; i < disallowed.Length; i++)
{
if (s.IndexOfOrdinal(disallowed[i]) != -1)
{
return false;
}
}
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool CheckProfanity(string s, int maxLength = 50) =>
NameVerification.Validate(
s,
1,
maxLength,
true,
true,
false,
0,
ProfanityProtection.Exceptions,
ProfanityProtection.Disallowed,
ProfanityProtection.DisallowedSearchValues
);
public void AddHtmlText(int x, int y, int width, int height, TextDefinition text, bool back, bool scroll)
{

View file

@ -1,3 +1,4 @@
using System;
using ModernUO.Serialization;
using Server.Gumps;
using Server.Misc;
@ -79,15 +80,15 @@ public class NameChangeDeedGump : Gump
var m = sender.Mobile;
var newName = info.GetTextEntry(0)?.Trim();
var newName = info.GetTextEntry(0).AsSpan().Trim();
if (!NameVerification.Validate(newName, 2, 16, true, false, true, 1, NameVerification.SpaceDashPeriodQuote))
if (!NameVerification.ValidatePlayerName(newName))
{
m.SendMessage("That name is unacceptable.");
return;
}
m.RawName = newName;
m.RawName = newName.ToString();
m.SendMessage("Your name has been changed!");
m.SendMessage($"You are now known as {newName}");
m_Sender.Delete();

View file

@ -1,247 +1,290 @@
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
namespace Server.Misc
namespace Server.Misc;
public static class NameVerification
{
public static class NameVerification
{
public static readonly char[] SpaceDashPeriodQuote =
{
' ', '-', '.', '\''
};
public static readonly SearchValues<char> AlphaNumeric = SearchValues.Create(
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
'U', 'V', 'W', 'X', 'Y', 'Z',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
'u', 'v', 'w', 'x', 'y', 'z'
);
public static readonly char[] Empty = Array.Empty<char>();
public static readonly SearchValues<char> Alphabetic = SearchValues.Create(
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
'U', 'V', 'W', 'X', 'Y', 'Z',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
'u', 'v', 'w', 'x', 'y', 'z'
);
public static string[] StartDisallowed { get; } =
{
public static readonly SearchValues<char> Numeric = SearchValues.Create(
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
);
public static readonly SearchValues<char> SpaceDashPeriodQuote = SearchValues.Create(' ', '-', '.', '\'');
public static readonly SearchValues<string> StartDisallowed = SearchValues.Create(
[
"seer",
"counselor",
"gm",
"admin",
"lady",
"lord"
};
],
StringComparison.OrdinalIgnoreCase
);
public static string[] Disallowed { get; } =
{
"jigaboo",
"chigaboo",
"wop",
"kyke",
"kike",
"tit",
"spic",
"prick",
"piss",
"lezbo",
"lesbo",
"felatio",
"dyke",
"dildo",
"chinc",
"chink",
"cunnilingus",
"cum",
"cocksucker",
"cock",
"clitoris",
"clit",
"ass",
"hitler",
"penis",
"nigga",
"nigger",
"klit",
"kunt",
"jiz",
"jism",
"jerkoff",
"jackoff",
"goddamn",
"fag",
"blowjob",
"bitch",
"asshole",
"dick",
"pussy",
"snatch",
"cunt",
"twat",
"shit",
"fuck",
"tailor",
"smith",
"scholar",
"rogue",
"novice",
"neophyte",
"merchant",
"medium",
"master",
"mage",
"lb",
"journeyman",
"grandmaster",
"fisherman",
"expert",
"chef",
"carpenter",
"british",
"blackthorne",
"blackthorn",
"beggar",
"archer",
"apprentice",
"adept",
"gamemaster",
"frozen",
"squelched",
"invulnerable",
"osi",
"origin"
};
public static readonly string[] Disallowed =
[
..ProfanityProtection.Disallowed,
"tailor",
"smith",
"scholar",
"rogue",
"novice",
"neophyte",
"merchant",
"medium",
"master",
"mage",
"lb",
"journeyman",
"grandmaster",
"fisherman",
"expert",
"chef",
"carpenter",
"british",
"blackthorne",
"blackthorn",
"beggar",
"archer",
"apprentice",
"adept",
"gamemaster",
"frozen",
"squelched",
"invulnerable",
"osi",
"origin"
];
public static void Configure()
public static readonly SearchValues<string> DisallowedSearchValues = SearchValues.Create(
Disallowed,
StringComparison.OrdinalIgnoreCase
);
public static void Configure()
{
CommandSystem.Register("ValidateName", AccessLevel.Administrator, ValidateName_OnCommand);
}
[Usage("ValidateName"), Description("Checks the result of NameValidation on the specified name.")]
public static void ValidateName_OnCommand(CommandEventArgs e)
{
if (Validate(e.ArgString, 2, 16, true, false, true, 1, SpaceDashPeriodQuote))
{
CommandSystem.Register("ValidateName", AccessLevel.Administrator, ValidateName_OnCommand);
e.Mobile.SendMessage(0x59, "That name is considered valid.");
}
else
{
e.Mobile.SendMessage(0x22, "That name is considered invalid.");
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool ValidatePlayerName(ReadOnlySpan<char> name) =>
Validate(name, 2, 16, true, false, true, 1, SpaceDashPeriodQuote);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool ValidatePetName(ReadOnlySpan<char> name) => Validate(name, 1, 16, true, false, exceptions: null);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool ValidateVendorName(ReadOnlySpan<char> name) => Validate(name, 1, 20, true, true);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Validate(
ReadOnlySpan<char> name, int minLength, int maxLength, bool allowLetters, bool allowDigits,
bool noExceptionsAtStart = true, int maxExceptions = 0, SearchValues<char> exceptions = null
) => Validate(
name,
minLength,
maxLength,
allowLetters,
allowDigits,
noExceptionsAtStart,
maxExceptions,
exceptions,
Disallowed,
DisallowedSearchValues,
StartDisallowed
);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool Validate(
ReadOnlySpan<char> name, int minLength, int maxLength, bool allowLetters, bool allowDigits,
bool noExceptionsAtStart, int maxExceptions, SearchValues<char> exceptions, ReadOnlySpan<string> disallowed,
SearchValues<string> disallowedSV
) => Validate(
name,
minLength,
maxLength,
allowLetters,
allowDigits,
noExceptionsAtStart,
maxExceptions,
exceptions,
disallowed,
disallowedSV,
null
);
public static bool Validate(
ReadOnlySpan<char> name, int minLength, int maxLength, bool allowLetters, bool allowDigits,
bool noExceptionsAtStart, int maxExceptions, SearchValues<char> exceptions, ReadOnlySpan<string> disallowed,
SearchValues<string> disallowedSV, SearchValues<string> startDisallowedSV
)
{
if (name.Length == 0 || name.Length < minLength || name.Length > maxLength)
{
return false;
}
[Usage("ValidateName"), Description("Checks the result of NameValidation on the specified name.")]
public static void ValidateName_OnCommand(CommandEventArgs e)
if (exceptions == null)
{
if (Validate(e.ArgString, 2, 16, true, false, true, 1, SpaceDashPeriodQuote))
// We don't have exceptions, so we might be limited to letters or numbers
var allowed = allowLetters switch
{
e.Mobile.SendMessage(0x59, "That name is considered valid.");
}
else
// If we don't allow exceptions, then non-alphanumeric is not allowed
true when allowDigits && maxExceptions == 0 => AlphaNumeric,
true when !allowDigits => Alphabetic,
false when allowDigits => Numeric,
// Everything has been allowed! Use `Utility.FixHtml()` to stop weird behavior
_ => null
};
if (allowed != null && name.ContainsAnyExcept(allowed))
{
e.Mobile.SendMessage(0x22, "That name is considered invalid.");
return false;
}
}
public static bool Validate(
string name, int minLength, int maxLength, bool allowLetters, bool allowDigits,
bool noExceptionsAtStart, int maxExceptions, char[] exceptions
) =>
Validate(
name,
minLength,
maxLength,
allowLetters,
allowDigits,
noExceptionsAtStart,
maxExceptions,
exceptions,
Disallowed,
StartDisallowed
);
public static bool Validate(
string name, int minLength, int maxLength, bool allowLetters, bool allowDigits,
bool noExceptionsAtStart, int maxExceptions, char[] exceptions, string[] disallowed, string[] startDisallowed
)
else
{
if (name == null || name.Length < minLength || name.Length > maxLength)
// We have exceptions, and at least one of the letters/digits flag is false:
var notAllowed = allowLetters switch
{
true when !allowDigits => Numeric,
false when allowDigits => Alphabetic,
_ => null
};
if (notAllowed != null && name.ContainsAny(notAllowed))
{
return false;
}
var exceptCount = 0;
name = name.ToLower();
if (!allowLetters || !allowDigits ||
exceptions.Length > 0 && (noExceptionsAtStart || maxExceptions < int.MaxValue))
if (ContainsExceptions(name, exceptions, noExceptionsAtStart, maxExceptions))
{
for (var i = 0; i < name.Length; ++i)
{
var c = name[i];
if (c >= 'a' && c <= 'z')
{
if (!allowLetters)
{
return false;
}
exceptCount = 0;
}
else if (c >= '0' && c <= '9')
{
if (!allowDigits)
{
return false;
}
exceptCount = 0;
}
else
{
var except = false;
for (var j = 0; !except && j < exceptions.Length; ++j)
{
if (c == exceptions[j])
{
except = true;
}
}
if (!except || i == 0 && noExceptionsAtStart)
{
return false;
}
if (exceptCount++ == maxExceptions)
{
return false;
}
}
}
return false;
}
for (var i = 0; i < disallowed.Length; ++i)
{
var indexOf = name.IndexOfOrdinal(disallowed[i]);
if (indexOf == -1)
{
continue;
}
var badPrefix = indexOf == 0;
for (var j = 0; !badPrefix && j < exceptions.Length; ++j)
{
badPrefix = name[indexOf - 1] == exceptions[j];
}
if (!badPrefix)
{
continue;
}
var badSuffix = indexOf + disallowed[i].Length >= name.Length;
for (var j = 0; !badSuffix && j < exceptions.Length; ++j)
{
badSuffix = name[indexOf + disallowed[i].Length] == exceptions[j];
}
if (badSuffix)
{
return false;
}
}
for (var i = 0; i < startDisallowed.Length; ++i)
{
if (name.StartsWithOrdinal(startDisallowed[i]))
{
return false;
}
}
return true;
}
if (disallowedSV != null && disallowed.Length > 0 && ContainsDisallowedWord(name, disallowed, disallowedSV))
{
return false;
}
return startDisallowedSV == null || name.IndexOfAny(startDisallowedSV) != 0;
}
public static bool ContainsExceptions(
ReadOnlySpan<char> name, SearchValues<char> exceptions, bool noExceptionsAtStart, int maxExceptions
)
{
if (!noExceptionsAtStart && maxExceptions is <= -1 or >= int.MaxValue)
{
return false;
}
var index = name.IndexOfAny(exceptions);
while (index != -1)
{
if (noExceptionsAtStart)
{
if (index == 0)
{
return true;
}
noExceptionsAtStart = false;
}
if (maxExceptions-- <= 0)
{
return true;
}
if (index + 1 < name.Length)
{
name = name[(index + 1)..];
index = name.IndexOfAny(exceptions);
}
else
{
index = -1;
}
}
return false;
}
public static bool ContainsDisallowedWord(ReadOnlySpan<char> name, ReadOnlySpan<string> disallowed, SearchValues<string> disallowedSV)
{
var index = name.IndexOfAny(disallowedSV);
while (index != -1)
{
var isStartBoundary = index == 0 || !char.IsLetterOrDigit(name[index - 1]);
if (isStartBoundary)
{
for (var i = 0; i < disallowed.Length; i++)
{
var word = disallowed[i].AsSpan();
if (index + word.Length > name.Length || !name.Slice(index, word.Length).InsensitiveEquals(word))
{
continue;
}
// End boundary
if (index + word.Length == name.Length || !char.IsLetterOrDigit(name[index + word.Length]))
{
return true;
}
}
}
if (index + 1 < name.Length)
{
name = name[(index + 1)..];
index = name.IndexOfAny(disallowedSV);
}
else
{
index = -1;
}
}
return false;
}
}

View file

@ -1,140 +1,147 @@
using System;
using System.Buffers;
namespace Server.Misc
namespace Server.Misc;
public enum ProfanityAction
{
public enum ProfanityAction
None, // no action taken
Disallow, // speech is not displayed
Criminal, // makes the player criminal, not killable by guards
CriminalAction, // makes the player criminal, can be killed by guards
Disconnect, // player is kicked
Other // some other implementation
}
public static class ProfanityProtection
{
private static bool Enabled;
private static ProfanityAction Action;
public static void Configure()
{
None, // no action taken
Disallow, // speech is not displayed
Criminal, // makes the player criminal, not killable by guards
CriminalAction, // makes the player criminal, can be killed by guards
Disconnect, // player is kicked
Other // some other implementation
Enabled = ServerConfiguration.GetSetting("profanityProtection.enabled", false);
Action = ServerConfiguration.GetSetting("profanityProtection.action", ProfanityAction.Disallow);
if (Enabled)
{
EventSink.Speech += EventSink_Speech;
}
}
public static class ProfanityProtection
// Used by the guild system
public static readonly SearchValues<char> Exceptions = SearchValues.Create(
' ', '-', '.', '\'', '"', ',', '_', '+', '=', '~', '`', '!', '^', '*', '\\', '/', ';', ':', '<', '>', '[', ']',
'{', '}', '?', '|', '(', ')', '%', '$', '&', '#', '@'
);
public static readonly string[] Disallowed =
[
"jigaboo",
"chigaboo",
"wop",
"kyke",
"kike",
"tit",
"spic",
"prick",
"piss",
"lezbo",
"lesbo",
"felatio",
"dyke",
"dildo",
"chinc",
"chink",
"cunnilingus",
"cum",
"cocksucker",
"cock",
"clitoris",
"clit",
"ass",
"hitler",
"penis",
"nigga",
"nigger",
"klit",
"kunt",
"jiz",
"jism",
"jerkoff",
"jackoff",
"goddamn",
"fag",
"blowjob",
"bitch",
"asshole",
"dick",
"pussy",
"snatch",
"cunt",
"twat",
"shit",
"fuck",
];
public static readonly SearchValues<string> DisallowedSearchValues = SearchValues.Create(
Disallowed,
StringComparison.OrdinalIgnoreCase
);
private static bool OnProfanityDetected(Mobile from, string speech)
{
// TODO: Move this to configuration
private static readonly bool Enabled = false;
private static readonly ProfanityAction
Action = ProfanityAction.Disallow; // change here what to do when profanity is detected
public static char[] Exceptions { get; } =
switch (Action)
{
' ', '-', '.', '\'', '"', ',', '_', '+', '=', '~', '`', '!', '^', '*', '\\', '/', ';', ':', '<', '>', '[', ']',
'{', '}', '?', '|', '(', ')', '%', '$', '&', '#', '@'
};
case ProfanityAction.None: return true;
case ProfanityAction.Disallow: return false;
case ProfanityAction.Criminal:
from.Criminal = true;
return true;
case ProfanityAction.CriminalAction:
from.CriminalAction(false);
return true;
case ProfanityAction.Disconnect:
{
from.NetState?.Disconnect("Using profanity.");
public static string[] StartDisallowed { get; } = Array.Empty<string>();
return false;
}
default:
case ProfanityAction.Other: // TODO: Provide custom implementation if this is chosen
{
return true;
}
}
}
public static string[] Disallowed { get; } =
public static bool ContainsProfanity(ReadOnlySpan<char> speech) =>
speech.Length > 0 &&
!NameVerification.Validate(
speech,
1,
int.MaxValue,
true,
true,
true,
int.MaxValue, // allow all non-alphanumeric characters
null,
Disallowed,
DisallowedSearchValues
);
private static void EventSink_Speech(SpeechEventArgs e)
{
var from = e.Mobile;
if (from.AccessLevel > AccessLevel.Player)
{
"jigaboo",
"chigaboo",
"wop",
"kyke",
"kike",
"tit",
"spic",
"prick",
"piss",
"lezbo",
"lesbo",
"felatio",
"dyke",
"dildo",
"chinc",
"chink",
"cunnilingus",
"cum",
"cocksucker",
"cock",
"clitoris",
"clit",
"ass",
"hitler",
"penis",
"nigga",
"nigger",
"klit",
"kunt",
"jiz",
"jism",
"jerkoff",
"jackoff",
"goddamn",
"fag",
"blowjob",
"bitch",
"asshole",
"dick",
"pussy",
"snatch",
"cunt",
"twat",
"shit",
"fuck"
};
public static void Initialize()
{
if (Enabled)
{
EventSink.Speech += EventSink_Speech;
}
return;
}
private static bool OnProfanityDetected(Mobile from, string speech)
if (ContainsProfanity(e.Speech))
{
switch (Action)
{
case ProfanityAction.None: return true;
case ProfanityAction.Disallow: return false;
case ProfanityAction.Criminal:
from.Criminal = true;
return true;
case ProfanityAction.CriminalAction:
from.CriminalAction(false);
return true;
case ProfanityAction.Disconnect:
{
from.NetState?.Disconnect("Using profanity.");
return false;
}
default:
case ProfanityAction.Other: // TODO: Provide custom implementation if this is chosen
{
return true;
}
}
}
private static void EventSink_Speech(SpeechEventArgs e)
{
var from = e.Mobile;
if (from.AccessLevel > AccessLevel.Player)
{
return;
}
if (!NameVerification.Validate(
e.Speech,
0,
int.MaxValue,
true,
true,
false,
int.MaxValue,
Exceptions,
Disallowed,
StartDisallowed
))
{
e.Blocked = !OnProfanityDetected(from, e.Speech);
}
e.Blocked = !OnProfanityDetected(from, e.Speech);
}
}
}

View file

@ -1,54 +1,31 @@
using System;
namespace Server.Misc
namespace Server.Misc;
public static class RenameRequests
{
public static class RenameRequests
public static void RenameRequest(Mobile from, Mobile targ, string name)
{
public static void RenameRequest(Mobile from, Mobile targ, string name)
if (!from.CanSee(targ) || !from.InRange(targ, 12) || !targ.CanBeRenamedBy(from))
{
if (from.CanSee(targ) && from.InRange(targ, 12) && targ.CanBeRenamedBy(from))
{
name = name.Trim();
return;
}
if (NameVerification.Validate(
name,
1,
16,
true,
false,
true,
0,
NameVerification.Empty,
NameVerification.StartDisallowed,
Core.ML ? NameVerification.Disallowed : Array.Empty<string>()
))
{
if (Core.ML)
{
var disallowed = ProfanityProtection.Disallowed;
var span = name.AsSpan().Trim();
for (var i = 0; i < disallowed.Length; i++)
{
if (name.IndexOfOrdinal(disallowed[i]) != -1)
{
from.SendLocalizedMessage(1072622); // That name isn't very polite.
return;
}
}
from.SendLocalizedMessage(
1072623,
$"{targ.Name}\t{name}"
); // Pet ~1_OLDPETNAME~ renamed to ~2_NEWPETNAME~.
}
targ.Name = name;
}
else
{
from.SendMessage("That name is unacceptable.");
}
}
if (NameVerification.ValidatePetName(span))
{
// Pet ~1_OLDPETNAME~ renamed to ~2_NEWPETNAME~.
from.SendLocalizedMessage(1072623, $"{targ.Name}\t{span}");
targ.Name = span.ToString();
}
else if (span.IndexOfAny(ProfanityProtection.DisallowedSearchValues) != -1)
{
from.SendLocalizedMessage(1072622); // That name isn't very polite.
}
else
{
from.SendMessage("That name is unacceptable.");
}
}
}

View file

@ -1232,9 +1232,9 @@ public partial class PlayerVendor : Mobile
return;
}
var name = text.Trim();
var name = text.AsSpan().Trim();
if (!NameVerification.Validate(name, 1, 20, true, true, true, 0, NameVerification.Empty))
if (!NameVerification.ValidateVendorName(name))
{
m_Vendor.SayTo(from, "That name is unacceptable.");
return;
@ -1261,9 +1261,9 @@ public partial class PlayerVendor : Mobile
return;
}
var name = text.Trim();
var name = text.AsSpan().Trim();
if (!NameVerification.Validate(name, 1, 20, true, true, true, 0, NameVerification.Empty))
if (!NameVerification.ValidateVendorName(name))
{
m_Vendor.SayTo(from, "That name is unacceptable.");
return;