Skip to content

feat(character): expand name restriction matching and split reserved name semantics#25461

Open
youdeoo wants to merge 3 commits intoazerothcore:masterfrom
youdeoo:feat-name-restriction-enhance
Open

feat(character): expand name restriction matching and split reserved name semantics#25461
youdeoo wants to merge 3 commits intoazerothcore:masterfrom
youdeoo:feat-name-restriction-enhance

Conversation

@youdeoo
Copy link
Copy Markdown
Contributor

@youdeoo youdeoo commented Apr 14, 2026

Changes Proposed:

This PR proposes changes to:

  • Core (units, players, creatures, game systems).
  • Scripts (bosses, spell scripts, creature scripts).
  • Database (SAI, creatures, etc).

This updates the character name restriction system to support richer matching and clearer separation of responsibilities between reserved_name and profanity_name.

Key changes:

  • Add flag-based matching to both tables:
    • 0 exact match
    • 1 ends with (%name)
    • 2 starts with (name%)
    • 3 contains (%name%)
  • Keep profanity_name as a locale-aware profanity filter using locale.
  • Rework reserved_name into a security-aware reserved name system using security, so it defines which account roles are allowed to use reserved names.
  • Apply the new rules consistently during:
    • character creation
    • character rename
    • character customization
    • faction/race change
    • login-time character loading
    • startup/reload scans that flag existing characters for rename
  • Add dedicated GM commands for managing each list directly:
    • .character name reserved list|lookup|add|remove
    • .character name profanity list|lookup|add|remove
  • Update SQL structure, command help text, and root-level test data/docs for the new model.

AI-assisted Pull Requests

Important

While the use of AI tools when preparing pull requests is not prohibited, contributors must clearly disclose when such tools have been used and specify the model involved.

Contributors are also expected to fully understand the changes they are submitting and must be able to explain and justify those changes when requested by maintainers.

  • AI tools (e.g. ChatGPT, Claude, or similar) were used entirely or partially in preparing this pull request. Please specify which tools were used, if any.

Issues Addressed:

SOURCE:

The changes have been validated through:

  • Live research (checked on live servers, e.g Classic WotLK, Retail, etc.)
  • Sniffs (remember to share them with the open source community!)
  • Video evidence, knowledge databases or other public sources (e.g forums, Wowhead, etc.)
  • The changes promoted by this pull request come partially or entirely from another project (cherry-pick). Cherry-picks must be committed using the proper --author tag in order to be accepted, thus crediting the original authors, unless otherwise unable to be found

Reference:

Tests Performed:

This PR has been:

  • Tested in-game by the author.
  • Tested in-game by other community members/someone else other than the author/has been live on production servers.
  • This pull request requires further testing and may have edge cases to be tested.

How to Test the Changes:

  • This pull request can be tested by following the reproduction steps provided in the linked issue
  • This pull request requires further testing. Provide steps to test your changes. If it requires any specific setup e.g multiple players please specify it as well.
  1. Apply the SQL updates that add the new flags, security, locale, and comment columns.
  2. Start worldserver and confirm it loads both restricted-name tables without SQL errors.
  3. Add reserved rules with different security levels:
    • .character name reserved add taff 3 3 Reserved for administrators only
    • .character name reserved add Roja 0 2 Exact Roja requires GM
  4. Add profanity rules with different locale values:
    • .character name profanity add Rojo 3 6 Spanish red male
    • .character name profanity add Nazi 3 -1 Global blocked word
  5. Verify listing and lookup commands:
    • .character name reserved list
    • .character name reserved lookup taff 3 3
    • .character name profanity list
    • .character name profanity lookup Rojo 3 6
  6. Test online character creation and rename paths:
    • a normal player should be blocked by reserved rules that require higher security
    • a GM account with sufficient security should be allowed to use matching reserved names
    • locale-specific profanity rules should only affect matching account/client locales
  7. Test reload behavior:
    • .reload reserved_name
    • .reload profanity_name
    • confirm existing characters that now violate the rules receive AT_LOGIN_RENAME
  8. Test login behavior for existing characters:
    • a character blocked by profanity or by insufficient reserved-name security should be forced to rename
    • a GM account whose character name is allowed by reserved-name security should not be forced to rename

