El Event Engine DSL es un lenguaje basado en YAML que te permite definir eventos automatizados en tu servidor sin escribir código Java. Cada archivo YAML dentro de plugins/CustomEventEngine/events/ describe qué disparadores activan el evento, qué condiciones deben cumplirse, y qué acciones se ejecutan.
- Automatizar comportamientos de servidor por intervalos o condiciones.
- Mantener una configuración clara y editable por admins no programadores.
- Permitir escalabilidad y modularidad del contenido.
[YAML en /events]
|
v
EventLoader -> EventDefinition -> FlowCompiler -> ExecutionPlan
| |
v v
Trigger + TargetResolver RuntimeScheduler
| |
v v
TerritorialTriggerDispatcher EventRuntime.tick()
Trigger (interval) -> selecciona chunk -> startEvent()
-> valida condiciones -> bloquea chunks -> ejecuta flow (acciones/delay)
- El engine se inicializa al arrancar el plugin.
- Carga definiciones desde
events/. - Registra triggers y arranca los schedulers.
- Expande el área del evento si está habilitado.
Implementaciones clave:
- EventLoader: carga y parseo de YAML.
- EventEngine: orquestación y ejecución runtime.
- Paper/Spigot 1.21+
- Java 21
- Compila el plugin.
- Copia el jar a
plugins/. - Inicia el servidor para generar
config.yml,messages.ymly la carpetaevents/.
scheduler:
tick-interval: 1
persistence:
enabled: true
interval-seconds: 60
expansion:
enabled: true
interval-ticks: 20
debug:
chunk-lookup: false- No se cargan eventos: asegúrate de que el YAML tenga la clave
event. - Evento no encontrado: revisa
event.ido el nombre del archivo. - No se ejecuta: usa
/cee reloadtras modificar archivos. - El comando dice "no hay evento" pero sí existe: activá
debug.chunk-lookup: truey ejecutá/cee event status <mundo> <x> <z>para ver el mapeo de coordenadas a chunk en consola.
event:
id: "mi_evento"
trigger:
type: "interval"
every: "60s"
conditions:
cond1:
type: "players_online"
min: 3
flow:
nodes:
- action: "spawn_lightning"
- delay: "5s"- String: ids, nombres y tipos (
type). - int: cantidades (
min,radius). - boolean:
expansion.enabled. - duraciones:
ms,s,m,h,t(ticks).
Ejemplos de duración:
500ms≈ 10 ticks5s= 100 ticks1m= 1200 ticks
Podés declarar variables dentro de cada evento. Se guardan en el contexto del evento y se pueden usar desde condiciones/acciones.
Estructura:
event:
variables:
fase:
type: string
scope: local
initial: "inicio"
description: "Estado del evento"Campos:
type:string,number,boolean,array,object.scope:local(por runtime) oglobal(compartida entre runtimes del engine).initial(opcional): valor inicial.description(opcional): texto descriptivo.
Reglas:
- El nombre debe ser
A-Za-z_seguido deA-Za-z0-9_. - Si
initialno existe, se usa un default por tipo (ej:0,false,[],{},""). - Si una variable es
global, el engine junta las definiciones de todos los eventos. Si dos eventos declaran la misma variable global con distintotype, se considera error. - Escritura de globales: para modificar una variable
globalconset_variable, el evento debe declararla explícitamente conscope: global. Si no está declarada en el evento,set_variableescribirá una variable local (aunque exista una global con el mismo nombre).
Inicialización avanzada:
- Referencia:
"${otra_variable}"copia el valor de otra variable. - Expresión:
"= <expr>"evalúa una expresión MVEL al iniciar el runtime.
Ejemplo (dependencias + expresión):
event:
variables:
base:
type: number
scope: global
initial: 10
bonus:
type: number
scope: local
initial: "= base + 5"
mensaje:
type: string
scope: local
initial: "Listo"En strings (por ejemplo broadcast.message o send_participants.message) podés inyectar variables:
{variable}${variable}
Ejemplo:
- action: "broadcast"
message: "&aFase: {fase} | Bonus: ${bonus}"event, id, trigger, conditions, variables, flow, nodes, action, delay, scope, expansion, target, chunk_load_rules, chunk_unload_rules
event.iden minúsculas con guiones o guiones bajos.- Tipos como ids cortos:
spawn_lightning,players_online.
/cee help [comando](permisocee.help)/cee list [pagina]y/cee list all(permisocee.view)/cee reloady/cee reload silent(permisocee.admin)/cee event start <evento> [mundo x z](permisocee.admin)/cee event stop [mundo x z](permisocee.admin)/cee event status [mundo x z](permisocee.view) (incluye eventos pendientes por intervalos)/cee event inspect [mundo x z](permisocee.admin)/cee event purge chunk [mundo x z](permisocee.admin)/cee event purge world <mundo>(permisocee.admin)/cee event purge region <mundo> <x1> <z1> <x2> <z2>(permisocee.admin)/cee player info <jugador>(permisocee.view)
- Si hay un runtime bloqueando el chunk objetivo, se muestra como evento activo.
- Además, lista los eventos con trigger
intervalregistrados y el tiempo restante (ticks) para el próximo disparo. /cee event stopdesregistra el scheduling del evento detenido si estaba configurado con triggerinterval.
- Purga = cancela runtimes activos, elimina sus tasks runtime y libera los chunks bloqueados para reutilización.
purge chunkacepta<mundo x z>y, si no encuentra runtime, intenta resolverx/zcomo coordenadas de bloque y como coordenadas de chunk.purge regiontoma un rectángulo y purga todo runtime que tenga algún chunk dentro. Interpretax/zcomo coordenadas de bloque (y si no encuentra nada, reintenta como coordenadas de chunk).- Flag
--include-schedulers: además desregistra schedulers de triggerintervalde los eventos purgados para que no vuelvan a dispararse automáticamente.
Ejemplos:
/cee event purge chunk world 100 200/cee event purge world world/cee event purge region world 0 0 512 512/cee event purge world world --include-schedulers
trigger.type = intervalconevery(duración).trigger.type = commandconcommand(opcional),permission(opcional) ycancel(opcional).- Triggers basados en eventos Bukkit:
trigger.type = on_player_jointrigger.type = on_player_death_insidetrigger.type = on_player_damage_insidetrigger.type = on_player_break_block_insidetrigger.type = on_player_place_block_insidetrigger.type = on_chunk_entertrigger.type = on_chunk_exittrigger.type = on_weather_changetrigger.type = on_thunder_starttrigger.type = on_world_difficulty_changetrigger.type = on_entity_death_insidetrigger.type = on_mob_spawn_inside
conditions.<key>.type = players_onlineconmin.conditions.<key>.type = expressionconexpression.conditions.<key>.type = world_timeconminymax.conditions.<key>.type = random_chanceconchance.conditions.<key>.type = variable_equalsconkeyyvalue.conditions.<key>.type = is_day(sin parámetros).conditions.<key>.type = is_night(sin parámetros).conditions.<key>.type = is_raining(sin parámetros).conditions.<key>.type = is_thundering(sin parámetros).conditions.<key>.type = participants_count_gtconvalue.conditions.<key>.type = participants_count_ltconvalue.conditions.<key>.type = any_participant_has_permissionconpermission.conditions.<key>.type = world_difficulty_iscondifficulty.event.variablespara variableslocal/globalcon inicialización.flow.nodes[].action = spawn_lightning,broadcast,clear_weather,set_time,send_participants,send_message,set_variable,log,execute_console_command,execute_player_command.flow.nodes[].delay = <duración>.flow.nodes[].loop = <repeticiones>para repetir un bloque N veces.flow.nodes[].condition_looppara repetir un bloque N veces si una expresión se cumple por iteración.flow.nodes[].configpara parámetros de la acción.scope.type = chunk_radiusconradius.target.strategy = random_loaded_chunk.
Los parámetros de las acciones se pueden definir de dos formas equivalentes:
- Como claves al mismo nivel del nodo (
message: ...,time: ..., etc.) - Dentro de
config:
| Action type | Utilidad | Parámetros disponibles | Ejemplo mínimo |
|---|---|---|---|
broadcast |
Enviar mensaje a todos los jugadores del servidor. | message (string) |
- action: "broadcast"\n message: "&aHola" |
clear_weather |
Quitar lluvia/tormenta y reiniciar duraciones climáticas del mundo. | (sin parámetros) | - action: "clear_weather" |
send_participants |
Enviar mensaje solo a los participantes del evento. | message (string) |
- action: "send_participants"\n message: "&eEvento activo" |
send_message |
Alias de send_participants. |
message (string) |
- action: "send_message"\n message: "&eEvento activo" |
set_time |
Ajustar el tiempo del mundo. | time (ticks/duración o ${variable} o = expr) |
- action: "set_time"\n time: "13000t" |
set_variable |
Guardar una variable en el contexto del evento. | key (string), value (any, soporta ${var} y = expr) |
- action: "set_variable"\n key: "fase"\n value: "inicio" |
log |
Escribir mensaje en logs del servidor. | message (string, soporta variables) |
- action: "log"\n message: "Fase: {fase}" |
execute_console_command |
Ejecutar comando como consola. | command (string, soporta variables) |
- action: "execute_console_command"\n command: "say Hola" |
execute_player_command |
Ejecutar comando como cada participante (online). | command (string, soporta variables) |
- action: "execute_player_command"\n command: "msg @s Hola" |
spawn_lightning |
Spawnear un rayo en la ubicación del evento. | (sin parámetros) | - action: "spawn_lightning" |
| Condition type | Utilidad | Parámetros disponibles | Ejemplo mínimo |
|---|---|---|---|
expression |
Evaluar una expresión MVEL para decidir si el evento puede ejecutarse. | expression (string) |
type: "expression"\nexpression: "playersOnline >= 5" |
players_online |
Requerir un mínimo de jugadores conectados. | min (int) |
type: "players_online"\nmin: 5 |
random_chance |
Permitir ejecución con una probabilidad aleatoria. | chance (number). Acepta 0.0–1.0 o porcentaje (25 = 25%). |
type: "random_chance"\nchance: 25 |
variable_equals |
Comparar una variable del contexto con un valor esperado. | key (string), value (string) |
type: "variable_equals"\nkey: "fase"\nvalue: "inicio" |
world_time |
Validar que el tiempo del mundo esté dentro de un rango (ticks 0–23999). Soporta rangos que cruzan 24000. | min (ticks), max (ticks) |
type: "world_time"\nmin: "12000t"\nmax: "23000t" |
is_day |
Verificar si el mundo está en día. | (sin parámetros) | type: "is_day" |
is_night |
Verificar si el mundo está en noche. | (sin parámetros) | type: "is_night" |
is_raining |
Verificar si está lloviendo. | (sin parámetros) | type: "is_raining" |
is_thundering |
Verificar si hay tormenta eléctrica. | (sin parámetros) | type: "is_thundering" |
participants_count_gt |
Verificar si el evento tiene más participantes que un valor. | value (int) |
type: "participants_count_gt"\nvalue: 2 |
participants_count_lt |
Verificar si el evento tiene menos participantes que un valor. | value (int) |
type: "participants_count_lt"\nvalue: 10 |
any_participant_has_permission |
Verificar si algún participante tiene un permiso. | permission (string) |
type: "any_participant_has_permission"\npermission: "cee.admin" |
world_difficulty_is |
Verificar la dificultad actual del mundo. | difficulty (string) |
type: "world_difficulty_is"\ndifficulty: "HARD" |
trigger:
type: "interval"
every: "60s"Activa el evento cuando un jugador ejecuta un comando.
command(opcional): comando a escuchar. Si se omite, se usa/cee event start <event>.permission(opcional): permiso requerido para disparar el evento. Default:cee.admin.cancel(opcional): sitrue, cancela la ejecución del comando original. Default:true.
Ejemplo (comando personalizado):
trigger:
type: "command"
command: "/tormenta"
permission: "cee.admin"
cancel: trueEjemplo (default del plugin para este evento):
trigger:
type: "command"Estos triggers activan el evento cuando ocurre un evento Bukkit. Se pueden usar sin parámetros adicionales:
trigger:
type: "on_player_join"Tipos disponibles:
on_player_joinon_player_death_insideon_player_damage_insideon_player_break_block_insideon_player_place_block_insideon_chunk_enteron_chunk_exiton_weather_changeon_thunder_starton_world_difficulty_changeon_entity_death_insideon_mob_spawn_inside
Notas:
on_world_difficulty_changees un trigger por polling (interno) y aceptaevery(duración). Default:20t.
Repite una subsecuencia un número fijo de veces.
flow:
nodes:
- loop: 3
nodes:
- action: "log"
message: "Iteración"Repite una subsecuencia un máximo de N veces, pero antes de cada iteración evalúa una expresión MVEL.
Si la expresión es false, el loop se corta y se continúa con el siguiente nodo del flow.
flow:
nodes:
- action: "set_variable"
key: "contador"
value: 0
- condition_loop:
times: 10
expression: "contador < 3"
nodes:
- action: "set_variable"
key: "contador"
value: "= (contador == null ? 0 : contador) + 1"
### Limitaciones actuales
- `chunk_load_rules` y `chunk_unload_rules` se parsean, pero no se aplican aún en runtime.
## 5) Tutorial paso a paso
### Paso 1: evento mínimo
```yaml
event:
id: "primer_evento"
trigger:
type: "interval"
every: "30s" # cada 30 segundos
flow:
nodes:
- delay: "5s" # espera antes de ejecutar la acción
- action: "spawn_lightning"event:
id: "tormenta_con_gente"
trigger:
type: "interval"
every: "1m"
conditions:
online:
type: "players_online"
min: 5
probabilidad:
type: "random_chance"
chance: 25
flow:
nodes:
- action: "spawn_lightning"event:
id: "tormenta_expansiva"
trigger:
type: "interval"
every: "2m"
scope:
type: "chunk_radius"
radius: 0
expansion:
enabled: true
max_radius: 2
step: 1
interval: "20t"
flow:
nodes:
- action: "send_participants"
message: "&e¡Evento activo!"
- delay: "5s"
- action: "clear_weather"/cee reload/cee list/cee event start tormenta_expansiva/cee event inspect
event:
id: "lluvia_manual"
trigger:
type: "interval"
every: "10m" # trigger no se usará si lo inicias manualmente
flow:
nodes:
- action: "spawn_lightning"event:
id: "tormenta_global_controlada"
trigger:
type: "interval"
every: "5m"
conditions:
online:
type: "players_online"
min: 10
horario:
type: "world_time"
min: "12000t"
max: "23000t"
flow:
nodes:
- delay: "2s"
- action: "spawn_lightning"- Usar intervalos razonables para no saturar el scheduler.
- Combinar triggers con conditions para reducir cargas.
- Empezar con
scope.radius = 0y escalar si es necesario. - Evitar
expansion.max_radiusaltos en servidores concurridos.
- Evento no encontrado: revisar
event.id. - Chunk ocupado: detener evento con
/cee event stop. - Condiciones fallidas: verificar
conditionsymin. - No hay runtime activo: el evento no está en ese chunk.
El engine expone una API para registrar acciones/condiciones/triggers personalizados. Ejemplo conceptual de acción:
public final class SaveMetricAction implements Action {
private final DataSource dataSource;
public SaveMetricAction(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public void execute(EventContext context) {
// Guardar métricas en BD
}
}- Crear evento simple.
- Usar
/cee event start <id>. - Usar
/cee event statusy/cee event inspect. - Revisar logs y
runtime.jsonsi está habilitada la persistencia.
- Evitar triggers de alta frecuencia sin necesidad.
- Reducir expansión y scope en eventos intensivos.
- Monitorear eventos activos para evitar saturación.
- Event: definición de comportamiento en YAML.
- Trigger: sistema que dispara el evento.
- Condition: filtro previo a la ejecución.
- Flow: secuencia de nodos (
action,delay). - Scope: chunks bloqueados por el evento.
- Expansion: expansión gradual del área.
- Chunk target: estrategia para seleccionar chunk.
- Variables de contexto: datos guardados en el runtime del evento.
Acciones nuevas:
clear_weather: limpia lluvia y tormenta.set_time: ajusta el tiempo del mundo.send_participants: envía mensaje a participantes.send_message: alias desend_participants.set_variable: guarda una variable en el contexto.log: escribe mensaje en logs del servidor.execute_console_command: ejecuta comando como consola.execute_player_command: ejecuta comando como participantes.
Condiciones nuevas:
world_time: valida rango de tiempo del mundo.random_chance: valida probabilidad aleatoria.variable_equals: compara una variable con un valor.is_day: valida día.is_night: valida noche.is_raining: valida lluvia.is_thundering: valida tormenta.participants_count_gt: compara cantidad de participantes.participants_count_lt: compara cantidad de participantes.any_participant_has_permission: valida permisos en participantes.world_difficulty_is: valida dificultad del mundo.
Duraciones comunes:
10t= 10 ticks500ms≈ 10 ticks5s= 100 ticks1m= 1200 ticks
- Compatibilidad: Paper/Spigot 1.20+, Java 21.
- No hay migraciones documentadas en el repositorio actual.
- Librerías: MVEL para condiciones
expression.
flow:
nodes:
- action: "set_variable"
key: "fase"
value: "inicio"
- action: "send_participants"
message: "&aFase: {fase}"
- action: "set_time"
time: "13000t"
- action: "clear_weather"conditions:
prob:
type: "random_chance"
chance: 0.2
noche:
type: "world_time"
min: "12000t"
max: "23000t"
fase:
type: "variable_equals"
key: "fase"
value: "inicio"