From 094ce7621e920276dc0e658cd43ca73db848f2f3 Mon Sep 17 00:00:00 2001 From: salmonik Date: Tue, 3 Nov 2026 22:53:05 +0100 Subject: 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. --- src/esp.cpp | 45 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 39 insertions(+), 6 deletions(-) (limited to 'src/esp.cpp') diff --git a/src/esp.cpp b/src/esp.cpp index 7c3f1bc..0966ff5 100644 --- a/src/esp.cpp +++ b/src/esp.cpp @@ -148,6 +148,34 @@ static bool model_is_neutral(const char *m) { return false; } +static bool npc_is_friendly(int idx, const char *model, int local_idx, + bool &neutral) { + int rel = server_npc_relation(idx, local_idx); + if (rel == 3) { // D_LI + neutral = false; + return true; + } + if (rel == 4) { // D_NU + neutral = true; + return false; + } + if (rel == 1 || rel == 2) { // D_HT / D_FR + neutral = false; + return false; + } + // Bugbait-controlled antlions follow a target even when their default + // relationship was never rewritten. + if (model && strstr(model, "antlion") && + server_npc_has_follow_target(idx)) { + neutral = false; + return true; + } + bool friendly = model_is_friendly(model); + neutral = model_is_neutral(model); + return friendly; +} + + static void short_name(const char *model, char *out, size_t outn) { if (!model) { snprintf(out, outn, "NPC"); return; } const char *base = strrchr(model, '/'); @@ -259,8 +287,9 @@ static void esp_collect(int local_idx) { t.health = hp > 0 ? (float)hp : 0.0f; t.maxhealth = maxhp > 0 ? (float)maxhp : 100.0f; t.show_health = hp > 0; - t.friendly = model_is_friendly(model); - t.neutral = model_is_neutral(model); + bool neutral = false; + t.friendly = npc_is_friendly(idx, model, local_idx, neutral); + t.neutral = neutral; short_name(model, t.name, sizeof(t.name)); if (npc && !model) snprintf(t.name, sizeof(t.name), "NPC"); } @@ -527,7 +556,8 @@ static bool select_aim_target(const QAngle &view, unsigned bone_mask, if (life != LIFE_ALIVE) return false; const char *model = entity_model_name(ent); - if (model_is_friendly(model) || model_is_neutral(model)) + bool neutral = false; + if (npc_is_friendly(idx, model, local, neutral) || neutral) return false; if (!bones_update(idx, ent)) return false; @@ -605,7 +635,8 @@ static bool select_aim_target(const QAngle &view, unsigned bone_mask, if (life != LIFE_ALIVE) continue; const char *model = entity_model_name(ent); - if (model_is_friendly(model) || model_is_neutral(model)) + bool neutral = false; + if (npc_is_friendly(idx, model, local, neutral) || neutral) continue; float maxz = g_net.maxs >= 0 ? *(float *)(e + g_net.maxs + 8) : 72.0f; @@ -990,7 +1021,8 @@ bool melee_aimbot_apply(CUserCmd *cmd) { if (life != LIFE_ALIVE) continue; const char *model = entity_model_name(ent); - if (model_is_friendly(model) || model_is_neutral(model)) + bool neutral = false; + if (npc_is_friendly(idx, model, local, neutral) || neutral) continue; Vector origin{*(float *)(e + g_net.origin), @@ -1320,7 +1352,8 @@ bool triggerbot_apply(CUserCmd *cmd) { if (life != LIFE_ALIVE) return false; const char *model = entity_model_name(hit); - if (model_is_friendly(model) || model_is_neutral(model)) + bool neutral = false; + if (npc_is_friendly(idx, model, local, neutral) || neutral) return false; cmd->buttons |= IN_ATTACK; static int logs = 0; -- cgit v1.2.3