Aller au contenu principal

Scripting Lua : Hooks et Cycle de Vie

webapp_banner.jpg

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.

Hooks des pouvoirs de boss

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.

HookSe déclenche quandcontext.player disponible ?
on_spawnL'elite mob apparaîtNon
on_game_tickUne fois par tick serveur (50 ms) tant que l'horloge du runtime est activeNon
on_boss_damagedLe boss subit des dégâts de n'importe quelle sourceNon
on_boss_damaged_by_playerLe boss subit des dégâts d'un joueurOui
on_boss_damaged_by_eliteLe boss subit des dégâts d'un autre elite mobNon
on_player_damaged_by_bossUn joueur subit des dégâts de ce bossOui
on_enter_combatLe boss entre en combatOui
on_exit_combatLe boss quitte le combatNon
on_healLe boss se soigneNon
on_boss_target_changedLe boss change de cibleOui
on_deathLe boss meurtNon
on_phase_switchUn boss à phases passe à une nouvelle phaseNon
on_zone_enterUne entité entre dans une zone surveilléeOui (si l'entité est un joueur)
on_zone_leaveUne entité quitte une zone surveilléeOui (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.

Source des hooks de zone

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éthodeTypeDescription
event.damage_amountdoubleValeur de dégâts brute
event.damage_causestringNom Spigot DamageCause (ex. "ENTITY_ATTACK", "PROJECTILE")
event.damagerentity tableEntité ayant infligé les dégâts. Présent uniquement sur les hooks de dégâts-par-entité.
event.projectileentity tableL'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éthodeTypeDescription
event.spawn_reasonstringNom Spigot SpawnReason
event.cancel_event()Annule l'apparition

Hook de mort

S'applique à on_death.

Champ / MéthodeTypeDescription
event.entityentity tableL'entité mourante

Hooks de zone

S'applique à on_zone_enter et on_zone_leave.

Champ / MéthodeTypeDescription
event.entityentity tableL'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 :

  1. Le runtime appelle shutdown().
  2. Chaque tâche possédée -- qu'elle soit unique (run_after) ou répétée (run_every) -- est automatiquement annulée.
  3. 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_tick légers -- ils s'exécutent à chaque tick.
  • Déplacez l'initialisation lourde dans on_spawn ou on_enter_combat plutô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
debugExpose l'état interne de la VM
dofileAccès au système de fichiers
ioAccès au système de fichiers
loadChargement de code arbitraire
loadfileAccès au système de fichiers
luajavaAccès direct aux classes Java
moduleSystème de modules (non nécessaire)
osAccès au système d'exploitation
packageSystème de modules (non nécessaire)
requireSystè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égorieFonctions
Mathmath.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.*
Stringstring.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.*
Tabletable.insert, table.remove, table.sort, table.concat, et toutes les autres fonctions table.*
Itérateurspairs, ipairs, next
Typetype, tostring, tonumber, select, unpack
Gestion d'erreurspcall, xpcall, error, assert
Autreprint, rawget, rawset, rawequal, rawlen, setmetatable, getmetatable
astuce

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.

FonctionObjectif
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