Aller au contenu principal

Scripting Lua : Scripts de PNJ

webapp_banner.jpg

Les scripts Lua de PNJ d'EliteMobs sont des fichiers .lua autonomes qui s'attachent aux configurations de PNJ. Ils sont distincts des pouvoirs Lua de boss : les pouvoirs de boss se trouvent dans plugins/EliteMobs/powers/, tandis que les scripts de PNJ se trouvent dans plugins/EliteMobs/npc_scripts/.

Les scripts de PNJ s'exécutent désormais sur le même runtime de scripting unifié MagmaCore que les pouvoirs de boss, les props de FreeMinecraftModels et les items FMM. Cela signifie qu'un script de PNJ dispose de toute la surface de scripting partagée — context.world (y compris strike_lightning), context.zones, context.scheduler, context.cooldowns, context.log, context.event et context.playerplus une table context.npc spécifique aux PNJ. Tout ce que MagmaCore expose aux scripts est également disponible ici.

Fonctionnalité expérimentale

Les scripts Lua de PNJ sont encore expérimentaux. Les hooks spécifiques aux PNJ et les utilitaires context.npc peuvent changer. Les tables partagées (context.world, context.zones, context.scheduler, context.cooldowns, context.log, context.event, context.player) sont les mêmes que celles documentées dans le Moteur de Scripting et la Référence de l'API Lua.


Emplacement des fichiers

Créez les fichiers de scripts de PNJ dans :

plugins/
EliteMobs/
npc_scripts/
wave.lua

Les sous-dossiers sont analysés, de manière récursive. Cependant, les scripts sont enregistrés par nom de fichier seul, donc npc_scripts/wave.lua et npc_scripts/town/wave.lua entrent en collision -- gardez des noms de base uniques dans toute l'arborescence.

L'extension .lua est optionnelle dans une configuration de PNJ : - wave et - wave.lua se résolvent tous deux en wave.lua. Si une configuration de PNJ référence un script inexistant, EliteMobs enregistre un avertissement et le PNJ apparaît quand même.


Attacher des scripts aux PNJ

Ajoutez une liste scripts: à la configuration du PNJ :

scripts:
- wave.lua

Plusieurs scripts peuvent être attachés à un même PNJ :

scripts:
- wave.lua
- greeting_particles.lua

Les scripts s'exécutent dans l'ordre de priorité. Les valeurs de priority les plus basses s'exécutent en premier. Si la priorité est omise, elle vaut 0 par défaut.


Forme d'un script

Chaque script de PNJ doit retourner une seule table :

return {
api_version = 1,
priority = 0,

on_spawn = function(context)
context.state.spawned = true
context.npc:play_model_animation("idle")
end
}

Seuls ces champs de premier niveau sont acceptés :

ChampTypeNotes
api_versionnumberRequis. Doit valoir 1.
prioritynumberOptionnel. Les valeurs les plus basses s'exécutent en premier.
on_spawnfunctionS'exécute après l'apparition du PNJ.
on_removefunctionS'exécute lorsque le PNJ est supprimé.
on_game_tickfunctionS'exécute à chaque tick serveur tant que le PNJ est valide. Gardez cela très léger.
on_npc_interactfunctionS'exécute lorsqu'un joueur interagit avec le PNJ.
on_npc_proximity_enterfunctionS'exécute une fois lorsqu'un joueur entre dans le rayon d'activation de ce PNJ.
on_npc_proximity_leavefunctionS'exécute une fois lorsqu'un joueur quitte le rayon d'activation de ce PNJ.
on_zone_enterfunctionS'exécute lorsqu'un joueur entre dans une zone surveillée par ce script (voir context.zones).
on_zone_leavefunctionS'exécute lorsqu'un joueur quitte une zone surveillée.

La surveillance de zone ne suit que les joueurs -- les mobs et autres entités ne déclenchent jamais on_zone_enter / on_zone_leave.

Les clés de premier niveau inconnues sont rejetées lors du chargement du script. Les fonctions auxiliaires doivent être déclarées comme fonctions local au-dessus de la table retournée.


Hooks de proximité

Les hooks de proximité des PNJ utilisent la valeur de configuration activationRadius du PNJ.

HookSe déclenche quand
on_npc_proximity_enterUn joueur passe de l'extérieur du rayon d'activation du PNJ à l'intérieur.
on_npc_proximity_leaveUn joueur passe de l'intérieur du rayon d'activation du PNJ à l'extérieur.

