Saltar al contenido principal

API y guía para desarrolladores de FreeMinecraftModels

La referencia Java generada permite buscar clases, métodos y firmas, e indica la versión de origen y el commit de cada módulo.

FreeMinecraftModels es un plugin independiente y proporciona una API para otros plugins.

Para configurar las dependencias compartidas y el entorno de ejecución, consulta el índice de API Java. El ejemplo de dependencia usa la versión publicada 2.12.9; el código actual es 2.12.9. Usa una dependencia que coincida con el plugin instalado.

Repositorio 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>

Dependencia​

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

Úsalo como compileOnly/provided. No incluyas (shade) el plugin dentro de tu propio jar.

Puntos de entrada principales​

  • ModeledEntityManager.modelExists(String)
  • ModeledEntityManager.reload()
  • ModeledEntityManager.getAllEntities()
  • ModeledEntityManager.getDynamicEntities()
  • ModeledEntityManager.propEntities()
  • DisguiseAPI — disfrazar / quitar el disfraz a jugadores como modelos cargados
  • LocationAPI — registrar detectores de mazmorras y proveedores de protección (alimenta los predicados Lua em.location.*)
  • ScriptedItemAPI — añadir identidad de objeto personalizado y datos de visualización de FMM a ItemStacks de terceros
  • ItemConstructionSnapshot — capturar en el hilo del servidor los datos de objetos y modelos de visualización para construir objetos en otro hilo

ModeledEntityManager.getAllEntities(), getDynamicEntities() y propEntities() devuelven copias puntuales. Mutar el conjunto o el mapa devuelto no modifica los registros vivos de FMM. Usa modelExists(String) para comprobar la existencia en lugar de retener y copiar repetidamente un registro completo.

Tipos principales de ejecución​

  • ModeledEntity
  • StaticEntity
  • DynamicEntity
  • PropEntity

Crear entidades​

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);

Todas las rutas de creación devuelven null si el ID de modelo solicitado no está cargado.

PropEntity.spawnPropEntity además devuelve null cuando ya hay cargado un prop de ese mismo modelo en el bloque de destino — el duplicado se rechaza en lugar de apilarse, y se registra una advertencia [FMM Props] Prevented duplicate prop spawn .... Comprueba siempre si su valor de retorno es nulo; un retorno no nulo es la única confirmación de que el prop se creó realmente.

createWithInvisibility es una variante que aplica una poción de invisibilidad en lugar de ocultar la entidad del cliente. Esto mantiene a la entidad rastreada del lado del cliente, lo cual es necesario para el control de vehículos (usado internamente por /fmm mount) y permite que los huesos de visualización del modelo vayan montados en la entidad en los clientes de Java; consulta displayBonesRideTheirEntity.

