Files
TiedUp-/docs/ARTIST_GUIDE.md
NotEvil e17998933c Add creator field and enriched tooltip for data-driven items
- Add optional "creator" JSON field to display author name in tooltip
- Show body regions, movement style, lock status, and escape difficulty
- Show pose priority and item ID in advanced mode (F3+H)
- Update ARTIST_GUIDE.md field reference and test JSON
2026-04-13 04:00:27 +02:00

63 KiB
Raw Permalink Blame History

TiedUp! — 3D Item Creation Guide

Everything you need to create custom bondage items for TiedUp! using Blender. No Java required — just a GLB file and a JSON definition.


Table of Contents

  1. How It Works
  2. The Skeleton
  3. Body Regions
  4. Modeling Your Item
  5. Weight Painting
  6. Tint Channels — dynamic per-zone coloring
  7. Animations — item poses, fallback chain, variants, context animations
  8. Animation Templates
  9. Exporting from Blender
  10. The JSON Definition
  11. Packaging as a Resource Pack
  12. Common Mistakes
  13. Examples
  14. Furniture Creation — interactive furniture (cross, pillory, cage, etc.)
  15. Quick Reference Cards — body items + furniture cheat sheets

How It Works

TiedUp! uses a 2-layer animation system to render 3D items on players:

Layer 1 — CONTEXT (provided by the mod, or by your GLB)
  Controls the player's overall posture: standing, sitting, kneeling, sneaking, walking.
  Affects body + legs. Never touches head (vanilla head tracking is preserved).
  Can be replaced by artist-made GLB files (see Context Animations).

Layer 2 — ITEM (provided by YOUR GLB file)
  Controls the bones your item owns (based on its body regions).
  Can ALSO animate free bones (not owned by any other equipped item).
  Overrides Layer 1 for those bones. Everything else stays vanilla.

What this means for you:

  • You animate the bones your item affects (e.g., arms for handcuffs).
  • You can also animate free bones — bones no other item claims (e.g., legs if no ankle cuffs are worn). See Free Bones.
  • The mod handles the rest: legs walk, body leans when sneaking, head follows the mouse.
  • Your 3D mesh is skinned to the player skeleton and follows it in real-time.
  • You can create context animation GLBs — standalone animation packs that replace or extend the mod's default postures (walk cycles, sit poses, etc.). See Context Animations.

The Skeleton

Your GLB must use this exact skeleton. Names are case-sensitive.

PlayerArmature          ← armature root object (never keyframe this)
  └─ body               ← context layer by default; animate if you own TORSO/WAIST or if free
       ├─ torso         ← maps to same body part as 'body'; prefer using 'body' instead
       │    ├─ head
       │    ├─ leftUpperArm
       │    │    └─ leftLowerArm
       │    └─ rightUpperArm
       │         └─ rightLowerArm
       ├─ leftUpperLeg
       │    └─ leftLowerLeg
       └─ rightUpperLeg
            └─ rightLowerLeg

11 skinned joints (body through rightLowerLeg). PlayerArmature is the armature root object, not a skinned joint — it must never be keyframed.

Bone What it controls Type
PlayerArmature Root transform Never animate
body Torso position/rotation Full rotation — context layer by default, animate if owned or free
torso Same as body (alias) Full rotation — prefer body instead
head Head Full rotation (pitch/yaw/roll)
leftUpperArm Left shoulder to elbow Full rotation
leftLowerArm Left elbow to wrist Bend (angle + direction)
rightUpperArm Right shoulder to elbow Full rotation
rightLowerArm Right elbow to wrist Bend (angle + direction)
leftUpperLeg Left hip to knee Full rotation
leftLowerLeg Left knee to ankle Bend (angle + direction)
rightUpperLeg Right hip to knee Full rotation
rightLowerLeg Right knee to ankle Bend (angle + direction)

Upper vs Lower Bones

  • Upper bones (head, body, upperArm, upperLeg) support full 3-axis rotation (pitch, yaw, roll).
  • Lower bones (lowerArm, lowerLeg) support bend angle + bend direction — the mod extracts both values from your Blender pose. You can pose them freely in any direction; the mod faithfully reproduces both the magnitude and direction of the bend.

What You Can and Cannot Animate

Never animate: PlayerArmature — it's the armature root object, not a bone.

Everything else follows this rule:

  • Your item always controls bones in its declared regions.
  • Your item can also animate free bones (not owned by any other equipped item).
  • Your item cannot override bones owned by another equipped item.
Bone Who Controls It
body / torso Context layer by default. Your item if it owns TORSO or WAIST, or if body is free.
head Vanilla head tracking by default. Your item if it owns HEAD, EYES, EARS, or MOUTH.
Arms (*UpperArm, *LowerArm) Vanilla by default. Your item if it owns ARMS or HANDS.
Legs (*UpperLeg, *LowerLeg) Context layer by default. Your item if it owns LEGS or FEET.

Note: torso and body both map to the same internal part. Prefer animating body — using torso produces the same result but is less intuitive.


Body Regions

Every item declares which body regions it occupies. This determines two things:

  1. Gameplay — which equipment slots are taken (conflict resolution)
  2. Animation — which bones your item layer controls

Region Table

Region GLB Bones Controlled Typical Items
HEAD head Hood, helmet, head harness
EYES head Blindfold
EARS head Earplugs
MOUTH head Gag, muzzle
NECK (none) Collar, choker
TORSO body (+ torso) Straitjacket, harness
ARMS rightUpperArm, rightLowerArm, leftUpperArm, leftLowerArm Handcuffs, armbinder
HANDS rightUpperArm, rightLowerArm, leftUpperArm, leftLowerArm Mittens, fist mitts
FINGERS (none) Finger cuffs
WAIST body (+ torso) Belt, chastity belt
LEGS rightUpperLeg, rightLowerLeg, leftUpperLeg, leftLowerLeg Ankle cuffs, leg binder
FEET rightUpperLeg, rightLowerLeg, leftUpperLeg, leftLowerLeg Forced shoes, foot cuffs
TAIL (none) Tail plug (pet play)
WINGS (none) Decorative wings

Global vs Sub Regions

Three regions are global — they encompass sub-regions:

Global Sub-regions
HEAD EYES, EARS, MOUTH
ARMS HANDS, FINGERS
LEGS FEET

Important: Blocking is not automatic. A hood that occupies HEAD does not automatically block EYES. You must explicitly declare blocked regions in your JSON definition. This gives you full control — a tiara occupies HEAD but blocks nothing.

Regions Without Bones

NECK, FINGERS, TAIL, and WINGS don't control any animation bones. Items in these regions are purely cosmetic from an animation standpoint — they render as a mesh on the skeleton but don't change the player's pose. They still participate in gameplay (slot blocking, escape difficulty, etc.).


Modeling Your Item

Starting Point

Use the TiedUp! Blender Template (provided with the mod). It contains:

  • The correct PlayerArmature skeleton with all 11 bones
  • A reference player mesh (Steve + Alex) for scale — toggle visibility as needed
  • Pre-named Action slots

