Adding Custom Molang Queries

Custom Molang queries allow you to expose additional data to your animations. These queries can be registered and updated through your animator implementations.

Defining Custom Queries

Step 1: Define Query Constants

First, define your custom query constants in your mods main class:

public static final String MY_CUSTOM_VALUE = MolangQueries.normalize("query.my_custom_value");

Step 2: Register Variables

Create a method to register your variables, typically called during initialization. Register against the shared MolangParser.INSTANCE — don't create a new MolangParser, since doing so just re-runs its constructor setup for no benefit:

private void registerXModsVariables() {
MolangParser.INSTANCE.register(new LazyVariable(ModClass.MY_CUSTOM_VALUE, 0));
}

Step 3: Implement Query Updates

Override applyMolangQueries in your AzAnimator class to update your query values during animation. Create your suppliers once and have them read the entity currently being animated, rather than creating a new lambda every frame. Binding then allocates nothing:

public class MyEntityAnimator extends AzEntityAnimator<MyEntity> {
// Resolved once, instead of looking the query up by name every frame.
private static final MolangVariableRef MY_CUSTOM_VALUE_REF = new MolangVariableRef(ModClass.MY_CUSTOM_VALUE);
// The entity currently being animated, set in applyMolangQueries below.
private MyEntity currentEntity;
// Created once, when the animator is created.
private final DoubleSupplier myCustomValue = () -> currentEntity.getChargeLevel();
@Override
protected void applyMolangQueries(MyEntity entity, double animTime, float partialTicks) {
super.applyMolangQueries(entity, animTime, partialTicks);
this.currentEntity = entity;
MY_CUSTOM_VALUE_REF.setMemoized(myCustomValue);
}
// ... registerControllers, getAnimationLocation, etc.
}

Adding Query Functions

A query function takes arguments, like query.my_charge_level(1). Use one when a single query needs a parameter, such as a slot, an axis or a threshold.

Step 1: Create the Function

Extend ContextQueryFunction. It gives you the current MolangQueryContext, which for AzureLib animators is an AzMolangQueryContext holding the entity being animated:

public class ChargeLevel extends ContextQueryFunction {
public ChargeLevel(IValue[] values, String name) throws Exception {
super(values, name);
}
@Override
public int getRequiredArguments() {
return 1;
}
@Override
protected double evaluate(MolangQueryContext context) {
if (context instanceof AzMolangQueryContext azContext && azContext.entity() instanceof MyEntity entity) {
return entity.getChargeLevel(intArg(0));
}
return 0;
}
}

Step 2: Register the Function

Register it during mod initialization, before resources load, alongside your variables:

MolangParser.INSTANCE.registerFunction("query.my_charge_level", ChargeLevel.class);

Animations can then call query.my_charge_level(1) or q.my_charge_level(1). Unlike variable queries, functions are always written with parentheses.

Registration Methods

Memoized Values

The supplier runs at most once each time the value is bound: the first time an animation reads the query for that entity that frame. Every other read that frame reuses the result. Because you bind it again every frame, the value still updates every frame. This is the right choice for almost every query, especially ones that are expensive to calculate or used by several keyframes:

MY_CUSTOM_VALUE_REF.setMemoized(() -> calculateValue());
private double calculateValue() {
// Runs once per entity per frame, no matter how many keyframes read the query
double baseValue = 10.0;
double modifier = 1.5;
return baseValue * modifier;
}

Dynamic Values

The supplier runs every time an animation reads the query. Only use this when the value must be recalculated on each read rather than once per entity per frame:

MY_CUSTOM_VALUE_REF.set(() -> getUpdatedValue());
private double getUpdatedValue() {
// Runs on every read of the query
double currentTime = System.currentTimeMillis() / 1000.0;
return Math.sin(currentTime) * 10.0; // Creates a smooth oscillating value
}

Static Values

For constant values:

MolangParser.INSTANCE.register(new LazyVariable(ModClass.MY_CUSTOM_VALUE, 42.0));

Using Your Queries in Animations

Queries can be written with either the full or the short prefix, so query.my_custom_value and q.my_custom_value are the same query. The same applies to variable./v., temp./t. and context./c..

Variables

Molang has three kinds of names you can store values in:

PrefixLivesTypical use
variable. / v.On each animatable, across frames and across every expressionState an animation keeps, values driven from code
temp. / t.One evaluation of one expression; starts at 0 every timeIntermediate results inside a longer expression
query. / q., context. / c., math., array.Read-onlyGame state, built-in functions and arrays

Each entity, block entity and item has its own set of variable.* values, so a value one entity assigns is never seen by another. A value assigned in one keyframe is readable in every other keyframe of that animatable, on the same frame and later frames.

Assigning to a query, a context.* or math.* name, or a constant is an error: the expression logs a message and evaluates to 0.

Driving variable.* from Code

The simplest way to feed per-entity data to an animation, without registering a query, is to set a variable on the animatable's scope:

@Override
protected void applyMolangQueries(MyEntity entity, double animTime, float partialTicks) {
super.applyMolangQueries(entity, animTime, partialTicks);
this.context().molangScope().set("v.charge", entity.getChargeLevel());
}

