Como criar troca de nick e classe via site no MU Online
Implemente troca de nick e mudança de classe via site no MU Online com validação de nome, atualização segura de chaves estrangeiras e cobrança opcional em WCoin.
Trocar o nome (nick) ou a classe de um personagem são dois dos serviços pagos mais procurados em servidores privados de MU Online. Um jogador quer renomear o personagem que criou às pressas; outro se cansou do Dark Wizard e quer virar Dark Knight sem recomeçar do zero. Oferecer essas trocas pelo sit
Trocar o nome (nick) ou a classe de um personagem são dois dos serviços pagos mais procurados em servidores privados de MU Online. Um jogador quer renomear o personagem que criou às pressas; outro se cansou do Dark Wizard e quer virar Dark Knight sem recomeçar do zero. Oferecer essas trocas pelo site é conveniente e rentável — mas também é uma das operações mais perigosas de implementar. Diferente de reset e PK Clear, que mexem em poucas colunas de uma linha, a troca de nick toca em relacionamentos: guild, lista de amigos, mensagens, rankings. Fazer errado gera personagens órfãos de guild, itens perdidos e inconsistências difíceis de rastrear.
Este tutorial mostra como construir troca de nick e troca de classe via site de forma segura e atômica, usando stored procedures no SQL Server e uma camada PHP enxuta. Os nomes de campos e tabelas são um EXEMPLO de Season 6; a estrutura exata varia por versão e por emulador. Sempre mapeie as dependências do nome no seu banco antes de aplicar. Se você ainda não tem servidor e site rodando, comece por como criar servidor de MU Online.
Pré-requisitos
- Servidor de MU Online funcional com banco
MuOnlineacessível. - SQL Server (2008/2014/2017/2019) com SSMS e permissão
db_owner. - Site em PHP 7.4+ conectado via PDO (
sqlsrvoudblib). - Painel de conta com login/sessão funcionando.
- Backup recente e ambiente de homologação para testes.
Passo 1 — Mapear todas as dependências do nome do personagem
Antes de trocar um único caractere, descubra onde o nome do personagem é referenciado. O nome geralmente é a chave lógica usada em várias tabelas. Rode:
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;
Num EXEMPLO comum de Season 6, o nome aparece em:
| Tabela | Coluna | Papel |
|---|---|---|
Character | Name | Registro principal do personagem |
Guild | G_Master | Nome do mestre da guild |
GuildMember | Name | Membros da guild |
Friend | Name / FriendName | Lista de amigos |
MEMB_INFO (via AccountCharacter) | — | Slots de personagem por conta |
warehouse/Ranking | dependente | Referências diversas |
Ignorar qualquer uma dessas gera inconsistência: por exemplo, renomear em Character mas não em GuildMember faz o personagem "sumir" da guild. A lista varia por versão — mapeie a sua com a query acima.
Passo 2 — Regras de validação do novo nome
Um nome inválido pode travar o cliente do jogo ou abrir espaço para exploits. Aplique validação rigorosa tanto no PHP quanto na procedure:
- Apenas letras e números; sem espaços, acentos ou símbolos.
- Comprimento típico de 1 a 10 caracteres (varia por versão).
- Verificação de duplicidade case-insensitive (evita "Player" e "player" coexistindo).
- Lista de palavras proibidas (nomes 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);
}
Passo 3 — Stored procedure de troca de nick
A troca de nome precisa ser atômica: ou todas as tabelas são atualizadas, ou nenhuma. Uma transação com XACT_ABORT ON garante isso. A procedure também valida propriedade, status online, duplicidade e cobrança.
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, 'Personagem nao pertence a esta conta.' AS Message;
RETURN;
END
IF @Online <> 0
BEGIN
ROLLBACK TRANSACTION;
SELECT -1 AS Result, 'Saia do jogo antes de trocar o nick.' AS Message;
RETURN;
END
-- Duplicidade case-insensitive
IF EXISTS (SELECT 1 FROM dbo.Character WHERE Name = @NewName)
BEGIN
ROLLBACK TRANSACTION;
SELECT -2 AS Result, 'Este nome ja esta em uso.' AS Message;
RETURN;
END
-- Cobranca em WCoin (EXEMPLO: campo em 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
-- Atualiza TODAS as referencias (ajuste conforme sua versao)
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 trocado com sucesso.' AS Message;
END
GO
Crie a tabela de log:
CREATE TABLE dbo.NickChangeLog (
ID INT IDENTITY(1,1) PRIMARY KEY,
AccountID VARCHAR(10),
OldName VARCHAR(10),
NewName VARCHAR(10),
ChangeDate DATETIME
);
> Alguns bancos definem as chaves estrangeiras com ON UPDATE CASCADE. Se for o caso da sua versão, atualizar apenas Character.Name propaga automaticamente. Confirme com sp_fkeys 'Character' antes de decidir se precisa dos UPDATEs manuais.
Passo 4 — Stored procedure de troca de classe
A troca de classe é conceitualmente mais simples (mexe em uma linha) porém tem efeitos colaterais: atributos e itens equipados podem ficar incompatíveis com a nova classe. A abordagem segura é trocar o Class, resetar os atributos base e devolver os pontos para redistribuição.
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, -- EXEMPLO: 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, 'Personagem nao pertence a esta conta.' AS Message;
RETURN;
END
IF @Online <> 0
BEGIN
ROLLBACK TRANSACTION;
SELECT -1 AS Result, 'Saia do jogo antes de trocar de classe.' 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
-- Pontos totais aproximados para redistribuir (EXEMPLO)
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, 'Classe trocada com sucesso. Redistribua seus pontos no jogo.' AS Message;
END
GO
Passo 5 — Processador PHP unificado
Um único processador atende às duas trocas, sempre com CSRF, validação e parâmetros vinculados.
<?php
session_start();
require 'db.php';
require 'validacao.php'; // funcao nomeValido()
if (empty($_SESSION['account_id'])) { http_response_code(403); exit('Nao 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('Nome 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]; // EXEMPLO
if (!in_array($classe, $permitidas, true)) { exit('Classe 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('Acao desconhecida.');
}
$res = $stmt->fetch();
echo (($res['Result'] ?? -99) == 1)
? 'Sucesso: ' . htmlspecialchars($res['Message'])
: 'Falha: ' . htmlspecialchars($res['Message'] ?? 'Erro.');
Passo 6 — Interface no painel de conta
Exiba os personagens da conta e ofereça as duas ações. Um pequeno JS ajusta o formulário conforme a ação escolhida.
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';
});
});
Passo 7 — Bloquear operação com personagem online
Como em reset e PK Clear, o personagem online é o maior risco: o GameServer sobrescreve o banco ao salvar, revertendo a troca e podendo cobrar sem entregar. As procedures já bloqueiam com ConnectStat <> 0. Confirme onde esse campo mora na sua versão e oriente o jogador a sair do jogo antes de qualquer troca.
Erros comuns e soluções
| Erro | Causa provável | Solução |
|---|---|---|
| Personagem some da guild após trocar nick | Só atualizou Character.Name | Atualizar GuildMember/Guild na mesma transação |
| Nome duplicado aceito | Checagem case-sensitive | Validar duplicidade sem diferenciar maiúsculas |
| Crash do cliente após trocar classe | Itens equipados incompatíveis | Desequipar/mover itens da nova classe |
| Troca revertida ao entrar no jogo | Personagem estava online | Bloquear com ConnectStat <> 0 |
| WCoin debitado sem troca concluída | Débito fora da transação | Débito e troca na mesma transação |
| Injeção pelo nome novo | Sem validação/regex | Regex estrito + prepared statements |
Checklist de lançamento
- Backup do banco
MuOnlinefeito antes dos testes. - Todas as tabelas que referenciam o nome mapeadas na sua versão.
- Chaves estrangeiras verificadas (
sp_fkeys 'Character'). - Códigos numéricos de classe confirmados no seu banco.
- Procedures
WZ_TrocaNickViaSiteeWZ_TrocaClasseViaSitetestadas no SSMS. - Tabelas
NickChangeLogeClassChangeLogcriadas. - Validação de nome (regex + palavras proibidas + duplicidade) ativa.
- Token CSRF de uso único no formulário e no processador.
- Bloqueio de troca para personagem online validado.
- Tratamento de itens incompatíveis na troca de classe testado.
- Custo em WCoin/Zen definido e validado dentro da transação.
- Teste ponta a ponta em homologação antes de abrir ao público.
Troca de nick e classe são serviços de alto valor percebido, mas exigem cuidado redobrado por tocarem em relacionamentos entre tabelas. Mantendo toda a lógica dentro de transações atômicas no banco, mapeando corretamente as dependências do nome e tratando os itens incompatíveis na troca de classe, você entrega um serviço confiável e monetizável sem gerar personagens quebrados. Teste sempre em homologação e ajuste os códigos de classe e as tabelas às particularidades da sua versão.
Perguntas frequentes
Trocar o nome do personagem quebra guild, amigos e itens?
Pode quebrar se você atualizar só a tabela Character. O nome do personagem costuma ser referenciado em GuildMember, Friend e outras tabelas. É preciso atualizar todas dentro da mesma transação, ou usar cascata.
O jogador precisa estar offline para trocar nick ou classe?
Sim. Com o personagem online, o GameServer sobrescreve o banco ao salvar. Bloqueie a operação quando ConnectStat for diferente de 0 e peça o logout antes.
Como validar um novo nome de personagem?
Aplique um regex que aceite apenas letras e números (comprimento típico até 10), rejeite nomes já existentes com verificação case-insensitive e bloqueie palavras proibidas. Faça a checagem de duplicidade dentro da transação.
Trocar de classe muda os atributos e habilidades?
A mudança de Class na tabela Character altera a classe base, mas atributos, itens equipados e skills podem ficar incompatíveis. O mais seguro é resetar stats e desequipar itens da nova classe incompatível durante a troca.
Como saber o código numérico de cada classe?
O campo Class é um inteiro. Em Season 6, valores base comuns são 0 (Dark Wizard), 16 (Dark Knight), 32 (Elf), 48 (Magic Gladiator), 64 (Dark Lord). Isso varia por versão, confirme no seu banco.