Ces hooks sont suivis par PNJ et par joueur par le scanner de proximité côté serveur. Se tenir près d'un PNJ n'empêche pas un autre PNJ de déclencher son propre événement d'entrée, et rester à l'intérieur du rayon ne provoque pas de répétition en boucle des événements d'entrée.

Le comportement normal de salutation, de dialogue et d'indicateur de quête continue de s'exécuter. Le hook Lua ajoute du comportement par-dessus.


Surface de scripting partagée

Comme les scripts de PNJ s'exécutent sur le runtime unifié, chaque hook de PNJ reçoit également les tables de context MagmaCore partagées utilisées par les scripts FreeMinecraftModels. Les pouvoirs de boss d'EliteMobs s'exécutent sur le même runtime mais utilisent des variantes spécifiques aux boss pour plusieurs tables. Les listes complètes de méthodes se trouvent dans la Référence de l'API Lua et le Moteur de Scripting :

TableCe qu'elle fait
context.worldEffets et requêtes sur le monde : strike_lightning, spawn_particle, play_sound, set_block_at, place_temporary_block, spawn_entity, spawn_firework, get_nearby_entities, get_nearby_players, raycast, et plus encore. Les deux formes sont acceptées : par coordonnées (strike_lightning(x, y, z)) et par table de localisation (strike_lightning_at_location(loc)).
context.zonesCréer des zones spatiales (create_sphere(x, y, z, radius), create_cylinder(x, y, z, radius, height), create_cuboid(x, y, z, xSize, ySize, zSize)) -- chacune renvoie un identifiant numérique. watch(handle, on_enter, on_leave) démarre le suivi (les callbacks déclenchent vos hooks on_zone_enter / on_zone_leave, pas les fonctions que vous passez) ; unwatch(handle) l'arrête.
context.schedulerrun_later(ticks, fn), run_repeating(delay, interval, fn), cancel(task_id).
context.cooldownsTemps de recharge partagés MagmaCore : local_ready, local_remaining, check_local, set_local, global_ready, set_global.
context.loginfo(msg), warn(msg), error(msg) — écrit dans la console du serveur.
context.eventL'événement Bukkit courant, lorsqu'il y en a un. Voir ci-dessous.
context.playerLe joueur qui interagit ou déclenche, lorsqu'il est présent. Voir ci-dessous.
context.stateUne table Lua simple qui persiste pour cette instance de script de PNJ jusqu'à la suppression du PNJ.

Exemple : foudroyer lors d'une interaction

return {
api_version = 1,

on_npc_interact = function(context)
-- NPC scripts can now reach the full world API.
context.world:strike_lightning_at_location(context.npc:get_location())
end
}

context.npc

context.npc est disponible dans chaque hook de PNJ.

Champs

ChampTypeNotes
namestringNom d'affichage du PNJ défini dans la configuration.
filenamestringNom du fichier de configuration du PNJ.
uuidstringUUID du PNJ à l'exécution.
activation_radiusnumberRayon d'activation configuré.
current_locationtable de localisationInstantané de la localisation lorsque l'entité sous-jacente existe.
entity_typestringType d'entité Bukkit lorsque l'entité sous-jacente existe.

Méthodes

MéthodeArgumentsRetourneNotes
is_valid()-booleanIndique si le PNJ possède encore une entité sous-jacente valide.
get_location()-table de localisationLocalisation actuelle du PNJ, ou localisation d'apparition si l'entité n'est pas disponible.
get_eye_location()-table de localisationLocalisation actuelle des yeux, ou repli sur la localisation d'apparition.
get_activation_radius()-numberRayon d'activation actuellement configuré.
get_nearby_players(radius)numbertableWrappers de joueurs dans le rayon autour du PNJ.
face_direction_or_location(target)vecteur ou localisationnilOriente vers un vecteur de direction ou tourne vers une localisation / la localisation d'un joueur.
say_greeting(player?)joueur, UUID, nom ou nilnilEnvoie une salutation configurée. Par défaut, le joueur déclencheur lorsqu'il est disponible.
say_dialog(player?)joueur, UUID, nom ou nilnilEnvoie le dialogue configuré. Par défaut, le joueur déclencheur lorsqu'il est disponible.
say_farewell(player?)joueur, UUID, nom ou nilnilEnvoie le texte d'adieu configuré. Par défaut, le joueur déclencheur lorsqu'il est disponible.
play_model_animation(name)stringnilJoue une animation de modèle personnalisé si elle existe. Sans effet et sans erreur sinon.
patrol_pause()-booléenMet en pause la patrouille configurée.
patrol_resume()-booléenAnnule une attente ou un déplacement temporaire et reprend la patrouille.
walk_to(x, y, z)trois nombresbooléenMarche jusqu'à un décalage depuis l'origine puis reprend la patrouille. Les longs trajets sont résolus automatiquement.
hold(x, y, z)trois nombresbooléenMarche jusqu'à un décalage et y reste.
teleport(x, y, z)trois nombresbooléenSe téléporte si la destination traite les entités.