Animations read it as v.charge or variable.charge. The scope also has get, isSet, remove and clear.

Variables can hold strings, arrays and entity references as well as numbers. Set those with setObject:

this.context().molangScope().setObject("v.mode", entity.isAngry() ? "angry" : "calm");
this.context().molangScope().setObject("v.skins", MolangArray.ofNumbers(0, 1, 2));

Entity references stored in a variable.* are held weakly, so a scope never keeps an entity alive. Once the entity is gone the variable reads as unset, and v.target ?? ... falls through.

Expression Syntax

Keyframe values can be a single expression or several statements separated by ;:

t.wave = math.sin(q.anim_time * 360);
v.peak = math.max(v.peak ?? 0, t.wave);
return t.wave * 10;
SyntaxMeaning
+ - * / % ^Arithmetic (^ is power).
< <= > >= == !=Comparisons, giving 1 or 0.
&&, ||, !Logic. && and || stop early, so the right side isn't evaluated when it can't matter.
a ? b : cb if a is non-zero, else c.
a ? bb if a is non-zero, else 0.
v.x ?? 1v.x if it has been given a value, else 1.
name = valueAssignment. Allowed anywhere an expression is; it evaluates to the assigned value.
{ ...; ...; }A block of statements, e.g. as the branch of a conditional.
return valueEnds the expression with value.
loop(count, { ... })Runs the block count times (at most 1024). break leaves the loop, continue skips ahead.
'text'A string. Case is kept; strings support only == and !=.
array[index]An array element. The index is floored, negative reads element 0, past the end wraps around.
for_each(t.e, array, { ... })Runs the block once per element, assigned to t.e (or a v.*). Supports break/continue.
entity->expressionEvaluates expression against another entity, e.g. t.e->q.health. 0 if it isn't a live entity.
thisThe current value of the bone channel the keyframe sets. 0 outside keyframes.
true, false, math.pi1, 0 and π.

A conditional with blocks:

q.is_on_ground ? { v.air_time = 0; } : { v.air_time = (v.air_time ?? 0) + 1; };
return math.min(v.air_time, 20) * 2;
  • Without a return, a multi-statement expression evaluates to its last statement.
  • Names are case-insensitive; string contents are not.
  • An expression the compiler can't parse is retried with AzureLib's older parser and logs a warning containing "Falling back to the legacy parser"; if that fails too, it evaluates to 0. Invalid Molang, such as 'a' + 1 or assigning to an array element, is rejected outright and evaluates to 0.

Strings

q.get_equipped_item_name == 'bow'
q.is_item_name_any('slot.weapon.mainhand', 0, 'minecraft:crossbow')
q.equipped_item_any_tag('main_hand', 'minecraft:swords')
q.any(q.get_equipped_item_name, 'bow', 'crossbow')

A string read as a number is 0.

Arrays and for_each

Arrays come from queries such as q.get_nearby_entities(radius, 'minecraft:pig'), from array.* names registered in code, or from a variable.* set with setObject. They are read-only and shared by reference.

t.babies = 0;
for_each(t.pig, q.get_nearby_entities(8, 'minecraft:pig'), {
t.babies = t.babies + t.pig->q.is_baby;
});
return t.babies;

q.count(array) gives the number of elements. q.get_nearby_entities scans the level each time it is called, so store the result in a t.* if you need it twice. Register an array.* with:

MolangParser.INSTANCE.registerArray("array.skins", () -> MolangArray.ofNumbers(0, 1, 2));

The arrow operator

entity->expression evaluates the right side as if animating that entity. Its queries read the other entity, and its variable.* values are that entity's own if it has an AzureLib animator. A removed or non-entity value on the left gives 0 without evaluating the right side.

this

In a keyframe, this is the value the bone channel currently holds, in the same units the keyframe uses: degrees offset from the bind rotation, position offset, or scale. That is the pose the bone finished the previous frame on, so this eases a bone toward 45° over several frames instead of snapping:

math.lerp(this, 45, 0.2)

Targets

q.has_target, q.target_x_rotation and q.target_y_rotation read the mob's attack target. Vanilla doesn't sync a mob's target to the client, so on most mobs these read 0 until you implement AzMolangTargetProvider on your entity and return a target you sync yourself (for example an entity id in SynchedEntityData). The rotations are absolute, like q.body_y_rotation; use math.min_angle(q.target_y_rotation - q.body_y_rotation) for a body-relative angle.

Items

While an item renders in an entity's hand, entity queries in its animations answer for the holder, like Bedrock attachables, and q.is_first_person is 1 when the item itself is drawn in a first-person hand.

Best Practices

  1. Always use MolangQueries.normalize() when defining query constants
  2. Define query constants as public static final fields
  3. Register variables during initialization using MolangParser.INSTANCE — never instantiate a new MolangParser
  4. Use memoized values unless a query really needs recalculating on every read
  5. In animators, bind through a MolangVariableRef and create suppliers once, reading the current entity from a field, instead of creating new lambdas every frame
  6. Keep query names descriptive and consistent
  7. For simple per-entity values, context().molangScope().set("v.name", value) avoids registering anything
  8. Use temp.* for scratch values inside an expression and variable.* only for state that must persist or be shared between keyframes