Categoría: Plugins de Minecraft

Crear un servidor de Minecraft con un equipo de IA#2¡13 módulos en un solo plugin! La estructura del core y el incidente del 'daño 1000029' 🧩

Esta entrada es una traducción del original en coreano.

De un vistazo

Este servidor tiene un solo jar de plugin con el core y 13 módulos. Los módulos no se llaman entre sí: solo se conectan mediante los servicios del core. Te cuento dos bugs reales de esta estructura y las funciones de recarga y reinicio que releen la configuración sin apagar el servidor.

  • Paper 1.21.11 · Java · 1 jar de plugin = core + 13 módulos · corregido de verdad del 2026-09-29 al 30
  • Los módulos no dependen entre sí: solo se conectan con servicios (interfaces) del core. Por eso los equipos pudieron desarrollar en paralelo
  • Que un módulo entero se apague al romper la regla del prefijo de los nodos de permiso era una protección intencional
  • Daño 1000029 = 'vida restante 29 + 1,000,000 para procesar la muerte'. No fallaba el cálculo, sino lo que se mostraba
Índice9 secciones

¡Hola! Soy Mojangcam 😎

En el #1 anterior presenté una forma de trabajar con Claude como líder del equipo y 15 tareas programadas como miembros. Este episodio va de la estructura del propio plugin que construyen esos equipos.

En nuestro servidor solo va un único archivo jar de plugin hecho por nosotros. Dentro, funciones como niveles, combate, habilidades, misiones, tienda y mobs están divididas en 13 módulos. En este episodio vamos a ver

  • 🧩 Por qué armé la estructura core + módulos de esta forma
  • 🔒 El incidente en que dos módulos se apagaron por completo por un solo nombre de permiso
  • 💥 El incidente en que el daño salía como 1000029 al matar un mob
  • 🔄 /리로드 (recargar), que vuelve a leer la configuración sin apagar el servidor, y /재시작 (reiniciar), que lo vuelve a encender solo

Vamos allá.

Pantalla del juego con un enorme número de daño verde, 1000029, sobre un mob


📌 Lo que necesitas (entorno)

  • Servidor: Paper 1.21.11, Java 21 o superior
  • Build: multiproyecto de Gradle (un subproyecto por módulo → todo empaquetado en un solo jar final)
  • BD: MariaDB (guarda el progreso)
  • Plugins externos, solo los mínimos: LuckPerms (permisos) · EssentialsX · WorldEdit

📌 Paso 1 - ¿Por qué "un jar + 13 módulos"? 🧠

Al principio pensé en hacer un plugin distinto para cada función. Pero niveles, combate, habilidades e ítems se pasan números todo el tiempo. Con plugins separados, el orden de carga, sincronizar versiones y compartir la API se vuelven un dolor de cabeza.

Así que lo decidí así.

Capa Función
core archivos de configuración · mensajes · conexión a la BD · registro de permisos · registro de comandos · registro de servicios · vida virtual · HUD
13 módulos economy · quest · job · level · combat · skill · item · companion · world · mob · log · storage, etc.
Regla Un módulo no importa directamente clases de otro módulo. Lo que necesita, lo usa solo a través de las interfaces (servicios) del core

Por ejemplo, si el módulo de combate quiere saber el poder de ataque de un arma, no llama al módulo de ítems: busca el ItemService registrado en el core y lo usa. Si el módulo de ítems está apagado, simplemente no hay servicio, y el módulo de combate funciona con valores por defecto.

Gracias a esta regla, 13 equipos pudieron desarrollar al mismo tiempo. Cada equipo solo tiene que confiar en las interfaces y trabajar.


📌 Paso 2 - El incidente del nombre de permiso que apagó módulos enteros 🔒

Al encender el servidor, aparecieron estas líneas en el log (resumidas).

Texto
[MyServer] quest 모듈 켜기 실패: 권한 노드는 'myserver.quest.' 로 시작해야 합니다 — myserver.qnpc.console
[MyServer] world 모듈 켜기 실패: 권한 노드는 'myserver.world.' 로 시작해야 합니다 — myserver.region.admin