Métodos útiles durante la ejecución​

  • ModeledEntity#setDisplayName(String) -- establece la etiqueta de nombre propia del modelo
  • ModeledEntity#setDisplayNameVisible(boolean) -- controla la visibilidad de esa etiqueta
  • ModeledEntity#setLeftClickCallback(...)
  • ModeledEntity#setRightClickCallback(...)
  • ModeledEntity#setHitboxContactCallback(...)
  • ModeledEntity#setModeledEntityHitByProjectileCallback(...)
  • ModeledEntity#playAnimation(String, boolean blend, boolean loop) -- devuelve false cuando el nombre no coincide ni con un estado integrado ni con una animación del modelo, o después de que se haya solicitado death. Para una animación personalizada, blend = true la reproduce en la capa de acción sobre idle o walk, y false deja que se apodere de toda la pose; loop solo se aplica a las animaciones personalizadas. Consulta Fusión de animaciones
  • ModeledEntity#stopCurrentAnimations() -- termina las animaciones personalizadas y los ataques y lleva el modelo de golpe a idle; no hace nada una vez que ha empezado la muerte
  • ModeledEntity#hasAnimation(String)
  • ModeledEntity#damage(double) / damage(Entity damager, double) / damage(Entity damager) / damage(Projectile) -- enrutados a través del DamageableComponent de la entidad
  • ModeledEntity#attack(LivingEntity) / attack(LivingEntity, double damage)
  • ModeledEntity#teleport(Location, boolean teleportUnderlyingEntity)
  • ModeledEntity#setUnderlyingEntity(Entity) / ModeledEntity.getModeledEntity(Entity) -- búsqueda inversa estática desde una entidad de Bukkit al modelo adjunto a ella, o null
  • ModeledEntity#showUnderlyingEntity(Player) / hideUnderlyingEntity(Player) -- visibilidad por jugador de la entidad vanilla de respaldo
  • ModeledEntity#getEntityID() -- devuelve el string del ID del modelo
  • ModeledEntity#getModelInstanceId() -- UUID por instancia, estable durante toda la vida del modelo
  • ModeledEntity#isRemoved() / isDying() -- marcas de ciclo de vida
  • ModeledEntity#getLocation() -- devuelve la Location actual
  • ModeledEntity#getSpawnLocation() -- devuelve la Location en la que se creó el modelo
  • ModeledEntity#getWorld() -- devuelve el World
  • ModeledEntity#getSkeleton() / getSkeletonBlueprint() / getMountPointManager() -- acceso a la estructura del runtime
  • ModeledEntity#getInteractionComponent() / getHitboxComponent() / getDamageableComponent() / getAnimationComponent() -- los objetos de componente que hay detrás de los métodos de conveniencia anteriores
  • ModeledEntity.getLoadedModeledEntities() -- conjunto estático y vivo de todas las entidades modeladas cargadas
  • ModeledEntity#getViewers() -- devuelve el HashSet<UUID> de jugadores que pueden ver la entidad
  • ModeledEntity#getNametagBones() -- devuelve List<Bone> de huesos de etiqueta de nombre (útil para colocar texto adicional)
  • ModeledEntity#getScaleModifier() / setScaleModifier(double)
  • ModeledEntity#removeWithDeathAnimation() -- elimina con la animación de muerte (si existe alguna)
  • ModeledEntity#removeWithMinimizedAnimation() -- elimina con una animación de reducción de escala
  • ModeledEntity#remove() -- elimina inmediatamente la entidad y todos los huesos
  • ModeledEntity#setTintColor(Color) / getTintColor() -- aplica un tinte persistente a través del canal de tinte de armadura de cuero. Los destellos de daño sobrescriben brevemente el tinte y luego se desvanecen de vuelta. Pasa null para borrarlo.
  • ModeledEntity#setViewDistanceOverride(int) / getEffectiveViewDistance() -- sobrescribe DefaultConfig.maxModelViewDistance para una única entidad. Pasa -1 para volver al predeterminado global del plugin.
  • DynamicEntity#setSyncMovement(boolean)
  • DynamicEntity#isDamagesOnContact() / setDamagesOnContact(boolean) -- controla si la entidad daña a los jugadores por contacto con la hitbox
  • DynamicEntity.isDynamicEntity(Entity) / DynamicEntity.getDynamicEntity(Entity) -- búsquedas estáticas a partir de una entidad de Bukkit
  • DynamicEntity#getBodyLocation() -- la ubicación de la entidad orientada según el yaw del modelo. Con weightedTurning activado, ese yaw sigue al yaw del cuerpo del mob con inercia de giro, así que puede ir por detrás del mob mientras el modelo gira. getLocation() devuelve la misma ubicación mientras la sincronización de movimiento esté activada
  • Bone#getBoneLocation()

Luz, inclinación, centrado y clics​

  • ModeledEntity#setBrightnessOverride(int blockLight, int skyLight) -- ilumina todos los huesos de display a un nivel fijo, cada uno de 0 a 15, en lugar de usar la luz del lugar donde está el hueso. 15, 15 es brillo total. Los espectadores reciben el cambio con la siguiente actualización de huesos. Solo para huesos de display de Java: los huesos de Bedrock y de armor stand conservan la luz del mundo
  • ModeledEntity#clearBrightnessOverride() / hasBrightnessOverride() / getBrightnessOverrideBlockLight() / getBrightnessOverrideSkyLight() -- quitan o leen la luz fija. Los getters devuelven -1 cuando no hay luz fija
  • DynamicEntity#setTravelOrientation(TravelOrientation) / getTravelOrientation() -- inclina el modelo con su desplazamiento. TravelOrientation es un record de un Style (UPRIGHT, TOP_FIRST, FRONT_FIRST), startSpeed, fullSpeed, maxTilt y turnSpeed; TravelOrientation.UPRIGHT es el valor por defecto y null lo restablece. Consulta Orientación de desplazamiento
  • ModeledEntity#setBodyTilt(Quaterniondc) / getBodyTilt() -- inclina todo el modelo con una rotación en el espacio del mundo alrededor del centro de sus cubos, después de su orientación y de sus animaciones. null lo deja derecho. Una orientación de desplazamiento distinta de UPRIGHT lo define en cada tick; en un modelo derecho, una inclinación que definas se mantiene
  • ModeledEntity#setCenteredOnBody(boolean) / isCenteredOnBody() / getBodyOffset() -- dibuja el modelo con el centro de sus cubos sobre el centro de su entidad. Consulta Centrado en el cuerpo
  • DynamicEntity#setClickSurface(boolean) / isClickSurface() -- cuando es true, los clientes reciben una entidad Interaction invisible sobre el modelo, de modo que los jugadores hacen clic en el modelo que ven y no en la caja del propio cuerpo. Toma el tamaño del hueso hitbox del modelo, o de los límites del propio modelo si no tiene ninguno, y se aplica en un máximo de dos ticks. Está desactivado por defecto, porque una caja que cubre un modelo largo o rotado también captura clics en sus esquinas vacías
  • DynamicEntity#isSteerableMount() / hasSteeringRider() / seatSteeringRider(Player) -- un modelo sobre un caballo con silla es una montura dirigible. seatSteeringRider sienta al jugador en el propio caballo como jinete que lo dirige cuando nadie lo monta, y devuelve si ahora lo monta. Un clic derecho sobre un modelo así hace esto por defecto
  • DynamicEntity#liftSaddleTo(Bone) -- escala el mob para que su silla quede sobre el hueso de asiento indicado en la pose de reposo del modelo, mientras el modelo conserva su tamaño. /fmm mount lo usa con el primer hueso m_ del modelo. No hace nada cuando el servidor no puede informar de la altura de la silla

