Aller au contenu principal

Script Lua : Exemples et modèles

Cette page contient des exemples complets et fonctionnels de pouvoirs Lua EliteMobs, ainsi que des modèles pratiques, des bonnes pratiques et des conseils. Chaque exemple comprend une explication détaillée de ce qu'il fait et pourquoi.

Si vous débutez avec les pouvoirs Lua, commencez par Premiers pas. Pour les détails complets de l'API, consultez la Référence API, Boss et entités, Monde et environnement, Zones et ciblage et Enums.

webapp_banner.jpg


Exemple : Ciblage par zone avec les utilitaires de script

Ce que cet exemple enseigne : Comment utiliser context.script pour créer une géométrie de zone de style EliteScript depuis Lua, générer des particules et infliger des dégâts aux entités dans la zone.

Fichier de pouvoir complet (cliquez pour développer)
return {
api_version = 1,

on_boss_damaged_by_player = function(context)
local cone = context.script:zone({
shape = "CONE",
Target = {
targetType = "SELF",
offset = "0,1,0"
},
Target2 = {
targetType = "NEARBY_PLAYERS",
range = 20
},
radius = 5
})

context.script:spawn_particles(
cone:full_target(0.4),
{
particle = "FLAME",
amount = 1,
speed = 0.05
}
)

context.script:damage(cone:full_target(), 1.0, 1.5)
end
}

Explication

  1. Création de zone -- context.script:zone(...) crée une forme de cône en utilisant les mêmes noms de champs que les Zones EliteScript. Target définit l'origine du cône (le boss lui-même, décalé de 1 bloc vers le haut), et Target2 définit la destination (les joueurs les plus proches dans un rayon de 20 blocs). radius contrôle l'ouverture du cône.

  2. Génération de particules -- cone:full_target(0.4) retourne un handle de cible qui résout toutes les positions à l'intérieur du cône avec 40% de couverture (échantillonne aléatoirement 40% des points de zone à chaque appel). La spécification de particules utilise les mêmes noms de champs que les particules EliteScript : particle, amount et speed.

  3. Dégâts -- context.script:damage(cone:full_target(), 1.0, 1.5) touche toutes les entités vivantes à l'intérieur du cône complet. Le premier nombre (1.0) est le montant de dégâts de base, et le second (1.5) est le multiplicateur de dégâts appliqué aux joueurs.

Noms de champs EliteScript

Les tables de zone et de cible passées à context.script utilisent les noms de champs EliteScript (targetType, shape, Target, Target2, range, offset, coverage). Pour la liste complète, consultez Zones EliteScript et Cibles EliteScript.


Exemple : Boucle d'attaque avec état et planificateur

Ce que cet exemple enseigne : Utilisation de context.state pour suivre l'état d'exécution, context.scheduler pour les tâches répétitives et le cycle de vie approprié d'entrée/sortie de combat.

Fichier de pouvoir complet (cliquez pour développer)
local function pick_action(context)
local roll = math.random(1, 2)

if roll == 1 then
context.boss:play_model_animation("slam")
else
context.boss:play_model_animation("roar")
end
end

return {
api_version = 1,

on_spawn = function(context)
context.state.started = false
context.state.loop_task_id = nil
end,

on_enter_combat = function(context)
if context.state.started then
return
end

context.state.started = true

context.state.loop_task_id = context.scheduler:run_every(100, function(loop_context)
if loop_context.boss.exists then
pick_action(loop_context)
end
end)
end,

on_exit_combat = function(context)
if context.state.loop_task_id ~= nil then
context.scheduler:cancel_task(context.state.loop_task_id)
context.state.loop_task_id = nil
end
context.state.started = false
end
}

Explication

  1. Initialisation de l'état -- on_spawn initialise context.state.started à false et context.state.loop_task_id à nil. La table state persiste pendant toute la durée de vie de cette instance de boss, donc les valeurs définies ici survivent entre les hooks.

  2. Garde de combat -- on_enter_combat vérifie context.state.started avant de démarrer la boucle. Cela empêche les boucles multiples superposées si l'événement se déclenche plus d'une fois.

  3. Modèle de planificateur -- context.scheduler:run_every(100, callback) exécute le callback toutes les 100 ticks (5 secondes). Le callback reçoit un contexte frais, donc loop_context.boss vous donne le dernier snapshot du boss. Le planificateur retourne un ID de tâche numérique que vous stockez dans l'état.

  4. Nettoyage à la sortie -- on_exit_combat annule la tâche répétitive en utilisant l'ID de tâche stocké et réinitialise l'état. C'est essentiel : sans nettoyage, le planificateur continue de s'exécuter même après la fin du combat.