Guidelines

  1. Model in rest pose. The template skeleton is in the Minecraft rest pose (arms down, legs straight). Model your item to fit the player in this pose.

  2. Keep it tight. Your mesh will deform with the skeleton. Loose geometry that isn't weight-painted to any bone will stay frozen in space while the player moves.

  3. Mind the polygon count. Minecraft renders every player in range every frame. A 500-triangle mesh is ideal. 2000 is acceptable. 10,000 is going to cause lag in multiplayer.

  4. Textures are baked into the GLB. Apply your materials and textures in Blender. The mod reads them directly from the GLB file. No separate texture file needed (unless you want server-side color variants).

  5. Slim model support. Minecraft has two player models: Steve (4px arms) and Alex (3px arms). If your item wraps tightly around the arms, consider providing a slim variant. Otherwise, the Steve-width mesh works for both (minor clipping on Alex is usually acceptable).


Weight Painting

Weight paint your mesh to the skeleton bones it should follow.

Rules

  • Only paint to the 11 standard bones. Any other bones in your Blender file will be ignored by the mod.
  • Paint to the bones of your regions. Handcuffs (ARMS region) should be weighted to rightUpperArm, rightLowerArm, leftUpperArm, leftLowerArm.
  • You can paint to bones outside your regions for smooth deformation. For example, handcuffs might have small weights on body near the shoulder area for smoother bending. This is fine — the weight painting is about mesh deformation, not animation control.
  • Normalize your weights. Each vertex's total weights across all bones must sum to 1.0. Blender does this by default.

Tips

  • For rigid items (metal cuffs), use hard weights — each vertex fully assigned to one bone.
  • For flexible items (rope, leather), blend weights between adjacent bones for smooth bending.
  • The chain between handcuffs? Weight it 50/50 to both arms, or use a separate mesh element weighted to body.

Tint Channels (Dynamic Colors)

Tint channels let players change the color of specific parts of your item without replacing the texture. A ball gag with a red ball can become blue, green, purple — while the black strap stays black.

How It Works

Your item's texture detail (highlights, shadows, stitching, scratches) is fully preserved. The mod multiplies the texture color by a tint color per-zone. Grayscale textures + color tint = any hue with full detail.

Blender Workflow

Use separate materials for fixed and colorable zones:

  1. Fixed zones — Name the material anything (e.g., strap, buckle, metal). These render as-is.
  2. Colorable zones — Name the material tintable_1, tintable_2, etc. These get tinted by the mod.

Example: Ball Gag

  • Select the strap faces → assign material strap → texture with leather detail (full color)
  • Select the ball faces → assign material tintable_1 → texture with grayscale surface detail (highlights, reflections, micro-scratches)
  • Export GLB

The grayscale texture on tintable_1 preserves all the surface detail. The mod multiplies it by the tint color: gray detail × red = detailed red ball.

Multiple Tint Channels

You can have as many independently-colorable zones as you want:

Material "strap"       → fixed (leather, never changes)
Material "tintable_1"  → ball (player picks color)
Material "tintable_2"  → ring (player picks a different color)
Material "buckle"      → fixed (chrome metal)

Each tintable_N channel gets its own color. Players can customize each zone independently.

Texturing Tips for Tintable Zones

  • Use grayscale textures for tintable zones. The tint color replaces the hue entirely.
  • Keep all the detail (bumps, scratches, reflections) in the luminance channel.
  • A mid-gray base (not pure white) produces more natural-looking results.
  • Dark areas in your grayscale texture stay dark regardless of tint — good for crevices and shadows.

What happens: finalPixelColor = textureGray × tintColor

  • Gray (128,128,128) × Red (255,0,0) = Dark Red (128,0,0)
  • White (255,255,255) × Red (255,0,0) = Bright Red (255,0,0)
  • Black (0,0,0) × Red (255,0,0) = Black (0,0,0) — shadows stay dark

JSON Definition

Declare default colors per channel in your item JSON:

{
  "type": "tiedup:bondage_item",
  "display_name": "Ball Gag",
  "model": "mycreator:models/gltf/ball_gag.glb",
  "regions": ["MOUTH"],
  "supports_color": true,
  "tint_channels": {
    "tintable_1": "#FF0000",
    "tintable_2": "#C0C0C0"
  },
  "animation_bones": {
    "idle": []
  },
  "pose_priority": 10,
  "escape_difficulty": 3
}

