diff options
| author | salmonik <salmonik@tuta.io> | 2026-10-26 16:25:28 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-10-26 16:25:28 +0100 |
| commit | 1e3b1cabaea62ef0e30ce8a478fb8418aa835229 (patch) | |
| tree | e56ae1c76ebf27e55ff05cc7f99e783b62833912 /src/bones.cpp | |
| parent | ESP: restrict skeleton to humanoid core bones (no fingers/toes/headcrab) (diff) | |
| download | hl2h-1e3b1cabaea62ef0e30ce8a478fb8418aa835229.tar.gz hl2h-1e3b1cabaea62ef0e30ce8a478fb8418aa835229.zip | |
ESP: derive skeleton core from hitbox bone paths (fixes unnamed ValveBiped bones)
Diffstat (limited to 'src/bones.cpp')
| -rw-r--r-- | src/bones.cpp | 63 |
1 files changed, 42 insertions, 21 deletions
diff --git a/src/bones.cpp b/src/bones.cpp index a9c6887..35756c2 100644 --- a/src/bones.cpp +++ b/src/bones.cpp | |||
| @@ -91,8 +91,10 @@ struct BoneEntry { | |||
| 91 | void *ent; | 91 | void *ent; |
| 92 | double stamp; | 92 | double stamp; |
| 93 | bool valid; | 93 | bool valid; |
| 94 | bool humanoid; | ||
| 94 | const StudioHdr *hdr; | 95 | const StudioHdr *hdr; |
| 95 | int count; | 96 | int count; |
| 97 | bool core[MAX_BONES]; | ||
| 96 | Matrix3x4 mats[MAX_BONES]; | 98 | Matrix3x4 mats[MAX_BONES]; |
| 97 | }; | 99 | }; |
| 98 | 100 | ||
| @@ -182,23 +184,41 @@ static bool hitbox_world(const BoneEntry *e, const Hitbox &b, Vector &out) { | |||
| 182 | return true; | 184 | return true; |
| 183 | } | 185 | } |
| 184 | 186 | ||
| 185 | // HL2 humanoids (combine, metrocop, citizen, zombie, ...) use the ValveBiped | 187 | // HL2 humanoids use the ValveBiped skeleton, but many of its bone names are |
| 186 | // skeleton. Draw only the core chain: pelvis/spine/neck/head, clavicle to | 188 | // stripped (the zombie's spine/legs/left arm are unnamed). Instead of matching |
| 187 | // hand, thigh to foot. Fingers, toes and extra model bones (e.g. the headcrab | 189 | // names, mark the paths from every hitbox bone up to the root: that yields the |
| 188 | // on a zombie) are skipped. | 190 | // chest/spine, arms and legs, while fingers/toes have no hitboxes and the |
| 189 | static bool core_bone_name(const char *n) { | 191 | // headcrab's HC_* hitbox is skipped explicitly. |
| 190 | if (!n || strncasecmp(n, "ValveBiped.Bip01_", 17) != 0) | 192 | static void build_core_mask(BoneEntry *e) { |
| 191 | return false; | 193 | e->humanoid = false; |
| 192 | if (contains_ci(n, "Finger") || contains_ci(n, "Toe") || | 194 | for (int i = 0; i < e->count; i++) { |
| 193 | contains_ci(n, "Headcrab")) | 195 | const char *n = bone_name_raw(e->hdr, i); |
| 194 | return false; | 196 | if (n && strncasecmp(n, "ValveBiped.Bip01_", 17) == 0) { |
| 195 | static const char *tokens[] = {"Pelvis", "Spine", "Neck", "Head1", | 197 | e->humanoid = true; |
| 196 | "Clavicle", "UpperArm", "Forearm", "Hand", | 198 | break; |
| 197 | "Thigh", "Calf", "Foot", nullptr}; | 199 | } |
| 198 | for (int i = 0; tokens[i]; i++) | 200 | } |
| 199 | if (contains_ci(n, tokens[i])) | 201 | memset(e->core, 0, sizeof(e->core)); |
| 200 | return true; | 202 | if (!e->humanoid) |
| 201 | return false; | 203 | return; |
| 204 | const HitboxSet *s = hitbox_set0(e->hdr); | ||
| 205 | if (!s) | ||
| 206 | return; | ||
| 207 | const Hitbox *boxes = hitboxes(s); | ||
| 208 | for (int i = 0; i < s->numhitboxes; i++) { | ||
| 209 | int b = boxes[i].bone; | ||
| 210 | if (b < 0 || b >= e->count) | ||
| 211 | continue; | ||
| 212 | const char *bn = bone_name_raw(e->hdr, b); | ||
| 213 | if (bn && (contains_ci(bn, "Headcrab") || contains_ci(bn, "HC_") || | ||
| 214 | contains_ci(bn, "Finger") || contains_ci(bn, "Toe"))) | ||
| 215 | continue; | ||
| 216 | int cur = b; | ||
| 217 | for (int guard = 0; cur >= 0 && cur < e->count && guard < 64; guard++) { | ||
| 218 | e->core[cur] = true; | ||
| 219 | cur = bone_at(e->hdr, cur)->parent; | ||
| 220 | } | ||
| 221 | } | ||
| 202 | } | 222 | } |
| 203 | 223 | ||
| 204 | static void dump_hitboxes(const BoneEntry *e) { | 224 | static void dump_hitboxes(const BoneEntry *e) { |
| @@ -297,6 +317,7 @@ bool bones_update(int idx, void *ent) { | |||
| 297 | 317 | ||
| 298 | e->hdr = hdr; | 318 | e->hdr = hdr; |
| 299 | e->count = hdr->numbones; | 319 | e->count = hdr->numbones; |
| 320 | build_core_mask(e); | ||
| 300 | e->valid = true; | 321 | e->valid = true; |
| 301 | e->stamp = now; | 322 | e->stamp = now; |
| 302 | if (g_logged < 4) { | 323 | if (g_logged < 4) { |
| @@ -333,18 +354,18 @@ int bone_parent(int idx, int bone) { | |||
| 333 | 354 | ||
| 334 | bool bone_is_skeleton_core(int idx, int bone) { | 355 | bool bone_is_skeleton_core(int idx, int bone) { |
| 335 | BoneEntry *e = find_entry(idx); | 356 | BoneEntry *e = find_entry(idx); |
| 336 | if (!e || !e->valid || bone < 0 || bone >= e->count) | 357 | if (!e || !e->valid || !e->humanoid || bone < 0 || bone >= e->count) |
| 337 | return false; | 358 | return false; |
| 338 | return core_bone_name(bone_name_raw(e->hdr, bone)); | 359 | return e->core[bone]; |
| 339 | } | 360 | } |
| 340 | 361 | ||
| 341 | int bone_nearest_core_parent(int idx, int bone) { | 362 | int bone_nearest_core_parent(int idx, int bone) { |
| 342 | BoneEntry *e = find_entry(idx); | 363 | BoneEntry *e = find_entry(idx); |
| 343 | if (!e || !e->valid || bone < 0 || bone >= e->count) | 364 | if (!e || !e->valid || !e->humanoid || bone < 0 || bone >= e->count) |
| 344 | return -1; | 365 | return -1; |
| 345 | int p = bone_at(e->hdr, bone)->parent; | 366 | int p = bone_at(e->hdr, bone)->parent; |
| 346 | for (int guard = 0; p >= 0 && p < e->count && guard < 64; guard++) { | 367 | for (int guard = 0; p >= 0 && p < e->count && guard < 64; guard++) { |
| 347 | if (core_bone_name(bone_name_raw(e->hdr, p))) | 368 | if (e->core[p]) |
| 348 | return p; | 369 | return p; |
| 349 | p = bone_at(e->hdr, p)->parent; | 370 | p = bone_at(e->hdr, p)->parent; |
| 350 | } | 371 | } |
