FreeMinecraftModels API and Developer Guide
FreeMinecraftModels is both a standalone plugin and an API surface for other plugins.
Maven Repository
<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>
Dependency
<dependency>
<groupId>com.magmaguy</groupId>
<artifactId>FreeMinecraftModels</artifactId>
<version>LATEST.VERSION.HERE</version>
<scope>provided</scope>
</dependency>
Use it as compileOnly/provided. Do not shade the plugin into your own jar.
Core Entry Points
ModeledEntityManager.modelExists(String)ModeledEntityManager.reload()ModeledEntityManager.getAllEntities()ModeledEntityManager.getDynamicEntities()ModeledEntityManager.propEntities()DisguiseAPI— disguise / undisguise players as loaded modelsLocationAPI— register dungeon detectors and protection providers (feeds Luaem.location.*predicates)ScriptedItemAPI— stamp third-party ItemStacks with FMM scripted-item metadata
Core Runtime Types
ModeledEntityStaticEntityDynamicEntityPropEntity
Creating Entities
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);
All creation paths return null if the requested model ID is not loaded.
createWithInvisibility is a variant that applies an invisibility potion instead of hiding the entity from clients. This keeps the entity tracked client-side, which is needed for vehicle steering (used internally by /fmm mount).
Useful Runtime Methods
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()-- returns the model ID stringModeledEntity#getLocation()-- returns the currentLocationModeledEntity#getWorld()-- returns theWorldModeledEntity#getViewers()-- returns theHashSet<UUID>of players who can see the entityModeledEntity#getNametagBones()-- returnsList<Bone>of nametag bones (useful for placing additional text)ModeledEntity#getScaleModifier()/setScaleModifier(double)ModeledEntity#removeWithDeathAnimation()-- removes with the death animation (if one exists)ModeledEntity#removeWithMinimizedAnimation()-- removes with a scaling-down animationModeledEntity#remove()-- immediately removes the entity and all bonesModeledEntity#setTintColor(Color)/getTintColor()-- applies a persistent tint via the leather-armor dye channel. Damage flashes briefly override the tint and then fade back to it. Passnullto clear.ModeledEntity#setViewDistanceOverride(int)/getEffectiveViewDistance()-- overridesDefaultConfig.maxModelViewDistancefor a single entity. Pass-1to revert to the plugin-wide default.DynamicEntity#setSyncMovement(boolean)DynamicEntity#isDamagesOnContact()/setDamagesOnContact(boolean)-- controls whether the entity damages players via hitbox contactBone#getBoneLocation()
Event Surface
Generic interaction events:
ModeledEntityLeftClickEventModeledEntityRightClickEventModeledEntityHitboxContactEventModeledEntityHitByProjectileEvent
Typed variants also exist for StaticEntity, DynamicEntity, and PropEntity.
Lifecycle events:
-
ResourcePackGenerationEvent-- fires when FMM finishes generating or regenerating the resource pack (on startup and on/fmm reload). Listen for this to trigger post-processing on the generated pack. -
FmmReloadedEvent-- fires after FMM finishes its initialization sequence on startup and after every/fmm reload. Always fires on the main server thread.Consumer plugins that hold long-lived
DynamicEntityorPropEntityreferences (EliteMobs, BetterStructures, etc.) must handle this event by re-creating their model attachments on the surviving underlying entities. Without this, those entities go invisible after a reload because FMM tore down the display entities duringonDisablewhile the consumer's reference is now stale.@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 And OBB Projectile Detection
FMM uses OBB (oriented bounding box) hit detection for projectiles against modeled entities. When a projectile intersects a modeled entity's OBB hitbox, FMM fires a ModeledEntityHitByProjectileEvent. This is a standard cancellable Bukkit event.
Key details:
- Arrow damage is calculated with support for the
POWERenchantment bonus PIERCINGenchantment is respected -- arrows can pass through to additional targets- Cancel the event to prevent damage and block the hit entirely
@EventHandler
public void onProjectileHitModel(ModeledEntityHitByProjectileEvent event) {
ModeledEntity target = event.getModeledEntity();
Projectile projectile = event.getProjectile();
// Cancel to prevent damage
event.setCancelled(true);
}
Item & Model Utilities
ModelItemFactory
Factory class for creating model-related ItemStacks programmatically.
// 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)-- creates a placement item for props. On 1.21.4+, automatically applies display model rendering if a display JSON exists.createCustomItem(String itemId, PropScriptConfigFields config)-- creates a custom item with name, lore, enchantments, and display model from the unified config.formatModelName(String modelId)-- utility that converts a model ID like01_em_flame_swordintoFlame Sword.
DisplayModelRegistry
Simple registry that tracks which models have a display JSON available.
// Check if a model has a display model JSON registered
boolean has3D = DisplayModelRegistry.hasDisplayModel("magic_sword");
register(String modelId)-- registers a model ID (called internally during reload)hasDisplayModel(String modelId)-- returnstrueif a.jsondisplay model exists for this modelgetRegisteredModels()-- returns an immutableSet<String>of all model IDs that have display models registeredshutdown()-- clears all registrations
ItemScriptManager
Manages the lifecycle of per-player Lua scripts for custom items (models with material: set in their YML config).
// Get all registered custom item definitions
Map<String, PropScriptConfigFields> items = ItemScriptManager.getItemDefinitions();
// Get the active Lua script instance for a player + item
ScriptInstance instance = ItemScriptManager.getActiveScript(playerUUID, "magic_sword");
// Get all active scripts for a player
Map<String, ScriptInstance> scripts = ItemScriptManager.getActiveScripts(playerUUID);
scanForCustomItems(File modelsFolder)-- scans model YML configs for custom itemsupdateEquippedScripts(Player player)-- diffs equipped items against running scripts, firing equip/unequip hooksremovePlayer(Player player)-- shuts down all scripts for a player (call on quit)getItemDefinitions()-- returns the map of item ID toPropScriptConfigFields
ScriptedItemAPI
Public API for external plugins to integrate with FMM's scripted item system. This allows other plugins to stamp their own ItemStacks with FMM scripted item data (PDC tag + item model) so that FMM's Lua script hooks fire for those items, without FMM overriding the item's name, lore, or enchantments.
// Check if a scripted item definition exists
boolean exists = ScriptedItemAPI.isValidItemId("flame_blade");
// Apply FMM scripted item data to an existing ItemStack
// This sets:
// - The fmm_item_id PDC tag (so FMM's script system recognizes the item)
// - The item model (1.21.4+) from FMM's display model registry
// Does NOT modify name, lore, enchantments, or any other item properties.
boolean success = ScriptedItemAPI.applyScriptedItemData(itemStack, "flame_blade");
// Get the config for a scripted item
PropScriptConfigFields config = ScriptedItemAPI.getItemConfig("flame_blade");
isValidItemId(String itemId)-- returnstrueif the item ID is registered in FMM's item definitionsapplyScriptedItemData(ItemStack itemStack, String itemId)-- stamps PDC tag and item model onto an existing ItemStack. Returnstrueon success,falseif the item ID is invalid or the ItemStack has no meta. Bow/crossbow note: if the givenitemIddoes not have a display model butitemId + "_idle"does (i.e. the item has bow/crossbow state models), the method automatically uses the_idlemodel as the display modelgetItemConfig(String itemId)-- returns thePropScriptConfigFieldsfor the given item ID, ornullif not found
EliteMobs uses this API internally via the scriptedItem config field. When an EliteMobs custom item sets scriptedItem: flame_blade, EliteMobs builds its item normally (name, lore, enchantments, level) then calls ScriptedItemAPI.applyScriptedItemData() to add FMM's model and script behavior on top.
DisguiseAPI
Public entry point for the player-disguise feature. Third-party plugins should call this class rather than the internal DisguiseManager so internal refactors stay safe.
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)-- returnsfalseif the model ID is not loadedundisguise(Player)-- returnstrueif a disguise was removedisDisguised(Player)-- quick boolean checkgetDisguiseModelID(Player)-- returns the active model ID ornullgetDisguisedPlayers()-- unmodifiable snapshot of disguised players
Disguised players are made invisible to others and stay that way until undisguised — milk buckets, beacon effect clears, and similar interactions do not break the invisibility.
LocationAPI
Public API for plugins to contribute dungeon detection and region-protection checks. The registered predicates feed FMM's Lua em.location.is_in_dungeon and em.location.is_protected checks (used by premade scripts like pickupable.lua and storage_double.lua).
Plugins pass a plain Predicate<Location> so no shaded FMM types cross plugin classloaders.
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)— any registered predicate returningtrueflags the location as "in dungeon"registerProtectionProvider(String providerName, Predicate<Location> predicate)— any registered predicate returningtrueflags the location as protected
Operators can verify registration with /fmm location, which reports the live provider count and tests both predicates against their current location.
PropScriptConfigFields
Unified configuration class for model YML config files. Used by both prop scripts and custom items.
# Example: torch_01.yml
isEnabled: true
scripts:
- torch_glow.lua
material: STICK # If set, model becomes a custom item
name: "&eMagic Torch" # Custom display name (optional)
lore: # Custom lore lines (optional)
- "&7Glows in the dark"
enchantments: # Enchantments (optional, format: NAME,LEVEL)
- "FIRE_ASPECT,1"
Key methods: isCustomItem(), getParsedMaterial(), getParsedEnchantments(), getScripts().
Lua Scripting
FreeMinecraftModels supports Lua scripts for both props and custom items through the MagmaCore 2.0 scripting engine. Script files are placed in plugins/FreeMinecraftModels/scripts/ and are bound to models via a sibling YML config next to the model file. The script file on disk must end in .lua; config entries may include the extension or omit it.
Props bind every script listed in scripts: as independent instances. Custom items currently bind only the first valid script in the list for each player/item pair.
Prop Script Hooks
| Hook | Trigger |
|---|---|
on_spawn | Prop is spawned into the world |
on_game_tick | Every tick while the prop is alive |
on_zone_enter | A player enters a script-created watched zone |
on_zone_leave | A player leaves a script-created watched zone |
on_destroy | Prop is removed |
on_left_click | Player left-clicks the prop |
on_right_click | Player right-clicks the prop |
on_projectile_hit | Reserved: accepted by validation, but not dispatched to prop scripts in the current runtime |
Item Script Hooks
Custom items (models with material: set) support 22 Lua hooks:
| Hook | Trigger |
|---|---|
on_equip | Item enters a tracked equipment slot |
on_unequip | Item leaves a tracked equipment slot |
on_game_tick | Every tick while the item is equipped |
on_attack_entity | Player attacks an entity while holding the item |
on_kill_entity | Player kills an entity while holding the item |
on_take_damage | Player takes damage while item is equipped |
on_shield_block | Player blocks with a shield |
on_shoot_bow | Player shoots a bow |
on_projectile_hit | A projectile fired by the player hits something |
on_projectile_launch | Player launches a projectile |
on_right_click | Player right-clicks with the item |
on_left_click | Player left-clicks with the item |
on_shift_right_click | Player shift-right-clicks with the item |
on_shift_left_click | Player shift-left-clicks with the item |
on_interact_entity | Player right-clicks an entity with the item |
on_swap_hands | Player swaps the item between hands |
on_drop | Player drops the item |
on_break_block | Player breaks a block while holding the item |
on_consume | Player consumes the item |
on_item_damage | Item takes durability damage |
on_fish | Player uses a fishing rod |
on_death | Player dies while item is equipped |
Item scripts receive context.item (with the item ID and player info) instead of context.prop.
Prop Script Context Table
Prop scripts receive a context table. Here is a summary of the key APIs -- see Lua Prop API for full details.
context.prop:
model_id-- the blueprint model namecurrent_location-- the prop's current locationplay_animation(name, blend, loop)-- plays the named animation (blend and loop default totrue)stop_animation()-- stops all current animationshurt_visual()-- plays the hurt (red flash) visual on the proppickup()-- queues removing the prop and dropping its placement itemmount(player)-- queues a mount attempt; atruereturn means the player and mount manager were valid, not that a seat was ultimately assigneddismount(player)-- queues a dismount check; atruereturn means the player and mount manager were validget_passengers()-- returns a list of players currently riding the propspawn_elitemobs_boss(filename, x, y, z)-- spawns an EliteMobs boss at absolute coordinates in the prop's current world
context.event:
- Available in prop
on_left_click,on_right_click,on_zone_enter, andon_zone_leave, plus item hooks that are caused by a player cancel(),uncancel(),is_cancelledwhen the underlying hook is cancellableplayer-- the player who triggered the event
context.world:
spawn_entity(entity_type, x, y, z)-- spawns a vanilla entity, or returnsnilif the entity type is invalidset_block_at(x, y, z, material)-- queues a block change if the material is valid; unloaded chunks are skipped- Plus particles, sounds, block queries, lightning, and nearby-entity lookups
context.cooldowns:
check_local(key, ticks)-- checks and starts a per-script cooldownglobal_ready()/set_global(ticks)-- shared cooldown for the prop or player owner
Player objects (from context.player or context.event.player):
get_held_item()-- returns the item the player is holding;typeis the uppercase Bukkit material nameconsume_held_item()-- removes one of the held itemhas_item(material)-- checks if the player has an itemsend_message(text)-- sends a chat message to the playergame_mode-- the player's current game mode
Prop Script Example
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
}
Item Script Example
return {
api_version = 1,
on_equip = function(context)
context.player:send_message("&6You equipped the Flame Blade!")
end,
on_attack_entity = function(context)
-- Fire effect on hit
context.player:send_message("&cBurn!")
end,
on_unequip = function(context)
context.player:send_message("&7Flame Blade sheathed.")
end
}
Notes
- FreeMinecraftModels is an installed-plugin dependency, not an embeddable library.
- If your plugin needs freshly imported models, call
ModeledEntityManager.reload()instead of trying to rebuild FreeMinecraftModels state yourself. - All plugins in the Nightbreak ecosystem now depend on MagmaCore 2.2.0-SNAPSHOT, which includes the shared Lua scripting engine used by FreeMinecraftModels prop scripts and EliteMobs Lua powers, plus the shared
LocationQueryRegistryandWorldFolderResolver. - FreeMinecraftModels declares WorldGuard, WorldEdit, GriefPrevention, and Vault as
softdepend. None are required to start the plugin, but they unlock specific features: WorldGuard/WorldEdit/GriefPrevention feedLocationAPI, and Vault enables the furniture shop.