Aller au contenu principal

API FreeMinecraftModels et guide du développeur

La référence Java générée permet de rechercher les classes, méthodes et signatures. Elle indique la version source et le commit de chaque module.

FreeMinecraftModels est à la fois un plugin autonome et une surface d'API pour d'autres plugins.

Pour la configuration commune des dépendances et de l'environnement d'exécution, consultez l'index des API Java. L'exemple de dépendance utilise la version publiée 2.12.9 ; le code source actuel est en 2.12.9. Faites correspondre votre dépendance au plugin installé.

Dépôt Maven​

<repository>
<id>magmaguy-repo-releases</id>
<name>MagmaGuy's Repository</name>
<url>https://repo.magmaguy.com/releases</url>
</repository>
<repository>
<id>magmaguy-repo-snapshots</id>
<name>MagmaGuy's Snapshot Repository</name>
<url>https://repo.magmaguy.com/snapshots</url>
</repository>

Dépendance​

<dependency>
<groupId>com.magmaguy</groupId>
<artifactId>FreeMinecraftModels</artifactId>
<version>2.12.9</version>
<scope>provided</scope>
</dependency>

Utilisez-le en compileOnly/provided. N'incorporez pas le plugin à votre propre jar.

Points d'entrée principaux​

  • ModeledEntityManager.modelExists(String)
  • ModeledEntityManager.reload()
  • ModeledEntityManager.getAllEntities()
  • ModeledEntityManager.getDynamicEntities()
  • ModeledEntityManager.propEntities()
  • DisguiseAPI — déguise / dé-déguise les joueurs en modèles chargés
  • LocationAPI — enregistre des détecteurs de donjons et fournisseurs de protection (alimente les prédicats Lua em.location.*)
  • ScriptedItemAPI — marque des ItemStacks tiers avec l'identité et les données d'affichage d'un objet défini dans FMM
  • ItemConstructionSnapshot — capture sur le thread du serveur les données d'objets et de modèles d'affichage afin de construire des objets sur un autre thread

ModeledEntityManager.getAllEntities(), getDynamicEntities() et propEntities() retournent des copies à un instant donné. Modifier l'ensemble ou la map retournés ne modifie pas les registres vivants de FMM. Utilisez modelExists(String) pour un test d'existence plutôt que de conserver et recopier sans cesse un registre complet.

Principaux types d'exécution​

  • ModeledEntity
  • StaticEntity
  • DynamicEntity
  • PropEntity

Création d'entités​

StaticEntity preview = StaticEntity.create("example_model", location);
DynamicEntity mobModel = DynamicEntity.create("example_model", livingEntity);
DynamicEntity mount = DynamicEntity.createWithInvisibility("example_model", livingEntity);
PropEntity prop = PropEntity.spawnPropEntity("example_model", location);

Tous les chemins de création retournent null si l'ID de modèle demandé n'est pas chargé.

PropEntity.spawnPropEntity retourne également null lorsqu'un prop de ce même modèle est déjà chargé sur le bloc cible — le doublon est refusé plutôt qu'empilé, et un avertissement [FMM Props] Prevented duplicate prop spawn ... est journalisé. Vérifiez toujours que sa valeur de retour n'est pas nulle ; un retour non nul est la seule confirmation que le prop a réellement été créé.

createWithInvisibility est une variante qui applique une potion d'invisibilité au lieu de masquer l'entité aux clients. Cela maintient l'entité suivie côté client, ce qui est nécessaire pour le contrôle de véhicule (utilisé en interne par /fmm mount) et permet aux os d'affichage du modèle de chevaucher l'entité sur les clients Java ; voir displayBonesRideTheirEntity.

