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 cargadosLocationAPI— registrar detectores de mazmorras y proveedores de protección (alimenta los predicados Luaem.location.*)ScriptedItemAPI— añadir identidad de objeto personalizado y datos de visualización de FMM a ItemStacks de tercerosItemConstructionSnapshot— 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
ModeledEntityStaticEntityDynamicEntityPropEntity
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 modeloModeledEntity#setDisplayNameVisible(boolean)-- controla la visibilidad de esa etiquetaModeledEntity#setLeftClickCallback(...)ModeledEntity#setRightClickCallback(...)ModeledEntity#setHitboxContactCallback(...)ModeledEntity#setModeledEntityHitByProjectileCallback(...)ModeledEntity#playAnimation(String, boolean blend, boolean loop)-- devuelvefalsecuando el nombre no coincide ni con un estado integrado ni con una animación del modelo, o después de que se haya solicitadodeath. Para una animación personalizada,blend = truela reproduce en la capa de acción sobre idle o walk, yfalsedeja que se apodere de toda la pose;loopsolo se aplica a las animaciones personalizadas. Consulta Fusión de animacionesModeledEntity#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 muerteModeledEntity#hasAnimation(String)ModeledEntity#damage(double)/damage(Entity damager, double)/damage(Entity damager)/damage(Projectile)-- enrutados a través delDamageableComponentde la entidadModeledEntity#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, onullModeledEntity#showUnderlyingEntity(Player)/hideUnderlyingEntity(Player)-- visibilidad por jugador de la entidad vanilla de respaldoModeledEntity#getEntityID()-- devuelve el string del ID del modeloModeledEntity#getModelInstanceId()--UUIDpor instancia, estable durante toda la vida del modeloModeledEntity#isRemoved()/isDying()-- marcas de ciclo de vidaModeledEntity#getLocation()-- devuelve laLocationactualModeledEntity#getSpawnLocation()-- devuelve laLocationen la que se creó el modeloModeledEntity#getWorld()-- devuelve elWorldModeledEntity#getSkeleton()/getSkeletonBlueprint()/getMountPointManager()-- acceso a la estructura del runtimeModeledEntity#getInteractionComponent()/getHitboxComponent()/getDamageableComponent()/getAnimationComponent()-- los objetos de componente que hay detrás de los métodos de conveniencia anterioresModeledEntity.getLoadedModeledEntities()-- conjunto estático y vivo de todas las entidades modeladas cargadasModeledEntity#getViewers()-- devuelve elHashSet<UUID>de jugadores que pueden ver la entidadModeledEntity#getNametagBones()-- devuelveList<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 escalaModeledEntity#remove()-- elimina inmediatamente la entidad y todos los huesosModeledEntity#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. Pasanullpara borrarlo.ModeledEntity#setViewDistanceOverride(int)/getEffectiveViewDistance()-- sobrescribeDefaultConfig.maxModelViewDistancepara una única entidad. Pasa-1para 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 hitboxDynamicEntity.isDynamicEntity(Entity)/DynamicEntity.getDynamicEntity(Entity)-- búsquedas estáticas a partir de una entidad de BukkitDynamicEntity#getBodyLocation()-- la ubicación de la entidad orientada según el yaw del modelo. ConweightedTurningactivado, 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é activadaBone#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 de0a15, en lugar de usar la luz del lugar donde está el hueso.15, 15es 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 mundoModeledEntity#clearBrightnessOverride()/hasBrightnessOverride()/getBrightnessOverrideBlockLight()/getBrightnessOverrideSkyLight()-- quitan o leen la luz fija. Los getters devuelven-1cuando no hay luz fijaDynamicEntity#setTravelOrientation(TravelOrientation)/getTravelOrientation()-- inclina el modelo con su desplazamiento.TravelOrientationes un record de unStyle(UPRIGHT,TOP_FIRST,FRONT_FIRST),startSpeed,fullSpeed,maxTiltyturnSpeed;TravelOrientation.UPRIGHTes el valor por defecto ynulllo restablece. Consulta Orientación de desplazamientoModeledEntity#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.nulllo deja derecho. Una orientación de desplazamiento distinta deUPRIGHTlo define en cada tick; en un modelo derecho, una inclinación que definas se mantieneModeledEntity#setCenteredOnBody(boolean)/isCenteredOnBody()/getBodyOffset()-- dibuja el modelo con el centro de sus cubos sobre el centro de su entidad. Consulta Centrado en el cuerpoDynamicEntity#setClickSurface(boolean)/isClickSurface()-- cuando estrue, 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 huesohitboxdel 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íasDynamicEntity#isSteerableMount()/hasSteeringRider()/seatSteeringRider(Player)-- un modelo sobre un caballo con silla es una montura dirigible.seatSteeringRidersienta 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 defectoDynamicEntity#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 mountlo usa con el primer huesom_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")ysetDisplayNameLines(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.1por defecto. Una fila mide0.225bloques de alto a escala 1, así que las filas sucesivas quedan separadas0.325bloques 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 respaldoPropEntity.hasLoadedPropOnSameBlock(String entityID, Location)-- la comprobación de duplicados quespawnPropEntityejecuta internamente; llámala primero si prefieres ramificar en lugar de comprobar si el resultado esnullPropEntity.respawnPropEntityFromArmorStand(String entityID, ArmorStand)-- reconstruye un prop alrededor de un armor stand que sobrevivió a una recarga de chunkPropEntity.getPropEntities()-- copia puntual de los props cargados, indexada por el UUID del soporte para armaduras; modificarla no cambia el registro activoPropEntity#setPersistent(boolean)-- alterna la persistencia en el armor stand de respaldoPropEntity#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 Luaset_persistent_data/get_persistent_data, bajo el espacio de nombresfmm_lua_<key>PropEntity#remove()/remove(boolean showRealBlocks)-- elimina el modelo pero deja la entrada persistentePropEntity#permanentlyRemove()-- elimina el modelo y su entrada persistentePropEntity#setVoxelizeConfig(boolean voxelize, boolean solidify)/applySolidify()-- los equivalentes en runtime de los campos YMLvoxelize:/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
PlayerInteractEventmantiene 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. ModeledEntityInteractEventusa la mano principal. FMM emiteModeledEntityRightClickEventsolo después de que termine ese despacho, y unModeledEntityRightClickEventcancelado 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
ModeledEntityInteractEventpara 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
ModeledEntityRightClickEventcancelable. Los props persistentes y los NPC de EliteMobs tienen entidades de respaldo y reciben ambas comprobaciones.
Eventos de interacción genéricos:
ModeledEntityLeftClickEventModeledEntityRightClickEventModeledEntityHitboxContactEventModeledEntityHitByProjectileEvent
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 + 1objetivos 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 como01_em_flame_swordenFlame 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.jsoninválido no se registra por el mero hecho de existirgetRegisteredModels()-- devuelve unSet<String>inmutable de todos los IDs de modelo que tienen modelos de visualización registradosshutdown()-- 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, onull.applyScriptedItemData(ItemStack, String): añadefmm_item_idy, si existe, el modelo de visualización registrado. Devuelvefalsesi 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 devuelvefalsedespués de eliminarlo. Pasa un jugador conectado y un ID cargado exacto; la API no normaliza los ID ni comprueba los permisos del comandoundisguise(Player)-- devuelvetruesi se eliminó un disfrazisDisguised(Player)-- comprobación booleana rápidagetDisguiseModelID(Player)-- devuelve el ID del modelo activo onullgetDisguisedPlayers()-- 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 devuelvatruemarca la ubicación como "en mazmorra"registerProtectionProvider(String providerName, Predicate<Location> predicate)— cualquier predicado registrado que devuelvatruemarca la ubicación como protegidaunregisterDungeonLocator(String providerName)yunregisterProtectionProvider(String providerName)eliminan el predicado registrado con ese nombre. Llámalos desde elonDisablede 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:
| Proveedor | Comportamiento de canBuild |
|---|---|
| Adaptador integrado de WorldGuard | Respeta 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 GriefPrevention | Si 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.registerProtectionProvider | Recae 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
LocationOwnershipsiguen 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
| Hook | Activador |
|---|---|
on_spawn | El prop aparece en el mundo |
on_game_tick | Cada tick mientras el prop está activo |
on_zone_enter | Un jugador entra en una zona vigilada creada por el script |
on_zone_leave | Un jugador sale de una zona vigilada creada por el script |
on_destroy | El prop se elimina definitivamente (no al descargarse el chunk ni al recargar) |
on_left_click | El jugador hace clic izquierdo en el prop |
on_right_click | El jugador hace clic derecho en el prop |
on_projectile_hit | Un 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 blueprintcurrent_location-- la ubicación actual del propplay_animation(name, blend, loop)-- solicita la animación indicada (blend y loop valentruepor defecto, lo que superpone una animación en bucle al idle del prop); la solicitud se aplica en el siguiente tick del modelostop_animation()-- termina las animaciones personalizadas y vuelve a reposo si está disponible; no interrumpe el estado terminal de muertehurt_visual()-- reproduce el visual de daño (destello rojo) en el proppickup()-- encola la eliminación del prop y suelta su objeto de colocaciónmount(player)-- encola un intento de montaje; un retornotruesignifica que el jugador y el gestor de montaje eran válidos, no que finalmente se asignara un asientodismount(player)-- encola una comprobación de desmontaje; un retornotruesignifica que el jugador y el gestor de montaje eran válidosget_passengers()-- devuelve una lista de jugadores que están montando el prop actualmentespawn_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_leaveyon_projectile_hit cancel(),uncancel(),is_cancelledcuando el hook subyacente es cancelableplayer-- el jugador que desencadenó el evento; enon_projectile_hit, la entidad viva que disparó el proyectil
context.world:
spawn_entity(entity_type, x, y, z)-- genera una entidad vanilla, o devuelvenilsi el tipo de entidad no es válidoset_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 scriptglobal_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;typees el nombre del material de Bukkit en mayúsculasis_holding_elitemobs_item(filename)-- comprueba la etiqueta de nombre de archivo de EliteMobs del objeto de la mano principal, comoshroomwhispers_shears.yml. Los nombres visibles y las traducciones no afectan a esta comprobación. Devuelvefalsesi 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 pagohas_item(material)-- comprueba si el jugador tiene un objetosend_message(text)-- envía un mensaje de chat al jugadorgame_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 encom.magmaguy.freeminecraftmodels.magmacore, así que el servidor no necesita una instalación aparte de MagmaCore. - Maven resuelve MagmaCore
2.2.0-SNAPSHOTdesde 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_DIRestá 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,LocationQueryRegistryyWorldFolderResolver. - 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 retornovoid, como prueba. Un contenido candidato inválido puede hacer que se rechace la recarga conservando el sistema activo.