Les méthodes de déplacement renvoient false si le PNJ n'a pas de patrouille configurée ou si la demande ne peut pas être acceptée. Consultez Patrouilles des PNJ et boss.


context.player

context.player est disponible dans on_npc_interact, on_npc_proximity_enter et on_npc_proximity_leave. Il vaut nil dans les hooks de cycle de vie qui n'impliquent pas de joueur.

C'est le wrapper de joueur partagé MagmaCore — la même table complète d'entité vivante / de joueur qu'utilisent les pouvoirs de boss et les scripts FMM, il expose donc bien plus que les bases (vie, effets de potion, send_message, show_title, show_action_bar, get_held_item, raycasting, et plus encore). Voir la Référence de l'API Lua pour la liste complète. Les éléments couramment utilisés ici :

Champ / MéthodeNotes
nameNom du joueur.
uuidUUID du joueur.
current_locationTable de position actuelle du joueur ; c'est un champ, pas une méthode.
get_eye_location()Localisation actuelle des yeux du joueur.
send_message(text)Envoie un message dans le chat. Prend en charge les codes couleur.

Vérifiez toujours que context.player n'est pas nil avant de l'utiliser dans des fonctions auxiliaires partagées.

Ici, entity_type est en minuscules

Sur les tables d'entité partagées MagmaCore, entity_type est le nom Bukkit en minuscules ("player", "zombie"). Seuls context.npc.entity_type et les tables d'entité des pouvoirs de boss EliteMobs utilisent la forme en majuscules. Comparez sans tenir compte de la casse si un script doit gérer les deux.

Champs EliteMobs ajoutés à chaque table d'entité partagée

Tant qu'EliteMobs tourne, il ajoute des champs supplémentaires à chaque table d'entité MagmaCore — context.player, les wrappers renvoyés par context.npc:get_nearby_players(...), et ceux que voient les scripts de props et d'objets FreeMinecraftModels :

ChampTypeNotes
is_elitebooléentrue si EliteMobs suit l'entité comme un élite
is_custom_bossbooléentrue s'il s'agit d'un Custom Boss (toujours false lorsque is_elite vaut false)
is_significant_bossbooléentrue pour un Custom Boss dont le multiplicateur de santé est supérieur à 1 — la vérification pratique « c'est un vrai boss, pas un renfort »
elitetable ou nilPrésent uniquement sur les élites. Voir ci-dessous

La sous-table elite :

Champ / MéthodeTypeNotes
elite.levelnombreNiveau de l'élite
elite.namechaîne ou nilNom d'affichage de l'élite
elite.healthnombreSanté actuelle de l'élite (lue en direct)
elite.max_healthnombreSanté maximale de l'élite (lue en direct)
elite.is_custom_bossbooléenMême valeur que le champ de premier niveau
elite.health_multipliernombreMultiplicateur de santé configuré
elite.damage_multipliernombreMultiplicateur de dégâts configuré
elite:remove()Fait disparaître l'élite
-- Warn the approaching player if a real boss is loose near this NPC
on_npc_proximity_enter = function(context)
if context.player == nil then return end
local here = context.npc:get_location()
local nearby = context.world:get_nearby_entities(here.x, here.y, here.z, 40)
for i = 1, #nearby do
if nearby[i].is_significant_boss then
context.player:send_message("&cA boss is nearby: " .. tostring(nearby[i].elite.name))
return
end
end
end

Ces champs n'apparaissent pas sur les wrappers d'entité des pouvoirs de boss EliteMobs, qui sont construits par un constructeur de table distinct côté boss — voir Boss et entités pour cet ensemble.


context.event

context.event vaut nil lorsque le hook n'a pas d'événement Bukkit. Lorsqu'il est présent, c'est la table d'événement partagée MagmaCore :

