Como criar um plugin para o WebEngine do seu servidor de MU Online
Aprenda a estruturar, codificar e publicar um plugin próprio para o WebEngine (painel web) do seu servidor de MU Online, integrando rankings, votação, loja e comandos com o banco de dados do jogo.
O WebEngine é a espinha dorsal da presença online do seu servidor de MU Online: é ele que mostra o ranking de resets, processa o registro de contas, integra a votação em toparama, exibe a loja de cash e, em muitos casos, permite comandos remotos como resetar personagem ou trocar de classe. Quando o
O WebEngine é a espinha dorsal da presença online do seu servidor de MU Online: é ele que mostra o ranking de resets, processa o registro de contas, integra a votação em toparama, exibe a loja de cash e, em muitos casos, permite comandos remotos como resetar personagem ou trocar de classe. Quando o painel padrão não cobre uma necessidade específica — um evento sazonal, uma integração com Discord, um sistema de cupons — a solução correta não é remendar o core, e sim criar um plugin. Este tutorial mostra como estruturar, codificar, testar e publicar um plugin do zero, cobrindo a arquitetura típica, a conexão segura com o banco e os cuidados de produção.
O que é um plugin de WebEngine e quando criar um
Um plugin é um módulo isolado que se integra ao painel principal sem alterar seus arquivos-núcleo. Ele expõe rotas próprias, views próprias e, se necessário, tabelas próprias no banco — tudo isolado para que uma atualização do WebEngine base não sobrescreva seu trabalho. Crie um plugin quando a funcionalidade é opcional, específica do seu servidor (não genérica o suficiente para virar core) ou precisa de ciclo de vida próprio (ativar/desativar, versionar, distribuir para outros admins).
Arquitetura típica de um WebEngine baseado em PHP
A maioria dos painéis de MU usa uma estrutura MVC (Model-View-Controller). Entender essa estrutura é o primeiro passo antes de escrever qualquer linha de plugin.
| Camada | Responsabilidade | Local típico |
|---|---|---|
| Controller | Recebe requisição HTTP, chama a lógica | application/controllers/ |
| Model | Consultas ao banco (MSSQL/MySQL) | application/models/ |
| View | HTML/Twig/Blade renderizado ao usuário | application/views/ |
| Config | Strings de conexão, chaves, rotas | application/config/ |
| Plugins | Módulos isolados de terceiros | application/plugins/<nome>/ |
Pré-requisitos antes de começar
- WebEngine já instalado e funcionando, conectado ao banco do servidor (veja o tutorial de criação de servidor).
- Acesso de escrita ao código-fonte do painel (FTP/SSH) e ambiente de staging separado da produção.
- Credenciais de leitura no banco de dados do jogo (idealmente um usuário com permissões restritas, não o
sa). - Editor de código com suporte a PHP e SQL, e um cliente de banco (SSMS ou HeidiSQL) para inspecionar tabelas.
Passo 1 — Definir o escopo e o contrato do plugin
Antes de codificar, escreva em uma frase o que o plugin faz: "Exibe um painel de resgate de código promocional que credita Cash Points na conta." Esse contrato define as tabelas que você vai tocar (MEMB_STAT, CashShop_Log ou equivalentes), as rotas que vai expor (/plugins/promocode/resgatar) e as permissões necessárias (usuário logado, não banido).
Passo 2 — Criar a estrutura de pastas do plugin
Um plugin isolado normalmente segue este esqueleto:
application/plugins/promocode/
├── PromocodeController.php
├── PromocodeModel.php
├── views/
│ └── resgatar.php
├── config.php
└── install.sql
O arquivo install.sql cria as tabelas próprias do plugin (ex.: plugin_promocode_codes), evitando que ele dependa de alterar tabelas do core do jogo.
Passo 3 — Escrever o Model (acesso ao banco)
<?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) {
// Sempre via transação — nunca UPDATE direto sem controle de concorrência
$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();
}
}
Note o uso de transações e queries parametrizadas — nunca concatene entrada do usuário diretamente em SQL, isso é a porta de entrada mais comum para SQL Injection em painéis de MU mal feitos.
Passo 4 — Escrever o Controller e a rota
<?php
class PromocodeController extends CI_Controller {
public function resgatar() {
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 ou já utilizado.');
redirect('plugins/promocode');
}
$this->PromocodeModel->creditarCash($conta, $item->valor);
$this->session->set_flashdata('sucesso', 'Cash creditado com sucesso!');
redirect('plugins/promocode');
}
}
Registre a rota no arquivo de rotas do painel (application/config/routes.php) apontando plugins/promocode para este controller.
Passo 5 — Integrar com o esquema do banco do MuServer
Tabelas variam por emulador, mas os pontos de integração mais comuns são:
| Necessidade | Tabela típica (MSSQL) | Cuidado |
|---|---|---|
| Dados de conta | MEMB_INFO / MEMB_STAT | Nunca altere senha em texto puro |
| Cash/Points da loja | MEMB_STAT.CashPoint ou tabela própria | Use transação, sempre |
| Correio in-game | PostMain / PostList (varia) | Siga o formato exato que o GameServer espera |
| Log de ações | Tabela própria do plugin | Sempre logue crédito de valores |
Se o seu emulador expõe stored procedures para operações sensíveis (criar item, enviar correio), prefira chamá-las em vez de fazer INSERT/UPDATE manual — elas já tratam concorrência e formatos binários específicos do MU.
Passo 6 — Autenticação, sessão e permissões
Reutilize o sistema de sessão já existente do WebEngine (não crie um paralelo). Verifique sempre se o usuário está logado antes de qualquer ação que grave dados, e adicione uma segunda camada de checagem se a ação envolver valores monetários (ex.: confirmar senha novamente para resgates de alto valor).
Passo 7 — Views e experiência do usuário
Mantenha o layout consistente com o restante do painel (mesmo CSS, mesmo cabeçalho/rodapé), reaproveitando os templates existentes em vez de criar um HTML isolado. Isso evita que o plugin "pareça" um site diferente e quebre a confiança do jogador.
Passo 8 — Testes em ambiente de staging
- Restaure uma cópia do banco de produção em um banco de teste.
- Aponte o WebEngine de staging para esse banco.
- Teste o fluxo completo: código válido, código inválido, código já usado, conta banida, falha de conexão simulada.
- Verifique os logs gerados e confirme que nenhum valor foi creditado em duplicidade sob requisições simultâneas (teste de concorrência com duas abas ao mesmo tempo).
Passo 9 — Empacotar e versionar o plugin
Separe o plugin em um pacote distribuível (zip com install.sql, código e um README de instalação), e mantenha um número de versão (1.0.0) no config.php. Isso facilita atualizações futuras e permite reverter caso um deploy quebre algo em produção.
Passo 10 — Publicar em produção com segurança
Publique fora do horário de pico, com backup do banco imediatamente antes do deploy. Rode o install.sql primeiro, valide as tabelas criadas, depois suba o código do plugin. Monitore os logs do WebEngine e o consumo de CPU/queries do banco nas primeiras horas após o lançamento.
Erros comuns e soluções
| Sintoma | Causa provável | Solução |
|---|---|---|
| Erro 500 ao acessar a rota do plugin | Rota não registrada ou model não carregado | Confirme routes.php e o load->model no controller |
| Cash creditado em duplicidade | Falta de transação/lock na requisição | Envolva a operação em transação e trave o código como usado antes de creditar |
| SQL Injection detectado em auditoria | Concatenação direta de input em query | Troque por queries parametrizadas (? bind) |
| Plugin quebra após atualização do core | Plugin alterou arquivos do core em vez de ficar isolado | Reestruture para não tocar em arquivos fora de plugins/ |
| Sessão do jogador não reconhecida no plugin | Sistema de sessão paralelo/incompatível | Reutilize a sessão nativa do WebEngine |
Checklist de publicação do plugin
- Escopo e contrato do plugin definidos em uma frase clara.
- Estrutura isolada em
plugins/<nome>/, sem tocar arquivos do core. - Queries parametrizadas e transações em qualquer escrita monetária.
- Autenticação reaproveitando a sessão nativa do WebEngine.
- Testado em staging com casos de erro e concorrência.
- Backup do banco realizado antes do deploy em produção.
- Logs e monitoramento ativos nas primeiras horas após o lançamento.
Com o plugin publicado e estável, o próximo passo natural é pensar em como ele se conecta ao restante da experiência do jogador — por exemplo, integrando o resgate de códigos com eventos in-game descritos no tutorial de criação de servidor de MU Online, fechando o ciclo entre web e jogo.
Perguntas frequentes
O que exatamente é o WebEngine de um servidor de MU?
É o painel web (geralmente em PHP ou .NET) que mostra rankings, permite registro de conta, votação, loja de cash e comandos remotos. Ele se conecta ao mesmo banco de dados do MuOnline/GameServer para ler e escrever informações de jogadores.
Preciso saber PHP para criar um plugin?
Depende do WebEngine usado. A maioria dos painéis da comunidade (baseados em CodeIgniter ou Laravel) usa PHP; alguns projetos mais novos usam .NET/C#. Você precisa conhecer a linguagem do painel específico e o esquema do banco (MSSQL na maioria dos emuladores).
Um plugin pode executar comandos direto no personagem do jogador?
Sim, desde que o plugin escreva nas tabelas ou stored procedures que o GameServer já lê, como filas de comando, correio in-game ou tabelas de itens. Nunca edite diretamente colunas sensíveis (like Money, Level) sem passar pelas procedures oficiais, sob risco de duplicar itens ou zerar personagens.
Como testo um plugin sem afetar o servidor de produção?
Sempre use um ambiente de staging com uma cópia do banco de dados e, se possível, um GameServer de teste. Só publique no ambiente real depois de validar consultas, tempos de resposta e casos de erro (conta duplicada, item inexistente, saldo insuficiente).
É seguro expor uma API dentro do plugin para o cliente do jogo consumir?
Sim, mas exige autenticação (token por sessão), rate limiting e validação de entrada rigorosa. Uma API mal protegida vira porta de entrada para bots de votação, duplicação de cash ou ataques de força bruta contra contas.