La función de registro de permisos del core comprueba la regla "el nombre del permiso debe empezar por myserver.<모듈>." y, si no se cumple, lanza una excepción. Si hay una excepción, ese módulo no se enciende.

El equipo de misiones usó myserver.qnpc.… para los comandos de NPC, y el equipo de mundo usó myserver.region.… para la protección de zonas. Eran nombres que parecían razonables incluso a ojos de una persona, así que se nos pasaron en la revisión.

Vamos a desglosarlo 💡

  • ¿Por qué tan estricto? : Con 13 módulos, es fácil que los nombres de permisos choquen. Si obligas a usar un prefijo, en LuckPerms puedes dar todos los permisos de un módulo con una sola línea: myserver.quest.*.
  • ¿Por qué apagar el módulo en vez de solo avisar? : Si sigue funcionando con permisos mal registrados, puede pasar que un comando solo para operadores quede abierto a usuarios normales. En una protección, es mejor frenar en seco.

El arreglo fue solo cambiar dos nombres. Pero para evitar el mismo accidente, añadí al procedimiento del líder del equipo una línea que lo comprueba automáticamente antes de fusionar.

Terminal
# En la rama module/<equipo>, buscar nodos de permiso que rompan la regla del prefijo (pasa si el resultado está vacío)
m=quest
git grep -hoE 'registerPermission\([^;]*' module/$m -- "$m/src/main" \
  | grep -oE '"myserver\.[a-z_]+\.' | awk -F. -v m=$m '$2!=m'

📌 Paso 3 - El incidente del daño 1000029 💥

En el juego, justo al matar un mob, apareció sobre su cabeza un número absurdo como 1000029. Pero el mob no había muerto de un solo golpe. Los ataques anteriores mostraban números normales; solo el último golpe era raro.

Causa: "vida virtual" y "matar sí o sí"

En este servidor la vida sube hasta miles o decenas de miles. La vida de vanilla (máximo 1024) no alcanza, así que el core gestiona aparte una vida virtual. Cuando la vida virtual llega a 0, el core tiene que matar al mob sin falta también del lado de vanilla, y para eso hacía esto.

Java
// core: cuando la vida virtual llega a 0, matarlo sin falta también en vanilla
e.damage(e.getHealth() + 1_000_000, damageSource);   // daño interno para procesar la muerte

Para vanilla, recibió un golpe de "vida restante + 1,000,000". Y el módulo de combate, que muestra los números de daño, recibió también ese evento de daño interno y lo mostró tal cual. Vida vanilla restante 29 + 1,000,000 = 1000029. El número cuadra exactamente 😂

Solución: no mostrar el daño de procesamiento de muerte

El tipo de este daño interno es GENERIC_KILL. Solo añadí una línea al módulo de combate.

Java
// combat: listener que muestra el número de daño (MONITOR)
if (event.getDamageSource().getDamageType() == DamageType.GENERIC_KILL) return; // el daño interno para procesar la muerte no se muestra como número
double shown = event.getFinalDamage();

El cálculo real del daño estuvo bien desde el principio; era un bug en el que solo fallaba lo que se mostraba. Este tipo de bug lo ves enseguida con una sola mirada dentro del juego, pero es difícil de atrapar con pruebas unitarias. Aquí brilló el papel del que hablé en el episodio 1: "las personas entran al juego, miran y lo cuentan".


📌 Paso 4 - Cambiar la configuración sin apagar el servidor: /리로드 · /재시작 🔄

Como el equipo de IA modifica la configuración y el jar varias veces al día, apagar y encender el servidor cada vez era demasiado engorroso. Así que añadí dos cosas al core.

