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!")
}