Figura/Complete Wiki
MC 1.21.1 · Figura 0.1.5 · Lua 5.2
Documentation

FIGURA
Complete Reference

The definitive guide to everything you can (and can't) do with Figura — avatars, Lua scripting, every API, code examples, and more.

FabricForgeQuiltNeoForgeLua 5.2v0.1.5

Figura is a Minecraft Java client mod that lets you fully replace your player model with a custom Avatar made in Blockbench, scripted in Lua. Other players running Figura see your avatar synced in real time — no server mod required.

This wiki covers the full Figura 0.1.5 API for Minecraft 1.21.1, extracted directly from the mod source code and JAR files.

Overview

What You Can & Cannot Do

Figura runs a sandboxed Lua 5.2 environment. Understanding the limits is crucial before building complex avatars.

✓ You Can Do

Replace or layer on top of the vanilla player model with any 3D model made in Blockbench
Play, pause, loop, blend and script Blockbench animations
Read player/entity position, rotation, health, inventory, NBT, armor, effects, etc.
Control model part visibility, position, rotation, scale, color, opacity, UV, render type
Show/hide vanilla model parts (body, arms, armor, elytra, cape, held items…)
Spawn particles and play sounds (vanilla or custom .ogg)
Bind custom keybinds and respond to press/release
Build a radial Action Wheel with 8 slots per page
Sync state to all viewers using Pings
Customize the nameplate (text, color, position, scale, visibility)
Create and manipulate textures at runtime
Raycast blocks and entities from Lua
Read world data: biomes, block states, light levels, time, weather
Perform HTTP requests and open WebSockets (requires High permission)
Read/write files inside the avatar folder (requires File API permission)
Persist data between sessions with the Config API
Modify camera position, rotation, FOV, and post effects
Render text, items, blocks, sprites, and entities as model part tasks
Modify or cancel incoming chat messages (host only)
Take screenshots from Lua (host only)
Intercept and cancel sounds via ON_PLAY_SOUND event
Cancel damage/totem animations via DAMAGE event
Share data between avatars using avatar:store()

✗ You Cannot Do

Access the filesystem arbitrarily (only the avatar folder, with permission)
Execute OS commands or call external programs
Access other mods' APIs or Minecraft's internal Java classes directly
Use debug, dofile, loadfile, or collectgarbage — these are removed from the sandbox
Run infinitely without limit — instruction counts are capped per phase
Spawn unlimited particles or sounds — rate-limited per permission level
Edit the nameplate unless viewer grants Default or higher permission
Use HTTP/Sockets unless viewer grants High or Max permission
Make other players perform actions (you can only sync state via pings)
Access server-side data (inventories, entities on server side, etc.)
Load textures larger than the viewer's texture size limit
Create models exceeding the viewer's complexity limit (face count)
Use cracked/offline accounts — Mojang auth is required
Interact with or grief other players beyond visual effects
Directly send packets to the Minecraft server
Access the clipboard without host.getClipboard() (host-only)
Run code when the avatar is "Blocked" by a viewer's permissions
Setup

Installation

1

Install a Mod Loader

Figura supports Fabric, Forge, Quilt, and NeoForge for Minecraft 1.21.1. Get the latest loader for your choice.

2

Download Figura

Get figura-0.1.5-1.21.1-[loader].jar from Modrinth, CurseForge, or GitHub. Drop it into .minecraft/mods/.

3

Launch & Find the Button

Boot Minecraft. Figura adds a button to your inventory screen. Click it to open the Wardrobe.

4

Create Your Avatar Folder

Navigate to .minecraft/figura/avatars/ and create a new folder. Inside it, add at minimum an empty file named avatar.json.

Avatar Setup

File Structure

Directory Tree
.minecraft/figura/avatars/
└── MyAvatar/
    ├── avatar.json         ← Required: metadata (can be empty {})
    ├── model.bbmodel       ← Blockbench model file
    ├── skin.png            ← Texture (referenced by model)
    ├── skin_e.png          ← Emissive layer (suffix _e)
    ├── script.lua          ← Main Lua script
    ├── utils.lua           ← Additional scripts (all auto-loaded)
    ├── sounds/
    │   └── blast.ogg       ← Custom sounds (.ogg only)
    └── libs/
        └── math_utils.lua  ← Use require("libs/math_utils")
INFO
All .lua files are loaded automatically. You don't need to import them. Use require("filename") (without .lua) only when you need to control load order or get return values from another script.
Avatar Setup

avatar.json

The avatar.json file is the only required file. It tells Figura the folder is an avatar. All fields are optional.

avatar.json
{
  "name": "My Cool Avatar",
  "description": "A custom Figura avatar",
  "authors": ["YourName"],
  "version": "1.0.0",
  "color": "#5af0c8"
}
Avatar Setup

Wardrobe Screen

ButtonAction
↑ UploadUpload avatar to the Figura cloud (Main, Server, or both)
↺ ReloadReload avatar from disk without restarting Minecraft
✕ DeleteRemove avatar from the cloud (Main, Server, or both)
⊞ FolderOpen figura/avatars/ in your file manager
✦ New AvatarLaunch Avatar Wizard to generate a starter avatar
♪ SoundsList all sounds the selected avatar has registered
⌨ KeybindsShow keybinds registered by the selected avatar
Scripting

Lua Scripting Basics

Figura runs Lua 5.2 in a sandbox. The following standard Lua globals are removed for safety: debug, dofile, loadfile, collectgarbage.

Figura-Added Globals

Figura adds these functions on top of standard Lua:

FunctionDescription
print(...)Print to the in-game chat (multiple args allowed)
printTable(t)Print a table or Figura type to chat
printJson(str)Print a Minecraft JSON string formatted to chat
parseJson(str)Convert a JSON string to a Lua table
toJson(val)Convert a Lua value to a JSON string
require("name")Load another script by name (without .lua). Only runs once; returns its return value.
listFiles(path)Return a table of all script filenames at a path (nil = root)
type(val)Overridden: returns Figura type name for Figura objects
vec(x,y,z,w)Alias for vectors.vec()
log(...)Same as print — writes to chat

Added Math Functions

FunctionDescription
math.lerp(a, b, t)Linear interpolation. Works on numbers and vectors
math.clamp(n, min, max)Clamp a number between min and max
math.round(n)Round to nearest integer
math.map(n, a1, b1, a2, b2)Map value from one range to another
math.shortAngle(a, b)Shortest angle between two angles
math.lerpAngle(a, b, t)Lerp between two angles (wraps around 360°)
math.sign(n)Returns -1, 0, or 1 based on sign of n
math.playerScaleConstant: player scaling relative to world
math.worldScaleConstant: world scaling relative to player
Lua Example — Basics
-- All scripts run top to bottom when loaded
vanilla_model.ALL:setVisible(false)

-- Use require to share code between files
local utils = require("libs/utils")

-- math.lerp works on vectors too
local v1 = vec(0, 0, 0)
local v2 = vec(1, 2, 3)
local v3 = math.lerp(v1, v2, 0.5)  -- {0.5, 1, 1.5}

-- type() returns Figura type names
print(type(v3))  -- "Vector3"
Scripting

All Global APIs

GlobalDescriptionNotes
playerProxy for the avatar owner's player entityall
userAlias for player
modelsRoot of all loaded .bbmodel partsall
eventsRegister callbacks for game eventsall
animationsAccess all Blockbench animationsall
vanilla_modelControl vanilla player model visibilityperm
pingsFunctions synced to all viewersall
action_wheelBuild the radial action wheel menuall
rendererCamera, FOV, fire, outline, armsall
nameplateCustomize the player nameplateperm
keybindsRegister custom key bindingshost
soundsPlay vanilla/custom soundsall
particlesSpawn vanilla particlesall
worldQuery blocks, biomes, entities, timeall
clientFPS, window, camera, system timeall
hostHost-only: chat, title, slots, cursorhost
avatarAvatar metadata, complexity, UUIDall
texturesCreate and manipulate textures at runtimeall
configPersist data between sessionshost
raycastRaycast blocks, entities, AABBsall
netHTTP requests and WebSocketsperm High+
fileRead/write files in the avatar folderperm
dataCreate byte buffersall
jsonBuild JSON objects programmaticallyall
resourcesAccess game resource pack dataall
vectorsConstruct Vec2/3/4all
matricesConstruct Mat2/3/4all
TIP
host vs all: Functions tagged host only do something when running as the avatar owner. On viewers' clients, those calls are silently ignored. Always check host:isHost() before host-only logic if needed.
Scripting

Events

Register functions with events.EVENT_NAME:register(fn, "optional_name") or the shorthand function events.EVENT_NAME() end.

Lua
-- Shorthand syntax
function events.TICK()
    -- runs every game tick
end

-- Register syntax (allows multiple handlers, named removal)
events.TICK:register(function()
    -- also runs every tick
end, "myHandler")

-- Remove by name
events.TICK:remove("myHandler")

-- Clear all handlers from an event
events.TICK:clear()

All Available Events

ENTITY_INIT
Runs once when the avatar's entity is loaded
Params: none
TICK
Every in-game tick (20/sec) when entity exists
Params: none
WORLD_TICK
Every in-game tick (runs even without entity)
Params: none
RENDER
Every frame, before avatar renders. Return value ignored.
Params: delta (0–1), context (string)
POST_RENDER
Every frame, after avatar renders
Params: delta (0–1), context (string)
WORLD_RENDER
Every frame before world renders (always runs)
Params: delta (0–1)
POST_WORLD_RENDER
Every frame after world renders (always runs)
Params: delta (0–1)
CHAT_SEND_MESSAGE
When you send a chat message. Return a string to replace it, or false to cancel.
Params: message (string)
CHAT_RECEIVE_MESSAGE
When a chat message is received
Params: text (string), jsonText (string)
KEY_PRESS
Any keyboard key pressed/released/held
Params: key (int), action (int), mods (int)
MOUSE_PRESS
Mouse button pressed/released. Return true to cancel.
Params: button (int), action (int), mods (int)
MOUSE_SCROLL
Mouse scrolled. Return true to cancel.
Params: delta (number)
MOUSE_MOVE
Mouse moved
Params: x (number), y (number)
CHAR_TYPED
Character typed on keyboard
Params: char (string), mods (int), codePoint (int)
USE_ITEM
Entity uses an item. Return true to cancel particles.
Params: item, action (string), particleCount (int)
ON_PLAY_SOUND
A sound starts playing. Return true to cancel it.
Params: id, pos, vol, pitch, loop, category
DAMAGE
You take damage. Return true to cancel totem/animation.
Params: type, source, attacker, amount
TOTEM
Totem of Undying activates. Return true to cancel anim.
Params: none
SKULL_RENDER
Called for each of your placed skull blocks
Params: delta, blockstate, pos, dir, entity
ARROW_RENDER
For each arrow shot by you. Return "CANCEL" to hide.
Params: delta, arrowEntity
TRIDENT_RENDER
For each trident thrown by you. Return "CANCEL" to hide.
Params: delta, tridentEntity
ITEM_RENDER
For each of your items being rendered
Params: item, mode, pos, rot, scale, leftHand
RESOURCE_RELOAD
Client resources reloaded (F3+T)
Params: none
Lua — Event Examples
-- RENDER: smooth rotation following body yaw
function events.RENDER(delta, context)
    local yaw = player:getBodyYaw(delta)
    models.MyModel.Body:setOffsetRot(0, -yaw, 0)
end

-- TICK: run only on host
function events.TICK()
    if not host:isHost() then return end
    -- host-only logic here
end

-- CHAT_SEND_MESSAGE: cancel any message containing "oops"
function events.CHAT_SEND_MESSAGE(msg)
    if msg:find("oops") then return false end
end

-- ON_PLAY_SOUND: mute explosion sounds
function events.ON_PLAY_SOUND(id, pos, vol, pitch, loop)
    if id == "minecraft:entity.generic.explode" then
        return true  -- cancel the sound
    end
end
Scripting

ModelPart API

Every cube, group, and mesh in your Blockbench model is a ModelPart. Access them through models.ModelName.PartName.

Transform Functions

Lua
local part = models.MyModel.Head

-- Position (offset from Blockbench position)
part:setPos(0, 5, 0)        -- move up 5 units
part:getPos()               -- returns Vec3
part:getTruePos()           -- pos + anim pos

-- Rotation (absolute, in degrees)
part:setRot(45, 0, 0)       -- tilt 45° on X
part:setOffsetRot(0, 10, 0) -- relative rot offset

-- Scale (multiplier, 1 = normal)
part:setScale(1.5, 1, 1)    -- 150% width
part:setOffsetScale(2, 2, 2)-- relative scale

-- Pivot (absolute)
part:setPivot(0, 24, 0)
part:setOffsetPivot(0, 4, 0)

Visibility & Rendering

Lua
-- Visibility
part:setVisible(false)     -- hide (children too)
part:getVisible()          -- true if this and all parents visible

-- Color (RGB 0-1)
part:setColor(1, 0.5, 0)   -- orange tint
part:setPrimaryColor(1, 0, 0)
part:setSecondaryColor(0, 0, 1)

-- Opacity (0-1). Needs TRANSLUCENT render type!
part:setOpacity(0.5)

-- Custom light level (0-15 block light, 0-15 sky light)
part:setLight(15, 15)      -- full bright

-- Overlay (white flash = 0-15, damage red = 0-15)
part:setOverlay(15, 0)    -- white flash

-- Render type
part:setPrimaryRenderType("TRANSLUCENT")
part:setSecondaryRenderType("EYES")

-- UV scrolling (0.0–1.0 normalized)
part:setUV(0.5, 0)          -- scroll half width
part:setUVPixels(8, 0)      -- in pixels

-- Parent type (controls where in skeleton this attaches)
part:setParentType("Head")

-- Texture override
part:setPrimaryTexture("RESOURCE", "minecraft:textures/entity/creeper/creeper.png")
part:setPrimaryTexture("CUSTOM", textures["myTexture"])

Render Tasks (Attach to Parts)

Lua
-- Add a text label floating above a part
local task = part:newText("myLabel")
task:setText('{"text":"Hello World","color":"gold"}')
task:setPos(0, 12, 0)
task:setScale(0.5, 0.5, 0.5)

-- Add a floating item
local item = part:newItem("myItem")
item:setItem("minecraft:diamond_sword")
item:setDisplayMode("GROUND")

-- Add a floating block
local block = part:newBlock("myBlock")
block:setBlock("minecraft:tnt")
block:setScale(0.5, 0.5, 0.5)

-- Add a sprite (texture quad)
local spr = part:newSprite("mySprite")
spr:setTexture(textures["myTex"], 16, 16)

-- Remove a task
part:removeTask("myLabel")

World Matrix

Lua
-- Get world position of a part (use in POST_RENDER)
function events.POST_RENDER()
    local mat = models.MyModel.Hand:partToWorldMatrix()
    local worldPos = mat:apply(vec(0, 0, 0))
    -- worldPos is now the hand's world coordinate
end
Scripting

Vanilla Model Parts

All available groups and parts under vanilla_model:

PartDescription
vanilla_model.ALLMulti-part: entire vanilla model
vanilla_model.PLAYERMulti-part: outer + inner layers + cape
vanilla_model.INNER_LAYERMulti-part: main body (no outer layer)
vanilla_model.OUTER_LAYERMulti-part: outer layer (hat, jacket, sleeves, pants)
vanilla_model.HEADHead (no hat)
vanilla_model.BODYBody (no jacket)
vanilla_model.LEFT_ARMLeft arm (no sleeve)
vanilla_model.RIGHT_ARMRight arm (no sleeve)
vanilla_model.LEFT_LEGLeft leg (no pants)
vanilla_model.RIGHT_LEGRight leg (no pants)
vanilla_model.HATHat layer
vanilla_model.JACKETJacket layer
vanilla_model.LEFT_SLEEVELeft sleeve layer
vanilla_model.RIGHT_SLEEVERight sleeve layer
vanilla_model.LEFT_PANTSLeft pants layer
vanilla_model.RIGHT_PANTSRight pants layer
vanilla_model.CAPEMulti-part: cape
vanilla_model.ARMORMulti-part: all armor
vanilla_model.HELMETMulti-part: helmet
vanilla_model.CHESTPLATEMulti-part: chestplate
vanilla_model.LEGGINGSMulti-part: leggings
vanilla_model.BOOTSMulti-part: boots
vanilla_model.ELYTRAMulti-part: elytra wings
vanilla_model.HELD_ITEMSMulti-part: items in hands
vanilla_model.LEFT_ITEMItem in left hand
vanilla_model.RIGHT_ITEMItem in right hand
vanilla_model.PARROTSMulti-part: shoulder parrots
Lua
-- Hide everything except armor
vanilla_model.ALL:setVisible(false)
vanilla_model.ARMOR:setVisible(true)

-- Read vanilla head rotation (for coping to custom model)
local headRot = vanilla_model.HEAD:getOriginRot()
models.MyModel.Head:setOffsetRot(headRot)
Scripting

Animations

Lua
-- Access: animations.modelName.animationName
local walk = animations.MyModel.walk

-- Playback controls
walk:play()
walk:pause()
walk:resume()
walk:stop()
walk:restart()

-- State checks
walk:isPlaying()   -- true/false
walk:isPaused()
walk:isStopped()

-- Time control
walk:setTime(1.5)    -- jump to 1.5 seconds
walk:getTime()
walk:setSpeed(2.0)  -- 2x speed
walk:setWeight(0.5) -- blend weight 0-1

-- Override vanilla transforms
walk:setOverride(true)

-- Loop mode
walk:setLoopMode("LOOP")    -- "ONCE", "LOOP", "HOLD"

-- Blend in/out over N ticks
walk:setBlend(5)

-- Play animation based on player movement
function events.TICK()
    if player:isMoving() then
        animations.MyModel.walk:play()
    else
        animations.MyModel.walk:stop()
    end
end
Entity APIs

Player API

Available via the global player (or user). Extends Entity and LivingEntity.

MethodReturnsDescription
:getFood()intHunger level 0–20
:getSaturation()numberSaturation level
:getExhaustion()numberExhaustion level
:getExperienceProgress()numberXP progress 0–1
:getExperienceLevel()intCurrent level
:getModelType()string"SLIM" or "DEFAULT"
:getGamemode()string"SURVIVAL", "CREATIVE", "ADVENTURE", "SPECTATOR"
:hasCape()boolHas a cape loaded
:hasSkin()boolHas a custom skin
:isSkinLayerVisible(layer)boolSkin customization layer visible
:isFishing()boolCurrently fishing
:getShoulderEntity(left)tableNBT of shoulder parrot (true=left)
:getTeamInfo()tableTeam data or nil
:getCooldownPercent(item)numberItem cooldown 0–1

LivingEntity Methods (inherited by player)

MethodReturnsDescription
:getHealth()numberCurrent health
:getMaxHealth()numberMax health
:getArmor()intArmor points
:getBodyYaw(delta)numberBody yaw in degrees
:getHeldItem(offhand)ItemStackItem in hand
:getActiveItem()ItemStackItem currently being used
:isUsingItem()boolEating/blocking/etc
:isClimbing()boolOn ladder/vine
:isGliding()boolElytra gliding
:isBlocking()boolShield blocking
:getDeathTime()intTicks dead (0 if alive)
:getArrowCount()intArrows stuck in entity
:getAbsorptionAmount()numberAbsorption hearts
:getSwingArm()string"MAIN_HAND" or "OFF_HAND"
:isSwingingArm()boolArm is swinging

Entity Methods (inherited by player)

MethodReturnsDescription
:getPos(delta)Vec3World position (interpolated with delta)
:getRot(delta)Vec2Pitch and yaw in degrees
:getVelocity()Vec3Current velocity
:getLookDir()Vec3Unit vector in look direction
:getUUID()stringUUID as string
:getType()stringEntity id e.g. "minecraft:player"
:getName()stringDisplay name
:getPose()string"STANDING", "CROUCHING", "SWIMMING", etc.
:isOnGround()boolCurrently on ground
:isSprinting()boolCurrently sprinting
:isSneaking()boolLogic sneak (no fall off edges)
:isCrouching()boolVisual sneak
:isMoving(noY)boolHas velocity (noY=true ignores vertical)
:isFalling()boolNegative Y velocity and not on ground
:isInWater()boolIn water block
:isUnderwater()boolEyes submerged
:isInLava()boolIn lava
:isOnFire()boolCurrently burning
:isWet()boolIn water, rain, or bubble column
:isGlowing()boolHas glow effect
:isInvisible()boolHas invisibility
:isAlive()boolAlive (not dead)
:getNbt()tableEntity NBT as Lua table
:getItem(slot)ItemStackItem at slot (1=mainhand, 2=offhand, 3-6=armor)
:getBoundingBox()Vec3Hitbox size (width, height, width)
:getVehicle()EntityEntity being ridden (or nil)
:getTargetedBlock(dist)BlockStateTargeted block (max 20 blocks)
:getTargetedEntity(dist)EntityTargeted entity (max 20 blocks)
:getNearestEntity(type,radius)EntityNearest entity (optional type filter)
:hasAvatar()boolEntity has a Figura avatar loaded
:getPermissionLevel()intPermission level (4 = op)
:getVariable(key)anyRead another avatar's stored variable
:getDimension()stringDimension id
:getFrozenTicks()intTicks in powder snow
Entity APIs

World API

Lua
-- Block queries
local bs = world:getBlockState(vec(0, 64, 0))
print(bs:getId())              -- "minecraft:grass_block"
print(bs:getProperties())     -- {snowy=false, ...}
bs:hasTag("minecraft:logs")   -- tag check
bs:isOpaque()

-- Get area of blocks (max 8x8x8)
local blocks = world:getBlocks(vec(-2,60,-2), vec(2,64,2))

-- Biome
local biome = world:getBiome(player:getPos())
print(biome:getId())           -- "minecraft:plains"
biome:getTemperature()
biome:isHot() biome:isCold()
biome:hasRain() biome:hasSnow()

-- Light
world:getLightLevel(player:getPos())
world:getSkyLightLevel(pos)
world:getBlockLightLevel(pos)
world:isOpenSky(pos)

-- Time & weather
world:getTime()          -- total world time
world:getTimeOfDay()     -- 0-24000
world:getDayTime()       -- 0-24000
world:getDay()
world:getMoonPhase()     -- 0-7
world:getRainGradient(delta)  -- 0-1
world:isThundering()

-- Entities
local players = world:getPlayers()  -- {name=EntityAPI, ...}
local ents = world:getEntities(pos1, pos2)
local ent = world:getEntity("uuid-string")

-- Misc
world:getDimension()   -- "minecraft:overworld"
world:getSpawnPoint()
world:getHeight(x, z) -- highest block at XZ
world:exists()         -- world loaded?

-- Parse new block/item from string
world:newBlock("minecraft:stone[lit=true]")
world:newItem("minecraft:diamond_sword", 1)
Client APIs

Renderer API

Controls how your avatar is rendered. Changes are local — only affect your own view for host-side settings.

Lua
-- Toggle rendering features
renderer:setRenderFire(false)       -- hide fire overlay
renderer:setRenderVehicle(false)    -- hide your vehicle
renderer:setRenderCrosshair(false)  -- hide crosshair
renderer:setRenderHud(false)       -- hide vanilla HUD
renderer:setForcePaperdoll(true)   -- always show paperdoll
renderer:setUpsideDown(true)       -- render upside down

-- Shadow
renderer:setShadowRadius(3)   -- 0–12 (default 0.5)

-- Camera position offset
renderer:setCameraPos(0, 0.5, 0)
renderer:setOffsetCameraPivot(0, 1, 0)

-- Camera rotation offset
renderer:setCameraRot(0, 45, 0)     -- absolute
renderer:setOffsetCameraRot(0, 10, 0)-- offset

-- FOV multiplier
renderer:setFov(1.5)   -- 150% FOV (nil = default)

-- Custom outline color (for glowing effect)
renderer:setOutlineColor(1, 0, 0, 1)  -- red glow

-- Custom fire texture
renderer:setPrimaryFireTexture("figura:textures/fire.png")

-- First-person arm visibility
renderer:setRenderLeftArm(true)
renderer:setRenderRightArm(true)

-- Eye offset (affects targeted block/entity)
renderer:setEyeOffset(0, 0.5, 0)

-- Block outline color
renderer:setBlockOutlineColor(0, 1, 1, 0.4)

-- Post effect (Super Secret Settings style)
renderer:setPostEffect("desaturate")

-- Query states
renderer:isFirstPerson()
renderer:isCameraBackwards()
Client APIs

Host API host only

All host functions only work when you are the avatar's owner. On other clients they silently do nothing.

Lua
-- Check if running as host
if not host:isHost() then return end

-- Logging
host:writeToLog("debug message")  -- to minecraft log, not chat
host:warnToLog("warning!")

-- Title / subtitle / actionbar
host:setTitle('{"text":"Hello","color":"gold"}')
host:setSubtitle('{"text":"subtitle"}')
host:setActionbar('{"text":"action bar"}')
host:setTitleTimes(10, 40, 10)  -- fade-in, stay, fade-out ticks
host:clearTitle()

-- Chat
host:sendChatMessage("Hello world!")
host:sendChatCommand("gamemode creative")
host:appendChatHistory("my message")
host:getChatMessage(1)    -- last chat message
host:setChatMessage(1, nil)  -- nil removes it
host:getChatText()          -- text being typed
host:setChatText("draft")
host:isChatOpen()

-- Inventory slots
host:getSlot(0)            -- get first hotbar slot
host:getSlot("armor.head")
host:setSlot(-1, world:newItem("minecraft:stick"))

-- Cursor
host:setUnlockCursor(true)   -- free the cursor
host:isCursorUnlocked()

-- Arm swing
host:swingArm(false)         -- false=main, true=offhand

-- Clipboard
host:getClipboard()
host:setClipboard("text")

-- Screen info
host:getScreen()            -- current screen class name
host:isContainerOpen()
host:getScreenSlot(0)

-- Status effects
local effects = host:getStatusEffects()
-- [{name=..., amplifier=..., duration=..., ambient=..., particles=...}]

-- Screenshot (returns Texture)
local tex = host:screenshot()

-- Player state
host:isJumping()
host:isFlying()
host:getAttackCharge()    -- 0-1
host:getAir()
host:getReachDistance()
host:getPickBlock()       -- targeted block
host:getPickEntity()      -- targeted entity
host:isAvatarUploaded()

-- Camera
host:isFirstPerson()
host:isCameraBackwards()
Client APIs

Client API

Lua
-- Performance / version
client:getFps()
client:getVersion()           -- "1.21.1"
client:getFiguraVersion()     -- "0.1.5"
client:getClientBrand()      -- "fabric", "forge", etc.
client:getServerBrand()      -- "vanilla", "paper", etc.
client:isSnapshot()

-- Window & GUI
client:getWindowSize()        -- Vec2 in pixels
client:getScaledWindowSize()  -- Vec2 in GUI units
client:getGuiScale()
client:getMousePos()          -- Vec2 pixel coords
client:isWindowFocused()
client:isHudEnabled()
client:isDebugOverlayEnabled() -- F3 screen open
client:isPaused()
client:getFov()

-- Camera
client:getCameraPos()
client:getCameraRot()
client:getCameraDir()
client:getCameraEntity()     -- spectated entity or self
client:getViewer()           -- the local player entity

-- Time
client:getSystemTime()       -- Unix time in milliseconds
client:getDate()             -- {year, month, day, hour, ...}
client:getFrameTime()        -- delta (same as RENDER event)

-- Text measuring
client:getTextWidth("Hello")
client:getTextHeight("Hello")

-- Memory
client:getUsedMemory()
client:getMaxMemory()
client:getJavaVersion()

-- Mod detection
client:isModLoaded("sodium")
client:hasShaderPackMod()
client:hasShaderPack()
client:getShaderPackName()

-- Server info
client:getServerData()   -- {name, ip, motd, ...}
client:getActiveLang()  -- "en_us"

-- HUD overlays
client:getActionbar()
client:getTitle()
client:getSubtitle()
client:getScoreboard()
client:getTabList()

-- Registries & resources
client:getRegistry("block")  -- all block IDs
client:hasResource("minecraft:textures/block/stone.png")
client:getActiveResourcePacks()

-- UUID utils
client:generateUUID()
client:compareVersions("0.1.4", "0.1.5")  -- -1, 0, 1

-- Translation
client:getTranslatedString("item.minecraft.diamond")
Features

Sounds API

Lua
-- Play a vanilla sound at a world position
sounds:playSound("minecraft:entity.player.levelup",
    player:getPos(), -- position
    1.0,             -- volume
    1.0              -- pitch
)

-- Play a custom sound from your avatar (.ogg file)
sounds:playSound("sounds/blast", pos, 1, 1)

-- Stop all sounds from your avatar
sounds:stopSound()
sounds:stopSound("minecraft:music.game")  -- specific ID

-- Register a custom sound at runtime from bytes
sounds:newSound("mySound", byteArray)

-- Check if a sound ID is valid
sounds:isPresent("minecraft:block.stone.break")

-- Get all custom sounds registered by this avatar
local customSounds = sounds:getCustomSounds()
PERM
Custom sounds require Custom Sounds permission enabled by the viewer. Number of concurrent sounds is limited by the viewer's Max Sounds setting.
Features

Particles API

Lua
-- Spawn a particle
particles:newParticle(
    "minecraft:flame",    -- particle ID
    player:getPos(),      -- world position
    vec(0, 0.1, 0)        -- velocity
)

-- Special dust particle (requires color + size)
particles:newParticle(
    "minecraft:dust 1 0 0 1.5",  -- red dust, size 1.5
    pos
)

-- Remove all particles from this avatar
particles:removeParticles()

-- Check if a particle ID exists
particles:isPresent("minecraft:heart")

-- Spawn hearts in a circle every tick
function events.TICK()
    for i = 0, 7 do
        local angle = i / 8 * math.pi * 2
        local pos = player:getPos() + vec(math.cos(angle), 2, math.sin(angle))
        particles:newParticle("minecraft:heart", pos)
    end
end
Features

Keybinds API host only

Lua
-- Create a keybind
local kb = keybinds:newKeybind("Toggle Wings", "key.keyboard.g")

-- Handle press and release
kb:setOnPress(function(modifiers)
    -- modifiers: bitmask (1=shift, 2=ctrl, 4=alt)
    pings.toggleWings(true)
end)

kb:setOnRelease(function()
    pings.toggleWings(false)
end)

-- Check state
kb:isPressed()
kb:getKey()        -- current key string
kb:getKeyName()    -- localized name
kb:isDefault()     -- not remapped by user?
kb:setEnabled(false)   -- disable this keybind
kb:setGui(true)        -- also trigger in GUIs

-- Change the key
kb:setKey("key.keyboard.h")
Features

Action Wheel

Hold the Action Wheel key (default B) to show the wheel. Up to 8 slots per page.

Lua
-- Create a page and set it active
local page = action_wheel:newPage("main")
action_wheel:setPage(page)

-- Add a simple action
local act = page:newAction()
    :title("Fly Mode")
    :item("minecraft:elytra")
    :color(0.4, 0.8, 1)
    :hoverColor(0.7, 1, 1)
    :onLeftClick(function() pings.toggleFly() end)

-- Toggle action (remembers state)
local toggle = page:newAction()
    :title("Wings OFF")
    :toggleTitle("Wings ON")
    :item("minecraft:feather")
    :onToggle(function(state)
        pings.setWings(state)
    end)

-- Scroll action
local scroll = page:newAction()
    :title("Volume")
    :onScroll(function(dir)
        volume = math.clamp(volume + dir * 0.1, 0, 1)
    end)

-- Custom texture on action
act:setTexture(textures["myTex"], 0, 0, 16, 16)

-- Multiple pages
local page2 = action_wheel:newPage("weapons")
action_wheel:setPage(page2)   -- switch to it

-- Check wheel state
action_wheel:isEnabled()      -- wheel open?
action_wheel:getSelected()   -- selected slot index
Features

Pings

Pings let you sync state from the host to all viewers. Define a function under pings, and when the host calls it, everyone runs it.

Lua
-- Define ping functions
function pings.setWingState(visible)
    models.MyModel.Wings:setVisible(visible)
end

function pings.playEffect(effectName)
    sounds:playSound("minecraft:entity.player.levelup", player:getPos())
    particles:newParticle("minecraft:totem_of_undying", player:getPos())
end

-- Call from host (e.g., on key press)
function events.TICK()
    if not host:isHost() then return end
    if wingKeybind:isPressed() then
        pings.setWingState(true)
    end
end
WARN
Ping limits: Pings are rate-limited and size-limited. Don't call them every tick — use them for discrete events (key press, action wheel click). Supported argument types: boolean, number, string, nil, and simple tables. No functions or complex userdata.
Features

Nameplate API requires Default+

Lua
-- Three nameplate locations
-- nameplate.ABOVE: above-head nameplate
-- nameplate.CHAT: name in chat
-- nameplate.LIST: name in tab list

-- Customize the above-head nameplate
nameplate.ABOVE:setText('[{"text":"★ ","color":"gold"},{"text":"MyName","color":"white"}]')
nameplate.ABOVE:setPos(0, 0.5, 0)
nameplate.ABOVE:setScale(1.5, 1.5, 1.5)
nameplate.ABOVE:setVisible(true)
nameplate.ABOVE:setBackground(false)  -- hide dark backdrop
nameplate.ABOVE:setShadow(true)

-- Chat nameplate
nameplate.CHAT:setText('[{"text":"☆ "},{"text":"MyName"}]')

-- Dynamic nameplate update every tick
function events.TICK()
    local hp = math.floor(player:getHealth())
    nameplate.ABOVE:setText('{"text":"HP: ' .. hp .. '","color":"red"}')
end
Features

Textures API

Lua
-- Create a new texture (filled with color)
local tex = textures:newTexture("myTex", 64, 64)

-- Pixel manipulation
tex:setPixel(0, 0, vec(1, 0, 0, 1))   -- RGBA 0-1
local col = tex:getPixel(0, 0)

-- Fill rectangle
tex:fill(0, 0, 32, 32, vec(0, 1, 0, 1))  -- green

-- Apply changes to GPU
tex:update()   -- call after pixel edits

-- Copy and retrieve textures
local copy = textures:copy("texCopy", tex)
local found = textures:get("myTex")

-- Grab a vanilla resource texture
local stone = textures:fromVanilla("minecraft:textures/block/stone.png")

-- Read from base64 string
local tex2 = textures:read("decodedTex", base64str)

-- Apply texture to a model part
models.MyModel.Head:setPrimaryTexture("CUSTOM", tex)

-- Animated texture example (in TICK)
local t = 0
function events.TICK()
    t = t + 1
    for x = 0, 15 do
        for y = 0, 15 do
            local v = (math.sin(x * 0.5 + t * 0.1) + 1) / 2
            tex:setPixel(x, y, vec(v, 0.5, 1, 1))
        end
    end
    tex:update()
end
Features

Config API host only

Save and load data between game sessions. Data is stored in a file in the avatar folder.

Lua
-- Save a value (any Lua primitive or table)
config:save("wingEnabled", true)
config:save("volume", 0.75)
config:save("theme", {r=1, g=0.5, b=0})

-- Load a value (nil if not saved)
local enabled = config:load("wingEnabled")

-- Load all saved values
local all = config:load()

-- Remove a value
config:save("wingEnabled", nil)

-- Change save file name (default = avatar name)
config:setName("myAvatarData")

-- Typical pattern: load on init, save on change
local wingEnabled = config:load("wing") or false

function pings.toggleWing()
    wingEnabled = not wingEnabled
    models.MyModel.Wings:setVisible(wingEnabled)
    if host:isHost() then
        config:save("wing", wingEnabled)
    end
end
Features

Raycast API

Lua
-- Raycast a block
local blockState, hitPos, hitSide = raycast:block(
    player:getPos() + vec(0, player:getEyeHeight(), 0),  -- from eye pos
    player:getPos() + player:getLookDir() * 10,           -- to 10 blocks ahead
    "COLLIDER",   -- BlockRaycastType
    "NONE"        -- FluidRaycastType
)
if blockState then
    print(blockState:getId(), hitPos, hitSide)
end

-- Raycast an entity
local entity, hitPos = raycast:entity(
    player:getPos() + vec(0, player:getEyeHeight(), 0),
    player:getPos() + player:getLookDir() * 15
)
if entity then
    print("Hit: ", entity:getName())
end

-- AABB raycast (custom bounding boxes)
local hit = raycast:aabb(
    startPos, endPos,
    {{-0.5, 0, -0.5, 0.5, 2, 0.5}}  -- {minX,minY,minZ,maxX,maxY,maxZ}
)
Features

Networking API requires High+ permission

Lua — HTTP
-- Check if networking is allowed
if not net:isNetworkingAllowed() then return end

-- GET request
local future = net.http:request("https://api.example.com/data")
    :method("GET")
    :send()

-- POST with body and headers
local future2 = net.http:request("https://api.example.com/submit")
    :method("POST")
    :header("Content-Type", "application/json")
    :body('{"key":"value"}')
    :send()

-- Handle the future result in TICK
function events.TICK()
    if future and future:isDone() then
        if future:hasError() then
            print("Error!")
        else
            local resp = future:getValue()
            print(resp:getResponseCode())
            -- resp:getData() returns an InputStream
        end
        future = nil
    end
end
Features

Render Tasks

Render Tasks attach to ModelParts and can render text, items, blocks, sprites, or entities at the part's location.

Task TypeKey MethodsDescription
newText(name):setText(json), :setAlignment(), :setShadow(), :setBackground()Render a JSON text component
newItem(name):setItem(id), :setDisplayMode(mode)Render an item in any display mode
newBlock(name):setBlock(id)Render a full block
newSprite(name):setTexture(tex,w,h), :setUV(u,v), :setSize(w,h)Render a 2D texture quad
newEntity(name):setNbt(str), :setHeadRotation()Render any entity by NBT

All tasks share :setPos(), :setRot(), :setScale(), :setVisible(), :setLight(), :setOverlay(), :setColor().

Visual

Emissive Textures

Emissive textures glow at full brightness regardless of world lighting.

Naming
skin.png       ← base texture
skin_e.png     ← emissive layer (suffix _e before .png)
IMPORTANT
Set non-glowing pixels to transparent black (#00000000). Any non-transparent pixel will glow. This also ensures compatibility with Iris/Oculus shaders.
TIP
For true bloom with shaders, set the render type to EYES: part:setPrimaryRenderType("EYES"). The default compatibility mode renders emissives as fullbright instead of actual bloom.
Visual

Render Types

TypeDescription
SOLIDDefault opaque rendering
CUTOUTAlpha cutout (on/off transparency, no blending)
CUTOUT_CULLCutout with back-face culling
TRANSLUCENTAlpha blending — required for setOpacity()
TRANSLUCENT_CULLTranslucent with culling
EYESFullbright, no shading — for emissive glow/bloom
EMISSIVEEmissive rendering with normal alpha blending
EMISSIVE_SOLIDEmissive opaque
END_PORTALThe animated end portal effect
GLINTEnchantment glint overlay
GLINT2Alternate enchantment glint
LINESRenders as wireframe lines
Visual

Parent Types

Parent types attach a model part to a specific vanilla skeleton position. Set in Blockbench (part name) or via Lua.

Parent TypeAttaches To
HeadPlayer head bone
BodyPlayer body bone
LeftArmLeft arm bone
RightArmRight arm bone
LeftLegLeft leg bone
RightLegRight leg bone
LeftItemPivotLeft hand item position
RightItemPivotRight hand item position
HelmetItemPivotHead item (helmet slot item) position
LeftElytraLeft elytra wing
RightElytraRight elytra wing
CapeCape bone
CameraFollows the camera
WorldFixed in world space (doesn't follow player)
NONENot attached to any parent (default)
Lua
-- Set parent type via Lua
models.MyModel.WingL:setParentType("LeftElytra")
models.MyModel.HUDElement:setParentType("Camera")

-- Or name the Blockbench group starting with the parent type:
-- Group named "Head" → auto-attaches to the head bone
System

Permissions System

Viewers control how much resource they grant to each avatar they see. You as the creator need to write your avatar to work gracefully within limits.

LevelSummaryFeatures Unlocked
BlockedAvatar completely disabledNone. Avatar not rendered or scripted.
LowMinimal trustBasic model rendering. Most Lua blocked. Ideal for tiny avatars.
DefaultStandard trustVanilla model edits, animations, sounds, particles. No nameplate.
HighHigh trustEverything in Default + nameplate, networking, custom render layers.
MaxMaximum trustAll features, unlimited resources.

Configurable Limits per Level

SettingControls
Init InstructionsMax Lua ops during script load phase
Tick InstructionsMax Lua ops per TICK event
World Tick InstructionsMax Lua ops per WORLD_TICK event
Render InstructionsMax Lua ops per RENDER event
World Render InstructionsMax Lua ops per WORLD_RENDER event
Animation InstructionsMax Lua ops per animation keyframe code
Max ComplexityMax rendered faces (1 cube = 6 faces; hidden parts don't count)
Animations ComplexityMax total animation channels playing at once
Max Particles / secCap on particles spawned
Max Sounds / secCap on sounds played
Avatar Sounds VolumeVolume multiplier for this avatar's sounds
Max Texture SizeMax pixel dimensions of runtime-created textures
Buffer Size / CountByte buffer limits for data processing
Max SocketsMax concurrent WebSocket connections
Vanilla Model ChangeToggle/untoggle (Default+)
Nameplate ChangeToggle/untoggle (High+)
NetworkingToggle/untoggle (High+)
Custom SoundsToggle/untoggle (Default+)
Custom SkullToggle/untoggle — avatar renders on skull blocks
Cancel SoundsToggle/untoggle — ON_PLAY_SOUND can cancel sounds
Cancel DamageToggle/untoggle — DAMAGE event can cancel effects
Render OffscreenToggle — render even when not in viewer's FOV
Custom Render LayersToggle — custom GLSL shaders via render layers
Help

FAQ

The folder needs an avatar.json file to be detected. Even an empty {} works. Also check that your OS isn't hiding extensions — the file must be avatar.json, not avatar.json.txt.
animations.ModelName.animName:play()
Replace ModelName with the .bbmodel filename (no extension) and animName with the animation name inside Blockbench.
Name your emissive texture the same as the base texture but add _e before the extension (e.g., skin_e.png). Set all non-glowing pixels to transparent black #00000000. For shader bloom use part:setPrimaryRenderType("EYES").
setRot sets an absolute rotation — completely overrides the Blockbench rotation. setOffsetRot adds to the Blockbench rotation. Use setOffsetRot if you want to rotate relative to the original position in your model.
No. Only players with Figura installed can see Figura avatars. Players without Figura see the default Minecraft skin. No server-side mod is required — only the client mod.
The host API only does something when the code runs on the avatar owner's machine. On viewers, host calls silently do nothing. Use pings to sync state, not host directly.
Use a keybind + a ping. Register the keybind (host-only), then on press call a ping function. The ping runs on all viewers.
local kb = keybinds:newKeybind("My Key", "key.keyboard.g")
kb:setOnPress(function() pings.doThing(true) end)
kb:setOnRelease(function() pings.doThing(false) end)
function pings.doThing(active)
    models.MyModel.Part:setVisible(active)
end
Instruction limits prevent poorly-written scripts from tanking FPS. Fix options: reduce loop complexity, break work across multiple ticks instead of one, avoid nested loops in RENDER, cache results instead of recalculating every frame.
Figura works with OptiFine but some issues may arise due to its closed-source nature. The recommended setup is Sodium + Iris (Fabric) or Rubidium + Oculus (Forge/NeoForge). These have better Figura compatibility.
You must set a compatible render type on the part first, then set opacity:
part:setPrimaryRenderType("TRANSLUCENT")
part:setOpacity(0.5)
Examples

★ First Avatar — Complete Script

A minimal but complete avatar script covering the most common patterns: hide vanilla model, drive animations from player state, and set up a keybind with ping sync.

script.lua — Full Starter Template
-- ─────────────────────────────────────────────
--  SETUP — runs once when avatar loads
-- ─────────────────────────────────────────────
vanilla_model.ALL:setVisible(false)  -- hide entire vanilla player
vanilla_model.ARMOR:setVisible(true)  -- but keep armor visible

-- ─────────────────────────────────────────────
--  STATE
-- ─────────────────────────────────────────────
local wingsOpen = false

-- ─────────────────────────────────────────────
--  PINGS — synced to all viewers
-- ─────────────────────────────────────────────
function pings.setWings(state)
    wingsOpen = state
    models.MyModel.Wings:setVisible(state)
    if state then
        animations.MyModel.wings_open:play()
    else
        animations.MyModel.wings_close:play()
    end
end

-- ─────────────────────────────────────────────
--  KEYBIND (host only)
-- ─────────────────────────────────────────────
if host:isHost() then
    local wingKey = keybinds:newKeybind("Toggle Wings", "key.keyboard.g")
    wingKey:setOnPress(function()
        pings.setWings(not wingsOpen)
    end)
end

-- ─────────────────────────────────────────────
--  TICK — game logic every 1/20 second
-- ─────────────────────────────────────────────
function events.TICK()
    -- Drive walk/idle animation from player movement
    if player:isMoving(true) then
        animations.MyModel.walk:play()
        animations.MyModel.idle:stop()
    else
        animations.MyModel.idle:play()
        animations.MyModel.walk:stop()
    end
end

-- ─────────────────────────────────────────────
--  RENDER — every rendered frame (use delta!)
-- ─────────────────────────────────────────────
function events.RENDER(delta)
    -- Copy vanilla head rotation to custom head
    local rot = player:getRot(delta)
    models.MyModel.Head:setOffsetRot(rot.x, 0, 0)

    -- Smooth body yaw
    local yaw = player:getBodyYaw(delta)
    models.MyModel.Body:setOffsetRot(0, -yaw, 0)
end
Examples

Animations — All Patterns

State Machine (walk / run / idle / fall)

Lua
local A = animations.MyModel  -- shorthand

function events.TICK()
    local onGround  = player:isOnGround()
    local moving    = player:isMoving(true)
    local sprinting  = player:isSprinting()
    local falling   = player:isFalling()
    local gliding   = player:isGliding()
    local crouching = player:isCrouching()

    -- Stop all before starting the right one
    A.idle:stop() A.walk:stop() A.run:stop()
    A.fall:stop() A.glide:stop() A.crouch:stop()

    if gliding then
        A.glide:play()
    elseif not onGround and falling then
        A.fall:play()
    elseif crouching then
        A.crouch:play()
    elseif sprinting then
        A.run:play()
    elseif moving then
        A.walk:play()
    else
        A.idle:play()
    end
end

Speed Walk Animation by Velocity

Lua
function events.TICK()
    local vel = player:getVelocity()
    local speed = math.sqrt(vel.x^2 + vel.z^2)  -- horizontal speed
    animations.MyModel.walk:setSpeed(speed * 10)  -- scale anim speed
    if speed > 0.01 then
        animations.MyModel.walk:play()
    else
        animations.MyModel.walk:stop()
    end
end

Blend Two Animations by Health

Lua
function events.TICK()
    local hp = player:getHealth()
    local maxHp = player:getMaxHealth()
    local t = hp / maxHp  -- 0 = dead, 1 = full

    animations.MyModel.healthy:setWeight(t)
    animations.MyModel.hurt:setWeight(1 - t)
    animations.MyModel.healthy:play()
    animations.MyModel.hurt:play()
end

One-Shot Attack Animation on Key

Lua
local attackAnim = animations.MyModel.attack
attackAnim:setLoopMode("ONCE")

if host:isHost() then
    local atkKey = keybinds:newKeybind("Attack", "key.keyboard.f")
    atkKey:setOnPress(function()
        pings.playAttack()
    end)
end

function pings.playAttack()
    attackAnim:restart()  -- restart each time
end
Examples

Model Control

Bobbing / Floating Effect

Lua
local t = 0
function events.RENDER(delta)
    t = t + delta * 0.1
    local bob = math.sin(t) * 2  -- ±2 units vertical
    models.MyModel:setPos(0, bob, 0)
end

Tail Wag — Oscillating Part

Lua
local t = 0
function events.RENDER(delta)
    t = t + delta * 0.15
    local wag = math.sin(t) * 25  -- ±25 degrees
    models.MyModel.Tail:setOffsetRot(0, wag, 0)
end

Scale Pulse (Heartbeat)

Lua
local t = 0
function events.RENDER(delta)
    t = t + delta * 0.08
    local s = 1 + math.sin(t) * 0.05  -- ±5% scale
    models.MyModel:setScale(s, s, s)
end

Part Follows Look Direction (Eye Movement)

Lua
function events.RENDER(delta)
    local rot = player:getRot(delta)
    -- Clamp pitch so eyes don't over-rotate
    local pitch = math.clamp(rot.x, -30, 30)
    local yaw   = math.clamp(rot.y, -45, 45)  -- relative to body
    models.MyModel.EyeL:setOffsetRot(pitch, yaw, 0)
    models.MyModel.EyeR:setOffsetRot(pitch, yaw, 0)
end

Show/Hide Part Based on Held Item

Lua
function events.TICK()
    local item = player:getHeldItem(false)  -- main hand
    local id = item:getId()

    -- Show sword model only when holding a sword
    local holdingSword = id:find("sword") ~= nil
    models.MyModel.SwordProp:setVisible(holdingSword)

    -- Show magic staff when holding a stick
    models.MyModel.Staff:setVisible(id == "minecraft:stick")
end

Swap Armor Textures Based on Equipment

Lua
function events.TICK()
    local helmet = player:getItem(5)  -- slot 5 = helmet
    if helmet:getId() == "minecraft:netherite_helmet" then
        models.MyModel.Hat:setPrimaryTexture("CUSTOM", textures["hat_nether"])
    else
        models.MyModel.Hat:setPrimaryTexture("PRIMARY")  -- restore default
    end
end

UV Scroll (Animated Texture)

Lua
local scroll = 0
function events.RENDER(delta)
    scroll = (scroll + delta * 0.01) % 1
    models.MyModel.Portal:setUV(scroll, 0)
end

Render Task — Floating Item Over Head

Lua
-- Set up once on load
local task = models.MyModel.Head:newItem("crownItem")
task:setItem("minecraft:golden_helmet")
task:setDisplayMode("GUI")
task:setPos(0, 12, 0)
task:setScale(0.6, 0.6, 0.6)

-- Slowly spin it
local t = 0
function events.RENDER(delta)
    t = t + delta * 3
    task:setRot(0, t, 0)
end

World-Space Part (Fixed to World, Not Player)

Lua
-- A part anchored to world position (e.g. orbiting rune)
models.MyModel.Rune:setParentType("World")

local t = 0
function events.RENDER(delta)
    t = t + delta * 0.05
    local r = 1.5
    local pos = player:getPos(delta)
    models.MyModel.Rune:setPos(
        pos.x + math.cos(t) * r,
        pos.y + 1,
        pos.z + math.sin(t) * r
    )
    models.MyModel.Rune:setRot(0, -math.deg(t), 0)
end
Examples

Player Data — Practical Patterns

Color Model by Health (green → yellow → red)

Lua
function events.TICK()
    local t = player:getHealth() / player:getMaxHealth()
    -- t=1: green. t=0.5: yellow. t=0: red
    local r = math.clamp(2 - t * 2, 0, 1)
    local g = math.clamp(t * 2, 0, 1)
    models.MyModel:setColor(r, g, 0)
end

Glow Brighter in the Dark

Lua
function events.TICK()
    local pos = player:getPos()
    local light = world:getLightLevel(pos)  -- 0-15
    -- In dark (light=0) → full bright; in light (light=15) → dim glow
    local glow = math.map(light, 0, 15, 15, 0)
    models.MyModel.GlowPart:setLight(math.floor(glow), 15)
end

Scale Avatar by Saturation (food buff)

Lua
local currentScale = 1
function events.RENDER(delta)
    local sat = math.clamp(player:getSaturation() / 20, 0, 1)
    local targetScale = math.lerp(0.9, 1.1, sat)
    currentScale = math.lerp(currentScale, targetScale, 0.05)  -- smooth
    models.MyModel:setScale(currentScale, currentScale, currentScale)
end

React to Potion Effects

Lua
function events.TICK()
    if not host:isHost() then return end
    local effects = host:getStatusEffects()
    local hasSpeed = false
    local hasInvis = false

    for _, e in pairs(effects) do
        if e.name == "minecraft:speed"  then hasSpeed = true end
        if e.name == "minecraft:invisibility" then hasInvis = true end
    end

    models.MyModel:setOpacity(hasInvis and 0.3 or 1.0)
    animations.MyModel.speedLines:setVisible(hasSpeed)
end

Detect Creative vs Survival

Lua
function events.TICK()
    local gm = player:getGamemode()
    -- Show creative "aura" in creative mode
    models.MyModel.CreativeAura:setVisible(gm == "CREATIVE")
end
Examples

World Queries

Change Model by Biome

Lua
function events.TICK()
    local biome = world:getBiome(player:getPos())
    local id = biome:getId()

    -- Underwater helmet
    models.MyModel.Helm:setVisible(player:isUnderwater())

    -- Snow effect in cold biomes
    models.MyModel.SnowLayer:setVisible(biome:hasSnow())

    -- Desert glow in hot biomes
    models.MyModel.SunGlow:setVisible(biome:isHot())
end

Check What Block You're Standing On

Lua
function events.TICK()
    local pos = player:getPos()
    -- Block directly below the player
    local below = world:getBlockState(vec(
        math.floor(pos.x), math.floor(pos.y - 0.1), math.floor(pos.z)
    ))

    -- Different footstep particles per surface
    if player:isMoving(true) and player:isOnGround() then
        local id = below:getId()
        if id == "minecraft:grass_block" then
            particles:newParticle("minecraft:happy_villager", pos)
        elseif id == "minecraft:sand" then
            particles:newParticle("minecraft:cloud", pos)
        end
    end
end

Day/Night Appearance Cycle

Lua
function events.TICK()
    local tod = world:getDayTime()  -- 0-24000
    local isNight = tod > 13000 and tod < 23000
    local rain = world:getRainGradient(1) > 0.5

    models.MyModel.NightForm:setVisible(isNight)
    models.MyModel.DayForm:setVisible(not isNight)
    models.MyModel.Umbrella:setVisible(rain)

    -- Moon phase ear shape
    local moonPhase = world:getMoonPhase()  -- 0-7
    models.MyModel.Ears:setScale(1 + moonPhase * 0.05, 1, 1)
end

Scan Blocks Around Player (Aura)

Lua
local nearLava = false
function events.TICK()
    local pos = player:getPos()
    nearLava = false
    -- Scan 3x3x3 area around player
    local blocks = world:getBlocks(pos - vec(2,2,2), pos + vec(2,2,2))
    for _, b in ipairs(blocks) do
        if b:getId():find("lava") then
            nearLava = true
            break
        end
    end
    models.MyModel.FireAura:setVisible(nearLava)
end
Examples

HUD & UI

Custom Health Bar (Render Task Text)

Lua
-- Attach a health bar text above head
local hpTask = models.MyModel.Head:newText("hpBar")
hpTask:setPos(0, 15, 0)
hpTask:setScale(0.4, 0.4, 0.4)
hpTask:setBackground(false)
hpTask:setShadow(true)

function events.TICK()
    local hp = player:getHealth()
    local max = player:getMaxHealth()
    local bars = math.floor(hp / max * 10)
    local bar = string.rep("█", bars) .. string.rep("░", 10 - bars)
    local col = hp > max * 0.5 and "green" or (hp > max * 0.25 and "yellow" or "red")
    hpTask:setText('{"text":"'..bar..'","color":"'..col..'"}')
end

Camera-Facing HUD Element

Lua
-- Part with parent type Camera always faces viewer
models.MyModel.HUD:setParentType("Camera")
models.MyModel.HUD:setPos(0, 0, -1)  -- 1 unit in front of camera
models.MyModel.HUD:setScale(0.1, 0.1, 0.1)

Show Title on Level Up

Lua
local lastLevel = -1
function events.TICK()
    if not host:isHost() then return end
    local lvl = player:getExperienceLevel()
    if lastLevel >= 0 and lvl > lastLevel then
        host:setTitle('{"text":"Level Up! ★","color":"gold","bold":true}')
        host:setSubtitle('{"text":"Level ' .. lvl .. '","color":"yellow"}')
        host:setTitleTimes(5, 50, 10)
    end
    lastLevel = lvl
end
Examples

Sounds & Particle Effects

Footstep Sounds

Lua
local stepTimer = 0
function events.TICK()
    stepTimer = stepTimer - 1
    if player:isMoving(true) and player:isOnGround() and stepTimer <= 0 then
        local spd = player:isSprinting() and 6 or 10  -- faster steps when sprinting
        stepTimer = spd
        sounds:playSound(
            "minecraft:entity.player.swim",
            player:getPos(),
            0.4,  -- volume
            math.random() * 0.4 + 0.8  -- random pitch
        )
    end
end

Particle Trail While Moving

Lua
function events.TICK()
    if not player:isMoving() then return end
    local pos = player:getPos() + vec(0, 0.5, 0)
    -- Spread particles slightly
    for i = 1, 3 do
        local spread = vec(
            (math.random() - 0.5) * 0.3,
            math.random() * 0.2,
            (math.random() - 0.5) * 0.3
        )
        particles:newParticle("minecraft:end_rod", pos + spread)
    end
end

Splash Effect on Landing

Lua
local wasFalling = false
function events.TICK()
    local falling = player:isFalling()
    local onGround = player:isOnGround()

    if wasFalling and onGround then
        -- Just landed!
        local pos = player:getPos()
        for i = 1, 20 do
            local angle = i / 20 * math.pi * 2
            local vel = vec(math.cos(angle) * 0.3, 0.3, math.sin(angle) * 0.3)
            particles:newParticle("minecraft:cloud", pos, vel)
        end
        sounds:playSound("minecraft:block.stone.hit", pos, 0.8, 0.8)
    end
    wasFalling = falling and not onGround
end

Cancel Explosion Sounds (example of ON_PLAY_SOUND)

Lua
function events.ON_PLAY_SOUND(id, pos, vol, pitch, loop, category)
    -- Cancel all explosion sounds
    if id:find("explode") or id:find("explosion") then
        return true  -- returning true cancels the sound
    end
    -- Replace creeper hiss with a meow
    if id == "minecraft:entity.creeper.primed" then
        sounds:playSound("minecraft:entity.cat.ambient", pos, vol, pitch)
        return true  -- cancel original
    end
end
Examples

Keybind + Ping — Sync to All Viewers

Hold-to-Activate with Smooth Lerp

Lua
local powerActive = false
local powerLerp = 0

function pings.setPower(active)
    powerActive = active
end

if host:isHost() then
    local key = keybinds:newKeybind("Power Mode", "key.keyboard.v")
    key:setOnPress(function()   pings.setPower(true)  end)
    key:setOnRelease(function() pings.setPower(false) end)
end

function events.RENDER(delta)
    local target = powerActive and 1 or 0
    powerLerp = math.lerp(powerLerp, target, 0.1)
    -- Scale and color modulated by power level
    local s = 1 + powerLerp * 0.3
    models.MyModel:setScale(s, s, s)
    models.MyModel:setColor(1, 1 - powerLerp * 0.5, 1 - powerLerp)
end

Multi-State Toggle (Cycle through modes)

Lua
local modes = {"normal", "stealth", "power"}
local modeIdx = 1

function pings.setMode(idx)
    modeIdx = idx
    local m = modes[idx]
    models.MyModel.NormalForm:setVisible(m == "normal")
    models.MyModel.StealthForm:setVisible(m == "stealth")
    models.MyModel.PowerForm:setVisible(m == "power")
end

if host:isHost() then
    keybinds:newKeybind("Cycle Mode", "key.keyboard.m")
        :setOnPress(function()
            local next = (modeIdx % #modes) + 1
            pings.setMode(next)
            host:setActionbar('{"text":"Mode: '..modes[next]..'","color":"aqua"}')
        end)
end

-- Initialize default
pings.setMode(1)
Examples

Action Wheel — Full Setup

Multi-Page Wheel with Scroll Slot

Lua
-- ── PAGE 1: Avatar Controls ──────────────────
local page1 = action_wheel:newPage("controls")
action_wheel:setPage(page1)

-- Toggle action (remembers state, shows different title)
local wingToggle = page1:newAction()
    :title("Wings: OFF")
    :toggleTitle("Wings: ON")
    :item("minecraft:feather")
    :color(0.8, 0.8, 0.8)
    :toggleColor(0.5, 0.9, 1.0)
    :onToggle(function(state)
        pings.setWings(state)
    end)

-- Scroll wheel for glow color
local glowColors = {
    vec(1,0,0), vec(0,1,0), vec(0,0,1),
    vec(1,1,0), vec(1,0,1), vec(0,1,1)
}
local colorIdx = 1

page1:newAction()
    :title("Glow Color")
    :item("minecraft:glowstone_dust")
    :onScroll(function(dir)
        colorIdx = ((colorIdx - 1 + math.floor(dir)) % #glowColors) + 1
        pings.setGlowColor(colorIdx)
    end)
    :onLeftClick(function()
        colorIdx = (colorIdx % #glowColors) + 1
        pings.setGlowColor(colorIdx)
    end)

-- Switch to page 2
page1:newAction()
    :title("→ Emotes")
    :item("minecraft:map")
    :onLeftClick(function()
        action_wheel:setPage("emotes")
    end)

-- ── PAGE 2: Emotes ───────────────────────────
local page2 = action_wheel:newPage("emotes")

local emoteList = {
    {"Wave",   "minecraft:hand",   "wave"},
    {"Dance",  "minecraft:music_disc_cat", "dance"},
    {"Sit",    "minecraft:oak_stairs", "sit"},
}
for _, e in ipairs(emoteList) do
    local name, item, anim = e[1], e[2], e[3]
    page2:newAction()
        :title(name)
        :item(item)
        :onLeftClick(function()
            pings.playEmote(anim)
        end)
end

page2:newAction()
    :title("← Back")
    :item("minecraft:arrow")
    :onLeftClick(function()
        action_wheel:setPage("controls")
    end)

-- Ping handlers
function pings.playEmote(name)
    animations.MyModel[name]:restart()
end
function pings.setGlowColor(idx)
    colorIdx = idx
    local c = glowColors[idx]
    models.MyModel.Glow:setColor(c.x, c.y, c.z)
end
Examples

Nameplate

Dynamic Nameplate with Stats

Lua
-- Rich formatted name with health indicator
local updateTimer = 0
function events.TICK()
    updateTimer = updateTimer - 1
    if updateTimer > 0 then return end
    updateTimer = 5  -- update every 5 ticks

    local hp = math.floor(player:getHealth())
    local maxHp = math.floor(player:getMaxHealth())
    local heartColor = hp > maxHp * 0.5 and "red" or "dark_red"
    local name = player:getName()

    nameplate.ABOVE:setText(toJson({
        {text="❤ ", color=heartColor},
        {text=hp.."/"..maxHp.." ", color="white"},
        {text=name, color="aqua", bold=true}
    }))
end

Chat Prefix (Role Badge)

Lua
-- Show a colored prefix in chat messages
nameplate.CHAT:setText(toJson({
    {text="[★] ", color="gold", bold=true},
    {text=player:getName(), color="white"}
}))

-- Tab list prefix
nameplate.LIST:setText(toJson({
    {text="◈ ", color="aqua"},
    {text=player:getName(), color="white"}
}))
Examples

Textures — Runtime Pixel Art

Dynamic Color Fill Based on Game State

Lua
-- Create a small texture and assign to a model part
local tex = textures:newTexture("healthTex", 16, 4)
models.MyModel.HealthBar:setPrimaryTexture("CUSTOM", tex)

local lastHp = -1
function events.TICK()
    local hp = player:getHealth()
    if hp == lastHp then return end  -- only update when changed
    lastHp = hp

    local filled = math.floor((hp / player:getMaxHealth()) * 16)
    for x = 0, 15 do
        local color = x < filled
            and vec(0.9, 0.1, 0.1, 1)   -- filled: red
            or  vec(0.2, 0.2, 0.2, 1)   -- empty: dark gray
        for y = 0, 3 do
            tex:setPixel(x, y, color)
        end
    end
    tex:update()
end

Plasma / Animated Noise Texture

Lua
local ptex = textures:newTexture("plasma", 32, 32)
models.MyModel.GlowPart:setPrimaryTexture("CUSTOM", ptex)
models.MyModel.GlowPart:setPrimaryRenderType("EYES")

local t = 0
function events.TICK()
    t = t + 0.05
    for x = 0, 31 do
        for y = 0, 31 do
            local v = math.sin(x * 0.4 + t) + math.sin(y * 0.4 + t * 1.3)
            v = (v + 2) / 4  -- normalize to 0-1
            ptex:setPixel(x, y, vec(v, 0.3, 1 - v, 1))
        end
    end
    ptex:update()
end
Examples

Camera & Visual FX

Camera Shake on Damage

Lua
local shakeTime = 0
local shakeIntensity = 0

function events.DAMAGE(type, source, attacker, amount)
    shakeTime = 10
    shakeIntensity = math.clamp(amount * 0.5, 1, 5)
end

function events.RENDER(delta)
    if shakeTime > 0 then
        shakeTime = shakeTime - delta
        local i = shakeIntensity * (shakeTime / 10)
        renderer:setOffsetCameraRot(
            (math.random() - 0.5) * i,
            (math.random() - 0.5) * i,
            0
        )
    else
        renderer:setOffsetCameraRot(0, 0, 0)
    end
end

FOV Zoom on Right Click (Sniper)

Lua
local zoomed = false
local fovLerp = 1

function pings.setZoom(z)
    zoomed = z
end

if host:isHost() then
    local zoomKey = keybinds:newKeybind("Zoom", "key.mouse.right")
    zoomKey:setOnPress(function()   pings.setZoom(true)  end)
    zoomKey:setOnRelease(function() pings.setZoom(false) end)
end

function events.RENDER(delta)
    local target = zoomed and 0.2 or 1.0  -- 0.2 = 20% FOV (strong zoom)
    fovLerp = math.lerp(fovLerp, target, 0.15)
    renderer:setFov(fovLerp)
end

Outline Color Based on Team

Lua
function events.TICK()
    local team = player:getTeamInfo()
    if team then
        -- Use the team color for glow outline
        local c = team.color  -- returns a color string like "red"
        renderer:setOutlineColor(
            c == "red"  and 1 or 0,
            c == "green" and 1 or 0,
            c == "blue"  and 1 or 0,
            1
        )
    end
end

First-Person Arm Sway

Lua
local swayX, swayY = 0, 0
local lastRot = vec(0, 0)

function events.RENDER(delta)
    local rot = player:getRot(delta)
    local dX = rot.x - lastRot.x
    local dY = rot.y - lastRot.y
    lastRot = rot

    -- Smooth sway
    swayX = math.lerp(swayX, -dX * 0.3, 0.1)
    swayY = math.lerp(swayY, -dY * 0.3, 0.1)

    if renderer:isFirstPerson() then
        models.MyModel.ArmR:setOffsetPos(swayY, swayX, 0)
    end
end
Examples

Config & Persistent Save

Save All Avatar Settings

Lua
-- Default settings
local settings = {
    wingEnabled = false,
    glowColor   = 1,
    volume      = 1.0,
    mode        = "normal",
}

-- Load saved settings on startup (host only)
if host:isHost() then
    local saved = config:load()  -- returns all saved keys
    if saved then
        for k, v in pairs(saved) do
            if settings[k] ~= nil then
                settings[k] = v
            end
        end
    end
end

-- Helper: save all and apply via ping
local function applyAndSave()
    pings.applySettings(settings.wingEnabled, settings.glowColor, settings.mode)
    if host:isHost() then
        for k, v in pairs(settings) do
            config:save(k, v)
        end
    end
end

function pings.applySettings(wings, color, mode)
    settings.wingEnabled = wings
    settings.glowColor   = color
    settings.mode        = mode
    models.MyModel.Wings:setVisible(wings)
    -- etc.
end

-- Apply saved settings once loaded
applyAndSave()
Examples

Raycast — Practical Uses

Laser Pointer (Beam to First Hit)

Lua
-- Beam that extends from hand to the first block/entity hit
local laser = models.MyModel.Hand:newSprite("laser")
laser:setTexture(textures["laser"], 1, 16)

function events.TICK()
    local eyePos = player:getPos() + vec(0, player:getEyeHeight(), 0)
    local dir    = player:getLookDir()

    local hit, hitPos = raycast:block(eyePos, eyePos + dir * 20)
    if not hit then
        local ent, entPos = raycast:entity(eyePos, eyePos + dir * 20)
        hitPos = entPos or (eyePos + dir * 20)
    end

    local dist = (hitPos - eyePos):length()
    laser:setScale(0.1, dist * 16, 0.1)  -- stretch to hit point
end

Ground Detection (Shadow / Landing Marker)

Lua
function events.TICK()
    local pos = player:getPos()
    -- Cast straight down to find ground
    local hit, groundPos = raycast:block(pos, pos - vec(0, 20, 0))
    if hit then
        local dist = pos.y - groundPos.y
        -- Scale shadow by distance (fades when high)
        local alpha = math.clamp(1 - dist / 20, 0, 1)
        models.MyModel.GroundShadow:setOpacity(alpha)
        models.MyModel.GroundShadow:setPos(
            pos.x - groundPos.x,
            groundPos.y + 0.05,
            pos.z - groundPos.z
        )
    end
end
Examples

Networking — HTTP Requests

REQUIRES
High or Max permission must be granted by the viewer for networking to work. Always check net:isNetworkingAllowed() first.

Fetch a Remote JSON Config

Lua
local remoteFuture = nil

function events.ENTITY_INIT()
    if not net:isNetworkingAllowed() then
        print("Networking not allowed by viewer")
        return
    end
    if not net:isLinkAllowed("https://raw.githubusercontent.com/...") then
        return
    end
    remoteFuture = net.http:request("https://raw.githubusercontent.com/your/repo/main/config.json"):send()
end

function events.TICK()
    if not remoteFuture then return end
    if not remoteFuture:isDone() then return end

    if remoteFuture:hasError() then
        print("Fetch failed!")
        remoteFuture = nil
        return
    end

    local resp = remoteFuture:getValue()
    if resp:getResponseCode() == 200 then
        -- Read all bytes from stream and parse as JSON
        local stream = resp:getData()
        local dataFuture = stream:readAsync(99999)
        -- Handle dataFuture in next tick cycle...
    end
    remoteFuture = nil
end
Examples

Advanced Patterns

Share Data Between Avatars

Lua
-- Store data others can read
avatar:store("team", "blue")
avatar:store("level", 42)
avatar:store("form", "dragon")

-- Read another player's stored data
function events.TICK()
    local players = world:getPlayers()
    for name, ply in pairs(players) do
        if ply:hasAvatar() then
            local theirTeam = ply:getVariable("team")
            if theirTeam == "blue" then
                print(name .. " is on the blue team!")
            end
        end
    end
end

Multiple Files — Modular Structure

main.lua
-- main.lua — entry point
local anim   = require("modules/animations")
local fx     = require("modules/effects")
local ui     = require("modules/ui")

-- Setup
vanilla_model.ALL:setVisible(false)
anim.init()
ui.init()

function events.TICK()
    anim.tick()
    fx.tick()
end

function events.RENDER(delta)
    anim.render(delta)
end
modules/animations.lua
-- Return a module table
local M = {}
local A = animations.MyModel

function M.init()
    A.idle:setLoopMode("LOOP")
    A.walk:setLoopMode("LOOP")
end

function M.tick()
    if player:isMoving(true) then
        A.walk:play() A.idle:stop()
    else
        A.idle:play() A.walk:stop()
    end
end

function M.render(delta)
    local rot = player:getRot(delta)
    models.MyModel.Head:setOffsetRot(rot.x, 0, 0)
end

return M

Detect When Player Enters Water

Lua
local wasInWater = false
function events.TICK()
    local inWater = player:isInWater()
    if inWater and not wasInWater then
        -- Just entered water
        pings.splash()
    elseif not inWater and wasInWater then
        -- Just exited water
        pings.drip()
    end
    wasInWater = inWater
    -- Swap model for underwater form
    models.MyModel.LandForm:setVisible(not inWater)
    models.MyModel.WaterForm:setVisible(inWater)
end

Avatar Communicates Avatar Data to Others

Lua
-- Get avatar info
print("Avatar size: " .. avatar:getSize() .. " bytes")
print("Complexity: " .. avatar:getComplexity())
print("Instructions used: " .. avatar:getCurrentInstructions())
print("Owner UUID: " .. avatar:getUUID())
print("Can edit nameplate: " .. tostring(avatar:canEditVanillaModel()))

-- Share with others via store()
avatar:store("complexity", avatar:getComplexity())
Libraries

EZAnims v3.0.0

Automaticamente detecta animações do Blockbench pelo nome e as reproduz conforme o estado do player. É só nomear as animações corretamente — sem configuração adicional.

Setup

Lua
-- Coloca EZAnims.lua na pasta do avatar, depois:
local anims = require("EZAnims")

-- Adiciona seu modelo
local myModel = anims:addBBModel(animations.model)

-- Pronto! As animações tocam automaticamente pelo nome.

Nomes de Animações — Exclusivas (só uma toca por vez)

NomeQuando toca
idleParado no chão
walkAndando
walkbackAndando para trás
sprintCorrendo
jumpupSubindo de um pulo intencional
jumpdownDescendo de um pulo intencional
fallCaindo de grande altura
crouchAgachado (base para outros estados agachados)
crouchwalkAndando agachado
elytraVoando com elytra
elytradownMergulhando com elytra
swimNadando
flyVoando em criativo (base)
climbEscalando escada/trepadeira (base)
sleepDormindo
sitSentado em veículo
crawlEngatinhando
hurtLevando dano
deathMorrendo
waterSubmerso em água (base)
tridentImpulso com tridente Riptide

Nomes de Animações — Inclusivas (tocam junto com as exclusivas)

NomeQuando toca
attackR / attackLAtacando (loop once)
mineR / mineLMinerando (loop once)
holdR / holdLSegurando qualquer item
eatR / eatLComendo
drinkR / drinkLBebendo
blockR / blockLBloqueando com escudo
bowR / bowLPuxando arco
crossR / crossLSegurando besta carregada
spyglassR / spyglassLUsando luneta
spearR / spearLPreparando tridente
brushR / brushLUsando escova arqueológica
TIP
Múltiplas animações: Adicione número ao final para ter várias do mesmo tipo. Ex: idle e idle2 ambas tocam ao ficar parado. Outros: Sufixo _outro toca quando o estado termina. Ex: idle_outro.

Funções Avançadas

Lua
local anims = require("EZAnims")
local myModel = anims:addBBModel(animations.model)

-- Múltiplos modelos na mesma instância
local combined = anims:addBBModel(animations.model, animations.model2)

-- Estado personalizado: toca animações com sufixo _sword
-- Ex: idle_sword, walk_sword
myModel:setState("sword")
myModel:setState()   -- volta ao normal

-- Verificar estado atual
myModel:getAnimationStates()           -- tabela com todos
myModel:getAnimationStates("idle")    -- boolean: está idling?

-- Overriders: uma animação para todas as exclusivas
myModel:addExcluOverrider(animations.model.attack)
myModel:addIncluOverrider(animations.model.special)
myModel:addAllOverrider(animations.model.cutscene)

-- Ligar/desligar categorias
myModel:setExcluOff(true)   -- desliga exclusivas
myModel:setIncluOff(false)  -- religa inclusivas
myModel:setAllOff(true)     -- desliga tudo

-- Velocidade de queda para trigger fall
anims:setFallVel(-1.0)  -- padrão: -0.6

-- Blend times (requer GSAnimBlend)
myModel:setBlendTimes(10)      -- 10 ticks para ambas
myModel:setBlendTimes(5, 8)   -- 5 exclusivas, 8 inclusivas

-- Estado: está voando em criativo?
anims:isFlying()
anims:isJumping()
Libraries

SquAPI v1.2.1

Coleção de módulos de física e comportamento para avatares. Requer SquAPI.lua e SquAssets.lua na pasta do avatar, com os módulos em SquAPI_modules/.

Lua — Setup
local squapi = require("SquAPI")

Ear — Física de Orelhas

Adiciona física às orelhas quando você move o personagem.

Lua
squapi.ear:new(
    models.model.root.Head.LeftEar,   -- leftEar (obrigatório)
    models.model.root.Head.RightEar,  -- rightEar (nil = sem orelha direita)
    1,        -- rangeMultiplier (1) — distância de rotação
    false,    -- horizontalEars (false) — true para orelhas tipo élfico
    2,        -- bendStrength (2) — força ao mover
    true,     -- doEarFlick (true) — animação aleatória de tremida
    400,      -- earFlickChance (400) — menor = mais frequente
    0.1,      -- earStiffness (0.1)
    0.8       -- earBounce (0.8)
)

Tail — Física de Cauda

Física para caudas com qualquer número de segmentos. Cada segmento deve estar dentro do anterior no Blockbench.

Lua
local myTail = {
    models.model.root.Body.tail,
    models.model.root.Body.tail.tailSeg1,
    models.model.root.Body.tail.tailSeg1.tailSeg2,
    models.model.root.Body.tail.tailSeg1.tailSeg2.tailTip,
}

squapi.tail:new(myTail,
    15,   -- idleXMovement (15) — balanço lateral
    5,    -- idleYMovement (5) — balanço vertical
    1.2, -- idleXSpeed (1.2)
    2,    -- idleYSpeed (2)
    2,    -- bendStrength (2)
    0,    -- velocityPush (0)
    0,    -- initialMovementOffset (0) — bom para dessincronizar múltiplas caudas
    1,    -- offsetBetweenSegments (1)
    0.005,-- stiffness (0.005)
    0.9, -- bounce (0.9)
    90,   -- flyingOffset (90) — ângulo ao voar/nadar
    -90,  -- downLimit (-90)
    45    -- upLimit (45)
)

Eye — Movimento dos Olhos

Move os olhos suavemente para onde você olha. Chame uma vez por olho.

Lua
-- Olho esquerdo
squapi.eye:new(
    models.model.root.Head.EyeLeft,
    0.25,  -- leftDistance (0.25)
    0.25,  -- rightDistance (0.25)
    0.5,   -- upDistance (0.5)
    0.5    -- downDistance (0.5)
)

-- Olho direito (mesmos parâmetros)
squapi.eye:new(models.model.root.Head.EyeRight, 0.25, 0.25, 0.5, 0.5)

SmoothHead — Cabeça Suave

A cabeça gira suavemente em vez de dar snap. Suporta pescoço e torso com múltiplos segmentos.

Lua
-- Apenas cabeça
squapi.smoothHead:new({
    models.model.root.Head
})

-- Com pescoço (pescoço contém a cabeça no Blockbench)
squapi.smoothHead:new(
    {
        models.model.root.Neck,
        models.model.root.Neck.Head,
    },
    1,      -- strength (1)
    0.1,    -- tilt (0.1)
    1,      -- speed (1)
    true,   -- keepOriginalHeadPos (true)
    true    -- fixPortrait (true)
)

-- Straighten durante animações específicas
squapi.smoothHead:new(
    {models.model.root.Head},
    1, 0.1, 1, true, true,
    {animations.model.attack},  -- animStraightenList
    0.5,   -- straightenMultiplier
    0.5    -- straightenSpeed
)

FPHand — Mão em Primeira Pessoa

Lua
squapi.FPHand:new(
    models.model.root.RightArm,  -- element
    vec(0, 0, 0),               -- position offset
    1,                          -- scale
    false                       -- onlyVisibleInFP
)

BounceWalk — Passo Saltitante

Lua
squapi.bounceWalk:new(
    models.model,  -- model (caminho raiz do modelo)
    1              -- bounceMultiplier (1)
)

Taur — Física de Corpo Centauro

Lua
squapi.taur:new(
    models.model.root.TaurBody,   -- taurBody (obrigatório)
    models.model.root.FrontLegs,  -- frontLegs (nil)
    models.model.root.BackLegs    -- backLegs (nil)
)
TIP
Todos os objetos SquAPI têm :enable(), :disable() e :toggle() para controlar quando rodam.
Libraries

PhysBone API v2.2

Física de ossos simulada — cabelo, orelhas, cordas, correntes. Criada por ChloeSpacedOut. Adiciona newPhysBone() diretamente aos ModelParts.

INFO
O nome do PhysBone é gerado automaticamente a partir do nome do group no Blockbench com prefixo physBone. Ex: group HairphysBone.physBoneHair.

Setup Básico

Lua
local physBone = require("physBoneAPI")

function events.ENTITY_INIT()
    -- Cria um physbone no group "Hair" dentro do Head
    local hair = models.model.root.Head.Hair:newPhysBone("physBone")

    hair:setNodeRadius(0.5)   -- raio visual dos nós
        :setNodeDensity(3)    -- quantos nós por segmento
        :setNodeEnd(12.5)     -- onde o bone termina (em unidades BB)
        :setVecMod(0.6, 1, 1) -- modificador de direção XYZ
        :setBounce(0.3)       -- quicar (0 = sem bounce)
        :setLength(20)        -- comprimento total

    -- Desativar density em bones que não precisam de nós visuais
    physBone.physBoneLeftEar:setNodeDensity(0)
    physBone.physBoneRightEar:setNodeDensity(0)
end

Todos os Parâmetros

MétodoDescriçãoPadrão
:setMass(n)Massa do bone — afeta a inércia1
:setLength(n)Comprimento total do bone16
:setGravity(n)Força da gravidade (positivo = cai)1
:setAirResistance(n)Resistência do ar — amortece o movimento0.2
:setSimSpeed(n)Velocidade da simulação1
:setSpringForce(n)Força de mola — atrai de volta à posição original0
:setEquilibrium(vec3)Posição de equilíbrio em repousovec(0,0,0)
:setForce(vec3)Força constante aplicada (como gravidade direcional)vec(0,0,0)
:setRotMod(vec3)Modificador de rotação XYZvec(1,1,1)
:setVecMod(x,y,z)Modificador de vetor de direçãovec(1,1,1)
:setRollMod(n)Modificador de rotação em roll1
:setNodeStart(n)Onde os nós começam0
:setNodeEnd(n)Onde os nós terminamcomprimento
:setNodeDensity(n)Nós por segmento (0 = desativado visualmente)2
:setNodeRadius(n)Raio visual dos nós de debug0.5
:setBounce(n)Quão elástico é o bounce (0 = sem bounce)0

Presets

Lua
-- Criar um preset reutilizável
physBone:setPreset("cabelo",
    1,    -- mass
    20,   -- length
    0.8, -- gravity
    0.2, -- airResistance
    1,    -- simSpeed
    nil,  -- equilibrium
    0,    -- springForce
    nil,  -- force
    nil,  -- rotMod
    vec(0.6, 1, 1), -- vecMod
    nil,  -- rollMod
    0, 12.5, 3, 0.5, 0.3  -- nodeStart, nodeEnd, density, radius, bounce
)

-- Aplicar preset em um bone
function events.ENTITY_INIT()
    models.model.root.Head.Hair:newPhysBone("physBone"):updateWithPreset("cabelo")
end
Libraries

Swinging Physics by Manuel

Física de balanço para parts na cabeça ou no corpo. Itens como brincos, pingentes, acessórios respondem ao movimento e rotação do player.

Lua — Setup
local SwingingPhysics = require("swinging_physics")

swingOnHead — Balançar na Cabeça

Lua
-- Brinco simples na cabeça
local earring = SwingingPhysics.swingOnHead(
    models.model.root.Head.Earring,   -- part
    90,                              -- dir: ângulo onde está (0=frente, 90=esquerda)
    {-30, 30, -20, 20, -15, 15},  -- limits: {xMin,xMax,yMin,yMax,zMin,zMax}
    nil,                             -- root: nil (não é corrente)
    0                                -- depth: 0 para o primeiro elo
)

-- Corrente de 3 elos (cada elo usa o anterior como root)
local chain1 = SwingingPhysics.swingOnHead(models.model.root.Head.Chain1, 0)
local chain2 = SwingingPhysics.swingOnHead(models.model.root.Head.Chain1.Chain2, 0, nil, chain1, 1)
local chain3 = SwingingPhysics.swingOnHead(models.model.root.Head.Chain1.Chain2.Chain3, 0, nil, chain2, 2)

swingOnBody — Balançar no Corpo

Lua
-- Pingente no corpo
local pendant = SwingingPhysics.swingOnBody(
    models.model.root.Body.Pendant,
    0,                               -- dir: 0 = frente
    {-20, 20, -15, 15, -10, 10},  -- limits
    nil, 0,
    true                            -- globalLimits: limites no espaço global
)

Referência de Direções (dir)

ValorDireção
0Frente
45Frente-esquerda diagonal
90Esquerda
135Trás-esquerda diagonal
180Trás
-90Direita

Controle do Handler

Lua
earring:setEnabled(false)  -- desativa (zera a rotação)
earring:getEnabled()        -- retorna boolean
earring:setPart(novoPart)   -- troca o part
earring:setDir(270)         -- troca a direção em runtime
earring:setLimits({...})    -- troca os limites em runtime
Libraries

Animated Text v0.2.0

Cria grupos de TextTasks que podem ser animados individualmente — cada caractere tem posição e rotação independente. Útil para nameplates animadas, textos flutuantes e efeitos de ondas.

Lua — Setup
local animatedText = require("animatedText")

new — Criar Texto

Lua
-- JSON simples
animatedText.new(
    "myText",                        -- name (ID para uso futuro)
    models.model.root.Head,           -- parent (ModelPart)
    vec(0, 20, 0),                   -- offset (Vec3)
    vec(0.4, 0.4, 0),               -- scale (Vec3)
    "Billboard",                      -- parentType
    {text = "Hello World!", color = "gold"}  -- json
)

-- JSON rico com múltiplos elementos
animatedText.new("nameplate", models.model.root.Head, vec(0,22,0), vec(0.4,0.4,0), "Billboard", {
    {text = "◈ ", color = "aqua"},
    {text = player:getName(), color = "white", bold = true},
    {text = " :java:", font = "figura:badges"},  -- emojis Figura!
})

setText — Atualizar Texto

Lua
-- Atualiza conteúdo (recria todos os TextTasks)
animatedText.setText("myText", {text = "Novo texto!", color = "red"})

-- Atualizar nameplate a cada tick com HP
function events.TICK()
    local hp = math.floor(player:getHealth())
    animatedText.setText("nameplate", {
        {text = "❤ " .. hp, color = "red"},
        {text = " | ", color = "gray"},
        {text = player:getName(), color = "white"},
    })
end

applyFunc — Animar por Caractere

Lua
-- Onda senoidal — cada caractere sobe/desce em offset
function events.RENDER(delta, context)
    if context ~= "FIRST_PERSON" and context ~= "RENDER" then return end

    animatedText.applyFunc("myText", function(char, i)
        local t = (world:getTime() + delta) / 8
        local wave = math.sin(t + i) * 0.25
        return vec(0, wave, 0)  -- pos offset, rot, scale
    end)
end

-- Rotação individual por caractere
animatedText.applyFunc("myText", function(char, i)
    local rot = math.sin(world:getTime() * 0.1 + i * 0.5) * 15
    return nil, vec(0, 0, rot)  -- nil pos, rot z
end)

remove

Lua
animatedText.remove("myText")  -- remove todos os TextTasks
Libraries

ActionLists v1.5.0

Adiciona um novo tipo de action na Action Wheel: uma lista scrollável de itens, navegada com scroll e confirmada com clique. Criado por @sh1zok_was_here.

Lua — Setup
require("ActionLists")  -- não precisa guardar retorno

Criando uma ActionList

Lua
local page = action_wheel:newPage()
action_wheel:setPage(page)

-- Usa :newActionList() no lugar de :newAction()
local lista = page:newActionList()
    :title("Escolha um Emote")
    :item("minecraft:book")
    :actionList({
        {title = "Aceno",    value = "wave"},
        {title = "Dança",    value = "dance"},
        {title = "Sentado",  value = "sit"},
        {title = "Bravo",    value = "angry"},
        {title = "Triste",   value = "sad"},
        {title = "Corado",   value = "blush"},
    })
    :onLeftClick(function(self, action, index)
        local value = action.value
        pings.playEmote(value)
    end)

Aparência e Customização

Lua
lista
    :title("Minha Lista")             -- título acima da lista
    :item("minecraft:compass")         -- item do botão
    :color(0.5, 0.5, 0.5)            -- cor do botão
    :hoverColor(1, 1, 1)             -- cor ao hover
    :actionListColor(0.6, 0.6, 0.6) -- cor dos itens não selecionados
    :selectedActionColor(1, 1, 0)   -- cor do item selecionado
    :visualSize(5)                   -- quantos itens aparecem de uma vez (7)
    :selectedActionIndex(1)          -- item selecionado inicial

Eventos

Lua
lista:onLeftClick(function(self, action, index)
    -- self = o objeto lista
    -- action = a tabela da opção selecionada {title=..., value=...}
    -- index = índice numérico da opção
    pings.selectOption(action.value)
end)

lista:onRightClick(function(self, action, index)
    -- clique direito na seleção atual
end)

lista:onScroll(function(self, dir)
    -- scroll manual (a lib já cuida do scroll automático)
end)

Itens com Aparência Própria

Lua
-- Cada opção pode ter item, cor e título de hover próprios
lista:actionList({
    {
        title = "Aceno",
        activeTitle = "▶ Aceno",     -- título quando selecionado
        item = "minecraft:feather",
        color = vec(0.8, 0.8, 1),
        value = "wave",
    },
    {
        title = "Dança",
        item = "minecraft:music_disc_cat",
        color = vec(1, 0.8, 0.8),
        value = "dance",
    },
})