Skip to main content

Player Disguises

FreeMinecraftModels can disguise any online player as a loaded model. Disguises are driven by commands, a public DisguiseAPI, and a per-player PlayerDisguiseEntity that handles invisibility and model rendering for the disguised player.

Commands

CommandPermissionNotes
/fmm disguise <modelID>freeminecraftmodels.disguise.selfDisguises the sender. Player-only
/fmm disguise <modelID> <player>freeminecraftmodels.disguise.othersDisguises the named player. Works from console
/fmm undisguisefreeminecraftmodels.disguise.selfRemoves the sender's disguise. Player-only
/fmm undisguise <player>freeminecraftmodels.disguise.othersRemoves the named player's disguise. Works from console
/fmm disguiselistfreeminecraftmodels.disguise.othersLists every disguised player and the model each is disguised as

The freeminecraftmodels.* wildcard grants admin, deleteall, disguise.self, and disguise.others at OP default. It also grants the separately registered freeminecraftmodels.bypassregionprotection permission.

Behavior

  • Disguising a player who is already disguised cleanly replaces the previous disguise — no need to undisguise first.
  • The disguised player is made invisible to other players so only the model is visible. The disguised player still sees themselves normally.
  • The invisibility persists through milk buckets, /effect clear, beacon effect clears, death and respawn, and any other interaction or plugin that would normally strip the effect (re-applied on the next tick by DisguiseEffectListener).
  • The player's hitbox is unchanged. Mob targeting, collision, and PvP all work as usual.
  • Disguises do not survive a server restart, a /fmm reload, or the player logging out — they are tracked in memory and the DisguiseListeners.onQuit handler removes the disguise on PlayerQuitEvent. Death and world changes do not undisguise the player.
  • On /fmm reload, all active disguises are torn down as part of plugin shutdown. Players need to be re-disguised after a reload.

Disguise Animations

Disguises do not use the normal modeled-entity animation state machine. Each disguise gets its own controller that reads the disguised player every tick and picks an animation by name.

Five reserved animation names are recognized, in this priority order (highest first):

PriorityAnimationTypeTriggered by
1attackone-shotThe player swings their arm
2jumpone-shotThe player leaves the ground moving upward
3sneakloopThe player is sneaking
4walkloopThe player moved more than ~0.01 blocks since the last tick
5idleloopNothing else applies

Behavior worth knowing:

  • Only animations the model actually has are considered. A model with no sneak animation simply falls through to walk or idle.

  • A one-shot animation interrupts the current loop and suppresses lower-priority animations for a fixed 20 ticks (1 second). The animation system exposes no completion callback, so that is an assumed upper bound rather than the real length — keep attack and jump animations at or under a second if you want a clean handoff back to the loop.

  • Loops are only re-issued when the chosen animation actually changes, so a steady walk is not restarted every tick.

  • jump is genuinely wired up for disguises, unlike the (currently inert) jump state on regular modeled entities. See Animations.

  • A disguise model with no idle animation logs a warning on every disguise:

    Disguise model '<model>' has no 'idle' animation — disguised players using this model will freeze on the last animation frame whenever no other action is active.

    Ship an idle animation with any model you intend to use as a disguise.

Bedrock Clients

Disguises rely on FMM rendering the custom model. The default sendCustomModelsToBedrockClientsV2: true in config.yml enables Bedrock model sending out of the box; if you set it to false (or carried over the older sendCustomModelsToBedrockClients: false), Bedrock players will see neither the model nor the underlying invisible player — effectively, they see nothing where the disguised player is standing. Even with the toggle on, you still need a Bedrock-compatible resource pack pipeline (Floodgate + Geyser + converted resource pack) for the model to render.

DisguiseAPI

Third-party plugins can drive disguises programmatically via com.magmaguy.freeminecraftmodels.api.DisguiseAPI.

import com.magmaguy.freeminecraftmodels.api.DisguiseAPI;

// Disguise (returns false if the model ID is not loaded)
boolean ok = DisguiseAPI.disguise(player, "dragon");

// Undisguise (returns true if a disguise was removed)
DisguiseAPI.undisguise(player);

// Queries
boolean isDisguised = DisguiseAPI.isDisguised(player);
String modelID = DisguiseAPI.getDisguiseModelID(player); // null if not disguised

// Unmodifiable snapshot of all disguised players
Collection<Player> all = DisguiseAPI.getDisguisedPlayers();

The API is the public, refactor-safe entry point. Plugins should call DisguiseAPI rather than the internal DisguiseManager.

Common Use Cases

  • Roleplay and RPG servers: disguise players as boss models for cinematics or quest scripts.
  • Event hosting: disguise an announcer as a giant custom model.
  • Mounts and vehicles: combine /fmm mount with /fmm disguise for creative transport setups.
  • EliteMobs integration: boss scripts can disguise the player who triggered an event for a temporary transformation effect.

Troubleshooting

Nothing happens when I run /fmm disguise dragon. The model ID must match a loaded model exactly. Check /fmm admin or the command's tab completion to verify the model name. The targeted form checks freeminecraftmodels.disguise.others before resolving the model or player and reports that missing permission directly.

My disguise vanished after /fmm reload. Expected. All disguises are torn down on reload. Listen for FmmReloadedEvent if you need to re-apply disguises automatically.

Bedrock players cannot see the disguise. See Bedrock Clients above.

The disguised player is still visible to others. The invisibility is applied as a packet-level effect. If another plugin is force-removing invisibility every tick (some anti-cheat plugins do this), it can fight FMM's reapplication. Whitelist FMM or disable the conflicting check.