Skip to content

Lua API

Fightura registers a global Lua object named fightura for every Figura avatar. It exposes Epic Fight state queries, joint pose data, and runtime bone overrides.

if fightura:isAvailable() then
-- this entity has an Epic Fight patch attached
end

True if the entity has an Epic Fight LivingEntityPatch attached. Always check this first; downstream queries return nil/false if it’s absent.

True when the player is in Epic Fight battle stance (sword drawn, etc.).

True for the duration of an Epic Fight attack animation.

True if a fresh pose snapshot was captured this frame. Use this in events.RENDER before calling joint queries.

function events.WORLD_TICK()
if fightura:isAttacking() then
-- play your custom hit-reaction animation
end
end

These read from the most recent Epic Fight pose snapshot (updated every frame the entity is rendered).

getJointRotation(name: string) -> Vector3 | nil

Section titled “getJointRotation(name: string) -> Vector3 | nil”

Returns the joint’s rotation as ZYX Euler angles in radians. Returns nil if the joint name doesn’t exist in the current armature.

function events.RENDER(delta)
if not fightura:hasPose() then return end
local rot = fightura:getJointRotation("Hand_L")
if rot then
models.MyArm.LowerArm:setRot(
math.deg(rot.x), math.deg(rot.y), math.deg(rot.z)
)
end
end

getJointMatrix(name: string) -> Matrix4 | nil

Section titled “getJointMatrix(name: string) -> Matrix4 | nil”

Returns the joint’s full 4×4 transform as a FiguraMat4. Best for matrix-driven rigs.

local headMat = fightura:getJointMatrix("Head")
if headMat then
models.MyHat:setMatrix(headMat)
end

Returns every joint name in the current entity’s Epic Fight armature. Use it to discover what’s available — different mobs/players might have different rigs.

for _, joint in pairs(fightura:getJoints()) do
print(joint)
end

If your avatar uses part names Fightura doesn’t recognize out of the box, bind them once on load. See Naming Conventions for the full alias list.

Bind a part name (or parentType name) to an Epic Fight joint. Case-insensitive on the alias side. Joint name must match Epic Fight exactly (Head, Chest, Arm_L, Hand_L, Thigh_L, Leg_L, etc.).

fightura:mapBone("MyTailRoot", "Torso")
fightura:mapBone("LSleeveCuff", "Hand_L")

Override only applies to this avatar — does not leak to other players.

Remove a single override.

fightura:clearBone("MyTailRoot")

Remove every override this avatar has set.

Return the list of every built-in alias Fightura recognizes (case-insensitive lowercase form). Useful for inspecting what won’t need a mapBone call.

Figura runs every vertex of a part through one matrix, so a part bound to Arm_R can only rotate about the shoulder — model an arm in one piece and it stays rigid through the elbow. Fightura blends each vertex between the joint and its bend child instead: Arm_L/Arm_R toward Hand_L/Hand_R, Thigh_L/Thigh_R toward Leg_L/Leg_R.

This needs no script at all. A limb whose geometry reaches past the joint bends on its own. Everything below is for the cases where you want something other than the default.

One thing has to hold for a limb to bend: its geometry reaches past the joint on both sides. A part that stops at the elbow is a plain upper segment and stays rigid, which is what keeps already-segmented avatars rendering exactly as before. Where the bend falls is read off Epic Fight’s armature — the elbow at 17.5 px and the knee at 6.2 — so there is nothing to measure or tune.

Cubes bend too. Fightura cuts the faces that cross the joint into thinner ones on the way to the screen, so a sleeve modelled as a single box curves rather than shearing. The cuts are shared corners, so no seam shows, and they only appear inside the blend band — a mesh already fine enough to bend on its own gets none. They cost nothing against your avatar’s complexity limit, which Figura has already charged by the time Fightura sees the part.

The fold passes through Epic Fight’s own crease joints — Elbow_L/Elbow_R and Knee_L/Knee_R. They rest exactly where the joint below them rests but are animated separately, and Epic Fight binds the ring of vertices at the fold straight to them. Going through them puts the crease where the animator put it rather than wherever mixing the two ends lands, which is what stops an elbow poking out through a sleeve.

