Skip to main content

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 models
  • LocationAPI — register dungeon detectors and protection providers (feeds Lua em.location.* predicates)
  • ScriptedItemAPI — stamp third-party ItemStacks with FMM scripted-item metadata

Core Runtime Types

  • ModeledEntity
  • StaticEntity
  • DynamicEntity
  • PropEntity

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 string
  • ModeledEntity#getLocation() -- returns the current Location
  • ModeledEntity#getWorld() -- returns the World
  • ModeledEntity#getViewers() -- returns the HashSet<UUID> of players who can see the entity
  • ModeledEntity#getNametagBones() -- returns List<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 animation
  • ModeledEntity#remove() -- immediately removes the entity and all bones
  • ModeledEntity#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. Pass null to clear.
  • ModeledEntity#setViewDistanceOverride(int) / getEffectiveViewDistance() -- overrides DefaultConfig.maxModelViewDistance for a single entity. Pass -1 to revert to the plugin-wide default.
  • DynamicEntity#setSyncMovement(boolean)
  • DynamicEntity#isDamagesOnContact() / setDamagesOnContact(boolean) -- controls whether the entity damages players via hitbox contact
  • Bone#getBoneLocation()

Event Surface

Generic interaction events:

  • ModeledEntityLeftClickEvent
  • ModeledEntityRightClickEvent
  • ModeledEntityHitboxContactEvent
  • ModeledEntityHitByProjectileEvent

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 DynamicEntity or PropEntity references (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 during onDisable while 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 POWER enchantment bonus
  • PIERCING enchantment 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 like 01_em_flame_sword into Flame 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) -- returns true if a .json display model exists for this model
  • getRegisteredModels() -- returns an immutable Set<String> of all model IDs that have display models registered
  • shutdown() -- 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 items
  • updateEquippedScripts(Player player) -- diffs equipped items against running scripts, firing equip/unequip hooks
  • removePlayer(Player player) -- shuts down all scripts for a player (call on quit)
  • getItemDefinitions() -- returns the map of item ID to PropScriptConfigFields

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) -- returns true if the item ID is registered in FMM's item definitions
  • applyScriptedItemData(ItemStack itemStack, String itemId) -- stamps PDC tag and item model onto an existing ItemStack. Returns true on success, false if the item ID is invalid or the ItemStack has no meta. Bow/crossbow note: if the given itemId does not have a display model but itemId + "_idle" does (i.e. the item has bow/crossbow state models), the method automatically uses the _idle model as the display model
  • getItemConfig(String itemId) -- returns the PropScriptConfigFields for the given item ID, or null if not found
EliteMobs Integration

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) -- returns false if the model ID is not loaded
  • undisguise(Player) -- returns true if a disguise was removed
  • isDisguised(Player) -- quick boolean check
  • getDisguiseModelID(Player) -- returns the active model ID or null
  • getDisguisedPlayers() -- 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 returning true flags the location as "in dungeon"
  • registerProtectionProvider(String providerName, Predicate<Location> predicate) — any registered predicate returning true flags 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

HookTrigger
on_spawnProp is spawned into the world
on_game_tickEvery tick while the prop is alive
on_zone_enterA player enters a script-created watched zone
on_zone_leaveA player leaves a script-created watched zone
on_destroyProp is removed
on_left_clickPlayer left-clicks the prop
on_right_clickPlayer right-clicks the prop
on_projectile_hitReserved: 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:

HookTrigger
on_equipItem enters a tracked equipment slot
on_unequipItem leaves a tracked equipment slot
on_game_tickEvery tick while the item is equipped
on_attack_entityPlayer attacks an entity while holding the item
on_kill_entityPlayer kills an entity while holding the item
on_take_damagePlayer takes damage while item is equipped
on_shield_blockPlayer blocks with a shield
on_shoot_bowPlayer shoots a bow
on_projectile_hitA projectile fired by the player hits something
on_projectile_launchPlayer launches a projectile
on_right_clickPlayer right-clicks with the item
on_left_clickPlayer left-clicks with the item
on_shift_right_clickPlayer shift-right-clicks with the item
on_shift_left_clickPlayer shift-left-clicks with the item
on_interact_entityPlayer right-clicks an entity with the item
on_swap_handsPlayer swaps the item between hands
on_dropPlayer drops the item
on_break_blockPlayer breaks a block while holding the item
on_consumePlayer consumes the item
on_item_damageItem takes durability damage
on_fishPlayer uses a fishing rod
on_deathPlayer 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 name
  • current_location -- the prop's current location
  • play_animation(name, blend, loop) -- plays the named animation (blend and loop default to true)
  • stop_animation() -- stops all current animations
  • hurt_visual() -- plays the hurt (red flash) visual on the prop
  • pickup() -- queues removing the prop and dropping its placement item
  • mount(player) -- queues a mount attempt; a true return means the player and mount manager were valid, not that a seat was ultimately assigned
  • dismount(player) -- queues a dismount check; a true return means the player and mount manager were valid
  • get_passengers() -- returns a list of players currently riding the prop
  • spawn_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, and on_zone_leave, plus item hooks that are caused by a player
  • cancel(), uncancel(), is_cancelled when the underlying hook is cancellable
  • player -- the player who triggered the event

context.world:

  • spawn_entity(entity_type, x, y, z) -- spawns a vanilla entity, or returns nil if the entity type is invalid
  • set_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 cooldown
  • global_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; type is the uppercase Bukkit material name
  • consume_held_item() -- removes one of the held item
  • has_item(material) -- checks if the player has an item
  • send_message(text) -- sends a chat message to the player
  • game_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 LocationQueryRegistry and WorldFolderResolver.
  • FreeMinecraftModels declares WorldGuard, WorldEdit, GriefPrevention, and Vault as softdepend. None are required to start the plugin, but they unlock specific features: WorldGuard/WorldEdit/GriefPrevention feed LocationAPI, and Vault enables the furniture shop.