Saltar al contenido principal

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 cargados
  • LocationAPI — registrar detectores de mazmorras y proveedores de protección (alimenta los predicados Lua em.location.*)
  • ScriptedItemAPI — marcar ItemStacks de terceros con metadatos de objeto-script de FMM

Tipos Principales del Runtime

  • ModeledEntity
  • StaticEntity
  • DynamicEntity
  • PropEntity

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 modelo
  • ModeledEntity#getLocation() -- devuelve la Location actual
  • ModeledEntity#getWorld() -- devuelve el World
  • 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
  • Bone#getBoneLocation()

Superficie de Eventos

Eventos de interacción genéricos:

  • ModeledEntityLeftClickEvent
  • ModeledEntityRightClickEvent
  • ModeledEntityHitboxContactEvent
  • ModeledEntityHitByProjectileEvent

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

  • 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 como 01_em_flame_sword en Flame 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) -- devuelve true si existe un .json de modelo de visualización para este modelo
  • 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

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 personalizados
  • updateEquippedScripts(Player player) -- compara los objetos equipados con los scripts en ejecución, disparando los hooks de equip/unequip
  • removePlayer(Player player) -- cierra todos los scripts para un jugador (llamar al salir)
  • getItemDefinitions() -- devuelve el mapa de ID de objeto a PropScriptConfigFields

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) -- devuelve true si el ID del objeto está registrado en las definiciones de objeto de FMM
  • applyScriptedItemData(ItemStack itemStack, String itemId) -- marca la etiqueta PDC y el modelo de objeto en un ItemStack existente. Devuelve true en caso de éxito, false si el ID del objeto es inválido o el ItemStack no tiene meta. Nota sobre arco/ballesta: si el itemId dado no tiene modelo de visualización pero itemId + "_idle" sí (es decir, el objeto tiene modelos de estado de arco/ballesta), el método usa automáticamente el modelo _idle como modelo de visualización
  • getItemConfig(String itemId) -- devuelve los PropScriptConfigFields para el ID de objeto dado, o null si no se encuentra
Integración con EliteMobs

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) -- devuelve false si el ID del modelo no está cargado
  • 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

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

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

HookActivador
on_spawnEl prop aparece en el mundo
on_game_tickCada tick mientras el prop está activo
on_zone_enterUn jugador entra en la zona del prop
on_zone_leaveUn jugador sale de la zona del prop
on_destroyEl prop es eliminado
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

Hooks de Script de Objeto

Los objetos personalizados (modelos con material: establecido) soportan 22 hooks Lua:

HookActivador
on_equipEl objeto entra en una ranura de equipamiento rastreada
on_unequipEl objeto sale de una ranura de equipamiento rastreada
on_game_tickCada tick mientras el objeto está equipado
on_attack_entityEl jugador ataca una entidad mientras sostiene el objeto
on_kill_entityEl jugador mata una entidad mientras sostiene el objeto
on_take_damageEl jugador recibe daño mientras el objeto está equipado
on_shield_blockEl jugador bloquea con un escudo
on_shoot_bowEl jugador dispara un arco
on_projectile_hitUn proyectil disparado por el jugador impacta algo
on_projectile_launchEl jugador lanza un proyectil
on_right_clickEl jugador hace clic derecho con el objeto
on_left_clickEl jugador hace clic izquierdo con el objeto
on_shift_right_clickEl jugador hace shift-clic derecho con el objeto
on_shift_left_clickEl jugador hace shift-clic izquierdo con el objeto
on_interact_entityEl jugador hace clic derecho en una entidad con el objeto
on_swap_handsEl jugador cambia el objeto entre manos
on_dropEl jugador suelta el objeto
on_break_blockEl jugador rompe un bloque mientras sostiene el objeto
on_consumeEl jugador consume el objeto
on_item_damageEl objeto recibe daño de durabilidad
on_fishEl jugador usa una caña de pescar
on_deathEl 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 blueprint
  • current_location -- la ubicación actual del prop
  • play_animation(name, blend, loop) -- reproduce la animación nombrada (blend y loop por defecto a true)
  • stop_animation() -- detiene todas las animaciones actuales
  • hurt_visual() -- reproduce el visual de daño (destello rojo) en el prop
  • pickup() -- elimina el prop y suelta su objeto de colocación
  • mount(player) -- hace que un jugador monte el prop
  • dismount(player) -- desmonta a un jugador del prop
  • 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 relativo al prop

context.event:

  • Disponible en on_left_click, on_right_click y on_projectile_hit
  • cancel(), uncancel(), is_cancelled
  • player -- el jugador que desencadenó el evento

context.world:

  • spawn_entity(entity_type, location) -- genera una entidad vanilla
  • set_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 sostiene
  • consume_held_item() -- elimina uno del objeto sostenido
  • 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

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 LocationQueryRegistry y WorldFolderResolver compartidos.
  • FreeMinecraftModels declara WorldGuard, WorldEdit, GriefPrevention y Vault como softdepend. Ninguno es necesario para iniciar el plugin, pero desbloquean funciones específicas: WorldGuard/WorldEdit/GriefPrevention alimentan LocationAPI, y Vault habilita la tienda de muebles.