Méthodes runtime utiles​

  • ModeledEntity#setDisplayName(String) -- définit la plaque de nom gérée par le modèle
  • ModeledEntity#setDisplayNameVisible(boolean) -- contrôle la visibilité de l'étiquette
  • ModeledEntity#setLeftClickCallback(...)
  • ModeledEntity#setRightClickCallback(...)
  • ModeledEntity#setHitboxContactCallback(...)
  • ModeledEntity#setModeledEntityHitByProjectileCallback(...)
  • ModeledEntity#playAnimation(String, boolean blend, boolean loop) -- retourne false lorsque le nom ne correspond ni à un état intégré ni à une animation du modèle, ou après une demande de death. Pour une animation personnalisée, blend = true la joue sur la couche d'action par-dessus idle ou walk, et false lui laisse prendre le contrôle de toute la pose ; loop ne s'applique qu'aux animations personnalisées. Voir Mélange des animations
  • ModeledEntity#stopCurrentAnimations() -- met fin aux animations personnalisées et aux attaques et fait passer le modèle d'un coup à idle ; ne fait rien une fois la mort commencée
  • ModeledEntity#hasAnimation(String)
  • ModeledEntity#damage(double) / damage(Entity damager, double) / damage(Entity damager) / damage(Projectile) -- acheminé via le DamageableComponent de l'entité
  • ModeledEntity#attack(LivingEntity) / attack(LivingEntity, double damage)
  • ModeledEntity#teleport(Location, boolean teleportUnderlyingEntity)
  • ModeledEntity#setUnderlyingEntity(Entity) / ModeledEntity.getModeledEntity(Entity) -- recherche inverse statique depuis une entité Bukkit vers le modèle qui lui est attaché, ou null
  • ModeledEntity#showUnderlyingEntity(Player) / hideUnderlyingEntity(Player) -- visibilité par joueur de l'entité vanilla sous-jacente
  • ModeledEntity#getEntityID() -- retourne la chaîne d'ID de modèle
  • ModeledEntity#getModelInstanceId() -- UUID par instance, stable pendant toute la vie du modèle
  • ModeledEntity#isRemoved() / isDying() -- drapeaux de cycle de vie
  • ModeledEntity#getLocation() -- retourne la Location actuelle
  • ModeledEntity#getSpawnLocation() -- retourne la Location où le modèle a été créé
  • ModeledEntity#getWorld() -- retourne le World
  • ModeledEntity#getSkeleton() / getSkeletonBlueprint() / getMountPointManager() -- accès à la structure d'exécution
  • ModeledEntity#getInteractionComponent() / getHitboxComponent() / getDamageableComponent() / getAnimationComponent() -- les objets composants derrière les méthodes de commodité ci-dessus
  • ModeledEntity.getLoadedModeledEntities() -- ensemble statique et vivant de toutes les entités modélisées chargées
  • ModeledEntity#getViewers() -- retourne le HashSet<UUID> des joueurs qui peuvent voir l'entité
  • ModeledEntity#getNametagBones() -- retourne List<Bone> des os de nametag (utile pour placer du texte supplémentaire)
  • ModeledEntity#getScaleModifier() / setScaleModifier(double)
  • ModeledEntity#removeWithDeathAnimation() -- supprime avec l'animation de mort (s'il en existe une)
  • ModeledEntity#removeWithMinimizedAnimation() -- supprime avec une animation de réduction d'échelle
  • ModeledEntity#remove() -- supprime immédiatement l'entité et tous les os
  • ModeledEntity#setTintColor(Color) / getTintColor() -- applique une teinte persistante via le canal de teinte de l'armure de cuir. Les flashs de dégâts remplacent brièvement la teinte puis s'estompent vers elle. Passez null pour effacer.
  • ModeledEntity#setViewDistanceOverride(int) / getEffectiveViewDistance() -- surcharge DefaultConfig.maxModelViewDistance pour une seule entité. Passez -1 pour revenir à la valeur par défaut du plugin.
  • DynamicEntity#setSyncMovement(boolean)
  • DynamicEntity#isDamagesOnContact() / setDamagesOnContact(boolean) -- contrôle si l'entité inflige des dégâts aux joueurs via le contact de la hitbox
  • DynamicEntity.isDynamicEntity(Entity) / DynamicEntity.getDynamicEntity(Entity) -- recherches statiques depuis une entité Bukkit
  • DynamicEntity#getBodyLocation() -- la position de l'entité orientée selon le yaw du modèle. Avec weightedTurning activé, ce yaw suit celui du corps du mob avec une inertie de rotation : il peut donc être en retard sur le mob pendant que le modèle tourne. getLocation() renvoie la même position tant que la synchronisation de mouvement est activée
  • Bone#getBoneLocation()

Lumière, inclinaison, centrage et clics​

  • ModeledEntity#setBrightnessOverride(int blockLight, int skyLight) -- éclaire tous les os d'affichage à un niveau fixe, chacun de 0 à 15, au lieu de la lumière présente à l'endroit de l'os. 15, 15 correspond à une luminosité maximale. Les spectateurs reçoivent le changement à la prochaine mise à jour des os. Os d'affichage Java uniquement : les os Bedrock et d'armor stand conservent la lumière du monde
  • ModeledEntity#clearBrightnessOverride() / hasBrightnessOverride() / getBrightnessOverrideBlockLight() / getBrightnessOverrideSkyLight() -- suppriment ou lisent la lumière fixe. Les getters renvoient -1 sans remplacement
  • DynamicEntity#setTravelOrientation(TravelOrientation) / getTravelOrientation() -- incline le modèle selon son déplacement. TravelOrientation est un record composé d'un Style (UPRIGHT, TOP_FIRST, FRONT_FIRST), de startSpeed, fullSpeed, maxTilt et turnSpeed ; TravelOrientation.UPRIGHT est la valeur par défaut et null la rétablit. Voir Orientation de déplacement
  • ModeledEntity#setBodyTilt(Quaterniondc) / getBodyTilt() -- incline le modèle entier par une rotation en espace monde autour du milieu de ses cubes, après son orientation et ses animations. null le remet droit. Une orientation de déplacement autre que UPRIGHT définit cette valeur à chaque tick ; sur un modèle droit, une inclinaison que vous définissez est conservée
  • ModeledEntity#setCenteredOnBody(boolean) / isCenteredOnBody() / getBodyOffset() -- dessine le modèle avec le milieu de ses cubes sur le milieu de son entité. Voir Centrage sur le corps
  • DynamicEntity#setClickSurface(boolean) / isClickSurface() -- lorsque la valeur est true, les clients reçoivent une entité Interaction invisible par-dessus le modèle : les joueurs cliquent ainsi sur le modèle qu'ils voient plutôt que sur la boîte du corps. Elle prend la taille de l'os hitbox du modèle, ou celle des limites du modèle s'il n'en a pas, et s'applique dans un délai de deux ticks. Désactivé par défaut, car une boîte couvrant un modèle long ou pivoté capte aussi les clics dans ses coins vides
  • DynamicEntity#isSteerableMount() / hasSteeringRider() / seatSteeringRider(Player) -- un modèle sur un cheval sellé est une monture dirigeable. seatSteeringRider installe le joueur sur le cheval lui-même comme cavalier qui dirige lorsque personne ne le monte, et renvoie s'il le monte maintenant. Un clic droit sur un tel modèle le fait par défaut
  • DynamicEntity#liftSaddleTo(Bone) -- met le mob à l'échelle pour que sa selle se pose sur l'os de siège donné dans la pose de repos du modèle, tandis que le modèle conserve sa taille. /fmm mount l'utilise avec le premier os m_ du modèle. Cette méthode ne fait rien lorsque le serveur ne peut pas indiquer la hauteur de la selle

