From 1fa3b61163b233f6c8c6b86dfa196fb798a6f094 Mon Sep 17 00:00:00 2001 From: salmonik Date: Wed, 4 Nov 2026 12:39:45 +0100 Subject: ESP: classify NPC damage immunity/armour/vulnerability - Resolve server m_takedamage (DAMAGE_NO/DAMAGE_EVENTS_ONLY = immune). - Classify gunship/strider/dropship as ARMORED (OnTakeDamage only accepts DMG_BLAST; only the RPG passes), and headcrab/antlion/manhack as VULN (gravity gun / physics impact). - ESP draws ARMORED/VULN/IMMUNE tags above the name (new "Damage tags" toggle, [esp] dmg). - Aimbot, silent aim, triggerbot and melee skip immune targets and armoured targets unless the active weapon is the RPG. --- src/esp.cpp | 69 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) (limited to 'src/esp.cpp') diff --git a/src/esp.cpp b/src/esp.cpp index 4a6b513..aa9f645 100644 --- a/src/esp.cpp +++ b/src/esp.cpp @@ -23,6 +23,7 @@ struct EspTarget { bool show_health; bool friendly; bool neutral; + int damage; // NpcDamageClass char name[48]; }; static EspTarget g_targets[128]; @@ -220,6 +221,48 @@ static bool npc_is_friendly(int idx, const char *model, int local_idx, } +enum NpcDamageClass { + NPC_DMG_NORMAL = 0, + NPC_DMG_ARMORED, // only blast damage (RPG) hurts it + NPC_DMG_VULNERABLE, // gravity gun / physics damage works well + NPC_DMG_IMMUNE // m_takedamage says no damage at all +}; + +static bool model_has(const char *model, const char *needle) { + return model && strcasestr(model, needle) != nullptr; +} + +// Server m_takedamage first (authoritative immunity), then the HL2 damage +// rules for units whose OnTakeDamage filters specific damage types. +static int npc_damage_class(int idx, const char *model) { + int td = server_npc_takedamage(idx); + if (td == 0 || td == 1) // DAMAGE_NO / DAMAGE_EVENTS_ONLY + return NPC_DMG_IMMUNE; + if (model_has(model, "gunship") || model_has(model, "combine_strider") || + model_has(model, "dropship")) + return NPC_DMG_ARMORED; + if (model_has(model, "headcrab") || model_has(model, "antlion") || + model_has(model, "manhack")) + return NPC_DMG_VULNERABLE; + return NPC_DMG_NORMAL; +} + +static bool active_weapon_contains(void *local_ent, const char *needle); + +// Armoured units need blast damage; the RPG is the only stock weapon that +// passes their OnTakeDamage filters. Immune units can never be damaged. +static bool npc_can_be_damaged(int idx, const char *model) { + int dc = npc_damage_class(idx, model); + if (dc == NPC_DMG_IMMUNE) + return false; + if (dc != NPC_DMG_ARMORED) + return true; + int local = engine_get_local_player(); + void *le = local > 0 ? entitylist_get(local) : nullptr; + return active_weapon_contains(le, "rocket_launcher") || + active_weapon_contains(le, "rpg"); +} + static void short_name(const char *model, char *out, size_t outn) { if (!model) { snprintf(out, outn, "NPC"); return; } const char *base = strrchr(model, '/'); @@ -334,6 +377,7 @@ static void esp_collect(int local_idx) { bool neutral = false; t.friendly = npc_is_friendly(idx, model, local_idx, neutral); t.neutral = neutral; + t.damage = npc_damage_class(idx, model); short_name(model, t.name, sizeof(t.name)); if (npc && !model) snprintf(t.name, sizeof(t.name), "NPC"); } @@ -603,6 +647,8 @@ static bool select_aim_target(const QAngle &view, unsigned bone_mask, bool neutral = false; if (npc_is_friendly(idx, model, local, neutral) || neutral) return false; + if (!npc_can_be_damaged(idx, model)) + return false; if (!bones_update(idx, ent)) return false; Vector pts[32]; @@ -682,6 +728,8 @@ static bool select_aim_target(const QAngle &view, unsigned bone_mask, bool neutral = false; if (npc_is_friendly(idx, model, local, neutral) || neutral) continue; + if (!npc_can_be_damaged(idx, model)) + continue; float maxz = g_net.maxs >= 0 ? *(float *)(e + g_net.maxs + 8) : 72.0f; if (!(maxz > 4.0f && maxz < 512.0f)) @@ -1063,6 +1111,8 @@ bool melee_aimbot_apply(CUserCmd *cmd) { bool neutral = false; if (npc_is_friendly(idx, model, local, neutral) || neutral) continue; + if (!npc_can_be_damaged(idx, model)) + continue; Vector origin{*(float *)(e + g_net.origin), *(float *)(e + g_net.origin + 4), @@ -1394,6 +1444,8 @@ bool triggerbot_apply(CUserCmd *cmd) { bool neutral = false; if (npc_is_friendly(idx, model, local, neutral) || neutral) return false; + if (!npc_can_be_damaged(idx, model)) + return false; cmd->buttons |= IN_ATTACK; static int logs = 0; if (logs < 5) { @@ -1517,6 +1569,23 @@ void esp_draw() { } if (esp_option(ESP_OPT_SHOW_NAME)) draw_text_col(left, top - 14, t.name, 255, 255, 255); + if (esp_option(ESP_OPT_DAMAGE_TAGS)) { + const char *tag = nullptr; + int tr = 255, tg = 255, tb = 255; + if (t.damage == NPC_DMG_ARMORED) { + tag = "ARMORED"; + tr = 255; tg = 170; tb = 60; + } else if (t.damage == NPC_DMG_VULNERABLE) { + tag = "VULN"; + tr = 90; tg = 220; tb = 255; + } else if (t.damage == NPC_DMG_IMMUNE) { + tag = "IMMUNE"; + tr = 180; tg = 180; tb = 180; + } + if (tag) + draw_text_col(left, top - (esp_option(ESP_OPT_SHOW_NAME) ? 28 : 14), + tag, tr, tg, tb); + } } // Groundwork probe: HL2H_BONE_DEBUG=1 draws a white marker at each NPC's -- cgit v1.2.3