Colors are hex RGB (#RRGGBB). These are the default colors — players can override them in-game via dyeing.

Material Naming Rules

Name Pattern Behavior Example
tintable_1, tintable_2, ... Colorable zone, numbered Ball, ring, strap
Anything else Fixed, rendered as-is metal, buckle, leather
  • Names are case-sensitive and must start with tintable_ (lowercase).
  • Numbers don't need to be sequential — tintable_1 and tintable_5 is fine.
  • The number is part of the channel name: tintable_1 in JSON matches tintable_1 in Blender.

Items Without Tint Channels

If your item has no tintable_ materials, everything renders as-is. No tint_channels field needed in the JSON. Existing items are completely unaffected.


Animations

Animations define how the player's body is posed when wearing your item.

The Only Required Animation: Idle

Every item needs at least one animation named Idle. This is the default pose — what the player looks like while standing still with your item equipped.

In Blender: Create an Action named PlayerArmature|Idle.

The PlayerArmature| prefix is Blender's convention for armature-scoped actions. The mod strips it automatically — the resolved name is just Idle.

What to Animate in Idle

Only keyframe the bones your item affects. Leave everything else untouched.

Item Type Bones to Keyframe Example Pose
Handcuffs (ARMS) Upper + lower arms Arms behind back, wrists together
Ankle cuffs (LEGS) Upper + lower legs Legs closer together, slightly bent
Blindfold (EYES) Head Slight head tilt down
Gag (MOUTH) (none) No pose change — mesh only
Straitjacket (ARMS+TORSO) Arms + body (+ legs if free) Arms crossed, slight forward lean, optional waddle

Why only your bones? The mod's 2-layer system activates your keyframes for bones in your declared regions. But there's a nuance: free bones (bones not owned by any equipped item) can also be animated by your item.

For example: if a player wears only a straitjacket (ARMS+TORSO), the legs are "free" — no item claims them. If your straitjacket's GLB has leg keyframes (e.g., a waddle walk), the mod will use them. But if the player also wears ankle cuffs (LEGS), those leg keyframes are ignored — the ankle cuffs take over.

The rule: Your item always controls its own bones. It can also animate free bones if your GLB has keyframes for them. It can never override another item's bones.

Idle is a Single-Frame Pose

Idle should be a static pose — one keyframe at frame 0. The mod loops it as a held position.

Frame 0: Pose all owned bones → done.

Optional Animations

Beyond Idle, you can provide animations for specific contexts. All are optional — if missing, the mod falls back through a chain (see below).

Animation Name Context Notes
Idle Standing still Required. Single-frame pose.
Struggle Player is struggling Multi-frame loop. 20-40 frames recommended.
Walk Player is walking Multi-frame loop synced to walk speed.
Sneak Player is sneaking Single-frame or short loop.
SitIdle Sitting (chair, minecart) Single-frame pose.
SitStruggle Sitting + struggling Multi-frame loop.
KneelIdle Kneeling Single-frame pose.
KneelStruggle Kneeling + struggling Multi-frame loop.
Crawl Crawling (dog pose) Multi-frame loop.

Naming in Blender: Always prefix with PlayerArmature|. Examples:

  • PlayerArmature|Idle
  • PlayerArmature|Struggle
  • PlayerArmature|SitIdle

Fallback Chain

If an animation doesn't exist in your GLB, the mod looks for alternatives. At each step, Full variants are tried first:

SIT + STRUGGLE:
  FullSitStruggle → SitStruggle → FullStruggle → Struggle
    → FullSit → Sit → FullStruggle → Struggle → FullIdle → Idle

KNEEL + STRUGGLE:
  FullKneelStruggle → KneelStruggle → FullStruggle → Struggle
    → FullKneel → Kneel → FullStruggle → Struggle → FullIdle → Idle

SIT + IDLE:      FullSitIdle   → SitIdle   → FullSit   → Sit   → FullIdle → Idle
KNEEL + IDLE:    FullKneelIdle → KneelIdle → FullKneel → Kneel → FullIdle → Idle
SNEAK:           FullSneak     → Sneak     → FullIdle  → Idle
WALK:            FullWalk      → Walk      → FullIdle  → Idle
STAND STRUGGLE:  FullStruggle  → Struggle  → FullIdle  → Idle
STAND IDLE:      FullIdle      → Idle

In practice, most items only need Idle. Add FullWalk or FullStruggle when your item changes how the whole body moves.

Practical impact: If you only provide Idle, your item works in every context. The player will hold the Idle pose while sitting, kneeling, sneaking, etc. It won't look perfect, but it will work. Add more animations over time to polish the experience.

Animation Variants (Random Selection)

For variety, you can provide multiple versions of the same animation. The mod picks one at random each time the animation triggers.

Convention: Append .1, .2, .3, etc.

PlayerArmature|Struggle.1    ← variant 1
PlayerArmature|Struggle.2    ← variant 2
PlayerArmature|Struggle.3    ← variant 3

Works for any animation name: Idle.1/Idle.2, SitIdle.1/SitIdle.2, etc.

Rules:

  • Number sequentially starting from .1. The mod stops scanning at the first gap after .1 (missing .1 does not stop the scan — it continues checking .2, .3, etc.).
  • If only one version exists, don't number it — just use Struggle, not Struggle.1.
  • The base name (e.g., Struggle) is included in the random pool alongside numbered variants if it exists. So Struggle + Struggle.1 + Struggle.2 = three candidates in the pool.

Full-Body Animations (Naming Convention)

Some items affect the entire body — not just their declared regions. A straitjacket makes the player waddle, a full-body bind forces hopping. For these, you want to animate all bones, including free ones like legs and body.

Convention: Prefix the animation name with Full.

Standard Name Full-Body Name What Changes
Idle FullIdle Owned bones + body lean, leg stance
Walk FullWalk Owned bones + leg waddle, body sway
Struggle FullStruggle Owned bones + full-body thrashing
Sneak FullSneak Owned bones + custom sneak posture

In Blender:

PlayerArmature|Idle          ← region-only: just arms for handcuffs
PlayerArmature|FullWalk      ← full-body: arms + legs waddle + body bob
PlayerArmature|FullStruggle  ← full-body: everything moves

How the mod resolves this:

  1. Checks for Full variant first (e.g., FullWalk)
  2. Falls back to standard name (e.g., Walk)
  3. Follows the normal fallback chain (WalkIdle)

When to use Full vs standard:

Animation Use Standard Use Full
Idle Your item only poses its own bones Your item changes the whole resting posture
Walk Legs should use vanilla/context walk Your item needs a custom walk cycle (waddle, hop, shuffle)
Struggle Only owned bones move Whole body thrashes and writhes
Sneak Default sneak lean is fine Your item changes how sneaking looks

Key points:

  • Full animations include keyframes for ALL bones you want to control (owned + free).
  • Free bones in Full animations are only used when no other item owns them.
  • You can provide BOTH: Idle (region-only) and FullWalk (full-body). The mod picks the right one per context.
  • FullIdle is rarely needed — most items only need a full-body version for movement animations.

Context Animations

Context animations are separate from item animations. They control the player's overall body posture — how they walk, sit, sneak, kneel, etc. They are the foundation that item animations build on top of.

The Two-Layer System

Layer 1 — CONTEXT (body posture)       ← walk cycle, sit pose, sneak lean...
  Controls: body, legs (and any unowned bones)
  Bones owned by items are disabled on this layer.

Layer 2 — ITEM (your item's pose)      ← arms behind back, legs bound...
  Controls: only the bones in the item's declared regions.
  Overrides Layer 1 for those bones.

The mod ships with default context animations (currently as internal JSON files). These provide basic postures: a static standing pose, a simple walk, a sneak lean, sitting/kneeling positions.

Artists Can Replace or Extend Context Animations

This is where it gets interesting. You can create GLB files that replace or add to the mod's context animations. These are standalone GLBs — they are NOT tied to any specific item.

Use cases:

  • Replace the default walk cycle with a smoother, more natural one
  • Add a new "hogtied" context the mod doesn't ship with
  • Create an animation overhaul pack that improves every default posture
  • Provide themed context packs (e.g., "petplay contexts" with crawl animations)

Context GLB Format

A context GLB uses the same PlayerArmature skeleton as item GLBs. The difference is what it contains:

  • Item GLB: mesh + animations for a specific item's bones
  • Context GLB: no mesh — animations only, for all bones (body, legs, etc.)

The filename determines which context the GLB replaces. It must match one of the context suffixes:

Filename Context It Replaces
stand_idle.glb Standing still
stand_walk.glb Walking
stand_sneak.glb Sneaking
stand_struggle.glb Standing + struggling
sit_idle.glb Sitting
sit_struggle.glb Sitting + struggling
kneel_idle.glb Kneeling
kneel_struggle.glb Kneeling + struggling
shuffle_idle.glb Shuffle style — standing still (legs close together)
shuffle_walk.glb Shuffle style — tiny dragging steps
hop_idle.glb Hop style — standing with feet bound together
hop_walk.glb Hop style — small bunny hops
waddle_idle.glb Waddle style — standing with slight sway
waddle_walk.glb Waddle style — side-to-side waddling gait
crawl_idle.glb Crawl style — on all fours, resting (petplay/dogwalk pose)
crawl_move.glb Crawl style — on all fours, crawling forward

Names are exact and case-sensitive. The mod strips the .glb extension and looks up the suffix.

You don't need to provide all of them. Missing contexts fall back to the mod's builtin defaults. The GLB itself should contain at least one animation clip — the mod uses the first clip found.

What to Animate in Context GLBs

Context animations should animate body and legs — the "posture" bones. Do NOT animate bones that items will control (arms, head), as your context keyframes would be overridden by any equipped item anyway.

Bone Recommended? Why
body Yes Lean, sway, bob
leftUpperLeg / rightUpperLeg Yes Walk stride, sit angle
leftLowerLeg / rightLowerLeg Yes Knee bend
head Usually no Overridden by any item owning HEAD/EYES/EARS/MOUTH, and by vanilla head tracking
leftUpperArm / rightUpperArm Usually no Overridden by any item owning ARMS/HANDS. Only useful as a fallback when arms are free
leftLowerArm / rightLowerArm Usually no Same as upper arms
torso Rarely Same effect as body; prefer body

Example: Smooth Walk Cycle

The mod's default walk is basic. You want a nicer one:

In Blender:

  1. Use the TiedUp! template (same skeleton)
  2. Delete the reference player mesh — context GLBs have no mesh
  3. Create PlayerArmature|Walk: a 20-frame walk cycle animating body (bob), leftUpperLeg/rightUpperLeg (stride), leftLowerLeg/rightLowerLeg (knee bend)
  4. Export as GLB (armature only, no mesh)

Place it in a resource pack:

assets/mycreator/tiedup_contexts/stand_walk.glb

The filename stand_walk.glb matches the stand_walk context suffix. The mod discovers it at startup (or on F3+T reload) and uses your walk cycle instead of the builtin one. Players wearing handcuffs will have your smooth walk cycle on their legs/body, with arms still locked behind their back by the item.

Example: Custom Kneeling Context

The mod ships a basic kneel. You want a more expressive one with the body leaned slightly forward and legs tucked tighter:

In Blender:

  1. Create PlayerArmature|KneelIdle: single-frame pose with body pitched forward 15°, legs folded tight
  2. Create PlayerArmature|KneelStruggle: multi-frame animation with body rocking side to side while kneeling
  3. Export as GLB

Replacing vs Extending

  • Replace: Provide a context animation with the same name as a default. Your animation is used instead.
  • Extend: Provide a context animation for a situation the mod doesn't cover yet (e.g., a "Hogtied" context). Items can then reference this new context.

Context Packs

Since context GLBs are loaded from resource packs, the community can create context animation packs — collections of improved or themed postures. Players install them like any resource pack. No code changes, no item modifications.

BetterAnimations Pack/
  assets/betteranims/tiedup_contexts/
    stand_walk.glb        ← replaces default walk
    stand_sneak.glb       ← replaces default sneak
    sit_idle.glb          ← replaces default sit
    kneel_idle.glb        ← replaces default kneel
    kneel_struggle.glb    ← replaces default kneel struggle
    crawl_idle.glb        ← replaces default crawl idle
    crawl_move.glb        ← replaces default crawl movement

Movement Style Contexts

The mod includes 8 movement style contexts used when a player wears leg-restraining items with a movement_style field. These control how the player animates while moving in a restricted way.

The crawl style should animate the player on all fours in a petplay/dogwalk pose — NOT a belly-down swimming pose. Think of a pet being walked on a leash, with knees and hands on the ground, body tilted forward. The vanilla SWIMMING hitbox is used for the 0.6-block height, but the animation completely overrides the vanilla swimming visuals.

Style Idle Walk/Move Visual
Shuffle Legs close together, slight sway Tiny dragging steps, short stride Feet hobbled with a short chain
Hop Feet together, standing Small bunny hops, feet never separate Feet bound together, forced to hop
Waddle Slight lateral sway Body rocks left-right, wide stance Legs hobbled mid-thigh
Crawl On all fours, resting On all fours, crawling forward Petplay/dogwalk, hands and knees

These are excellent candidates for artist GLB replacements — the default animations are basic placeholders. A well-made walk cycle for shuffle_walk.glb or crawl_move.glb will dramatically improve the feel of the restraint system.


Animation Templates

Not every item needs custom animations. Many items in the same category share identical poses — all handcuffs put arms behind the back, all ankle cuffs restrict leg movement.

TiedUp! provides animation template GLBs with pre-made animations:

assets/tiedup/models/gltf/templates/
  handcuff_anims.glb       ← Idle, Struggle, SitIdle for ARMS items
  leg_restraint_anims.glb  ← Idle, Walk, SitIdle for LEGS items
  full_body_anims.glb      ← Idle, Walk, Struggle, SitIdle for full-body items

How to Use a Template

In your JSON definition, separate the mesh from the animations:

{
  "model": "mycreator:models/gltf/my_fancy_cuffs.glb",
  "animation_source": "tiedup:models/gltf/templates/handcuff_anims.glb"
}
  • model — your GLB with the 3D mesh (no animations needed)
  • animation_source — the template GLB with animations
  • If animation_source is omitted, animations come from model

Artist workflow: Model your item, weight-paint it, skip all animation work, reference a template. Done.

When NOT to Use a Template

  • Your item has a unique silhouette that would clip with template poses
  • You want a distinctive struggle animation
  • Your item affects bones differently (e.g., arms in front vs behind back)
  • You want to ship a premium, polished item

Exporting from Blender

Export Settings

File > Export > glTF 2.0 (.glb)

Setting Value Why
Format glb (binary) Single file, faster loading
Include > Limit to Selected Objects Export only armature + item mesh
Transform > +Y Up Checked glTF standard
Mesh > Apply Modifiers Checked Bake subdivision, mirror, etc.
Mesh > Normals Checked Needed for lighting
Mesh > Vertex Colors Checked (if used) For tintable items
Animation > Export Actions Checked Include all named actions
Animation > Group by NLA Track Unchecked Avoids duplicate animations
Animation > Sampling Rate 1 One sample per frame

Pre-Export Checklist

  • Armature is named PlayerArmature
  • All 11 bones have correct names (case-sensitive)
  • Actions are named PlayerArmature|Idle, PlayerArmature|Struggle, etc.
  • Mesh is weight-painted to skeleton bones only
  • Weights are normalized
  • No orphan bones (extra bones not in the standard 11 are ignored but add file size)
  • Materials/textures are applied (the GLB bakes them in)
  • Scale is correct (1 Blender unit = 1 Minecraft block = 16 pixels)

The JSON Definition

Every item needs a JSON file that declares its gameplay properties. The mod scans assets/<namespace>/tiedup_items/*.json at startup and on resource reload (F3+T).

Minimal Example — Rope Gag

{
  "type": "tiedup:bondage_item",
  "display_name": "Rope Gag",
  "model": "mycreator:models/gltf/rope_gag.glb",
  "regions": ["MOUTH"],
  "animation_bones": {
    "idle": []
  },
  "pose_priority": 10,
  "escape_difficulty": 2,
  "lockable": false
}

Standard Example — Iron Handcuffs

{
  "type": "tiedup:bondage_item",
  "display_name": "Iron Handcuffs",
  "model": "mycreator:models/gltf/iron_cuffs.glb",
  "slim_model": "mycreator:models/gltf/iron_cuffs_slim.glb",
  "animation_source": "tiedup:models/gltf/templates/handcuff_anims.glb",
  "regions": ["ARMS"],
  "animation_bones": {
    "idle": ["rightArm", "leftArm"],
    "struggle": ["rightArm", "leftArm"]
  },
  "pose_priority": 30,
  "escape_difficulty": 5,
  "lockable": true
}

Complex Example — Straitjacket

{
  "type": "tiedup:bondage_item",
  "display_name": "Leather Straitjacket",
  "model": "mycreator:models/gltf/straitjacket.glb",
  "slim_model": "mycreator:models/gltf/straitjacket_slim.glb",
  "regions": ["ARMS", "HANDS", "TORSO"],
  "blocked_regions": ["ARMS", "HANDS", "TORSO", "FINGERS"],
  "animation_bones": {
    "idle": ["rightArm", "leftArm", "body"],
    "struggle": ["rightArm", "leftArm", "body"]
  },
  "pose_priority": 50,
  "escape_difficulty": 7,
  "lockable": true
}

Complex Example — Ankle Chains (with movement style)

{
  "type": "tiedup:bondage_item",
  "display_name": "Ankle Chains",
  "model": "mycreator:models/gltf/ankle_chains.glb",
  "regions": ["FEET"],
  "animation_bones": {
    "idle": ["rightLeg", "leftLeg"],
    "walk": ["rightLeg", "leftLeg"]
  },
  "pose_priority": 30,
  "escape_difficulty": 5,
  "lockable": true,
  "movement_style": "shuffle"
}

The movement_style changes how the player physically moves — slower speed, different walking animation, and potentially disabled jumping. See Movement Styles below.

Complex Example — Hogtie (crawl style)

{
  "type": "tiedup:bondage_item",
  "display_name": "Hogtie Harness",
  "model": "mycreator:models/gltf/hogtie.glb",
  "regions": ["ARMS", "HANDS", "LEGS", "FEET"],
  "blocked_regions": ["ARMS", "HANDS", "LEGS", "FEET", "FINGERS", "WAIST"],
  "animation_bones": {
    "idle": ["rightArm", "leftArm", "rightLeg", "leftLeg", "body"],
    "struggle": ["rightArm", "leftArm", "rightLeg", "leftLeg", "body"]
  },
  "pose_priority": 90,
  "escape_difficulty": 9,
  "lockable": true,
  "movement_style": "crawl",
  "movement_modifier": {
    "speed_multiplier": 0.15
  }
}

Field Reference

Field Type Required Description
type string Yes Always "tiedup:bondage_item"
display_name string Yes Name shown in-game
model string Yes ResourceLocation of the GLB mesh
slim_model string No GLB for Alex-model players (3px arms)
texture string No Override texture (if not baked in GLB)
animation_source string No GLB to read animations from (defaults to model)
regions string[] Yes Body regions this item occupies
blocked_regions string[] No Regions blocked for other items (defaults to regions)
pose_priority int Yes Higher = overrides lower-priority items (see below)
escape_difficulty int Yes 1-10 scale. Higher = harder to struggle free
lockable bool No Can a padlock be applied? Default: true
supports_color bool No Whether this item has tintable zones. Default: false
tint_channels object No Default colors per tintable zone: {"tintable_1": "#FF0000"}
icon string No Inventory sprite model (see Inventory Icons below)
animations string/object No "auto" (default) or explicit name mapping
movement_style string No Movement restriction: "waddle", "shuffle", "hop", or "crawl"
movement_modifier object No Override speed/jump for the movement style (requires movement_style)
creator string No Author/creator name, shown in the item tooltip
animation_bones object Yes Per-animation bone whitelist (see below)

animation_bones (required)

Declares which bones each named animation is allowed to control for this item. This enables fine-grained per-animation bone filtering: an item might own body via its regions but only want the "idle" animation to affect the arms.

Format: A JSON object where each key is an animation name (matching the GLB animation names) and each value is an array of bone names.

Valid bone names: head, body, rightArm, leftArm, rightLeg, leftLeg

Example:

"animation_bones": {
  "idle": ["rightArm", "leftArm"],
  "struggle": ["rightArm", "leftArm", "body"]
}

At runtime, the effective bones for a given animation clip are computed as the intersection of animation_bones[clipName] and the item's owned parts (from region conflict resolution). If the clip name is not listed in animation_bones, the item falls back to using all its owned parts.

This field is required. Items without animation_bones will be rejected by the parser.

Pose Priority

When multiple items affect the same bones, the highest pose_priority wins.

Priority Range Item Type Example
110 Light cosmetic Collar, blindfold, gag
2040 Standard restraint Handcuffs, ankle cuffs
4060 Heavy restraint Armbinder, straitjacket
6080 Full-body restraint Leg binder + armbinder combo
80100 Total restraint Sleep sack, full bind

Blocked Regions

blocked_regions prevents other items from being equipped on those regions while your item is worn.

Examples:

  • Hood → regions: ["HEAD"], blocked_regions: ["HEAD", "EYES", "EARS"] — covers head, eyes, ears. Mouth is still accessible (for a gag underneath).
  • Handcuffs → regions: ["ARMS"], no blocked_regions needed — only blocks ARMS by default.
  • Straitjacket → regions: ["ARMS", "HANDS", "TORSO"], blocked_regions: ["ARMS", "HANDS", "TORSO", "FINGERS"] — also blocks finger accessories.

If blocked_regions is omitted, it defaults to the same as regions.

Movement Styles

Items that restrict the player's legs can declare a movement_style to change how the player physically moves. This affects both server-side movement (speed, jumping) and client-side animation.

Available styles:

Style Speed Jump Animation Typical Use
waddle 0.6× allowed Side-to-side sway Hobble skirt, thigh cuffs
shuffle 0.4× disabled Tiny dragging steps Short ankle chain, leg binder
hop 0.35× (between hops) auto-hop Small bunny hops Bound feet (rope, tape)
crawl 0.2× disabled On all fours (petplay) Hogtie, pet harness

Resolution: If multiple equipped items have different styles, the most constraining one wins (crawl > hop > shuffle > waddle).

Override defaults: Use movement_modifier to fine-tune speed for a specific item:

{
  "movement_style": "shuffle",
  "movement_modifier": {
    "speed_multiplier": 0.3,
    "jump_disabled": true
  }
}

movement_modifier only works when movement_style is set. Without a style, the modifier is ignored.

For artists: Each style has idle and walk/move context animations that can be replaced with GLB files (see Movement Style Contexts above). The default animations are basic placeholders — custom GLBs will dramatically improve the feel.

Inventory Icons

By default, data-driven items show a generic placeholder icon in the inventory. To give your item a custom inventory sprite, use the icon field.

How it works: The icon field points to a Minecraft item model (not a raw texture). The mod resolves this model at render time and displays it as the inventory sprite.

Step 1: Create a 16x16 PNG texture

Place it in your resource pack:

assets/<namespace>/textures/item/my_armbinder_icon.png

Step 2: Create a model JSON

Create a simple item/generated model that references your texture:

assets/<namespace>/models/item/my_armbinder_icon.json

Contents:

{
  "parent": "minecraft:item/generated",
  "textures": {
    "layer0": "<namespace>:item/my_armbinder_icon"
  }
}

Step 3: Reference it in your item/furniture JSON

{
  "display_name": "Leather Armbinder",
  "icon": "<namespace>:item/my_armbinder_icon",
  ...
}

The icon value is the model's ResourceLocation: <namespace>:item/<filename> (without the .json extension, matching how Minecraft references models).

Shortcut: You can also reference any existing vanilla or mod item model. For example, "icon": "minecraft:item/chain" displays the vanilla chain icon. Useful for testing.

If icon is omitted: The item displays the default generic sprite. No crash, no error.


Packaging as a Resource Pack

File Structure

MyItemPack/
  pack.mcmeta
  assets/
    mycreator/
      tiedup_items/                  ← JSON definitions (scanned at startup + F3+T)
        rope_gag.json
        iron_cuffs.json
        straitjacket.json
      models/gltf/                   ← GLB mesh files
        rope_gag.glb
        iron_cuffs.glb
        straitjacket.glb
      models/item/                   ← Icon model JSONs (for inventory sprites)
        rope_gag_icon.json
        iron_cuffs_icon.json
      textures/item/                 ← Icon textures (16x16 PNG)
        rope_gag_icon.png
        iron_cuffs_icon.png
      tiedup_contexts/               ← Context GLBs (optional, replaces default postures)
        stand_walk.glb
        stand_sneak.glb

pack.mcmeta

{
  "pack": {
    "pack_format": 15,
    "description": "My Custom TiedUp! Items"
  }
}

Namespace

Use your own namespace (e.g., mycreator) to avoid conflicts with the base mod or other packs. The mod scans all namespaces for tiedup_items/ and tiedup_contexts/ directories.


Common Mistakes

Skeleton Issues

Mistake Symptom Fix
Bone name typo (RightUpperArm instead of rightUpperArm) Mesh doesn't follow that bone Names are camelCase, not PascalCase. Check exact spelling.
Extra bones in the armature No visible issue (ignored), larger file Delete non-standard bones before export
Missing PlayerArmature root Mesh renders at wrong position Rename your armature root to PlayerArmature
Animating body bone without TORSO region Body keyframes used only if body is free (no other item owns it) Declare TORSO/WAIST region if you always want to control body, or use Full animations for free-bone effects

Animation Issues

Mistake Symptom Fix
Action not prefixed with PlayerArmature| Animation not found, falls back to first clip Rename: IdlePlayerArmature|Idle
Wrong case (idle instead of Idle) Animation not found Use exact PascalCase: Idle, SitIdle, KneelStruggle
Variant gap (.1, .2, .4 — missing .3) Only .1 and .2 are used Number sequentially with no gaps
Animating bones outside your regions Keyframes silently ignored Only animate bones in your declared regions
Multi-frame Idle Works but wastes resources Idle should be a single keyframe at frame 0

Weight Painting Issues

Mistake Symptom Fix
Vertices not weighted to any bone Part of mesh stays frozen in space Weight paint everything to at least one bone
Weights not normalized Mesh stretches or compresses oddly Blender > Weights > Normalize All
Weighted to a non-standard bone That part of mesh stays frozen Only weight to the 11 standard bones

JSON Issues

Mistake Symptom Fix
Wrong model path Item invisible Check ResourceLocation format: namespace:path/to/file.glb
Missing regions Item can't be equipped Every item needs at least one region
pose_priority: 0 Other items always override yours Use at least 1. See priority guide above.
blocked_regions too broad Players can't equip combinations you intended Only block what your item physically covers

Examples

Example 1: Simple Collar (No Animation)

A collar sits on the neck. It doesn't change the player's pose.

Blender:

  • Model a ring mesh around the neck area
  • Weight paint to body bone (so it follows torso movement)
  • Create PlayerArmature|Idle with a single keyframe — don't move any bones (identity pose)
  • Export GLB

JSON:

{
  "type": "tiedup:bondage_item",
  "display_name": "Leather Collar",
  "model": "mycreator:models/gltf/leather_collar.glb",
  "regions": ["NECK"],
  "animation_bones": {
    "idle": []
  },
  "pose_priority": 5,
  "escape_difficulty": 3,
  "lockable": true
}

The collar renders on the player's neck, follows body movement, and takes the NECK slot. No bones are animated — the player moves normally.

Example 2: Handcuffs (Arms Behind Back)

Blender:

  • Model cuff meshes on both wrists + a chain between them
  • Weight paint cuffs to rightLowerArm and leftLowerArm, chain to body
  • Create PlayerArmature|Idle: pose both arms behind the back
  • Optionally create PlayerArmature|Struggle: multi-frame animation of pulling against cuffs
  • Export GLB

JSON:

{
  "type": "tiedup:bondage_item",
  "display_name": "Iron Handcuffs",
  "model": "mycreator:models/gltf/iron_cuffs.glb",
  "animation_source": "tiedup:models/gltf/templates/handcuff_anims.glb",
  "regions": ["ARMS"],
  "animation_bones": {
    "idle": ["rightArm", "leftArm"],
    "struggle": ["rightArm", "leftArm"]
  },
  "pose_priority": 30,
  "escape_difficulty": 5,
  "lockable": true
}

Using animation_source means the cuffs mesh comes from your GLB but animations come from the official handcuff template. The arms go behind the back, the cuffs mesh follows. The player can still walk, look around, sit, and sneak — only the arms are locked.

Example 3: Blindfold (Head Region, Cosmetic Pose)

Blender:

  • Model a strip of cloth across the eyes
  • Weight paint to head bone
  • Create PlayerArmature|Idle: tilt head down ~10 degrees (subtle "I can't see" pose)
  • Export GLB

JSON:

{
  "type": "tiedup:bondage_item",
  "display_name": "Silk Blindfold",
  "model": "mycreator:models/gltf/silk_blindfold.glb",
  "regions": ["EYES"],
  "animation_bones": {
    "idle": ["head"]
  },
  "pose_priority": 10,
  "escape_difficulty": 1,
  "lockable": false
}

The blindfold occupies EYES (a sub-region of HEAD). A hood (HEAD) would block it, but a gag (MOUTH) wouldn't. The head tilt is subtle and combines with vanilla head tracking.


Furniture Creation

Create interactive furniture (St. Andrew's Cross, pillory, cage, etc.) using a single GLB file containing both the furniture model and player seat skeletons.

How Furniture Works

Unlike body items (which are skinned onto the player's skeleton), furniture is a standalone entity in the world. It has its own mesh, its own skeleton, and its own animations. Players "sit" on it via the riding system, and the mod forces a pose on the player from the furniture's GLB.

One GLB file contains everything:

  Furniture_Armature      ← The furniture mesh (cross, pillory, cage...)
  │  Mesh: wood_frame, chains, locks...
  │  Animations: Idle, Occupied, LockClose, Shake...
  │
  Player_main             ← Player skeleton #1 (positioned on the furniture)
  │  Standard 11 bones (same as body items)
  │  Animations: main:Idle, main:Struggle, main:Enter...
  │
  Player_left (optional)  ← Player skeleton #2 (for multi-seat furniture)
  │  Same 11 bones
  │  Animations: left:Idle, left:Struggle...
  │
  Player_right (optional) ← Player skeleton #3
     ...

The artist sees the exact result in Blender — the player posed on the furniture. No guessing offsets in JSON.

Note: There is currently no ghost preview when placing furniture in-game. The entity spawns immediately on right-click. Use Blender to check positioning.

Furniture Skeleton

The furniture armature can have any bones you want. Unlike body items (which must use the standard 11-bone player skeleton), furniture bones are custom. Name them whatever makes sense: door_hinge, chain_left, lock_bolt, etc. The name Furniture_Armature used in this guide is a convention — any name works as long as it does NOT start with Player_.

Furniture_Armature       ← armature root (required, any name NOT starting with "Player_")
  └─ frame               ← main body of the furniture
       ├─ door_hinge      ← animated: opens/closes
       ├─ chain_left      ← animated: tightens when occupied
       ├─ chain_right
       └─ lock_bolt       ← animated: rotates when locked

Rules:

  • The furniture armature name must NOT start with Player_ (that prefix is reserved for seat skeletons)
  • All bones are kept — no filtering (unlike body items which filter through isKnownBone)
  • Weight paint your furniture mesh to these bones normally

Seat Skeletons (Player_*)

Each seat is a standard player skeleton (the same 11 bones from body items) positioned on the furniture. The armature name determines the seat ID.

Player_main     → seat ID: "main"
Player_left     → seat ID: "left"
Player_right    → seat ID: "right"
Player_grab     → seat ID: "grab" (for monsters)

How to set up a seat in Blender:

  1. Duplicate the standard player armature (from a body item template or the skeleton reference above)
  2. Rename it to Player_{seatId} (e.g., Player_main)
  3. Position it on the furniture exactly where you want the player to appear
  4. Pose it in the Idle position (arms spread on a cross, bent over a pillory, etc.)
  5. The root position + rotation of this armature = where the player sits in-game

Important:

  • The seat skeleton uses the EXACT same 11 bone names as body items (body, head, leftUpperArm, etc.)
  • You do NOT need a mesh for the seat skeleton — it's data-only (positioning + animation)
  • The seat skeleton's world-space position relative to the furniture origin = the player's offset in-game
  • Multiple seats = multiple Player_* armatures (each positioned differently on the furniture)

Furniture Animations

Furniture uses a naming convention to separate furniture animations from player animations.

Furniture Mesh Animations

Target the Furniture_Armature. Blender exports them as Furniture_Armature|AnimName.

Animation Name When Played Required?
Idle Default state, no passengers Recommended
Occupied At least one player is seated Optional (falls back to Idle)
LockClose A seat gets locked (one-shot) Optional
LockOpen A seat gets unlocked (one-shot) Optional
Shake Player is struggling (loops during struggle) Optional

Example in Blender's Action Editor:

Furniture_Armature|Idle       ← chains hanging loose
Furniture_Armature|Occupied   ← chains pulled taut
Furniture_Armature|Shake      ← whole frame vibrates

Player Seat Animations

Target the Player_* armatures. Blender exports them as Player_main|AnimName. The mod resolves them as {seatId}:{AnimName}.

Animation Name When Played Required?
Idle Default seated pose Yes (no fallback)
Struggle Player struggling to escape Optional (stays in Idle)
Enter Mount transition (one-shot, 1 second) Optional (snaps to Idle if absent)
Exit Dismount transition (one-shot, 1 second) Optional (snaps to vanilla if absent)

Example in Blender's Action Editor:

Player_main|Idle       → resolved as "main:Idle"     ← arms spread, legs apart
Player_main|Struggle   → resolved as "main:Struggle"  ← pulling against restraints
Player_left|Idle       → resolved as "left:Idle"      ← head and arms through pillory
Player_right|Idle      → resolved as "right:Idle"     ← same pose, other side

Key difference from body items: Furniture player animations control ALL 11 bones, not just region-owned bones. The furniture overrides the player's entire pose for the blocked regions, and the remaining regions still show body item effects (gag, blindfold, etc.).

The JSON Definition

Furniture JSON goes in data/<namespace>/tiedup_furniture/. Unlike body items, furniture definitions are server-authoritative and synced to clients automatically.

Full Format

{
  "id": "mynamespace:my_cross",
  "display_name": "Custom Cross",
  "translation_key": "furniture.mynamespace.my_cross",

  "model": "mynamespace:models/gltf/furniture/my_cross.glb",
  "icon": "mynamespace:item/my_cross_icon",

  "tint_channels": {
    "tintable_0": "#8B4513",
    "tintable_1": "#1A1A1A"
  },
  "supports_color": true,

  "hitbox": { "width": 1.2, "height": 2.4 },

  "placement": {
    "snap_to_wall": true,
    "floor_only": true
  },

  "lockable": true,
  "break_resistance": 100,
  "drop_on_break": true,

  "seats": [
    {
      "id": "main",
      "armature": "Player_main",
      "blocked_regions": ["ARMS", "HANDS", "LEGS", "FEET"],
      "lockable": true,
      "locked_difficulty": 150,
      "item_difficulty_bonus": true
    }
  ],

  "category": "restraint"
}

JSON Field Guide

Field Required? Description
id Yes Unique ID, e.g., mynamespace:wooden_cross
display_name Yes Fallback name if no translation key
translation_key No i18n key for localized name
model Yes Path to GLB file in assets
icon No Inventory sprite model (same system as body items — see Inventory Icons)
tint_channels No Default tint colors (hex #RRGGBB) per channel
supports_color No Planned — player recoloring via dye (default: false)
hitbox.width No Entity collision width (0.15.0, default: 1.0)
hitbox.height No Entity collision height (0.15.0, default: 1.0)
placement.snap_to_wall No Align to nearest wall on placement (default: false)
placement.floor_only No Can only be placed on solid ground (default: true)
lockable No Can seats be locked with a key? (default: false)
break_resistance No Cumulative damage to break (110000, default: 100)
drop_on_break No Drop placer item when broken? (default: true)
seats Yes 18 seat definitions (see below)
feedback.mount_sound No Sound when a player is force-mounted
feedback.lock_sound No Sound when a seat is locked
feedback.unlock_sound No Sound when a seat is unlocked
feedback.struggle_loop_sound No Sound played when struggle starts
feedback.escape_sound No Sound on successful escape (default: chain break)
feedback.denied_sound No Sound when action is denied (locked dismount, no seat)
category No Creative tab grouping (default: "furniture")

Seat Definition Fields

Field Required? Description
id Yes Seat identifier (must match Player_{id} armature name, no : allowed)
armature Yes GLB armature name (e.g., Player_main)
blocked_regions No Body regions the furniture controls (default: empty — no blocking)
lockable No Can this seat be locked? (inherits from top-level lockable)
locked_difficulty Yes if lockable Struggle difficulty when locked (110000)
item_difficulty_bonus No Body items on free regions add to escape difficulty? (default: false)

Blocked Regions Explained

When a player sits in a seat with blocked_regions: ["ARMS", "HANDS"]:

  • Animation: The furniture's player pose controls arm + hand bones. Body items on ARMS/HANDS are ignored.
  • Rendering: Body items on ARMS/HANDS are hidden (the furniture pose replaces them visually).
  • Gameplay: The master cannot equip/unequip items on ARMS/HANDS while the player is seated.
  • Other regions (HEAD, MOUTH, NECK, etc.) work normally — items render, can be changed, and contribute to escape difficulty if item_difficulty_bonus: true.

Note: The struggle minigame for escaping locked furniture seats uses the same continuous struggle system as body items. The player holds directional keys to drain resistance. The total difficulty = locked_difficulty + bonus from equipped body items on non-blocked regions (if item_difficulty_bonus: true), capped at 600.

Tint Channels (Same as Body Items)

Furniture supports the exact same tint channel system as body items. See Tint Channels above — everything applies identically.

Name your Blender materials tintable_0, tintable_1, etc., use grayscale textures, and define defaults in the JSON tint_channels field.

Exporting Furniture from Blender

Same export settings as body items, with one extra consideration:

  1. Select ALL armatures — Furniture_Armature AND all Player_* armatures
  2. File → Export → glTF 2.0 (.glb)
  3. Settings:
    • Format: glTF Binary (.glb)
    • Include: Selected Objects
    • Mesh: Apply Modifiers
    • Animation: Export all actions (NLA strips or all actions)
    • Important: Do NOT merge armatures — each must remain separate

Packaging as a Resource/Data Pack

Furniture needs files in two locations:

my_resource_pack/
├── assets/mynamespace/
│   ├── models/gltf/furniture/
│   │   └── my_cross.glb              ← The GLB model (client resource)
│   ├── models/item/
│   │   └── my_cross_icon.json        ← Icon model (inventory sprite)
│   └── textures/item/
│       └── my_cross_icon.png         ← Icon texture (16x16)
│
└── data/mynamespace/
    └── tiedup_furniture/
        └── my_cross.json             ← The JSON definition (server data)

The GLB goes in assets/ (it's a client resource for rendering). The JSON goes in data/ (it's server-authoritative gameplay data, synced to clients via packet).

Common Furniture Mistakes

Mistake Symptom Fix
Seat armature named Main instead of Player_main No seat detected, furniture is decoration only Prefix MUST be Player_
Seat ID contains : (e.g., Player_seat:left) JSON rejected by parser Use only alphanumeric + underscore in seat IDs
Player skeleton uses wrong bone names Player not posed correctly on furniture Use the exact 11 standard bone names (camelCase)
Armatures merged into one on export Parser can't separate furniture from seats Export with separate armatures, don't merge
No Idle animation for a seat Player has no pose on furniture Every seat MUST have at least `{armature}
Furniture animation named `Player_main Idle` Parsed as a seat animation, not furniture
More than 8 seats JSON rejected by parser Maximum 8 seats per furniture piece
blocked_regions references unknown region JSON rejected by parser Use exact names from the Region Table above

Furniture Examples

Example 1: Simple Chair (No Restraint)

A decorative chair anyone can sit on. No locking, no blocked regions.

Blender:

  • Model a chair mesh, weight paint to a single frame bone
  • Add Player_main skeleton sitting on the chair
  • Create Player_main|Idle with the player seated (legs bent, arms on armrests)
  • Create Furniture_Armature|Idle (static, single keyframe)

JSON:

{
  "id": "mycreator:wooden_chair",
  "display_name": "Wooden Chair",
  "model": "mycreator:models/gltf/furniture/wooden_chair.glb",
  "hitbox": { "width": 0.8, "height": 1.0 },
  "seats": [
    {
      "id": "main",
      "armature": "Player_main",
      "blocked_regions": []
    }
  ]
}

No locking, no blocked regions. The player sits freely and can stand up anytime.

Example 2: St. Andrew's Cross (Single Seat)

Blender:

  • Model the X-frame with chains at wrist and ankle positions
  • Add bones for animated parts: chain_left, chain_right, lock_mechanism
  • Add Player_main skeleton with arms and legs spread in an X pose
  • Create animations:
    • Furniture_Armature|Idle — chains hanging loose
    • Furniture_Armature|Occupied — chains pulled taut
    • Furniture_Armature|Shake — frame vibrating (for struggle)
    • Player_main|Idle — arms spread, legs apart, flush against the cross
    • Player_main|Struggle — pulling against restraints, body twisting

JSON:

{
  "id": "mycreator:saint_andrews_cross",
  "display_name": "St. Andrew's Cross",
  "model": "mycreator:models/gltf/furniture/cross.glb",
  "tint_channels": { "tintable_0": "#8B4513" },
  "supports_color": true,
  "hitbox": { "width": 1.2, "height": 2.4 },
  "placement": { "snap_to_wall": true },
  "lockable": true,
  "break_resistance": 150,
  "seats": [
    {
      "id": "main",
      "armature": "Player_main",
      "blocked_regions": ["ARMS", "HANDS", "LEGS", "FEET"],
      "lockable": true,
      "locked_difficulty": 150,
      "item_difficulty_bonus": true
    }
  ],
  "category": "restraint"
}

The player is locked with arms and legs controlled by the cross. A master can still equip a gag (MOUTH), blindfold (EYES), or collar (NECK) on the mounted player. The gag adds to escape difficulty because item_difficulty_bonus: true.

Example 3: Double Pillory (Two Seats)

Blender:

  • Model a long wooden pillory with two holes
  • Add Player_left and Player_right skeletons, each bent forward with head and arms through the pillory holes
  • Position them side by side on the furniture

JSON:

{
  "id": "mycreator:double_pillory",
  "display_name": "Double Pillory",
  "model": "mycreator:models/gltf/furniture/pillory.glb",
  "hitbox": { "width": 2.0, "height": 1.5 },
  "lockable": true,
  "seats": [
    {
      "id": "left",
      "armature": "Player_left",
      "blocked_regions": ["ARMS", "HANDS", "HEAD"],
      "lockable": true,
      "locked_difficulty": 120
    },
    {
      "id": "right",
      "armature": "Player_right",
      "blocked_regions": ["ARMS", "HANDS", "HEAD"],
      "lockable": true,
      "locked_difficulty": 120
    }
  ],
  "category": "restraint"
}

Two players can be locked side by side. The mod picks the seat nearest to where you're looking when you sit down.

Monster Seat System (Planned)

The furniture system is built on a universal ISeatProvider interface that is not limited to static furniture. Any living entity (monster, NPC) can implement the same interface to hold players in constrained poses using the same mechanics: blocked regions, forced animations, lock/escape.

Example use case: A tentacle monster that grabs a player on attack — the player "rides" the monster, gets a forced pose (arms restrained), and must struggle to escape. The monster's GLB would contain a Player_grab armature with Player_grab|Idle and Player_grab|Struggle animations, following the exact same convention as furniture seats.

What this means for artists: If you create a monster model, you can include Player_* armatures in the GLB using the same workflow as furniture. The seat animations, blocked regions, and escape mechanics will work identically.

Current status: The ISeatProvider interface and all downstream systems (animation, rendering, packets, escape) are implemented and ready. No monster entity or AI has been created yet — this requires its own design phase (AI goals, behaviors, spawn conditions, etc.).


Quick Reference Cards

Body Items

REQUIRED:
  ✓ Skeleton: PlayerArmature with 11 named bones (camelCase, exact)
  ✓ At least one animation: PlayerArmature|Idle
  ✓ Weight painting to standard bones
  ✓ JSON in assets/<namespace>/tiedup_items/ with type, display_name, model, regions

NEVER DO:
  ✗ Animate PlayerArmature (armature root, not a bone)
  ✗ Use wrong case for bone names or animation names
  ✗ Leave vertices unweighted
  ✗ Use pose_priority 0

GOOD TO KNOW:
  → Only Idle is required. Everything else has fallbacks.
  → Templates let you skip animation entirely.
  → Free bones (not owned by any item) CAN be animated by your GLB.
  → Bones owned by another equipped item are always ignored.
  → The mod handles sitting, sneaking, walking — you don't have to.
  → Context GLBs in tiedup_contexts/ replace default postures.
  → Slim model is optional. Steve mesh works on Alex (minor clipping).
  → Textures bake into the GLB. No separate file needed.

Furniture

REQUIRED:
  ✓ Furniture_Armature (any name NOT starting with "Player_")
  ✓ At least one Player_{seatId} armature with 11 standard player bones
  ✓ At least one animation per seat: Player_{seatId}|Idle
  ✓ GLB in assets/<namespace>/models/gltf/furniture/
  ✓ JSON in data/<namespace>/tiedup_furniture/ with id, display_name, model, seats

NEVER DO:
  ✗ Name furniture armature starting with "Player_"
  ✗ Use ":" in seat IDs
  ✗ Merge armatures on export (each must stay separate)
  ✗ Use wrong bone names in seat skeletons
  ✗ More than 8 seats per furniture

GOOD TO KNOW:
  → Furniture bones can be anything (not limited to player skeleton)
  → Seat position = Player_* armature position in Blender (no JSON offset)
  → Player animations on blocked regions override body items
  → Body items on non-blocked regions still render normally
  → Furniture mesh animations (Idle, Occupied, Shake) are optional
  → Tint channels work the same as body items
  → JSON is server-authoritative, synced to clients automatically