Skip to content

Courage.Attach#

require("Courage.Attach") attaches J3D model resources to game actors. The attached bone hierarchy is grafted onto the actor's skeleton and rendered each frame with optional joint remapping, shape visibility, and a positional/rotation offset.

Loading the module#

local Attach = require("Courage.Attach")
import * as Attach from "Courage.Attach"

API#

Function Returns Description
Attach.attach(actor, arc, resIdx, opts?) id or nil Load arc (archive name) and attach resource index resIdx to actor. Returns an attachment id on success, nil on failure.
Attach.detach(id) (none) Remove the attachment created by attach.
Attach.detach_all(actor) (none) Remove every attachment registered to actor.
Attach.is_ready(id) boolean true once the archive is loaded and the model is visible.
Attach.set_shape_vis(id, matIdx, visible) boolean Show or hide material index matIdx on the attached model.

Options table#

The fourth argument to Attach.attach is an optional table with these fields:

Field Type Description
base integer Joint index on the attachment skeleton to use as the root (default -1 = skeleton root).
joints { [n]: { dstJoint, srcJoint } }[] Array of { dst, src } pairs to remap skeleton joints. dst is the actor joint; src is the archive joint.
hide_mats integer[] Material indices to hide immediately after attaching.
show_mats integer[] Material indices to force-show immediately after attaching.
offset { x, y, z } Positional offset applied relative to the attachment joint, in game units.
rot { x, y, z } Euler rotation offset in degrees applied after offset.

Example#

local Hook   = require("Augment.Hook")
local Attach = require("Courage.Attach")

local MyMod = mod("custom-hat")

local s_attachId = nil

-- Hook the Link actor's execute so we have a pointer to it
Hook["daAlink_c::execute"]:before(function(ctx)
    if s_attachId ~= nil then return end
    local link = ctx.self
    s_attachId = Attach.attach(link, "custom_hat", 0, {
        base = 5,                    -- attach to joint 5
        offset = { 0, 20, 0 },       -- 20 units above the joint
        rot    = { 0, 90, 0 },       -- rotated 90 degrees around Y
    })
end)

function MyMod:shutdown()
    if s_attachId then
        Attach.detach(s_attachId)
        s_attachId = nil
    end
end

return MyMod
import * as Hook   from "Augment.Hook"
import * as Attach from "Courage.Attach"

declare function mod(name: string): any

const MyMod = mod("custom-hat")

let attachId: number | undefined

Hook["daAlink_c::execute"].before((ctx) => {
    if (attachId !== undefined) return
    const link = ctx.self
    attachId = Attach.attach(link, "custom_hat", 0, {
        base: 5,
        offset: [0, 20, 0],
        rot:    [0, 90, 0],
    })
})

MyMod.shutdown = function () {
    if (attachId !== undefined) {
        Attach.detach(attachId)
        attachId = undefined
    }
}

export = MyMod

Notes#

  • attach queues an archive load. Poll is_ready(id) in your tick before relying on the attachment being visible.
  • Calling detach_all(actor) is convenient in shutdown when you don't track individual ids.
  • Material indices follow the J3D material order in the archive. Use a model viewer to find the correct index.
  • set_shape_vis returns false when id is invalid or the index is out of range.

See also#