The torso is included, between Torso and Chest, but it leans rather than folds. Epic Fight weights the vanilla body across both joints the whole way up — 0.23 at the waist, 0.77 at the shoulders, neither end ever handed over — so the body moves as one piece. Fightura uses those same numbers, so a torso replacement behaves like the body it stands in for instead of creasing at the waist.

Turn bending off for a part that would otherwise get it, or back on.

fightura:setSkin("RightArm", false)

Blend toward a joint other than the usual bend child — for a rig where the part should follow something else. Named joints work the same way: the bend lands on that joint’s rest position.

fightura:mapBone("Tentacle", "Arm_R")
fightura:bindSkin("Tentacle", "Hand_R")

Move the bend somewhere other than the joint: distance from the part’s pivot down the bone, in model pixels. 4.8 is where the player’s elbow sits.

How wide the blend is either side of the bend, in model pixels. Defaults to 2; 3 or more is softer.

fightura:setSkinSplit("Tentacle", 8)
fightura:setSkinBand("Tentacle", 4)

Each of these takes every argument it declares, on purpose. Figura reuses one argument buffer per method and never clears the slots a call leaves empty, so a method with optional trailing arguments would quietly inherit whatever the previous caller left there.

The outside of the fold curves through the full joint angle. The inside pinches, the way every blended joint does — Epic Fight’s own limbs included — and it shows most at the sharpest angles, around 90° and past. A wider setSkinBand spreads it over more of the limb and softens it.

Most idle poses barely bend the elbow, so it is easy to conclude nothing is happening when the setup is fine. Measured off Epic Fight’s own biped animations, as the angle between the Arm_R and Hand_R poses:

PoseElbow
Standing idle
Holding a longsword15°
Walking18°
Basic sword swing37°
Sneaking59°
Running82°
Holding a greatsword88°
Swimming104°

Test while swimming or running. At 7° the whole limb moves less than a pixel.

Drop a single skin override.

Drop every skin override this avatar has set.

Fightura fires Lua events when the entity’s Epic Fight state changes — no polling required. Each event is a LuaEvent field on the fightura API; register handlers with :register(func).

fightura.attackStart:register(function(motion)
-- motion is the current living motion name (e.g. "ATTACK")
animations.MyAttackReact:play()
end)
fightura.attackEnd:register(function()
animations.MyAttackReact:stop()
end)
fightura.modeChange:register(function(newMode, oldMode)
if newMode == "BATTLE" then
animations.DrawSword:play()
elseif newMode == "MINING" then
animations.SheathSword:play()
end
end)
fightura.hurtStart:register(function()
animations.HurtFlinch:play()
end)
fightura.knockDown:register(function()
animations.KnockedDown:play()
end)
fightura.motionChange:register(function(newMotion, oldMotion)
-- e.g. transitioning from "IDLE" to "WALK"
end)
EventArgsFires when
attackStartmotion: stringAn Epic Fight attack animation begins
attackEnd(none)The attack animation finishes
hurtStart(none)The entity enters a hurt state
knockDown(none)The entity is knocked down
modeChangenewMode, oldMode: stringplayerMode changes (BATTLE / MINING / DEFAULT)
motionChangenewMotion, oldMotion: stringCurrent living motion changes

Events fire on the client tick. Polling APIs (isAttacking(), isEpicFightMode()) still work and are equivalent for logic that runs every frame.

A reasonable starting script:

-- Bind any non-standard part names once.
fightura:mapBone("HornLeft", "Head")
fightura:mapBone("HornRight", "Head")
fightura:mapBone("TailRoot", "Torso")
function events.WORLD_TICK()
if fightura:isAvailable() and fightura:isAttacking() then
-- Trigger custom reaction animations.
end
end
function events.RENDER(delta)
if not fightura:hasPose() then return end
-- Per-frame transforms driven by Epic Fight pose.
local hand = fightura:getJointRotation("Hand_L")
if hand then
models.weapon_glow:setRot(math.deg(hand.x), math.deg(hand.y), math.deg(hand.z))
end
end

Some things deliberately aren’t in the API to keep the surface small:

  • Direct armature mutation (read-only access only)
  • Triggering Epic Fight skills
  • Network packet sniffing
  • Vanilla model state queries (use Figura’s vanilla_model API for that)

If you need something that isn’t here, open an issue on the Discord.

  • Troubleshooting — what to do if a method returns nil when you expect data