◈ 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.
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.
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
avatar:store()✗ You Cannot Do
debug, dofile, loadfile, or collectgarbage — these are removed from the sandboxInstallation
Install a Mod Loader
Figura supports Fabric, Forge, Quilt, and NeoForge for Minecraft 1.21.1. Get the latest loader for your choice.
Download Figura
Get figura-0.1.5-1.21.1-[loader].jar from Modrinth, CurseForge, or GitHub. Drop it into .minecraft/mods/.
Launch & Find the Button
Boot Minecraft. Figura adds a button to your inventory screen. Click it to open the Wardrobe.
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.
File Structure
.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")
require("filename") (without .lua) only when you need to control load order or get return values from another script.avatar.json
The avatar.json file is the only required file. It tells Figura the folder is an avatar. All fields are optional.
{
"name": "My Cool Avatar",
"description": "A custom Figura avatar",
"authors": ["YourName"],
"version": "1.0.0",
"color": "#5af0c8"
}Wardrobe Screen
| Button | Action |
|---|---|
↑ Upload | Upload avatar to the Figura cloud (Main, Server, or both) |
↺ Reload | Reload avatar from disk without restarting Minecraft |
✕ Delete | Remove avatar from the cloud (Main, Server, or both) |
⊞ Folder | Open figura/avatars/ in your file manager |
✦ New Avatar | Launch Avatar Wizard to generate a starter avatar |
♪ Sounds | List all sounds the selected avatar has registered |
⌨ Keybinds | Show keybinds registered by the selected avatar |
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:
| Function | Description |
|---|---|
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
| Function | Description |
|---|---|
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.playerScale | Constant: player scaling relative to world |
math.worldScale | Constant: world scaling relative to player |
-- 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"
All Global APIs
| Global | Description | Notes |
|---|---|---|
player | Proxy for the avatar owner's player entity | all |
user | Alias for player | |
models | Root of all loaded .bbmodel parts | all |
events | Register callbacks for game events | all |
animations | Access all Blockbench animations | all |
vanilla_model | Control vanilla player model visibility | perm |
pings | Functions synced to all viewers | all |
action_wheel | Build the radial action wheel menu | all |
renderer | Camera, FOV, fire, outline, arms | all |
nameplate | Customize the player nameplate | perm |
keybinds | Register custom key bindings | host |
sounds | Play vanilla/custom sounds | all |
particles | Spawn vanilla particles | all |
world | Query blocks, biomes, entities, time | all |
client | FPS, window, camera, system time | all |
host | Host-only: chat, title, slots, cursor | host |
avatar | Avatar metadata, complexity, UUID | all |
textures | Create and manipulate textures at runtime | all |
config | Persist data between sessions | host |
raycast | Raycast blocks, entities, AABBs | all |
net | HTTP requests and WebSockets | perm High+ |
file | Read/write files in the avatar folder | perm |
data | Create byte buffers | all |
json | Build JSON objects programmatically | all |
resources | Access game resource pack data | all |
vectors | Construct Vec2/3/4 | all |
matrices | Construct Mat2/3/4 | all |
host:isHost() before host-only logic if needed.Events
Register functions with events.EVENT_NAME:register(fn, "optional_name") or the shorthand function events.EVENT_NAME() end.
-- 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
-- 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
ModelPart API
Every cube, group, and mesh in your Blockbench model is a ModelPart. Access them through models.ModelName.PartName.
Transform Functions
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
-- 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)
-- 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
-- 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
Vanilla Model Parts
All available groups and parts under vanilla_model:
| Part | Description |
|---|---|
vanilla_model.ALL | Multi-part: entire vanilla model |
vanilla_model.PLAYER | Multi-part: outer + inner layers + cape |
vanilla_model.INNER_LAYER | Multi-part: main body (no outer layer) |
vanilla_model.OUTER_LAYER | Multi-part: outer layer (hat, jacket, sleeves, pants) |
vanilla_model.HEAD | Head (no hat) |
vanilla_model.BODY | Body (no jacket) |
vanilla_model.LEFT_ARM | Left arm (no sleeve) |
vanilla_model.RIGHT_ARM | Right arm (no sleeve) |
vanilla_model.LEFT_LEG | Left leg (no pants) |
vanilla_model.RIGHT_LEG | Right leg (no pants) |
vanilla_model.HAT | Hat layer |
vanilla_model.JACKET | Jacket layer |
vanilla_model.LEFT_SLEEVE | Left sleeve layer |
vanilla_model.RIGHT_SLEEVE | Right sleeve layer |
vanilla_model.LEFT_PANTS | Left pants layer |
vanilla_model.RIGHT_PANTS | Right pants layer |
vanilla_model.CAPE | Multi-part: cape |
vanilla_model.ARMOR | Multi-part: all armor |
vanilla_model.HELMET | Multi-part: helmet |
vanilla_model.CHESTPLATE | Multi-part: chestplate |
vanilla_model.LEGGINGS | Multi-part: leggings |
vanilla_model.BOOTS | Multi-part: boots |
vanilla_model.ELYTRA | Multi-part: elytra wings |
vanilla_model.HELD_ITEMS | Multi-part: items in hands |
vanilla_model.LEFT_ITEM | Item in left hand |
vanilla_model.RIGHT_ITEM | Item in right hand |
vanilla_model.PARROTS | Multi-part: shoulder parrots |
-- 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)
Animations
-- 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
Player API
Available via the global player (or user). Extends Entity and LivingEntity.
| Method | Returns | Description |
|---|---|---|
:getFood() | int | Hunger level 0–20 |
:getSaturation() | number | Saturation level |
:getExhaustion() | number | Exhaustion level |
:getExperienceProgress() | number | XP progress 0–1 |
:getExperienceLevel() | int | Current level |
:getModelType() | string | "SLIM" or "DEFAULT" |
:getGamemode() | string | "SURVIVAL", "CREATIVE", "ADVENTURE", "SPECTATOR" |
:hasCape() | bool | Has a cape loaded |
:hasSkin() | bool | Has a custom skin |
:isSkinLayerVisible(layer) | bool | Skin customization layer visible |
:isFishing() | bool | Currently fishing |
:getShoulderEntity(left) | table | NBT of shoulder parrot (true=left) |
:getTeamInfo() | table | Team data or nil |
:getCooldownPercent(item) | number | Item cooldown 0–1 |
LivingEntity Methods (inherited by player)
| Method | Returns | Description |
|---|---|---|
:getHealth() | number | Current health |
:getMaxHealth() | number | Max health |
:getArmor() | int | Armor points |
:getBodyYaw(delta) | number | Body yaw in degrees |
:getHeldItem(offhand) | ItemStack | Item in hand |
:getActiveItem() | ItemStack | Item currently being used |
:isUsingItem() | bool | Eating/blocking/etc |
:isClimbing() | bool | On ladder/vine |
:isGliding() | bool | Elytra gliding |
:isBlocking() | bool | Shield blocking |
:getDeathTime() | int | Ticks dead (0 if alive) |
:getArrowCount() | int | Arrows stuck in entity |
:getAbsorptionAmount() | number | Absorption hearts |
:getSwingArm() | string | "MAIN_HAND" or "OFF_HAND" |
:isSwingingArm() | bool | Arm is swinging |
Entity Methods (inherited by player)
| Method | Returns | Description |
|---|---|---|
:getPos(delta) | Vec3 | World position (interpolated with delta) |
:getRot(delta) | Vec2 | Pitch and yaw in degrees |
:getVelocity() | Vec3 | Current velocity |
:getLookDir() | Vec3 | Unit vector in look direction |
:getUUID() | string | UUID as string |
:getType() | string | Entity id e.g. "minecraft:player" |
:getName() | string | Display name |
:getPose() | string | "STANDING", "CROUCHING", "SWIMMING", etc. |
:isOnGround() | bool | Currently on ground |
:isSprinting() | bool | Currently sprinting |
:isSneaking() | bool | Logic sneak (no fall off edges) |
:isCrouching() | bool | Visual sneak |
:isMoving(noY) | bool | Has velocity (noY=true ignores vertical) |
:isFalling() | bool | Negative Y velocity and not on ground |
:isInWater() | bool | In water block |
:isUnderwater() | bool | Eyes submerged |
:isInLava() | bool | In lava |
:isOnFire() | bool | Currently burning |
:isWet() | bool | In water, rain, or bubble column |
:isGlowing() | bool | Has glow effect |
:isInvisible() | bool | Has invisibility |
:isAlive() | bool | Alive (not dead) |
:getNbt() | table | Entity NBT as Lua table |
:getItem(slot) | ItemStack | Item at slot (1=mainhand, 2=offhand, 3-6=armor) |
:getBoundingBox() | Vec3 | Hitbox size (width, height, width) |
:getVehicle() | Entity | Entity being ridden (or nil) |
:getTargetedBlock(dist) | BlockState | Targeted block (max 20 blocks) |
:getTargetedEntity(dist) | Entity | Targeted entity (max 20 blocks) |
:getNearestEntity(type,radius) | Entity | Nearest entity (optional type filter) |
:hasAvatar() | bool | Entity has a Figura avatar loaded |
:getPermissionLevel() | int | Permission level (4 = op) |
:getVariable(key) | any | Read another avatar's stored variable |
:getDimension() | string | Dimension id |
:getFrozenTicks() | int | Ticks in powder snow |
World API
-- 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)
Renderer API
Controls how your avatar is rendered. Changes are local — only affect your own view for host-side settings.
-- 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()
Host API host only
All host functions only work when you are the avatar's owner. On other clients they silently do nothing.
-- 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 API
-- 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")
Sounds API
-- 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()
Particles API
-- 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
Keybinds API host only
-- 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")
Action Wheel
Hold the Action Wheel key (default B) to show the wheel. Up to 8 slots per page.
-- 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
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.
-- 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
Nameplate API requires Default+
-- 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
Textures API
-- 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
Config API host only
Save and load data between game sessions. Data is stored in a file in the avatar folder.
-- 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
Raycast API
-- 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} )
Networking API requires High+ permission
-- 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
Render Tasks
Render Tasks attach to ModelParts and can render text, items, blocks, sprites, or entities at the part's location.
| Task Type | Key Methods | Description |
|---|---|---|
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().
Emissive Textures
Emissive textures glow at full brightness regardless of world lighting.
skin.png ← base texture skin_e.png ← emissive layer (suffix _e before .png)
EYES: part:setPrimaryRenderType("EYES"). The default compatibility mode renders emissives as fullbright instead of actual bloom.Render Types
| Type | Description |
|---|---|
SOLID | Default opaque rendering |
CUTOUT | Alpha cutout (on/off transparency, no blending) |
CUTOUT_CULL | Cutout with back-face culling |
TRANSLUCENT | Alpha blending — required for setOpacity() |
TRANSLUCENT_CULL | Translucent with culling |
EYES | Fullbright, no shading — for emissive glow/bloom |
EMISSIVE | Emissive rendering with normal alpha blending |
EMISSIVE_SOLID | Emissive opaque |
END_PORTAL | The animated end portal effect |
GLINT | Enchantment glint overlay |
GLINT2 | Alternate enchantment glint |
LINES | Renders as wireframe lines |
Parent Types
Parent types attach a model part to a specific vanilla skeleton position. Set in Blockbench (part name) or via Lua.
| Parent Type | Attaches To |
|---|---|
Head | Player head bone |
Body | Player body bone |
LeftArm | Left arm bone |
RightArm | Right arm bone |
LeftLeg | Left leg bone |
RightLeg | Right leg bone |
LeftItemPivot | Left hand item position |
RightItemPivot | Right hand item position |
HelmetItemPivot | Head item (helmet slot item) position |
LeftElytra | Left elytra wing |
RightElytra | Right elytra wing |
Cape | Cape bone |
Camera | Follows the camera |
World | Fixed in world space (doesn't follow player) |
NONE | Not attached to any parent (default) |
-- 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
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.
| Level | Summary | Features Unlocked |
|---|---|---|
| Blocked | Avatar completely disabled | None. Avatar not rendered or scripted. |
| Low | Minimal trust | Basic model rendering. Most Lua blocked. Ideal for tiny avatars. |
| Default | Standard trust | Vanilla model edits, animations, sounds, particles. No nameplate. |
| High | High trust | Everything in Default + nameplate, networking, custom render layers. |
| Max | Maximum trust | All features, unlimited resources. |
Configurable Limits per Level
| Setting | Controls |
|---|---|
| Init Instructions | Max Lua ops during script load phase |
| Tick Instructions | Max Lua ops per TICK event |
| World Tick Instructions | Max Lua ops per WORLD_TICK event |
| Render Instructions | Max Lua ops per RENDER event |
| World Render Instructions | Max Lua ops per WORLD_RENDER event |
| Animation Instructions | Max Lua ops per animation keyframe code |
| Max Complexity | Max rendered faces (1 cube = 6 faces; hidden parts don't count) |
| Animations Complexity | Max total animation channels playing at once |
| Max Particles / sec | Cap on particles spawned |
| Max Sounds / sec | Cap on sounds played |
| Avatar Sounds Volume | Volume multiplier for this avatar's sounds |
| Max Texture Size | Max pixel dimensions of runtime-created textures |
| Buffer Size / Count | Byte buffer limits for data processing |
| Max Sockets | Max concurrent WebSocket connections |
| Vanilla Model Change | Toggle/untoggle (Default+) |
| Nameplate Change | Toggle/untoggle (High+) |
| Networking | Toggle/untoggle (High+) |
| Custom Sounds | Toggle/untoggle (Default+) |
| Custom Skull | Toggle/untoggle — avatar renders on skull blocks |
| Cancel Sounds | Toggle/untoggle — ON_PLAY_SOUND can cancel sounds |
| Cancel Damage | Toggle/untoggle — DAMAGE event can cancel effects |
| Render Offscreen | Toggle — render even when not in viewer's FOV |
| Custom Render Layers | Toggle — custom GLSL shaders via render layers |
FAQ
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()
ModelName with the .bbmodel filename (no extension) and animName with the animation name inside Blockbench._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.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.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
part:setPrimaryRenderType("TRANSLUCENT") part:setOpacity(0.5)
Resources & Links
| Resource | Description |
|---|---|
| ◈ Official Wiki | Full API reference maintained by the FiguraMC team |
| ◌ Discord | Community support, showcases, announcements |
| ⬡ Blockbench | Free 3D modeling software for Figura avatars |
| › Lua Quickstart | Beginner Lua guide by Manuel, tailored for Figura |
| ◎ Figs Offline Wiki | Web-accessible mirror of the Figura wiki |
| ✦ VSCode VSDocs | Autocomplete extension for Figura API in VS Code |
| ⊛ Figura VS Code Ext | VS Code extension with model path autocomplete |
| ▷ Video Tutorials | Chloe's comprehensive Figura tutorial series on YouTube |
| ⊞ Blockbench Plugin | Blockbench plugin for the Figura model format |
| ⇗ Weblate | Help translate Figura to your language |
| ↓ Modrinth | Download Figura from Modrinth |
| ♥ Open Collective | Support Figura development financially |
★ 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.
-- ───────────────────────────────────────────── -- 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
Animations — All Patterns
State Machine (walk / run / idle / fall)
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
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
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
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
Model Control
Bobbing / Floating Effect
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
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)
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)
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
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
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)
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
-- 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)
-- 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
Player Data — Practical Patterns
Color Model by Health (green → yellow → red)
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
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)
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
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
function events.TICK() local gm = player:getGamemode() -- Show creative "aura" in creative mode models.MyModel.CreativeAura:setVisible(gm == "CREATIVE") end
World Queries
Change Model by Biome
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
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
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)
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
HUD & UI
Custom Health Bar (Render Task Text)
-- 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
-- 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
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
Sounds & Particle Effects
Footstep Sounds
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
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
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)
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
Keybind + Ping — Sync to All Viewers
Hold-to-Activate with Smooth Lerp
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)
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)
Action Wheel — Full Setup
Multi-Page Wheel with Scroll Slot
-- ── 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
Nameplate
Dynamic Nameplate with Stats
-- 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)
-- 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"} }))
Textures — Runtime Pixel Art
Dynamic Color Fill Based on Game State
-- 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
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
Camera & Visual FX
Camera Shake on Damage
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)
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
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
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
Config & Persistent Save
Save All Avatar Settings
-- 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()
Raycast — Practical Uses
Laser Pointer (Beam to First Hit)
-- 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)
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
Networking — HTTP Requests
net:isNetworkingAllowed() first.Fetch a Remote JSON Config
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
Advanced Patterns
Share Data Between Avatars
-- 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 — 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
-- 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
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
-- 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())
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
-- 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)
| Nome | Quando toca |
|---|---|
idle | Parado no chão |
walk | Andando |
walkback | Andando para trás |
sprint | Correndo |
jumpup | Subindo de um pulo intencional |
jumpdown | Descendo de um pulo intencional |
fall | Caindo de grande altura |
crouch | Agachado (base para outros estados agachados) |
crouchwalk | Andando agachado |
elytra | Voando com elytra |
elytradown | Mergulhando com elytra |
swim | Nadando |
fly | Voando em criativo (base) |
climb | Escalando escada/trepadeira (base) |
sleep | Dormindo |
sit | Sentado em veículo |
crawl | Engatinhando |
hurt | Levando dano |
death | Morrendo |
water | Submerso em água (base) |
trident | Impulso com tridente Riptide |
Nomes de Animações — Inclusivas (tocam junto com as exclusivas)
| Nome | Quando toca |
|---|---|
attackR / attackL | Atacando (loop once) |
mineR / mineL | Minerando (loop once) |
holdR / holdL | Segurando qualquer item |
eatR / eatL | Comendo |
drinkR / drinkL | Bebendo |
blockR / blockL | Bloqueando com escudo |
bowR / bowL | Puxando arco |
crossR / crossL | Segurando besta carregada |
spyglassR / spyglassL | Usando luneta |
spearR / spearL | Preparando tridente |
brushR / brushL | Usando escova arqueológica |
idle e idle2 ambas tocam ao ficar parado. Outros: Sufixo _outro toca quando o estado termina. Ex: idle_outro.Funções Avançadas
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()
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/.
local squapi = require("SquAPI")
Ear — Física de Orelhas
Adiciona física às orelhas quando você move o personagem.
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.
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.
-- 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.
-- 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
squapi.FPHand:new( models.model.root.RightArm, -- element vec(0, 0, 0), -- position offset 1, -- scale false -- onlyVisibleInFP )
BounceWalk — Passo Saltitante
squapi.bounceWalk:new( models.model, -- model (caminho raiz do modelo) 1 -- bounceMultiplier (1) )
Taur — Física de Corpo Centauro
squapi.taur:new( models.model.root.TaurBody, -- taurBody (obrigatório) models.model.root.FrontLegs, -- frontLegs (nil) models.model.root.BackLegs -- backLegs (nil) )
:enable(), :disable() e :toggle() para controlar quando rodam.PhysBone API v2.2
Física de ossos simulada — cabelo, orelhas, cordas, correntes. Criada por ChloeSpacedOut. Adiciona newPhysBone() diretamente aos ModelParts.
physBone. Ex: group Hair → physBone.physBoneHair.Setup Básico
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étodo | Descrição | Padrão |
|---|---|---|
:setMass(n) | Massa do bone — afeta a inércia | 1 |
:setLength(n) | Comprimento total do bone | 16 |
:setGravity(n) | Força da gravidade (positivo = cai) | 1 |
:setAirResistance(n) | Resistência do ar — amortece o movimento | 0.2 |
:setSimSpeed(n) | Velocidade da simulação | 1 |
:setSpringForce(n) | Força de mola — atrai de volta à posição original | 0 |
:setEquilibrium(vec3) | Posição de equilíbrio em repouso | vec(0,0,0) |
:setForce(vec3) | Força constante aplicada (como gravidade direcional) | vec(0,0,0) |
:setRotMod(vec3) | Modificador de rotação XYZ | vec(1,1,1) |
:setVecMod(x,y,z) | Modificador de vetor de direção | vec(1,1,1) |
:setRollMod(n) | Modificador de rotação em roll | 1 |
:setNodeStart(n) | Onde os nós começam | 0 |
:setNodeEnd(n) | Onde os nós terminam | comprimento |
:setNodeDensity(n) | Nós por segmento (0 = desativado visualmente) | 2 |
:setNodeRadius(n) | Raio visual dos nós de debug | 0.5 |
:setBounce(n) | Quão elástico é o bounce (0 = sem bounce) | 0 |
Presets
-- 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
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.
local SwingingPhysics = require("swinging_physics")
swingOnHead — Balançar na Cabeça
-- 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
-- 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)
| Valor | Direção |
|---|---|
0 | Frente |
45 | Frente-esquerda diagonal |
90 | Esquerda |
135 | Trás-esquerda diagonal |
180 | Trás |
-90 | Direita |
Controle do Handler
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
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.
local animatedText = require("animatedText")
new — Criar Texto
-- 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
-- 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
-- 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
animatedText.remove("myText") -- remove todos os TextTasks
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.
require("ActionLists") -- não precisa guardar retorno
Criando uma ActionList
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
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 inicialEventos
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
-- 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", }, })