Brazil's biggest MU Online portal — since 2003
Tutorial Intermediate Web

How to build nickname and class change via the website in MU Online

Implement nickname and class changes via the website in MU Online with name validation, safe foreign-key updates, and optional WCoin billing.

BR Bruno · Updated on Oct 5, 2025 · ⏱ 20 min read
Quick answer

Changing a character's name (nick) or class is one of the two most sought-after paid services on private MU Online servers. One player wants to rename the character they created in a hurry; another got tired of the Dark Wizard and wants to become a Dark Knight without starting from scratch. Offering

Changing a character's name (nick) or class is one of the two most sought-after paid services on private MU Online servers. One player wants to rename the character they created in a hurry; another got tired of the Dark Wizard and wants to become a Dark Knight without starting from scratch. Offering these changes through the website is convenient and profitable — but it is also one of the most dangerous operations to implement. Unlike reset and PK Clear, which touch a few columns of a single row, a nick change touches relationships: guild, friends list, messages, rankings. Doing it wrong produces characters orphaned from their guild, lost items, and inconsistencies that are hard to trace.

This tutorial shows how to build nick and class changes via the website safely and atomically, using stored procedures in SQL Server and a lean PHP layer. The field and table names are an EXAMPLE from Season 6; the exact structure varies by version and by emulator. Always map the name's dependencies in your database before applying anything. If you do not have a server and site running yet, start with how to create a MU Online server.

Prerequisites

  • A working MU Online server with an accessible MuOnline database.
  • SQL Server (2008/2014/2017/2019) with SSMS and db_owner permission.
  • A site in PHP 7.4+ connected via PDO (sqlsrv or dblib).
  • An account panel with working login/session.
  • A recent backup and a staging environment for testing.

Step 1 — Map every dependency of the character name

Before changing a single character, find out where the character name is referenced. The name is usually the logical key used across several tables. Run:

SELECT t.name AS Tabela, c.name AS Coluna
FROM sys.columns c
JOIN sys.tables t ON t.object_id = c.object_id
WHERE c.name IN ('Name', 'Char', 'CharName', 'Member', 'GameID')
ORDER BY t.name;

In a common Season 6 EXAMPLE, the name appears in:

TableColumnRole
CharacterNameMain character record
GuildG_MasterGuild master's name
GuildMemberNameGuild members
FriendName / FriendNameFriends list
MEMB_INFO (via AccountCharacter)Character slots per account
warehouse/RankingdependentMiscellaneous references

Ignoring any of these produces inconsistency: for example, renaming in Character but not in GuildMember makes the character "vanish" from the guild. The list varies by version — map yours with the query above.

Step 2 — Validation rules for the new name

An invalid name can freeze the game client or open the door to exploits. Apply strict validation both in PHP and in the procedure:

  • Letters and numbers only; no spaces, accents, or symbols.
  • Typical length of 1 to 10 characters (varies by version).
  • Case-insensitive duplicate check (prevents "Player" and "player" coexisting).
  • A forbidden-words list (reserved names, slurs, "GM", "Admin").
<?php
function nomeValido(string $nome): bool {
    if (!preg_match('/^[A-Za-z0-9]{1,10}$/', $nome)) return false;
    $proibidos = ['gm', 'admin', 'gamemaster', 'moderador'];
    return !in_array(strtolower($nome), $proibidos, true);
}

Step 3 — Nick change stored procedure

The name change has to be atomic: either all the tables are updated, or none of them. A transaction with XACT_ABORT ON guarantees that. The procedure also validates ownership, online status, duplicates, and billing.

USE MuOnline;
GO

IF OBJECT_ID('dbo.WZ_TrocaNickViaSite', 'P') IS NOT NULL
    DROP PROCEDURE dbo.WZ_TrocaNickViaSite;
GO

CREATE PROCEDURE dbo.WZ_TrocaNickViaSite
    @AccountID  VARCHAR(10),
    @OldName    VARCHAR(10),
    @NewName    VARCHAR(10),
    @WCoinCost  INT = 0