Plaques de nom et cibles du regard​

ModeledEntity expose désormais setDisplayNameLines(List<String>), setDisplayNameScale(float), setDisplayNameLineGap(double), setDisplayNameVisible(boolean) et getNameplateLocation(). Un os de nom est facultatif. À défaut, la plaque de nom se place un quart de bloc (quatre pixels) au-dessus du cube le plus haut du modèle au repos, à l'échelle de l'entité.

Noms multilignes :

  • setDisplayName("Top line\nBottom line") et setDisplayNameLines(List<String>) prennent les lignes dans l'ordre de lecture, de haut en bas. La ligne du bas reste à l'ancre et les lignes supplémentaires s'étendent vers le haut. Les retours à la ligne explicites définissent les lignes ; le texte n'est pas replié automatiquement.
  • Un nom vide masque la plaque de nom. setDisplayNameVisible(false) masque toutes les lignes et reste en vigueur lorsque le texte change.
  • setDisplayNameScale(float) met le texte à l'échelle indépendamment de la taille du modèle. setDisplayNameLineGap(double) définit l'espace libre entre les lignes, en blocs à l'échelle 1, 0.1 par défaut. Une ligne mesure 0.225 bloc de haut à l'échelle 1 : les lignes successives sont donc espacées de 0.325 bloc par défaut.
  • Lorsqu'un modèle a plusieurs os tag_, le premier par ordre alphabétique ancre la plaque de nom, y compris son mouvement et son animation.
  • Les joueurs Java voient des text displays, qui mettent à l'échelle à la fois les glyphes et l'espacement. Les joueurs Bedrock voient des étiquettes d'armor stand marqueurs distinctes, qui ne peuvent pas être mises à l'échelle : leur espacement reste donc à l'échelle 1. Choisir les étiquettes Bedrock nécessite Floodgate ou Geyser sur le serveur backend pour identifier les joueurs Bedrock ; une installation de ces plugins sur le proxy seulement, sans transmission d'identité, ne peut pas les choisir de manière fiable.
  • La portée de vue, l'invisibilité, la suppression et le rechargement suivent le modèle. Un texte et des positions inchangés n'envoient aucun paquet.

Pour savoir comment EliteMobs dispose le nom et le rôle d'un PNJ qui utilise un modèle FreeMinecraftModels, voir Créer des PNJ.

DynamicEntity.setLookTarget(Location) fournit au squelette une copie de la cible visuelle du regard sans tourner ni déplacer l'entité sous-jacente. Passez null pour la libérer.

Spécificités de PropEntity​

  • PropEntity.isPropEntity(ArmorStand) / PropEntity.getPropEntityID(ArmorStand) -- identifie un prop depuis son porte-armure de support
  • PropEntity.hasLoadedPropOnSameBlock(String entityID, Location) -- la vérification de doublon que spawnPropEntity exécute en interne ; appelez-la d'abord si vous préférez brancher plutôt que tester un null
  • PropEntity.respawnPropEntityFromArmorStand(String entityID, ArmorStand) -- reconstruit un prop autour d'un porte-armure ayant survécu à un rechargement de chunk
  • PropEntity.getPropEntities() -- copie instantanée des props chargés, indexée par UUID de porte-armure ; la modifier ne change pas le registre actif
  • PropEntity#setPersistent(boolean) -- bascule la persistance du porte-armure de support
  • PropEntity#setCustomDataString(NamespacedKey, String) / getCustomDataString(NamespacedKey) -- lit/écrit vos propres valeurs PDC sur le prop. C'est le même magasin qu'utilisent les aides Lua set_persistent_data / get_persistent_data, sous l'espace de noms fmm_lua_<key>
  • PropEntity#remove() / remove(boolean showRealBlocks) -- supprime le modèle mais laisse l'entrée persistante
  • PropEntity#permanentlyRemove() -- supprime le modèle et son entrée persistante
  • PropEntity#setVoxelizeConfig(boolean voxelize, boolean solidify) / applySolidify() -- les équivalents à l'exécution des champs YML voxelize: / solidify:
  • PropEntity#showFakePropBlocksToPlayer(Player) / showRealBlocksToPlayer(Player) et les variantes ...ToAllPlayers() -- contrôlent les blocs barrière envoyés uniquement par paquets qui donnent une collision côté client à un prop solidifié

