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.
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
MuOnlinedatabase. - SQL Server (2008/2014/2017/2019) with SSMS and
db_ownerpermission. - A site in PHP 7.4+ connected via PDO (
sqlsrvordblib). - 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:
| Table | Column | Role |
|---|---|---|
Character | Name | Main character record |
Guild | G_Master | Guild master's name |
GuildMember | Name | Guild members |
Friend | Name / FriendName | Friends list |
MEMB_INFO (via AccountCharacter) | — | Character slots per account |
warehouse/Ranking | dependent | Miscellaneous 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
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
| Error | Likely cause | Fix |
|---|---|---|
| Character vanishes from the guild after a nick change | Only Character.Name was updated | Update GuildMember/Guild in the same transaction |
| Duplicate name accepted | Case-sensitive check | Validate duplicates without distinguishing case |
| Client crash after a class change | Incompatible equipped items | Unequip/move items for the new class |
| Change reverted upon entering the game | Character was online | Block with ConnectStat <> 0 |
| WCoin debited without the change completing | Debit outside the transaction | Debit and change in the same transaction |
| Injection through the new name | No validation/regex | Strict regex + prepared statements |
Launch checklist
- Backup of the
MuOnlinedatabase 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_TrocaNickViaSiteandWZ_TrocaClasseViaSitetested in SSMS. - Tables
NickChangeLogandClassChangeLogcreated. - 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.