AS
BEGIN
    SET NOCOUNT ON;
    SET XACT_ABORT ON;

    DECLARE @Owner VARCHAR(10), @Online TINYINT, @WCoin INT;

    BEGIN TRANSACTION;

    SELECT @Owner = AccountID, @Online = ConnectStat
    FROM dbo.Character WITH (UPDLOCK, ROWLOCK)
    WHERE Name = @OldName;

    IF @Owner IS NULL OR @Owner <> @AccountID
    BEGIN
        ROLLBACK TRANSACTION;
        SELECT -10 AS Result, 'This character does not belong to this account.' AS Message;
        RETURN;
    END

    IF @Online <> 0
    BEGIN
        ROLLBACK TRANSACTION;
        SELECT -1 AS Result, 'Log out of the game before changing your nick.' AS Message;
        RETURN;
    END

    -- Case-insensitive duplicate check
    IF EXISTS (SELECT 1 FROM dbo.Character WHERE Name = @NewName)
    BEGIN
        ROLLBACK TRANSACTION;
        SELECT -2 AS Result, 'This name is already in use.' AS Message;
        RETURN;
    END

    -- WCoin billing (EXAMPLE: field in MEMB_INFO)
    IF @WCoinCost > 0
    BEGIN
        SELECT @WCoin = WCoin FROM dbo.MEMB_INFO WHERE memb___id = @AccountID;
        IF @WCoin < @WCoinCost
        BEGIN
            ROLLBACK TRANSACTION;
            SELECT -3 AS Result, 'Insufficient WCoin.' AS Message;
            RETURN;
        END
        UPDATE dbo.MEMB_INFO SET WCoin = WCoin - @WCoinCost WHERE memb___id = @AccountID;
    END

    -- Update ALL references (adjust to your version)
    UPDATE dbo.Character   SET Name  = @NewName WHERE Name = @OldName;
    UPDATE dbo.GuildMember SET Name  = @NewName WHERE Name = @OldName;
    UPDATE dbo.Guild       SET G_Master = @NewName WHERE G_Master = @OldName;
    UPDATE dbo.Friend      SET Name  = @NewName WHERE Name = @OldName;
    UPDATE dbo.Friend      SET FriendName = @NewName WHERE FriendName = @OldName;

    INSERT INTO dbo.NickChangeLog (AccountID, OldName, NewName, ChangeDate)
    VALUES (@AccountID, @OldName, @NewName, GETDATE());

    COMMIT TRANSACTION;

    SELECT 1 AS Result, 'Nick changed successfully.' AS Message;
END
GO

Create the log table:

CREATE TABLE dbo.NickChangeLog (
    ID         INT IDENTITY(1,1) PRIMARY KEY,
    AccountID  VARCHAR(10),
    OldName    VARCHAR(10),
    NewName    VARCHAR(10),
    ChangeDate DATETIME
);

> Some databases define the foreign keys with ON UPDATE CASCADE. If that is the case in your version, updating only Character.Name propagates automatically. Confirm with sp_fkeys 'Character' before deciding whether you need the manual UPDATEs.

Step 4 — Class change stored procedure

The class change is conceptually simpler (it touches one row) but has side effects: stats and equipped items may become incompatible with the new class. The safe approach is to change the Class, reset the base stats, and return the points for redistribution.

USE MuOnline;
GO

IF OBJECT_ID('dbo.WZ_TrocaClasseViaSite', 'P') IS NOT NULL
    DROP PROCEDURE dbo.WZ_TrocaClasseViaSite;
GO

CREATE PROCEDURE dbo.WZ_TrocaClasseViaSite
    @AccountID  VARCHAR(10),
    @CharName   VARCHAR(10),
    @NewClass   TINYINT,      -- EXAMPLE: 0=DW, 16=DK, 32=Elf, 48=MG, 64=DL
    @WCoinCost  INT = 0
