Entities
Entities are everything in the world that is not a player: troopers, jungle camps, the midboss, crates, powerups, gravestones, skeletons, projectiles. Players have their own accessor; everything else is in the entity list.
You can also approach detection through particles, modifiers, or other systems, but checking the entity list is usually the first thing to try when designing a script, especially anything ESP or detection-related.
Discovery: Show All Entities ESP
TSUKI ships with a dev tool that overlays every entity it currently sees with its class name, health, and distance. Open Dev Studio from the bug icon in the pill above the menu, go to World, and turn on Show All Entities on ESP.

In the screenshot above, Graves' Gravestone ultimate shows up as citadel_gravestone_blocker, and the skeletons it spawns are npc_necro_skele. The breakable props next to the gravestone are citadel_breakableprop. Calico's cat is npc_neutral_hideout_cat, and the souls dropped on the floor are citadel_pickup_gold. Toggle the ESP on to discover the exact class name for whatever entity you're trying to script around, then filter get_world_entities() by that class name.
Include All Entity Types
Include All Entity Types (below the ESP toggle, off by default) makes get_world_entities() also return engine entities such as env_sky, zipline nodes, func_brush and trigger volumes. Off, it returns only the curated types in the table below plus projectiles, gravestones and breakables.
Leave it off unless a script needs one of those classes. Scanning the full entity list every frame has measurable overhead, which is why the curated parse is the default.
entities.by_class
get_world_entities returns a curated list filtered by type name and carries the pickup fields. entities.by_class returns every entity of one class by name, only for classes you ask for, and its rows can read a game field by name through the internal.
entities.classes() maps every class alive right now to a count. Use it to find the spelling to pass to by_class.
| Function | Returns |
|---|---|
entities.by_class(class_name) | Array of row tables, {}, or nil |
entities.classes() | {[class_name] = count} of everything alive, or nil |
entities.diag() | Counters, or nil |
All three work in any script.
Row fields
Each element of the array by_class returns is a row table with these fields:
| Field | Type | Description |
|---|---|---|
index | integer | Entity index |
ptr | integer | Opaque pointer value for the entity. row:get reads through it, scripts have no other use for it |
handle | integer | Entity handle. Stable while the entity lives |
class | string | The class you asked for, in its normalised form |
class_name | string or nil | The raw C++ class name, present only when known |
x, y, z | number | World position |
position | vec3 | The same position as a vec3, ready to hand to a 3D draw call |
team | integer | Team number |
health | integer | Current HP |
max_health | integer | Maximum HP |
owner_index | integer | Index of the owning entity, -1 for none |
distance_m | number | Distance from the local player, in meters |
alive_seq | integer | The refresh this row came from |
Plus one method, row:get(name), which reads a schema field off the entity behind the row.
row:get needs the internal
Every field above works with nothing loaded. row:get needs the internal running and needs_internal = true in script_info, and returns nil without them. See Entity Properties.
A row is a plain table with one method. It has no bones, modifiers or visibility.
nil and an empty table mean different things
nil means no answer yet: the first call for a new class always returns nil, and the rows arrive a tick or two later. {} means answered and none alive. by_class(c) or {} hides the difference.
Asking is what keeps a class alive
A class is read only while something asks for it. After 2 seconds without a call it is dropped. Call by_class every tick while you need it.
Limits
At most 8 classes at once. A ninth returns nil and logs once; a slot frees when a class idles for 2 seconds. At most 64 entities per class; the rest are not read, and entities.diag().capped counts them.
Class names are normalised
A leading CCitadel_ or C_ is stripped and the rest lowercased, so C_CitadelPlayerPawn, CCitadelPlayerPawn and citadelplayerpawn are the same class, and C_NPC_Trooper is npc_trooper.
Rows are reused and shared
Row tables and the array are reused between calls and shared between scripts. Copy the values you need; do not modify a row or keep it across ticks.
Entity events
Calling by_class on a class is also what registers you for on_entity_added and on_entity_removed on that class. See Events.
Counts differ from the player list
The pawn class sees every pawn in the match; get_all_players() holds only the teams your ESP shows. Compare positions, not counts.
entities.diag()
Counters, no game values.
| Field | Meaning |
|---|---|
wants | Classes currently being read |
refused | Calls turned away because 8 classes were already wanted |
capped | Entities dropped past the 64 per class limit |
stale | Rows dropped because the entity slot was reused by another entity |
added, removed | Entity add and remove counts |
events_delivered, events_pending | Delivered and queued entity events |
Example
A killsteal marker from a class query and each trooper's health. my_team, max_m, limit and COL are set earlier in the script:
local rows = entities.by_class("npc_trooper")
if rows then
for _, e in ipairs(rows) do
if e.team ~= my_team and e.health > 0 and e.distance_m <= max_m then
if e.health <= limit then
draw.circle3d(e.position, 14, COL, 2, 20)
draw.text3d(e.position, string.format("KS %d hp", e.health), COL)
end
end
end
endBoth draw calls last one tick, so this belongs in on_tick. To read a game field off a row, see Entity Properties.
get_world_entities([filter])
Returns a table of all world entities currently tracked. Optionally filter by a curated type name.
Arguments:
filter(string, optional). Matches either the curated type name ("sinner") or the exact engine class name ("citadel_gravestone_blocker"). The match is exact and case-sensitive. There is no substring matching.
Returns: Table of entity tables, each with these fields:
| Field | Type | Description |
|---|---|---|
type | string | Curated entity type (see list below). Never empty. Anything outside the curated set arrives as "entity" (parsed but unclassified) or "unknown" |
class_name | string | Internal RTTI class name (e.g. npc_neutral_sinners_sacrifice, citadel_gravestone_blocker) |
x, y, z | number | World position |
health | integer | Current HP (if applicable) |
max_health | integer | Maximum HP (if applicable) |
team | integer | Team number. 0=none, 1=spectator, 2=amber, 3=sapphire, 4=neutral (jungle, props) |
distance | number | Distance from the local player, in meters |
currency_value | integer | Soul value (for dropped souls) |
pickup_name | string or nil | Pickup identifier, only present on powerup, pickup_modifier, pickup_gold, and pickup_health types. See Pickup entities. |
active | boolean | For pickup entities, whether this variant is currently spawned and collectible. Multiple pickup entities can stack at the same position, only one is active at a time. |
subclass | integer | m_nSubclassID - distinguishes entities that share the same class name. For example, golden statues and soul crates are both citadel_breakableprop but have different subclass values (0xDDAC9D93 and 0xEDA33BFB respectively). |
This class_name is not the same field as class_name on an entities.by_class row. The two rows are produced by different readers and are not interchangeable, so pick one of the two and stay with it.
Curated types
| Type | Description |
|---|---|
trooper | Lane creeps |
trooper_boss | Boss / super troopers |
camp | Jungle camp neutrals and bugs |
sinner | Sinner sacrifice camps |
tower | Guardians, walkers, shrines |
midboss | Mid boss |
soul_orb | Denial/last-hit soul orbs |
soul_urn | Soul urn pickups |
dropped_souls | Dropped souls from kills |
powerup | Map powerups (casting, movement, survival, gun) |
pickup_gold | Small gold boxes |
pickup_health | Health pickups |
pickup_modifier | Stat modifier pickups (hp, wp, cd, spirit, firerate) |
guided_owl | Grey Talon guided arrow projectile |
doorman_cart | Doorman luggage cart projectile |
shop | Shop trigger volumes |
entity | Unclassified entities (props, spawners, items on ground) |
unknown | Anything the scanner parsed but could not classify, notably the exotic engine classes surfaced by Include All Entity Types |
You can filter entities outside the curated list by class name directly: get_world_entities("citadel_gravestone_blocker"). This covers gravestones, breakable props, ability projectiles, and skeletons. Use the no-argument form to discover class names, not when you already know the one you want.
When the local player position isn't valid yet (spectating, pre-spawn, between rounds) get_world_entities() returns an empty table. If you got an entity back, distance is always a valid number. No nil check needed.
Pickup entities
Entities of type powerup, pickup_modifier, pickup_gold, and pickup_health carry the extra pickup_name and active fields. Each powerup spawn location has four entities stacked at the same position, one per buff variant, and only one is active at a time. Always filter by active to avoid rendering duplicate labels at the same world point.
Powerup variants (type "powerup", temporary buffs from bridge spawn points):
pickup_name | Buff |
|---|---|
survival_powerup_pickup | Survival |
casting_powerup_pickup | Casting |
movement_powerup_pickup | Movement |
gun_powerup_pickup | Gun |
Modifier variants (type "pickup_modifier", permanent stat bonuses from breakable props):
pickup_name | Stat |
|---|---|
hp_permanent_pickup_label | Max health |
wp_permanent_pickup_label | Weapon damage |
cd_permanent_pickup_label | Cooldown reduction |
spirit_permanent_pickup_label | Spirit power |
firerate_permanent_pickup_label | Fire rate |
ammo_permanent_pickup_label | Ammo |
Names ending in _lv2 are upgraded versions of the same stat.
Basic powerup ESP example, labels the four powerup variants by name:
function on_tick()
for _, p in ipairs(get_world_entities("powerup")) do
if p.active and p.distance < 200 then
local sp = draw.world_to_screen(vec3(p.x, p.y, p.z))
if sp then
local label = "POWERUP"
if p.pickup_name then
if p.pickup_name:find("survival") then label = "SURVIVAL"
elseif p.pickup_name:find("casting") then label = "CASTING"
elseif p.pickup_name:find("movement") then label = "MOVEMENT"
elseif p.pickup_name:find("gun") then label = "GUN"
end
end
draw.text(sp.x, sp.y, label, draw.color(255, 0, 255))
end
end
end
endFor a fuller version that handles all four pickup types (powerups, modifiers, gold, health) with color-coded categories, see Powerup ESP.
Examples
-- Total count
local all = get_world_entities()
print("Total entities: " .. #all)-- All sinners with distance
for _, s in ipairs(get_world_entities("sinner")) do
print("Sinner at " .. s.x .. ", " .. s.y .. " - " .. s.distance .. "m away")
end-- Mark nearby powerups on screen
for _, p in ipairs(get_world_entities("powerup")) do
if p.distance < 30 then
local sp = draw.world_to_screen(vec3(p.x, p.y, p.z))
if sp then
draw.text(sp.x, sp.y, "POWERUP", draw.color(255, 255, 0, 255))
end
end
end-- Show camp HP bars
for _, c in ipairs(get_world_entities("camp")) do
if c.health > 0 and c.distance < 100 then
local sp = draw.world_to_screen(vec3(c.x, c.y, c.z))
if sp then
draw.text(sp.x, sp.y, c.health .. "/" .. c.max_health, draw.color(120, 120, 255, 255))
end
end
end-- Gravestone marker (filter straight on the class name)
for _, e in ipairs(get_world_entities("citadel_gravestone_blocker")) do
draw.box3d(vec3(e.x, e.y, e.z), 50, 50, 80, draw.color(0, 255, 0, 255), 2)
end-- Discover unknown entity classes
local seen = {}
function on_tick()
for _, e in ipairs(get_world_entities()) do
if not seen[e.class_name] then
seen[e.class_name] = true
print("class:", e.class_name)
end
end
endentity.set_rate(type, tier)
Controls how frequently entity positions are updated. Entities have default update rates based on their type; see the table below. Use this to promote entities to faster updates when your script needs precise, real-time tracking.
Arguments:
type(string). Entity type name (e.g."sinner","powerup") or internal class name (e.g."npc_neutral_sinners_sacrifice","citadel_punchablepowerup").tier(string). One of:"fast". Updated every frame (~16ms). Use for time-critical features like custom parry or precision aiming."medium". Updated every 200ms. Good for ESP or general tracking."slow". Updated every ~1 second. For entities you just need to know exist."default". Revert to the system's default rate for this type.
Returns: nothing.
An unrecognised tier raises a Lua error instead of being ignored: entity.set_rate: tier must be 'fast', 'medium', 'slow', or 'default'. A typo aborts the calling script unless you wrap the call in pcall. Both arguments must be strings.
Notes:
- You can only promote entities to faster speeds, not demote below their default. For example, sinners default to
"medium", setting them to"slow"has no effect. - Overrides persist until you call
set_ratewith"default"or the script is unloaded. - Multiple scripts can set different rates for the same type. The fastest rate wins.
Examples:
-- Make powerup positions update every frame for precision tracking
entity.set_rate("powerup", "fast")
-- Revert to default
entity.set_rate("powerup", "default")
-- For a custom auto-parry against a specific projectile, promote it to per-frame
entity.set_rate("citadel_punchablepowerup", "fast")entity.get_rate(type)
Returns the current Lua rate override for an entity type.
Arguments:
type(string). Entity type name or class name.
Returns: string: "fast", "medium", "slow", or "default" (no override set).
print(entity.get_rate("sinner")) -- "default"
entity.set_rate("sinner", "fast")
print(entity.get_rate("sinner")) -- "fast"
entity.set_rate("sinner", "default")
print(entity.get_rate("sinner")) -- "default"Default update rates
Entities update at different speeds depending on their importance. You don't need to call set_rate unless you need faster updates than the defaults.
| Type | Default Rate | Notes |
|---|---|---|
trooper | Per-frame when trooper ESP/aimbot is on, otherwise slow | High count (~100+) |
soul_orb | Per-frame when soul aimbot/triggerbot is on, otherwise slow | High count |
guided_owl | Per-frame | Always fast when present |
sinner | 200 ms | Always on |
tower | 200 ms | Always on |
midboss | 200 ms | Always on |
camp | 200 ms | Always on |
powerup | 200 ms | Always on |
pickup_gold | 200 ms | Always on |
pickup_health | 200 ms | Always on |
pickup_modifier | 200 ms | Always on |
dropped_souls | 200 ms | Always on |
soul_urn | 200 ms | Always on |
doorman_cart | 200 ms | Always on |
entity | ~1 second | Static entities (props, items) |
Position freshness in get_world_entities() depends on the rate, per-frame entities have positions ~16 ms old, 200 ms entities up to ~200 ms old, slow entities up to ~1 s old. For static entities like props and items, the position never changes so staleness doesn't matter.