Comando Qué hace
/리로드 (/myserver reload) Vuelve a leer los archivos de configuración del core → de todos los módulos encendidos, con un resumen ✓/✗ por módulo. Si una configuración está mal, solo falla ese módulo y se conserva la configuración anterior
/리로드 level Vuelve a leer solo ese módulo
/재시작 60 Cuenta atrás de 60 segundos (chat + barra de jefe) y luego apaga el servidor → se vuelve a encender solo
(automático) Si cambia un archivo de configuración, lo vuelve a leer solo; si entra un jar nuevo en plugins/update, avisa a los operadores

Lo de "se vuelve a encender solo" no se puede hacer solo con código del lado del servidor. Justo antes de apagarse, el plugin crea un archivo marcador, y el archivo por lotes que arranca el servidor lo ve y lo vuelve a encender.

Símbolo del sistema
@echo off
chcp 65001 > nul
cd /d "%~dp0"
:loop
if exist "plugins\MyServer\.restart" del /q "plugins\MyServer\.restart"
java -Xmx8G -jar server.jar nogui
if exist "plugins\MyServer\.restart" (
  echo 재시작 요청 확인 - 5초 뒤 다시 켭니다. 중지하려면 Ctrl+C
  timeout /t 5
  goto loop
)
echo 서버가 종료되었습니다.
pause

Vamos a desglosarlo 💡

  • Archivo .restart : Lo crea /재시작. Si lo apagas con un simple /stop, el archivo no existe y no se vuelve a encender.
  • Borrarlo al inicio del bucle : Evita reinicios infinitos por restos de un reinicio anterior.
  • Memoria (-Xmx) : Ajústala según las especificaciones de tu servidor.

📌 Errores frecuentes

  1. El módulo no se enciende — "el nodo de permiso debe empezar por …" (권한 노드는 … 로 시작해야 합니다) Causa: incumplir la regla del prefijo del nombre de permiso → Solución: renombrarlo a myserver.<모듈>.…
  2. Después de /리로드, solo un módulo da ✗ Causa: error de sintaxis en el JSON de configuración de ese módulo → Solución: mira el número de línea en el log, corrígelo y vuelve a hacer /리로드. Mientras tanto, sigue funcionando con la configuración anterior
  3. El servidor no se enciende después de reiniciar Causa: lo arrancaste directamente con java -jar en lugar del archivo por lotes → Solución: arráncalo con el archivo por lotes del bucle

📌 Para terminar

Hoy repasamos la estructura del plugin y dos incidentes reales.

  • Un jar + 13 módulos; los módulos solo se conectan mediante servicios del core
  • La regla del prefijo de permisos es una protección que apaga el módulo + una comprobación automática antes de fusionar
  • 1000029 era el daño interno de procesamiento de muerte mostrado como número
  • Con /리로드 · /재시작, operar el servidor es cómodo aunque el equipo de IA haga cambios a menudo

En el próximo episodio toca hablar del balance de los números. Cómo multipliqué por 10 la vida y el daño de una vez, la curva de experiencia de "12 horas hasta el nivel 30" y la causa inesperada que encontró la IA del equipo de planificación al simular 6 clases. ¡No te lo pierdas! 🙌

Yo marqué la dirección de este artículo y lo redacté junto con Claude. Este servidor es un proyecto personal, no es contenido oficial de Minecraft y no tiene relación con Mojang ni Microsoft.

Serie · Parte 2 de 5

Crear un servidor de Minecraft con un equipo de IA

  1. #1¡1 líder de equipo IA + 15 miembros creando un plugin MMORPG! 🤖
  2. #2¡13 módulos en un solo plugin! La estructura del core y el incidente del 'daño 1000029' 🧩Estás aquí
  3. #3¡Números ×10, curva de experiencia de 12 horas y simulación de 6 clases! 📊
  4. #4¡Un flujo de paquetes de recursos donde ChatGPT dibuja y Claude revisa! 🎨
  5. #5¡Gestionar el equipo de desarrollo con un bot de Discord! Foro de decisiones, lista de comandos y canal de paquetes de recursos 🤝

Autor

Mojangcam

Un registro práctico de desarrollo e IT: código que escribo, servidores que mantengo y los errores que resuelvo por el camino.