Cómo Implementar Sistema de Ranking Automático en el Website de MU
Aprende a crear un sistema de ranking automático integrado con la base de datos SQL Server de tu servidor MU Online, con actualizaciones en tiempo real via PHP.
Mostrar un ranking actualizado automáticamente es una de las funciones más valoradas por los jugadores de servidores privados de MU Online. Esta guía explica cómo integrar la base de datos SQL Server de tu instalación MuServer (Season 6 en adelante) con un website en PHP para generar rankings de personajes, resets y guilds — sin intervención manual.
Requisitos Previos
Antes de comenzar, confirma que tienes:
- Acceso a SQL Server Management Studio (SSMS) con permisos de lectura/escritura en la base de datos
MuOnline - PHP 7.4+ con la extensión
sqlsrvopdo_sqlsrvinstalada (XAMPP o AppServ en Windows Server) - Acceso al SQL Server Agent para programar Jobs
- Permiso para crear tablas y stored procedures en la base de datos
MuOnline del MuServer Season 6. Versiones anteriores (S2/S3) usan estructuras de tablas ligeramente diferentes — adapta los nombres de columnas según sea necesario consultando tu propia tabla Character.Paso 1: Entender la Estructura de las Tablas Relevantes
El MuServer almacena los datos de personaje en la tabla Character dentro de la base de datos MuOnline. Las columnas más usadas para ranking son:
-- Verificar estructura de la tabla Character
USE MuOnline;
SELECT COLUMN_NAME, DATA_TYPE
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'Character'
ORDER BY ORDINAL_POSITION;
Columnas esenciales para ranking:
| Columna | Descripción |
|---|---|
Name | Nombre del personaje |
AccountID | Cuenta vinculada |
cLevel | Level actual |
Class | Clase del personaje (byte) |
Resets | Número de resets (si está configurado) |
PkCount | Conteo de PK |
Money | Zen del personaje |
MapNumber | Mapa actual (para verificar si está online) |
Paso 2: Crear la Tabla de Caché de Ranking
En lugar de consultar la tabla Character directamente en cada solicitud del website, crea una tabla de caché dedicada. Esto evita lentitud en la base de datos durante picos de acceso.
USE MuOnline;
GO
CREATE TABLE RankingCache (
RankPosition INT IDENTITY(1,1) PRIMARY KEY,
CharacterName VARCHAR(10) NOT NULL,
AccountID VARCHAR(10) NOT NULL,
CharClass TINYINT NOT NULL,
CharLevel SMALLINT NOT NULL,
CharResets INT NOT NULL DEFAULT 0,
CharZen BIGINT NOT NULL DEFAULT 0,
GuildName VARCHAR(8) NULL,
LastUpdate DATETIME NOT NULL DEFAULT GETDATE(),
RankType VARCHAR(20) NOT NULL DEFAULT 'level'
);
CREATE INDEX IX_RankingCache_Type ON RankingCache (RankType, RankPosition);
GO
Paso 3: Crear el Stored Procedure de Actualización
El stored procedure a continuación borra y recrea los datos de caché para cada tipo de ranking. Ejecútalo en SSMS para crearlo:
USE MuOnline;
GO
CREATE PROCEDURE sp_UpdateRankingCache
AS
BEGIN
SET NOCOUNT ON;
-- Limpiar caché anterior
DELETE FROM RankingCache;
-- Ranking por Level y Resets
INSERT INTO RankingCache (CharacterName, AccountID, CharClass, CharLevel, CharResets, CharZen, GuildName, RankType)
SELECT TOP 200
C.Name,
C.AccountID,
C.Class,
C.cLevel,
ISNULL(C.Resets, 0),
C.Money,
G.G_Name,
'level'
FROM Character C
LEFT JOIN GuildMember GM ON GM.Name = C.Name
LEFT JOIN Guild G ON G.G_Name = GM.G_Name
WHERE C.cLevel > 0
AND C.AccountID NOT IN (
SELECT memb___id FROM MEMB_INFO WHERE memb_stat = 1
)
ORDER BY ISNULL(C.Resets, 0) DESC, C.cLevel DESC, C.Money DESC;
-- Ranking por Zen (riqueza)
INSERT INTO RankingCache (CharacterName, AccountID, CharClass, CharLevel, CharResets, CharZen, GuildName, RankType)
SELECT TOP 100
C.Name,
C.AccountID,
C.Class,
C.cLevel,
ISNULL(C.Resets, 0),
C.Money,
G.G_Name,
'zen'
FROM Character C
LEFT JOIN GuildMember GM ON GM.Name = C.Name
LEFT JOIN Guild G ON G.G_Name = GM.G_Name
WHERE C.cLevel > 0
AND C.AccountID NOT IN (
SELECT memb___id FROM MEMB_INFO WHERE memb_stat = 1
)
ORDER BY C.Money DESC;
-- Actualizar timestamp
UPDATE RankingCache SET LastUpdate = GETDATE();
PRINT 'RankingCache actualizado correctamente.';
END;
GO
memb_stat = 1 filtra cuentas de GM. Si tu servidor usa una columna diferente para marcar administradores, ajusta el filtro según la estructura de tu MEMB_INFO.Paso 4: Programar Actualización Automática con SQL Server Agent
Crea un Job en el SQL Server Agent para ejecutar el procedure automáticamente cada 10 minutos:
- Abre SSMS → expande SQL Server Agent → clic derecho en Jobs → New Job
- Pestaña General: Name =
Actualizar Ranking MU - Pestaña Steps → New Step:
- Step Name:
Ejecutar sp_UpdateRankingCache - Type:
Transact-SQL script (T-SQL) - Database:
MuOnline - Command:
EXEC sp_UpdateRankingCache
- Pestaña Schedules → New Schedule:
- Name:
Cada 10 minutos - Frequency:
Daily - Daily frequency:
Occurs every 10 minutes - Start/End:
00:00:00a23:59:59
- Haz clic en OK para guardar.
Para probar de inmediato, haz clic derecho en el Job → Start Job at Step.
services.msc). Si está detenido, los Jobs no se ejecutarán y el ranking quedará desactualizado.Paso 5: Configurar la Conexión PHP al SQL Server
En tu website, crea el archivo de configuración de base de datos. Guárdalo en /web/includes/db_config.php:
<?php
// /web/includes/db_config.php
define('DB_SERVER', '127.0.0.1'); // IP del SQL Server
define('DB_PORT', '1433');
define('DB_USER', 'mu_web_user'); // usuario con permiso SELECT únicamente
define('DB_PASS', 'TuContraseñaAqui');
define('DB_NAME', 'MuOnline');
function getDbConnection() {
$connectionOptions = [
'Database' => DB_NAME,
'Uid' => DB_USER,
'PWD' => DB_PASS,
'CharacterSet' => 'UTF-8',
'TrustServerCertificate' => true,
];
$conn = sqlsrv_connect(DB_SERVER . ', ' . DB_PORT, $connectionOptions);
if ($conn === false) {
error_log('Falla en conexión DB: ' . print_r(sqlsrv_errors(), true));
return null;
}
return $conn;
}
?>
SELECT en la tabla RankingCache. Nunca uses sa ni el usuario del GameServer para conexiones web.Paso 6: Crear la Página de Ranking en PHP
Guarda el archivo en /web/ranking.php:
<?php
require_once 'includes/db_config.php';
$type = isset($_GET['type']) ? $_GET['type'] : 'level';
$type = in_array($type, ['level', 'zen']) ? $type : 'level';
$page = max(1, (int)($_GET['page'] ?? 1));
$limit = 25;
$offset = ($page - 1) * $limit;
// Mapa de clases Season 6
$classNames = [
0 => 'Dark Wizard', 1 => 'Soul Master', 2 => 'Grand Master',
16 => 'Dark Knight', 17 => 'Blade Knight', 18 => 'Blade Master',
32 => 'Fairy Elf', 33 => 'Muse Elf', 34 => 'High Elf',
48 => 'Magic Gladiator', 64 => 'Dark Lord',
80 => 'Summoner', 96 => 'Rage Fighter',
];
$conn = getDbConnection();
$sql = "SELECT CharacterName, CharClass, CharLevel, CharResets, CharZen, GuildName,
ROW_NUMBER() OVER (PARTITION BY RankType ORDER BY RankPosition) AS Pos
FROM RankingCache
WHERE RankType = ?
ORDER BY RankPosition
OFFSET ? ROWS FETCH NEXT ? ROWS ONLY";
$params = [$type, $offset, $limit];
$result = sqlsrv_query($conn, $sql, $params);
$lastUpdate = '';
$luSql = "SELECT TOP 1 LastUpdate FROM RankingCache WHERE RankType = ?";
$luRes = sqlsrv_query($conn, $luSql, [$type]);
if ($luRow = sqlsrv_fetch_array($luRes, SQLSRV_FETCH_ASSOC)) {
$lastUpdate = $luRow['LastUpdate']->format('d/m/Y H:i');
}
?>
<!DOCTYPE html>
<html lang="es">
<head><meta charset="UTF-8"><title>Ranking - ViciadosMU</title></head>
<body>
<h1>Ranking <?= $type === 'zen' ? 'Zen' : 'Level/Resets' ?></h1>
<p>Última actualización: <?= htmlspecialchars($lastUpdate) ?></p>
<table border="1" cellpadding="6">
<tr><th>#</th><th>Personaje</th><th>Clase</th><th>Level</th><th>Resets</th><th>Guild</th></tr>
<?php
$rank = $offset + 1;
while ($row = sqlsrv_fetch_array($result, SQLSRV_FETCH_ASSOC)):
$cls = $classNames[$row['CharClass']] ?? 'Desconocido';
?>
<tr>
<td><?= $rank++ ?></td>
<td><?= htmlspecialchars($row['CharacterName']) ?></td>
<td><?= htmlspecialchars($cls) ?></td>
<td><?= (int)$row['CharLevel'] ?></td>
<td><?= (int)$row['CharResets'] ?></td>
<td><?= htmlspecialchars($row['GuildName'] ?? '-') ?></td>
</tr>
<?php endwhile; ?>
</table>
</body>
</html>
Paso 7: Resolver Problemas Comunes
El ranking no se actualiza
- Verifica el servicio SQL Server Agent →
services.msc→ SQL Server Agent (MSSQLSERVER) - Ejecuta manualmente en SSMS:
EXEC MuOnline..sp_UpdateRankingCache - Revisa errores en:
SSMS → SQL Server Agent → Error Logs
La página PHP devuelve pantalla en blanco
// Agrega al inicio del archivo para depuración (eliminar en producción)
ini_set('display_errors', 1);
error_reporting(E_ALL);
Verifica que la extensión sqlsrv esté habilitada en php.ini:
; En el php.ini de XAMPP (C:\xampp\php\php.ini)
extension=php_sqlsrv_74_ts_x64.dll
extension=php_pdo_sqlsrv_74_ts_x64.dll
Error de conexión "Cannot open database"
Ejecuta en SSMS:
-- Verificar que el usuario web tenga acceso
USE MuOnline;
GRANT SELECT ON RankingCache TO mu_web_user;
GRANT EXECUTE ON sp_UpdateRankingCache TO mu_web_user;
Resets de la tabla Character: CREATE INDEX IX_Char_Resets ON Character (Resets DESC, cLevel DESC). Esto acelera significativamente el stored procedure de actualización.Expansiones Posibles
Con la estructura base funcionando, puedes agregar:
- Ranking de Guild: agrega puntos sumando los resets de los miembros via
GROUP BY G_Name - Ranking de PvP: ordena por
PkCount DESCcon filtro de cuentas activas - Historial de posiciones: agrega una tabla
RankingHistorye inserta snapshots diarios via un Job separado programado a medianoche - API JSON: retorna los datos con
header('Content-Type: application/json'); echo json_encode($rows);para integrar con bots de Discord o apps móviles
Este sistema escala bien para servidores de pequeño a mediano tamaño. Para servidores con más de 1.000 jugadores simultáneos, considera migrar la capa de caché a Redis o memcached y consultar el SQL Server únicamente durante las actualizaciones programadas.
Perguntas frequentes
¿Con qué frecuencia debo actualizar el ranking?
Para servidores activos, un intervalo de 5 a 15 minutos es suficiente. Usa el SQL Server Agent con un Job programado apuntando al stored procedure de actualización. Intervalos menores a 5 minutos pueden sobrecargar la base de datos en servidores con muchos jugadores.
El ranking muestra datos desactualizados, ¿qué verificar?
Confirma que el servicio SQL Server Agent está corriendo en Servicios de Windows, que el Job está activo y que la tabla RankingCache tiene la columna LastUpdate actualizándose. Ejecuta manualmente: EXEC sp_UpdateRankingCache y verifica si hay errores en SQL Server Management Studio.
¿Cómo evitar que personajes GM aparezcan en el ranking?
Agrega un filtro en la query principal: WHERE AccountID NOT IN (SELECT memb___id FROM MEMB_INFO WHERE memb_stat = 1). También puedes mantener una tabla exclusion_list con los AccountIDs de los administradores y hacer JOIN contra ella.
¿Puedo mostrar rankings filtrados por clase de personaje?
Sí. Agrega un filtro de clase en la query: AND Class BETWEEN 0 AND 1 para Dark Wizard, BETWEEN 16 AND 17 para Dark Knight, BETWEEN 32 AND 33 para Elf, BETWEEN 48 AND 49 para Magic Gladiator, y BETWEEN 64 AND 64 para Dark Lord. Crea pestañas separadas en el website para cada filtro.