Intégrations des armes magiques​

Utilisez MagicWeaponAPI.isOperational() avant de dépendre du service de magie, isWeapon(String) pour vérifier un identifiant défini et applyWeaponData(ItemStack, String) pour appliquer son identité d'arme et son affichage sans remplacer le nom, le lore, le niveau ni les enchantements de l'objet hôte. Le seul enregistrement du service ne prouve pas qu'il est opérationnel.

Un MagicAttackResolver facultatif peut refuser des lancements, filtrer ou classer les cibles, capturer les données de combat au lancement et déterminer les dégâts. Il doit utiliser le MagicDamageApplication fourni, utilisable une seule fois, et ne doit pas infliger directement les dégâts à la cible. FMM gère les entrées, le trajet du projectile, les collisions et l'application des dégâts.

MagicProjectileTravelEvent est une requête de collision synchrone et annulable qui balaie un segment, avec une vérification au moment de l'impact. Son annulation absorbe le projectile sans dégâts ni explosion. Un segment peut être vérifié plusieurs fois ; les écouteurs ne doivent donc pas traiter cet événement comme une notification de dégâts.

Surface d'événements​

Les clics droits déclenchent d'abord ModeledEntityInteractEvent sur la véritable entité sous-jacente. Il hérite des gestionnaires de PlayerInteractEntityEvent, ce qui permet aux écouteurs ordinaires de protection des entités de l'annuler. L'annulation empêche l'événement et le callback de clic droit du modèle. Cette vérification est distincte de l'autorisation de placer ou de casser des blocs.

Comment les vérifications du clic droit s'articulent :

  • Les interactions avec les blocs et avec les modèles sont décidées séparément. Annuler un PlayerInteractEvent maintient l'action sur le bloc ou l'objet annulée, mais le clic droit peut encore sélectionner un modèle grâce aux vérifications de portée et d'occlusion par les blocs de FMM. L'annulation du clic gauche fonctionne comme avant.
  • ModeledEntityInteractEvent utilise la main principale. FMM ne déclenche ModeledEntityRightClickEvent qu'une fois cette distribution terminée, et un ModeledEntityRightClickEvent annulé arrête le callback du modèle, le comportement du prop et le montage par défaut.
  • L'événement passerelle est une vérification d'autorisation, pas un clic supplémentaire. Les routeurs d'entrée, d'arme magique et de props de FMM l'ignorent. Un plugin qui effectue des actions sur les événements natifs d'interaction avec une entité peut ignorer ModeledEntityInteractEvent pour éviter d'agir deux fois ; les écouteurs de protection doivent continuer à le traiter.
  • Un modèle sans entité Bukkit sous-jacente ne peut pas déclencher d'événement d'entité Bukkit. Il déclenche tout de même le ModeledEntityRightClickEvent annulable. Les props persistants et les PNJ EliteMobs ont des entités sous-jacentes et subissent les deux vérifications.

Événements d'interaction génériques :

  • ModeledEntityLeftClickEvent
  • ModeledEntityRightClickEvent
  • ModeledEntityHitboxContactEvent
  • ModeledEntityHitByProjectileEvent

Les quatre sont des événements Bukkit annulables ; les chemins de détection intégrés de FMM les émettent sur le thread principal du serveur. Annuler l'un d'eux empêche le callback ou le comportement par défaut correspondant de FMM. Les scans de contact de hitbox s'exécutent tous les deux ticks serveur ; les événements de clic conservent une courte fenêtre de déduplication par joueur afin que le chemin d'interaction par paquets et le raytrace OBB ne puissent pas déclencher deux fois la même action.

Événements de cycle de vie :

  • FmmReloadedEvent -- programmé sur le thread principal 40 ticks après une initialisation complète réussie, au démarrage comme après une recharge complète réussie. La recharge limitée au contenu importé l'émet aussi en cas de réussite. Une demande rejetée pendant la préparation conserve le système actif et n'émet pas d'événement de recharge terminée.

La surface d'interaction publique est volontairement neutre vis-à-vis du type de modèle. Les anciennes variantes StaticEntity*Event, DynamicEntity*Event et PropEntity*Event, ainsi que ResourcePackGenerationEvent, ne font plus partie de l'API actuelle. Utilisez plutôt les quatre événements génériques d'entité modélisée ci-dessus et FmmReloadedEvent.

Les plugins consommateurs qui détiennent des références DynamicEntity ou PropEntity à long terme (EliteMobs, BetterStructures, etc.) doivent gérer cet événement en recréant leurs attachements de modèle sur les entités sous-jacentes survivantes. Sans cela, ces entités deviennent invisibles après un reload car FMM a démonté les display entities durant onDisable tandis que la référence du consommateur est maintenant obsolète.

