API y Guía para Desarrolladores de FreeMinecraftModels
FreeMinecraftModels es tanto un plugin independiente como una superficie de API para otros plugins.
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>LATEST.VERSION.HERE</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— marcar ItemStacks de terceros con metadatos de objeto-script de FMM
Tipos Principales del Runtime
ModeledEntityStaticEntityDynamicEntityPropEntity
Creando 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.
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).
Métodos Útiles del Runtime
ModeledEntity#setDisplayName(String)ModeledEntity#setDisplayNameVisible(boolean)ModeledEntity#setLeftClickCallback(...)ModeledEntity#setRightClickCallback(...)ModeledEntity#setHitboxContactCallback(...)ModeledEntity#setModeledEntityHitByProjectileCallback(...)ModeledEntity#playAnimation(String, boolean, boolean)ModeledEntity#stopCurrentAnimations()ModeledEntity#hasAnimation(String)ModeledEntity#getEntityID()-- devuelve el string del ID del modeloModeledEntity#getLocation()-- devuelve laLocationactualModeledEntity#getWorld()-- devuelve elWorldModeledEntity#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 hitboxBone#getBoneLocation()
Superficie de Eventos
Eventos de interacción genéricos:
ModeledEntityLeftClickEventModeledEntityRightClickEventModeledEntityHitboxContactEventModeledEntityHitByProjectileEvent
También existen variantes tipadas para StaticEntity, DynamicEntity y PropEntity.
Eventos de ciclo de vida:
-
ResourcePackGenerationEvent-- se dispara cuando FMM termina de generar o regenerar el resource pack (al iniciar y con/fmm reload). Escucha esto para desencadenar postprocesado sobre el pack generado. -
FmmReloadedEvent-- se dispara después de que FMM termine su secuencia de inicialización al iniciar y después de cada/fmm reload. Siempre se dispara en el hilo principal del servidor.Los plugins consumidores que mantienen referencias
DynamicEntityoPropEntityde 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 duranteonDisablemientras 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:
- El daño de flecha se calcula con soporte para el bono del encantamiento
POWER - Se respeta el encantamiento
PIERCING-- las flechas pueden atravesar y alcanzar objetivos adicionales - Cancela el evento para prevenir el daño y bloquear el impacto por completo
@EventHandler
public void onProjectileHitModel(ModeledEntityHitByProjectileEvent event) {
ModeledEntity target = event.getModeledEntity();
Projectile projectile = event.getProjectile();
// Cancelar para prevenir el daño
event.setCancelled(true);
}
Utilidades de Objeto y Modelo
ModelItemFactory
Clase factoría para crear ItemStacks relacionados con modelos de forma programática.
// Crea un objeto de colocación de prop (usa la clave PDC "model_id")
ItemStack placementItem = ModelItemFactory.createModelItem("lamp_post", Material.STICK);
// Crea un objeto personalizado desde la configuración (usa la clave PDC "fmm_item_id")
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.
// Comprueba si un modelo tiene un JSON de modelo de visualización registrado
boolean has3D = DisplayModelRegistry.hasDisplayModel("magic_sword");
register(String modelId)-- registra un ID de modelo (llamado internamente durante la recarga)hasDisplayModel(String modelId)-- devuelvetruesi existe un.jsonde modelo de visualización para este modelogetRegisteredModels()-- devuelve unSet<String>inmutable de todos los IDs de modelo que tienen modelos de visualización registradosshutdown()-- borra todos los registros
ItemScriptManager
Gestiona el ciclo de vida de los scripts Lua por jugador para objetos personalizados (modelos con material: establecido en su configuración YML).
// Obtener todas las definiciones de objeto personalizado registradas
Map<String, PropScriptConfigFields> items = ItemScriptManager.getItemDefinitions();
// Obtener la instancia de script Lua activa para un par jugador + objeto
ScriptInstance instance = ItemScriptManager.getActiveScript(playerUUID, "magic_sword");
// Obtener todos los scripts activos para un jugador
Map<String, ScriptInstance> scripts = ItemScriptManager.getActiveScripts(playerUUID);
scanForCustomItems(File modelsFolder)-- escanea las configuraciones YML del modelo en busca de objetos personalizadosupdateEquippedScripts(Player player)-- compara los objetos equipados con los scripts en ejecución, disparando los hooks de equip/unequipremovePlayer(Player player)-- cierra todos los scripts para un jugador (llamar al salir)getItemDefinitions()-- devuelve el mapa de ID de objeto aPropScriptConfigFields
ScriptedItemAPI
API pública para que los plugins externos se integren con el sistema de objetos con script de FMM. Esto permite que otros plugins marquen sus propios ItemStacks con datos de objeto-script de FMM (etiqueta PDC + modelo de objeto) para que los hooks de script Lua de FMM se disparen para esos objetos, sin que FMM sobrescriba el nombre, lore o encantamientos del objeto.
// Comprueba si existe una definición de objeto con script
boolean exists = ScriptedItemAPI.isValidItemId("flame_blade");
// Aplica datos de objeto con script de FMM a un ItemStack existente
// Esto establece:
// - La etiqueta PDC fmm_item_id (para que el sistema de scripts de FMM reconozca el objeto)
// - El modelo de objeto (1.21.4+) desde el registro de modelos de visualización de FMM
// NO modifica nombre, lore, encantamientos ni ninguna otra propiedad del objeto.
boolean success = ScriptedItemAPI.applyScriptedItemData(itemStack, "flame_blade");
// Obtener la configuración para un objeto con script
PropScriptConfigFields config = ScriptedItemAPI.getItemConfig("flame_blade");
isValidItemId(String itemId)-- devuelvetruesi el ID del objeto está registrado en las definiciones de objeto de FMMapplyScriptedItemData(ItemStack itemStack, String itemId)-- marca la etiqueta PDC y el modelo de objeto en un ItemStack existente. Devuelvetrueen caso de éxito,falsesi el ID del objeto es inválido o el ItemStack no tiene meta. Nota sobre arco/ballesta: si elitemIddado no tiene modelo de visualización peroitemId + "_idle"sí (es decir, el objeto tiene modelos de estado de arco/ballesta), el método usa automáticamente el modelo_idlecomo modelo de visualizacióngetItemConfig(String itemId)-- devuelve losPropScriptConfigFieldspara el ID de objeto dado, onullsi no se encuentra
EliteMobs usa esta API internamente a través del campo de configuración scriptedItem. Cuando un objeto personalizado de EliteMobs establece scriptedItem: flame_blade, EliteMobs construye su objeto normalmente (nombre, lore, encantamientos, nivel) y luego llama a ScriptedItemAPI.applyScriptedItemData() para añadir encima el modelo y el comportamiento de script de FMM.
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;
// Disfraza a un jugador como un modelo cargado. Reemplaza cualquier disfraz existente limpiamente.
boolean ok = DisguiseAPI.disguise(player, "dragon");
// Quita el disfraz (devuelve true si se eliminó un disfraz).
DisguiseAPI.undisguise(player);
// Consultar estado.
boolean disguised = DisguiseAPI.isDisguised(player);
String modelID = DisguiseAPI.getDisguiseModelID(player); // null si no está disfrazado
// Snapshot de todos los jugadores disfrazados actualmente.
Collection<Player> all = DisguiseAPI.getDisguisedPlayers();
disguise(Player, String modelID)-- devuelvefalsesi el ID del modelo no está cargadoundisguise(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
Los jugadores disfrazados se vuelven invisibles para los demás y permanecen así hasta que se les quita el disfraz — los cubos de leche, los clears de efectos por baliza y otras interacciones similares no rompen la invisibilidad.
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;
// En el enable de tu plugin, después de que WorldGuard/EliteMobs/etc. estén disponibles.
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 protegida
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.
PropScriptConfigFields
Clase de configuración unificada para los archivos de configuración YML del modelo. Usada tanto por scripts de prop como por objetos personalizados.
# Ejemplo: torch_01.yml
isEnabled: true
scripts:
- torch_glow.lua
material: STICK # Si se establece, el modelo se convierte en objeto personalizado
name: "&eMagic Torch" # Nombre de visualización personalizado (opcional)
lore: # Líneas de lore personalizadas (opcional)
- "&7Glows in the dark"
enchantments: # Encantamientos (opcional, formato: NAME,LEVEL)
- "FIRE_ASPECT,1"
Métodos clave: isCustomItem(), getParsedMaterial(), getParsedEnchantments(), getScripts().
Scripting Lua
FreeMinecraftModels soporta scripts Lua tanto para props como para objetos personalizados a través del motor de scripting MagmaCore 2.0. Los archivos de script se colocan en plugins/FreeMinecraftModels/scripts/ y se vinculan a modelos a través de un YML compañero junto al archivo del modelo.
Hooks de Script de Prop
| 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 la zona del prop |
on_zone_leave | Un jugador sale de la zona del prop |
on_destroy | El prop es eliminado |
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 |
Hooks de Script de Objeto
Los objetos personalizados (modelos con material: establecido) soportan 22 hooks Lua:
| Hook | Activador |
|---|---|
on_equip | El objeto entra en una ranura de equipamiento rastreada |
on_unequip | El objeto sale de una ranura de equipamiento rastreada |
on_game_tick | Cada tick mientras el objeto está equipado |
on_attack_entity | El jugador ataca una entidad mientras sostiene el objeto |
on_kill_entity | El jugador mata una entidad mientras sostiene el objeto |
on_take_damage | El jugador recibe daño mientras el objeto está equipado |
on_shield_block | El jugador bloquea con un escudo |
on_shoot_bow | El jugador dispara un arco |
on_projectile_hit | Un proyectil disparado por el jugador impacta algo |
on_projectile_launch | El jugador lanza un proyectil |
on_right_click | El jugador hace clic derecho con el objeto |
on_left_click | El jugador hace clic izquierdo con el objeto |
on_shift_right_click | El jugador hace shift-clic derecho con el objeto |
on_shift_left_click | El jugador hace shift-clic izquierdo con el objeto |
on_interact_entity | El jugador hace clic derecho en una entidad con el objeto |
on_swap_hands | El jugador cambia el objeto entre manos |
on_drop | El jugador suelta el objeto |
on_break_block | El jugador rompe un bloque mientras sostiene el objeto |
on_consume | El jugador consume el objeto |
on_item_damage | El objeto recibe daño de durabilidad |
on_fish | El jugador usa una caña de pescar |
on_death | El jugador muere mientras el objeto está equipado |
Los scripts de objeto reciben context.item (con el ID del objeto e información del jugador) en lugar de context.prop.
Tabla Context del Script de Prop
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)-- reproduce la animación nombrada (blend y loop por defecto atrue)stop_animation()-- detiene todas las animaciones actualeshurt_visual()-- reproduce el visual de daño (destello rojo) en el proppickup()-- elimina el prop y suelta su objeto de colocaciónmount(player)-- hace que un jugador monte el propdismount(player)-- desmonta a un jugador del propget_passengers()-- devuelve una lista de jugadores que están montando el prop actualmentespawn_elitemobs_boss(filename, x, y, z)-- genera un jefe de EliteMobs relativo al prop
context.event:
- Disponible en
on_left_click,on_right_clickyon_projectile_hit cancel(),uncancel(),is_cancelledplayer-- el jugador que desencadenó el evento
context.world:
spawn_entity(entity_type, location)-- genera una entidad vanillaset_block_at(location, material)-- establece un bloque en el mundo- Además partículas, sonidos, consultas de bloque, rayos y búsquedas de entidades cercanas
Objetos de jugador (desde context.event.player):
get_held_item()-- devuelve el objeto que el jugador sostieneconsume_held_item()-- elimina uno del objeto sostenidohas_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
Ejemplo de Script de Prop
function on_spawn(context)
context.prop.play_animation("idle", true, true)
end
function on_right_click(context)
context.prop.play_animation("activate", false, false)
end
Ejemplo de Script de Objeto
function on_equip(context)
context.event.player.send_message("&6You equipped the Flame Blade!")
end
function on_attack_entity(context)
-- Efecto de fuego al impactar
context.event.player.send_message("&cBurn!")
end
function on_unequip(context)
context.event.player.send_message("&7Flame Blade sheathed.")
end
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. - Todos los plugins del ecosistema Nightbreak ahora dependen de MagmaCore 2.2.0-SNAPSHOT, que incluye el motor de scripting Lua compartido usado por los scripts de prop de FreeMinecraftModels y los poderes Lua de EliteMobs, además del
LocationQueryRegistryyWorldFolderResolvercompartidos. - FreeMinecraftModels declara WorldGuard, WorldEdit, GriefPrevention y Vault como
softdepend. Ninguno es necesario para iniciar el plugin, pero desbloquean funciones específicas: WorldGuard/WorldEdit/GriefPrevention alimentanLocationAPI, y Vault habilita la tienda de muebles.