Toujours annuler les tâches du planificateur

Si vous démarrez une tâche répétitive dans on_enter_combat, annulez-la toujours dans on_exit_combat. Oublier d'annuler laisse une tâche en arrière-plan qui s'exécute jusqu'au despawn du boss, ce qui gaspille des performances et peut causer un comportement inattendu.


Exemple : Effet de feu au toucher

Ce que cet exemple enseigne : Un simple pouvoir « appliquer un effet au toucher » -- le modèle le plus courant pour les pouvoirs de combat.

Fichier de pouvoir complet (cliquez pour développer)
return {
api_version = 1,

on_player_damaged_by_boss = function(context)
-- Garde : le joueur peut être nil dans des cas limites
if context.player == nil then
return
end

-- Vérifie et définit un cooldown local de 60 ticks (3 secondes) en un seul appel
if not context.cooldowns:check_local("fire_touch", 60) then
return
end

-- Met le joueur en feu pendant 60 ticks (3 secondes)
context.player:set_fire_ticks(60)

-- Retour visuel : génère des particules de flamme à la position du joueur
context.world:spawn_particle_at_location(
context.player.current_location,
{ particle = "FLAME", amount = 20, speed = 0.1 }
)

-- Indique au joueur ce qui s'est passé
context.player:send_message("&cThe boss's touch burns!")

-- Définit le cooldown global des pouvoirs pour que les autres pouvoirs
-- de ce boss ne se déclenchent pas tous au même instant
context.cooldowns:set_global(40)
end
}