Etiquetas de nombre y objetivos de mirada​

ModeledEntity expone ahora setDisplayNameLines(List<String>), setDisplayNameScale(float), setDisplayNameLineGap(double), setDisplayNameVisible(boolean) y getNameplateLocation(). El hueso de etiqueta es opcional. Sin él, la placa de nombre queda un cuarto de bloque (cuatro píxeles) por encima del cubo más alto del modelo en reposo, escalada junto con la entidad.

Nombres de varias líneas:

  • setDisplayName("Top line\nBottom line") y setDisplayNameLines(List<String>) toman las filas en orden de lectura, de arriba abajo. La fila inferior se queda en el ancla y las filas adicionales se extienden hacia arriba. Los saltos de línea explícitos definen las filas; el texto no se ajusta automáticamente.
  • Un nombre vacío oculta la placa de nombre. setDisplayNameVisible(false) oculta todas las filas y sigue vigente cuando cambia el texto.
  • setDisplayNameScale(float) escala el texto independientemente del tamaño del modelo. setDisplayNameLineGap(double) define el hueco libre entre filas, en bloques a escala 1, 0.1 por defecto. Una fila mide 0.225 bloques de alto a escala 1, así que las filas sucesivas quedan separadas 0.325 bloques por defecto.
  • Cuando un modelo tiene varios huesos tag_, el primero por orden alfabético ancla la placa de nombre, incluidos su movimiento y su animación.
  • Los jugadores de Java ven displays de texto, que escalan tanto los glifos como el espaciado. Los jugadores de Bedrock ven etiquetas de armor stand marcador independientes, que no pueden escalarse, así que su espaciado se queda a escala 1. Elegir las etiquetas de Bedrock requiere Floodgate o Geyser en el servidor backend para identificar a los jugadores de Bedrock; una configuración solo con proxy y sin reenvío de identidad no puede elegirlas de forma fiable.
  • El rango del espectador, la invisibilidad, la eliminación y la recarga siguen al modelo. El texto y las posiciones sin cambios no envían paquetes.

Para saber cómo EliteMobs dispone el nombre y el rol de un NPC que usa un modelo de FreeMinecraftModels, consulta Crear NPC.

DynamicEntity.setLookTarget(Location) proporciona al esqueleto una copia del objetivo visual de mirada sin rotar ni mover la entidad subyacente. Pasa null para liberarlo.

Particularidades de PropEntity​

  • PropEntity.isPropEntity(ArmorStand) / PropEntity.getPropEntityID(ArmorStand) -- identifican un prop a partir de su armor stand de respaldo
  • PropEntity.hasLoadedPropOnSameBlock(String entityID, Location) -- la comprobación de duplicados que spawnPropEntity ejecuta internamente; llámala primero si prefieres ramificar en lugar de comprobar si el resultado es null
  • PropEntity.respawnPropEntityFromArmorStand(String entityID, ArmorStand) -- reconstruye un prop alrededor de un armor stand que sobrevivió a una recarga de chunk
  • PropEntity.getPropEntities() -- copia puntual de los props cargados, indexada por el UUID del soporte para armaduras; modificarla no cambia el registro activo
  • PropEntity#setPersistent(boolean) -- alterna la persistencia en el armor stand de respaldo
  • PropEntity#setCustomDataString(NamespacedKey, String) / getCustomDataString(NamespacedKey) -- lee y escribe tus propios valores PDC en el prop. Es el mismo almacén que usan los ayudantes de Lua set_persistent_data / get_persistent_data, bajo el espacio de nombres fmm_lua_<key>
  • PropEntity#remove() / remove(boolean showRealBlocks) -- elimina el modelo pero deja la entrada persistente
  • PropEntity#permanentlyRemove() -- elimina el modelo y su entrada persistente
  • PropEntity#setVoxelizeConfig(boolean voxelize, boolean solidify) / applySolidify() -- los equivalentes en runtime de los campos YML voxelize: / solidify:
  • PropEntity#showFakePropBlocksToPlayer(Player) / showRealBlocksToPlayer(Player) y las variantes ...ToAllPlayers() -- controlan los bloques barrera de solo paquetes que dan colisión del lado del cliente a un prop solidificado

