Skip to content

Powerup ESP ​

Color-coded ESP labels for every pickup entity in range: powerups from bridge spawn points (survival, casting, movement, gun), permanent stat modifiers from breakable props (hp, wp, cd, spirit, firerate, ammo), gold boxes, and health pickups. Each pickup category has a distinct color.

What this shows ​

  • One unified loop for all four pickup types. powerup, pickup_modifier, pickup_gold, and pickup_health get read in one pass via get_world_entities(type). The pickup_name field disambiguates.
  • The active filter is doing real work. Each powerup spawn point has four entities stacked at the same position (one per buff variant). Without the active filter the script would draw four overlapping labels at every powerup location.
  • pickup_name drives the label and color. A small get_label_and_color helper substring-matches the pickup name to a human-readable label and a category color. Adding a new pickup type means adding one if name:find(...) branch in get_label_and_color.
  • Distance label. A faint distance readout below each pickup label so you can quickly tell what's nearby versus far away without parsing world geometry.

Script ​

lua
-- powerup_esp.lua
-- Shows active powerup and pickup entities with their type labels.

local colors = {
    survival  = draw.color(90, 210, 170),
    casting   = draw.color(180, 100, 255),
    movement  = draw.color(100, 200, 255),
    gun       = draw.color(255, 160, 50),
    hp        = draw.color(50, 255, 50),
    wp        = draw.color(200, 200, 255),
    cd        = draw.color(255, 255, 100),
    spirit    = draw.color(200, 100, 255),
    firerate  = draw.color(255, 100, 100),
    ammo      = draw.color(255, 180, 50),
    gold      = draw.color(255, 215, 0),
    health    = draw.color(50, 255, 100),
    fallback  = draw.color(200, 200, 200),
    dist      = draw.color(160, 160, 160),
}

local function get_label_and_color(name)
    if not name or name == "" then return "PICKUP", colors.fallback end
    name = name:lower()
    if name:find("survival") then return "SURVIVAL", colors.survival end
    if name:find("casting") then return "CASTING", colors.casting end
    if name:find("movement") then return "MOVEMENT", colors.movement end
    if name:find("gun") then return "GUN", colors.gun end
    if name:find("firerate") then return "FIRE RATE", colors.firerate end
    if name:find("ammo") then return "AMMO", colors.ammo end
    if name:find("spirit") then return "SPIRIT", colors.spirit end
    if name:find("wp_") then return "WEAPON", colors.wp end
    if name:find("hp_") then return "HEALTH+", colors.hp end
    if name:find("cd_") then return "COOLDOWN", colors.cd end
    if name:find("gold") then return "GOLD", colors.gold end
    if name:find("health") then return "HEALTH", colors.health end
    return name:gsub("_pickup.*", ""):upper(), colors.fallback
end

function on_tick()
    local types = {"powerup", "pickup_modifier", "pickup_gold", "pickup_health"}
    for _, t in ipairs(types) do
        local ents = get_world_entities(t)
        for _, e in ipairs(ents) do
            if e.active and e.distance < 200 then
                local sp = draw.world_to_screen(vec3(e.x, e.y, e.z))
                if sp then
                    local label, color = get_label_and_color(e.pickup_name)
                    draw.text(sp.x, sp.y, label, color)
                    draw.text(sp.x, sp.y + 14, string.format("%.0fm", e.distance), colors.dist)
                end
            end
        end
    end
end

Notes ​

pickup_name is omitted from the entity table when it is empty (lua_engine.h lines 2848-2850: the field is only pushed if (!we.pickup_name.empty())). Accessing e.pickup_name on an entity without one returns nil. The script only calls get_label_and_color(e.pickup_name) for active entities that passed the type filter, and get_label_and_color guards on if not name or name == "", so nil is handled correctly. If you adapt this loop to other entity types that mix pickup and non-pickup entries, add an explicit nil check before passing the field.

The 200 m distance gate keeps the screen clean, pickups across the map don't get labeled. Lower it if you only want immediate surroundings, or remove the check entirely for full-map awareness during pre-fight planning.

get_label_and_color falls back to a generic "PICKUP" label for anything it doesn't recognize. If new pickup classes are added in a future patch, the script keeps working, those pickups just render with the fallback label until a matching case is added. The fallback also strips _pickup.* from the unrecognized name and uppercases it, which usually produces something readable.

To change colors or add categories, edit the colors table at the top and append a new if name:find(...) branch in get_label_and_color. The render loop never needs to change.

Not affiliated with Valve Corporation.