El mayor portal de MU Online de Brasil — desde 2003
Tutorial Intermedio Web

Cómo crear cambio de nick y clase vía web en MU Online

Implementa el cambio de nick y de clase vía web en MU Online con validación de nombre, actualización segura de claves foráneas y cobro opcional en WCoin.

BR Bruno · Actualizado el 5 oct 2025 · ⏱ 20 min de lectura
Respuesta rápida

Cambiar el nombre (nick) o la clase de un personaje son dos de los servicios de pago más solicitados en servidores privados de MU Online. Un jugador quiere renombrar el personaje que creó con prisas; otro se cansó del Dark Wizard y quiere convertirse en Dark Knight sin empezar de cero. Ofrecer estos

Cambiar el nombre (nick) o la clase de un personaje son dos de los servicios de pago más solicitados en servidores privados de MU Online. Un jugador quiere renombrar el personaje que creó con prisas; otro se cansó del Dark Wizard y quiere convertirse en Dark Knight sin empezar de cero. Ofrecer estos cambios desde la web es cómodo y rentable, pero también es una de las operaciones más peligrosas de implementar. A diferencia del reset y del PK Clear, que tocan pocas columnas de una fila, el cambio de nick toca relaciones: guild, lista de amigos, mensajes, rankings. Hacerlo mal genera personajes huérfanos de guild, ítems perdidos e inconsistencias difíciles de rastrear.

Este tutorial muestra cómo construir el cambio de nick y de clase vía web de forma segura y atómica, usando stored procedures en SQL Server y una capa PHP escueta. Los nombres de campos y tablas son un EJEMPLO de Season 6; la estructura exacta varía según la versión y el emulador. Mapea siempre las dependencias del nombre en tu base de datos antes de aplicar. Si aún no tienes servidor y web funcionando, empieza por cómo crear servidor de MU Online.

Requisitos previos

  • Servidor de MU Online funcional con base de datos MuOnline accesible.
  • SQL Server (2008/2014/2017/2019) con SSMS y permiso db_owner.
  • Web en PHP 7.4+ conectada vía PDO (sqlsrv o dblib).
  • Panel de cuenta con login/sesión funcionando.
  • Copia de seguridad reciente y entorno de homologación para pruebas.

Paso 1 — Mapear todas las dependencias del nombre del personaje

Antes de cambiar un solo carácter, descubre dónde está referenciado el nombre del personaje. El nombre suele ser la clave lógica usada en varias tablas. Ejecuta:

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;

En un EJEMPLO común de Season 6, el nombre aparece en:

TablaColumnaRol
CharacterNameRegistro principal del personaje
GuildG_MasterNombre del máster de la guild
GuildMemberNameMiembros de la guild
FriendName / FriendNameLista de amigos
MEMB_INFO (vía AccountCharacter)Slots de personaje por cuenta
warehouse/RankingdependienteReferencias diversas

Ignorar cualquiera de ellas genera inconsistencia: por ejemplo, renombrar en Character pero no en GuildMember hace que el personaje "desaparezca" de la guild. La lista varía según la versión — mapea la tuya con la query de arriba.

Paso 2 — Reglas de validación del nuevo nombre

Un nombre inválido puede colgar el cliente del juego o abrir espacio para exploits. Aplica validación rigurosa tanto en PHP como en el procedure:

  • Solo letras y números; sin espacios, acentos ni símbolos.
  • Longitud típica de 1 a 10 caracteres (varía según la versión).
  • Verificación de duplicidad case-insensitive (evita que coexistan "Player" y "player").
  • Lista de palabras prohibidas (nombres reservados, ofensas, "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);
}

Paso 3 — Stored procedure de cambio de nick