Integraciones de armas mágicas​

Usa MagicWeaponAPI.isOperational() antes de depender del servicio de magia, isWeapon(String) para comprobar un ID definido y applyWeaponData(ItemStack, String) para aplicar la identidad y visualización del arma sin reemplazar el nombre, lore, nivel ni encantamientos del objeto original. Que el servicio esté registrado no demuestra que sea operativo.

Un MagicAttackResolver opcional puede rechazar lanzamientos, filtrar y ordenar objetivos, capturar datos de combate al lanzar y resolver el daño. Debe usar el MagicDamageApplication suministrado, que solo se puede consumir una vez, en lugar de dañar directamente al objetivo. FMM controla los controles, el recorrido del proyectil, la colisión y la aplicación del daño.

MagicProjectileTravelEvent es una consulta síncrona y cancelable de colisión sobre un segmento barrido, incluida una comprobación en el momento del impacto. Cancelarla absorbe el proyectil sin daño ni explosión. Un segmento puede comprobarse varias veces, así que los escuchadores no deben tratarla como una notificación de daño.

Eventos disponibles​

Los clics derechos primero emiten ModeledEntityInteractEvent sobre la entidad subyacente real. Hereda los manejadores de PlayerInteractEntityEvent, para que los escuchadores normales de protección de entidades puedan cancelarlo. Cancelarlo impide el evento y la función de clic derecho del modelo. Esta comprobación es independiente del permiso para colocar o romper bloques.

Cómo encajan las comprobaciones de clic derecho:

  • Las interacciones con bloques y con modelos se deciden por separado. Cancelar un PlayerInteractEvent mantiene cancelada la acción del bloque o del objeto, pero el clic derecho aún puede seleccionar un modelo mediante las comprobaciones de alcance y de oclusión por bloques de FMM. La cancelación del clic izquierdo funciona como antes.
  • ModeledEntityInteractEvent usa la mano principal. FMM emite ModeledEntityRightClickEvent solo después de que termine ese despacho, y un ModeledEntityRightClickEvent cancelado detiene la función del modelo, el comportamiento del prop y el montaje por defecto.
  • El evento puente es una comprobación de permiso, no otro clic. Los enrutadores de entrada, de arma mágica y de props de FMM lo ignoran. Un plugin que realiza acciones en eventos nativos de interacción con entidades puede omitir ModeledEntityInteractEvent para no actuar dos veces; los escuchadores de protección deberían seguir gestionándolo.
  • Un modelo sin entidad de Bukkit de respaldo no puede emitir un evento de entidad de Bukkit. Sigue emitiendo el ModeledEntityRightClickEvent cancelable. Los props persistentes y los NPC de EliteMobs tienen entidades de respaldo y reciben ambas comprobaciones.

Eventos de interacción genéricos:

  • ModeledEntityLeftClickEvent
  • ModeledEntityRightClickEvent
  • ModeledEntityHitboxContactEvent
  • ModeledEntityHitByProjectileEvent

Los cuatro son eventos de Bukkit cancelables; las rutas de detección integradas de FMM los despachan en el hilo principal del servidor. Cancelar uno impide el callback o el comportamiento predeterminado correspondiente de FMM. Los escaneos de contacto con la hitbox se ejecutan cada dos ticks del servidor; los eventos de clic siguen teniendo una ventana corta de deduplicación por jugador para que las rutas de interacción por paquetes y de raytrace OBB no puedan disparar dos veces la misma acción.

Eventos de ciclo de vida:

  • FmmReloadedEvent -- se programa en el hilo principal 40 ticks después de una inicialización completa correcta, incluido el inicio y una recarga completa correcta. La recarga limitada al contenido importado también lo emite si termina correctamente. Una petición rechazada durante la preparación conserva el sistema activo y no emite un evento de recarga completada.

