Scripting Lua : Hooks et Cycle de Vie
Cette page couvre chaque hook qu'un pouvoir Lua peut définir, l'ordre d'exécution des hooks, comment chaque boss obtient son propre environnement d'exécution isolé, et quelles fonctions de la bibliothèque standard sont disponibles dans le sandbox.
Si vous n'avez pas encore écrit de pouvoir Lua, commencez par Pour Commencer.
Cette page documente les hooks des pouvoirs Lua de boss dans plugins/EliteMobs/powers/. Les scripts Lua de PNJ utilisent leur propre dossier plugins/EliteMobs/npc_scripts/ et des hooks spécifiques aux PNJ comme on_npc_interact et on_npc_proximity_enter ; voir Scripts de PNJ.
Référence des Hooks
Chaque fichier de pouvoir Lua renvoie une table. Chaque clé de cette table (en dehors de api_version et priority) doit être l'un des hooks listés ci-dessous. L'environnement d'exécution appelle la fonction correspondante chaque fois que l'événement de jeu correspondant se déclenche.
| Hook | Se déclenche quand | context.player disponible ? |
|---|---|---|
on_spawn | L'elite mob apparaît | Non |
on_game_tick | Une fois par tick serveur (50 ms) tant que l'horloge du runtime est active | Non |
on_boss_damaged | Le boss subit des dégâts de n'importe quelle source | Non |
on_boss_damaged_by_player | Le boss subit des dégâts d'un joueur | Oui |
on_boss_damaged_by_elite | Le boss subit des dégâts d'un autre elite mob | Non |
on_player_damaged_by_boss | Un joueur subit des dégâts de ce boss | Oui |
on_enter_combat | Le boss entre en combat | Oui |
on_exit_combat | Le boss quitte le combat | Non |
on_heal | Le boss se soigne | Non |
on_boss_target_changed | Le boss change de cible | Oui |
on_death | Le boss meurt | Non |
on_phase_switch | Un boss à phases passe à une nouvelle phase | Non |
on_zone_enter | Une entité entre dans une zone surveillée | Oui (si l'entité est un joueur) |
on_zone_leave | Une entité quitte une zone surveillée | Oui (si l'entité est un joueur) |
Lorsque context.player est indiqué « Non », y accéder renvoie nil. Vérifiez toujours qu'il n'est pas nil avant de l'utiliser.
Les hooks de premier niveau on_zone_enter et on_zone_leave sont déclenchés par les événements EliteScript/ScriptZone. Les surveillants (watchers) créés en Lua via context.zones:watch_zone(...) et context.script:zone(...):watch(...) appellent directement leurs callbacks on_enter / on_leave au lieu d'invoquer ces hooks de premier niveau.
Pouvoir typique multi-hooks
Un seul pouvoir Lua peut définir autant de hooks que nécessaire. Voici un squelette qui utilise trois hooks ensemble :
return {
api_version = 1,
on_enter_combat = function(context)
-- Initialize per-fight state when combat begins
context.state.hit_count = 0
context.log:info("Combat started!")
end,
on_boss_damaged_by_player = function(context)
-- Track hits and trigger an ability every 5th hit
context.state.hit_count = (context.state.hit_count or 0) + 1
if context.state.hit_count % 5 ~= 0 then
return
end
if not context.cooldowns:check_local("counter_attack", 100) then
return
end
-- Fire a projectile back at the player
local origin = context.boss:get_location()
origin:add(0, 1, 0)
context.boss:summon_projectile(
"SMALL_FIREBALL", origin, context.player:get_location(), 1.5
)
end,
on_death = function(context)
-- Spawn a firework on death
context.world:spawn_particle_at_location(
context.boss:get_location(), "EXPLOSION_EMITTER", 1
)
end
}
Données d'événement (context.event)
Certains hooks reçoivent une table context.event qui expose des données sur l'événement de jeu ayant déclenché le hook. Les champs disponibles dépendent du hook en cours d'exécution.
Hooks de dégâts
S'applique à on_boss_damaged, on_boss_damaged_by_player, on_boss_damaged_by_elite et on_player_damaged_by_boss.
| Champ / Méthode | Type | Description |
|---|---|---|
event.damage_amount | double | Valeur de dégâts brute |
event.damage_cause | string | Nom Spigot DamageCause (ex. "ENTITY_ATTACK", "PROJECTILE") |
event.damager | entity table | Entité ayant infligé les dégâts. Présent uniquement sur les hooks de dégâts-par-entité. |
event.projectile | entity table | L'entité projectile, si l'attaquant était un projectile. |
event.set_damage_amount(n) | — | Remplace les dégâts par une valeur fixe |
event.multiply_damage_amount(n) | — | Multiplie les dégâts actuels par n |
event.cancel_event() | — | Annule entièrement l'événement de dégâts |
on_boss_damaged_by_player = function(context)
-- Halve all projectile damage
if context.event.damage_cause == "PROJECTILE" then
context.event.multiply_damage_amount(0.5)
end
end
Hook d'apparition
S'applique à on_spawn.
| Champ / Méthode | Type | Description |
|---|---|---|
event.spawn_reason | string | Nom Spigot SpawnReason |
event.cancel_event() | — | Annule l'apparition |
Hook de mort
S'applique à on_death.
| Champ / Méthode | Type | Description |
|---|---|---|
event.entity | entity table | L'entité mourante |
Hooks de zone
S'applique à on_zone_enter et on_zone_leave.
| Champ / Méthode | Type | Description |
|---|---|---|
event.entity | entity table | L'entité entrant dans la zone ou la quittant |
Les surveillants de zone créés en Lua ne remplissent pas context.event ; ils transmettent directement l'entité qui entre/sort à leur callback.
Événements annulables (généralités)
Tout hook dont l'événement de jeu sous-jacent est annulable expose event.cancel_event(). Si context.event vaut nil pour un hook donné (ex. on_game_tick, on_heal), il n'y a aucun événement sous-jacent avec lequel interagir.
Pour les champs complets des tables d'entité, voir Boss et Entités. Pour les valeurs de cause de dégâts et de raison d'apparition, voir Enums et Valeurs.
Ordre d'exécution des hooks
Lorsqu'un boss a plusieurs pouvoirs Lua attachés, le hook de chaque pouvoir est appelé pour le même événement. L'ordre est déterminé par le champ priority :
- Les valeurs plus basses s'exécutent en premier (la valeur par défaut est
0). - Les pouvoirs ayant la même priorité s'exécutent dans l'ordre de chargement (en pratique non spécifié).
return {
api_version = 1,
priority = -10, -- runs before most other powers
on_boss_damaged_by_player = function(context)
-- This runs early, so other powers see any state changes we make
context.state.last_attacker = context.player.uuid
end
}
La priorité n'affecte que l'ordre entre les pouvoirs Lua d'un même boss. Elle n'interagit pas avec l'ordre d'exécution d'EliteScript.
Modèle d'exécution
Un runtime par boss
Chaque entité boss obtient sa propre instance de runtime Lua indépendante. Lorsque le boss apparaît, EliteMobs charge la source Lua, l'évalue dans un environnement sandboxé tout neuf, et stocke la table renvoyée. Lorsque le boss disparaît ou est retiré, le runtime est arrêté.
Cela signifie :
- Les variables globales Lua définies lors de l'évaluation du fichier (comme les fonctions utilitaires avec
local function) sont privées à ce boss. - Les fonctions de hook de la table renvoyée ne sont jamais partagées entre les boss.
Isolation de l'état
Chaque runtime possède sa propre table context.state. L'état d'un boss est totalement invisible pour tous les autres boss, même s'ils partagent le même fichier de pouvoir Lua. Utilisez context.state pour stocker des compteurs, des flags, des minuteurs ou toute donnée propre au boss dont vous avez besoin entre les hooks.
return {
api_version = 1,
on_boss_damaged_by_player = function(context)
-- Each boss tracks its own enrage counter independently
context.state.enrage_hits = (context.state.enrage_hits or 0) + 1
if context.state.enrage_hits >= 20 then
context.boss:apply_potion_effect("SPEED", 200, 2)
end
end
}
Propriété des tâches planifiées
Toutes les tâches créées via context.scheduler appartiennent au runtime qui les a créées. Lorsqu'un boss disparaît :
- Le runtime appelle
shutdown(). - Chaque tâche possédée -- qu'elle soit unique (
run_after) ou répétée (run_every) -- est automatiquement annulée. - Toutes les surveillances de zone sont effacées.
Vous n'avez jamais besoin de nettoyer manuellement les tâches planifiées lors du retrait d'un boss. Cependant, vous devriez tout de même annuler les tâches répétées lorsqu'elles ne sont plus nécessaires durant le jeu normal, afin d'éviter un travail inutile :
return {
api_version = 1,
on_enter_combat = function(context)
local pulse_count = 0
local task_id
task_id = context.scheduler:run_every(20, function(tick_context)
pulse_count = pulse_count + 1
if pulse_count > 10 or not tick_context.boss.exists then
tick_context.scheduler:cancel_task(task_id)
return
end
tick_context.world:spawn_particle_at_location(
tick_context.boss:get_location(),
{ particle = "FLAME", amount = 20, speed = 0.1 }
)
end)
end
}
Comportement de l'horloge par tick
L'horloge interne de tick d'une instance de pouvoir Lua ne tourne que lorsque le pouvoir définit un hook on_game_tick. Les surveillances de zone créées via context.zones:watch_zone(...) ou context.script:zone(...):watch(...) créent leurs propres tâches répétées possédées au lieu d'activer le hook on_game_tick de premier niveau du pouvoir.
Si un pouvoir n'a ni on_game_tick ni surveillants de zone, aucun travail par tick n'est engendré. Le travail de tick et les tâches de surveillants de zone sont annulés automatiquement lorsque le boss disparaît ou que le runtime s'arrête.
Comportement en matière d'erreurs et de performances
EliteMobs applique des limites strictes d'erreur et de performance sur les pouvoirs Lua :
Exceptions
Si une fonction de hook ou un callback planifié déclenche une erreur Lua (ou si une exception Java remonte d'un appel d'API), le pouvoir est immédiatement désactivé pour cette instance de boss. Le runtime est arrêté et toutes les tâches possédées sont annulées.
L'erreur est consignée dans la console du serveur avec le nom du fichier du pouvoir, le numéro de ligne et le hook qui était en cours d'exécution :
[Lua] Error in 'frost_cone.lua' at line 35 during 'on_boss_damaged_by_player':
[Lua] -> ...explanation of what went wrong...
[Lua] -> Script has been disabled for this entity to prevent further errors.
Budget d'exécution
Chaque invocation de hook et chaque invocation de callback est chronométrée. Si un seul appel prend plus de 50 millisecondes, le pouvoir est désactivé avec un avertissement dans la console :
[Lua] my_power.lua took 73ms in 'on_game_tick' (limit: 50ms) — script disabled to prevent lag.
Cela empêche les scripts incontrôlés de geler le serveur. Pour rester dans le budget :
- Évitez les boucles non bornées à l'intérieur des hooks. Utilisez
context.scheduler:run_every(...)pour répartir le travail sur plusieurs ticks. - Gardez les gestionnaires
on_game_ticklégers -- ils s'exécutent à chaque tick. - Déplacez l'initialisation lourde dans
on_spawnouon_enter_combatplutôt que de la répéter à chaque tick.
Sandbox Lua
Les pouvoirs Lua s'exécutent dans un environnement LuaJ sandboxé. Plusieurs variables globales qui pourraient accéder au système de fichiers ou au runtime Java sont supprimées.
Variables globales supprimées
Les variables globales Lua standard suivantes sont mises à nil et ne peuvent pas être utilisées :
| Supprimé | Pourquoi |
|---|---|
debug | Expose l'état interne de la VM |
dofile | Accès au système de fichiers |
io | Accès au système de fichiers |
load | Chargement de code arbitraire |
loadfile | Accès au système de fichiers |
luajava | Accès direct aux classes Java |
module | Système de modules (non nécessaire) |
os | Accès au système d'exploitation |
package | Système de modules (non nécessaire) |
require | Système de modules / accès au système de fichiers |
Bibliothèque standard disponible
Tout le reste de la bibliothèque standard Lua fonctionne normalement :
| Catégorie | Fonctions |
|---|---|
| Math | math.abs, math.ceil, math.floor, math.max, math.min, math.random, math.sin, math.cos, math.sqrt, math.pi, et toutes les autres fonctions math.* |
| String | string.byte, string.char, string.find, string.format, string.gsub, string.len, string.lower, string.match, string.rep, string.sub, string.upper, et toutes les autres fonctions string.* |
| Table | table.insert, table.remove, table.sort, table.concat, et toutes les autres fonctions table.* |
| Itérateurs | pairs, ipairs, next |
| Type | type, tostring, tonumber, select, unpack |
| Gestion d'erreurs | pcall, xpcall, error, assert |
| Autre | print, rawget, rawset, rawequal, rawlen, setmetatable, getmetatable |
print écrit dans la console du serveur, mais préférez context.log:info(msg) ou context.log:warn(msg) pour la sortie. Ceux-ci sont préfixés par le nom du pouvoir, ce qui facilite le repérage du pouvoir ayant produit le message.
Espace de noms d'aide em
La table em est disponible au moment du chargement du fichier (avant l'exécution de tout hook). Elle fournit des constructeurs d'aide pour construire des location tables, des vector tables et des définitions de zone utilisées dans toute l'API.
| Fonction | Objectif |
|---|---|
em.create_location(x, y, z [, world, yaw, pitch]) | Crée une location table avec un nom de monde, un yaw et un pitch optionnels |
em.create_vector(x, y, z) | Crée une vector table |
em.zone.create_sphere_zone(radius) | Crée une définition de zone sphère |
em.zone.create_dome_zone(radius) | Crée une définition de zone dôme |
em.zone.create_cylinder_zone(radius, height) | Crée une définition de zone cylindre |
em.zone.create_cuboid_zone(x, y, z) | Crée une définition de zone cuboïde |
em.zone.create_cone_zone(length, radius) | Crée une définition de zone cône |
em.zone.create_static_ray_zone(length, thickness) | Crée une définition de zone rayon statique |
em.zone.create_rotating_ray_zone(length, point_radius, animation_duration) | Crée une définition de zone rayon rotatif |
em.zone.create_translating_ray_zone(length, point_radius, animation_duration) | Crée une définition de zone rayon translatant |
Les constructeurs de zone renvoient des tables chaînables avec :set_center(loc) (ou :set_origin(loc) / :set_destination(loc) selon le type de zone). Ils sont conçus pour être utilisés en haut d'un fichier ou à l'intérieur des hooks :
-- At file scope: create a reusable zone shape
local blast_zone = em.zone.create_sphere_zone(5)
return {
api_version = 1,
on_boss_damaged_by_player = function(context)
-- Anchor the zone to the boss's current location at call time
blast_zone:set_center(context.boss:get_location())
local entities = context.zones:get_entities_in_zone(blast_zone)
for i = 1, #entities do
if entities[i].type == "PLAYER" then
entities[i]:apply_potion_effect("SLOWNESS", 60, 1)
end
end
end
}
Pour une présentation complète des formes de zone, des filtres, des surveillants et des modèles de ciblage, voir Zones et Ciblage.
Étapes suivantes
- Boss et Entités --
context.boss,context.player, les wrappers d'entité - Monde et Environnement -- particules, sons, apparition,
context.world - Zones et Ciblage -- zones natives, utilitaires de script,
context.zones/context.script - Exemples et Modèles -- des pouvoirs complets et fonctionnels que vous pouvez étudier et adapter
- Enums et Valeurs -- liens Javadoc Spigot pour toutes les constantes de chaîne