@EventHandler
public void onFmmReloaded(FmmReloadedEvent event) {
for (MyTrackedEntity tracked : myEntities) {
if (tracked.bukkitEntity != null && tracked.bukkitEntity.isValid()) {
tracked.fmmModel = DynamicEntity.create("my_model", tracked.bukkitEntity);
}
}
}

ModeledEntityHitByProjectileEvent et détection de projectiles OBB​

FMM utilise la détection de coup OBB (oriented bounding box) pour les projectiles contre les entités modélisées. Lorsqu'un projectile intersecte la hitbox OBB d'une entité modélisée, FMM déclenche un ModeledEntityHitByProjectileEvent. Il s'agit d'un événement Bukkit standard annulable.

Détails clés :

  • La détection vérifie le segment entre les positions suivies pour détecter la traversée d'un modèle fin ; sans position précédente, elle utilise un test de chevauchement
  • Les candidats sur ce segment sont résolus du plus proche au plus lointain, indépendamment de l'ordre d'itération du registre des modèles
  • Un projectile non perforant est acheminé vers une seule cible modélisée. Une flèche avec Perforation peut toucher pierce level + 1 cibles modélisées distinctes, dans l'ordre du trajet, sans double déclenchement lorsque l'OBB et la hitbox vanilla sous-jacente l'observent tous deux
  • Le gestionnaire par défaut des entités dynamiques transmet l'impact à l'entité vivante sous-jacente sous forme de véritable événement de dégâts de projectile, en utilisant la vélocité du projectile au moment de l'impact. Cela préserve la cause du projectile, la gestion par les plugins de combat et l'attribution au tireur
  • Annuler l'événement empêche le callback et le comportement de dégâts par défaut de FMM, mais la collision consomme tout de même le budget de cibles modélisées du projectile. Un coup non perforant annulé consomme donc quand même le projectile
@EventHandler
public void onProjectileHitModel(ModeledEntityHitByProjectileEvent event) {
ModeledEntity target = event.getEntity();
Projectile projectile = event.getProjectile();
// Cancel to prevent damage
event.setCancelled(true);
}

Utilitaires d'objets et de modèles​

ModelItemFactory​

Classe factory pour créer programmatiquement des ItemStacks liés aux modèles.

// Create a prop placement item (uses "model_id" PDC key)
ItemStack placementItem = ModelItemFactory.createModelItem("lamp_post", Material.STICK);

// Create a custom item from config (uses "fmm_item_id" PDC key)
PropScriptConfigFields config = ItemScriptManager.getItemDefinitions().get("magic_sword");
ItemStack customItem = ModelItemFactory.createCustomItem("magic_sword", config);
  • createModelItem(String modelId, Material material) -- crée un objet de placement pour les props. Sur 1.21.4+, applique automatiquement le rendu de modèle d'affichage si un JSON d'affichage existe.
  • createCustomItem(String itemId, PropScriptConfigFields config) -- crée un objet personnalisé avec nom, lore, enchantements et modèle d'affichage depuis la config unifiée.
  • formatModelName(String modelId) -- utilitaire qui convertit un ID de modèle comme 01_em_flame_sword en Flame Sword.

DisplayModelRegistry​

Registre simple qui suit quels modèles ont un JSON d'affichage disponible.

// Check if a model has a display model JSON registered
boolean has3D = DisplayModelRegistry.hasDisplayModel("magic_sword");
  • register(String modelId) -- enregistre un ID de modèle (appelé en interne pendant le reload)
  • hasDisplayModel(String modelId) -- vérifie que le modèle d'affichage a été enregistré ; un .json mal formé n'est pas enregistré du seul fait de sa présence
  • getRegisteredModels() -- retourne un Set<String> immuable de tous les IDs de modèle qui ont des modèles d'affichage enregistrés
  • shutdown() -- efface tous les enregistrements

ItemScriptManager​

Malgré son nom de classe conservé, il s'agit désormais d'un catalogue d'objets définis et d'armes magiques, et non d'un moteur de scripts d'objets équipés. getItemDefinitions() expose les définitions d'objets enregistrées ; getWeaponCatalog() expose les définitions d'armes validées. Les anciennes API de scripts actifs, de mise à jour de l'équipement et de suppression des scripts d'un joueur ont été retirées.

ScriptedItemAPI​

  • isValidItemId(String) : vérifie l'identifiant exact de l'objet défini (nom du fichier de configuration sans .yml).
  • getItemConfig(String) : retourne la définition, ou null.
  • applyScriptedItemData(ItemStack, String) : inscrit fmm_item_id et, lorsqu'il existe, le modèle d'affichage enregistré. Retourne false si l'identifiant d'objet est invalide ou si les métadonnées de l'objet sont absentes.

Cette méthode ne modifie pas le nom, le lore ni les enchantements de l'objet hôte, et ne lui associe aucun comportement Lua. Appliquez les enchantements séparément via MagmaCore. Les états de tension des arcs et arbalètes appartiennent à la définition du modèle d'affichage de base ; n'ajoutez pas _idle à l'identifiant de l'objet défini.

ModelItemAPI​