Champ / MéthodeNotes
is_cancelledIndique si l'événement sous-jacent est annulé (n'a de sens que pour les événements annulables).
cancel()Annule l'événement, lorsqu'il est annulable.
uncancel()Rétablit l'événement annulé, lorsqu'il est annulable.
playerL'acteur de l'événement (par exemple le joueur qui interagit), sous forme de wrapper de joueur, lorsqu'il est présent.

Pour le joueur qui interagit ou déclenche la proximité, préférez context.player (il est défini pour ces hooks).


État, planificateur et temps de recharge

context.state est une table Lua simple qui persiste pour cette instance de script de PNJ jusqu'à la suppression du PNJ.

context.scheduler est le planificateur partagé MagmaCore. Les noms MagmaCore et les noms EliteMobs run_after / run_every fonctionnent tous les deux — ce sont des alias du même comportement :

MéthodeArgumentsNotes
run_later(ticks, callback) / run_after(ticks, callback)number, functionS'exécute une fois après un délai. Retourne un ID de tâche.
run_repeating(delay, interval, callback)number, number, functionS'exécute de façon répétée après un délai initial. Retourne un ID de tâche.
run_every(interval, callback)number, functionS'exécute toutes les interval ticks (délai initial de 0). Retourne un ID de tâche.
cancel(task_id) / cancel_task(task_id)numberAnnule une tâche dont le script est propriétaire.

Les callbacks du planificateur reçoivent un context neuf. Ils ne reçoivent pas le context.player ni le context.event d'origine. Toutes les tâches détenues sont annulées automatiquement lorsque le PNJ est supprimé.

context.cooldowns est la table de temps de recharge partagée MagmaCore :

MéthodeArgumentsRetourneNotes
local_ready(key?)stringbooleanVrai lorsque le temps de recharge local a expiré.
local_remaining(key?)stringnumberTicks restants, ou 0 lorsque prêt.
check_local(key?, duration)string, numberbooleanSi prêt, démarre le temps de recharge et retourne vrai.
set_local(duration, key?)number, stringnilDéfinit ou réinitialise le temps de recharge.
global_ready()-booleanVrai lorsque le temps de recharge global partagé est prêt.
set_global(duration)numbernilDémarre le temps de recharge global.
Unified cooldown API

Les scripts de PNJ utilisent désormais l'ordre d'arguments partagé de MagmaCore (check_local(key?, duration)), le même que les pouvoirs de boss et les scripts FreeMinecraftModels. Les anciens builds expérimentaux de PNJ utilisaient check_local(duration, key?) — mettez à jour vos anciens scripts vers l'ordre partagé.


Exemple : saluer à l'approche

Ce script fait en sorte que le PNJ se tourne vers le joueur qui entre et joue l'animation de modèle personnalisé wave. L'entrée en proximité se déclenche déjà une seule fois par paire PNJ/joueur tant que le joueur reste dans le rayon ; le temps de recharge évite que des cycles rapides de sortie/entrée ne rejouent l'animation trop souvent.

return {
api_version = 1,
priority = 0,

on_npc_proximity_enter = function(context)
if context.player == nil then return end

if context.cooldowns:check_local("wave:" .. context.player.uuid, 60) then
context.npc:face_direction_or_location(context.player.current_location)
context.npc:play_model_animation("wave")
end
end
}

play_model_animation(name) ne fait rien, sans erreur, lorsque le PNJ n'a pas de modèle personnalisé ou que le modèle ne possède pas cette animation.


Recommandations de performance

  • Gardez les hooks on_game_tick réduits. Ils s'exécutent 20 fois par seconde pour chaque instance de script de PNJ qui les définit. (Les scripts qui ne déclarent pas on_game_tick ne sont jamais tickés.)
  • Préférez on_npc_proximity_enter et on_npc_proximity_leave pour les comportements de proximité plutôt que d'interroger les joueurs proches à chaque tick.
  • Utilisez context.cooldowns:check_local(...) pour limiter les animations, les sons et les rafales de particules.
  • Utilisez context.scheduler:run_repeating(...) avec un intervalle raisonnable lorsqu'un comportement n'a pas besoin de s'exécuter à chaque tick.
  • Évitez les recherches volumineuses en Lua. context.npc:get_nearby_players(radius) convient pour de petites vérifications locales, mais les balayages larges doivent rester dans le runtime du plugin.

Pages liées