AS
BEGIN
    SET NOCOUNT ON;
    SET XACT_ABORT ON;

    DECLARE @Owner VARCHAR(10), @Online TINYINT, @WCoin INT, @Points INT, @Level INT;

    BEGIN TRANSACTION;

    SELECT @Owner = AccountID, @Online = ConnectStat,
           @Level = cLevel
    FROM dbo.Character WITH (UPDLOCK, ROWLOCK)
    WHERE Name = @CharName;

    IF @Owner IS NULL OR @Owner <> @AccountID
    BEGIN
        ROLLBACK TRANSACTION;
        SELECT -10 AS Result, 'This character does not belong to this account.' AS Message;
        RETURN;
    END

    IF @Online <> 0
    BEGIN
        ROLLBACK TRANSACTION;
        SELECT -1 AS Result, 'Log out of the game before changing class.' AS Message;
        RETURN;
    END

    IF @WCoinCost > 0
    BEGIN
        SELECT @WCoin = WCoin FROM dbo.MEMB_INFO WHERE memb___id = @AccountID;
        IF @WCoin < @WCoinCost
        BEGIN
            ROLLBACK TRANSACTION;
            SELECT -3 AS Result, 'Insufficient WCoin.' AS Message;
            RETURN;
        END
        UPDATE dbo.MEMB_INFO SET WCoin = WCoin - @WCoinCost WHERE memb___id = @AccountID;
    END

    -- Approximate total points to redistribute (EXAMPLE)
    SET @Points = 300 + (@Level * 4);

    UPDATE dbo.Character
    SET Class        = @NewClass,
        Strength     = 20,
        Dexterity    = 20,
        Vitality     = 20,
        Energy       = 20,
        Leadership   = CASE WHEN @NewClass = 64 THEN 25 ELSE 0 END,
        LevelUpPoint = @Points
    WHERE Name = @CharName;

    INSERT INTO dbo.ClassChangeLog (AccountID, CharName, NewClass, ChangeDate)
    VALUES (@AccountID, @CharName, @NewClass, GETDATE());

    COMMIT TRANSACTION;

    SELECT 1 AS Result, 'Class changed successfully. Redistribute your points in the game.' AS Message;
END
GO
Atenção: Equipped items incompatible with the new class can crash the client or be "swallowed". The ideal is, within the transaction, to move to the inventory (or unequip) any item the new class cannot use. Since the inventory layout is binary and varies by version, test exhaustively in staging — or require the player to unequip everything before changing.

Step 5 — Unified PHP processor

A single processor handles both changes, always with CSRF, validation, and bound parameters.

<?php
session_start();
require 'db.php';
require 'validacao.php'; // nomeValido() function

if (empty($_SESSION['account_id'])) { http_response_code(403); exit('Not authenticated.'); }
if (!hash_equals($_SESSION['csrf'] ?? '', $_POST['csrf'] ?? '')) {
    http_response_code(400); exit('Invalid token.');
}
unset($_SESSION['csrf']);

$acc  = $_SESSION['account_id'];
$char = trim($_POST['char'] ?? '');
$acao = $_POST['acao'] ?? '';
$pdo  = getDB();