Utilisez ModelItemAPI.applyDisplayModel(ItemStack, String) lorsque l'hôte n'a besoin que d'un modèle d'affichage FMM. Cette méthode vérifie le registre des modèles d'affichage sans inscrire d'identité d'objet défini. Cela se distingue du choix d'une définition d'arme ou d'objet FMM.

ItemConstructionSnapshot​

Destiné aux plugins qui construisent des objets hors du thread du serveur. Appelez ItemConstructionSnapshot.capture() sur le thread du serveur ; cette méthode copie les définitions d'objets, les modèles d'affichage et les types d'armes magiques actuels. Un thread de travail peut ensuite appeler item(id) ou weapon(id) et appliquer le ItemData renvoyé à un ItemStack avec apply(ItemStack), qui inscrit fmm_item_id pour un objet défini ainsi que le modèle d'affichage lorsqu'il en existe un. weapon(id) renvoie null sauf si l'ID désigne une arme magique et que le service magique était opérationnel au moment de la capture. Avant de publier les objets construits, appelez requireCurrent() sur le thread du serveur : cette méthode lève ConcurrentModificationException si les définitions de FMM ont changé depuis la capture, par exemple après un rechargement.

DisguiseAPI​

Point d'entrée public pour la fonctionnalité de déguisement des joueurs. Les plugins tiers devraient appeler cette classe plutôt que le DisguiseManager interne afin que les refactorisations internes restent sûres.

import com.magmaguy.freeminecraftmodels.api.DisguiseAPI;

// Disguise a player as a loaded model. Replaces any existing disguise cleanly.
boolean ok = DisguiseAPI.disguise(player, "dragon");

// Undisguise (returns true if a disguise was removed).
DisguiseAPI.undisguise(player);

// Query state.
boolean disguised = DisguiseAPI.isDisguised(player);
String modelID = DisguiseAPI.getDisguiseModelID(player); // null if not disguised

// Snapshot of all currently disguised players.
Collection<Player> all = DisguiseAPI.getDisguisedPlayers();
  • disguise(Player, String modelID) -- retire l'ancien déguisement avant d'essayer son remplacement ; un ID inconnu renvoie false après ce retrait. Passez un joueur connecté et un ID chargé exact ; l'API ne normalise pas les ID et ne vérifie pas les permissions de commande
  • undisguise(Player) -- retourne true si un déguisement a été retiré
  • isDisguised(Player) -- vérification booléenne rapide
  • getDisguiseModelID(Player) -- retourne l'ID de modèle actif ou null
  • getDisguisedPlayers() -- snapshot non modifiable des joueurs déguisés

FMM combine la visibilité par défaut du joueur, un effet de potion d'invisibilité et des substitutions d'affichage de l'équipement. Il rétablit un effet supprimé uniquement si ce déguisement l'avait ajouté ; un effet préexistant n'est ni géré ni rétabli. La commande valide le modèle avant remplacement, contrairement à l'API. Consultez les limites de cycle de vie et d'affichage dans Déguisements des joueurs.

LocationAPI​

API publique pour les plugins afin de contribuer à la détection de donjons et aux vérifications de protection de région. Les prédicats enregistrés alimentent les vérifications Lua em.location.is_in_dungeon et em.location.is_protected de FMM (utilisés par les scripts préfaits comme pickupable.lua et storage_double.lua).

Les plugins passent un Predicate<Location> simple afin qu'aucun type FMM shadé ne traverse les classloaders de plugin.

import com.magmaguy.freeminecraftmodels.api.LocationAPI;

// On your plugin's enable, after WorldGuard/EliteMobs/etc. are available.
LocationAPI.registerDungeonLocator("EliteMobs",
location -> EliteMobs.isInsideDungeon(location));

LocationAPI.registerProtectionProvider("WorldGuard",
location -> WorldGuardBridge.isProtected(location));
  • registerDungeonLocator(String providerName, Predicate<Location> predicate) — tout prédicat enregistré retournant true marque l'emplacement comme « in dungeon »
  • registerProtectionProvider(String providerName, Predicate<Location> predicate) — tout prédicat enregistré retournant true marque l'emplacement comme protégé
  • unregisterDungeonLocator(String providerName) et unregisterProtectionProvider(String providerName) retirent le prédicat enregistré sous ce nom. Appelez-les depuis le onDisable de votre plugin pour que FMM cesse d'appeler l'instance désactivée

Un nouvel enregistrement sous le même nom de fournisseur remplace le prédicat précédent : un plugin qui se réenregistre à chaque activation n'empile donc pas de copies. Un nom de fournisseur null enregistre un prédicat qui ne peut être ni remplacé ni désenregistré.

Les opérateurs peuvent vérifier l'enregistrement avec /fmm location, qui rapporte le nombre de fournisseurs en direct et teste les deux prédicats sur leur position actuelle.

Fournisseurs de protection et placement de props​

Ces mêmes fournisseurs de protection pilotent également la vérification de placement de props preventPropPlacementInProtectedRegions, mais cette vérification appelle canBuild(player, location) plutôt que le isProtected(location) basé uniquement sur l'emplacement. La distinction a son importance :

