Motor de Scripting Lua de MagmaCore
MagmaCore proporciona el sandbox de Lua compartido y el runtime genérico ScriptInstance usados por varios plugins de Nightbreak. Los poderes de jefe de EliteMobs, los scripts de NPC de EliteMobs, los props de FreeMinecraftModels y los ítems de FreeMinecraftModels se ejecutan todos sobre este runtime y comparten la misma validación de archivos, sandbox, ciclo de vida del context, propiedad del planificador, contrato de cooldowns, contrato de logging, motor de vigilancia de zonas y modelo de context.state.
Los props/ítems de FreeMinecraftModels y los scripts de NPC de EliteMobs heredan directamente las tablas de context genéricas de MagmaCore (context.world, context.zones, context.scheduler, context.cooldowns, context.log, context.event, context.player y context.state) y añaden su tabla propietaria (context.prop, context.item o context.npc). Los poderes de jefe de EliteMobs se ejecutan sobre el mismo runtime pero exponen variantes más ricas y específicas de jefe para varias tablas (context.world, context.zones, context.cooldowns, context.scheduler, context.log, context.event) además de helpers exclusivos de jefe como context.players, context.entities, context.vectors, context.settings y context.script.
Actualmente, los siguientes plugins usan este motor:
- EliteMobs -- Poderes Lua para jefes personalizados (hooks como
on_boss_damaged_by_player,on_enter_combat, etc.) y scripts Lua de NPC para comportamiento de interacción/proximidad (hooks comoon_npc_interact,on_npc_proximity_enter, etc.) - FreeMinecraftModels -- Scripts Lua para props y objetos personalizados (hooks como
on_right_click,on_left_click,on_equip, etc.)
Esta página documenta las características compartidas del motor. Para hooks, APIs y flujos de trabajo específicos de cada plugin, consulta las páginas de plugin enlazadas arriba.
Mini Introducción a Lua
Si eres completamente nuevo en Lua, aquí tienes la sintaxis mínima que necesitas para escribir scripts para cualquier plugin de Nightbreak.
Variables
Usa local para almacenar un valor:
local cooldown_key = "fire_burst"
local damage_multiplier = 1.5
local significa que la variable pertenece solo a este archivo o bloque.
Funciones
Las funciones son bloques de lógica reutilizables:
local function warn_player(player)
player:send_message("&cMove!")
end
Más adelante, puedes invocarla:
warn_player(some_player)
Comprobaciones if
Usa if cuando algo solo deba ocurrir a veces:
if context.player == nil then
return
end
Eso significa "si no hay jugador para este hook, detente aquí".
nil
nil significa "ningún valor". Es la versión de Lua de "no hay nada aquí".
A menudo comprobarás nil con:
if context.event ~= nil then
-- hacer algo con el evento
end
~= significa "no es igual a".
Tablas
Lua usa tablas para varios propósitos:
- Listas
- Objetos con claves nombradas
- La definición final del script que se retorna
Ejemplo de una tabla con claves nombradas:
local particle = {
particle = "FLAME",
amount = 1,
speed = 0.05
}
Retornando la definición del script
Al final de cada archivo de script, retornas una tabla:
return {
api_version = 1,
on_spawn = function(context)
end
}
Esa tabla retornada es el archivo de script.
Comentarios
Usa -- para escribir una nota para humanos:
-- Este cooldown evita que el ataque se dispare en cada golpe
context.cooldowns:set_local(60, "fire_burst")
Sandbox de Lua
Todos los scripts Lua se ejecutan dentro de un entorno LuaJ sandboxeado. Varios globales que podrían acceder al sistema de archivos o al runtime de Java se eliminan. Las reglas del sandbox son idénticas en todos los plugins que usan MagmaCore.
Globales Eliminados
Los siguientes globales estándar de Lua se establecen a nil y no se pueden usar:
| Eliminado | Por qué |
|---|---|
debug | Expone el estado interno de la VM |
dofile | Acceso al sistema de archivos |
io | Acceso al sistema de archivos |
load | Carga arbitraria de código |
loadfile | Acceso al sistema de archivos |
luajava | Acceso directo a clases de Java |
module | Sistema de módulos (no necesario) |
os | Acceso al sistema operativo |
package | Sistema de módulos (no necesario) |
require | Sistema de módulos / acceso al sistema de archivos |
Biblioteca Estándar Disponible
Todo lo demás de la biblioteca estándar de Lua funciona con normalidad:
| Categoría | Funciones |
|---|---|
| Matemáticas | math.abs, math.ceil, math.floor, math.max, math.min, math.random, math.sin, math.cos, math.sqrt, math.pi, y todas las demás funciones math.* |
| Cadenas | string.byte, string.char, string.find, string.format, string.gsub, string.len, string.lower, string.match, string.rep, string.sub, string.upper, y todas las demás funciones string.* |
| Tablas | table.insert, table.remove, table.sort, table.concat, y todas las demás funciones table.* |
| Iteradores | pairs, ipairs, next |
| Tipo | type, tostring, tonumber, select, unpack |
| Manejo de errores | pcall, xpcall, error, assert |
| Otros | print, rawget, rawset, rawequal, rawlen, setmetatable, getmetatable |
os no está disponibleLa biblioteca os está completamente eliminada del sandbox. Usa context.world:get_time() para el tiempo del mundo en las superficies genéricas de MagmaCore; los poderes de jefe de EliteMobs lo reciben a través de la base compartida de su tabla world de jefe. Para lógica de tiempo transcurrido, guarda contadores de ticks en context.state mediante el hook on_game_tick.
print escribe en la consola del servidor, pero prefiere context.log:info(msg). Todas las superficies exponen context.log: los scripts de FreeMinecraftModels y los scripts de NPC de EliteMobs exponen info, warn y error; los poderes de jefe de EliteMobs exponen info, warn y debug.
Contrato del Archivo
Cada script Lua debe return una tabla. Esa tabla es intencionadamente estricta.
Campos de Nivel Superior Requeridos y Opcionales
| Campo | Requerido | Tipo | Notas |
|---|---|---|---|
api_version | Sí | Number | Actualmente debe ser 1 |
priority | No | Number | Valor de ordenación opcional. Los scripts de jefe y de NPC de EliteMobs ejecutan primero los valores más bajos. FMM valida el campo, pero los scripts de prop se ejecutan en el orden de la lista scripts: y los scripts de ítem solo vinculan el primer script válido |
| claves de hooks soportadas | No | Function | Deben usar uno de los nombres exactos de hooks soportados por el plugin |
Reglas de Validación
- El archivo debe retornar una tabla.
api_versiones requerido y actualmente debe ser1.prioritydebe ser numérico si está presente.- Cada clave extra de nivel superior debe ser un nombre de hook soportado.
- Cada clave de hook debe apuntar a una función.
- Las claves de nivel superior desconocidas se rechazan.
El validador de Lua acepta priority para todos los scripts respaldados por MagmaCore. EliteMobs lo usa para el orden de los scripts de jefe y de NPC. FreeMinecraftModels actualmente no ordena por priority: los props siguen el orden de la lista scripts: de la configuración, y los ítems personalizados solo vinculan el primer script válido de esa lista.
Dónde viven los archivos de script
Cada plugin apunta el motor a un único directorio de scripts. Ese directorio se escanea de forma recursiva, así que los archivos .lua en subcarpetas también se recogen y puedes organizar los scripts como quieras. Los archivos se visitan en orden alfabético dentro de cada carpeta.
Los scripts se indexan por nombre de archivo, no por ruta. Dos archivos llamados door.lua en subcarpetas distintas son la misma clave, y solo uno de ellos acaba registrado. Dale a cada script un nombre de archivo distinto.
Las funciones auxiliares y las constantes locales deben vivir encima del return final, no dentro de la tabla retornada salvo que sean hooks reales.
local ANIMATION_NAME = "idle"
local function do_something(context)
context.log:info("Doing something!")
end
return {
api_version = 1,
priority = 0,
on_spawn = function(context)
do_something(context)
end
}
Nombres de Hooks Comunes
Estos nombres de hook aparecen en varios runtimes de Lua. Cada tipo de script habilita solo el subconjunto que tiene sentido para ese propietario: los scripts de prop de FMM usan todos estos, los scripts de ítem de FMM usan on_game_tick de este grupo más sus hooks específicos de ítem, los poderes de jefe de EliteMobs usan su propia lista de hooks de jefe, y los scripts de NPC de EliteMobs usan on_spawn, on_remove, on_game_tick, on_npc_interact, on_npc_proximity_enter, on_npc_proximity_leave, on_zone_enter y on_zone_leave.
| Hook | Cuándo se dispara |
|---|---|
on_spawn | Se llama una vez cuando se crea la instancia del script para los propietarios soportados (props de FMM, jefes de EliteMobs y scripts de NPC de EliteMobs) |
on_game_tick | Se llama cada tick del servidor mientras la entidad esté viva/activa |
on_zone_enter | Se llama cuando una zona soportada por el runtime informa de un evento de entrada. Las vigilancias de zona de FMM y de los NPC de EliteMobs se basan en jugadores; los hooks de zona de nivel superior de los jefes de EliteMobs provienen de eventos de zona de EliteScript. |
on_zone_leave | Se llama cuando una zona soportada por el runtime informa de un evento de salida. Las vigilancias de zona de FMM y de los NPC de EliteMobs se basan en jugadores; los hooks de zona de nivel superior de los jefes de EliteMobs provienen de eventos de zona de EliteScript. |
Sintaxis de Métodos: : vs .
En Lua, object:method(arg) es una forma abreviada de object.method(object, arg). La API de MagmaCore acepta ambas formas, así que cualquiera funciona:
context.log:info("hello")
context.log.info("hello") -- lo mismo
Toda la documentación usa : de forma consistente.
Presupuesto de Ejecución
Cada invocación de hook, cada callback programado y la propia evaluación inicial del archivo de script se ejecutan bajo un presupuesto de ejecución estricto. El presupuesto se aplica dentro de la VM de Lua, así que actúa mientras tu código sigue ejecutándose en lugar de limitarse a mirar el reloj después.
Una invocación individual se aborta en cuanto supera cualquiera de los dos límites:
| Límite | Valor |
|---|---|
| Tiempo de reloj | 50 milisegundos |
| Instrucciones de Lua ejecutadas | 250,000 |
Alcanzar cualquiera de los dos límites lanza un error de Lua dentro del script, que el runtime trata entonces como cualquier otro error de script: el mensaje se registra y la instancia del script se deshabilita para esa entidad.
Como la comprobación ocurre por instrucción, un while true do end ya no congela el servidor -- se corta al llegar al presupuesto. Aún así consume hasta 50 ms del tick en el que se ejecutó, y aún así deshabilita el script, así que esto es una red de seguridad, no una licencia para escribir bucles sin límite.
Una llamada que retorna con normalidad pero que aun así superó los 50 ms de reloj también se deshabilita, con su propia advertencia en consola:
[Lua] my_script.lua took 73ms in 'on_game_tick' (limit: 50ms) -- script disabled to prevent lag.
Las llamadas anidadas comparten un único presupuesto: si un hook invoca un callback que invoca otro, toda la cadena se mide como una única asignación de 50 ms / 250,000 instrucciones, no una asignación para cada uno.
Para mantenerte dentro del presupuesto:
- Evita bucles sin límite dentro de los hooks.
- Mantén los manejadores de
on_game_tickligeros -- se ejecutan en cada tick. - Usa
context.scheduler:run_repeating(...)para repartir el trabajo entre ticks. - Mueve el trabajo costoso detrás de
context.cooldownso un intervalo razonable.
Si un script Lua lanza un error en tiempo de ejecución en cualquier hook o callback planificado, la instancia del script se deshabilita inmediata y permanentemente para esa entidad. Corrige el error en tu archivo de script -- el script se reinicializará en el próximo spawn de entidad.
context.state
Una tabla Lua simple que persiste durante toda la vida útil de la instancia del script. Úsala para almacenar flags, contadores, IDs de tareas, estados de alternancia y cualquier dato que necesites compartir entre hooks.
on_spawn = function(context)
context.state.is_open = false
context.state.click_count = 0
context.state.task_id = nil
end,
on_right_click = function(context)
context.state.click_count = (context.state.click_count or 0) + 1
end
Solo context.state está pensado para datos persistentes propiedad del script. La mayoría de las demás tablas de context (context.world, context.event, etc.) se reconstruyen o se recrean de forma perezosa según haga falta. FreeMinecraftModels cachea context.prop por prop por rendimiento, pero sus campos en vivo como current_location son perezosos, así que las lecturas siguen reflejando el estado actual. context.state sobrevive desde el momento en que se crea la instancia del script hasta que se destruye.
context.log
Métodos de logging en consola, disponibles en todas las superficies de script. Los mensajes se prefijan con el nombre del archivo del script en la consola del servidor. Los scripts de FreeMinecraftModels y los scripts de NPC de EliteMobs exponen info, warn y error; los poderes de jefe de EliteMobs exponen info, warn y debug.
| Método | Notas |
|---|---|
log:info(message) | Mensaje informativo |
log:warn(message) | Mensaje de advertencia |
log:error(message) | Mensaje de nivel warning (registrado a nivel WARN, igual que log:warn) |
El context.log de EliteMobs registra debug en lugar de error. Usa log:debug(message) para la salida de depuración (aparece a nivel info con un prefijo debug).
context.log:info("Script loaded!")
context.log:warn("Something unexpected happened")
-- Generic/FMM/NPC logger:
context.log:error("Critical failure in zone setup")
-- EliteMobs boss logger:
context.log:debug("Boss debug detail")
context.cooldowns
La tabla cooldowns gestiona los cooldowns basados en tiempo de tus scripts. Hay dos ámbitos:
- Los cooldowns locales son por instancia de script e identificados por una clave de cadena. Si no se proporciona una clave, se usa el nombre del archivo del script como clave por defecto.
- Los cooldowns globales se comparten entre todos los scripts de la misma entidad propietaria (por ejemplo, todos los scripts del mismo prop, o todos los poderes Lua del mismo jefe).
cooldowns:check_local(key?, duration)
El método más común. Comprueba si el cooldown está listo, y si lo está, lo inicia y devuelve true. Si no está listo, devuelve false. Es una comprobación-y-establecimiento atómica — sin condiciones de carrera.
| Parámetro | Tipo | Notas |
|---|---|---|
key | string (opcional) | Identificador del cooldown. Por defecto, el nombre del archivo del script. |
duration | int | Duración del cooldown en ticks (20 = 1 segundo) |
on_right_click = function(context)
-- Solo permite esta acción una vez cada 3 segundos
if not context.cooldowns:check_local("interact", 60) then
return
end
-- ... ejecutar la acción
end
Todas las superficies de script — poderes de jefe, scripts de NPC y scripts de FreeMinecraftModels — usan el orden compartido mostrado arriba: check_local(key?, duration). (Las compilaciones experimentales anteriores de NPC usaban check_local(duration, key?); actualiza cualquier script de NPC antiguo al orden compartido.)
cooldowns:local_ready(key?)
Devuelve true si el cooldown local ha expirado (o nunca se estableció), false si sigue activo.
cooldowns:local_remaining(key?)
Devuelve el número de ticks restantes en el cooldown local, o 0 si está listo.
cooldowns:set_local(duration, key?)
Establece un cooldown local sin comprobar si ya hay uno activo. Úsalo para reinicios incondicionales de cooldown.
| Parámetro | Tipo | Notas |
|---|---|---|
duration | int | Duración en ticks. Pasa 0 o un valor negativo para limpiar. |
key | string (opcional) | Identificador del cooldown. Por defecto, el nombre del archivo del script. |
cooldowns:global_ready()
Devuelve true si el cooldown global (compartido entre todos los scripts de la misma entidad) ha expirado.
cooldowns:set_global(duration)
Establece el cooldown global.
| Parámetro | Tipo | Notas |
|---|---|---|
duration | int | Duración en ticks |
El "propietario" de un cooldown global depende del tipo de script:
- Poderes Lua de EliteMobs — compartidos por
EliteEntity(un cubo por jefe).global_ready()/set_global()usan el sistema integrado de cooldowns de poderes del jefe; los cooldowns locales también se comparten entre todos los poderes del mismo jefe. - Scripts de NPC de EliteMobs — los cooldowns globales se comparten por NPC (un cubo para todos los scripts de ese NPC); los cooldowns locales son por instancia de script del NPC.
- Scripts de prop de FMM — compartidos por
PropEntity(un cubo por prop colocado). - Scripts de objeto de FMM — compartidos por UUID de jugador (un cubo por jugador). Los cooldowns locales en objetos persisten entre ciclos de equipar/desequipar, indexados por
playerUUID → "itemId:scriptFile" → key, de modo que un cooldown sobrevive a que el jugador saque el objeto de su hotbar y vuelva a meterlo.
context.scheduler
Esta sección usa los nombres del planificador de MagmaCore (run_later, run_repeating, cancel). Los nombres de EliteMobs (run_after(ticks, callback), run_every(interval, callback), cancel_task(taskId)) se aceptan como alias en todas las superficies, así que cualquiera de los dos estilos funciona en cualquier script.
El planificador te permite ejecutar tareas retrasadas y repetidas. Todas las tareas son propiedad de la instancia del script y se cancelan automáticamente cuando la instancia del script se destruye (por ejemplo, cuando se retira un prop o muere un jefe).
scheduler:run_later(ticks, callback)
Ejecuta un callback una vez tras un retraso. Devuelve un ID numérico de tarea.
| Parámetro | Tipo | Notas |
|---|---|---|
ticks | int | Retraso en ticks del servidor (20 ticks = 1 segundo) |
callback | function | Llamada con un context fresco cuando expira el retraso |
context.scheduler:run_later(40, function(delayed_context)
delayed_context.log:info("2 seconds have passed!")
end)
scheduler:run_repeating(delay, interval, callback)
Ejecuta un callback repetidamente en un intervalo fijo. Devuelve un ID numérico de tarea.
| Parámetro | Tipo | Notas |
|---|---|---|
delay | int | Retraso inicial en ticks antes de la primera ejecución |
interval | int | Ticks entre cada ejecución posterior |
callback | function | Llamada con un context fresco en cada intervalo |
context.state.particle_task = context.scheduler:run_repeating(0, 20, function(tick_context)
tick_context.log:info("Another second passed!")
end)
scheduler:cancel(taskId)
Cancela una tarea planificada por su ID.
| Parámetro | Tipo | Notas |
|---|---|---|
taskId | int | El ID de tarea devuelto por run_later o run_repeating |
Si inicias una tarea repetida, cancélala siempre en tu hook de limpieza (on_destroy para props, on_exit_combat / on_death para jefes, on_unequip para objetos). Olvidar cancelarla deja una tarea en segundo plano ejecutándose hasta que se destruya la instancia del script, lo que desperdicia rendimiento y puede causar errores.
Los callbacks planificados reciben un context fresco como su parámetro. Usa siempre el argumento context propio del callback, no el context externo del hook que creó el callback. El context externo puede contener referencias obsoletas.
context.world
La tabla world proporciona métodos para consultar e interactuar con el mundo de Minecraft. Se construye a partir del mundo actual de la entidad.
Los métodos documentados aquí son la tabla context.world compartida de MagmaCore. Los props/ítems de FreeMinecraftModels y los scripts de NPC de EliteMobs reciben esta tabla directamente, incluido strike_lightning(...). FreeMinecraftModels además añade tres helpers opcionales de botín de EliteMobs (drop_elitemobs_procedural_loot, drop_elitemobs_random_loot, drop_elitemobs_custom_loot) que solo hacen algo cuando EliteMobs está instalado; consulta context.world: adiciones de FreeMinecraftModels. Los poderes de jefe de EliteMobs parten de esta misma tabla base y después añaden o sobrescriben métodos específicos de EliteMobs como rayos protegidos, proyectiles falsos, generación de refuerzos y helpers de botín de jugador; consulta Mundo y Entorno de EliteMobs.
context.world acepta tanto llamadas con coordenadas (p. ej. strike_lightning(x, y, z), spawn_particle(name, x, y, z, ...)) como sobrecargas con tabla de ubicación (p. ej. strike_lightning_at_location(loc), spawn_particle_at_location(loc, ...)) en las superficies genéricas. Los poderes de jefe de EliteMobs también admiten ambos estilos generales, pero algunos métodos con tabla de ubicación tienen comportamiento específico de jefe.
world.name
Un campo de cadena que contiene el nombre del mundo.
world:get_block_at(x, y, z)
Devuelve el nombre del material del bloque en las coordenadas dadas como cadena en minúsculas (por ejemplo, "stone", "air").
| Parámetro | Tipo | Notas |
|---|---|---|
x | int | Coordenada X del bloque |
y | int | Coordenada Y del bloque |
z | int | Coordenada Z del bloque |
world:set_block_at(x, y, z, material)
Establece el bloque en las coordenadas dadas. Se ejecuta en el hilo principal.
| Parámetro | Tipo | Notas |
|---|---|---|
x | int | Coordenada X del bloque |
y | int | Coordenada Y del bloque |
z | int | Coordenada Z del bloque |
material | string | Nombre del Material de Bukkit, en minúsculas (por ejemplo, "stone", "air", "oak_planks") |
Devuelve true si el bloque se estableció correctamente, false en caso contrario.
world:spawn_particle(particle, x, y, z, count, dx, dy, dz, speed)
Genera partículas en una ubicación.
| Parámetro | Tipo | Por defecto | Notas |
|---|---|---|---|
particle | string | requerido | Nombre del enum Particle de Bukkit, EN MAYÚSCULAS (por ejemplo, "FLAME", "HAPPY_VILLAGER") |
x | number | requerido | Coordenada X |
y | number | requerido | Coordenada Y |
z | number | requerido | Coordenada Z |
count | int | 1 | Número de partículas |
dx | number | 0 | Dispersión/offset X |
dy | number | 0 | Dispersión/offset Y |
dz | number | 0 | Dispersión/offset Z |
speed | number | 0 | Velocidad de la partícula |
Algunos tipos de partículas requieren datos extra que esta API simple no soporta. DUST, BLOCK_CRACK, FALLING_DUST, BLOCK_DUST e ITEM_CRACK pueden fallar o no producir un efecto visible. Usa alternativas sin datos en su lugar: CLOUD, SMOKE, CAMPFIRE_COSY_SMOKE, SNOWFLAKE, FLAME, HAPPY_VILLAGER, HEART, NOTE, ENCHANT, etc.
Como la API simple no proporciona objetos de datos de partícula de Bukkit, las partículas que requieren datos pueden lanzar una excepción subyacente de Bukkit y deshabilitar la instancia del script. Prefiere los nombres de partícula sin datos de arriba para los scripts de FMM/MagmaCore.
world:play_sound(sound, x, y, z, volume, pitch)
Reproduce un sonido en una ubicación.
| Parámetro | Tipo | Por defecto | Notas |
|---|---|---|---|
sound | string | requerido | Nombre del enum Sound de Bukkit (p. ej. "ENTITY_EXPERIENCE_ORB_PICKUP") o una clave de sonido personalizada del resource pack (p. ej. "mypack:boss.roar") |
x | number | requerido | Coordenada X |
y | number | requerido | Coordenada Y |
z | number | requerido | Coordenada Z |
volume | number | 1.0 | Volumen |
pitch | number | 1.0 | Tono |
El nombre se compara primero con los sonidos registrados de Bukkit (sin distinguir mayúsculas, así que "ENTITY_ARROW_SHOOT" y "entity_arrow_shoot" son lo mismo). Si nada coincide, se normaliza a una clave con namespace en minúsculas y se reproduce como un sonido personalizado del resource pack. Un valor que no es ni un sonido conocido ni una clave con namespace válida no reproduce nada en lugar de dar error, así que una errata simplemente no hace nada en vez de deshabilitar tu script.
Lo mismo aplica a world:play_sound_at_location(loc, sound, volume, pitch).
world:strike_lightning(x, y, z)
Lanza un rayo en una ubicación (visual y dañino).
| Parámetro | Tipo | Notas |
|---|---|---|
x | number | Coordenada X |
y | number | Coordenada Y |
z | number | Coordenada Z |
world:get_time()
Devuelve el tiempo actual del mundo en ticks.
world:set_time(ticks)
Establece el tiempo del mundo.
| Parámetro | Tipo | Notas |
|---|---|---|
ticks | int | Tiempo del mundo (0 = amanecer, 6000 = mediodía, 13000 = noche, 18000 = medianoche) |
world:get_nearby_entities(x, y, z, radius)
Devuelve un array de tablas envoltorio de entidades para todas las entidades dentro de una caja delimitadora centrada en las coordenadas dadas.
| Parámetro | Tipo | Notas |
|---|---|---|
x | number | Centro X |
y | number | Centro Y |
z | number | Centro Z |
radius | number | Radio de búsqueda (utilizado como semi-extensión en los tres ejes) |
Esto devuelve TODAS las entidades en el rango, incluidas las entidades no vivientes (armor stands, objetos soltados, etc.). Protege siempre con if entity.damage then antes de llamar a métodos de entidad viviente.
world:get_nearby_players(x, y, z, radius)
Devuelve un array de tablas envoltorio de jugador para todos los jugadores dentro de una caja delimitadora centrada en las coordenadas dadas.
| Parámetro | Tipo | Notas |
|---|---|---|
x | number | Centro X |
y | number | Centro Y |
z | number | Centro Z |
radius | number | Radio de búsqueda |
world:spawn_entity(entity_type, x, y, z)
Genera una entidad vanilla de Minecraft en la ubicación dada.
| Parámetro | Tipo | Notas |
|---|---|---|
entity_type | string | Nombre del tipo de entidad de Bukkit, en minúsculas (por ejemplo, "zombie", "skeleton", "pig") |
x | number | Coordenada X |
y | number | Coordenada Y |
z | number | Coordenada Z |
Devuelve una tabla de entidad para la entidad generada (con métodos de entidad viviente si aplica), o nil si el tipo de entidad es inválido.
world:get_highest_block_y(x, z)
Devuelve la coordenada Y del bloque no-aire más alto en la posición X/Z dada.
| Parámetro | Tipo | Notas |
|---|---|---|
x | int | Coordenada X del bloque |
z | int | Coordenada Z del bloque |
world:raycast(from_x, from_y, from_z, dir_x, dir_y, dir_z, [max_distance])
Lanza un rayo desde un punto en una dirección y devuelve información sobre lo que golpea.
| Parámetro | Tipo | Por defecto | Notas |
|---|---|---|---|
from_x | number | requerido | Origen X |
from_y | number | requerido | Origen Y |
from_z | number | requerido | Origen Z |
dir_x | number | requerido | Componente X de la dirección |
dir_y | number | requerido | Componente Y de la dirección |
dir_z | number | requerido | Componente Z de la dirección |
max_distance | number | 50 | Distancia máxima del rayo |
Devuelve una tabla con los siguientes campos:
| Campo | Tipo | Notas |
|---|---|---|
hit_entity | tabla entidad o nil | La primera entidad alcanzada por el rayo, o nil si ninguna |
hit_location | tabla location o nil | El punto exacto donde el rayo golpeó algo |
hit_block | tabla o nil | {x, y, z, material} del bloque golpeado, o nil si no se golpeó ningún bloque |
world:spawn_firework(x, y, z, colors, [type], [power])
Genera un cohete de fuegos artificiales en la ubicación dada.
| Parámetro | Tipo | Por defecto | Notas |
|---|---|---|---|
x | number | requerido | Coordenada X |
y | number | requerido | Coordenada Y |
z | number | requerido | Coordenada Z |
colors | tabla | requerido | Array de cadenas de color, por ejemplo, {"RED", "BLUE", "WHITE"} |
type | string | "BALL" | Forma del fuego artificial: "BALL", "BALL_LARGE", "STAR", "BURST", "CREEPER" |
power | int | 1 | Potencia de vuelo, 0-127 |
-- Ejemplo: explosión de fuegos artificiales rojos y dorados
context.world:spawn_firework(loc.x, loc.y, loc.z, {"RED", "GOLD"}, "BURST", 2)
world:place_temporary_block(x, y, z, material, [ticks], [require_air])
Coloca un bloque que se revierte automáticamente a su estado original tras un retraso.
| Parámetro | Tipo | Por defecto | Notas |
|---|---|---|---|
x | int | requerido | Coordenada X del bloque |
y | int | requerido | Coordenada Y del bloque |
z | int | requerido | Coordenada Z del bloque |
material | string | requerido | Nombre del Material de Bukkit (por ejemplo, "stone", "ice") |
ticks | int | 0 | Duración en ticks antes de que el bloque revierta. 0 significa permanente. |
require_air | boolean | false | Si es true, solo coloca el bloque si el destino es aire |
Devuelve true si se colocó el bloque, false si el material era inválido o no se cumplió el requisito de aire.
world:drop_item(x, y, z, material, [amount])
Suelta una entidad de objeto en la ubicación dada con dispersión natural.
| Parámetro | Tipo | Por defecto | Notas |
|---|---|---|---|
x | number | requerido | Coordenada X |
y | number | requerido | Coordenada Y |
z | number | requerido | Coordenada Z |
material | string | requerido | Nombre del Material de Bukkit |
amount | int | 1 | Tamaño de la pila |
Devuelve una tabla de entidad para el objeto soltado, o nil si el material era inválido.
context.zones
La tabla zones permite a los scripts crear zonas espaciales y vigilarlas en busca de eventos de entrada/salida de jugadores. Los props de FMM, los ítems de FMM y los scripts de NPC de EliteMobs usan los métodos genéricos create_sphere(...) / create_cylinder(...) / create_cuboid(...) más watch(...) / unwatch(...) documentados aquí, que disparan los hooks on_zone_enter / on_zone_leave. Las zonas están ligadas a la instancia del script y se limpian automáticamente cuando la instancia del script se destruye. Los poderes de jefe de EliteMobs usan una tabla de zonas separada y específica de jefe con métodos como get_entities_in_zone(...), get_locations_in_zone(...) y watch_zone(...); consulta EliteMobs Zones & Targeting.
zones:create_sphere(x, y, z, radius)
Crea una zona esférica centrada en las coordenadas dadas. Devuelve un handle numérico de zona.
| Parámetro | Tipo | Notas |
|---|---|---|
x | number | Centro X |
y | number | Centro Y |
z | number | Centro Z |
radius | number | Radio de la esfera |
zones:create_cylinder(x, y, z, radius, height)
Crea una zona cilíndrica. Devuelve un handle numérico de zona.
| Parámetro | Tipo | Notas |
|---|---|---|
x | number | Centro X |
y | number | Centro Y (base) |
z | number | Centro Z |
radius | number | Radio del cilindro |
height | number | Altura del cilindro |
zones:create_cuboid(x, y, z, xSize, ySize, zSize)
Crea una zona cuboide. Devuelve un handle numérico de zona.
| Parámetro | Tipo | Notas |
|---|---|---|
x | number | Centro X |
y | number | Centro Y |
z | number | Centro Z |
xSize | number | Semi-extensión en X |
ySize | number | Semi-extensión en Y |
zSize | number | Semi-extensión en Z |
zones:watch(handle, onEnterCallback, onLeaveCallback)
Comienza a vigilar una zona para eventos de entrada/salida de jugadores. Los parámetros del callback actúan como señales booleanas — pasar un valor no-nulo (por ejemplo, una función o true) habilita el hook correspondiente en el script. Los propios callbacks no se invocan directamente. En su lugar, la lógica de entrada/salida se dispara mediante los hooks on_zone_enter / on_zone_leave del script.
| Parámetro | Tipo | Notas |
|---|---|---|
handle | int | Handle de zona de una llamada create_* |
onEnterCallback | cualquier no-nulo o nil | No-nulo habilita el hook on_zone_enter para esta zona |
onLeaveCallback | cualquier no-nulo o nil | No-nulo habilita el hook on_zone_leave para esta zona |
Devuelve true si la vigilancia se configuró correctamente, nil si el handle de zona era inválido.
Las vigilancias de zona se comprueban cada tick del servidor contra todos los jugadores en el mismo mundo. Mantén la cuenta de zonas razonable para evitar sobrecarga de rendimiento.
zones:unwatch(handle)
Detiene la vigilancia de una zona y limpia sus recursos.
| Parámetro | Tipo | Notas |
|---|---|---|
handle | int | Handle de zona para dejar de vigilar |
Ejemplo: Zona disparada por proximidad
return {
api_version = 1,
on_spawn = function(context)
local loc = context.prop.current_location
if loc == nil then return end
local handle = context.zones:create_sphere(loc.x, loc.y, loc.z, 5)
-- Pasa valores no-nulos para habilitar los hooks on_zone_enter y on_zone_leave.
-- Son señales booleanas, no callbacks — la lógica real va en los hooks de abajo.
context.zones:watch(handle, function() end, function() end)
context.state.zone_handle = handle
end,
on_zone_enter = function(context)
context.log:info("Player entered zone!")
end,
on_zone_leave = function(context)
context.log:info("Player left zone!")
end,
on_destroy = function(context)
if context.state.zone_handle then
context.zones:unwatch(context.state.zone_handle)
end
end
}
context.event
La tabla event está presente cuando el hook actual se desencadenó por un evento de Bukkit (por ejemplo on_right_click) o cuando el runtime proporciona un actor jugador para un hook genérico de zona de MagmaCore. No está presente durante on_game_tick, callbacks planificados ni hooks de ciclo de vida sin jugador desencadenante.
Para los hooks genéricos on_zone_enter / on_zone_leave, context.player y context.event.player identifican al jugador que entra o sale de la zona. Esos hooks de zona no están respaldados por un evento cancelable de Bukkit, así que cancel() y uncancel() no están presentes ahí.
| Campo / Método | Tipo | Notas |
|---|---|---|
is_cancelled | boolean | Estado de cancelación en el momento en que se construyó el context. Este campo no se refresca tras llamar a cancel() o uncancel(). |
cancel() | method | Cancela el evento (evita el comportamiento por defecto) |
uncancel() | method | Des-cancela un evento previamente cancelado |
player | tabla entidad | El jugador involucrado en el evento, si lo hay |
on_right_click = function(context)
if context.event then
context.event:cancel() -- evita la interacción por defecto del clic derecho
end
end
Los scripts de poder de EliteMobs tienen una tabla de eventos más detallada con cantidades de daño, causas de daño, referencias del dañador y métodos de modificación de daño. Consulta Referencia de la API de Lua para los campos completos de eventos de EliteMobs.
Namespace de Ayuda em
La tabla em está disponible en el momento de carga del archivo (antes de que se ejecute cualquier hook). Proporciona constructores auxiliares para construir tablas de ubicación, tablas de vector y definiciones de zona usadas a lo largo de la API.
| Función | Propósito |
|---|---|
em.create_location(x, y, z [, world, yaw, pitch]) | Crea una tabla de ubicación con nombre de mundo, yaw y pitch opcionales. La tabla devuelta también tiene un método .add(dx, dy, dz) que desplaza la ubicación en su lugar y se devuelve a sí misma para encadenamiento. |
em.create_vector(x, y, z) | Crea una tabla de vector |
em.zone.create_sphere_zone(radius) | Crea una definición de zona esférica |
em.zone.create_dome_zone(radius) | Crea una definición de zona de cúpula |
em.zone.create_cylinder_zone(radius, height) | Crea una definición de zona cilíndrica |
em.zone.create_cuboid_zone(x, y, z) | Crea una definición de zona cuboide |
em.zone.create_cone_zone(length, radius) | Crea una definición de zona cónica |
em.zone.create_static_ray_zone(length, thickness) | Crea una definición de zona de rayo estático |
em.zone.create_rotating_ray_zone(length, point_radius, animation_duration) | Crea una definición de zona de rayo rotatorio |
em.zone.create_translating_ray_zone(length, point_radius, animation_duration) | Crea una definición de zona de rayo trasladante |
em.location.is_in_dungeon(loc) | Comprueba si una ubicación está dentro de alguna región de dungeon registrada (cualquier propietario que reporte el tipo "dungeon") |
em.location.is_protected(loc) | Comprueba si una ubicación está dentro de alguna región protegida (WorldGuard, GriefPrevention, regiones de EM, etc.) |
em.location.owned_by(loc, namespace) | Comprueba si un namespace de plugin específico posee la ubicación (por ejemplo, "EliteMobs") |
em.location.owners(loc) | Array de cadenas de namespace que poseen la ubicación |
em.location.kinds_at(loc) | Array de etiquetas de tipo en la ubicación ("elitemobs", "dungeon", "world_package", "open_world_dungeon", categoría de tamaño de dungeon) |
em.location.has_kind(loc, kind) | Comprueba si la ubicación está etiquetada con el tipo dado |
MagmaCore incluye sus propios adaptadores de WorldGuard y GriefPrevention y los registra por ti la primera vez que se ejecuta una consulta de protección -- no hay configuración ni dependencia obligatoria. Sin ninguno de los dos plugins instalados y sin ningún plugin de contenido registrando propiedad, em.location.is_protected(loc) simplemente devuelve false y una advertencia única en consola explica por qué. Pasar una ubicación nula o inválida devuelve false desde los predicados y una tabla vacía desde owners / kinds_at; estos helpers nunca lanzan excepciones.
Los constructores de zonas devuelven tablas encadenables con :set_center(loc) (o :set_origin(loc) / :set_destination(loc) dependiendo del tipo de zona):
-- A nivel de archivo: crear una forma de zona reutilizable
local blast_zone = em.zone.create_sphere_zone(5)
return {
api_version = 1,
on_spawn = function(context)
-- Anclar la zona en el momento de la llamada
blast_zone:set_center(context.boss:get_location())
local entities = context.zones:get_entities_in_zone(blast_zone)
-- ...
end
}
El namespace em es particularmente útil en EliteMobs, donde las definiciones de zona se usan con context.zones:get_entities_in_zone() y context.script. En FreeMinecraftModels, los métodos context.zones:create_sphere(...) / create_cylinder(...) / create_cuboid(...) se usan más comúnmente para zonas simples basadas en vigilancia.
Tablas de Entidad
Las tablas de entidad se devuelven de consultas del mundo, datos de eventos y callbacks de zona. Proporcionan un conjunto en capas de campos y métodos según el tipo de entidad.
Campos Base de Entidad
| Campo | Tipo | Notas |
|---|---|---|
uuid | string | El UUID de la entidad |
entity_type | string | El tipo de entidad (por ejemplo, "player", "zombie", "skeleton", "villager") |
is_valid | boolean | Si la referencia de la entidad sigue siendo válida |
is_dead | boolean | Si la entidad está muerta |
is_player | boolean | Si la entidad es un jugador |
is_hostile | boolean | Si la entidad es un mob hostil (zombie, esqueleto, etc.) |
is_passive | boolean | Si la entidad es un mob pasivo (vaca, cerdo, gallina, etc.) |
current_location | tabla location | La posición actual de la entidad (x, y, z, world, yaw, pitch) |
world | string | El nombre del mundo en el que se encuentra la entidad |
Métodos Base de Entidad
| Método | Devuelve | Notas |
|---|---|---|
teleport(location_table) | void | Teletransporta la entidad. La tabla de ubicación debe tener los campos x, y, z, world; yaw y pitch son opcionales. |
remove() | void | Elimina la entidad del mundo. Solo actúa si la entidad sigue siendo válida. |
set_silent(flag) | void | Suprime o vuelve a habilitar los sonidos de la entidad. |
set_invulnerable(flag) | void | Hace que la entidad sea invulnerable o vulnerable al daño. |
set_gravity(flag) | void | Habilita o deshabilita la gravedad de la entidad. |
set_glowing(flag) | void | Alterna el efecto de contorno brillante sobre la entidad. |
Campos de Entidad Viviente
Las entidades vivientes (jugadores, mobs, etc.) tienen todos los campos base de entidad más:
| Campo | Tipo | Notas |
|---|---|---|
health | number | Salud actual |
maximum_health | number | Salud máxima |
name | string | Nombre mostrado |
is_alive | boolean | Si la entidad está viva |
Métodos de Entidad Viviente
| Método | Devuelve | Notas |
|---|---|---|
damage(amount) | void | Inflige la cantidad dada de daño a la entidad |
push(x, y, z) | void | Aplica un impulso de velocidad |
set_facing(x, y, z) | void | Establece la dirección a la que mira la entidad |
add_potion_effect(effect, duration, amplifier) | void | Añade un efecto de poción. effect es una cadena (por ejemplo, "speed", "slowness", "regeneration"). duration está en ticks. amplifier es el nivel del efecto menos 1 (0 = nivel I). |
remove_potion_effect(effect) | void | Elimina un efecto de poción por nombre |
get_scale() | number | Devuelve la escala actual de la entidad (por defecto 1.0 en servidores sin el atributo generic.scale) |
set_scale(value) | void | Establece la escala de la entidad mediante el atributo generic.scale. Sin efecto en servidores anteriores a 1.20.5. |
No todas las entidades devueltas por get_nearby_entities() son entidades vivientes. Puedes usar entity.is_player, entity.is_hostile o entity.is_passive para filtrar por categoría, o comprobar if entity.damage then antes de llamar a métodos de entidad viviente como damage(), push() o add_potion_effect().
Campos de Integración con Plugins
Las tablas de entidad incluyen automáticamente campos para detectar e interactuar con entidades gestionadas por otros plugins de Nightbreak. Estos campos solo se rellenan cuando el plugin relevante está instalado -- de lo contrario, por defecto son false / nil sin sobrecarga.
Campos de EliteMobs
Disponible cuando EliteMobs está instalado.
| Campo | Tipo | Notas |
|---|---|---|
is_elite | boolean | Si la entidad es un elite de EliteMobs |
is_custom_boss | boolean | Si la entidad es un jefe personalizado de EliteMobs (también disponible dentro de la subtabla elite) |
is_significant_boss | boolean | Si la entidad es un jefe personalizado con un multiplicador de salud superior a 1 (es decir, un encuentro diseñado, no un elite de relleno) |
elite | tabla o nil | Subtabla de información del elite (ver abajo). nil si la entidad no es un elite. |
La subtabla elite contiene:
| Campo | Tipo | Notas |
|---|---|---|
elite.level | int | El nivel del elite |
elite.name | string | El nombre mostrado del elite |
elite.health | number | Salud actual |
elite.max_health | number | Salud máxima |
elite.is_custom_boss | boolean | Si es un jefe personalizado (en oposición a un elite natural) |
elite.health_multiplier | number | Multiplicador de salud definido en la configuración |
elite.damage_multiplier | number | Multiplicador de daño definido en la configuración |
elite:remove() | void | Elimina el elite mediante el pipeline de eliminación adecuado de EliteMobs (limpia tracking, botín, etc.) |
Ejemplo: Dañar elites de forma diferente
local entities = context.world:get_nearby_entities(x, y, z, 5)
for _, entity in ipairs(entities) do
if entity.is_elite then
-- Inflige el doble de daño a elites
entity:damage(20)
context.log:info("Hit elite: " .. entity.elite.name .. " (level " .. entity.elite.level .. ")")
elseif entity.is_hostile then
entity:damage(10)
end
end
Campos de FreeMinecraftModels
Disponible cuando FreeMinecraftModels está instalado.
| Campo | Tipo | Notas |
|---|---|---|
is_modeled | boolean | Si la entidad tiene un modelo FMM adjunto (DynamicEntity o PropEntity) |
is_prop | boolean | Si la entidad es un prop de FMM (entidad decorativa estática) |
model | tabla o nil | Subtabla de información del modelo (ver abajo). nil si la entidad no está modelada. |
La subtabla model contiene:
| Campo | Tipo | Notas |
|---|---|---|
model.model_id | string | El ID del blueprint del modelo (por ejemplo, "torch_01") |
model.is_dynamic | boolean | Si la entidad modelada es una DynamicEntity (modelo de mob/jefe) en lugar de un prop estático |
model:play_animation(name, [blend], [loop]) | boolean | Reproduce una animación con nombre. blend por defecto es false, loop por defecto es false. Devuelve true si se encontró la animación. |
model:stop_animations() | void | Detiene todas las animaciones que se estén reproduciendo actualmente en el modelo. |
model:remove() | void | Elimina la entidad modelada mediante el pipeline de eliminación adecuado de FMM. |
El bridge de modelo (disponible en cualquier entidad) tiene por defecto blend y loop en false. El tabla prop play_animation tiene ambos por defecto en true porque los props normalmente quieren animaciones mezcladas y en bucle.
Ejemplo: Filtrar entidades por tipo
local entities = context.world:get_nearby_entities(x, y, z, 10)
for _, entity in ipairs(entities) do
if entity.is_prop then
-- Omitir props
elseif entity.is_player then
entity:damage(5)
elseif entity.entity_type == "villager" then
-- No herir aldeanos
elseif entity.is_elite then
entity:damage(20)
elseif entity.is_hostile then
entity:damage(10)
end
end
Campos Específicos del Jugador
Las entidades de jugador tienen todos los campos de entidad y entidad viviente más:
| Campo | Tipo | Notas |
|---|---|---|
game_mode | string | El modo de juego del jugador ("creative", "survival", "adventure", "spectator") |
Métodos Específicos del Jugador
Allá donde la API dice "admite códigos de color", Magmacore pasa la cadena por su parser MiniMessage integrado. Los códigos & heredados, el hex &#RRGGBB, <#RRGGBB>, <gradient>, <rainbow> y el resto del conjunto de etiquetas de MiniMessage funcionan y se pueden mezclar libremente. Kyori Adventure no es una dependencia y los scripts no tienen ninguna API de Adventure disponible -- no la necesitas.
Los métodos de texto de Lua convierten a una cadena con códigos de color heredados, así que los colores, degradados y decoraciones se renderizan correctamente mientras que las etiquetas interactivas (<hover>, <click>, <insert>, <font>, <key>, <lang>) se analizan pero no tienen efecto. Consulta Códigos de Color para la lista completa de etiquetas.
| Método | Devuelve | Notas |
|---|---|---|
send_message(msg) | void | Envía un mensaje de chat al jugador. Admite códigos de color. |
get_held_item() | tabla o nil | Devuelve {type, amount, display_name} para el objeto en la mano principal del jugador, o nil si está vacía. type es el nombre del Material de Bukkit en mayúsculas, como "GOLD_INGOT". |
consume_held_item(amount?) | void | Consume objetos de la mano principal del jugador. amount por defecto es 1 |
has_item(material, amount?) | boolean | Devuelve true si el jugador tiene al menos amount (por defecto 1) del material dado en cualquier parte de su inventario |
get_target_entity([range]) | tabla entidad o nil | Devuelve la entidad a la que mira el jugador mediante raycast, o nil si ninguna. El rango por defecto es 50. |
get_eye_location() | tabla location | Devuelve una tabla de ubicación a la altura de los ojos del jugador |
get_look_direction() | tabla | Devuelve un vector de dirección {x, y, z} para hacia donde mira el jugador |
send_block_change(x, y, z, material, [ticks]) | boolean | Envía un bloque falso visible solo para este jugador. Si se proporciona ticks, el bloque falso se restablece automáticamente tras esa duración. Devuelve true en éxito, false si el material es inválido. |
reset_block(x, y, z) | boolean | Restablece un bloque falso al bloque real para este jugador. Siempre devuelve true. |
sleep(x, y, z) | void | Hace que el jugador entre en una animación de dormir en cama en las coordenadas dadas. El bloque se restaura automáticamente cuando el jugador deja de dormir. |
wake_up() | void | Despierta a un jugador dormido. |
Métodos de UI del Jugador
Estos métodos están disponibles en cualquier tabla de entidad jugador y proporcionan formas de mostrar información al jugador mediante los elementos de UI integrados de Minecraft.
player:show_boss_bar(text, [color], progress, [ticks])
Muestra una barra de jefe al jugador.
| Parámetro | Tipo | Por defecto | Notas |
|---|---|---|---|
text | string | requerido | El texto a mostrar. Admite códigos de color &. |
color | string | "WHITE" | Color de la barra. Uno de: "RED", "BLUE", "GREEN", "YELLOW", "PURPLE", "PINK", "WHITE" |
progress | number | requerido | Cantidad de relleno desde 0.0 (vacío) hasta 1.0 (lleno) |
ticks | int | nil | Retraso opcional de auto-cierre en ticks. Si se omite, la barra permanece hasta que se oculta manualmente. |
player:hide_boss_bar()
Elimina la barra de jefe de la pantalla del jugador. No toma parámetros.
player:show_action_bar(text, [ticks])
Muestra texto en el área de la barra de acción (sobre el hotbar).
| Parámetro | Tipo | Por defecto | Notas |
|---|---|---|---|
text | string | requerido | El texto a mostrar. Admite códigos de color &. |
ticks | int | nil | Duración opcional en ticks. Si se proporciona, el mensaje se reenvía cada 40 ticks para mantenerlo visible durante toda la duración. |
player:show_title(title, [subtitle], fade_in, stay, fade_out)
Muestra una pantalla de título al jugador.
| Parámetro | Tipo | Por defecto | Notas |
|---|---|---|---|
title | string | requerido | Texto principal del título. Admite códigos de color &. |
subtitle | string | "" | Texto del subtítulo debajo del título principal. Opcional. |
fade_in | int | requerido | Duración del fundido de entrada en ticks |
stay | int | requerido | Cuánto tiempo permanece el título en pantalla en ticks |
fade_out | int | requerido | Duración del fundido de salida en ticks |
Siguientes Pasos
Para hooks, APIs y flujos de trabajo específicos de cada plugin:
- EliteMobs: Primeros Pasos | Hooks y Ciclo de Vida | Jefes y Entidades | Mundo y Entorno | Zonas y Targeting
- FreeMinecraftModels: Primeros Pasos | API de Prop y Objeto | Ejemplos
Los constructores actuales exponen em.create_location(...), em.zone.create_sphere_zone(...) y consultas como em.location.is_protected(...); las tablas de jugador añaden has_permission(node). El contexto exacto depende del hook: no presupongas context.event.player en callbacks programados.