Known Issues and TODO List:

  • Further in-game validation is still needed for locale-specific behavior across different client/account locales.
  • NamesReserved.dbc is intentionally not merged into the new security-based reserved_name model and should be reviewed explicitly during PR discussion.

How to Test AzerothCore PRs

When a PR is ready to be tested, it will be marked as [WAITING TO BE TESTED].

You can help by testing PRs and writing your feedback here on the PR's page on GitHub. Follow the instructions here:

http://www.azerothcore.org/wiki/How-to-test-a-PR

REMEMBER: when testing a PR that changes something generic (i.e. a part of code that handles more than one specific thing), the tester should not only check that the PR does its job (e.g. fixing spell XXX) but especially check that the PR does not cause any regression (i.e. introducing new bugs).

For example: if a PR fixes spell X by changing a part of code that handles spells X, Y, and Z, we should not only test X, but we should test Y and Z as well.

…name semantics

add flag-based matching for reserved_name and profanity_name to support exact, prefix, suffix, and contains checks rework reserved_name into a security-based reserved name system so it no longer overlaps with profanity filtering apply locale and security-aware validation across character creation, rename, login loading, and restricted-name reload scans add dedicated GM commands for reserved and profanity name management, and update help text and test data accordingly.
@github-actions github-actions Bot added DB related to the SQL database CORE Related to the core Script Refers to C++ Scripts for the Core file-cpp Used to trigger the matrix build labels Apr 14, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors character name restrictions to support richer matching (exact / prefix / suffix / contains) and to split responsibilities between reserved_name (security-gated) and profanity_name (locale-aware), applying the updated checks across character lifecycle paths and adding GM commands for managing both lists.

Changes:

  • Extend reserved/profanity rule matching with flag-based wildcard semantics and propagate locale/security into ObjectMgr::CheckPlayerName call sites.
  • Rework reserved_name rules to be security-aware and profanity_name rules to be locale-aware, including startup/reload scanning that flags characters for rename.
  • Add .character name reserved|profanity list|lookup|add|remove commands and update DB schema / command help text accordingly.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/server/shared/DataStores/DBCfmt.h Adjust DBC format strings to load the Language field for NamesReserved/NamesProfanity.
src/server/shared/DataStores/DBCStructure.h Add Language field to the DBC entry structs.
src/server/scripts/Commands/cs_character.cpp Add new GM subcommands to manage name restriction lists; update rename validation to pass locale/security into name checks.
src/server/game/Tools/PlayerDump.cpp Pass locale/security into CheckPlayerName for dump imports.
src/server/game/Handlers/CharacterHandler.cpp Pass session locale/security into CheckPlayerName for create/rename/customize/faction-change paths.
src/server/game/Globals/ObjectMgr.h Introduce restriction flag/security/locale types and update reserved/profanity APIs and storage containers.
src/server/game/Globals/ObjectMgr.cpp Implement matching logic, DB/DBC loading for the new model, and realm-wide rename-flag scanning after restriction refreshes.
src/server/game/Entities/Player/PlayerStorage.cpp Enforce new name restriction rules during login-time player load using session locale/security.
src/server/database/Database/Implementation/CharacterDatabase.cpp Update prepared statements for new schema and upsert behavior (unique keys on name/flags/scope).
data/sql/updates/pending_db_world/rev_1775969016231915300.sql Add/refresh command table entries and help text for new commands.
data/sql/updates/pending_db_characters/rev_1776136547300231200.sql Alter reserved_name/profanity_name schema (id PK, flags, scope, comment, unique constraints).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

