FreeMinecraftModels API and Developer Guide
Generated Java reference includes searchable classes, methods and signatures, with the source version and commit recorded for each module.
FreeMinecraftModels is both a standalone plugin and an API surface for other plugins.
For shared dependency and runtime setup, see the Java API index. The dependency example uses the published 2.12.9 release; the current source is 2.12.9. Match your dependency to the installed plugin.
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>2.12.9</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 authored-item identity and display dataItemConstructionSnapshot- capture item and display-model data on the server thread for item construction on another thread
ModeledEntityManager.getAllEntities(), getDynamicEntities(), and propEntities() return point-in-time copies. Mutating the returned set or map does not modify FMM's live registries. Use modelExists(String) for an existence check instead of retaining and repeatedly copying a full registry.
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.
PropEntity.spawnPropEntity additionally returns null when a prop of that same model is already loaded on the target block — the duplicate is refused rather than stacked, and a [FMM Props] Prevented duplicate prop spawn ... warning is logged. Always null-check its return value; a non-null return is the only confirmation the prop was actually created.
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) and lets the model's display bones ride the entity on Java clients; see displayBonesRideTheirEntity.
Useful Runtime Methods
ModeledEntity#setDisplayName(String)- sets the model-owned nameplateModeledEntity#setDisplayNameVisible(boolean)- controls nameplate visibilityModeledEntity#setLeftClickCallback(...)ModeledEntity#setRightClickCallback(...)ModeledEntity#setHitboxContactCallback(...)ModeledEntity#setModeledEntityHitByProjectileCallback(...)ModeledEntity#playAnimation(String, boolean blend, boolean loop)-- returnsfalsewhen the name matches neither a built-in state nor an animation in the model, or afterdeathwas requested. For a custom animation,blend = trueplays it on the action layer over idle or walk, andfalselets it take over the whole pose;looponly applies to custom animations. See Animation blendingModeledEntity#stopCurrentAnimations()-- ends custom animations and attacks and snaps the model to idle; does nothing once death has startedModeledEntity#hasAnimation(String)ModeledEntity#damage(double)/damage(Entity damager, double)/damage(Entity damager)/damage(Projectile)-- routed through the entity'sDamageableComponentModeledEntity#attack(LivingEntity)/attack(LivingEntity, double damage)ModeledEntity#teleport(Location, boolean teleportUnderlyingEntity)ModeledEntity#setUnderlyingEntity(Entity)/ModeledEntity.getModeledEntity(Entity)-- static reverse lookup from a Bukkit entity to the model attached to it, ornullModeledEntity#showUnderlyingEntity(Player)/hideUnderlyingEntity(Player)-- per-player visibility of the backing vanilla entityModeledEntity#getEntityID()-- returns the model ID stringModeledEntity#getModelInstanceId()-- per-instanceUUID, stable for the life of the modelModeledEntity#isRemoved()/isDying()-- lifecycle flagsModeledEntity#getLocation()-- returns the currentLocationModeledEntity#getSpawnLocation()-- returns theLocationthe model was created atModeledEntity#getWorld()-- returns theWorldModeledEntity#getSkeleton()/getSkeletonBlueprint()/getMountPointManager()-- runtime structure accessModeledEntity#getInteractionComponent()/getHitboxComponent()/getDamageableComponent()/getAnimationComponent()-- the component objects behind the convenience methods aboveModeledEntity.getLoadedModeledEntities()-- static live set of every loaded modeled entityModeledEntity#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 contactDynamicEntity.isDynamicEntity(Entity)/DynamicEntity.getDynamicEntity(Entity)-- static lookups from a Bukkit entityDynamicEntity#getBodyLocation()-- the entity's location facing the model's yaw. WithweightedTurningon, that yaw follows the mob's body yaw with turning inertia, so it can lag the mob while the model turns.getLocation()returns the same location while movement sync is onBone#getBoneLocation()
Light, lean, centring and clicks
ModeledEntity#setBrightnessOverride(int blockLight, int skyLight)-- lights every display bone at a fixed level, each0to15, instead of the light where the bone is.15, 15is fully bright. Viewers get the change with the next bone update. Java display bones only: Bedrock and armor-stand bones keep their world lightModeledEntity#clearBrightnessOverride()/hasBrightnessOverride()/getBrightnessOverrideBlockLight()/getBrightnessOverrideSkyLight()-- drop or read the fixed light. The getters return-1without an overrideDynamicEntity#setTravelOrientation(TravelOrientation)/getTravelOrientation()-- leans the model with its travel.TravelOrientationis a record of aStyle(UPRIGHT,TOP_FIRST,FRONT_FIRST),startSpeed,fullSpeed,maxTiltandturnSpeed;TravelOrientation.UPRIGHTis the default andnullrestores it. See Travel orientationModeledEntity#setBodyTilt(Quaterniondc)/getBodyTilt()-- leans the whole model by a world-space rotation about the middle of its cubes, after its facing and its animations.nullstands it upright. A travel orientation other thanUPRIGHTsets this every tick; on an upright model a tilt you set staysModeledEntity#setCenteredOnBody(boolean)/isCenteredOnBody()/getBodyOffset()-- draws the model with the middle of its cubes on the middle of its entity. See Centring on the bodyDynamicEntity#setClickSurface(boolean)/isClickSurface()-- whentrue, clients get an invisible Interaction entity over the model, so players click the model they see rather than the body's own box. It takes the size of the model'shitboxbone, or of the model's own bounds when it has none, and applies within two ticks. Off by default, because a box covering a long or rotated model also catches clicks in its empty cornersDynamicEntity#isSteerableMount()/hasSteeringRider()/seatSteeringRider(Player)-- a model on a saddled horse is a steerable mount.seatSteeringRiderseats the player on the horse itself as its steering rider when nobody rides it, and returns whether they now ride it. A right-click on such a model does this by defaultDynamicEntity#liftSaddleTo(Bone)-- scales the mob so its saddle lands on the given seat bone at the model's rest pose, while the model keeps its size./fmm mountuses it with the model's firstm_bone. It does nothing when the server cannot report the saddle height
Nameplates And Look Targets
ModeledEntity now exposes setDisplayNameLines(List<String>), setDisplayNameScale(float), setDisplayNameLineGap(double), setDisplayNameVisible(boolean), and getNameplateLocation(). A tag bone is optional. Without one, the nameplate sits a quarter block (four pixels) above the model's highest cube at rest, scaled with the entity.
Multiline names:
setDisplayName("Top line\nBottom line")andsetDisplayNameLines(List<String>)take rows in reading order, top to bottom. The bottom row stays at the anchor and extra rows extend upward. Explicit newlines define the rows; text does not wrap.- An empty name hides the nameplate.
setDisplayNameVisible(false)hides every row and stays in effect when the text changes. setDisplayNameScale(float)scales the text independently of the model's size.setDisplayNameLineGap(double)sets the clear gap between rows, in blocks at scale 1,0.1by default. A row is0.225blocks tall at scale 1, so successive rows sit0.325blocks apart by default.- When a model has several
tag_bones, the alphabetically first one anchors the nameplate, including its movement and animation. - Java players see text displays, which scale both the glyphs and the spacing. Bedrock players see separate marker armor-stand labels, which cannot scale, so their spacing stays at scale 1. Choosing the Bedrock labels needs Floodgate or Geyser on the backend server to identify Bedrock players; a proxy-only setup without identity forwarding cannot reliably choose them.
- Viewer range, invisibility, removal and reload follow the model. Unchanged text and positions send no packets.
For how EliteMobs lays out the name and role of an NPC that uses a FreeMinecraftModels model, see Creating NPCs.
DynamicEntity.setLookTarget(Location) gives the skeleton a copied visual look target without rotating or moving the backing entity. Pass null to release it.
PropEntity specifics
PropEntity.isPropEntity(ArmorStand)/PropEntity.getPropEntityID(ArmorStand)-- identify a prop from its backing armor standPropEntity.hasLoadedPropOnSameBlock(String entityID, Location)-- the duplicate checkspawnPropEntityruns internally; call it first if you want to branch instead of null-checkingPropEntity.respawnPropEntityFromArmorStand(String entityID, ArmorStand)-- rebuilds a prop around an armor stand that survived a chunk reloadPropEntity.getPropEntities()-- snapshot of loaded props keyed by armor-stand UUID; changing it does not modify the live registryPropEntity#setPersistent(boolean)-- toggles persistence on the backing armor standPropEntity#setCustomDataString(NamespacedKey, String)/getCustomDataString(NamespacedKey)-- read/write your own PDC values on the prop. This is the same store the Luaset_persistent_data/get_persistent_datahelpers use, under thefmm_lua_<key>namespacePropEntity#remove()/remove(boolean showRealBlocks)-- removes the model but leaves the persistent entryPropEntity#permanentlyRemove()-- removes the model and its persistent entryPropEntity#setVoxelizeConfig(boolean voxelize, boolean solidify)/applySolidify()-- the runtime equivalents of thevoxelize:/solidify:YML fieldsPropEntity#showFakePropBlocksToPlayer(Player)/showRealBlocksToPlayer(Player)and the...ToAllPlayers()variants -- control the packet-only barrier blocks that give a solidified prop client-side collision
Magic weapon integrations
Use MagicWeaponAPI.isOperational() before depending on the magic service, isWeapon(String) to check an authored ID, and applyWeaponData(ItemStack, String) to apply its weapon identity and display without replacing the host's name, lore, level, or enchantments. Service registration alone is not proof that it is operational.
An optional MagicAttackResolver can reject casts, filter/rank targets, capture launch-time combat facts, and resolve damage. It must use the supplied one-shot MagicDamageApplication, not damage the target directly. FMM owns input, projectile travel, collision, and damage application.
MagicProjectileTravelEvent is a synchronous, cancellable swept-segment collision query, including an impact-time check. Cancellation absorbs the projectile without damage or an explosion. A segment may be checked more than once, so listeners must not treat it as a damage notification.
Event Surface
Right-click inputs first fire ModeledEntityInteractEvent against the real backing entity. It inherits PlayerInteractEntityEvent handlers so ordinary entity-protection listeners can cancel it. Cancellation prevents the modeled right-click event and callback. This check is distinct from permission to place or break blocks.
How the right-click checks fit together:
- Block and model interactions are decided separately. Cancelling a
PlayerInteractEventkeeps the block or item action cancelled, but the right-click can still select a model through FMM's reach and block-occlusion checks. Left-click cancellation works as before. ModeledEntityInteractEventuses the main hand. FMM firesModeledEntityRightClickEventonly after that dispatch finishes, and a cancelledModeledEntityRightClickEventstops the model callback, prop behavior and default mounting.- The bridge event is a permission check, not another click. FMM's own input, magic weapon and prop routers ignore it. A plugin that performs actions on native entity-interaction events can skip
ModeledEntityInteractEventto avoid acting twice; protection listeners should keep handling it. - A model without a backing Bukkit entity cannot fire a Bukkit entity event. It still fires the cancellable
ModeledEntityRightClickEvent. Persistent props and EliteMobs NPCs have backing entities and get both checks.
Generic interaction events:
ModeledEntityLeftClickEventModeledEntityRightClickEventModeledEntityHitboxContactEventModeledEntityHitByProjectileEvent
All four are cancellable Bukkit events; FMM's built-in detection paths dispatch them on the primary server thread. Cancelling one prevents FMM's matching callback/default behavior. Hitbox-contact scans run every two server ticks; click events still have a short per-player deduplication window so the packet-interaction and OBB-raytrace paths cannot fire the same action twice.
Lifecycle events:
FmmReloadedEvent-- scheduled on the main server thread 40 ticks after successful full initialization, including startup and a successful full reload. The narrower imported-content reload emits it on success too. A request rejected during candidate preparation does not tear down the active runtime or emit a completed-reload event.
The public interaction surface is intentionally model-type-neutral. Older StaticEntity*Event, DynamicEntity*Event, and PropEntity*Event variants, along with ResourcePackGenerationEvent, are no longer part of the current API. Use the four generic modeled-entity events above and FmmReloadedEvent instead.
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:
- Detection tests the segment between tracked positions to catch crossings of thin models; when no previous position is available it falls back to an overlap test
- Candidates on that segment are resolved nearest-first, independently of model-registry iteration order
- A non-piercing projectile is routed to one modeled target. A Piercing arrow can hit
pierce level + 1distinct modeled targets, in travel order, without double-firing when both the OBB and vanilla backing hitboxes observe it - The default dynamic-entity handler forwards the impact to the underlying living entity as a real projectile damage event, using the projectile's impact-time velocity. This preserves projectile cause, combat-plugin handling, and shooter attribution
- Cancelling the event prevents FMM's callback/default damage behavior, but the collision still spends that projectile's modeled-target budget. A cancelled non-piercing hit therefore still consumes the projectile
@EventHandler
public void onProjectileHitModel(ModeledEntityHitByProjectileEvent event) {
ModeledEntity target = event.getEntity();
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)-- checks whether a display model was registered; a malformed.jsonis not registered merely because the file existsgetRegisteredModels()-- returns an immutableSet<String>of all model IDs that have display models registeredshutdown()-- clears all registrations
ItemScriptManager
Despite its retained class name, this is now an authored-item and magic-weapon catalog, not an equipped-item script runtime. getItemDefinitions() exposes registered item definitions; getWeaponCatalog() exposes validated weapon definitions. The former active-script, equip-update, and player-script removal APIs are gone.
ScriptedItemAPI
isValidItemId(String): checks for the exact authored item ID (configuration filename without.yml).getItemConfig(String): returns the definition, ornull.applyScriptedItemData(ItemStack, String): stampsfmm_item_idand, when present, the registered display model. Returnsfalsefor an invalid item ID or missing item metadata.
This method does not change the host item's name, lore, or enchantments, and does not attach Lua behavior. Apply enchantments separately through MagmaCore. Bow and crossbow draw states belong to the base display-model definition; do not append _idle to the authored item ID.
ModelItemAPI
Use ModelItemAPI.applyDisplayModel(ItemStack, String) when the host needs only an FMM display model. It checks the display-model registry and does not stamp authored-item identity. This is distinct from choosing an FMM weapon or item definition.
ItemConstructionSnapshot
For plugins that build items off the server thread. Call ItemConstructionSnapshot.capture() on the server thread; it copies the current authored-item definitions, display models and magic weapon kinds. A worker thread can then call item(id) or weapon(id) and apply the returned ItemData to an ItemStack with apply(ItemStack), which stamps fmm_item_id for an authored item and the display model when one is registered. weapon(id) returns null unless the ID is a magic weapon and the magic service was operational at capture time. Before publishing the built items, call requireCurrent() on the server thread: it throws ConcurrentModificationException if FMM's definitions changed since the capture, for example after a reload.
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)-- removes the old disguise before attempting its replacement; an unknown ID returnsfalseafter the old disguise has been removed. Pass an online player and an exact loaded ID; the API does not normalize IDs or check command permissionsundisguise(Player)-- returnstrueif a disguise was removedisDisguised(Player)-- quick boolean checkgetDisguiseModelID(Player)-- returns the active model ID ornullgetDisguisedPlayers()-- unmodifiable snapshot of disguised players
FMM combines default player visibility, an invisibility potion effect and equipment-display overrides. It restores a removed effect only if that disguise originally added it; an effect already present at disguise time is not owned or restored. The command validates the model before replacement, unlike the API. See Player Disguises for lifecycle and client-rendering limits.
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 protectedunregisterDungeonLocator(String providerName)andunregisterProtectionProvider(String providerName)remove the predicate registered under that name. Call them from your plugin'sonDisableso FMM stops calling into the disabled instance
Registering again under the same provider name replaces the earlier predicate, so a plugin that re-registers on every enable does not stack copies. A null provider name registers a predicate that can be neither replaced nor unregistered.
Operators can verify registration with /fmm location, which reports the live provider count and tests both predicates against their current location.
Protection providers and prop placement
The same protection providers also drive the preventPropPlacementInProtectedRegions prop-placement check, but that check calls canBuild(player, location) rather than the location-only isProtected(location). The distinction matters:
| Provider | canBuild behavior |
|---|---|
| Built-in WorldGuard adapter | Honors WorldGuard's own bypass, then defers to WorldGuard's testBuild — so region members and owners can build normally |
| Built-in GriefPrevention adapter | No claim at the location means allowed; inside a claim it defers to GriefPrevention's own build permission for that player |
Provider registered via LocationAPI.registerProtectionProvider | Falls back to !isProtected(location) — location-only, blocks everyone in a location your predicate calls protected |
That fallback is intentional and conservative: a Predicate<Location> cannot express player-specific permission, so FMM will not invent one. If you want genuinely player-aware behavior for your own region system, implement MagmaCore's RegionProtectionProvider directly and override canBuild, then register it with LocationQueryRegistry.registerProtectionProvider instead of going through the predicate convenience wrapper.
Two more behaviors worth knowing:
- Adapter failures fail closed. If a provider throws during a build query, the placement is refused and a warning naming the provider is logged. It never silently falls through to "allowed".
- Every registered provider must agree. The first provider to say no wins; ownership providers registered through
LocationOwnershipare still location-only and block everyone.
Item placement checks the adjacent cell first. Voxelized props additionally check the rotated footprint, using rounded hitbox dimensions with a minimum of one block; the packet-barrier volume uses a different calculation. Direct PropEntity.spawnPropEntity calls do not run the player item's placement checks. See Configuration before exposing placement through another plugin.
PropScriptConfigFields
Parses the model-adjacent YAML used for props and authored items. Prop scripts: lists bind independent Lua scripts. A material: definition instead registers a held item and cannot contain a nonempty scripts: list.
See model configuration for separate prop and held-item examples, and magic weapons for the weapon schema.
Lua Scripting
FreeMinecraftModels supports prop Lua scripts through the MagmaCore 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. Held-item effects use the separate enchantment provider runtime and cannot use a nonempty item scripts: list.
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 for good (not on chunk unload or reload) |
on_left_click | Player left-clicks the prop |
on_right_click | Player right-clicks the prop |
on_projectile_hit | A projectile hits the prop; context.projectile is the projectile and context.event.player the shooter |
Item Script Hooks
The equipped-item Lua hooks and context.item runtime are retired. Use enchantment definitions for item effects. Prop hooks remain supported.
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)-- requests the named animation (blend and loop default totrue, which layers a looping animation over the prop's idle); the request applies on the model's next tickstop_animation()-- ends custom animations and returns to idle when available; does not interrupt terminal deathhurt_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,on_zone_leave, andon_projectile_hit cancel(),uncancel(),is_cancelledwhen the underlying hook is cancellableplayer-- the player who triggered the event; inon_projectile_hit, the living entity that shot the projectile
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
Player objects (from context.player or context.event.player):
get_held_item()-- returns the item the player is holding;typeis the uppercase Bukkit material nameis_holding_elitemobs_item(filename)-- checks the main-hand item's EliteMobs filename tag, such asshroomwhispers_shears.yml. Display names and translations do not affect this check. Returnsfalsefor an empty hand, a different item, or an unavailable EliteMobs integration.consume_held_item()-- queues removal of one item from the main hand as it exists when the task runs; returns no payment resulthas_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
Use filename identity for EliteMobs quest items:
if not player:is_holding_elitemobs_item("shroomwhispers_shears.yml") then
return
end
This method is provided by FMM's EliteMobs integration. Renaming an ordinary item does not give it the required filename tag.
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
The former equipped-item example is no longer supported. See magic weapons and enchantment definitions for current item authoring.
Building From Source
The source is on GitHub. FreeMinecraftModels is a Maven project for Java 21:
mvn -DskipTests package
- The plugin jar is written to
target/FreeMinecraftModels.jar. - MagmaCore, bStats and the other bundled libraries are shaded in at the
packagephase. MagmaCore is relocated tocom.magmaguy.freeminecraftmodels.magmacore, so the server needs no separate MagmaCore install. - Maven resolves MagmaCore
2.2.0-SNAPSHOTfrom MagmaGuy's repository. When the source needs MagmaCore changes that are not published there yet, build MagmaCore and publish it to your local Maven repository first.-Dmagmacore.version=<version>builds against another MagmaCore version. - When the
MC_DIST_DIRenvironment variable is set, the build also copies the jar into that folder.
Imported models, textures, fonts and content packs keep their authors' licenses. Generating a resource pack from them does not change those rights.
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. - The current FMM source depends on MagmaCore
2.2.0-SNAPSHOT, which supplies the shared Lua engine,LocationQueryRegistryandWorldFolderResolver. - FreeMinecraftModels declares WorldGuard, WorldEdit, GriefPrevention, Vault, floodgate, and Geyser-Spigot as
softdepend. WorldGuard and GriefPrevention provide the built-in player-aware protection adapters; WorldEdit is a WorldGuard dependency, not an independent FMM protection adapter. The shop requires Vault plus an economy provider and enabled shop configuration. Bedrock rendering also needs its compatible pack and integration path. ModeledEntityManager.reload()requests the same asynchronous preparation and full lifecycle used by/fmm reload; it does not wait for completion. Call from the main server thread and use successful lifecycle feedback rather than itsvoidreturn as evidence. Invalid candidate content can reject the reload while preserving the active runtime.