Hitboxes, attachments and other odds and ends

The small structures hanging directly off studiohdr_t. They have little in common beyond being short and pointing at bones.

Hitboxes come in sets. studiohdr_t.hitboxsetindex reaches mstudiohitboxset_t, not the boxes themselves – a model can carry several complete alternative hitbox layouts and switch between them, which is how a character can be shot differently depending on what it is doing. Each set owns numhitboxes mstudiobbox_t, and each of those is an axis-aligned box in the space of one bone, so it follows that bone as the model animates.

mstudiobbox_t is the one place the format guards a string index against zero rather than leaving zero-means-null to convention:

const char* pszHitboxName()
{
    if( szhitboxnameindex == 0 )
        return "";
    return ((const char*)this) + szhitboxnameindex;
}

That guard appears to be for other people’s models. Across the Team Fortress 2 corpus not one hitbox takes it: every szhitboxnameindex is a real offset, and every one of them points at an empty string. studiomdl writes the pointer and the terminator rather than writing zero. So the name reads as '' either way, but by a different route than the SDK suggests – and a reader that only handles the zero case will still be correct here, while a writer that emits zero will produce a file that differs from studiomdl’s byte for byte.

Attachments are named coordinate frames. Each mstudioattachment_t is a matrix in one bone’s space, which is how the engine knows where a weapon’s muzzle flash goes or where a player’s cosmetic hangs. local is the offset from localbone.

Pose parameters are the inputs to blended sequences – move_x, body_yaw and so on. loop is the value the parameter wraps at: 0 for a parameter that does not wrap, 360 for one that describes a rotation.

Model groups are $includemodel, the mechanism by which a model borrows animations from other files at runtime. sznameindex is the other mdl’s path. Only 35 of the 9858 models in a stock Team Fortress 2 install use it, and they are almost all bots – bot_heavy, bot_demo, headless_hatman – which is exactly why studiohdr_t.numlocalanim and numlocalseq are named local. A model like that carries few animations of its own and expects the engine to merge in the ones it names here. ValveMDL reads the list; resolving it means opening other files, and it does not do that.

struct valvemdl.structs.misc.mstudioanimblock_t
datastart(int)<parsed from long>

offset into the external .ani file

dataend(int)<parsed from long>
struct valvemdl.structs.misc.mstudiomodelgroup_t
szlabelindex(int)<parsed from long>

offset to a string; the textual name

sznameindex(int)<parsed from long>

offset to a string; the path of the mdl to include

struct valvemdl.structs.misc.mstudiomouth_t
bone(int)<parsed from long>
forward(Vector)
flexdesc(int)<parsed from long>
struct valvemdl.structs.misc.mstudioposeparamdesc_t
sznameindex(int)<parsed from long>

offset to a string

flags(int)<parsed from long>
start(float)<parsed from float>

starting value

end(float)<parsed from float>

ending value

loop(float)<parsed from float>

the value the parameter wraps at; 0 for no looping, 360 for a rotation

struct valvemdl.structs.misc.mstudioattachment_t
sznameindex(int)<parsed from long>

offset to a string

flags(int)<parsed from unsigned long>

ATTACHMENT_FLAG_WORLD_ALIGN is 0x10000

localbone(int)<parsed from long>

the bone this attachment hangs off

local(float[4][3])<parsed from float>

the attachment point, in the bone space

unused(int[8])<parsed from long>
struct valvemdl.structs.misc.mstudiobbox_t
bone(int)<parsed from long>

the bone this box is attached to, and moves with

group(int)<parsed from long>

intersection group; what body part this counts as being hit

bbmin(Vector)

bounding box, in the bone space

bbmax(Vector)
szhitboxnameindex(int)<parsed from long>

offset to a string; the SDK reads 0 as unnamed, but studiomdl writes a real offset to an empty string instead

unused(int[8])<parsed from long>
struct valvemdl.structs.misc.mstudiohitboxset_t
sznameindex(int)<parsed from long>

offset to a string

numhitboxes(int)<parsed from long>
hitboxindex(int)<parsed from long>

offset to an array of mstudiobbox_t