Skript addon · reference documentation

Bring your creatures to life.

SkBetterModels exposes BetterModel's animated-entity engine as native Skript syntax — attach models, drive animations, tint and hide parts, bind rideable hitboxes, and hook model lifecycle events, all from .sk scripts.

This plugin is in alpha. Core features are stable enough for daily use, but several are still experimental — expect the occasional rough edge, especially around threading, hitboxes, and the Citizens integration.

Run into a bug? Send a report straight to the developer on Discord: discord.com/users/1042566656410538072

Plugin version
0.1.0 — alpha
Main class
SkBetterModels
Depends on
Skript, BetterModel
API target
1.21

01Installation

SkBetterModels is a Skript addon, not a standalone plugin — it registers its syntax with Skript on enable and does nothing without it.

1

Install the two dependencies first

Drop Skript and BetterModel into /plugins. Both must already be present — plugin.yml declares depend: [Skript, BetterModel], so the server won't enable SkBetterModels without them.

2

Add SkBetterModels.jar

Place SkBettermodels-0_1_0.jar in /plugins alongside them, then restart (or /reload, though a full restart is safer for addon jars).

3

Confirm it loaded

Check the console log on startup for:

[SkBetterModels] SkBetterModels enabled — BetterModel syntax is now available in Skript.

If it's missing, the addon failed to register its syntax elements — check for a preceding SEVERE stack trace naming a missing class from BetterModel.

4

Write scripts as normal

Every syntax element on this page becomes available in any .sk file under plugins/Skript/scripts/ — no extra import or script header needed.

02Core concepts

A handful of ideas recur across almost every effect and condition below. Understanding these up front makes the reference section much faster to read.

Attach, then act

Nearly everything else assumes the entity already has a model tracker attached.

Flags you control

"Locked" and "paired" are metadata hooks for your own scripts, not built-in behaviour.

"model" in a sentence usually means "an entity with a BetterModel tracker attached." Most syntax takes %entities% and internally resolves each one's tracker — the BetterModel object that actually holds the mesh, bones, and animation state. If an entity has no model attached, these silently do nothing to it rather than erroring.

Attach a model before doing anything else to it. Almost every other effect/condition depends on the entity already having a tracker. The very first thing a script touching models should do is:

attach model "your_model_name" to {_entity}

The model name is whatever you named the model in BetterModel's own model files — this addon doesn't validate it beyond checking BetterModel actually has a renderer registered under that name.

Two different "animation" systems are exposed, and they overlap in practice.

  • set default state of model … to "…", play limb animation … on model …, and play bettermodel animation … on … all ultimately call the same underlying tracker.animate(name) method. They exist as separate, differently-worded effects for readability in scripts, not because they do different things internally.
  • play/stop player animation … on %players% calls that same method but is scoped to a player's own tracker (relevant if you attach cosmetic models directly to players, e.g. a limb/cape overlay) rather than to a separate mob entity.

Threading matters for the async sections. run async: and async loop … to …: execute their body off the main server thread. Bukkit and BetterModel APIs are not thread-safe — touching entities, the world, or model trackers from inside either of those without wrapping that part in run task sync: will cause unpredictable crashes or corruption. See Sections below for the correct pattern.

Locked / paired states are addon-managed, not read from BetterModel. model … is locked and model … is paired check for Bukkit metadata keys (skbettermodels_locked / skbettermodels_paired) on the entity. Nothing in this addon currently sets those keys — they're conditions waiting for you to set the metadata yourself (e.g. via a vanilla Skript effect or another plugin) as a lightweight flag system for your own scripts. See the known limitations section.

03Effects

Effects are actions you run on a line by themselves — attaching models, playing animations, changing appearance, and managing riders.

Attaching & identifying models

attach model

Creates a BetterModel tracker for a model and attaches it to one or more entities.

EFFECT
attach model %string% to %entities%
example
attach model "raptor_juvenile" to {_dino}
Looks up the model name against BetterModel's registered renderers. If no renderer exists under that name, this does nothing — no error, no broadcast.
Remembers the name. After attaching, the entity's model name is cached internally so event-model and other name lookups work without re-querying BetterModel.

Animation

set default state

Plays a named animation state on a model — typically used for idle/default loops.