Explication

  1. Garde nil -- context.player est une clé paresseuse qui résout le joueur impliqué dans l'événement. Dans de rares cas limites (par ex., le joueur s'est déconnecté entre le déclenchement de l'événement et l'exécution du hook), il peut être nil. Vérifiez toujours avant d'utiliser.

  2. Cooldown local -- context.cooldowns:check_local("fire_touch", 60) fait deux choses de manière atomique : il vérifie si la clé de cooldown "fire_touch" est prête, et si c'est le cas, il définit immédiatement le cooldown à 60 ticks. Si le cooldown n'est pas prêt, il retourne false et la fonction se termine prématurément. La clé "fire_touch" est limitée à cette instance de boss -- les autres boss avec le même pouvoir ont des cooldowns indépendants.

  3. Ticks de feu -- context.player:set_fire_ticks(60) met le joueur en feu pendant 60 ticks de jeu (3 secondes). Cela appelle directement la méthode Bukkit sous-jacente.

  4. Particules -- context.world:spawn_particle_at_location(location, spec) génère des particules à un emplacement spécifique. La table spec accepte particle (nom de l'enum de particules Bukkit), amount et speed.

  5. Message -- context.player:send_message(text) envoie un message avec codes couleur dans le chat. Les codes couleur Minecraft standard comme &c (rouge) fonctionnent automatiquement.

  6. Cooldown global -- context.cooldowns:set_global(40) met tous les pouvoirs de ce boss en cooldown de 40 ticks (2 secondes). Cela empêche plusieurs pouvoirs de se déclencher simultanément.


Exemple : Pouvoir AoE par zone avec zones natives Lua

Ce que cet exemple enseigne : Créer et interroger des zones Lua natives pour infliger des dégâts aux joueurs dans une zone.

Fichier de pouvoir complet (cliquez pour développer)
return {
api_version = 1,

on_spawn = function(context)
context.state.aoe_task_id = nil
end,

on_enter_combat = function(context)
-- Empêcher les boucles en double
if context.state.aoe_task_id ~= nil then
return
end

context.state.aoe_task_id = context.scheduler:run_every(60, function(tick_context)
-- S'assurer que le boss est toujours en vie
if not tick_context.boss.exists then
return
end

-- Vérifier un cooldown local pour éviter l'empilement avec d'autres effets
if not tick_context.cooldowns:check_local("pulse_aoe", 60) then
return
end

-- Construire une zone sphérique centrée sur la position actuelle du boss
local zone_def = {
kind = "sphere",
radius = 8,
origin = tick_context.boss:get_location()
}

-- Trouver tous les joueurs à l'intérieur de la sphère
local victims = tick_context.zones:get_entities_in_zone(zone_def, { filter = "players" })

-- Infliger des dégâts et afficher des particules sur chaque joueur trouvé
for i = 1, #victims do
local victim = victims[i]
victim:deal_custom_damage(4.0)

tick_context.world:spawn_particle_at_location(
victim.current_location,
{ particle = "DUST", amount = 15, speed = 0, red = 128, green = 0, blue = 255 }
)
end

-- Générer des particules visuelles en anneau au boss
tick_context.world:spawn_particle_at_location(
tick_context.boss:get_location(),
{ particle = "WITCH", amount = 40, speed = 0.1 }
)

-- Définir le cooldown global
tick_context.cooldowns:set_global(60)
end)
end,

on_exit_combat = function(context)
if context.state.aoe_task_id ~= nil then
context.scheduler:cancel_task(context.state.aoe_task_id)
context.state.aoe_task_id = nil
end
end
}

Explication

  1. Configuration de l'état -- on_spawn initialise aoe_task_id à nil. Cet ID de tâche conservera la référence du planificateur répétitif.

  2. Attaque répétitive -- on_enter_combat démarre une tâche répétitive toutes les 60 ticks (3 secondes). La garde au début empêche de démarrer une seconde boucle si le combat reprend.

  3. Définition de zone -- La table zone_def utilise la syntaxe de zone Lua native. Le champ kind spécifie la forme ("sphere"), radius définit la taille, et origin est défini sur la position actuelle du boss au moment où le callback s'exécute. Cela signifie que la zone suit le boss lorsqu'il se déplace.

  4. Requête d'entités -- tick_context.zones:get_entities_in_zone(zone_def, { filter = "players" }) retourne un tableau Lua de toutes les tables de joueurs à l'intérieur de la sphère. L'option filter accepte "players", "elites", "mobs" ou "living" (par défaut).

  5. Infliger des dégâts -- victim:deal_custom_damage(4.0) inflige 4 points de dégâts attribués au boss. Cela utilise le système de dégâts personnalisé d'EliteMobs, qui respecte l'armure et les autres modificateurs de combat.

  6. Retour visuel par particules -- Des particules de poussière violette apparaissent sur chaque victime, et des particules ambiantes apparaissent à la position du boss pour signaler visuellement l'impulsion.

  7. Nettoyage -- on_exit_combat annule la tâche répétitive et efface l'état, suivant le même modèle que l'exemple précédent.

Zones natives vs. zones d'utilitaires de script

Cet exemple utilise les zones Lua natives (context.zones:get_entities_in_zone()), qui prennent une table simple avec kind, radius, origin, etc. Les utilitaires de script (context.script:zone(...)) utilisent les noms de champs EliteScript comme shape, Target et Target2. Les deux fonctionnent -- utilisez les zones natives pour les formes simples et context.script lorsque vous avez besoin de la résolution avancée de cibles d'EliteScript.


Exemple : Mécanique de boss multi-phases

Ce que cet exemple enseigne : Utilisation de l'état pour suivre les phases du boss et changer de comportement aux seuils de santé.

Fichier de pouvoir complet (cliquez pour développer)
local function phase_one_attack(context)
-- Frappe lente et lourde
context.boss:play_model_animation("slam")

local zone_def = {
kind = "sphere",
radius = 5,
origin = context.boss:get_location()
}

local targets = context.zones:get_entities_in_zone(zone_def, { filter = "players" })
for i = 1, #targets do
targets[i]:deal_custom_damage(3.0)
end

context.world:spawn_particle_at_location(
context.boss:get_location(),
{ particle = "EXPLOSION", amount = 3, speed = 0 }
)
end

local function phase_two_attack(context)
-- Multi-coups rapides et frénétiques
context.boss:play_model_animation("frenzy")

local zone_def = {
kind = "sphere",
radius = 8,
origin = context.boss:get_location()
}

local targets = context.zones:get_entities_in_zone(zone_def, { filter = "players" })
for i = 1, #targets do
targets[i]:deal_custom_damage(2.0)
targets[i]:apply_potion_effect("SLOWNESS", 40, 1)
end

context.world:spawn_particle_at_location(
context.boss:get_location(),
{ particle = "DUST", amount = 30, speed = 0.2, red = 255, green = 0, blue = 0 }
)

context.world:play_sound_at_location(
context.boss:get_location(),
"entity.wither.ambient",
1.0, 1.5
)
end

return {
api_version = 1,

on_spawn = function(context)
context.state.phase = 1
context.state.attack_task_id = nil
context.state.phase_switched = false
end,

on_enter_combat = function(context)
if context.state.attack_task_id ~= nil then
return
end

-- Démarrer la boucle d'attaque phase 1 : toutes les 100 ticks (5 secondes)
context.state.attack_task_id = context.scheduler:run_every(100, function(tick_context)
if not tick_context.boss.exists then
return
end

if not tick_context.cooldowns:check_local("phase_attack", 100) then
return
end

phase_one_attack(tick_context)
end)
end,

on_game_tick = function(context)
-- Ne vérifier la transition de phase que toutes les 20 ticks (1 seconde) pour rester léger
if not context.cooldowns:check_local("phase_check", 20) then
return
end

-- Ignorer si déjà en phase 2
if context.state.phase ~= 1 then
return
end

-- Vérifier le ratio de santé
local health_ratio = context.boss.health / context.boss.maximum_health

if health_ratio <= 0.5 then
-- Transition vers la phase 2
context.state.phase = 2
context.state.phase_switched = true

context.log:info("Boss entering phase 2 at " .. tostring(math.floor(health_ratio * 100)) .. "% health")

-- Annuler l'ancienne boucle d'attaque
if context.state.attack_task_id ~= nil then
context.scheduler:cancel_task(context.state.attack_task_id)
context.state.attack_task_id = nil
end

-- Jouer les effets de transition
context.boss:play_model_animation("transform")

-- Annoncer le changement de phase aux joueurs proches
local nearby = context.players.nearby_players(40)
for i = 1, #nearby do
nearby[i]:send_message("&4&lThe boss enters a frenzy!")
nearby[i]:show_title("&4Phase 2", "&cThe boss is enraged!", 10, 40, 10)
end

-- Démarrer une boucle d'attaque phase 2 plus rapide : toutes les 40 ticks (2 secondes)
context.state.attack_task_id = context.scheduler:run_every(40, function(tick_context)
if not tick_context.boss.exists then
return
end

if not tick_context.cooldowns:check_local("phase_attack", 40) then
return
end

phase_two_attack(tick_context)
end)
end
end,

on_exit_combat = function(context)
if context.state.attack_task_id ~= nil then
context.scheduler:cancel_task(context.state.attack_task_id)
context.state.attack_task_id = nil
end
end
}

Explication

  1. Initialisation de l'état -- on_spawn définit phase à 1, attack_task_id à nil et phase_switched à false. Ces valeurs persistent entre tous les hooks pour cette instance de boss.

  2. Boucle d'attaque phase 1 -- on_enter_combat démarre une tâche répétitive qui exécute phase_one_attack toutes les 100 ticks. L'attaque crée une sphère de 5 blocs, inflige des dégâts aux joueurs à l'intérieur et joue une animation de frappe avec des particules d'explosion.

  3. Vérification de phase dans on_game_tick -- on_game_tick se déclenche à chaque tick serveur, donc la première chose qu'il fait est de vérifier un cooldown de 20 ticks ("phase_check") pour éviter d'exécuter une logique coûteuse à chaque tick. Si le boss est déjà en phase 2, il retourne immédiatement.

  4. Seuil de santé -- context.boss.health / context.boss.maximum_health donne la santé actuelle sous forme de fraction. Lorsqu'elle descend à 50% ou en dessous, la transition commence.

  5. Transition de phase -- L'ancienne boucle d'attaque est annulée, une animation de transformation est jouée, les joueurs proches reçoivent un message et un titre, et une nouvelle boucle d'attaque plus rapide démarre (toutes les 40 ticks au lieu de 100).

  6. Attaques phase 2 -- phase_two_attack utilise une sphère plus grande (8 blocs), inflige un peu moins de dégâts par coup mais se déclenche beaucoup plus fréquemment, applique un effet de potion de lenteur et utilise des particules de poussière rouge et un son de wither pour une ambiance différente.

  7. Journalisation -- context.log:info(...) écrit dans la console du serveur, ce qui est inestimable pour déboguer les transitions de phase pendant le développement.

  8. Nettoyage -- on_exit_combat annule la boucle d'attaque active, quelle que soit la phase du boss.

Gardez on_game_tick léger

on_game_tick s'exécute à chaque tick serveur (20 fois par seconde). Protégez toujours le travail coûteux derrière une vérification de cooldown, comme montré avec check_local("phase_check", 20). Si votre hook dépasse 50ms, EliteMobs désactivera automatiquement le pouvoir.


Conseils pour la génération par IA

Si vous souhaitez que l'IA génère des pouvoirs Lua de manière fiable, assurez-vous que le prompt inclut :

  • Nom de hook exact -- par ex., on_player_damaged_by_boss, pas « quand le boss frappe un joueur ».
  • Zones Lua natives ou utilitaires de script -- précisez lequel. Les zones natives utilisent context.zones:get_entities_in_zone(zone_def, opts) avec kind, radius, origin. Les utilitaires de script utilisent context.script:zone(...) avec les noms de champs EliteScript comme shape, Target, Target2.
  • Cooldowns locaux et globaux -- précisez le nom de clé de cooldown et la durée en ticks. context.cooldowns:check_local(key, ticks) pour les cooldowns par boss, context.cooldowns:set_global(ticks) pour le cooldown de pouvoir partagé.
  • Noms d'animation de modèle personnalisé -- par ex., context.boss:play_model_animation("slam"). L'IA ne peut pas deviner les noms d'animation ; fournissez-les.
  • Sélection de cible -- précisez si le pouvoir cible context.player (le joueur de l'événement), context.players.nearby_players(range) ou une requête de zone.
  • Type d'effet -- dégâts (deal_custom_damage), potion (apply_potion_effect), feu (set_fire_ticks), vélocité (set_velocity_vector, apply_push_vector), etc.
  • N'utilisez que des noms de méthodes documentés -- si ce n'est pas sur la page de Référence API, cela n'existe pas.
  • Les spécifications d'utilitaires de script utilisent les noms de champs EliteScript -- targetType, shape, Target, Target2, range, offset, coverage, pas des noms de style Lua.
  • Les callbacks du planificateur acceptent un contexte frais -- le paramètre du callback est un nouveau contexte, pas le contexte externe. Utilisez toujours tick_context (ou le nom que vous donnez au paramètre) à l'intérieur des callbacks.

Bon exemple de prompt

Écrivez un pouvoir Lua qui utilise on_enter_combat pour démarrer une tâche répétitive toutes les 80 ticks. À chaque tick, créez une zone de sphère Lua native (rayon 6) centrée sur le boss, interrogez les joueurs à l'intérieur et infligez 2.0 de dégâts personnalisés à chacun. Utilisez une clé de cooldown locale "pulse" avec une durée de 80. Annulez la tâche dans on_exit_combat. Générez des particules DUST (red=0, green=255, blue=100) sur chaque victime.

Contraintes supplémentaires à inclure

  • « Retournez une seule table avec api_version = 1. »
  • « Initialisez tous les champs d'état dans on_spawn. »
  • « Protégez les callbacks du planificateur avec if not tick_context.boss.exists then return end. »
  • « Annulez toutes les tâches répétitives dans on_exit_combat. »
  • « N'inventez pas de noms de méthodes -- n'utilisez que les méthodes de la page de Référence API. »
  • « Utilisez context.cooldowns:check_local(key, ticks) pour une vérification-et-définition combinée. »

Liste de vérification QC pour révision humaine ou IA

Utilisez cette liste de vérification pour valider un pouvoir Lua avant de le déployer :

  1. Le fichier retourne exactement une table avec api_version = 1.
  2. Chaque nom de hook correspond exactement à une entrée dans la liste des hooks (par ex., on_player_damaged_by_boss, pas on_player_hit).
  3. context.player est protégé avec == nil avant utilisation dans les hooks où il peut être nil.
  4. Les champs de context.state sont initialisés dans on_spawn.
  5. Chaque appel context.scheduler:run_every(...) a un context.scheduler:cancel_task(...) correspondant dans on_exit_combat.
  6. Les callbacks du planificateur utilisent le paramètre de contexte propre au callback, pas le context externe.
  7. Les clés de cooldown sont des chaînes descriptives (par ex., "fire_pulse") et les durées sont en ticks.
  8. Les hooks on_game_tick protègent le travail coûteux derrière une vérification de cooldown.
  9. Tous les noms de méthodes existent dans la Référence API -- pas d'alias inventés.
  10. Les tables d'utilitaires de script utilisent les noms de champs EliteScript (targetType, shape, Target, etc.), pas des noms de style Lua.
  11. Les définitions de zone natives utilisent kind, radius, origin, destination, etc.
  12. Le pouvoir n'appelle aucune opération bloquante ou de longue durée à l'intérieur d'un hook ou callback.
  13. Les spécifications de particules utilisent des noms d'enum de particules Bukkit valides en MAJUSCULES (par ex., "FLAME", "DUST", "EXPLOSION").

Bonnes pratiques

  • Commencez petit et vérifiez. Écrivez un seul on_spawn qui envoie un message de log. Confirmez qu'il se déclenche. Puis construisez à partir de là.

  • Gardez les fonctions auxiliaires locales. Déclarez les helpers comme local function pick_action(context) au-dessus de la table de retour. Cela les garde hors de la portée globale et évite les collisions avec d'autres pouvoirs Lua chargés dans le même runtime.

  • Placez la géométrie dans les utilitaires de script. Si vous avez besoin de cônes, de rayons rotatifs, de rayons en translation ou de zones animées, utilisez context.script:zone(...) avec les noms de champs EliteScript. Les utilitaires de script réutilisent le moteur de zones éprouvé d'EliteScript.

  • Utilisez context.state pour l'état d'exécution. N'utilisez pas de variables globales Lua. context.state est limité à une seule instance de boss et persiste entre les hooks pendant toute la durée de vie de ce boss.

  • Utilisez des clés de cooldown locales nommées. Au lieu de simples nombres, utilisez des clés descriptives comme "fire_touch" ou "aoe_pulse". Cela facilite le débogage et empêche les collisions accidentelles entre différents cooldowns dans le même pouvoir.

  • Gardez on_game_tick léger. Protégez-le toujours derrière une vérification de cooldown. Si votre logique s'exécute à chaque tick, elle doit se terminer en bien moins de 50ms ou le pouvoir sera désactivé.

  • Annulez les tâches répétitives quand c'est terminé. Chaque run_every doit avoir un cancel_task correspondant dans on_exit_combat (et éventuellement on_death). Les tâches non annulées gaspillent du CPU et peuvent causer des erreurs de référence nulle après la disparition du boss.

  • Utilisez des contextes frais dans les callbacks du planificateur. Les callbacks du planificateur (run_every, run_after) reçoivent un contexte frais comme paramètre. Utilisez toujours ce paramètre -- pas le context externe -- car le contexte externe peut contenir des snapshots obsolètes.

  • Journalisez les transitions d'état avec context.log:info(). Pendant le développement, ajoutez des logs pour les changements de phase, les débuts de cooldown et les démarrages/arrêts du planificateur. Supprimez ou changez en context.log:debug() avant le déploiement.

  • Réutilisez la documentation EliteScript existante. Les pages Zones, Cibles, Vecteurs relatifs et Conditions documentent les mêmes noms de champs que les utilitaires de script acceptent. Ne dupliquez pas ces informations dans votre pouvoir Lua -- faites simplement référence à celles-ci.


Erreurs courantes de débutant

  • Utiliser le context externe dans un callback du planificateur. Le contexte externe capture un snapshot au moment où le hook s'est exécuté. Dans un callback run_every ou run_after, utilisez toujours le paramètre propre du callback (par ex., tick_context), qui vous donne un snapshot frais.

  • Oublier d'annuler les tâches répétitives. Si vous démarrez un run_every dans on_enter_combat mais ne l'annulez jamais, la tâche continue de s'exécuter jusqu'à ce que le boss soit retiré du serveur, même après la fin du combat.

  • Ne pas initialiser l'état dans on_spawn. Si vous lisez context.state.phase dans on_game_tick mais ne le définissez jamais dans on_spawn, il sera nil et vos comparaisons se comporteront de manière inattendue.

  • Vérifier context.player sans garde nil. Dans les hooks comme on_player_damaged_by_boss, le joueur est presque toujours disponible -- mais « presque toujours » n'est pas « toujours ». Un seul garde nil manquant peut faire planter le pouvoir.

  • Utiliser des noms de champs style Lua dans les appels d'utilitaires de script. Les utilitaires de script attendent targetType, Target, Target2, shape -- pas target_type, target, target2, zone_shape. Les noms non concordants ne produisent silencieusement aucun résultat.

  • Exécuter une logique lourde dans on_game_tick sans garde de cooldown. Ce hook se déclenche à chaque tick du serveur. Même une arithmétique simple répétée 20 fois par seconde sur de nombreux boss s'accumule.

  • Inventer des noms de méthodes. Si une méthode n'est pas listée dans la Référence API, elle n'existe pas. Les erreurs courantes incluent écrire entity:teleport(loc) au lieu de entity:teleport_to_location(loc), ou player:set_velocity(vec) au lieu de player:set_velocity_vector(vec).

  • Utiliser context.boss.health pour définir la santé. context.boss.health est un snapshot en lecture seule. Pour guérir le boss, utilisez context.boss:restore_health(amount).

  • Oublier api_version = 1. La table retournée doit inclure ce champ, sinon EliteMobs ne chargera pas le pouvoir.


Étapes suivantes