summaryrefslogtreecommitdiffstats
path: root/src/esp.cpp
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-11-04 12:39:45 +0100
committersalmonik <salmonik@tuta.io>2026-11-04 12:39:45 +0100
commit1fa3b61163b233f6c8c6b86dfa196fb798a6f094 (patch)
treee21fd421dcfc78ef6ec73883442a60cdb6fe6597 /src/esp.cpp
parentProps: remove studio diagnostics; document the prop tint path (diff)
downloadhl2h-1fa3b61163b233f6c8c6b86dfa196fb798a6f094.tar.gz
hl2h-1fa3b61163b233f6c8c6b86dfa196fb798a6f094.zip
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.
Diffstat (limited to 'src/esp.cpp')
-rw-r--r--src/esp.cpp69
1 files changed, 69 insertions, 0 deletions
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 {
23 bool show_health; 23 bool show_health;
24 bool friendly; 24 bool friendly;
25 bool neutral; 25 bool neutral;
26 int damage; // NpcDamageClass
26 char name[48]; 27 char name[48];
27}; 28};
28static EspTarget g_targets[128]; 29static EspTarget g_targets[128];
@@ -220,6 +221,48 @@ static bool npc_is_friendly(int idx, const char *model, int local_idx,
220} 221}
221 222
222 223
224enum NpcDamageClass {
225 NPC_DMG_NORMAL = 0,
226 NPC_DMG_ARMORED, // only blast damage (RPG) hurts it
227 NPC_DMG_VULNERABLE, // gravity gun / physics damage works well
228 NPC_DMG_IMMUNE // m_takedamage says no damage at all
229};
230
231static bool model_has(const char *model, const char *needle) {
232 return model && strcasestr(model, needle) != nullptr;
233}
234
235// Server m_takedamage first (authoritative immunity), then the HL2 damage
236// rules for units whose OnTakeDamage filters specific damage types.
237static int npc_damage_class(int idx, const char *model) {
238 int td = server_npc_takedamage(idx);
239 if (td == 0 || td == 1) // DAMAGE_NO / DAMAGE_EVENTS_ONLY
240 return NPC_DMG_IMMUNE;
241 if (model_has(model, "gunship") || model_has(model, "combine_strider") ||
242 model_has(model, "dropship"))
243 return NPC_DMG_ARMORED;
244 if (model_has(model, "headcrab") || model_has(model, "antlion") ||
245 model_has(model, "manhack"))
246 return NPC_DMG_VULNERABLE;
247 return NPC_DMG_NORMAL;
248}
249
250static bool active_weapon_contains(void *local_ent, const char *needle);
251
252// Armoured units need blast damage; the RPG is the only stock weapon that
253// passes their OnTakeDamage filters. Immune units can never be damaged.
254static bool npc_can_be_damaged(int idx, const char *model) {
255 int dc = npc_damage_class(idx, model);
256 if (dc == NPC_DMG_IMMUNE)
257 return false;
258 if (dc != NPC_DMG_ARMORED)
259 return true;
260 int local = engine_get_local_player();
261 void *le = local > 0 ? entitylist_get(local) : nullptr;
262 return active_weapon_contains(le, "rocket_launcher") ||
263 active_weapon_contains(le, "rpg");
264}
265
223static void short_name(const char *model, char *out, size_t outn) { 266static void short_name(const char *model, char *out, size_t outn) {
224 if (!model) { snprintf(out, outn, "NPC"); return; } 267 if (!model) { snprintf(out, outn, "NPC"); return; }
225 const char *base = strrchr(model, '/'); 268 const char *base = strrchr(model, '/');
@@ -334,6 +377,7 @@ static void esp_collect(int local_idx) {
334 bool neutral = false; 377 bool neutral = false;
335 t.friendly = npc_is_friendly(idx, model, local_idx, neutral); 378 t.friendly = npc_is_friendly(idx, model, local_idx, neutral);
336 t.neutral = neutral; 379 t.neutral = neutral;
380 t.damage = npc_damage_class(idx, model);
337 short_name(model, t.name, sizeof(t.name)); 381 short_name(model, t.name, sizeof(t.name));
338 if (npc && !model) snprintf(t.name, sizeof(t.name), "NPC"); 382 if (npc && !model) snprintf(t.name, sizeof(t.name), "NPC");
339 } 383 }
@@ -603,6 +647,8 @@ static bool select_aim_target(const QAngle &view, unsigned bone_mask,
603 bool neutral = false; 647 bool neutral = false;
604 if (npc_is_friendly(idx, model, local, neutral) || neutral) 648 if (npc_is_friendly(idx, model, local, neutral) || neutral)
605 return false; 649 return false;
650 if (!npc_can_be_damaged(idx, model))
651 return false;
606 if (!bones_update(idx, ent)) 652 if (!bones_update(idx, ent))
607 return false; 653 return false;
608 Vector pts[32]; 654 Vector pts[32];
@@ -682,6 +728,8 @@ static bool select_aim_target(const QAngle &view, unsigned bone_mask,
682 bool neutral = false; 728 bool neutral = false;
683 if (npc_is_friendly(idx, model, local, neutral) || neutral) 729 if (npc_is_friendly(idx, model, local, neutral) || neutral)
684 continue; 730 continue;
731 if (!npc_can_be_damaged(idx, model))
732 continue;
685 733
686 float maxz = g_net.maxs >= 0 ? *(float *)(e + g_net.maxs + 8) : 72.0f; 734 float maxz = g_net.maxs >= 0 ? *(float *)(e + g_net.maxs + 8) : 72.0f;
687 if (!(maxz > 4.0f && maxz < 512.0f)) 735 if (!(maxz > 4.0f && maxz < 512.0f))
@@ -1063,6 +1111,8 @@ bool melee_aimbot_apply(CUserCmd *cmd) {
1063 bool neutral = false; 1111 bool neutral = false;
1064 if (npc_is_friendly(idx, model, local, neutral) || neutral) 1112 if (npc_is_friendly(idx, model, local, neutral) || neutral)
1065 continue; 1113 continue;
1114 if (!npc_can_be_damaged(idx, model))
1115 continue;
1066 1116
1067 Vector origin{*(float *)(e + g_net.origin), 1117 Vector origin{*(float *)(e + g_net.origin),
1068 *(float *)(e + g_net.origin + 4), 1118 *(float *)(e + g_net.origin + 4),
@@ -1394,6 +1444,8 @@ bool triggerbot_apply(CUserCmd *cmd) {
1394 bool neutral = false; 1444 bool neutral = false;
1395 if (npc_is_friendly(idx, model, local, neutral) || neutral) 1445 if (npc_is_friendly(idx, model, local, neutral) || neutral)
1396 return false; 1446 return false;
1447 if (!npc_can_be_damaged(idx, model))
1448 return false;
1397 cmd->buttons |= IN_ATTACK; 1449 cmd->buttons |= IN_ATTACK;
1398 static int logs = 0; 1450 static int logs = 0;
1399 if (logs < 5) { 1451 if (logs < 5) {
@@ -1517,6 +1569,23 @@ void esp_draw() {
1517 } 1569 }
1518 if (esp_option(ESP_OPT_SHOW_NAME)) 1570 if (esp_option(ESP_OPT_SHOW_NAME))
1519 draw_text_col(left, top - 14, t.name, 255, 255, 255); 1571 draw_text_col(left, top - 14, t.name, 255, 255, 255);
1572 if (esp_option(ESP_OPT_DAMAGE_TAGS)) {
1573 const char *tag = nullptr;
1574 int tr = 255, tg = 255, tb = 255;
1575 if (t.damage == NPC_DMG_ARMORED) {
1576 tag = "ARMORED";
1577 tr = 255; tg = 170; tb = 60;
1578 } else if (t.damage == NPC_DMG_VULNERABLE) {
1579 tag = "VULN";
1580 tr = 90; tg = 220; tb = 255;
1581 } else if (t.damage == NPC_DMG_IMMUNE) {
1582 tag = "IMMUNE";
1583 tr = 180; tg = 180; tb = 180;
1584 }
1585 if (tag)
1586 draw_text_col(left, top - (esp_option(ESP_OPT_SHOW_NAME) ? 28 : 14),
1587 tag, tr, tg, tb);
1588 }
1520 } 1589 }
1521 1590
1522 // Groundwork probe: HL2H_BONE_DEBUG=1 draws a white marker at each NPC's 1591 // Groundwork probe: HL2H_BONE_DEBUG=1 draws a white marker at each NPC's