From d45bf3b974171b34655421c8996f1ff37d34178a Mon Sep 17 00:00:00 2001 From: salmonik Date: Mon, 26 Oct 2026 18:26:43 +0100 Subject: Bones: head fallback for headcrab black/fast - head lookup now accepts head-like bone names (teeth, mouth, jaw, face, eye, front, snout, beak) after the HITGROUP_HEAD check (headcrabblack: HCblack.teeth; headcrab/fast: HCfast.wiggle_front) - final fallback is the model's first hitbox center for non-humanoids with no head information at all --- src/bones.cpp | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) (limited to 'src/bones.cpp') diff --git a/src/bones.cpp b/src/bones.cpp index 12c7406..1940449 100644 --- a/src/bones.cpp +++ b/src/bones.cpp @@ -168,6 +168,18 @@ static bool contains_ci(const char *hay, const char *needle) { return false; } +// Models without a humanoid head: headcrabs use teeth/front bones, others may +// expose only eye/jaw/mouth names. The fallback is the first hitbox center. +static bool head_like_name(const char *n) { + static const char *patterns[] = {"head", "teeth", "mouth", "jaw", + "face", "eye", "front", "snout", + "beak", nullptr}; + for (int i = 0; patterns[i]; i++) + if (contains_ci(n, patterns[i])) + return true; + return false; +} + static bool hitbox_world(const BoneEntry *e, const Hitbox &b, Vector &out) { if (b.bone < 0 || b.bone >= e->count) return false; @@ -423,8 +435,8 @@ bool hitbox_center(int idx, const char *name, Vector &out) { } // Head position with fallbacks, because several HL2 models have no hitbox -// names: exact "head" hitbox -> hitbox whose bone name contains "head" -> -// head bone origin. Dumps the hitbox set once for diagnosis. +// names: exact "head" hitbox -> HITGROUP_HEAD hitbox -> head-like bone name +// (teeth/front/jaw/eye/...) -> first hitbox center. Dumps the hitbox set once. bool head_position(int idx, Vector &out) { BoneEntry *e = find_entry(idx); if (!e || !e->valid || !e->hdr) @@ -449,15 +461,22 @@ bool head_position(int idx, Vector &out) { if (boxes[i].bone < 0 || boxes[i].bone >= e->count) continue; const char *bn = bone_name_raw(e->hdr, boxes[i].bone); - if (bn && contains_ci(bn, "head")) + if (bn && head_like_name(bn)) return hitbox_world(e, boxes[i], out); } } for (int i = 0; i < e->count; i++) { const char *bn = bone_name_raw(e->hdr, i); - if (bn && contains_ci(bn, "head")) + if (bn && head_like_name(bn)) return bone_pos(idx, i, out); } + // Last resort for non-humanoid models with unnamed heads/hitboxes: the + // first hitbox is the body (still a valid aim point). + if (s && s->numhitboxes > 0) { + const Hitbox *boxes = hitboxes(s); + if (hitbox_world(e, boxes[0], out)) + return true; + } return false; } -- cgit v1.2.3