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

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.

GA Gabriel · Actualizado el 12 ago 2025 · ⏱ 17 min de lectura
Respuesta rápida

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 MuOnline accesible.
  • 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 sqlsrv o dblib).
  • 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:

CampoFunciónValor "limpio"
PkLevelGrado de penalidad3 (neutro) en la mayoría de las builds
PkCountNúmero de kills acumuladas0
PkTimeTiempo de penalidad restante0
ConnectStatEstado 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:

  1. Precio significativo: el costo en Zen o WCoin debe doler lo suficiente para que matar players tenga una consecuencia real.
  2. Cooldown por personaje: registra un LastPkClear y bloquea una nueva limpieza antes de X minutos.
  3. Rate limiting por cuenta: evita scripts que limpian PK en bucle.
  4. 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

ErrorCausa probableSolución
El PK vuelve tras entrar al juegoEl personaje estaba en líneaBloquear con ConnectStat <> 0; pedir logout
El PK Clear no cambia nada visibleValor neutro equivocado (0 x 3)Confirmar el neutro de la versión con una prueba
Cobro hecho pero PK no limpiadoDébito en query separadaDébito y limpieza en la misma transacción
Personaje de otra cuenta limpiadoNo valida la propiedadComparar AccountID en la procedure
Error de conversión en el PHPTipo de parámetro incompatibleAlinear tipos PDO x procedure
Inyección por el nombre del charConcatenación en la querySiempre prepared statements

Lista de verificación de lanzamiento

  • Backup de la base de datos MuOnline hecho antes de las pruebas.
  • Campos de PK (PkLevel, PkCount, PkTime) confirmados en tu versión.
  • Valor neutro de PkLevel validado con personaje de prueba.
  • Stored procedure WZ_PkClearViaSite creada y probada en SSMS.
  • Tabla PkClearLog creada 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.

GA
Editor de guías y builds

Gabriel cubre gameplay, builds de clases, PvP y progresión. Prueba cada estrategia en un servidor antes de publicar.

Sigue leyendo

Artículos relacionados