La superficie pública de interacción es intencionalmente neutral respecto al tipo de modelo. Las antiguas variantes StaticEntity*Event, DynamicEntity*Event y PropEntity*Event, junto con ResourcePackGenerationEvent, ya no forman parte de la API actual. Usa en su lugar los cuatro eventos genéricos de entidad modelada anteriores y FmmReloadedEvent.

Los plugins consumidores que mantienen referencias DynamicEntity o PropEntity de larga vida (EliteMobs, BetterStructures, etc.) deben manejar este evento recreando sus adjuntos de modelo en las entidades subyacentes sobrevivientes. Sin esto, esas entidades se vuelven invisibles tras una recarga porque FMM derribó las display entities durante onDisable mientras la referencia del consumidor ahora está obsoleta.

@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 y detección de proyectiles OBB​

FMM usa detección de impacto OBB (oriented bounding box) para proyectiles contra entidades modeladas. Cuando un proyectil interseca la hitbox OBB de una entidad modelada, FMM dispara un ModeledEntityHitByProjectileEvent. Este es un evento Bukkit estándar cancelable.

Detalles clave:

  • La detección comprueba el segmento entre posiciones registradas para detectar cruces de modelos finos; si falta la posición anterior, usa una comprobación de solapamiento
  • Los candidatos de ese segmento se resuelven del más cercano al más lejano, con independencia del orden de iteración del registro de modelos
  • Un proyectil sin perforación se enruta a un único objetivo modelado. Una flecha con Perforación puede alcanzar pierce level + 1 objetivos modelados distintos, en orden de recorrido, sin disparar dos veces cuando tanto la OBB como las hitboxes vanilla de respaldo lo observan
  • El manejador predeterminado de entidades dinámicas reenvía el impacto a la entidad viva subyacente como un evento real de daño por proyectil, usando la velocidad del proyectil en el momento del impacto. Esto conserva la causa del proyectil, el manejo por parte de plugins de combate y la atribución al tirador
  • Cancelar el evento impide el callback o el daño predeterminado de FMM, pero la colisión sigue gastando el presupuesto de objetivos modelados de ese proyectil. Por tanto, un impacto sin perforación cancelado consume igualmente el proyectil
@EventHandler
public void onProjectileHitModel(ModeledEntityHitByProjectileEvent event) {
ModeledEntity target = event.getEntity();
Projectile projectile = event.getProjectile();
// Cancel to prevent damage
event.setCancelled(true);
}

Utilidades de objetos y modelos​

ModelItemFactory​

Clase factoría para crear ItemStacks relacionados con modelos de forma programática.

// 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) -- crea un objeto de colocación para props. En 1.21.4+, aplica automáticamente el renderizado del modelo de visualización si existe un JSON de display.
  • createCustomItem(String itemId, PropScriptConfigFields config) -- crea un objeto personalizado con nombre, lore, encantamientos y modelo de visualización desde la configuración unificada.
  • formatModelName(String modelId) -- utilidad que convierte un ID de modelo como 01_em_flame_sword en Flame Sword.

DisplayModelRegistry​

Registro simple que rastrea qué modelos tienen un JSON de display disponible.

// Check if a model has a display model JSON registered
boolean has3D = DisplayModelRegistry.hasDisplayModel("magic_sword");
  • register(String modelId) -- registra un ID de modelo (llamado internamente durante la recarga)
  • hasDisplayModel(String modelId) -- comprueba que el modelo de visualización esté registrado; un .json inválido no se registra por el mero hecho de existir
  • getRegisteredModels() -- devuelve un Set<String> inmutable de todos los IDs de modelo que tienen modelos de visualización registrados
  • shutdown() -- borra todos los registros

ItemScriptManager​

Aunque conserva su nombre de clase, ahora es un catálogo de objetos personalizados y armas mágicas, no un motor de scripts de objetos equipados. getItemDefinitions() expone las definiciones registradas de objetos; getWeaponCatalog() expone las definiciones validadas de armas. Las antiguas API de scripts activos, actualización de equipo y eliminación de scripts de jugador ya no existen.

ScriptedItemAPI​

  • isValidItemId(String): comprueba el ID exacto del objeto definido (nombre del archivo de configuración sin .yml).
  • getItemConfig(String): devuelve la definición, o null.
  • applyScriptedItemData(ItemStack, String): añade fmm_item_id y, si existe, el modelo de visualización registrado. Devuelve false si el ID no es válido o faltan los metadatos del objeto.

Este método no modifica el nombre, lore ni encantamientos del objeto original, y tampoco adjunta comportamiento Lua. Aplica los encantamientos por separado mediante MagmaCore. Los estados de tensado de arcos y ballestas pertenecen a la definición base del modelo de visualización; no añadas _idle al ID del objeto definido.