FournisseurComportement de canBuild
Adaptateur WorldGuard intégréRespecte le contournement propre à WorldGuard, puis délègue à testBuild de WorldGuard — les membres et propriétaires de région peuvent donc construire normalement
Adaptateur GriefPrevention intégréL'absence de claim à cet emplacement signifie autorisé ; à l'intérieur d'un claim, il délègue à la permission de construction propre à GriefPrevention pour ce joueur
Fournisseur enregistré via LocationAPI.registerProtectionProviderRetombe sur !isProtected(location) — uniquement basé sur l'emplacement, bloque tout le monde dans un emplacement que votre prédicat déclare protégé

Ce repli est intentionnel et prudent : un Predicate<Location> ne peut pas exprimer une permission propre à un joueur, et FMM n'en inventera pas une. Si vous voulez un comportement réellement conscient du joueur pour votre propre système de régions, implémentez directement le RegionProtectionProvider de MagmaCore en surchargeant canBuild, puis enregistrez-le avec LocationQueryRegistry.registerProtectionProvider au lieu de passer par l'enveloppe de commodité par prédicat.

Deux autres comportements à connaître :

  • Les échecs d'adaptateur échouent en mode fermé. Si un fournisseur lève une erreur pendant une requête de construction, le placement est refusé et un avertissement nommant le fournisseur est journalisé. Il ne retombe jamais silencieusement sur « autorisé ».
  • Tous les fournisseurs enregistrés doivent être d'accord. Le premier fournisseur qui dit non l'emporte ; les fournisseurs de propriété enregistrés via LocationOwnership restent basés uniquement sur l'emplacement et bloquent tout le monde.

Le placement par objet vérifie d'abord la case adjacente. Les props voxelisés vérifient aussi leur emprise après rotation, avec des dimensions de hitbox arrondies et un minimum d'un bloc ; le volume des barrières envoyées par paquets utilise un autre calcul. Les appels directs à PropEntity.spawnPropEntity n'exécutent pas les vérifications de placement de l'objet du joueur. Consultez Configuration avant d'autoriser le placement depuis un autre plugin.

PropScriptConfigFields​

Analyse le YAML placé à côté du modèle pour les props et les objets définis. Les listes scripts: des props associent des scripts Lua indépendants. Une définition material: enregistre à la place un objet tenu et ne peut pas contenir de liste scripts: non vide.

Consultez la configuration des modèles pour des exemples distincts de props et d'objets tenus, et les armes magiques pour le schéma des armes.

Scripts Lua​

FreeMinecraftModels prend en charge les scripts Lua de props via le moteur de scripts MagmaCore. Les fichiers de scripts sont placés dans plugins/FreeMinecraftModels/scripts/ et associés aux modèles par une configuration YML voisine du fichier de modèle. Le fichier de script sur le disque doit se terminer par .lua ; les entrées de configuration peuvent inclure l'extension ou l'omettre.

Les props associent chaque script de scripts: comme une instance indépendante. Les effets d'objets tenus utilisent le moteur distinct des fournisseurs d'enchantements et ne peuvent pas utiliser une liste scripts: d'objet non vide.

Hooks des scripts de props​

HookDéclencheur
on_spawnLe prop apparaît dans le monde
on_game_tickÀ chaque tick tant que le prop est vivant
on_zone_enterUn joueur entre dans une zone surveillée créée par le script
on_zone_leaveUn joueur quitte une zone surveillée créée par le script
on_destroyLe prop est retiré définitivement (pas lors d'un déchargement de chunk ni d'un rechargement)
on_left_clickLe joueur clique gauche sur le prop
on_right_clickLe joueur clique droit sur le prop
on_projectile_hitUn projectile touche le prop ; context.projectile est le projectile et context.event.player le tireur

Hooks des scripts d'objets​

Les hooks Lua d'objets équipés et leur contexte d'exécution context.item ont été retirés. Utilisez les définitions d'enchantements pour les effets d'objets. Les hooks de props restent pris en charge.

Table de contexte des scripts de props​

Les scripts de prop reçoivent une table context. Voici un résumé des APIs clés -- voir API Prop Lua pour tous les détails.

context.prop :

  • model_id -- le nom de modèle blueprint
  • current_location -- la position actuelle du prop
  • play_animation(name, blend, loop) -- demande l'animation nommée (blend et loop valent true par défaut, ce qui superpose une animation en boucle à l'idle du prop) ; la demande s'applique au prochain tick du modèle
  • stop_animation() -- met fin aux animations personnalisées et revient à idle si disponible ; n'interrompt pas l'animation de mort terminale
  • hurt_visual() -- joue le visuel de blessure (flash rouge) sur le prop
  • pickup() -- met en file d'attente la suppression du prop et le drop de son objet de placement
  • mount(player) -- met en file d'attente une tentative de montage ; un retour true signifie que le joueur et le gestionnaire de montures étaient valides, pas qu'un siège a finalement été attribué
  • dismount(player) -- met en file d'attente une vérification de démontage ; un retour true signifie que le joueur et le gestionnaire de montures étaient valides
  • get_passengers() -- retourne une liste des joueurs chevauchant actuellement le prop
  • spawn_elitemobs_boss(filename, x, y, z) -- fait apparaître un boss EliteMobs à des coordonnées absolues dans le monde actuel du prop

