diff options
| author | salmonik <salmonik@tuta.io> | 2026-11-03 22:53:05 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-11-03 22:53:05 +0100 |
| commit | 094ce7621e920276dc0e658cd43ca73db848f2f3 (patch) | |
| tree | 5c191fe2aa69fb7d59229c19091f12dfe208ce56 /src/server.cpp | |
| parent | Fix undefined fullbright symbols; link with --no-undefined (diff) | |
| download | hl2h-094ce7621e920276dc0e658cd43ca73db848f2f3.tar.gz hl2h-094ce7621e920276dc0e658cd43ca73db848f2f3.zip | |
AI: use server relationships to classify NPC friendliness
- Read the server NPC's m_Relationship CUtlVector (entity row for the local
player first, then the CLASS_PLAYER row): D_LI friendly, D_NU neutral,
D_HT/D_FR hostile.
- Use it for ESP colours and aimbot/silent-aim/melee target filtering, with
the existing model heuristic as a fallback.
- Treat bugbait-controlled antlions as friendly when their server
m_hFollowTarget is set.
- Resolve m_Relationship/m_hFollowTarget datamap offsets and log them.
Diffstat (limited to 'src/server.cpp')
| -rw-r--r-- | src/server.cpp | 64 |
1 files changed, 60 insertions, 4 deletions
diff --git a/src/server.cpp b/src/server.cpp index 5599227..4702c44 100644 --- a/src/server.cpp +++ b/src/server.cpp | |||
| @@ -25,7 +25,12 @@ enum { EDICT_UNKNOWN_OFF = 0x0C }; | |||
| 25 | 25 | ||
| 26 | // typedescription_t (32-bit): fieldType@0, fieldName@4, fieldOffset[0]@8, | 26 | // typedescription_t (32-bit): fieldType@0, fieldName@4, fieldOffset[0]@8, |
| 27 | // fieldOffset[1]@0xC, fieldSize@0x10, flags@0x12, externalName@0x14 | 27 | // fieldOffset[1]@0xC, fieldSize@0x10, flags@0x12, externalName@0x14 |
| 28 | enum { FIELD_INTEGER = 5, FIELD_CHARACTER = 8 }; | 28 | enum { |
| 29 | FIELD_INTEGER = 5, | ||
| 30 | FIELD_CHARACTER = 8, | ||
| 31 | FIELD_EMBEDDED = 10, | ||
| 32 | FIELD_EHANDLE = 13 | ||
| 33 | }; | ||
| 29 | 34 | ||
| 30 | int g_srv_health = -1; | 35 | int g_srv_health = -1; |
| 31 | int g_srv_maxhealth = -1; | 36 | int g_srv_maxhealth = -1; |
| @@ -34,6 +39,8 @@ int g_srv_modelindex = -1; | |||
| 34 | int g_srv_absvelocity = -1; | 39 | int g_srv_absvelocity = -1; |
| 35 | int g_srv_velocity = -1; | 40 | int g_srv_velocity = -1; |
| 36 | int g_srv_activeweapon = -1; | 41 | int g_srv_activeweapon = -1; |
| 42 | static int g_srv_relationship = -1; | ||
| 43 | static int g_srv_followtarget = -1; | ||
| 37 | static bool g_srv_ready = false; | 44 | static bool g_srv_ready = false; |
| 38 | static bool g_srv_checked = false; | 45 | static bool g_srv_checked = false; |
| 39 | 46 | ||
| @@ -110,14 +117,17 @@ bool resolve_server_netvars() { | |||
| 110 | g_srv_modelindex = datamap_offset("m_nModelIndex", 7); // FIELD_SHORT | 117 | g_srv_modelindex = datamap_offset("m_nModelIndex", 7); // FIELD_SHORT |
| 111 | g_srv_absvelocity = datamap_offset("m_vecAbsVelocity", 3); // FIELD_VECTOR | 118 | g_srv_absvelocity = datamap_offset("m_vecAbsVelocity", 3); // FIELD_VECTOR |
| 112 | g_srv_velocity = datamap_offset("m_vecVelocity", 3); // FIELD_VECTOR | 119 | g_srv_velocity = datamap_offset("m_vecVelocity", 3); // FIELD_VECTOR |
| 113 | g_srv_activeweapon = datamap_offset("m_hActiveWeapon", 13); // FIELD_EHANDLE | 120 | g_srv_activeweapon = datamap_offset("m_hActiveWeapon", FIELD_EHANDLE); |
| 121 | g_srv_relationship = datamap_offset("m_Relationship", FIELD_EMBEDDED); | ||
| 122 | g_srv_followtarget = datamap_offset("m_hFollowTarget", FIELD_EHANDLE); | ||
| 114 | g_srv_ready = g_srv_health > 0; | 123 | g_srv_ready = g_srv_health > 0; |
| 115 | if (g_srv_ready) { | 124 | if (g_srv_ready) { |
| 116 | logf("server datamap (%s): health=%d maxhealth=%d lifeste=%d " | 125 | logf("server datamap (%s): health=%d maxhealth=%d lifeste=%d " |
| 117 | "modelindex=%d absvel=%d vel=%d activeweapon=%d", | 126 | "modelindex=%d absvel=%d vel=%d activeweapon=%d relationship=%d " |
| 127 | "followtarget=%d", | ||
| 118 | g_server.path.c_str(), g_srv_health, g_srv_maxhealth, g_srv_lifestate, | 128 | g_server.path.c_str(), g_srv_health, g_srv_maxhealth, g_srv_lifestate, |
| 119 | g_srv_modelindex, g_srv_absvelocity, g_srv_velocity, | 129 | g_srv_modelindex, g_srv_absvelocity, g_srv_velocity, |
| 120 | g_srv_activeweapon); | 130 | g_srv_activeweapon, g_srv_relationship, g_srv_followtarget); |
| 121 | fail_logged = false; | 131 | fail_logged = false; |
| 122 | } else if (!fail_logged) { | 132 | } else if (!fail_logged) { |
| 123 | fail_logged = true; | 133 | fail_logged = true; |
| @@ -189,6 +199,52 @@ int server_model_index(void *base) { | |||
| 189 | // networked m_vecVelocity directly is deliberately avoided because the server | 199 | // networked m_vecVelocity directly is deliberately avoided because the server |
| 190 | // datamap scan can find a same-named field on another class first. The movement | 200 | // datamap scan can find a same-named field on another class first. The movement |
| 191 | // code syncs m_vecVelocity through SetAbsVelocity after the command. | 201 | // code syncs m_vecVelocity through SetAbsVelocity after the command. |
| 202 | // Server-side AI relationship (D_ER=0, D_HT=1, D_FR=2, D_LI=3, D_NU=4). | ||
| 203 | // m_Relationship is a CUtlVector<Relationship_t>; each entry is | ||
| 204 | // EHANDLE entity, Class_T classType, Disposition_t disposition, int priority. | ||
| 205 | int server_npc_relation(int idx, int local_idx) { | ||
| 206 | if (!g_srv_ready || g_srv_relationship < 0 || local_idx <= 0) | ||
| 207 | return -1; | ||
| 208 | void *base = nullptr; | ||
| 209 | server_entity_health(idx, nullptr, nullptr, &base); | ||
| 210 | if (!base) | ||
| 211 | return -1; | ||
| 212 | uintptr_t rel = (uintptr_t)base + g_srv_relationship; | ||
| 213 | int count = *(int *)(rel + 12); | ||
| 214 | if (count <= 0 || count > 64) | ||
| 215 | return -1; | ||
| 216 | uintptr_t elems = *(uintptr_t *)(rel + 16); | ||
| 217 | if (!elems) | ||
| 218 | elems = *(uintptr_t *)(rel + 0); | ||
| 219 | if (!elems || (elems & 3)) | ||
| 220 | return -1; | ||
| 221 | int class_relation = -1; | ||
| 222 | for (int i = 0; i < count; i++) { | ||
| 223 | uintptr_t e = elems + (uintptr_t)i * 16; | ||
| 224 | uint32_t h = *(uint32_t *)(e + 0); | ||
| 225 | int class_type = *(int *)(e + 4); | ||
| 226 | int disposition = *(int *)(e + 8); | ||
| 227 | if (h != 0 && h != 0xFFFFFFFFu) { | ||
| 228 | if ((int)(h & 0x7FF) == local_idx) | ||
| 229 | return disposition; | ||
| 230 | } else if (class_type == 1) { // CLASS_PLAYER | ||
| 231 | class_relation = disposition; | ||
| 232 | } | ||
| 233 | } | ||
| 234 | return class_relation; | ||
| 235 | } | ||
| 236 | |||
| 237 | bool server_npc_has_follow_target(int idx) { | ||
| 238 | if (!g_srv_ready || g_srv_followtarget < 0) | ||
| 239 | return false; | ||
| 240 | void *base = nullptr; | ||
| 241 | server_entity_health(idx, nullptr, nullptr, &base); | ||
| 242 | if (!base) | ||
| 243 | return false; | ||
| 244 | uint32_t h = *(uint32_t *)((uintptr_t)base + g_srv_followtarget); | ||
| 245 | return h != 0 && h != 0xFFFFFFFFu; | ||
| 246 | } | ||
| 247 | |||
| 192 | bool server_set_velocity_z(int idx, float vz) { | 248 | bool server_set_velocity_z(int idx, float vz) { |
| 193 | if (!g_srv_ready || !g_iface.engineserver || g_srv_absvelocity <= 0) | 249 | if (!g_srv_ready || !g_iface.engineserver || g_srv_absvelocity <= 0) |
| 194 | return false; | 250 | return false; |