ModelItemAPI​

Usa ModelItemAPI.applyDisplayModel(ItemStack, String) cuando el objeto original solo necesite un modelo de visualización de FMM. Comprueba el registro de modelos de visualización y no añade identidad de objeto personalizado. Es una operación distinta de seleccionar una definición de arma u objeto de FMM.

ItemConstructionSnapshot​

Para plugins que construyen objetos fuera del hilo del servidor. Llama a ItemConstructionSnapshot.capture() en el hilo del servidor; copia las definiciones actuales de objetos personalizados, los modelos de visualización y los tipos de armas mágicas. Después, un hilo de trabajo puede llamar a item(id) o weapon(id) y aplicar el ItemData devuelto a un ItemStack con apply(ItemStack), que añade fmm_item_id en el caso de un objeto personalizado y el modelo de visualización cuando haya uno registrado. weapon(id) devuelve null salvo que el ID sea un arma mágica y el servicio de magia estuviera operativo en el momento de la captura. Antes de publicar los objetos construidos, llama a requireCurrent() en el hilo del servidor: lanza ConcurrentModificationException si las definiciones de FMM han cambiado desde la captura, por ejemplo tras una recarga.

DisguiseAPI​

Punto de entrada público para la función de disfraz de jugador. Los plugins de terceros deberían llamar a esta clase en lugar del DisguiseManager interno para que las refactorizaciones internas permanezcan seguras.

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) -- elimina el disfraz anterior antes de intentar sustituirlo; un ID desconocido devuelve false después de eliminarlo. Pasa un jugador conectado y un ID cargado exacto; la API no normaliza los ID ni comprueba los permisos del comando
  • undisguise(Player) -- devuelve true si se eliminó un disfraz
  • isDisguised(Player) -- comprobación booleana rápida
  • getDisguiseModelID(Player) -- devuelve el ID del modelo activo o null
  • getDisguisedPlayers() -- snapshot inmodificable de los jugadores disfrazados

FMM combina la visibilidad predeterminada del jugador, un efecto de poción de invisibilidad y cambios en la visualización del equipo. Solo restaura un efecto eliminado si ese disfraz lo añadió originalmente; no controla ni restaura un efecto que ya existía. A diferencia de la API, el comando valida el modelo antes de sustituirlo. Consulta los límites de ciclo de vida y visualización en Disfraces de jugadores.

LocationAPI​

API pública para que los plugins contribuyan con detección de mazmorras y comprobaciones de protección de regiones. Los predicados registrados alimentan las comprobaciones Lua de FMM em.location.is_in_dungeon y em.location.is_protected (usadas por scripts premade como pickupable.lua y storage_double.lua).

Los plugins pasan un Predicate<Location> plano para que ningún tipo FMM con shade cruce los classloaders de plugins.

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) — cualquier predicado registrado que devuelva true marca la ubicación como "en mazmorra"
  • registerProtectionProvider(String providerName, Predicate<Location> predicate) — cualquier predicado registrado que devuelva true marca la ubicación como protegida
  • unregisterDungeonLocator(String providerName) y unregisterProtectionProvider(String providerName) eliminan el predicado registrado con ese nombre. Llámalos desde el onDisable de tu plugin para que FMM deje de llamar a la instancia deshabilitada

Volver a registrar con el mismo nombre de proveedor sustituye el predicado anterior, así que un plugin que se registra en cada habilitación no acumula copias. Un nombre de proveedor null registra un predicado que no se puede sustituir ni eliminar.

Los operadores pueden verificar el registro con /fmm location, que informa el conteo de proveedores activos y prueba ambos predicados contra su ubicación actual.

Proveedores de protección y colocación de elementos decorativos​

Los mismos proveedores de protección también impulsan la comprobación de colocación de props preventPropPlacementInProtectedRegions, pero esa comprobación llama a canBuild(player, location) en lugar del isProtected(location) basado solo en ubicación. La distinción importa:

ProveedorComportamiento de canBuild
Adaptador integrado de WorldGuardRespeta el propio bypass de WorldGuard y después delega en el testBuild de WorldGuard — así que los miembros y propietarios de la región pueden construir con normalidad
Adaptador integrado de GriefPreventionSi no hay reclamación en la ubicación se permite; dentro de una reclamación delega en el permiso de construcción de GriefPrevention para ese jugador
Proveedor registrado vía LocationAPI.registerProtectionProviderRecae en !isProtected(location) — solo por ubicación, bloquea a todo el mundo en una ubicación que tu predicado considere protegida

