Cómo Crear Sistema de Voto y Recompensa en el Servidor de MU Online
Aprende a implementar un sistema de voto con recompensa automática en tu servidor de MU Online usando SQL Server, PHP y sitios de votación como GTOP100 y Xtremetop100.
Descripción General del Sistema
Un sistema de voto y recompensa incentiva a los jugadores a promocionar tu servidor en los principales sitios de ranking de servidores privados de MU Online, como GTOP100, Xtremetop100 y MuOnline.com.br. Cada voto suma puntos en el ranking y, a cambio, el jugador recibe ítems, Zen, W-Coins o resets automáticos.
El flujo completo funciona así:
Sitio de votación → Pingback HTTP a tu servidor → Script PHP recibe y valida → Registro en SQL Server → Stored Procedure entrega la recompensa
Paso 1: Crear la Tabla de Control de Votos en SQL Server
Abre SQL Server Management Studio (SSMS) y conéctate a la base de datos MuOnline. Ejecuta:
USE MuOnline;
GO
CREATE TABLE VoteLog (
VoteID INT IDENTITY(1,1) PRIMARY KEY,
AccountID VARCHAR(10) NOT NULL,
SiteID VARCHAR(30) NOT NULL, -- 'gtop100', 'xtremetop100', etc.
VoteKey VARCHAR(64) NOT NULL, -- clave única devuelta por el sitio
VoteDate DATETIME NOT NULL DEFAULT GETDATE(),
VoteStatus TINYINT NOT NULL DEFAULT 0, -- 0=pendiente, 1=entregado
IPAddress VARCHAR(45) NOT NULL DEFAULT ''
);
GO
-- Índice para consultas rápidas por cuenta
CREATE INDEX IX_VoteLog_AccountID ON VoteLog (AccountID, VoteStatus);
GO
VoteKey almacena el token único que el sitio de votación envía en el pingback. Sirve como prueba de voto legítimo y evita inserciones duplicadas — agrega una restricción UNIQUE para mayor protección: ALTER TABLE VoteLog ADD CONSTRAINT UQ_VoteKey UNIQUE (VoteKey);Paso 2: Crear la Stored Procedure de Entrega de Recompensa
La stored procedure lee los votos pendientes e inserta los ítems de recompensa en la tabla del personaje. Ajusta ItemCode, ItemLevel e ItemOpt según la recompensa que deseas ofrecer.
USE MuOnline;
GO
CREATE PROCEDURE SP_DeliverVoteReward
AS
BEGIN
SET NOCOUNT ON;
DECLARE @AccountID VARCHAR(10)
DECLARE @CharName VARCHAR(10)
DECLARE @VoteID INT
-- El cursor recorre todos los votos pendientes
DECLARE cur CURSOR FOR
SELECT VoteID, AccountID
FROM VoteLog
WHERE VoteStatus = 0
ORDER BY VoteDate ASC
OPEN cur
FETCH NEXT FROM cur INTO @VoteID, @AccountID
WHILE @@FETCH_STATUS = 0
BEGIN
-- Obtiene el personaje más reciente de la cuenta
SELECT TOP 1 @CharName = Name
FROM Character
WHERE AccountID = @AccountID
ORDER BY ConnectStat DESC, ResetCount DESC
IF @CharName IS NOT NULL
BEGIN
-- Entrega 200 W-Coins (ajusta WCoinP según tu columna de coins)
UPDATE MEMB_INFO
SET WCoinP = ISNULL(WCoinP, 0) + 200
WHERE memb___id = @AccountID
-- Opcional: entregar ítem físico vía tabla de depósito
-- INSERT INTO ITEM_STORE (AccountID, ItemCode, ItemLevel, ItemDur, ItemOpt)
-- VALUES (@AccountID, 7936, 0, 255, 0) -- ejemplo: Box of Kundun+5
-- Marca el voto como entregado
UPDATE VoteLog
SET VoteStatus = 1
WHERE VoteID = @VoteID
END
FETCH NEXT FROM cur INTO @VoteID, @AccountID
END
CLOSE cur
DEALLOCATE cur
END
GO
WCoinP, en otras wcoin_p o GoblinPoint. Ejecuta SELECT TOP 1 * FROM MEMB_INFO en tu base de datos para confirmar el nombre exacto de la columna antes de ejecutar la procedure.Paso 3: Configurar el Script PHP de Recepción (Pingback)
Crea el archivo vote_callback.php en el directorio raíz de tu sitio (ej.: C:\AppServ\www\tusitio\vote_callback.php o C:\xampp\htdocs\tusitio\vote_callback.php):
<?php
// vote_callback.php — Receptor de pingback de votos
// Coloca este archivo en: /public_html/vote_callback.php o equivalente
define('DB_SERVER', 'localhost'); // IP del SQL Server
define('DB_USER', 'sa'); // usuario del SQL Server
define('DB_PASS', 'TuClave123'); // contraseña del SQL Server
define('DB_NAME', 'MuOnline');
// Clave secreta configurada en el panel de GTOP100 / Xtremetop100
define('VOTE_SECRET', 'mi_clave_secreta_aqui');
// Conexión a SQL Server vía PDO con driver SQLSRV
try {
$dsn = "sqlsrv:Server=" . DB_SERVER . ";Database=" . DB_NAME;
$pdo = new PDO($dsn, DB_USER, DB_PASS);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
http_response_code(500);
exit('DB error');
}
// --- GTOP100 ---
if (isset($_GET['pingback']) && $_GET['pingback'] === VOTE_SECRET) {
$accountID = preg_replace('/[^a-zA-Z0-9]/', '', $_GET['pingbackkey'] ?? '');
$voteKey = md5($_GET['pingbackkey'] . time());
$ip = $_SERVER['REMOTE_ADDR'];
if (empty($accountID)) {
http_response_code(400);
exit('Invalid account');
}
// Verifica voto duplicado en las últimas 12 horas
$stmt = $pdo->prepare("
SELECT COUNT(*) FROM VoteLog
WHERE AccountID = ? AND SiteID = 'gtop100'
AND VoteDate > DATEADD(HOUR, -12, GETDATE())
");
$stmt->execute([$accountID]);
if ($stmt->fetchColumn() > 0) {
http_response_code(200);
exit('Already voted');
}
// Registra el voto
$stmt = $pdo->prepare("
INSERT INTO VoteLog (AccountID, SiteID, VoteKey, IPAddress)
VALUES (?, 'gtop100', ?, ?)
");
$stmt->execute([$accountID, $voteKey, $ip]);
http_response_code(200);
exit('OK');
}
// --- Xtremetop100 ---
if (isset($_POST['pingbackkey'])) {
$accountID = preg_replace('/[^a-zA-Z0-9]/', '', $_POST['pingbackkey']);
$voteKey = $_POST['pingbackkey'] . '_xt_' . time();
$ip = $_SERVER['REMOTE_ADDR'];
$stmt = $pdo->prepare("
SELECT COUNT(*) FROM VoteLog
WHERE AccountID = ? AND SiteID = 'xtremetop100'
AND VoteDate > DATEADD(HOUR, -12, GETDATE())
");
$stmt->execute([$accountID]);
if ($stmt->fetchColumn() > 0) { exit('1'); }
$stmt = $pdo->prepare("
INSERT INTO VoteLog (AccountID, SiteID, VoteKey, IPAddress)
VALUES (?, 'xtremetop100', ?, ?)
");
$stmt->execute([$accountID, $voteKey, $ip]);
exit('1'); // Xtremetop100 espera '1' como confirmación
}
http_response_code(400);
exit('Bad request');
?>
php_pdo_sqlsrv_XX_ts.dll compatible con tu versión de PHP desde microsoft.com/sqlsrv y agrega al php.ini: extension=php_pdo_sqlsrv_XX_ts.dll. Reinicia Apache/IIS después de modificar el archivo.Paso 4: Configurar el SQL Server Agent para Ejecutar la Recompensa
La stored procedure debe ejecutarse automáticamente cada pocos minutos. En SQL Server Management Studio:
- Expande SQL Server Agent → Jobs → clic derecho → New Job
- En General: Nombre =
EntregarRecompensaVoto - En Steps → New Step:
- Step name:
Ejecutar SP - Type:
Transact-SQL script (T-SQL) - Database:
MuOnline - Command:
``sql EXEC SP_DeliverVoteReward; ``
- En Schedules → New Schedule:
- Name:
Cada 5 minutos - Frequency:
Daily, Every5minutes
- Haz clic en OK para guardar el job.
SQL Server Agent (MSSQLSERVER) esté como En ejecución con inicio Automático.Paso 5: Configurar los Paneles de los Sitios de Votación
GTOP100
- Accede a tu panel en gtop100.com → tu servidor → Edit Listing
- En Pingback URL ingresa:
http://tudominio.com/vote_callback.php?pingback=mi_clave_secreta_aqui - En Pingback Variable Name coloca:
pingbackkey - El jugador vota vía:
http://gtop100.com/vote/TUID?pingbackkey=nombrecuenta
Xtremetop100
- Accede a tu panel → Edit Server → In-game Reward Voting
- En Reward URL:
http://tudominio.com/vote_callback.php - En Variable name:
pingbackkey
netsh advfirewall firewall add rule name="HTTP Vote" protocol=TCP dir=in localport=80 action=allowPaso 6: Crear la Página de Voto en el Sitio del Servidor
Agrega al sitio un formulario donde el jugador ingresa su nombre de cuenta antes de ser redirigido a votar:
<!-- vote.php — Página de voto en el sitio del servidor -->
<form action="vote.php" method="post">
<label>Nombre de Cuenta (Login):</label>
<input type="text" name="account" maxlength="10" required>
<button type="submit">Votar y Ganar Recompensa</button>
</form>
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$account = preg_replace('/[^a-zA-Z0-9]/', '', $_POST['account'] ?? '');
if (!empty($account)) {
$gtop_url = "https://gtop100.com/topsites/MuOnline/TUID/vote?pingbackkey=" . urlencode($account);
$xt_url = "https://www.xtremetop100.com/in.php?site=TUID&postback=" . urlencode($account);
echo "<p><a href='$gtop_url' target='_blank'>Votar en GTOP100 (+200 W-Coins)</a></p>";
echo "<p><a href='$xt_url' target='_blank'>Votar en Xtremetop100 (+200 W-Coins)</a></p>";
}
}
?>
Paso 7: Verificar y Monitorear el Sistema
Para revisar votos recientes y estado de entrega directamente en SSMS:
-- Votos de las últimas 24 horas
SELECT VoteID, AccountID, SiteID, VoteDate,
CASE VoteStatus WHEN 0 THEN 'Pendiente' ELSE 'Entregado' END AS Estado
FROM VoteLog
WHERE VoteDate > DATEADD(HOUR, -24, GETDATE())
ORDER BY VoteDate DESC;
-- Total de votos por cuenta (ranking de votación)
SELECT AccountID, COUNT(*) AS TotalVotos
FROM VoteLog
WHERE VoteStatus = 1
GROUP BY AccountID
ORDER BY TotalVotos DESC;
-- Forzar entrega manual de votos pendientes
EXEC SP_DeliverVoteReward;
/admin/votos.php protegida por contraseña que muestre esta consulta como tabla HTML — facilita atender reclamos de jugadores sin necesidad de abrir SSMS cada vez.Solución Rápida de Problemas
| Problema | Causa probable | Solución |
|---|---|---|
| Pingback no llega | Firewall bloqueando puerto 80 | Verificar regla en Windows Firewall y en el panel de hosting |
AccountID no encontrado | Nombre de cuenta escrito incorrectamente | Validar contra tabla MEMB_INFO antes de aceptar el voto |
| W-Coins no sumados | Nombre de columna incorrecto en MEMB_INFO | SELECT TOP 1 * FROM MEMB_INFO para verificar columnas reales |
| Job de SQL Agent no ejecuta | Servicio Agent detenido | services.msc → iniciar SQL Server Agent |
| Voto duplicado aceptado | Falta verificación de ventana de tiempo | Agregar WHERE VoteDate > DATEADD(HOUR, -12, GETDATE()) |
Perguntas frequentes
El jugador votó pero no recibió la recompensa. ¿Qué debo verificar?
Primero confirma que el VoteKey devuelto por el pingback se está guardando correctamente en la tabla VoteLog. Verifica que el AccountID coincida exactamente con el nombre de cuenta del jugador (sensible a mayúsculas en GTOP100). Luego ejecuta: SELECT * FROM MuOnline.dbo.VoteLog WHERE AccountID = 'nombrecuenta' ORDER BY VoteDate DESC — si el registro existe pero la recompensa no llegó, el problema está en el script PHP de entrega o en la programación de la stored procedure.
¿Puedo recompensar con más de un ítem por voto?
Sí. Solo agrega múltiples sentencias INSERT dentro de la stored procedure SP_DeliverVoteReward, una por ítem. Usa el mismo CharacterName y ajusta los campos ItemCode, ItemLevel, ItemDur, ItemOpt según los ítems deseados. Ten en cuenta que la tabla de ítems (generalmente MEMB_ITEMS o equivalente) tiene límite de slots — verifica que el inventario del personaje no esté lleno.
¿Cuál es la diferencia entre recompensar vía base de datos y vía comando en juego?
Vía base de datos (INSERT directo o stored procedure) el ítem se entrega incluso con el servidor offline, pero requiere que el personaje esté desconectado en el momento de la entrega o que uses la tabla de entrega de ítems del MuServer. Vía comando en juego (/add_item o similar) el GameServer ejecuta inmediatamente, pero el personaje debe estar online y el servidor en funcionamiento.
¿Cómo evito que un jugador vote varias veces antes de recibir la recompensa?
Agrega un campo VoteStatus en la tabla VoteLog (0=pendiente, 1=entregado). Antes de aceptar un nuevo voto, ejecuta: SELECT COUNT(*) FROM VoteLog WHERE AccountID = @AccountID AND VoteStatus = 0 — si devuelve > 0, rechaza el nuevo registro. Esto evita la acumulación de recompensas no cobradas y detecta posibles intentos de explotación del sistema.