El cambio de nombre debe ser atómico: o se actualizan todas las tablas, o ninguna. Una transacción con XACT_ABORT ON lo garantiza. El procedure también valida propiedad, estado online, duplicidad y cobro.

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, 'El personaje no pertenece a esta cuenta.' AS Message;
        RETURN;
    END

    IF @Online <> 0
    BEGIN
        ROLLBACK TRANSACTION;
        SELECT -1 AS Result, 'Sal del juego antes de cambiar el nick.' AS Message;
        RETURN;
    END

    -- Duplicidad case-insensitive
    IF EXISTS (SELECT 1 FROM dbo.Character WHERE Name = @NewName)
    BEGIN
        ROLLBACK TRANSACTION;
        SELECT -2 AS Result, 'Este nombre ya esta en uso.' AS Message;
        RETURN;
    END

    -- Cobro en WCoin (EJEMPLO: campo en 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, 'WCoin insuficiente.' AS Message;
            RETURN;
        END
        UPDATE dbo.MEMB_INFO SET WCoin = WCoin - @WCoinCost WHERE memb___id = @AccountID;
    END

    -- Actualiza TODAS las referencias (ajusta segun tu 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 cambiado con exito.' AS Message;
END
GO

Crea la tabla de log:

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

> Algunas bases de datos definen las claves foráneas con ON UPDATE CASCADE. Si es el caso de tu versión, actualizar solo Character.Name propaga automáticamente. Confirma con sp_fkeys 'Character' antes de decidir si necesitas los UPDATEs manuales.

Paso 4 — Stored procedure de cambio de clase

El cambio de clase es conceptualmente más simple (toca una fila) pero tiene efectos colaterales: los atributos e ítems equipados pueden quedar incompatibles con la nueva clase. El enfoque seguro es cambiar el Class, resetear los atributos base y devolver los puntos para redistribución.

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,      -- EJEMPLO: 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, 'El personaje no pertenece a esta cuenta.' AS Message;
        RETURN;
    END

    IF @Online <> 0
    BEGIN
        ROLLBACK TRANSACTION;
        SELECT -1 AS Result, 'Sal del juego antes de cambiar de clase.' 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, 'WCoin insuficiente.' AS Message;
            RETURN;
        END
        UPDATE dbo.MEMB_INFO SET WCoin = WCoin - @WCoinCost WHERE memb___id = @AccountID;
    END

    -- Puntos totales aproximados para redistribuir (EJEMPLO)
    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, 'Clase cambiada con exito. Redistribuye tus puntos en el juego.' AS Message;
END
GO
Atenção: Los ítems equipados incompatibles con la nueva clase pueden causar crash del cliente o ser "tragados". Lo ideal es, dentro de la transacción, mover al inventario (o desequipar) cualquier ítem que la nueva clase no pueda usar. Como el layout del inventario es binario y varía según la versión, prueba exhaustivamente en homologación — o exige que el jugador desequipe todo antes de cambiar.

Paso 5 — Procesador PHP unificado

Un único procesador atiende ambos cambios, siempre con CSRF, validación y parámetros vinculados.

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

if (empty($_SESSION['account_id'])) { http_response_code(403); exit('No autenticado.'); }
if (!hash_equals($_SESSION['csrf'] ?? '', $_POST['csrf'] ?? '')) {
    http_response_code(400); exit('Token invalido.');
}
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('Nombre invalido.'); }
    $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]; // EJEMPLO
    if (!in_array($classe, $permitidas, true)) { exit('Clase invalida.'); }
    $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('Accion desconocida.');
}

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

Paso 6 — Interfaz en el panel de cuenta

Muestra los personajes de la cuenta y ofrece las dos acciones. Un pequeño JS ajusta el formulario según la acción elegida.

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';
  });
});

Paso 7 — Bloquear la operación con el personaje online

Como en el reset y el PK Clear, el personaje online es el mayor riesgo: el GameServer sobrescribe la base de datos al guardar, revirtiendo el cambio y pudiendo cobrar sin entregar. Los procedures ya bloquean con ConnectStat <> 0. Confirma dónde reside ese campo en tu versión y orienta al jugador a salir del juego antes de cualquier cambio.

