Como criar um sistema de PK Clear (limpar PK) via site no MU Online
Crie um sistema de PK Clear via site no MU Online para limpar o status de assassino dos personagens, com stored procedure segura, cobrança opcional e proteção contra abuso.
No MU Online, matar outros jogadores fora de eventos transforma seu personagem em um assassino (PK — Player Killer). Esse status traz penalidades desagradáveis: chance de dropar itens ao morrer, bloqueio de acesso a NPCs em cidades e, dependendo do grau, guardas que atacam automaticamente. Tradicion
No MU Online, matar outros jogadores fora de eventos transforma seu personagem em um assassino (PK — Player Killer). Esse status traz penalidades desagradáveis: chance de dropar itens ao morrer, bloqueio de acesso a NPCs em cidades e, dependendo do grau, guardas que atacam automaticamente. Tradicionalmente o jogador precisa esperar o tempo de penalidade passar ou pagar um NPC específico dentro do jogo para limpar o PK. Oferecer o PK Clear via site é uma comodidade valorizada — e uma fonte de monetização — que permite ao jogador limpar o status pelo painel de conta em segundos.
Este tutorial mostra como construir esse sistema de forma segura, com a lógica dentro de uma stored procedure no SQL Server e uma camada PHP enxuta. Assim como no reset, a chave está em validar tudo no banco, tratar cada dado do formulário como hostil e impedir que a operação rode com o personagem online. Os nomes de campos apresentados são um EXEMPLO de Season 6; a estrutura exata varia por versão — confirme sempre o esquema do seu banco antes de aplicar. Se você ainda não montou a base do servidor e do site, veja primeiro como criar servidor de MU Online.
Pré-requisitos
- Servidor de MU Online funcional com GameServer e banco
MuOnlineacessível. - SQL Server (2008/2014/2017/2019) com SSMS e permissão
db_owner. - Site em PHP 7.4+ conectado ao banco via PDO (driver
sqlsrvoudblib). - Sistema de login/sessão do painel de conta funcionando.
- Backup recente do banco antes de qualquer teste de UPDATE.
Passo 1 — Entender o status de PK no banco
O status de assassino é armazenado em colunas da tabela Character. Antes de escrever qualquer código, descubra os nomes exatos na sua versão:
SELECT COLUMN_NAME, DATA_TYPE
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'Character'
AND (COLUMN_NAME LIKE '%Pk%' OR COLUMN_NAME LIKE '%Kill%');
Num EXEMPLO típico de Season 6, os campos relevantes são:
| Campo | Função | Valor "limpo" |
|---|---|---|
PkLevel | Grau de penalidade | 3 (neutro) na maioria das builds |
PkCount | Número de kills acumuladas | 0 |
PkTime | Tempo de penalidade restante | 0 |
ConnectStat | Status online (0 = offline) | usado para bloqueio |
Atenção ao valor "neutro" de PkLevel: em muitos emuladores de Season 6, 3 significa personagem comum/sem PK, enquanto valores maiores indicam graus crescentes de assassino. Em outras versões o neutro é 0. Isso varia por versão — teste com um personagem de homologação antes de definir a constante.
Passo 2 — Criar a stored procedure de PK Clear
Toda a regra de negócio (propriedade do personagem, status online, cobrança, limite) fica na procedure, dentro de uma transação. O PHP só chama e interpreta o retorno.
USE MuOnline;
GO
IF OBJECT_ID('dbo.WZ_PkClearViaSite', 'P') IS NOT NULL
DROP PROCEDURE dbo.WZ_PkClearViaSite;
GO
CREATE PROCEDURE dbo.WZ_PkClearViaSite
@AccountID VARCHAR(10),
@CharName VARCHAR(10),
@NeutralPk INT = 3, -- EXEMPLO: valor neutro varia por versao
@ZenCost BIGINT = 0
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
DECLARE @Owner VARCHAR(10), @Online TINYINT, @PkLevel INT, @Money BIGINT;
BEGIN TRANSACTION;
SELECT @Owner = AccountID, @Online = ConnectStat,
@PkLevel = PkLevel, @Money = Money
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 limpar o PK.' AS Message;
RETURN;
END
-- Se ja esta neutro, nao cobra nem altera
IF @PkLevel <= @NeutralPk
BEGIN
ROLLBACK TRANSACTION;
SELECT -2 AS Result, 'Este personagem nao esta com PK.' AS Message;
RETURN;
END
IF @ZenCost > 0 AND @Money < @ZenCost
BEGIN
ROLLBACK TRANSACTION;
SELECT -3 AS Result, 'Zen insuficiente.' AS Message;
RETURN;
END
-- Limpa o status de PK
UPDATE dbo.Character
SET PkLevel = @NeutralPk,
PkCount = 0,
PkTime = 0,
Money = Money - @ZenCost
WHERE Name = @CharName;
INSERT INTO dbo.PkClearLog (AccountID, CharName, PkLevelAntes, ClearDate, Origem)
VALUES (@AccountID, @CharName, @PkLevel, GETDATE(), 'SITE');
COMMIT TRANSACTION;
SELECT 1 AS Result, 'PK limpo com sucesso.' AS Message;
END
GO
E a tabela de log:
CREATE TABLE dbo.PkClearLog (
ID INT IDENTITY(1,1) PRIMARY KEY,
AccountID VARCHAR(10),
CharName VARCHAR(10),
PkLevelAntes INT,
ClearDate DATETIME,
Origem VARCHAR(10)
);
> O log não é opcional. PK Clear barato e sem registro incentiva "ganking" — jogadores que matam outros à vontade porque sabem que limpam a penalidade em um clique. O log permite detectar contas que limpam PK repetidamente.
Passo 3 — Conexão PDO reutilizável
Mantenha a conexão num arquivo único, com credenciais fora do controle de versão.
<?php
// db.php
function getDB(): PDO {
$dsn = 'sqlsrv:Server=' . (getenv('MU_DB_HOST') ?: '127.0.0.1') . ';Database=MuOnline';
return new PDO($dsn, getenv('MU_DB_USER'), getenv('MU_DB_PASS'), [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]);
}
Passo 4 — Formulário de PK Clear no painel
Liste apenas os personagens da conta logada, marcando quais estão com PK. Isso melhora a experiência (o jogador vê onde precisa agir) e evita cliques inúteis.
<?php
session_start();
require 'db.php';
if (empty($_SESSION['account_id'])) { header('Location: /login.php'); exit; }
if (empty($_SESSION['csrf'])) { $_SESSION['csrf'] = bin2hex(random_bytes(32)); }
$pdo = getDB();
$stmt = $pdo->prepare(
'SELECT Name, PkLevel, PkCount FROM Character WHERE AccountID = ? ORDER BY Name'
);
$stmt->execute([$_SESSION['account_id']]);
$chars = $stmt->fetchAll();
$NEUTRO = 3; // EXEMPLO: ajuste ao valor neutro da sua versao
?>
<form method="post" action="/pkclear_processar.php">
<input type="hidden" name="csrf" value="<?= htmlspecialchars($_SESSION['csrf']) ?>">
<select name="char" required>
<?php foreach ($chars as $c):
$temPk = ((int)$c['PkLevel'] > $NEUTRO); ?>
<option value="<?= htmlspecialchars($c['Name']) ?>" <?= $temPk ? '' : 'disabled' ?>>
<?= htmlspecialchars($c['Name']) ?>
<?= $temPk ? '— PK nível ' . (int)$c['PkLevel'] . ' (' . (int)$c['PkCount'] . ' kills)' : '— sem PK' ?>
</option>
<?php endforeach; ?>
</select>
<button type="submit">Limpar PK</button>
</form>
Passo 5 — Processar a limpeza com segurança
Valide o token CSRF, chame a procedure com parâmetros vinculados e trate o retorno. Nenhuma regra de negócio é decidida no PHP.
<?php
session_start();
require 'db.php';
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. Recarregue a pagina.');
}
unset($_SESSION['csrf']);
$char = trim($_POST['char'] ?? '');
if ($char === '' || strlen($char) > 10) { exit('Personagem invalido.'); }
$pdo = getDB();
$stmt = $pdo->prepare(
'EXEC dbo.WZ_PkClearViaSite @AccountID = :acc, @CharName = :char,
@NeutralPk = :neutro, @ZenCost = :zen'
);
$stmt->execute([
':acc' => $_SESSION['account_id'],
':char' => $char,
':neutro' => 3, // EXEMPLO
':zen' => 3000000, // EXEMPLO de custo
]);
$res = $stmt->fetch();
echo (($res['Result'] ?? -99) == 1)
? 'Sucesso: ' . htmlspecialchars($res['Message'])
: 'Falha: ' . htmlspecialchars($res['Message'] ?? 'Erro desconhecido.');
Passo 6 — Impedir PK Clear com personagem online
Este é o mesmo risco do reset via site: enquanto o personagem está no jogo, o GameServer guarda os dados em memória e sobrescreve o banco no próximo save. Se você limpar o PK pelo site com o char online, a limpeza é revertida — e o jogador pode ter pago por nada. A procedure já bloqueia com ConnectStat <> 0, mas confirme onde esse campo vive na sua versão: pode estar em Character ou em MEMB_STAT (ligado à conta). Ajuste a consulta conforme necessário e, idealmente, oriente o jogador a sair do jogo antes de limpar.
Passo 7 — Cooldown e proteção contra abuso
Um PK Clear muito acessível quebra a mecânica de risco do PvP. Recomendações:
- Preço significativo: o custo em Zen ou WCoin deve doer o suficiente para que matar players tenha consequência real.
- Cooldown por personagem: registre um
LastPkCleare bloqueie nova limpeza antes de X minutos. - Rate limiting por conta: evita scripts que limpam PK em loop.
- Auditoria da
PkClearLog: monitore contas com muitas limpezas seguidas.
-- Detectar contas que limparam PK mais de 5 vezes nas ultimas 24h
SELECT AccountID, COUNT(*) AS Limpezas
FROM dbo.PkClearLog
WHERE ClearDate > DATEADD(HOUR, -24, GETDATE())
GROUP BY AccountID
HAVING COUNT(*) > 5
ORDER BY Limpezas DESC;
Erros comuns e soluções
| Erro | Causa provável | Solução |
|---|---|---|
| PK volta após entrar no jogo | Personagem estava online | Bloquear com ConnectStat <> 0; pedir logout |
| PK Clear não muda nada visível | Valor neutro errado (0 x 3) | Confirmar o neutro da versão com teste |
| Cobrança feita mas PK não limpo | Débito em query separada | Débito e limpeza na mesma transação |
| Personagem de outra conta limpo | Não valida propriedade | Comparar AccountID na procedure |
| Erro de conversão no PHP | Tipo de parâmetro incompatível | Alinhar tipos PDO x procedure |
| Injeção pelo nome do char | Concatenação na query | Sempre prepared statements |
Checklist de lançamento
- Backup do banco
MuOnlinefeito antes dos testes. - Campos de PK (
PkLevel,PkCount,PkTime) confirmados na sua versão. - Valor neutro de
PkLevelvalidado com personagem de teste. - Stored procedure
WZ_PkClearViaSitecriada e testada no SSMS. - Tabela
PkClearLogcriada e recebendo registros. - Conexão PDO com prepared statements, sem concatenação.
- Token CSRF de uso único no formulário e no processador.
- Bloqueio de limpeza para personagem online validado.
- Validação de propriedade (AccountID) em duas camadas.
- Custo e cooldown calibrados para não quebrar o PvP.
- Teste ponta a ponta em homologação antes de abrir ao público.
Com esse sistema, o PK Clear vira um serviço rápido e lucrativo sem comprometer o equilíbrio do servidor. A lógica sensível fica protegida no banco, cada operação é atômica e registrada, e a camada PHP permanece pequena e difícil de explorar. Ajuste os valores neutros, o custo e o cooldown ao perfil do seu servidor — e sempre teste em homologação antes de subir para produção.
Perguntas frequentes
O que exatamente o PK Clear remove do personagem?
Ele zera o status de assassino (PK): o nível de PK, o contador de kills e o tempo de penalidade. Os campos variam por versão, mas geralmente são PkLevel, PkCount e PkTime na tabela Character.
O jogador precisa estar offline para o PK Clear via site funcionar?
Sim, na maioria das versões. Se o personagem estiver online, o GameServer sobrescreve o banco ao salvar e o PK volta. Bloqueie a operação quando ConnectStat for diferente de 0.
Qual a diferença entre PkLevel e PkCount?
PkLevel é o grau de penalidade (comum, assassino, herói), que afeta drop ao morrer e bloqueio em cidades. PkCount é o número de mortes de jogadores acumuladas. O PK Clear normalmente zera ambos e o PkTime.
Posso cobrar WCoin ou Zen pelo PK Clear?
Sim. Faça o débito dentro da mesma stored procedure e da mesma transação que limpa o PK, garantindo que a cobrança só ocorra se a limpeza for concluída.
Como impedir que jogadores abusem do PK Clear para farmar players sem punição?
Cobre um valor significativo, aplique cooldown por personagem e registre cada limpeza numa tabela de log para auditar padrões suspeitos.