# Desenvolvimento ## Estrutura ``` server/openmu/ submodule OpenMU (NÃO editar — ver "upstream") clients/web/ submodule OpenMu-Client-Babylon custom/server/ nossos plugins C# (MuCustom.*) + startup hook services/ Dockerfiles nossos (openmu, web-client, game-proxy) infra/ Traefik, nginx tools/ ferramentas (ws-login-probe.ts) scripts/ operação (bash) docs/ documentação (servida pelo container docs) ``` ## Servidor (OpenMU + camada custom) ### Rebuild ```bash docker compose build openmu && docker compose up -d openmu docker compose logs -f openmu | grep -E "MuCustom|Error" ``` O log deve conter `[MuCustom.StartupHook] loaded MuCustom.Gameplay …`. ### Testes do upstream (sem Docker, se houver .NET 10 SDK) ```bash cd server/openmu dotnet build src/Startup/MUnique.OpenMU.Startup.csproj -c Release -p:ci=true dotnet test tests/MUnique.OpenMU.Network.Packets.Tests -c Release -p:ci=true # demais: tests/*Tests (Web.Tests usa bunit) ``` `-p:ci=true` é obrigatório (sem ele o build tenta rodar `npx`/TypeScript para gerar docs). Resultado em 2026-10-02 (`204c11b5`): Packets 616 ✅, Network 73 ✅ (4 skip), AttributeSystem 44 ✅, Pathfinding 10 ✅, PlugIns 41 ✅, ChatServer 26 ✅, MUnique.OpenMU.Tests 1338 ✅, Persistence.Initialization 75 ✅ (6 skip). ### Rodar o servidor sem Docker (debug rápido, banco em memória) ```bash cd server/openmu/src/Startup/bin/Release ASPNETCORE_URLS=http://127.0.0.1:8090 dotnet MUnique.OpenMU.Startup.dll -demo -autostart -resolveIP:loopback # com nossos plugins: DOTNET_STARTUP_HOOKS=/caminho/MuCustom.StartupHook.dll MUCUSTOM_PLUGIN_DIR=/caminho/plugins dotnet ... ``` `-demo` não persiste nada. Útil para testar plugins em segundos. ### Criando um plugin 1. Crie o projeto em `custom/server/src/MuCustom./` (copie o `.csproj` de `MuCustom.Gameplay`). 2. Implemente uma interface de ponto de extensão do OpenMU (exemplos no upstream): - chat command: `IChatCommandPlugIn` (`GameLogic/PlugIns/ChatCommands`) - tarefa periódica: `IPeriodicTaskPlugIn` / `PeriodicTaskBasePlugIn` - eventos de jogo: `GameLogic/PlugIns/I*PlugIn.cs` (morte de monstro, level up, entrar no mapa…) - handlers de pacote: `IPacketHandlerPlugIn` (`GameServer/MessageHandler`) - views por cliente: `IViewPlugIn` + `[MinimumClient]` - configuração editável no Admin: `ISupportCustomConfiguration` + `ISupportDefaultCustomConfiguration` 3. `[Guid("...novo...")]`, `[PlugIn]`, `[Display(Name = "MuCustom: ...")]`. 4. `docker compose build openmu && docker compose up -d openmu` → Admin Panel → *Plugins* (filtrar "MuCustom"). 5. Registre na [matriz de compatibilidade](compatibility-matrix.md). ### Boas práticas de merge com o upstream - **Não edite `server/openmu`.** Se precisar mudar o core: abra issue/PR no MUnique/OpenMU, ou, se urgente, mantenha um patch mínimo em `services/openmu/patches/000N-*.patch` (aplicado no build por `apply.sh`), com teste e justificativa em `docs/decisions.md` (exemplo: D19, B13). ### Como alteramos o core do OpenMU (patches) O submodule `server/openmu` fica no SHA fixado; a correção vive no branch local `mu-custom/server-fixes` (não é enviado a lugar nenhum) e vira patch: ```bash cd server/openmu git checkout mu-custom/server-fixes # ou: git checkout -b mu-custom/server-fixes # ... corrigir + escrever o teste em tests/MUnique.OpenMU.Tests, commitar no branch ... git format-patch ..HEAD -o ../../services/openmu/patches/ # apague os .patch antigos antes git checkout ``` Testes em container (o SDK é o mesmo do Dockerfile; no Git Bash use `MSYS_NO_PATHCONV=1`): ```bash docker run --rm -v "$PWD/server/openmu:/src" -v mu-nuget:/root/.nuget/packages -w /src mcr.microsoft.com/dotnet/sdk@sha256: dotnet test tests/MUnique.OpenMU.Tests/MUnique.OpenMU.Tests.csproj -c Release -p:ci=true ``` Só `src/` entra na imagem; o `apply.sh` normaliza para LF os arquivos tocados (o checkout no Windows é CRLF). - Pacotes novos: o upstream exige XML em `src/Network/Packets` e variantes `…Extended` — contribua lá. - Atualizar upstream: `scripts/bump-upstream.sh openmu` → build → `smoke-test.sh` → `VERSIONS.md` → commit. - Siga `server/openmu/docs/CODING_RULES.md` em código que possa voltar ao upstream. ## Cliente web (Babylon) ### Como alteramos o cliente web (patches) O submodule `clients/web` fica **sempre no SHA fixado**. Nossas mudanças vivem em `services/web-client/patches/000N-*.patch` e são aplicadas no build da imagem (`patches/apply.sh`). ```bash cd clients/web git config core.autocrlf false # obrigatório: o upstream mistura CRLF/LF (scripts/lib.sh já faz) git checkout mu-custom/extended-protocol # branch local com a série (não é enviado a lugar nenhum) # ... editar, rodar typecheck e testes em container (abaixo), commitar no branch ... git format-patch ..HEAD -o ../../services/web-client/patches/ # apague os .patch antigos antes git checkout # volta o submodule ao estado do upstream ``` Ao atualizar o upstream (`scripts/bump-upstream.sh web`): `git rebase` do branch sobre o novo SHA, regerar os patches, rebuild e smoke test. Um patch que não aplica **falha o build**, de propósito. Pacote novo do servidor: copie o `` de `server/openmu/src/Network/Packets/ServerToClient/ServerToClientPackets.xml` para `src/common/packets/packetsDefinitions/` e regere com `bun run generate` (em container, com `node_modules`); desfaça a mudança que o gerador faz em `src/common/packets/index.ts` (o upstream editou esse arquivo à mão). Antes de `docker compose build web-client`, volte o submodule ao SHA fixado: o Dockerfile copia a cópia de trabalho e aplica os patches por cima (no branch eles já estariam aplicados e o build falha). ### Rebuild ```bash docker compose build web-client && docker compose up -d web-client # bundle docker compose build ws-proxy && docker compose up -d ws-proxy # proxy ``` Os `VITE_*` são **fixados no build** (`WEB_*` no `.env`). Mudou domínio/porta → rebuild. Overrides em runtime pela URL (do próprio cliente): `?cs=host:porta`, `?ws=wss://host:porta`, `?gs=cs`, `?list=off`. ### Lint / typecheck / testes (em container, sem instalar Bun no host) ```bash docker run --rm -v "$PWD/clients/web:/app" -w /app oven/bun:1.4.2-slim sh -c \ "bun install --frozen-lockfile && bun run tsc && bun run lint && bun run test && bun test proxy/" ``` ### Proxy WS↔TCP — notas de segurança (`clients/web/proxy/main.ts`) | Aspecto | Upstream | Nossa configuração | |---|---|---| | Alvo da conexão | escolhido pelo cliente (`?host&port`) | `ALLOW_TARGETS` obrigatório (compose falha sem) | | TLS/WSS | não (ws puro) | Traefik termina TLS | | Limite de conexões por IP | não existe | 🚧 backlog: middleware `inFlightConn`/`rateLimit` no Traefik | | Tamanho máx. de mensagem / idle | defaults do Bun (16 MB / 120 s) | 🚧 backlog: contribuição upstream | | Graceful shutdown | não trata SIGTERM | `stop_grace_period` do compose; 🚧 upstream | | Log de pacotes | **ligado por padrão** | `LOG_PACKETS=off` | | IP real do jogador | não lê `X-Forwarded-For` | IP real só nos access logs do Traefik | | Tracker/journal | SQLite em `~/.mu-proxy` | volume `wsproxy-data`, whispers desligados, retenção 7 dias | ## Debug e logs ```bash ./scripts/logs.sh openmu # segue ./scripts/logs.sh openmu --errors # só erros docker compose exec openmu ls /app/logs # arquivos do Serilog (volume openmu-logs) MU_ENV=dev → dashboard do Traefik em http://127.0.0.1:8088 WS_LOG_PACKETS=on (só em dev!) → hexdump de todos os pacotes no ws-proxy ``` ## Testes de ponta a ponta ```bash ./scripts/smoke-test.sh # infra + protocolo + HTTP + WS + login TCP estendido + login web docker compose --profile tools run --rm login-probe --account test0 --host 127.0.0.1 --connect-port 44406 docker compose exec ws-proxy bun tools/ws-login-probe.ts --account test0 ./scripts/game-test.sh # bot entra no jogo, comandos, quests, combate/EXP e sistemas 0xFC (contas de teste) ./scripts/audit-events.sh [bc] [ds] [cc] # auditoria dos eventos pelo bot (reinicia o openmu; relatório em docs/audit/eventos.md) ``` ### Bot de protocolo (`tools/ws-game-bot.ts`) Faz o caminho do navegador (WebSocket → ws-proxy → OpenMU, versão 20404) sem interface. Útil para testar comandos, regras do servidor e combate sem clicar no jogo: ```bash docker compose exec ws-proxy sh -c "cd /app && bun tools/ws-game-bot.ts --account testgm --character testgmDk --chat '/resetinfo;/online' --expect 'Reset' --teleport '200 150' --spawn 3 --trace" ``` `--chat` envia comandos e imprime as respostas; `--expect` falha se nenhuma resposta tiver o texto; `--teleport`/`--spawn` (GM) criam um monstro fora da safe zone e o bot ataca até matar; `--trace` lista os pacotes recebidos. Saída: uma linha JSON por etapa; exit 0 = tudo certo. Monstro não nasce em safe zone.