Errores comunes y soluciones

ErrorCausa probableSolución
El personaje desaparece de la guild tras cambiar el nickSolo se actualizó Character.NameActualizar GuildMember/Guild en la misma transacción
Nombre duplicado aceptadoComprobación case-sensitiveValidar la duplicidad sin diferenciar mayúsculas
Crash del cliente tras cambiar de claseÍtems equipados incompatiblesDesequipar/mover los ítems de la nueva clase
Cambio revertido al entrar al juegoEl personaje estaba onlineBloquear con ConnectStat <> 0
WCoin debitado sin cambio completadoDébito fuera de la transacciónDébito y cambio en la misma transacción
Inyección por el nombre nuevoSin validación/regexRegex estricto + prepared statements

Lista de verificación de lanzamiento

  • Copia de seguridad de la base de datos MuOnline hecha antes de las pruebas.
  • Todas las tablas que referencian el nombre mapeadas en tu versión.
  • Claves foráneas verificadas (sp_fkeys 'Character').
  • Códigos numéricos de clase confirmados en tu base de datos.
  • Procedures WZ_TrocaNickViaSite y WZ_TrocaClasseViaSite probados en SSMS.
  • Tablas NickChangeLog y ClassChangeLog creadas.
  • Validación de nombre (regex + palabras prohibidas + duplicidad) activa.
  • Token CSRF de un solo uso en el formulario y en el procesador.
  • Bloqueo de cambio para personaje online validado.
  • Tratamiento de ítems incompatibles en el cambio de clase probado.
  • Costo en WCoin/Zen definido y validado dentro de la transacción.
  • Prueba de punta a punta en homologación antes de abrir al público.

El cambio de nick y de clase son servicios de alto valor percibido, pero exigen un cuidado redoblado por tocar relaciones entre tablas. Manteniendo toda la lógica dentro de transacciones atómicas en la base de datos, mapeando correctamente las dependencias del nombre y tratando los ítems incompatibles en el cambio de clase, entregas un servicio confiable y monetizable sin generar personajes rotos. Prueba siempre en homologación y ajusta los códigos de clase y las tablas a las particularidades de tu versión.

Preguntas frecuentes

¿Cambiar el nombre del personaje rompe la guild, los amigos y los ítems?

Puede romperse si actualizas solo la tabla Character. El nombre del personaje suele estar referenciado en GuildMember, Friend y otras tablas. Hay que actualizarlas todas dentro de la misma transacción, o usar cascada.

¿El jugador necesita estar offline para cambiar el nick o la clase?

Sí. Con el personaje online, el GameServer sobrescribe la base de datos al guardar. Bloquea la operación cuando ConnectStat sea distinto de 0 y pide el logout antes.

¿Cómo validar un nuevo nombre de personaje?

Aplica un regex que acepte solo letras y números (longitud típica hasta 10), rechaza nombres ya existentes con verificación case-insensitive y bloquea palabras prohibidas. Haz la comprobación de duplicidad dentro de la transacción.

¿Cambiar de clase modifica los atributos y las habilidades?

El cambio de Class en la tabla Character altera la clase base, pero los atributos, ítems equipados y skills pueden quedar incompatibles. Lo más seguro es resetear stats y desequipar los ítems que la nueva clase no puede usar durante el cambio.

¿Cómo saber el código numérico de cada clase?

El campo Class es un entero. En Season 6, los valores base comunes son 0 (Dark Wizard), 16 (Dark Knight), 32 (Elf), 48 (Magic Gladiator), 64 (Dark Lord). Esto varía según la versión, confírmalo en tu base de datos.

BR
Editor de eventos, mapas e ítems

Bruno es especialista en eventos, mapas, bosses y economía de ítems de MU Online. Documenta cada detalle basándose en el juego real.

Sigue leyendo

Artículos relacionados