EFFECT
set default state of model %entities% to %string%
example
set default state of model {_dino} to "idle"
Empty strings are ignored. Internally this is the same call as play limb animation — it exists as its own effect purely for naming clarity ("this is the animation the model rests in").

play bettermodel animation

Plays a named animation on a single entity's model and fires a corresponding Skript event.

EFFECT
play bettermodel animation %string% on %entity%
example
play bettermodel animation "attack" on {_dino}
Only effect that fires an event. This is the only animation-playing effect that also raises on bettermodel animation play, giving you event-model and event-animation in a listener elsewhere. Use this one specifically when other scripts need to react to the animation starting.

play limb animation

Plays a named animation across one or more entities' models.

EFFECT
play limb animation %string% on model %entities%
example
play limb animation "roar" on model {_pack::*}
Accepts multiple entities in one call — good for pack-wide cues (e.g. every raptor in a pack roaring together).
Fires on bettermodel limb animation play once per entity.

play / stop player animation

Plays or stops a named animation on a player's own model tracker (not a separate mob entity).

EFFECT
play player animation %string% on %players% stop player animation %string% on %players%
example
play player animation "wave" on player
stop player animation "wave" on player
Only affects players who already have a BetterModel tracker of their own — e.g. via a cosmetic/cape model attached directly to the player entity. Most players won't have one unless your setup specifically attaches models to players; on a player with no tracker, this silently no-ops.

stop all player animations

Stops every animation currently running on a player's own model tracker.

EFFECT
stop all player animations on %players%
example
stop all player animations on player
Experimental — implemented as a scan, not a native call. There's no single "stop everything" call in BetterModel's API — this works around that by checking every bone for a running animation and stopping each distinct name found. Functionally complete, but worth watching for edge cases.

Parts, appearance & visuals

set part visible / hidden

Shows or hides a named bone/part of a model.

EFFECT
set part %string% of model %entities% to (visible|hidden)
example
set part "tail" of model {_dino} to hidden
set part "head" of model {_dino} to visible
Part names match child bones by name only (not nested children of that bone). The current visibility is tracked internally, which is what powers the model part … is visible condition.

change part

Replaces a named part of one model with a part taken from another model.

EFFECT
change part %string% of model %entities% to %string%
example
change part "tail" of model {_dino} to "spiked_tail"
Useful for cosmetic variation on the same base model — e.g. swapping a frill, tail, or crest part without attaching a whole separate model.

set tint

Applies a colour tint across a model.

EFFECT
set tint of model %entities% to %color%
example
set tint of model {_dino} to rgb(255, 80, 80)
Accepts any Skript colour value, including named colours and rgb(…). Useful for species colour variants, damage flashes, or team-colouring a tamed dinosaur.

set glow

Toggles the glowing outline effect on a model.

EFFECT
set glow of model %entities% to %boolean%
example
set glow of model {_dino} to true

apply / remove enchant glint

Toggles the shimmering enchantment overlay on a model.

EFFECT
apply enchant glint to model %entities% remove enchant glint from model %entities%
example
apply enchant glint to model {_dino}
remove enchant glint from model {_dino}
Same on/off shape as set glow, but phrased as two separate commands rather than a boolean — a shimmer overlay rather than an outline.

set brightness

Sets the light level a model renders at, independent of the world around it.

EFFECT
set brightness of model %entities% to %number%
example
set brightness of model {_dino} to 15
Value is clamped to 0–15 (the same range as vanilla block/sky light levels). Applies the same value to both block-light and sky-light channels; there's currently no way to set them independently through this effect.

set body rotation

Sets how far a model's body can rotate independently of its head/facing direction.

EFFECT
set body rotation of model %entities% to %number%
example
set body rotation of model {_dino} to 90
The number sets a symmetric max/min — e.g. 90 allows the body to swing 90° in either direction from center. Larger values make a model's torso track its head more loosely; smaller values keep it rigid.

Hitboxes & riding

bind hitbox

Creates an interactable, mountable hitbox on a named bone of a model.

EFFECT
bind hitbox %string% to model %entities%
example
bind hitbox "saddle" to model {_dino}
Creates the hitbox with an empty listener — it becomes clickable/mountable immediately, without needing a separate event wired up first. Use on bettermodel hitbox interact or on bettermodel hitbox enter to react to it afterward.
Do this once per bone you want interactive — e.g. a "saddle" bone for mounting, or a "head" bone you want players to be able to click for a feeding interaction.