context.event :

  • Disponible dans les hooks de props on_left_click, on_right_click, on_zone_enter, on_zone_leave et on_projectile_hit
  • cancel(), uncancel(), is_cancelled lorsque le hook sous-jacent est annulable
  • player -- le joueur qui a déclenché l'événement ; dans on_projectile_hit, l'entité vivante qui a tiré le projectile

context.world :

  • spawn_entity(entity_type, x, y, z) -- fait apparaître une entité vanilla, ou retourne nil si le type d'entité est invalide
  • set_block_at(x, y, z, material) -- met en file d'attente un changement de bloc si le matériau est valide ; les chunks non chargés sont ignorés
  • Plus particules, sons, requêtes de blocs, foudre et recherches d'entités proches

context.cooldowns :

  • check_local(key, ticks) -- vérifie et démarre un temps de recharge propre au script
  • global_ready() / set_global(ticks) -- temps de recharge partagé pour le prop

Objets joueur (depuis context.player ou context.event.player) :

  • get_held_item() -- retourne l'objet que tient le joueur ; type est le nom de matériau Bukkit en majuscules
  • is_holding_elitemobs_item(filename) -- vérifie l'étiquette de nom de fichier EliteMobs de l'objet en main principale, par exemple shroomwhispers_shears.yml. Les noms affichés et traductions n'affectent pas cette vérification. Renvoie false si la main est vide, si l'objet est différent ou si l'intégration EliteMobs est indisponible.
  • consume_held_item() -- programme le retrait d'un objet de la main principale telle qu'elle sera au moment de l'exécution ; ne renvoie aucun résultat de paiement
  • has_item(material) -- vérifie si le joueur a un objet
  • send_message(text) -- envoie un message de chat au joueur
  • game_mode -- le mode de jeu actuel du joueur

Utilisez l'identité par nom de fichier pour les objets de quête EliteMobs :

if not player:is_holding_elitemobs_item("shroomwhispers_shears.yml") then
return
end

L'intégration EliteMobs de FMM fournit cette méthode. Renommer un objet ordinaire ne lui donne pas l'étiquette de nom de fichier requise.

Exemple de script de prop​

return {
api_version = 1,

on_spawn = function(context)
context.prop:play_animation("idle", true, true)
end,

on_right_click = function(context)
if context.cooldowns:check_local("activate", 40) then
context.prop:play_animation("activate", false, false)
end
end
}

Exemple de script d'objet​

L'ancien exemple d'objet équipé n'est plus pris en charge. Consultez les armes magiques et les définitions d'enchantements pour créer des objets avec le système actuel.

Compiler depuis les sources​

Les sources sont sur GitHub. FreeMinecraftModels est un projet Maven pour Java 21 :

mvn -DskipTests package
  • Le jar du plugin est écrit dans target/FreeMinecraftModels.jar.
  • MagmaCore, bStats et les autres bibliothèques incluses sont intégrés (shadés) lors de la phase package. MagmaCore est relocalisé sous com.magmaguy.freeminecraftmodels.magmacore : le serveur n'a donc besoin d'aucune installation séparée de MagmaCore.
  • Maven résout MagmaCore 2.2.0-SNAPSHOT depuis le dépôt de MagmaGuy. Lorsque les sources nécessitent des changements de MagmaCore qui n'y sont pas encore publiés, compilez d'abord MagmaCore et publiez-le dans votre dépôt Maven local. -Dmagmacore.version=<version> compile contre une autre version de MagmaCore.
  • Lorsque la variable d'environnement MC_DIST_DIR est définie, le build copie aussi le jar dans ce dossier.

Les modèles, textures, polices et packs de contenu importés conservent les licences de leurs auteurs. Générer un resource pack à partir de ceux-ci ne modifie pas ces droits.

Notes​

  • FreeMinecraftModels est une dépendance plugin installée, pas une bibliothèque embarquable.
  • Si votre plugin a besoin de modèles fraîchement importés, appelez ModeledEntityManager.reload() au lieu d'essayer de reconstruire l'état de FreeMinecraftModels vous-même.
  • Le code actuel de FMM dépend de MagmaCore 2.2.0-SNAPSHOT, qui fournit le moteur Lua partagé, LocationQueryRegistry et WorldFolderResolver.
  • FreeMinecraftModels déclare WorldGuard, WorldEdit, GriefPrevention, Vault, floodgate et Geyser-Spigot comme softdepend. WorldGuard et GriefPrevention fournissent les adaptateurs de protection intégrés tenant compte du joueur ; WorldEdit est une dépendance de WorldGuard, pas un adaptateur FMM indépendant. La boutique nécessite Vault, un fournisseur d'économie et une configuration de boutique activée. L'affichage Bedrock nécessite également un pack compatible et son intégration.
  • ModeledEntityManager.reload() demande la même préparation asynchrone et le même cycle complet que /fmm reload, sans attendre leur fin. Appelez-le depuis le thread principal et utilisez le retour d'information du cycle réussi comme preuve, pas son retour void. Un contenu candidat invalide peut entraîner le rejet de la recharge tout en conservant le système actif.