From e2d238e1b4071ccbf3858eacb7b3ce8f98fa48e1 Mon Sep 17 00:00:00 2001 From: salmonik Date: Mon, 26 Oct 2026 16:09:38 +0100 Subject: Bones: robust head lookup (hitbox name -> head bone) + hitbox dump --- src/bones.cpp | 134 ++++++++++++++++++++++++++++++++++++++++++++++------------ src/esp.cpp | 8 +++- src/hl2h.h | 1 + 3 files changed, 115 insertions(+), 28 deletions(-) (limited to 'src') diff --git a/src/bones.cpp b/src/bones.cpp index 2ff282b..bba61b9 100644 --- a/src/bones.cpp +++ b/src/bones.cpp @@ -136,6 +136,76 @@ static const char *bone_name_raw(const StudioHdr *h, int i) { return n[0] ? n : nullptr; } +static const HitboxSet *hitbox_set0(const StudioHdr *h) { + if (!h || h->numhitboxsets <= 0 || h->hitboxsetindex <= 0) + return nullptr; + const HitboxSet *s = + (const HitboxSet *)((const uint8_t *)h + h->hitboxsetindex); + if (s->numhitboxes <= 0 || s->numhitboxes > 512 || s->hitboxindex <= 0) + return nullptr; + return s; +} + +static const Hitbox *hitboxes(const HitboxSet *s) { + return (const Hitbox *)((const uint8_t *)s + s->hitboxindex); +} + +static const char *hitbox_name(const Hitbox &b) { + if (b.szhitboxnameindex <= 0 || b.szhitboxnameindex > 0x10000) + return ""; + return (const char *)&b + b.szhitboxnameindex; +} + +static bool contains_ci(const char *hay, const char *needle) { + if (!hay || !needle || !*needle) + return false; + size_t n = strlen(needle); + for (const char *p = hay; *p; p++) + if (strncasecmp(p, needle, n) == 0) + 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; + Vector local{(b.bbmin.x + b.bbmax.x) * 0.5f, + (b.bbmin.y + b.bbmax.y) * 0.5f, + (b.bbmin.z + b.bbmax.z) * 0.5f}; + const Matrix3x4 &m = e->mats[b.bone]; + out.x = + local.x * m.m[0][0] + local.y * m.m[0][1] + local.z * m.m[0][2] + m.m[0][3]; + out.y = + local.x * m.m[1][0] + local.y * m.m[1][1] + local.z * m.m[1][2] + m.m[1][3]; + out.z = + local.x * m.m[2][0] + local.y * m.m[2][1] + local.z * m.m[2][2] + m.m[2][3]; + return true; +} + +static void dump_hitboxes(const BoneEntry *e) { + const StudioHdr *h = e->hdr; + logf("hitbox dump: idx=%d sets=%d setidx=%d", e->idx, h->numhitboxsets, + h->hitboxsetindex); + const HitboxSet *s = hitbox_set0(h); + if (!s) { + logf("hitbox dump: no usable hitbox set"); + return; + } + const Hitbox *hb = hitboxes(s); + logf("hitbox dump: set0 n=%d hitboxindex=%d", s->numhitboxes, + s->hitboxindex); + int n = s->numhitboxes < 24 ? s->numhitboxes : 24; + for (int i = 0; i < n; i++) { + const char *bn = (hb[i].bone >= 0 && hb[i].bone < e->count) + ? bone_name_raw(h, hb[i].bone) + : nullptr; + logf(" hb %d name='%s' bone=%d(%s) bb=(%.1f %.1f %.1f)-(%.1f %.1f %.1f)", + i, hitbox_name(hb[i]), hb[i].bone, bn ? bn : "?", hb[i].bbmin.x, + hb[i].bbmin.y, hb[i].bbmin.z, hb[i].bbmax.x, hb[i].bbmax.y, + hb[i].bbmax.z); + } +} + } // namespace bool bones_update(int idx, void *ent) { @@ -253,36 +323,46 @@ bool hitbox_center(int idx, const char *name, Vector &out) { BoneEntry *e = find_entry(idx); if (!e || !e->valid || !name || !e->hdr) return false; - const StudioHdr *h = e->hdr; - if (h->numhitboxsets <= 0 || h->hitboxsetindex <= 0) + const HitboxSet *s = hitbox_set0(e->hdr); + if (!s) return false; - const HitboxSet *set = - (const HitboxSet *)((const uint8_t *)h + h->hitboxsetindex); - if (set->numhitboxes <= 0 || set->numhitboxes > 512 || - set->hitboxindex <= 0) + const Hitbox *boxes = hitboxes(s); + for (int i = 0; i < s->numhitboxes; i++) { + if (strcasecmp(hitbox_name(boxes[i]), name) == 0) + return hitbox_world(e, boxes[i], out); + } + return false; +} + +// 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. +bool head_position(int idx, Vector &out) { + BoneEntry *e = find_entry(idx); + if (!e || !e->valid || !e->hdr) return false; - const Hitbox *boxes = - (const Hitbox *)((const uint8_t *)set + set->hitboxindex); - for (int i = 0; i < set->numhitboxes; i++) { - const Hitbox &b = boxes[i]; - if (b.szhitboxnameindex <= 0) - continue; - const char *hn = (const char *)&b + b.szhitboxnameindex; - if (strcasecmp(hn, name) != 0) - continue; - if (b.bone < 0 || b.bone >= e->count) - return false; - Vector local{(b.bbmin.x + b.bbmax.x) * 0.5f, - (b.bbmin.y + b.bbmax.y) * 0.5f, - (b.bbmin.z + b.bbmax.z) * 0.5f}; - const Matrix3x4 &m = e->mats[b.bone]; - out.x = local.x * m.m[0][0] + local.y * m.m[0][1] + local.z * m.m[0][2] + - m.m[0][3]; - out.y = local.x * m.m[1][0] + local.y * m.m[1][1] + local.z * m.m[1][2] + - m.m[1][3]; - out.z = local.x * m.m[2][0] + local.y * m.m[2][1] + local.z * m.m[2][2] + - m.m[2][3]; + static bool dumped = false; + if (!dumped) { + dumped = true; + dump_hitboxes(e); + } + if (hitbox_center(idx, "head", out)) return true; + const HitboxSet *s = hitbox_set0(e->hdr); + if (s) { + const Hitbox *boxes = hitboxes(s); + for (int i = 0; i < s->numhitboxes; i++) { + 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")) + 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")) + return bone_pos(idx, i, out); } return false; } diff --git a/src/esp.cpp b/src/esp.cpp index b384bc4..996a886 100644 --- a/src/esp.cpp +++ b/src/esp.cpp @@ -334,8 +334,14 @@ void esp_draw() { if (!ent || !bones_update(t.idx, ent)) continue; Vector head; - if (!hitbox_center(t.idx, "head", head)) + if (!head_position(t.idx, head)) { + static int no_head = 0; + if (no_head < 4) { + no_head++; + logf("head: idx=%d no head hitbox/bone", t.idx); + } continue; + } float hx, hy; if (!world_to_screen(head, hx, hy)) continue; diff --git a/src/hl2h.h b/src/hl2h.h index d57105d..68d5d55 100644 --- a/src/hl2h.h +++ b/src/hl2h.h @@ -175,6 +175,7 @@ int bone_find(int idx, const char *name); int bone_parent(int idx, int bone); bool bone_pos(int idx, int bone, Vector &out); bool hitbox_center(int idx, const char *hitbox_name, Vector &out); +bool head_position(int idx, Vector &out); void outer_draw(); // raw ISurface slot calls used by the PaintTraverseEx hook (hl2h.cpp) -- cgit v1.2.3