The VScript Book

Chapter 5.4: Entity Utilities - Safe Creation & Finding

Finding and creating entities safely is a fundamental task in every VScript project. The vanilla Entities class works, but it's unforgiving: it returns null without warning, lacks type safety, and requires verbose setup for entity creation. PCapture-Lib's entLib class solves these problems.

Finding Entities: The entLib Way

All entLib find methods are drop-in replacements for Entities methods, but they return pcapEntity objects instead of raw CBaseEntity handles.

Basic Finding

// By name (targetname)
local door = entLib.FindByName("exit_door")

// By classname
local cube = entLib.FindByClassname("prop_weighted_cube")

// By model
local crate = entLib.FindByModel("models/props/metal_box.mdl")

// All entities in radius
local nearbyEnts = []
local ent = null
while (ent = entLib.FindInSphere(playerPos, 200, ent)) {
    nearbyEnts.append(ent)
}

Spatial Finding: Within Radius

The *Within methods combine name/class/model search with radius filtering.

local player = GetPlayerEx()
local playerPos = player.GetOrigin()

// Find button named "puzzle_button" within 100 units of player
local button = entLib.FindByNameWithin("puzzle_button", playerPos, 100)

// Find any turret within 500 units
local turret = entLib.FindByClassnameWithin("npc_portal_turret_floor", playerPos, 500)

// Find specific prop by model near a location
local nearbyBarrel = entLib.FindByModelWithin(
    "models/props/metal_box.mdl",
    Vector(100, 200, 0),
    150
)

Iteration Pattern

Just like vanilla Entities, you can iterate by passing the previously found entity as the start_ent parameter.

// Find ALL cubes in the map
local cubes = []
local cube = null
while (cube = entLib.FindByClassname("prop_weighted_cube", cube)) {
    cubes.append(cube)
}
printl("Found " + cubes.len() + " cubes")

// Process each cube
foreach (cube in cubes) {
    cube.SetColor("0 255 0")  // Make all cubes green
}

Creating Entities: The Right Way

Creating entities dynamically is powerful but tricky. Vanilla VScript's Entities.CreateByClassname() exists, but it spawns entities in an incomplete state. PCapture's entLib.CreateByClassname() handles this properly.

Basic Creation

// Create an entity with keyvalues
local newLight = entLib.CreateByClassname("light_dynamic", {
    targetname = "my_light",
    _light = "255 0 0 200",  // Bright red light
    origin = "0 0 100"
})

// The entity is fully spawned and ready to use
EntFireByHandle(newLight, "TurnOn")

Creating Props

Props are common enough that PCapture provides a dedicated helper.

// Create a physics prop
local prop = entLib.CreateProp(
    "prop_physics",                      // classname
    Vector(100, 200, 50),                // origin
    "models/props/metal_box.mdl",        // model
    1,                                   // activity (usually 1 for idle)
    { targetname = "my_prop" }           // additional keyvalues
)

// Prop is immediately usable
prop.SetVelocity(Vector(0, 0, 500))  // Launch upward

Wrapping Existing Entities

Sometimes you already have a CBaseEntity handle from vanilla code. Convert it to a pcapEntity:

// From vanilla Entities search
local vanillaHandle = Entities.FindByName(null, "some_entity")

// Wrap it
local enhanced = entLib.FromEntity(vanillaHandle)

// Now use pcapEntity methods
enhanced.SetColor("255 0 0")

// Check if wrapping was successful
if (enhanced == null) {
    printl("Entity was null or invalid")
}

Comparing Entities: The Golden Rule

A major pitfall for scripters is comparing entities. You might find the same player entity in two different ways, but trying to compare them with == will fail.

// Find the player two different ways
local pcapPlayer = GetPlayerEx()         // This returns a pcapEntity
local cbasePlayer = GetPlayer()          // This returns a CBaseEntity (an engine handle)

// ❌ WRONG: This will always be false!
// You are comparing a weapper class to an engine handle, which can never be equal.
if (pcapPlayer == cbasePlayer) {
    printl("This message will never appear.")
}

This happens because pcapPlayer is a wrapper object and CBaseEntity is a direct engine handle. They are different data types, even if they both refer to the same player in the game.

The Solution: Always use macros.isEqually() to compare any two variables that might be entity handles. This function intelligently compares them by their unique Entity Index, which is always the same for a specific entity, regardless of handle type.

// ✅ CORRECT: This works for any combination of handle types.
if (macros.isEqually(pcapPlayer, cbasePlayer)) {
    printl("These are the same player entity!")
}

local door1 = entLib.FindByName("exit_door")
local door2 = entLib.FindByName("exit_door")

if (macros.isEqually(door1, door2)) {
    printl("These are the same door entity!")
}