Cómo crear un sistema de PK Clear (limpiar PK) vía sitio en MU Online
Crea un sistema de PK Clear vía sitio en MU Online para limpiar el estado de asesino de los personajes, con stored procedure segura, cobro opcional y protección contra abuso.
En MU Online, matar a otros jugadores fuera de eventos convierte a tu personaje en un asesino (PK — Player Killer). Ese estado trae penalidades desagradables: probabilidad de dropear ítems al morir, bloqueo de acceso a NPCs en ciudades y, dependiendo del grado, guardias que atacan automáticamente. T
En MU Online, matar a otros jugadores fuera de eventos convierte a tu personaje en un asesino (PK — Player Killer). Ese estado trae penalidades desagradables: probabilidad de dropear ítems al morir, bloqueo de acceso a NPCs en ciudades y, dependiendo del grado, guardias que atacan automáticamente. Tradicionalmente el jugador necesita esperar a que pase el tiempo de penalidad o pagar a un NPC específico dentro del juego para limpiar el PK. Ofrecer el PK Clear vía sitio es una comodidad valorada —y una fuente de monetización— que permite al jugador limpiar el estado desde el panel de cuenta en segundos.
Este tutorial muestra cómo construir ese sistema de forma segura, con la lógica dentro de un stored procedure en SQL Server y una capa PHP mínima. Al igual que en el reset, la clave está en validar todo en la base de datos, tratar cada dato del formulario como hostil e impedir que la operación se ejecute con el personaje en línea. Los nombres de campos presentados son un EJEMPLO de Season 6; la estructura exacta varía por versión — confirma siempre el esquema de tu base de datos antes de aplicar. Si aún no montaste la base del servidor y del sitio, mira primero cómo crear servidor de MU Online.
Requisitos previos
- Servidor de MU Online funcional con GameServer y base de datos
MuOnlineaccesible. - SQL Server (2008/2014/2017/2019) con SSMS y permiso
db_owner. - Sitio en PHP 7.4+ conectado a la base de datos vía PDO (driver
sqlsrvodblib). - Sistema de login/sesión del panel de cuenta funcionando.
- Backup reciente de la base de datos antes de cualquier prueba de UPDATE.
Paso 1 — Entender el estado de PK en la base de datos
El estado de asesino se almacena en columnas de la tabla Character. Antes de escribir cualquier código, descubre los nombres exactos en tu versión:
SELECT COLUMN_NAME, DATA_TYPE
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'Character'
AND (COLUMN_NAME LIKE '%Pk%' OR COLUMN_NAME LIKE '%Kill%');
En un EJEMPLO típico de Season 6, los campos relevantes son:
| Campo | Función | Valor "limpio" |
|---|---|---|
PkLevel | Grado de penalidad | 3 (neutro) en la mayoría de las builds |
PkCount | Número de kills acumuladas | 0 |
PkTime | Tiempo de penalidad restante | 0 |
ConnectStat | Estado en línea (0 = offline) | usado para el bloqueo |
Atención al valor "neutro" de PkLevel: en muchos emuladores de Season 6, 3 significa personaje común/sin PK, mientras que valores mayores indican grados crecientes de asesino. En otras versiones el neutro es 0. Esto varía por versión — prueba con un personaje de homologación antes de definir la constante.
Paso 2 — Crear la stored procedure de PK Clear
Toda la regla de negocio (propiedad del personaje, estado en línea, cobro, límite) queda en la procedure, dentro de una transacción. El PHP solo la llama e interpreta el 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, -- EJEMPLO: el valor neutro varia por version
@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, '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 limpiar el PK.' AS Message;
RETURN;
END
-- Si ya esta neutro, no cobra ni altera
IF @PkLevel <= @NeutralPk
BEGIN
ROLLBACK TRANSACTION;
SELECT -2 AS Result, 'Este personaje no tiene PK.' AS Message;
RETURN;
END
IF @ZenCost > 0 AND @Money < @ZenCost
BEGIN
ROLLBACK TRANSACTION;
SELECT -3 AS Result, 'Zen insuficiente.' AS Message;
RETURN;
END
-- Limpia el estado 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 limpiado con exito.' AS Message;
END
GO
Y la tabla 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)
);
> El log no es opcional. Un PK Clear barato y sin registro incentiva el "ganking" — jugadores que matan a otros a su antojo porque saben que limpian la penalidad con un clic. El log permite detectar cuentas que limpian PK repetidamente.
Paso 3 — Conexión PDO reutilizable
Mantén la conexión en un archivo único, con las credenciales fuera del control de versión.
<?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,
]);
}
Paso 4 — Formulario de PK Clear en el panel
Lista solo los personajes de la cuenta logueada, marcando cuáles tienen PK. Esto mejora la experiencia (el jugador ve dónde necesita actuar) y evita clics inútiles.
<?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; // EJEMPLO: ajusta al valor neutro de tu version
?>
<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 nivel ' . (int)$c['PkLevel'] . ' (' . (int)$c['PkCount'] . ' kills)' : '— sin PK' ?>
</option>
<?php endforeach; ?>
</select>
<button type="submit">Limpiar PK</button>
</form>
Paso 5 — Procesar la limpieza con seguridad
Valida el token CSRF, llama a la procedure con parámetros vinculados y trata el retorno. Ninguna regla de negocio se decide en el PHP.
<?php
session_start();
require 'db.php';
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. Recarga la pagina.');
}
unset($_SESSION['csrf']);
$char = trim($_POST['char'] ?? '');
if ($char === '' || strlen($char) > 10) { exit('Personaje 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, // EJEMPLO
':zen' => 3000000, // EJEMPLO de costo
]);
$res = $stmt->fetch();
echo (($res['Result'] ?? -99) == 1)
? 'Exito: ' . htmlspecialchars($res['Message'])
: 'Fallo: ' . htmlspecialchars($res['Message'] ?? 'Error desconocido.');
Paso 6 — Impedir el PK Clear con el personaje en línea
Este es el mismo riesgo que el reset vía sitio: mientras el personaje está en el juego, el GameServer guarda los datos en memoria y sobrescribe la base de datos en el próximo save. Si limpias el PK desde el sitio con el char en línea, la limpieza se revierte — y el jugador puede haber pagado por nada. La procedure ya bloquea con ConnectStat <> 0, pero confirma dónde vive ese campo en tu versión: puede estar en Character o en MEMB_STAT (ligado a la cuenta). Ajusta la consulta según sea necesario e, idealmente, orienta al jugador a salir del juego antes de limpiar.
Paso 7 — Cooldown y protección contra abuso
Un PK Clear demasiado accesible rompe la mecánica de riesgo del PvP. Recomendaciones:
- Precio significativo: el costo en Zen o WCoin debe doler lo suficiente para que matar players tenga una consecuencia real.
- Cooldown por personaje: registra un
LastPkCleary bloquea una nueva limpieza antes de X minutos. - Rate limiting por cuenta: evita scripts que limpian PK en bucle.
- Auditoría de la
PkClearLog: monitorea cuentas con muchas limpiezas seguidas.
-- Detectar cuentas que limpiaron PK mas de 5 veces en las 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;
Errores comunes y soluciones
| Error | Causa probable | Solución |
|---|---|---|
| El PK vuelve tras entrar al juego | El personaje estaba en línea | Bloquear con ConnectStat <> 0; pedir logout |
| El PK Clear no cambia nada visible | Valor neutro equivocado (0 x 3) | Confirmar el neutro de la versión con una prueba |
| Cobro hecho pero PK no limpiado | Débito en query separada | Débito y limpieza en la misma transacción |
| Personaje de otra cuenta limpiado | No valida la propiedad | Comparar AccountID en la procedure |
| Error de conversión en el PHP | Tipo de parámetro incompatible | Alinear tipos PDO x procedure |
| Inyección por el nombre del char | Concatenación en la query | Siempre prepared statements |
Lista de verificación de lanzamiento
- Backup de la base de datos
MuOnlinehecho antes de las pruebas. - Campos de PK (
PkLevel,PkCount,PkTime) confirmados en tu versión. - Valor neutro de
PkLevelvalidado con personaje de prueba. - Stored procedure
WZ_PkClearViaSitecreada y probada en SSMS. - Tabla
PkClearLogcreada y recibiendo registros. - Conexión PDO con prepared statements, sin concatenación.
- Token CSRF de uso único en el formulario y en el procesador.
- Bloqueo de limpieza para personaje en línea validado.
- Validación de propiedad (AccountID) en dos capas.
- Costo y cooldown calibrados para no romper el PvP.
- Prueba de punta a punta en homologación antes de abrir al público.
Con este sistema, el PK Clear se convierte en un servicio rápido y lucrativo sin comprometer el equilibrio del servidor. La lógica sensible queda protegida en la base de datos, cada operación es atómica y registrada, y la capa PHP permanece pequeña y difícil de explotar. Ajusta los valores neutros, el costo y el cooldown al perfil de tu servidor — y siempre prueba en homologación antes de subir a producción.
Preguntas frecuentes
¿Qué elimina exactamente el PK Clear del personaje?
Pone a cero el estado de asesino (PK): el nivel de PK, el contador de kills y el tiempo de penalidad. Los campos varían por versión, pero generalmente son PkLevel, PkCount y PkTime en la tabla Character.
¿El jugador necesita estar offline para que el PK Clear vía sitio funcione?
Sí, en la mayoría de las versiones. Si el personaje está en línea, el GameServer sobrescribe la base de datos al guardar y el PK vuelve. Bloquea la operación cuando ConnectStat sea distinto de 0.
¿Cuál es la diferencia entre PkLevel y PkCount?
PkLevel es el grado de penalidad (común, asesino, héroe), que afecta el drop al morir y el bloqueo en ciudades. PkCount es el número de muertes de jugadores acumuladas. El PK Clear normalmente pone a cero ambos y el PkTime.
¿Puedo cobrar WCoin o Zen por el PK Clear?
Sí. Haz el débito dentro del mismo stored procedure y de la misma transacción que limpia el PK, garantizando que el cobro solo ocurra si la limpieza se completa.
¿Cómo impido que los jugadores abusen del PK Clear para farmear players sin castigo?
Cobra un valor significativo, aplica cooldown por personaje y registra cada limpieza en una tabla de log para auditar patrones sospechosos.