('character name reserved lookup', 2, 'Syntax: .character name reserved lookup $name [$flags] [$security]\r\n\r\nLooks up reserved name restrictions matching $name.\r\n$flags: 0 exact, 1 %%name, 2 name%%, 3 %%name%%.\r\n$security: optional security filter (-1 all accounts, 0 player, 1 moderator, 2 gamemaster, 3 administrator, 4 console).'),
('character name reserved add', 2, 'Syntax: .character name reserved add $name $flags [$security] [$comment]\r\n\r\nAdds a reserved name rule.\r\n$flags: 0 exact, 1 %%name, 2 name%%, 3 %%name%%.\r\n$security: optional security value (-1 all accounts, 0 player, 1 moderator, 2 gamemaster, 3 administrator, 4 console).'),
('character name reserved remove', 2, 'Syntax: .character name reserved remove $name $flags [$security]\r\n\r\nRemoves reserved name rules.\r\n$flags: use -1 to remove all flags for the given name.\r\n$security: optional security filter (-1 all accounts, 0 player, 1 moderator, 2 gamemaster, 3 administrator, 4 console).'),
('character rename', 2, 'Syntax: .character rename [$name] [reserveName] [$newName]\r\n\r\nMark selected in game or by $name in command character for rename at next login.\r\n\r\nIf [reserveName] is 1 then the player''s current name is added to the list of reserved names with exact match, security 0 and an empty comment.\r\nIf [newName] then the player will be forced rename.');
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The .character rename help text says [reserveName]=1 adds the current name as reserved with security 0. With the new reserved-name model (security-based allowance), security=0 typically means the name is allowed for normal players, so this description (and likely the underlying behavior) is inconsistent. Please update the help text to match the intended semantics (and the actual parameters used when adding the reserved rule).

Suggested change
('character rename', 2, 'Syntax: .character rename [$name] [reserveName] [$newName]\r\n\r\nMark selected in game or by $name in command character for rename at next login.\r\n\r\nIf [reserveName] is 1 then the player''s current name is added to the list of reserved names with exact match, security 0 and an empty comment.\r\nIf [newName] then the player will be forced rename.');
('character rename', 2, 'Syntax: .character rename [$name] [reserveName] [$newName]\r\n\r\nMark selected in game or by $name in command character for rename at next login.\r\n\r\nIf [reserveName] is 1 then the player''s current name is added to the list of reserved names with exact match, security 1 and an empty comment.\r\nIf [newName] then the player will be forced rename.');

Copilot uses AI. Check for mistakes.
Comment thread src/server/game/Globals/ObjectMgr.cpp Outdated

static bool SecurityAllowsReservedName(int32 security, int8 requiredSecurity)
{
return requiredSecurity == PLAYER_NAME_RESERVED_FOR_ALL_ACCOUNTS || security >= requiredSecurity;
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SecurityAllowsReservedName() currently treats PLAYER_NAME_RESERVED_FOR_ALL_ACCOUNTS (-1) as allowing the reserved name for everyone. Given the constant name and command help text ("-1 all accounts"), this likely inverts the intended semantics and can make a reserved rule a no-op (and can also affect profanity evaluation when a reserved match exists). Consider changing the logic so -1 means "reserved for all accounts" (i.e., never allowed), and use SEC_PLAYER (0) to mean "allowed for all" instead.

Suggested change
return requiredSecurity == PLAYER_NAME_RESERVED_FOR_ALL_ACCOUNTS || security >= requiredSecurity;
if (requiredSecurity == PLAYER_NAME_RESERVED_FOR_ALL_ACCOUNTS)
return false;
return security >= requiredSecurity;

Copilot uses AI. Check for mistakes.
Comment on lines +9351 to 9357
ReservedPlayerNameMatch reservedMatch = sObjectMgr->GetReservedPlayerNameMatch(name, security);

if (reservedMatch.Matched && !reservedMatch.IsAllowedForCurrentSecurity)
return CHAR_NAME_RESERVED;

// Check Profanity Name
if (sObjectMgr->IsProfanityName(name))
if (!reservedMatch.Matched && sObjectMgr->IsProfanityName(name, locale))
return CHAR_NAME_PROFANE;
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CheckPlayerName() skips the profanity check whenever any reserved rule matches (if (!reservedMatch.Matched && IsProfanityName(...))). This means an account that is allowed to use a reserved name (or any reserved rule that effectively allows the name) can bypass profanity filtering entirely for that name. Consider checking profanity whenever the reserved match is allowed (e.g., only skip profanity when returning CHAR_NAME_RESERVED).

Copilot uses AI. Check for mistakes.
Comment thread src/server/game/Globals/ObjectMgr.cpp Outdated
Comment on lines +9304 to +9305
if (reservedMatch.Matched)
shouldFlagRename = !reservedMatch.IsAllowedForCurrentSecurity;
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FlagCharactersWithRestrictedNames() only checks profanity when reservedMatch.Matched is false. As a result, characters whose names are profane and match an allowed reserved rule will not be flagged for rename. Consider applying the same decision order as name creation: if reserved match is disallowed => flag, otherwise still evaluate profanity and flag if profane.

Suggested change
if (reservedMatch.Matched)
shouldFlagRename = !reservedMatch.IsAllowedForCurrentSecurity;
if (reservedMatch.Matched && !reservedMatch.IsAllowedForCurrentSecurity)
shouldFlagRename = true;

Copilot uses AI. Check for mistakes.
Comment thread src/server/game/Globals/ObjectMgr.cpp Outdated
Comment on lines +9009 to +9016
CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_RESERVED_PLAYER_NAME);
stmt->SetData(0, name);
stmt->SetData(1, flags);
stmt->SetData(2, int16(security));
stmt->SetData(3, comment);
CharacterDatabase.Execute(stmt);