mount entity to model

Mounts a rider onto the first available rideable hitbox of a model.

EFFECT
mount %entity% to model %entity%
example
mount player to model {_dino}
Experimental — always mounts onto the first hitbox in the model's registry. Correct for a single-seat mount, but not seat-specific for a model exposing multiple named hitboxes (e.g. a two-rider mount). There's no way to target a specific named hitbox through this effect currently; see known limitations.
Requires the target model to already have at least one hitbox — call bind hitbox first if it doesn't.

dismount from model

Removes a specific rider, or every rider, from all of a model's hitboxes.

EFFECT
dismount %entity% from model %entity% dismount all from model %entity%
example
dismount player from model {_dino}
dismount all from model {_dino}
The "all" form iterates every hitbox on the model and clears every rider from each — useful for forced dismounts (death, taming failure, a "buck off" mechanic).

Integrations

set model of citizen

Applies a BetterModel model to a Citizens NPC, by NPC ID.

EFFECT
set model of citizen %number% to %string%
example
set model of citizen 4 to "steve_dino"
Experimental — requires the Citizens plugin to also be installed. This is not declared as a hard dependency in plugin.yml, so the addon will still load without Citizens present, but this specific effect will fail to find the NPC (or throw, if Citizens' API classes aren't on the classpath at all) if it's missing.
Implemented by dispatching console commands (npc select then npc model) rather than calling a Citizens Java API directly — functionally equivalent to typing those commands yourself, so it inherits whatever behaviour Citizens' own model command has (including any of its own error messages appearing in console, not in Skript).

04Conditions

Boolean checks for use in if/while statements and elsewhere a condition is expected.

model is / isn't

Checks whether an entity's attached model matches a given name.

CONDITION
model %entities% (is|are) %string% model %entities% (isn't|aren't) %string%
example
if model {_entity} is "blue_wizard":
    broadcast "This is a blue wizard!"
Comparison is case-insensitive. With multiple entities, this behaves like a normal Skript group condition — it checks that all of them match (or, negated, that all of them don't) depending on how it's used in the surrounding sentence.

model part is visible

Checks whether a named part of a model is currently shown.

CONDITION
model part %string% of %entity% is visible
example
if model part "tail" of {_dino} is visible:
Reads from the same internal visibility map that set part … visible/hidden writes to. A part that was never explicitly hidden is considered visible by default.

model has passenger

Checks whether a model currently has any rider mounted on it.

CONDITION
model %entities% (has|have) passenger
example
if model {_dino} has passenger:
This is a property condition, so it also supports Skript's standard negation wording (e.g. doesn't have passenger) automatically.

model is locked

Checks a metadata-backed "locked" flag on the entity.

CONDITION
model %entities% is locked
example
if model {_dino} is locked:
Experimental — no effect in this addon sets this flag. It checks for Bukkit metadata key skbettermodels_locked on the entity — you're expected to set/remove that metadata yourself (e.g. via vanilla Skript's metadata effects, or another plugin) as a simple locking mechanism your scripts can build on. See known limitations.

model is paired

Checks a metadata-backed "paired" flag on the entity.

CONDITION
model %entities% is paired
example
if model {_dino} is paired:
Experimental — same shape as model is locked. Checks Bukkit metadata key skbettermodels_paired, which nothing in this addon sets automatically. Intended as a flag for your own mating/pairing logic (e.g. for the dinosaur breeding system) to read and write.

has active player animations

Checks whether a player's own model tracker currently has any animation running.

CONDITION
%players% has active player animations
example
if player has active player animations:
Scans every bone on the player's tracker for a running animation. Requires the player to actually have a tracker attached (see the note on play/stop player animation) — otherwise this is always false.

05Events

Triggers you can listen to with Skript's on …: syntax, plus one repeating-timer event.

on bettermodel spawn

Fires the moment a new model tracker is created for an entity.

EVENT
on bettermodel spawn:
example
on bettermodel spawn:
    broadcast "a model appeared"