Ese respaldo es intencionado y conservador: un Predicate<Location> no puede expresar permisos específicos por jugador, así que FMM no se los inventa. Si quieres un comportamiento genuinamente consciente del jugador para tu propio sistema de regiones, implementa directamente el RegionProtectionProvider de MagmaCore y sobrescribe canBuild, y después regístralo con LocationQueryRegistry.registerProtectionProvider en lugar de pasar por el envoltorio de conveniencia del predicado.

Dos comportamientos más que conviene conocer:

  • Los fallos del adaptador fallan de forma cerrada. Si un proveedor lanza una excepción durante una consulta de construcción, la colocación se rechaza y se registra una advertencia que indica el proveedor. Nunca cae en silencio a "permitido".
  • Todos los proveedores registrados deben estar de acuerdo. Gana el primer proveedor que diga que no; los proveedores de propiedad registrados a través de LocationOwnership siguen siendo solo por ubicación y bloquean a todo el mundo.

La colocación mediante un objeto comprueba primero la celda adyacente. Los props voxelizados comprueban además su superficie rotada, con dimensiones de hitbox redondeadas y un mínimo de un bloque; el volumen de barreras por paquetes usa otro cálculo. Las llamadas directas a PropEntity.spawnPropEntity no ejecutan estas comprobaciones de colocación del objeto del jugador. Consulta Configuración antes de permitir colocar props desde otro plugin.

PropScriptConfigFields​

Analiza el YAML situado junto al modelo que se usa para elementos decorativos y objetos personalizados. Las listas scripts: de elementos decorativos vinculan scripts Lua independientes. Una definición con material: registra un objeto que se puede sostener y no puede contener una lista scripts: no vacía.

Consulta configuración del modelo para ver ejemplos separados de elementos decorativos y objetos, y armas mágicas para el esquema de armas.

Scripts Lua​

FreeMinecraftModels admite scripts Lua para elementos decorativos mediante el motor de MagmaCore. Los archivos se colocan en plugins/FreeMinecraftModels/scripts/ y se vinculan al modelo mediante un YML situado junto a él. El archivo de script en disco debe terminar en .lua; las entradas de configuración pueden incluir u omitir la extensión.

Los elementos decorativos vinculan todos los scripts de scripts: como instancias independientes. Los efectos de objetos que se sostienen usan el motor separado del proveedor de encantamientos y no pueden usar una lista scripts: no vacía.

Eventos de script de elementos decorativos​

HookActivador
on_spawnEl prop aparece en el mundo
on_game_tickCada tick mientras el prop está activo
on_zone_enterUn jugador entra en una zona vigilada creada por el script
on_zone_leaveUn jugador sale de una zona vigilada creada por el script
on_destroyEl prop se elimina definitivamente (no al descargarse el chunk ni al recargar)
on_left_clickEl jugador hace clic izquierdo en el prop
on_right_clickEl jugador hace clic derecho en el prop
on_projectile_hitUn proyectil impacta en el prop; context.projectile es el proyectil y context.event.player quien lo disparó

Eventos de script de objetos​

Los eventos Lua de objetos equipados y el sistema context.item se han retirado. Usa definiciones de encantamientos para efectos de objetos. Los eventos de elementos decorativos siguen admitidos.

Tabla de contexto del script de elemento decorativo​

Los scripts de prop reciben una tabla context. Aquí hay un resumen de las APIs clave -- consulta Lua Prop API para más detalles.

context.prop:

  • model_id -- el nombre del modelo blueprint
  • current_location -- la ubicación actual del prop
  • play_animation(name, blend, loop) -- solicita la animación indicada (blend y loop valen true por defecto, lo que superpone una animación en bucle al idle del prop); la solicitud se aplica en el siguiente tick del modelo
  • stop_animation() -- termina las animaciones personalizadas y vuelve a reposo si está disponible; no interrumpe el estado terminal de muerte
  • hurt_visual() -- reproduce el visual de daño (destello rojo) en el prop
  • pickup() -- encola la eliminación del prop y suelta su objeto de colocación
  • mount(player) -- encola un intento de montaje; un retorno true significa que el jugador y el gestor de montaje eran válidos, no que finalmente se asignara un asiento
  • dismount(player) -- encola una comprobación de desmontaje; un retorno true significa que el jugador y el gestor de montaje eran válidos
  • get_passengers() -- devuelve una lista de jugadores que están montando el prop actualmente
  • spawn_elitemobs_boss(filename, x, y, z) -- genera un jefe de EliteMobs en coordenadas absolutas del mundo actual del prop

