diff options
| author | salmonik <salmonik@tuta.io> | 2026-10-26 18:26:43 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-10-26 18:26:43 +0100 |
| commit | d45bf3b974171b34655421c8996f1ff37d34178a (patch) | |
| tree | 48bc35e3a6574b86c5815a60b819dfd10ae524e7 | |
| parent | Binds: allow multiple features to share the same key (diff) | |
| download | hl2h-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
| -rw-r--r-- | src/bones.cpp | 27 |
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. | ||
| 173 | static 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 | |||
| 171 | static bool hitbox_world(const BoneEntry *e, const Hitbox &b, Vector &out) { | 183 | static 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. |
| 428 | bool head_position(int idx, Vector &out) { | 440 | bool 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 | ||
