An interactive breakdown of the do { … } while loop you wrote: a nearest-target search that drives a server-plugin lock-on for single-player Source games. Watch every line run, drag the NPCs around, and see how the state changes.
Top-down arena: the amber triangle is the player. Every tick advances the loop by one step; the highlighted lines on the right are the ones executing.
arena · GameFrame()pass 0
drag the player or any entity
serverEntity—
distance—
closestEnemyDistanceFLT_MAX
closestEnemyVectorFurthest
Press Step or let it play.
aimbot_loop.cppline —
enemy (IsEntityEnemy)friendly NPCprop / point entityclosest so far
02 · Walkthrough
What each block does
The loop is a classic linear minimum search over the server's entity list, with the "apply" step folded into the start of each pass.
L1–5 · ITERATE
Start at the head of the list
FirstEntity() returns the first server entity (normally worldspawn). NextEntity() walks forward until it returns nullptr.
L6–10 · COMMIT
New pass: aim, then reset
When the cursor is back at the first entity, the winner of the previous pass is used to set eye angles, then the best distance is reset to FLT_MAX.
L13–17 · FILTER
Skip what can't be a target
Point entities without a model are ignored. The classname from GetEntityName() is checked against the enemy list (npc_combine_s, npc_zombie…).
L18–29 · COMPARE
Keep the nearest eye position
Distance from the player's eye to the enemy's eye. If it beats the best so far, both the distance and the vector are stored for the next commit.
03 · Code review
Seven things worth tightening
It works, but a few details cost cycles or produce odd behaviour at the edges.
logic
The aim lags one pass behind
SetPlayerEyeAnglesToPosition runs at the top of the pass with the result of the pass before. The very first frame aims at GetFurthestVector(), and a target that just died stays aimed at for one more frame. Aim after the loop instead.
lines 6–10
perf
Player position fetched per enemy
GetPlayerPosition() can't change during the loop, but it's called once for every enemy found. With 40 NPCs that's 40 redundant calls per frame. Hoist it above the loop.
line 21
perf
FirstEntity() on every iteration
The "new pass" test calls FirstEntity() again for each entity just to compare pointers. Cache it, or better, do the reset before the loop and drop the check.
line 6
perf
A std::string per entity
A string is built for every entity with a model. Short classnames fit in the small-string buffer, but longer ones like npc_antlionguard allocate. Have IsEntityEnemy take a std::string_view.
line 15
logic
<= picks the last tie
With equal distances, the later entity wins. With floats that rarely happens, but < keeps the first match and stops needless writes.
line 26
logic
No enemies: staring at infinity
If nothing matches, closestEnemyVector stays at GetFurthestVector() and the view snaps there every frame. Only aim when a target was actually found (std::optional).
lines 8, 10
robustness
No visibility or alive checks
No line-of-sight trace, health check or field-of-view limit, so it will lock through walls and onto dying NPCs. Add a UTIL_TraceLine visibility test and a GetHealth() > 0 filter before comparing.
lines 17–29
refactored · aim after the loop, hoisted lookups
// Call once per GameFrame
void UpdateAim() {
Vector eyePosition{};
QAngle eyeAngles{};
GetServerTools()->GetPlayerPosition(eyePosition, eyeAngles);
float bestDistance{ std::numeric_limits<float>::max() };
std::optional<Vector> bestTarget{};
for (auto* entity{ reinterpret_cast<IServerEntity*>(GetServerTools()->FirstEntity()) };
entity != nullptr;
entity = reinterpret_cast<IServerEntity*>(GetServerTools()->NextEntity(entity))) {
if (entity->GetModelName().ToCStr() == nullptr) continue;
if (!IsEntityEnemy(GetEntityName(entity))) continue; // takes std::string_view
const auto enemyEyePosition{ GetEyePosition(entity) };
const auto distance{ VectorDistance(enemyEyePosition, eyePosition) };
if (distance < bestDistance) {
bestDistance = distance;
bestTarget = enemyEyePosition;
}
}
if (bestTarget) {
SetPlayerEyeAnglesToPosition(*bestTarget); // same frame, no lag
}
}