context.event:

  • Disponible en los eventos de elementos decorativos on_left_click, on_right_click, on_zone_enter, on_zone_leave y on_projectile_hit
  • cancel(), uncancel(), is_cancelled cuando el hook subyacente es cancelable
  • player -- el jugador que desencadenó el evento; en on_projectile_hit, la entidad viva que disparó el proyectil

context.world:

  • spawn_entity(entity_type, x, y, z) -- genera una entidad vanilla, o devuelve nil si el tipo de entidad no es válido
  • set_block_at(x, y, z, material) -- encola un cambio de bloque si el material es válido; los chunks descargados se omiten
  • Además partículas, sonidos, consultas de bloque, rayos y búsquedas de entidades cercanas

context.cooldowns:

  • check_local(key, ticks) -- comprueba e inicia un cooldown por script
  • global_ready() / set_global(ticks) -- tiempo de reutilización compartido para el elemento decorativo

Objetos de jugador (desde context.player o context.event.player):

  • get_held_item() -- devuelve el objeto que el jugador sostiene; type es el nombre del material de Bukkit en mayúsculas
  • is_holding_elitemobs_item(filename) -- comprueba la etiqueta de nombre de archivo de EliteMobs del objeto de la mano principal, como shroomwhispers_shears.yml. Los nombres visibles y las traducciones no afectan a esta comprobación. Devuelve false si la mano está vacía, el objeto es distinto o la integración con EliteMobs no está disponible.
  • consume_held_item() -- encola la retirada de una unidad de la mano principal tal como esté cuando se ejecute la tarea; no devuelve un resultado de pago
  • has_item(material) -- comprueba si el jugador tiene un objeto
  • send_message(text) -- envía un mensaje de chat al jugador
  • game_mode -- el modo de juego actual del jugador

Usa la identidad por nombre de archivo para los objetos de misión de EliteMobs:

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

La integración de FMM con EliteMobs proporciona este método. Cambiar el nombre de un objeto normal no le añade la etiqueta de nombre de archivo requerida.

Ejemplo de script de elemento decorativo​

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
}

Ejemplo de script de objeto​

El antiguo ejemplo de objeto equipado ya no se admite. Consulta armas mágicas y definiciones de encantamientos para la creación actual de objetos.

Compilar desde el código fuente​

El código fuente está en GitHub. FreeMinecraftModels es un proyecto de Maven para Java 21:

mvn -DskipTests package
  • El jar del plugin se escribe en target/FreeMinecraftModels.jar.
  • MagmaCore, bStats y las demás bibliotecas incluidas se integran (shade) en la fase package. MagmaCore se reubica en com.magmaguy.freeminecraftmodels.magmacore, así que el servidor no necesita una instalación aparte de MagmaCore.
  • Maven resuelve MagmaCore 2.2.0-SNAPSHOT desde el repositorio de MagmaGuy. Cuando el código fuente necesita cambios de MagmaCore que aún no están publicados allí, compila MagmaCore y publícalo primero en tu repositorio Maven local. -Dmagmacore.version=<version> compila contra otra versión de MagmaCore.
  • Cuando la variable de entorno MC_DIST_DIR está definida, la compilación también copia el jar a esa carpeta.

Los modelos, texturas, fuentes y paquetes de contenido importados conservan las licencias de sus autores. Generar un resource pack a partir de ellos no cambia esos derechos.

Notas​

  • FreeMinecraftModels es una dependencia de plugin instalado, no una librería integrable.
  • Si tu plugin necesita modelos recién importados, llama a ModeledEntityManager.reload() en lugar de intentar reconstruir el estado de FreeMinecraftModels tú mismo.
  • El código actual de FMM depende de MagmaCore 2.2.0-SNAPSHOT, que proporciona el motor Lua compartido, LocationQueryRegistry y WorldFolderResolver.
  • FreeMinecraftModels declara WorldGuard, WorldEdit, GriefPrevention, Vault, floodgate y Geyser-Spigot como softdepend. WorldGuard y GriefPrevention proporcionan los adaptadores de protección integrados que consideran al jugador; WorldEdit es una dependencia de WorldGuard, no un adaptador independiente de FMM. La tienda necesita Vault, un proveedor de economía y la configuración de tienda habilitada. La visualización Bedrock también necesita su pack compatible y su integración.
  • ModeledEntityManager.reload() solicita la misma preparación asíncrona y el ciclo completo de /fmm reload; no espera a que termine. Llámalo desde el hilo principal y usa la confirmación correcta del ciclo de vida, no el retorno void, como prueba. Un contenido candidato inválido puede hacer que se rechace la recarga conservando el sistema activo.