Event valueTypeNotes
event-entityEntityThe entity the new tracker belongs to.
Fires for any model spawn recognized by BetterModel — including ones created outside this addon (e.g. by BetterModel's own commands) — not only ones created via attach model.

on bettermodel hitbox interact

Fires when a player interacts with (right-clicks) a hitbox bound to a model.

EVENT
on bettermodel hitbox interact:
example
on bettermodel hitbox interact:
    broadcast "a hitbox was clicked"
Event valueTypeNotes
event-playerPlayerThe player who clicked.
event-entityEntityThe model entity the hitbox belongs to.
Requires the hitbox to have been created first — via bind hitbox or by BetterModel itself. A model with no hitbox never fires this.

on bettermodel hitbox enter

Fires when a player's position first overlaps a model's hitbox region.

EVENT
on bettermodel hitbox enter:
example
on bettermodel hitbox enter:
    broadcast "%event-player% entered a BetterModel hitbox"
Event valueTypeNotes
event-playerPlayerThe player who entered the region.
event-entityEntityThe source entity the hitbox belongs to.
Checked on a 1-tick repeating scan across every active model hitbox and every online player, on the same world — this is proximity-based ("standing inside the hitbox's rough bounding box"), not a click. Only fires once per continuous overlap (re-entering after leaving fires again).
Experimental — performance scales with hitbox count. Because this scans all hitboxes × all players every tick, a server with very many simultaneous models and hitboxes will spend proportionally more time on this check. Consider this if you're deploying dozens of dinosaurs with bound hitboxes to a busy server.

on bettermodel animation play

Fires whenever the play bettermodel animation effect runs.

EVENT
on bettermodel animation play:
example
on bettermodel animation play:
    broadcast "%event-model%: %event-animation%"
Event valueTypeNotes
event-entityEntityThe entity the animation was played on.
event-modelTextThe model's name (see event-model).
event-animationTextThe animation name that was played (see event-animation).
Only fires from play bettermodel animation specifically — not from play limb animation, set default state, or play player animation, even though all four ultimately trigger the same underlying animation call. If you need a listener to catch animations started via those other effects too, trigger this event's underlying pattern from your own script, or use on bettermodel limb animation play for the limb-animation case specifically.

on bettermodel limb animation play

Fires whenever play limb animation runs on a model.

EVENT
on bettermodel limb animation play:
example
on bettermodel limb animation play:
    broadcast "Limb animation: %event-animation%"
Event valueTypeNotes
event-entityEntityThe entity the animation was played on.
event-animationTextThe animation name that was played.

on bettermodel animation signal

Wraps BetterModel's own internal animation-signal event.

EVENT
on bettermodel animation signal:
example
on bettermodel animation signal:
    # react to a raw signal from BetterModel's animation state machine
Experimental. No dedicated event-values are registered for this one — you're catching BetterModel's raw AnimationSignalEvent as a bare trigger. Useful mainly for advanced scripts that already know what they're listening for at the BetterModel level.

every N async ticks / seconds

A repeating timer that runs its body on an async scheduler thread instead of the main thread.

EVENT
every [%-number%] async tick[s] every %number% async second[s]
example
every 5 async ticks:
    # heavy computation off the main thread

every 2 async seconds:
    # same idea, expressed in seconds
The number defaults to 1 if omitted in the ticks form. Seconds are converted to ticks internally (× 20) — both forms schedule the same kind of repeating async task.
This body runs off the main thread. Don't touch entities, the world, or BetterModel trackers directly inside it — wrap that part in run task sync first. See Core concepts.

06Expressions

Values you can read (and in one case, that are read-only outside their event) from anywhere an expression is expected.

model of %entity%

Returns the name of the model attached to an entity.

EXPRESSION
model of %entity% %entity%'s model
example
broadcast "model: %model of {_dino}%"
broadcast "model: %{_dino}'s model%"
Returns nothing (an empty result) if the entity has no tracker attached. Both pattern forms are equivalent — use whichever reads more naturally in context.

event-model

The model name involved in the currently-firing event.

EXPRESSION
event-model
example
on bettermodel animation play:
    broadcast "model was: %event-model%"
Only meaningful inside on bettermodel animation play — it reads directly from that event object. Used anywhere else, it returns nothing.

event-animation

The animation name involved in the currently-firing event.

EXPRESSION
event-animation
example
on bettermodel animation play:
    broadcast "animation: %event-animation%"
Only meaningful inside on bettermodel animation play. Note that on bettermodel limb animation play exposes its animation name through a separately-registered event value of the same name, not through this expression class — both read as event-animation in a script, but come from different underlying event types depending which block you're in.

07Sections

Multi-line blocks — each of these wraps an indented body of your own script and changes how or where that body runs.

run async

Runs its body on a background thread instead of the main server thread.

SECTION
run async:
example
run async:
    broadcast "This is running asynchronously"
Prints its own diagnostic lines to console when it starts (which thread it hopped to) and if the body throws an error, including a reminder to use run task sync for anything touching Bukkit/Minecraft state.
The outer script does not wait for this to finish — it continues immediately while the body runs in the background. Don't rely on statements after run async: seeing side effects from inside it unless you've synchronized some other way.

run task sync

Runs its body on the main server thread, and blocks the calling thread until it's done.

SECTION
run task (sync|synchronously):
example
async loop 1 to 1000:
    run task sync:
        broadcast "main-thread work here"
This is the required pairing for anything async that needs to touch the game world. Wrap the specific lines that read/write entities, blocks, or BetterModel trackers in this section when they live inside run async or async loop.
If the script is already on the main thread when it reaches this section (e.g. used outside any async context), it just runs the body immediately without switching threads — safe to use defensively even when you're not sure what thread you're on.

async loop

Runs its body once per whole number in a range, off the main thread.

SECTION
async loop %number% to %number%:
example
async loop 1 to 1000:
    # heavy, non-Bukkit-API computation here
    run task sync:
        # anything touching entities/world/BetterModel goes here instead
Counts down instead of up automatically if the first number is larger than the second (e.g. async loop 10 to 1 counts 10, 9, 8 … 1).
The outer script continues immediately after this section is reached — the loop itself runs on a background task, so statements written after the loop in your script do not wait for all iterations to finish.
Experimental. The current iteration number is exposed inside the body as a raw event-number value from Skript's own vocabulary, not a dedicated named expression from this addon — reference it the way you'd reference any other event-number in a Skript event body.

08Known limitations

Alpha software has rough edges. Here's what to know before you build around a specific behaviour — and where to send a bug report if you find one that isn't listed.

Locked / paired are flags, not features. model … is locked and model … is paired read Bukkit metadata keys that nothing in v0.1.0 writes automatically. They're a hook for your own scripts (or another plugin) to set — for example, marking two dinosaurs as "paired" during your own mating logic, then reading that flag back elsewhere.

Mounting always targets the first hitbox. mount %entity% to model %entity% has no way to specify which named hitbox to mount onto when a model has more than one — it always picks whichever hitbox comes first in the model's internal registry. Fine for single-seat mounts; not seat-specific for multi-rider models.

"Stop all player animations" is a scan, not a native call. There's no single "stop everything" method in BetterModel's animation API — this effect works by checking every bone for a currently-running animation and stopping each one found. Functionally complete, but technically a workaround rather than one direct call.

Citizens integration is a soft dependency. set model of citizen requires the separate Citizens plugin, but Citizens is not declared in plugin.yml's depend list — the addon will enable fine without it, and only this one effect will fail (silently, or with a Citizens-side error in console) if Citizens isn't actually installed.

Hitbox-enter detection is a per-tick scan. on bettermodel hitbox enter checks every player against every hitbox on every active model tracker once per tick — proportionally more expensive the more models with bound hitboxes you have active at once. Keep this in mind at scale (e.g. many dozens of dinosaurs on a busy server).

event-animation means different things in different events. Inside on bettermodel animation play, event-animation comes from this addon's own expression class tied specifically to that event. Inside on bettermodel limb animation play, the same-looking expression is backed by a separately-registered event value on a different underlying event type. They read identically in script but are wired up independently — each only works inside its own matching event block.

This is alpha software. Anything on this page can change shape in a future version, and features marked experimental above are more likely to shift or get replaced outright as BetterModel's own API evolves. Pin your server to a known-working jar version if you're relying on this in production.

↑ back to top

SkBetterModels v0.1.0 (alpha) · built on Skript + BetterModel · reference documentation.