From 5a9fd14f9424d7f66b0454464be95b40b10ee5c4 Mon Sep 17 00:00:00 2001 From: salmonik Date: Fri, 6 Nov 2026 16:23:29 +0100 Subject: ESP: extract item ESP into its own translation unit Second slice of the esp.cpp split. Model-name lookup joins the shared helpers in esp_internal.h. --- src/esp.cpp | 815 +----------------------------------------------------------- 1 file changed, 1 insertion(+), 814 deletions(-) (limited to 'src/esp.cpp') diff --git a/src/esp.cpp b/src/esp.cpp index fef3e7c..3f21dce 100644 --- a/src/esp.cpp +++ b/src/esp.cpp @@ -252,7 +252,7 @@ static void entptr_store(void *ent, int idx) { // m_nModelIndex -> IVModelInfoClient(006)::GetModel/GetModelName. // Itanium ABI: two dtor slots, so GetModel=2, GetModelName=4. -static const char *model_name_from_index(int modelindex) { +const char *model_name_from_index(int modelindex) { if (!g_iface.modelinfo || modelindex <= 0) return nullptr; if (modelindex < MODEL_CACHE) { @@ -2508,816 +2508,3 @@ void esp_draw() { // Valuable item tags // --------------------------------------------------------------------------- -enum ItemKind { - IK_HEALTH, - IK_AMMO, - IK_ARMOR, - IK_WEAPON, - IK_CRATE, - IK_OTHER -}; - -struct ItemTarget { - int idx; - int kind; - int ammo_type; // m_iAmmo index for IK_AMMO, -1 otherwise - Vector origin; - char label[128]; -}; - -static ItemTarget g_item_targets[192]; -static int g_nitems = 0; -static bool g_items_logged = false; - -static bool item_icontains(const char *hay, const char *needle) { - if (!hay || !needle || !*needle) - return false; - size_t n = strlen(needle); - for (const char *p = hay; *p; p++) - if (strncasecmp(p, needle, n) == 0) - return true; - return false; -} - -static bool item_handle_valid(uint32_t h) { - return h != 0 && h != 0xFFFFFFFFu; -} - -// Weapons held by the player or an NPC are not world pickups. -bool item_weapon_is_owned(void *ent) { - if (!ent) - return false; - if (g_net.owner >= 0) { - uint32_t h = *(uint32_t *)((uintptr_t)ent + g_net.owner); - if (item_handle_valid(h)) - return true; - } - if (g_net.weapon_owner >= 0) { - uint32_t h = *(uint32_t *)((uintptr_t)ent + g_net.weapon_owner); - if (item_handle_valid(h)) - return true; - } - return false; -} - -// Entity index of the weapon's owner, or -1. Used to distinguish the local -// player's inventory from NPC weapons. -static int item_weapon_owner_idx(void *ent) { - if (!ent) - return -1; - uint32_t h = 0; - if (g_net.owner >= 0) - h = *(uint32_t *)((uintptr_t)ent + g_net.owner); - if (!item_handle_valid(h) && g_net.weapon_owner >= 0) - h = *(uint32_t *)((uintptr_t)ent + g_net.weapon_owner); - if (!item_handle_valid(h)) - return -1; - return (int)(h & 0x7FF); -} - -static int local_active_weapon_idx(int local_idx) { - if (local_idx <= 0 || g_net.activeweapon < 0) - return -1; - void *local = entitylist_get(local_idx); - if (!local) - return -1; - uint32_t h = *(uint32_t *)((uintptr_t)local + g_net.activeweapon); - if (!item_handle_valid(h)) - return -1; - return (int)(h & 0x7FF); -} - -static int classify_weapon(const char *model, const char *cls, char *label, - size_t outn) { - if (!item_icontains(model, "models/weapons/w_") && - !item_icontains(cls, "weapon")) - return 0; - if (item_icontains(model, "w_crowbar")) - snprintf(label, outn, "Crowbar"); - else if (item_icontains(model, "w_pistol")) - snprintf(label, outn, "Pistol"); - else if (item_icontains(model, "w_357")) - snprintf(label, outn, "Revolver"); - else if (item_icontains(model, "w_smg1") || - item_icontains(model, "w_smg2")) - snprintf(label, outn, "SMG"); - else if (item_icontains(model, "w_ar2") || - item_icontains(model, "w_irifle")) - snprintf(label, outn, "AR2"); - else if (item_icontains(model, "w_shotgun")) - snprintf(label, outn, "Shotgun"); - else if (item_icontains(model, "w_crossbow")) - snprintf(label, outn, "Crossbow"); - else if (item_icontains(model, "w_rocket_launcher")) - snprintf(label, outn, "RPG"); - else if (item_icontains(model, "w_frag") || - item_icontains(model, "w_grenade")) - snprintf(label, outn, "Grenade"); - else if (item_icontains(model, "w_physcannon") || - item_icontains(model, "w_physics")) - snprintf(label, outn, "Gravity gun"); - else if (item_icontains(model, "w_stunstick") || - item_icontains(model, "w_stunbaton")) - snprintf(label, outn, "Stunstick"); - else if (item_icontains(model, "w_bugbait")) - snprintf(label, outn, "Bugbait"); - else if (item_icontains(model, "w_sniper")) - snprintf(label, outn, "Sniper"); - else if (item_icontains(model, "w_slam")) - snprintf(label, outn, "SLAM"); - else - snprintf(label, outn, "%s", model); - return 1; -} - -// Weapon identity for the "owned weapon -> fallen weapon is ammo" rule. -enum { - WT_NONE = 0, - WT_PISTOL, - WT_357, - WT_SMG, - WT_AR2, - WT_SHOTGUN, - WT_CROSSBOW, - WT_RPG, - WT_GRENADE, - WT_SNIPER, - WT_COUNT -}; - -static int weapon_type_id(const char *model, const char *cls) { - if (item_icontains(model, "pistol") || item_icontains(cls, "pistol")) - return WT_PISTOL; - if (item_icontains(model, "357") || item_icontains(cls, "357")) - return WT_357; - if (item_icontains(model, "smg") || item_icontains(cls, "smg")) - return WT_SMG; - if (item_icontains(model, "ar2") || item_icontains(model, "irifle") || - item_icontains(cls, "ar2") || item_icontains(cls, "irifle")) - return WT_AR2; - if (item_icontains(model, "shotgun") || item_icontains(cls, "shotgun")) - return WT_SHOTGUN; - if (item_icontains(model, "crossbow") || item_icontains(cls, "crossbow")) - return WT_CROSSBOW; - if (item_icontains(model, "rocket") || item_icontains(cls, "rpg")) - return WT_RPG; - if (item_icontains(model, "grenade") || item_icontains(cls, "frag")) - return WT_GRENADE; - if (item_icontains(model, "sniper") || item_icontains(cls, "sniper")) - return WT_SNIPER; - return WT_NONE; -} - -static int weapon_ammo_type(int wt) { - switch (wt) { - case WT_PISTOL: return 2; - case WT_357: return 4; - case WT_SMG: return 3; - case WT_AR2: return 0; - case WT_SHOTGUN: return 6; - case WT_CROSSBOW: return 5; - case WT_RPG: return 7; - case WT_GRENADE: return 11; - case WT_SNIPER: return 9; - default: return -1; - } -} - -// Ammo type index used by CBasePlayer::m_iAmmo (order of AddAmmoType in -// hl2_gamerules.cpp). -static int item_ammo_type(const char *model) { - if (!model) - return -1; - if (item_icontains(model, "boxsrounds") || - item_icontains(model, "ammocrate_pistol") || - item_icontains(model, "w_pistol")) - return 2; - if (item_icontains(model, "boxmrounds") || - item_icontains(model, "ammocrate_smg1")) - return 3; - if (item_icontains(model, "boxlrounds") || - item_icontains(model, "combine_rifle") || - item_icontains(model, "ammocrate_ar2") || - item_icontains(model, "w_ar2") || item_icontains(model, "w_irifle")) - return 0; - if (item_icontains(model, "357ammo") || - item_icontains(model, "357ammobox") || - item_icontains(model, "w_357")) - return 4; - if (item_icontains(model, "crossbowrounds") || - item_icontains(model, "w_crossbow")) - return 5; - if (item_icontains(model, "boxbuckshot") || - item_icontains(model, "ammocrate_buckshot") || - item_icontains(model, "w_shotgun")) - return 6; - if (item_icontains(model, "w_missile_closed") || - item_icontains(model, "ammocrate_rockets") || - item_icontains(model, "w_rocket_launcher")) - return 7; - if (item_icontains(model, "ar2_grenade") || - item_icontains(model, "ammocrate_smg2") || - item_icontains(model, "smg1_grenade") || - item_icontains(model, "smg_grenade")) - return 8; - if (item_icontains(model, "ammocrate_grenade") || - item_icontains(model, "w_grenade") || - item_icontains(model, "w_frag") || - item_icontains(model, "frag_grenade") || - item_icontains(model, "items/grenade")) - return 11; - if (item_icontains(model, "w_smg1") || item_icontains(model, "w_smg2")) - return 3; - return -1; -} - -static int convar_int(const char *name) { - if (!g_iface.cvar || !name) - return -1; - using FindFn = void *(*)(void *, const char *); - void *v = vfunc(g_iface.cvar, 12)(g_iface.cvar, name); - if (!v) - return -1; - return (int)*(float *)((uintptr_t)v + 0x2c); // ConVar m_Value.f -} - -// Maximum reserve ammo for an m_iAmmo index. Prefers the sk_max_* cvar and -// falls back to the stock HL2 cap so the RPG's 3-rocket limit is honoured -// even when server cvars are not visible from the client. -static int ammo_max(int type) { - static int cache[16]; - static bool init = false; - if (type < 0 || type >= 16) - return -1; - if (!init) { - for (int i = 0; i < 16; i++) - cache[i] = -2; - init = true; - } - if (cache[type] != -2) - return cache[type]; - const char *name = nullptr; - int stock = -1; - switch (type) { - case 0: name = "sk_max_ar2"; stock = 60; break; - case 2: name = "sk_max_pistol"; stock = 150; break; - case 3: name = "sk_max_smg1"; stock = 225; break; - case 4: name = "sk_max_357"; stock = 12; break; - case 5: name = "sk_max_crossbow"; stock = 10; break; - case 6: name = "sk_max_buckshot"; stock = 30; break; - case 7: name = "sk_max_rpg_round"; stock = 3; break; - case 8: name = "sk_max_smg1_grenade"; stock = 3; break; - case 9: name = "sk_max_sniper_round"; stock = 30; break; - case 11: name = "sk_max_grenade"; stock = 5; break; - default: break; - } - int v = convar_int(name); - if (v <= 0) - v = stock; - cache[type] = v; - return v; -} - -static int local_ammo(void *local_ent, int type) { - if (type < 0 || type >= 32) - return -1; - // Prefer the authoritative server array; the client m_iAmmo netvar offset - // can be off by an element, which made "full" counts flicker as other ammo - // was spent. server_player_ammo already skips the CNetworkArray header. - int local = engine_get_local_player(); - if (local > 0 && g_iface.engineserver) { - resolve_server_netvars(); - int s = server_player_ammo(local, type); - if (s >= 0) - return s; - } - if (local_ent && g_net.ammo >= 0) - return *(int *)((uintptr_t)local_ent + g_net.ammo + type * 4); - return -1; -} - -static int classify_item(const char *model, const char *cls, char *label, - size_t outn) { - if (!model && !cls) - return -1; - - // Breakable prop chunks are debris, not pickups. - if (item_icontains(model, "chunk")) - return -1; - - if (item_icontains(model, "healthkit")) { - snprintf(label, outn, "Medkit"); - return IK_HEALTH; - } - if (item_icontains(model, "healthvial")) { - snprintf(label, outn, "Health vial"); - return IK_HEALTH; - } - if (item_icontains(model, "health_charger")) { - snprintf(label, outn, "Health charger"); - return IK_HEALTH; - } - if (item_icontains(model, "battery")) { - snprintf(label, outn, "Battery"); - return IK_ARMOR; - } - if (item_icontains(model, "hevsuit")) { - snprintf(label, outn, "HEV suit"); - return IK_ARMOR; - } - if (item_icontains(model, "w_longjump")) { - snprintf(label, outn, "Long jump"); - return IK_ARMOR; - } - if (item_icontains(model, "suit_charger")) { - snprintf(label, outn, "Suit charger"); - return IK_ARMOR; - } - - // Only the two item-crate models are supply crates. Other crates are just - // breakable map props and deliberately not tagged. - if (item_icontains(model, "item_item_crate") || - item_icontains(model, "item_beacon_crate")) { - snprintf(label, outn, "Supply crate"); - return IK_CRATE; - } - - if (item_icontains(model, "ammocrate_pistol")) { - snprintf(label, outn, "Pistol ammo crate"); - return IK_AMMO; - } - if (item_icontains(model, "ammocrate_smg1")) { - snprintf(label, outn, "SMG ammo crate"); - return IK_AMMO; - } - if (item_icontains(model, "ammocrate_ar2")) { - snprintf(label, outn, "AR2 ammo crate"); - return IK_AMMO; - } - if (item_icontains(model, "ammocrate_rockets")) { - snprintf(label, outn, "RPG ammo crate"); - return IK_AMMO; - } - if (item_icontains(model, "ammocrate_buckshot")) { - snprintf(label, outn, "Shotgun ammo crate"); - return IK_AMMO; - } - if (item_icontains(model, "ammocrate_grenade")) { - snprintf(label, outn, "Grenade crate"); - return IK_AMMO; - } - if (item_icontains(model, "ammocrate_smg2")) { - snprintf(label, outn, "SMG grenade crate"); - return IK_AMMO; - } - if (item_icontains(model, "ammocrate")) { - snprintf(label, outn, "Ammo crate"); - return IK_AMMO; - } - - if (item_icontains(model, "w_missile_closed")) { - snprintf(label, outn, "RPG rocket"); - return IK_AMMO; - } - if (item_icontains(model, "boxsrounds")) { - snprintf(label, outn, "Pistol ammo"); - return IK_AMMO; - } - if (item_icontains(model, "boxmrounds")) { - snprintf(label, outn, "SMG ammo"); - return IK_AMMO; - } - if (item_icontains(model, "boxlrounds") || - item_icontains(model, "combine_rifle_cartridge") || - item_icontains(model, "combine_rifle_ammo")) { - snprintf(label, outn, "AR2 ammo"); - return IK_AMMO; - } - if (item_icontains(model, "357ammo") || - item_icontains(model, "357ammobox")) { - snprintf(label, outn, ".357 ammo"); - return IK_AMMO; - } - if (item_icontains(model, "crossbowrounds")) { - snprintf(label, outn, "Crossbow bolts"); - return IK_AMMO; - } - if (item_icontains(model, "boxbuckshot")) { - snprintf(label, outn, "Shotgun shells"); - return IK_AMMO; - } - if (item_icontains(model, "boxsniperrounds")) { - snprintf(label, outn, "Sniper ammo"); - return IK_AMMO; - } - if (item_icontains(model, "boxflares") || - item_icontains(model, "models/items/flare")) { - snprintf(label, outn, "Flares"); - return IK_AMMO; - } - if (item_icontains(model, "ar2_grenade")) { - snprintf(label, outn, "AR2 grenade"); - return IK_AMMO; - } - if (item_icontains(model, "w_grenade") || - item_icontains(model, "w_frag") || - item_icontains(model, "frag_grenade") || - item_icontains(model, "items/grenade")) { - snprintf(label, outn, "Frag grenade"); - return IK_AMMO; - } - if (item_icontains(model, "models/items/")) { - snprintf(label, outn, "Ammo"); - return IK_AMMO; - } - - if (classify_weapon(model, cls, label, outn) == 1) - return IK_WEAPON; - - if (item_icontains(cls, "healthkit") || item_icontains(cls, "healthvial") || - item_icontains(cls, "wallhealth") || - item_icontains(cls, "healthcharger")) { - snprintf(label, outn, "Health"); - return IK_HEALTH; - } - if (item_icontains(cls, "battery") || item_icontains(cls, "itemsuit") || - item_icontains(cls, "recharge")) { - snprintf(label, outn, "Armor"); - return IK_ARMOR; - } - if (item_icontains(cls, "itemcrate")) { - snprintf(label, outn, "Supply crate"); - return IK_CRATE; - } - if (item_icontains(cls, "item_ammo") || item_icontains(cls, "itemammo")) { - snprintf(label, outn, "Ammo"); - return IK_AMMO; - } - if (item_icontains(cls, "weapon")) { - snprintf(label, outn, "Weapon"); - return IK_WEAPON; - } - if (item_icontains(cls, "item") || item_icontains(cls, "pickup")) { - snprintf(label, outn, "Item"); - return IK_OTHER; - } - return -1; -} - -static bool item_kind_enabled(int kind) { - switch (kind) { - case IK_HEALTH: return g_item_health; - case IK_AMMO: return g_item_ammo; - case IK_ARMOR: return g_item_armor; - case IK_WEAPON: return g_item_weapons; - case IK_CRATE: return g_item_crates; - default: return true; - } -} - -// Health chargers are brushes (func_healthcharger/CWallHealth) and suit -// chargers are item_suitcharger/CNewRecharge. Both expose m_iJuice. -static bool item_is_charger(const char *model, const char *cls) { - return item_icontains(model, "charger") || - item_icontains(model, "suit_charger") || - item_icontains(cls, "charger") || - item_icontains(cls, "wallhealth") || - item_icontains(cls, "recharge"); -} - -static bool item_is_suit_charger(const char *model, const char *cls) { - return item_icontains(model, "suit_charger") || - item_icontains(model, "suitcharger") || - item_icontains(cls, "recharge") || - item_icontains(cls, "suitcharger"); -} - -static void items_collect(int local_idx) { - static double last_scan = 0.0; - double now = esp_now(); - if (now - last_scan < 0.10) - return; - last_scan = now; - g_nitems = 0; - if (!g_iface.entitylist || g_net.origin < 0 || g_net.modelindex < 0) - return; - resolve_server_netvars(); - - using Fn = int (*)(void *); - int hi = vfunc(g_iface.entitylist, 6)(g_iface.entitylist); - if (hi < 0 || hi > 4096) - hi = 4096; - int active_idx = local_active_weapon_idx(local_idx); - - // Which weapon types the local player already carries. A fallen weapon of - // the same type is then just an ammo pickup. Owner handles are unreliable on - // this client (owner/weapon_owner netvars resolve to -1), so remember every - // type the player has held since the current local entity appeared. - static uint32_t owned_mask = 0; - static void *last_local = nullptr; - void *local_ent = entitylist_get(local_idx); - if (local_ent != last_local) { - last_local = local_ent; - owned_mask = 0; - } - int owned_idx = active_idx; - if (owned_idx <= 0 && g_iface.engineserver) { - resolve_server_netvars(); - owned_idx = server_active_weapon(local_idx); - } - if (owned_idx > 0) { - void *aw = entitylist_get(owned_idx); - void *anet = entity_networkable(owned_idx); - const char *acls = anet ? entity_class_name(anet, nullptr) : nullptr; - const char *amodel = aw ? entity_model_name(aw) : nullptr; - int at = weapon_type_id(amodel, acls); - if (at > 0) - owned_mask |= 1u << at; - } - for (int idx = 1; idx <= hi; idx++) { - if (idx == local_idx) - continue; - void *ent = entitylist_get(idx); - if (!ent || item_weapon_owner_idx(ent) != local_idx) - continue; - void *net = entity_networkable(idx); - const char *cls = net ? entity_class_name(net, nullptr) : nullptr; - const char *model = entity_model_name(ent); - int wt = weapon_type_id(model, cls); - if (wt > 0) - owned_mask |= 1u << wt; - } - // Server-side inventory, independent of the client recv-table arrays. - if (g_iface.engineserver) { - for (int slot = 0; slot < 48; slot++) { - uint32_t h = server_player_weapon(local_idx, slot); - if (!item_handle_valid(h)) - continue; - int wi = (int)(h & 0x7FF); - if (wi <= 0 || wi > 4096) - continue; - void *wep = entitylist_get(wi); - void *net = entity_networkable(wi); - const char *wc = net ? entity_class_name(net, nullptr) : nullptr; - const char *wm = wep ? entity_model_name(wep) : nullptr; - if (!wm) { - void *sb = nullptr; - server_entity_health(wi, nullptr, nullptr, &sb); - if (sb) - wm = model_name_from_index(server_model_index(sb)); - } - int wt = weapon_type_id(wm, wc); - if (wt > 0) - owned_mask |= 1u << wt; - } - } - bool owned_weapon[WT_COUNT] = {}; - for (int t = 1; t < WT_COUNT; t++) - owned_weapon[t] = (owned_mask & (1u << t)) != 0; - static bool cats_logged = false; - if (!cats_logged) { - cats_logged = true; - char maxbuf[16]; - if (g_item_max_dist == 0) - snprintf(maxbuf, sizeof(maxbuf), "All"); - else - snprintf(maxbuf, sizeof(maxbuf), "%d", g_item_max_dist); - logf("items: categories health=%d ammo=%d armor=%d weapons=%d " - "crates=%d distance=%d max=%s", - (int)g_item_health, (int)g_item_ammo, (int)g_item_armor, - (int)g_item_weapons, (int)g_item_crates, (int)g_item_distance, - maxbuf); - } - static int dbg_lines = 0; - static bool owner_warned = false; - if (!owner_warned && g_net.owner < 0 && g_net.weapon_owner < 0) { - owner_warned = true; - logf("items: weapon owner netvar unavailable; held weapons may tag"); - } - - for (int idx = 1; idx <= hi && g_nitems < 192; idx++) { - if (idx == local_idx) - continue; - void *ent = entitylist_get(idx); - if (!ent) - continue; - void *net = entity_networkable(idx); - if (!net || entity_is_dormant(net)) - continue; - const char *cls = entity_class_name(net, nullptr); - const char *scls = nullptr; - if (g_iface.engineserver && - (!cls || !*cls || strcmp(cls, "CBaseEntity") == 0)) - scls = server_entity_classname(idx); - const char *model = entity_model_name(ent); - // CPhysicsProp's client m_nModelIndex is often 0 (e.g. item_item_crate); - // fall back to the server entity's model index, which is set in Spawn(). - if ((!model || !*model) && g_iface.engineserver) { - void *sbase = nullptr; - server_entity_health(idx, nullptr, nullptr, &sbase); - if (sbase) - model = model_name_from_index(server_model_index(sbase)); - } - char label[128]; - int kind = classify_item(model, cls, label, sizeof(label)); - if (kind < 0 && scls) - kind = classify_item(model, scls, label, sizeof(label)); - if (kind < 0) - continue; - // Health/suit chargers are dispensers that stay useful after a refill, but - // an empty suit charger cannot give anything. The health charger's - // m_iJuice offset is still being verified (it read 0 on a full charger), - // so it is left visible for now; server_item_juice logs both candidate - // values for diagnosis. - bool is_charger = item_is_charger(model, cls) || - (scls && item_is_charger(model, scls)); - bool is_suit = item_is_suit_charger(model, cls) || - (scls && item_is_suit_charger(model, scls)); - if (is_charger) { - int juice = server_item_juice(idx); - if (is_suit && juice == 0) - continue; - } - - Vector origin{*(float *)((uintptr_t)ent + g_net.origin), - *(float *)((uintptr_t)ent + g_net.origin + 4), - *(float *)((uintptr_t)ent + g_net.origin + 8)}; - float dx = origin.x - g_cam_eye.x; - float dy = origin.y - g_cam_eye.y; - float dz = origin.z - g_cam_eye.z; - float dist = sqrtf(dx * dx + dy * dy + dz * dz); - - if (kind == IK_WEAPON) { - if (idx == active_idx || item_weapon_is_owned(ent)) - continue; - int wt = weapon_type_id(model, cls); - if (wt > 0 && owned_weapon[wt]) { - char base_label[160]; - snprintf(base_label, sizeof(base_label), "%s", label); - snprintf(label, sizeof(label), "%.100s ammo", base_label); - kind = IK_AMMO; - } - } - // Distance applies to every tag; 0 means unlimited. - if (g_item_max_dist > 0 && dist > (float)g_item_max_dist) - continue; - - ItemTarget &t = g_item_targets[g_nitems++]; - t.idx = idx; - t.kind = kind; - t.ammo_type = -1; - if (kind == IK_AMMO) { - t.ammo_type = item_ammo_type(model); - if (t.ammo_type < 0) - t.ammo_type = weapon_ammo_type(weapon_type_id(model, cls)); - } - t.origin = origin; - snprintf(t.label, sizeof(t.label), "%s", label); - if (dbg_lines < 8) { - dbg_lines++; - logd("item: idx=%d kind=%d cls=%s model=%s", idx, kind, - cls ? cls : "?", model ? model : "?"); - } - } - - if (!g_items_logged) { - g_items_logged = true; - logf("items: tagging %d valuable entities", g_nitems); - } -} - -// Authoritative local health/armor for the "only if usable" item filter. -// The health ceiling comes from the server player's m_iMaxHealth, not a -// hardcoded 100; when it cannot be read the filter fails open. -static void local_vitals(int &hp, int &maxhp, int &armor) { - hp = maxhp = armor = -1; - int local = engine_get_local_player(); - if (local <= 0) - return; - void *le = entitylist_get(local); - if (le && g_net.health >= 0) - hp = *(int *)((uintptr_t)le + g_net.health); - if (g_iface.engineserver) { - int smax = -1; - int shp = server_entity_health(local, &smax, nullptr, nullptr); - if (shp >= 0) - hp = shp; - if (smax > 0) - maxhp = smax; - armor = server_player_armor(local); - } - if (maxhp < 0 && le && g_net.maxhealth >= 0) - maxhp = *(int *)((uintptr_t)le + g_net.maxhealth); -} - -void esp_draw_items() { - if (!g_surface || !g_iface.entitylist || g_net.origin < 0) - return; - if (!g_item_health && !g_item_ammo && !g_item_armor && !g_item_weapons && - !g_item_crates) - return; - int local = engine_get_local_player(); - if (local <= 0) - return; - if (!esp_update_screen()) - return; - items_collect(local); - - int vit_hp = -1, vit_max = -1, vit_armor = -1; - if (g_item_usable) - local_vitals(vit_hp, vit_max, vit_armor); - static bool ammo_logged = false; - if (g_item_usable && !ammo_logged) { - ammo_logged = true; - char buf[512]; - int pos = 0; - void *le = entitylist_get(local); - for (int t = 0; t < 16 && pos < 500; t++) { - int a = local_ammo(le, t); - int m = ammo_max(t); - if (a >= 0 && m > 0) - pos += snprintf(buf + pos, sizeof(buf) - pos, " %d:%d/%d", t, a, m); - } - logf("items: ammo%s", buf); - } - - for (int i = 0; i < g_nitems; i++) { - ItemTarget &t = g_item_targets[i]; - // Follow the entity between the 10 Hz discovery scans. - Vector live; - if (entity_origin(entitylist_get(t.idx), live)) - t.origin = live; - if (!item_kind_enabled(t.kind)) - continue; - // Dispensers (chargers, ammo crates, supply crates) stay useful after a - // refill, so the fullness filter does not apply to them. - bool dispenser = strcasestr(t.label, "crate") || - strcasestr(t.label, "charger"); - if (g_item_usable && !dispenser) { - if (t.kind == IK_HEALTH && vit_hp >= 0 && vit_max > 0 && - vit_hp >= vit_max) - continue; - if (t.kind == IK_ARMOR && vit_armor >= 0 && vit_armor >= 100) - continue; - if (t.kind == IK_AMMO && t.ammo_type >= 0) { - int maxa = ammo_max(t.ammo_type); - int cura = local_ammo(entitylist_get(local), t.ammo_type); - if (maxa > 0 && cura >= 0 && cura >= maxa) - continue; - } - } - float sx, sy; - if (!world_to_screen(t.origin, sx, sy)) - continue; - if (sx < -40 || sx > (float)g_scr_w + 40 || sy < -20 || - sy > (float)g_scr_h + 20) - continue; - - int r = 230, g = 230, b = 230; - switch (t.kind) { - case IK_HEALTH: r = 90; g = 255; b = 130; break; - case IK_AMMO: r = 255; g = 210; b = 80; break; - case IK_ARMOR: r = 90; g = 180; b = 255; break; - case IK_WEAPON: r = 120; g = 230; b = 255; break; - case IK_CRATE: r = 220; g = 150; b = 255; break; - default: break; - } - int x = (int)sx, y = (int)sy; - int rad = (t.kind == IK_CRATE) ? 7 : 4; - surf_color(r, g, b, 255); - surf_rect(0, x - rad, y - rad, x + rad, y + rad); - surf_line(x - rad - 4, y, x - rad - 1, y); - surf_line(x + rad + 1, y, x + rad + 4, y); - surf_line(x, y - rad - 4, x, y - rad - 1); - surf_line(x, y + rad + 1, x, y + rad + 4); - if (t.kind == IK_CRATE) - surf_rect(1, x - 2, y - 2, x + 2, y + 2); - - float dx = t.origin.x - g_cam_eye.x; - float dy = t.origin.y - g_cam_eye.y; - float dz = t.origin.z - g_cam_eye.z; - int dist = (int)sqrtf(dx * dx + dy * dy + dz * dz); - char text[192]; - if (g_item_distance) - snprintf(text, sizeof(text), "%s %du", t.label, dist); - else - snprintf(text, sizeof(text), "%s", t.label); - int tw = (int)strlen(text) * 9; - int tx = x + 10; - if (tx + tw > g_scr_w - 4) - tx = x - 10 - tw; - if (tx < 4) - tx = 4; - int ty = y - 7; - if (ty < 0) - ty = 0; - if (ty + 16 > g_scr_h) - ty = g_scr_h - 16; - draw_text_col(tx, ty, text, r, g, b); - } -} - -// --------------------------------------------------------------------------- -// projectile ESP: grenades, rockets, helicopter bombs, AR2 balls -// --------------------------------------------------------------------------- - -- cgit v1.2.3