void ObjectMgr::AddReservedPlayerName(std::string const& name)
{
if (!IsReservedName(name))
{
std::wstring wstr;
if (!Utf8toWStr(name, wstr))
{
LOG_ERROR("server", "Could not add invalid name to reserved player names: {}", name);
return;
}
wstrToLower(wstr);

_reservedNamesStore.insert(wstr);

CharacterDatabasePreparedStatement* stmt = CharacterDatabase.GetPreparedStatement(CHAR_INS_RESERVED_PLAYER_NAME);
stmt->SetData(0, name);
CharacterDatabase.Execute(stmt);
}
FlagCharactersWithRestrictedNames();
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AddReservedPlayerName() / AddProfanityPlayerName() call FlagCharactersWithRestrictedNames() immediately after every insertion. Since FlagCharactersWithRestrictedNames() scans all accounts and all characters and potentially issues many UPDATEs, running it on each .character name ... add can be very expensive on large realms. Consider limiting the full scan to startup/reload, or making the command path update only affected characters (or running the scan asynchronously/off-thread).

Copilot uses AI. Check for mistakes.

ResponseCodes res = ResponseCodes(ObjectMgr::CheckPlayerName(newName, true));
int32 security = handler->GetSession() ? handler->GetSession()->GetSecurity() : SEC_CONSOLE;
ResponseCodes res = ResponseCodes(ObjectMgr::CheckPlayerName(newName, true, handler->GetSessionDbcLocale(), security));
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When .character rename ... [reserveName]=1 is used, the code later calls sObjectMgr->AddReservedPlayerName(player->GetName()) with default arguments (flags=0, security=SEC_PLAYER). With the new security-based reserved-name semantics, this defaults to a rule that won’t actually reserve the name from normal players (and may also interact badly with profanity checks when a reserved match exists). Consider passing explicit flags and a non-player security level (e.g. SEC_GAMEMASTER) to preserve the command’s intended behavior.

Suggested change
ResponseCodes res = ResponseCodes(ObjectMgr::CheckPlayerName(newName, true, handler->GetSessionDbcLocale(), security));
int32 effectiveSecurity = reserveName ? SEC_GAMEMASTER : security;
ResponseCodes res = ResponseCodes(ObjectMgr::CheckPlayerName(newName, true, handler->GetSessionDbcLocale(), effectiveSecurity));

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CORE Related to the core DB related to the SQL database file-cpp Used to trigger the matrix build Script Refers to C++ Scripts for the Core

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature: Expand the usability of reserve and profanity name tables

2 participants