summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-10-26 18:26:43 +0100
committersalmonik <salmonik@tuta.io>2026-10-26 18:26:43 +0100
commitd45bf3b974171b34655421c8996f1ff37d34178a (patch)
tree48bc35e3a6574b86c5815a60b819dfd10ae524e7 /src
parentBinds: allow multiple features to share the same key (diff)
downloadhl2h-d45bf3b974171b34655421c8996f1ff37d34178a.tar.gz
hl2h-d45bf3b974171b34655421c8996f1ff37d34178a.zip
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
Diffstat (limited to '')
-rw-r--r--src/bones.cpp27
1 files changed, 23 insertions, 4 deletions
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) {
168 return false; 168 return false;
169} 169}
170 170
171// Models without a humanoid head: headcrabs use teeth/front bones, others may
172// expose only eye/jaw/mouth names. The fallback is the first hitbox center.
173static bool head_like_name(const char *n) {
174 static const char *patterns[] = {"head", "teeth", "mouth", "jaw",
175 "face", "eye", "front", "snout",
176 "beak", nullptr};
177 for (int i = 0; patterns[i]; i++)
178 if (contains_ci(n, patterns[i]))
179 return true;
180 return false;
181}
182
171static bool hitbox_world(const BoneEntry *e, const Hitbox &b, Vector &out) { 183static bool hitbox_world(const BoneEntry *e, const Hitbox &b, Vector &out) {
172 if (b.bone < 0 || b.bone >= e->count) 184 if (b.bone < 0 || b.bone >= e->count)
173 return false; 185 return false;
@@ -423,8 +435,8 @@ bool hitbox_center(int idx, const char *name, Vector &out) {
423} 435}
424 436
425// Head position with fallbacks, because several HL2 models have no hitbox 437// Head position with fallbacks, because several HL2 models have no hitbox
426// names: exact "head" hitbox -> hitbox whose bone name contains "head" -> 438// names: exact "head" hitbox -> HITGROUP_HEAD hitbox -> head-like bone name
427// head bone origin. Dumps the hitbox set once for diagnosis. 439// (teeth/front/jaw/eye/...) -> first hitbox center. Dumps the hitbox set once.
428bool head_position(int idx, Vector &out) { 440bool head_position(int idx, Vector &out) {
429 BoneEntry *e = find_entry(idx); 441 BoneEntry *e = find_entry(idx);
430 if (!e || !e->valid || !e->hdr) 442 if (!e || !e->valid || !e->hdr)
@@ -449,15 +461,22 @@ bool head_position(int idx, Vector &out) {
449 if (boxes[i].bone < 0 || boxes[i].bone >= e->count) 461 if (boxes[i].bone < 0 || boxes[i].bone >= e->count)
450 continue; 462 continue;
451 const char *bn = bone_name_raw(e->hdr, boxes[i].bone); 463 const char *bn = bone_name_raw(e->hdr, boxes[i].bone);
452 if (bn && contains_ci(bn, "head")) 464 if (bn && head_like_name(bn))
453 return hitbox_world(e, boxes[i], out); 465 return hitbox_world(e, boxes[i], out);
454 } 466 }
455 } 467 }
456 for (int i = 0; i < e->count; i++) { 468 for (int i = 0; i < e->count; i++) {
457 const char *bn = bone_name_raw(e->hdr, i); 469 const char *bn = bone_name_raw(e->hdr, i);
458 if (bn && contains_ci(bn, "head")) 470 if (bn && head_like_name(bn))
459 return bone_pos(idx, i, out); 471 return bone_pos(idx, i, out);
460 } 472 }
473 // Last resort for non-humanoid models with unnamed heads/hitboxes: the
474 // first hitbox is the body (still a valid aim point).
475 if (s && s->numhitboxes > 0) {
476 const Hitbox *boxes = hitboxes(s);
477 if (hitbox_world(e, boxes[0], out))
478 return true;
479 }
461 return false; 480 return false;
462} 481}
463 482