if ($acao === 'nick') {
    $novo = trim($_POST['novo_nome'] ?? '');
    if (!nomeValido($novo)) { exit('Invalid name.'); }
    $stmt = $pdo->prepare('EXEC dbo.WZ_TrocaNickViaSite
        @AccountID=:acc, @OldName=:old, @NewName=:new, @WCoinCost=:wc');
    $stmt->execute([':acc'=>$acc, ':old'=>$char, ':new'=>$novo, ':wc'=>200]);
} elseif ($acao === 'classe') {
    $classe = (int)($_POST['nova_classe'] ?? -1);
    $permitidas = [0, 16, 32, 48, 64]; // EXAMPLE
    if (!in_array($classe, $permitidas, true)) { exit('Invalid class.'); }
    $stmt = $pdo->prepare('EXEC dbo.WZ_TrocaClasseViaSite
        @AccountID=:acc, @CharName=:char, @NewClass=:cls, @WCoinCost=:wc');
    $stmt->execute([':acc'=>$acc, ':char'=>$char, ':cls'=>$classe, ':wc'=>300]);
} else {
    exit('Unknown action.');
}

$res = $stmt->fetch();
echo (($res['Result'] ?? -99) == 1)
    ? 'Success: ' . htmlspecialchars($res['Message'])
    : 'Failure: ' . htmlspecialchars($res['Message'] ?? 'Error.');

Step 6 — Interface in the account panel

Show the account's characters and offer both actions. A small piece of JS adjusts the form based on the chosen action.

document.querySelectorAll('input[name="acao"]').forEach(radio => {
  radio.addEventListener('change', e => {
    document.getElementById('campo-nick').hidden   = e.target.value !== 'nick';
    document.getElementById('campo-classe').hidden = e.target.value !== 'classe';
  });
});

Step 7 — Block the operation while the character is online

As with reset and PK Clear, an online character is the biggest risk: the GameServer overwrites the database when it saves, reverting the change and possibly charging without delivering. The procedures already block it with ConnectStat <> 0. Confirm where that field lives in your version and instruct the player to log out of the game before any change.

Common errors and fixes

ErrorLikely causeFix
Character vanishes from the guild after a nick changeOnly Character.Name was updatedUpdate GuildMember/Guild in the same transaction
Duplicate name acceptedCase-sensitive checkValidate duplicates without distinguishing case
Client crash after a class changeIncompatible equipped itemsUnequip/move items for the new class
Change reverted upon entering the gameCharacter was onlineBlock with ConnectStat <> 0
WCoin debited without the change completingDebit outside the transactionDebit and change in the same transaction
Injection through the new nameNo validation/regexStrict regex + prepared statements

Launch checklist

  • Backup of the MuOnline database taken before testing.
  • All tables that reference the name mapped in your version.
  • Foreign keys checked (sp_fkeys 'Character').
  • Numeric class codes confirmed in your database.
  • Procedures WZ_TrocaNickViaSite and WZ_TrocaClasseViaSite tested in SSMS.
  • Tables NickChangeLog and ClassChangeLog created.
  • Name validation (regex + forbidden words + duplicates) active.
  • Single-use CSRF token in the form and the processor.
  • Change blocking for online characters validated.
  • Handling of incompatible items in the class change tested.
  • WCoin/Zen cost defined and validated inside the transaction.
  • End-to-end test in staging before going public.

Nick and class changes are high-perceived-value services, but they demand extra care because they touch relationships between tables. By keeping all the logic inside atomic transactions in the database, mapping the name's dependencies correctly, and handling incompatible items in the class change, you deliver a reliable, monetizable service without producing broken characters. Always test in staging and adjust the class codes and tables to the specifics of your version.

Frequently asked questions

Does changing the character name break the guild, friends, and items?

It can break if you only update the Character table. The character name is usually referenced in GuildMember, Friend, and other tables. You have to update them all within the same transaction, or use cascading.

Does the player need to be offline to change nick or class?

Yes. With the character online, the GameServer overwrites the database when it saves. Block the operation when ConnectStat is different from 0 and ask for a logout first.

How do I validate a new character name?

Apply a regex that accepts only letters and numbers (typical length up to 10), reject already-existing names with a case-insensitive check, and block forbidden words. Do the duplicate check inside the transaction.

Does changing class change the stats and skills?

Changing Class in the Character table alters the base class, but stats, equipped items, and skills may become incompatible. The safest route is to reset stats and unequip items incompatible with the new class during the change.

How do I find the numeric code for each class?

The Class field is an integer. In Season 6, common base values are 0 (Dark Wizard), 16 (Dark Knight), 32 (Elf), 48 (Magic Gladiator), 64 (Dark Lord). This varies by version, confirm it in your database.

BR
Events, maps & items editor

Bruno specializes in MU Online events, maps, bosses and item economy. He documents every detail based on real gameplay.

Keep reading

Related articles