Cómo crear un plugin para el WebEngine de tu servidor de MU Online
Aprende a estructurar, programar y publicar un plugin propio para el WebEngine (panel web) de tu servidor de MU Online, integrando rankings, votación, tienda y comandos con la base de datos del juego.
El WebEngine es la columna vertebral de la presencia online de tu servidor de MU Online: es el que muestra el ranking de resets, procesa el registro de cuentas, integra la votación en toplistas, exhibe la tienda de cash y, en muchos casos, permite comandos remotos como resetear personaje o cambiar d
El WebEngine es la columna vertebral de la presencia online de tu servidor de MU Online: es el que muestra el ranking de resets, procesa el registro de cuentas, integra la votación en toplistas, exhibe la tienda de cash y, en muchos casos, permite comandos remotos como resetear personaje o cambiar de clase. Cuando el panel estándar no cubre una necesidad específica —un evento estacional, una integración con Discord, un sistema de cupones—, la solución correcta no es parchar el core, sino crear un plugin. Este tutorial muestra cómo estructurar, programar, probar y publicar un plugin desde cero, cubriendo la arquitectura típica, la conexión segura con la base de datos y los cuidados de producción.
Qué es un plugin de WebEngine y cuándo crear uno
Un plugin es un módulo aislado que se integra al panel principal sin alterar sus archivos núcleo. Expone sus propias rutas, sus propias vistas y, si es necesario, sus propias tablas en la base de datos —todo aislado para que una actualización del WebEngine base no sobrescriba tu trabajo. Crea un plugin cuando la funcionalidad es opcional, específica de tu servidor (no lo suficientemente genérica para volverse core) o necesita un ciclo de vida propio (activar/desactivar, versionar, distribuir a otros admins).
Arquitectura típica de un WebEngine basado en PHP
La mayoría de los paneles de MU usa una estructura MVC (Model-View-Controller). Entender esta estructura es el primer paso antes de escribir cualquier línea de plugin.
| Capa | Responsabilidad | Ubicación típica |
|---|---|---|
| Controller | Recibe la solicitud HTTP, invoca la lógica | application/controllers/ |
| Model | Consultas a la base de datos (MSSQL/MySQL) | application/models/ |
| View | HTML/Twig/Blade renderizado al usuario | application/views/ |
| Config | Cadenas de conexión, claves, rutas | application/config/ |
| Plugins | Módulos aislados de terceros | application/plugins/<nombre>/ |
Requisitos previos antes de empezar
- WebEngine ya instalado y funcionando, conectado a la base de datos del servidor (consulta el tutorial de creación de servidor).
- Acceso de escritura al código fuente del panel (FTP/SSH) y un entorno de staging separado de producción.
- Credenciales de lectura en la base de datos del juego (idealmente un usuario con permisos restringidos, no el
sa). - Editor de código con soporte para PHP y SQL, y un cliente de base de datos (SSMS o HeidiSQL) para inspeccionar tablas.
Paso 1 — Definir el alcance y el contrato del plugin
Antes de programar, escribe en una frase qué hace el plugin: "Muestra un panel de canje de código promocional que acredita Cash Points en la cuenta." Ese contrato define las tablas que vas a tocar (MEMB_STAT, CashShop_Log o equivalentes), las rutas que vas a exponer (/plugins/promocode/canjear) y los permisos necesarios (usuario logueado, no baneado).
Paso 2 — Crear la estructura de carpetas del plugin
Un plugin aislado normalmente sigue este esqueleto:
application/plugins/promocode/
├── PromocodeController.php
├── PromocodeModel.php
├── views/
│ └── canjear.php
├── config.php
└── install.sql
El archivo install.sql crea las tablas propias del plugin (ej.: plugin_promocode_codes), evitando que dependa de modificar tablas del core del juego.
Paso 3 — Escribir el Model (acceso a la base de datos)
<?php
class PromocodeModel extends CI_Model {
public function validarCodigo($codigo, $accountId) {
$this->db->where('code', $codigo);
$this->db->where('used', 0);
$query = $this->db->get('plugin_promocode_codes');
return $query->row();
}
public function creditarCash($accountId, $valor) {
// Siempre vía transacción — nunca UPDATE directo sin control de concurrencia
$this->db->trans_start();
$this->db->query(
"UPDATE MEMB_STAT SET CashPoint = CashPoint + ? WHERE memb___id = ?",
[$valor, $accountId]
);
$this->db->trans_complete();
return $this->db->trans_status();
}
}
Observa el uso de transacciones y consultas parametrizadas — nunca concatenes entrada del usuario directamente en SQL, esa es la puerta de entrada más común para SQL Injection en paneles de MU mal hechos.
Paso 4 — Escribir el Controller y la ruta
<?php
class PromocodeController extends CI_Controller {
public function canjear() {
if (!$this->session->userdata('logged_in')) {
redirect('login');
}
$codigo = $this->input->post('codigo');
$conta = $this->session->userdata('account_id');
$this->load->model('plugins/promocode/PromocodeModel');
$item = $this->PromocodeModel->validarCodigo($codigo, $conta);
if (!$item) {
$this->session->set_flashdata('erro', 'Código inválido o ya utilizado.');
redirect('plugins/promocode');
}
$this->PromocodeModel->creditarCash($conta, $item->valor);
$this->session->set_flashdata('sucesso', '¡Cash acreditado con éxito!');
redirect('plugins/promocode');
}
}
Registra la ruta en el archivo de rutas del panel (application/config/routes.php) apuntando plugins/promocode a este controller.
Paso 5 — Integrar con el esquema de la base de datos del MuServer
Las tablas varían por emulador, pero los puntos de integración más comunes son:
| Necesidad | Tabla típica (MSSQL) | Cuidado |
|---|---|---|
| Datos de cuenta | MEMB_INFO / MEMB_STAT | Nunca alteres la contraseña en texto plano |
| Cash/Points de la tienda | MEMB_STAT.CashPoint o tabla propia | Usa transacción, siempre |
| Correo in-game | PostMain / PostList (varía) | Sigue el formato exacto que el GameServer espera |
| Log de acciones | Tabla propia del plugin | Registra siempre el crédito de valores |
Si tu emulador expone stored procedures para operaciones sensibles (crear ítem, enviar correo), prefiere invocarlas en lugar de hacer INSERT/UPDATE manual — ya manejan la concurrencia y los formatos binarios específicos de MU.
Paso 6 — Autenticación, sesión y permisos
Reutiliza el sistema de sesión ya existente del WebEngine (no crees uno paralelo). Verifica siempre si el usuario está logueado antes de cualquier acción que grabe datos, y agrega una segunda capa de verificación si la acción involucra valores monetarios (ej.: confirmar la contraseña nuevamente para canjes de alto valor).
Paso 7 — Vistas y experiencia del usuario
Mantén el diseño consistente con el resto del panel (mismo CSS, mismo encabezado/pie de página), reutilizando las plantillas existentes en lugar de crear un HTML aislado. Esto evita que el plugin "parezca" un sitio diferente y rompa la confianza del jugador.
Paso 8 — Pruebas en entorno de staging
- Restaura una copia de la base de producción en una base de prueba.
- Apunta el WebEngine de staging a esa base de datos.
- Prueba el flujo completo: código válido, código inválido, código ya usado, cuenta baneada, falla de conexión simulada.
- Verifica los logs generados y confirma que ningún valor fue acreditado por duplicado bajo solicitudes simultáneas (prueba de concurrencia con dos pestañas al mismo tiempo).
Paso 9 — Empaquetar y versionar el plugin
Separa el plugin en un paquete distribuible (zip con install.sql, código y un README de instalación), y mantén un número de versión (1.0.0) en config.php. Esto facilita futuras actualizaciones y permite revertir en caso de que un deploy rompa algo en producción.
Paso 10 — Publicar en producción de forma segura
Publica fuera del horario pico, con backup de la base de datos inmediatamente antes del deploy. Ejecuta primero el install.sql, valida las tablas creadas, luego sube el código del plugin. Monitorea los logs del WebEngine y el consumo de CPU/consultas de la base de datos en las primeras horas tras el lanzamiento.
Errores comunes y soluciones
| Síntoma | Causa probable | Solución |
|---|---|---|
| Error 500 al acceder a la ruta del plugin | Ruta no registrada o model no cargado | Confirma routes.php y el load->model en el controller |
| Cash acreditado por duplicado | Falta de transacción/lock en la solicitud | Envuelve la operación en una transacción y marca el código como usado antes de acreditar |
| SQL Injection detectado en auditoría | Concatenación directa de input en la consulta | Reemplaza por consultas parametrizadas (bind ?) |
| Plugin se rompe tras actualización del core | El plugin alteró archivos del core en lugar de mantenerse aislado | Reestructura para no tocar archivos fuera de plugins/ |
| Sesión del jugador no reconocida en el plugin | Sistema de sesión paralelo/incompatible | Reutiliza la sesión nativa del WebEngine |
Lista de verificación de publicación del plugin
- Alcance y contrato del plugin definidos en una frase clara.
- Estructura aislada en
plugins/<nombre>/, sin tocar archivos del core. - Consultas parametrizadas y transacciones en cualquier escritura monetaria.
- Autenticación reutilizando la sesión nativa del WebEngine.
- Probado en staging con casos de error y concurrencia.
- Backup de la base de datos realizado antes del deploy en producción.
- Logs y monitoreo activos en las primeras horas tras el lanzamiento.
Con el plugin publicado y estable, el siguiente paso natural es pensar en cómo se conecta al resto de la experiencia del jugador —por ejemplo, integrando el canje de códigos con eventos in-game descritos en el tutorial de creación de servidor de MU Online, cerrando el ciclo entre web y juego.
Preguntas frecuentes
¿Qué es exactamente el WebEngine de un servidor de MU?
Es el panel web (generalmente en PHP o .NET) que muestra rankings, permite el registro de cuentas, votación, tienda de cash y comandos remotos. Se conecta a la misma base de datos del MuOnline/GameServer para leer y escribir información de los jugadores.
¿Necesito saber PHP para crear un plugin?
Depende del WebEngine usado. La mayoría de los paneles de la comunidad (basados en CodeIgniter o Laravel) usan PHP; algunos proyectos más nuevos usan .NET/C#. Necesitas conocer el lenguaje del panel específico y el esquema de la base de datos (MSSQL en la mayoría de los emuladores).
¿Un plugin puede ejecutar comandos directamente sobre el personaje del jugador?
Sí, siempre que el plugin escriba en las tablas o stored procedures que el GameServer ya lee, como colas de comandos, correo in-game o tablas de ítems. Nunca edites directamente columnas sensibles (como Money, Level) sin pasar por los procedimientos oficiales, bajo riesgo de duplicar ítems o resetear personajes.
¿Cómo pruebo un plugin sin afectar el servidor de producción?
Usa siempre un entorno de staging con una copia de la base de datos y, si es posible, un GameServer de prueba. Publica en el entorno real solo después de validar consultas, tiempos de respuesta y casos de error (cuenta duplicada, ítem inexistente, saldo insuficiente).
¿Es seguro exponer una API dentro del plugin para que el cliente del juego la consuma?
Sí, pero requiere autenticación (token por sesión), rate limiting y validación de entrada estricta. Una API mal protegida se convierte en puerta de entrada para bots de votación, duplicación de cash o ataques de fuerza bruta contra cuentas.