In-game debug tool for finding prop attachment offsets and rotations — on ped bones or vehicle bones — with a draggable 3D gizmo, a freecam, nearest-bone recalibration, and copy-paste code export.
Built for tuning AttachEntityToEntity values visually instead of guessing numbers and restarting.
| Command | What it does |
|---|---|
/propattacher |
Open / minimize the tool (keeps the current target mode) |
/propattacher ped |
Target the player ped |
/propattacher veh |
Target the vehicle you're sitting in, or the nearest one within 20m |
/propattacher veh near |
Target the nearest other vehicle (ignores the one you're in) |
Running the command while the tool is open in the same mode minimizes it. Running it with a different mode (or veh near) switches target — any in-progress placement is cancelled.
- SPAWN tab — type a prop model (e.g.
w_lr_rpg) and hit SPAWN. Pick a bone from the list (searchable), or type an index/name directly. In vehicle mode the bone list is filtered to bones that actually exist on the targeted vehicle model. - ADJUST tab — position the prop:
- Drag an arrow to move along that axis. Drag a ring to rotate around it.
- Hold RMB outside the panel for freecam: WASD to fly, mouse to look, Shift = fast, Q/E = up/down. Release RMB to get the cursor back — the camera stays where you left it.
- Scroll wheel nudges Z. Nudge buttons / direct number entry / step-size presets also work.
- E confirms, Backspace minimizes.
- ⟳ RECAL — finds the bone closest to where the prop currently sits and re-attaches to it without the prop moving in the world. The result is applied as a live preview with measured accuracy (Δpos / Δrot) shown in the popup — keep it or revert.
- CONFIRM saves the placement to the attached list. Spawn the next prop or:
- ATTACHED tab — click any entry to pull it back into live editing (works across modes — a vehicle attachment re-targets its vehicle automatically). During an edit, CANCEL restores the original attachment instead of deleting the prop. ✕ deletes an entry.
- EXPORT tab — generate ready-to-paste Lua in two formats:
- Full
AttachEntityToEntityblocks (model loading included) - Compact
{model, bone, pos, rot}tables
- Full
Ped exports use GetPedBoneIndex(ped, <id>); vehicle exports use GetEntityBoneIndexByName(veh, "<name>") with the correct isPed flag.
fxmanifest.lua
shared/bones.lua -- ped bone list (tags), vehicle bone list (names), ped presets
client/client.lua -- everything: gizmo, freecam, placement, recal, NUI callbacks
html/ -- NUI panel (index.html, css/style.css, js/app.js)
Add to your server config: ensure noted_propattacher. No dependencies, client-side only — props are spawned locally and are not networked to other players as persistent attachments.
- Rotation order is ROT_YZX. All
AttachEntityToEntitycalls here passrotationOrder = 1, which the engine applies as intrinsic Y→Z→X (M = Ry(roll)·Rz(yaw)·Rx(pitch)). This does not match the commonGetEntityRotation(2)ZXY convention.RotFromEuler/EulerFromAxesinclient.luaimplement the correct chart — every euler↔matrix conversion must go through them. Using the wrong chart was the historical cause of inverted drag axes, dead rotation rings, and recal landing ~45° off. - Gizmo axes are derived, not queried. The engine guarantees
prop_matrix = bone_frame · R(rotation), so the true attach frame is computed asprop_matrix · R⁻¹from the prop's live entity matrix. Don't replace this withGetEntityBoneRotationformulas — their convention doesn't match the attach frame. - Ring drags compose matrices. A ring drag applies a true rotation about the ring's bone-frame axis and converts back to euler (
RotateAboutBoneAxis) — adding degrees to a single euler component only works for the outermost axis under YZX. - Recalibrate measures empirically, then converges. The new bone's attach frame is measured by attaching at zero and reading the entity matrix, then a convergence loop corrects offset/rotation from the observed landing spot until Δpos < 1.5cm and Δrot < 0.5°. Two hard-won rules:
recalBusymust suspend the placement loop — it re-attaches the prop to the old bone every frame and corrupts measurements taken mid-Wait.- Never measure an invisible attached entity —
GetEntityMatrixgoes stale and silently poisons both the math and its own verification. Reads are validated againstGetWorldPositionOfEntityBone(skeleton-fresh) instead of blind-waiting.
- Cursor rays come from
GetWorldCoordFromScreenCoord, which uses the active render camera — correct while the scripted freecam is rendering (a paused freecam keeps rendering) and at any FOV/aspect ratio. - Recal debug tracing: set
RECAL_DEBUG = falseinclient.luato silence the[recal]F8 prints. Leave it on while tuning — iteration 1 should reportangErrnear zero; if it doesn't, the rotation chart no longer matches the attach flags.
- Rotation extraction has a gimbal singularity at yaw ±90° (rare in practice; recal's convergence loop still bounds the error).
- Recal accuracy on peds bottoms out around the idle-animation sway (~1cm) — the skeleton literally moves while measuring.
- Attached-list entries reference live entity handles; props and their parent vehicles don't survive resource restarts or entity despawns (the UI reports and prunes dead entries when you interact with them).