Autoaim
The game picks part of a shot's aim, steering it toward a target near where the player points.
On this page
Autoaim lets the game choose part of a shot’s aim. Doom traces ahead and 5.625 °1 to each side for a target, and a bullet takes the slope to whatever the search found, but keeps the player’s facing1. A player’s rocket, plasma ball, or BFG ball turns toward the target as well1.
Duke Nukem 3D turns the shot itself toward the nearest target within about 8.4° of the facing2. Perfect Dark leaves the shot alone and moves the crosshair: it eases toward the picked target, and the shot leaves through it3. How wide its window is depends on the difficulty4.
- Vertical searchDoom (1993). Traces ahead and 5.625° to each side set the shot's slope; only missiles turn toward the target.1
- Nearest in a coneDuke Nukem 3D (1996). The nearest target within 8.4° of the facing turns the shot both across and up or down.2
- Crosshair pullPerfect Dark (2000). The best-scoring armed enemy in the middle half of the screen, the nearest under the screen's center winning outright, with a margin scaled by difficulty; the crosshair eases toward it and the shot follows the crosshair.5,4,3
Games
| Game | Year | How | Note |
|---|---|---|---|
| Doom | 1993 | Vertical search | traces ahead and 5.625° to each side set the shot's slope; only missiles turn toward the target |
| Duke Nukem 3D | 1996 | Nearest in a cone | the nearest target within 8.4° of the facing turns the shot both across and up or down |
| Perfect Dark | 2000 | Crosshair pull | the best-scoring armed enemy in the middle half of the screen, the nearest under the screen's center winning outright, with a margin scaled by difficulty; the crosshair eases toward it and the shot follows the crosshair |
Every claim about this mechanic, by game (5)
References
- ^ abcdDoom aims vertically for the player:
P_BulletSlopetraces 1,024 units straight ahead, then 5.625° (1<<26) to the left, then to the right, and takes the slope to the middle of the first shootable thing's visible part, within ±100/160; a bullet keeps the player's facing and takes only that slope, but a player's rocket, plasma, or BFG ball also turns to the side trace that found the target and flies level when none did.Codeid-Software/DOOMHow we know
601void P_BulletSlope (mobj_t* mo) ⋮ 606 an = mo->angle; 607 bulletslope = P_AimLineAttack (mo, an, 16*64*FRACUNIT); ⋮ 611 an += 1<<26; 612 bulletslope = P_AimLineAttack (mo, an, 16*64*FRACUNIT); ⋮ 615 an -= 2<<26; 616 bulletslope = P_AimLineAttack (mo, an, 16*64*FRACUNIT);
linuxdoom-1.10/p_pspr.c lines 601–619 at a77dfb9 · upstream ↗ 1038 // can't shoot outside view angles 1039 topslope = 100*FRACUNIT/160; 1040 bottomslope = -100*FRACUNIT/160; ⋮ 1050 if (linetarget) 1051 return aimslope; ⋮ 1053 return 0;
linuxdoom-1.10/p_map.c lines 1038–1053 at a77dfb9 · upstream ↗ 882 // this thing can be hit! 883 if (thingtopslope > topslope) 884 thingtopslope = topslope; ⋮ 889 aimslope = (thingtopslope+thingbottomslope)/2; 890 linetarget = th;
linuxdoom-1.10/p_map.c lines 882–892 at a77dfb9 · upstream ↗ 633 damage = 5*(P_Random ()%3+1); 634 angle = mo->angle; ⋮ 639 P_LineAttack (mo, angle, MISSILERANGE, bulletslope, damage);
linuxdoom-1.10/p_pspr.c lines 633–639 at a77dfb9 · upstream ↗ 948 an = source->angle; 949 slope = P_AimLineAttack (source, an, 16*64*FRACUNIT); ⋮ 951 if (!linetarget) ⋮ 954 slope = P_AimLineAttack (source, an, 16*64*FRACUNIT); ⋮ 956 if (!linetarget) ⋮ 959 slope = P_AimLineAttack (source, an, 16*64*FRACUNIT); ⋮ 962 if (!linetarget) ⋮ 964 an = source->angle; 965 slope = 0; ⋮ 979 th->angle = an; ⋮ 984 th->momz = FixedMul( th->info->speed, slope);
linuxdoom-1.10/p_mobj.c lines 948–984 at a77dfb9 · upstream ↗ 69#define ANG90 0x40000000linuxdoom-1.10/tables.h line 69 at a77dfb9 · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-21.
Checked by a second AI model, 2026-10-21.
- ^ abWhen a Duke Nukem 3D player fires the pistol, shotgun, or chaingun,
aimlooks for a target withinAUTO_AIM_ANGLE, 48 of 2,048 angle units (about 8.4°, computed), to either side of the facing; of those in sight, more than 512 along the facing, and with a height difference over that distance (scaled by 10) within 100 of the view's pitch, it takes the nearest along the facing, searching the sprite lists 10, 13, 1, and 2 in that order and stopping at the first list with a match, and skipping fellow players in co-op. The shot then turns to the target's angle (getangle) and takes the slope to a point((xrepeat × tile height) << 1) + (5 << 8)above the target's z. Doom'sP_BulletSlopetraces only straight ahead and 5.625° to each side, and its bullet keeps the player's facing.Code3D Realms, duke3dsource.zip · id-Software/DOOMHow we know
62#define AUTO_AIM_ANGLE 48source/DUKE3D.H line 62 at e75bb06 · upstream ↗ 204short aim(spritetype *s,short aang) ⋮ 208 short aimstats[] = {10,13,1,2}; ⋮ 222 dx1 = sintable[(a+512-aang)&2047]; 223 dy1 = sintable[(a-aang)&2047]; 224 dx2 = sintable[(a+512+aang)&2047]; 225 dy2 = sintable[(a+aang)&2047];
source/PLAYER.C lines 204–227 at e75bb06 · upstream ↗ 230 for(k=0;k<4;k++) ⋮ 232 if( j >= 0 ) 233 break; ⋮ 240 if( PN == APLAYER && ⋮ 242 ud.coop == 1 && 243 s->picnum == APLAYER && 244 s != &sprite[i]) 245 continue;
source/PLAYER.C lines 230–246 at e75bb06 · upstream ↗ 273 if( (dy1*xv) <= (dx1*yv) ) 274 if( ( dy2*xv ) >= (dx2*yv) ) ⋮ 276 sdist = mulscale(dx3,xv,14) + mulscale(dy3,yv,14); 277 if( sdist > 512 && sdist < smax ) ⋮ 279 if(s->picnum == APLAYER) 280 a = (klabs(scale(SZ-s->z,10,sdist)-(ps[s->yvel].horiz+ps[s->yvel].horizoff-100)) < 100); ⋮ 287 if( a && cans ) ⋮ 289 smax = sdist; 290 j = i;
source/PLAYER.C lines 273–291 at e75bb06 · upstream ↗ 489 j = aim( s, AUTO_AIM_ANGLE ); 490 if(j >= 0) ⋮ 492 dal = ((sprite[j].xrepeat*tilesizy[sprite[j].picnum])<<1)+(5<<8); ⋮ 507 zvel = ( ( sprite[j].z-sz-dal )<<8 ) / ldist(&sprite[ps[p].i], &sprite[j]) ; 508 sa = getangle(sprite[j].x-sx,sprite[j].y-sy);
source/PLAYER.C lines 489–508 at e75bb06 · upstream ↗ 601void P_BulletSlope (mobj_t* mo) ⋮ 606 an = mo->angle; 607 bulletslope = P_AimLineAttack (mo, an, 16*64*FRACUNIT); ⋮ 611 an += 1<<26; 612 bulletslope = P_AimLineAttack (mo, an, 16*64*FRACUNIT); ⋮ 615 an -= 2<<26; 616 bulletslope = P_AimLineAttack (mo, an, 16*64*FRACUNIT);
linuxdoom-1.10/p_pspr.c lines 601–619 at a77dfb9 · upstream ↗ 627( mobj_t* mo, 628 boolean accurate ) ⋮ 634 angle = mo->angle; ⋮ 639 P_LineAttack (mo, angle, MISSILERANGE, bulletslope, damage);
linuxdoom-1.10/p_pspr.c lines 626–639 at a77dfb9 · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-22.
Checked by a second AI model, 2026-10-22.
- ^ abPerfect Dark's autoaim moves the crosshair, not the view: once a target is picked,
bmove_update_autoaim_x_propand_y_propset the aim point to the target's screen center (clamped to the window), and a new target replaces the old only after a 30/60 s hold;bgun_swivelthen eases the crosshair toward the point, keeping a fractioncrossdampof the gap each 1/240 s (a unit the decompilers' comments give). On NTSC that fraction,autoaimdamp, falls by 0.00032 a 60th of a second while autoaim holds a target, from at most 0.979 (it climbs back there while none is held) to 0.963, so the pull strengthens over up to 50/60 s (computed); at 0.963 the crosshair closes 14% of the gap each 60th of a second (1 − 0.963⁴, computed). The shot leaves through the crosshair's position plus a random spread. Doom's bullet keeps the player's facing and takes only a vertical slope, and Duke Nukem 3D turns the shot itself to the target's angle in the call that fires it.Coderyandwyer/perfect-dark · id-Software/DOOM · 3D Realms, duke3dsource.zipHow we know
2736 if (bestprop) { 2737 if (bmove_is_autoaim_y_enabled_for_current_weapon() || iscmpsec) { 2738 bmove_update_autoaim_y_prop(bestprop, (aimpos[1] - cam_get_screen_top()) / (cam_get_screen_height() * 0.5f) - 1); ⋮ 2742 bmove_update_autoaim_x_prop(bestprop, (aimpos[0] - cam_get_screen_left()) / (cam_get_screen_width() * 0.5f) - 1);
src/game/prop.c lines 2736–2743 at 46d3393 · upstream ↗ 97void bmove_update_autoaim_y_prop(struct prop *prop, f32 autoaimy) ⋮ 99 if (g_Vars.currentplayer->autoyaimtime60 >= 0) { 100 g_Vars.currentplayer->autoyaimtime60 -= g_Vars.lvupdate60; ⋮ 103 if (prop != g_Vars.currentplayer->autoyaimprop) { 104 if (g_Vars.currentplayer->autoyaimtime60 < 0) { 105 g_Vars.currentplayer->autoyaimtime60 = TICKS(30); 106 g_Vars.currentplayer->autoyaimprop = prop; 107 } else { 108 return; ⋮ 112 g_Vars.currentplayer->autoaimy = autoaimy;
src/game/bondmove.c lines 97–112 at 46d3393 · upstream ↗ 118#else 119#define TICKS(val) (val)
src/include/constants.h lines 118–119 at 46d3393 · upstream ↗ 1763 // Auto aim - move crosshair towards target ⋮ 1771 if (g_Vars.currentplayer->autoaimdamp > (PAL ? 0.955f : 0.963f)) { 1772 g_Vars.currentplayer->autoaimdamp -= (PAL ? 0.00037999986670911f : 0.00031999943894334f) * g_Vars.lvupdate60freal; ⋮ 1779 x = g_Vars.currentplayer->autoaimx; 1780 y = g_Vars.currentplayer->autoaimy; ⋮ 1785 bgun_swivel_with_damp(x, y, g_Vars.currentplayer->autoaimdamp); ⋮ 1788 // This code moves the crosshair as the player turns and makes 1789 // it return to the centre when not affected by anything else. 1790 if (g_Vars.currentplayer->autoaimdamp < (PAL ? 0.974f : 0.979f)) { 1791 g_Vars.currentplayer->autoaimdamp += (PAL ? 0.00037999986670911f : 0.00031999943894334f) * g_Vars.lvupdate60freal;
src/game/bondmove.c lines 1763–1791 at 46d3393 · upstream ↗ 4964 for (l = 0; l < g_Vars.lvupdate240; l++) { 4965 player->crosspossum[0] = player->crosspossum[0] * crossdamp + screenx; 4966 player->crosspossum[1] = player->crosspossum[1] * crossdamp + screeny; ⋮ 4975 player->crosspos[0] = player->crosspossum[0] * (1.0f - crossdamp) * screenwidth * 0.5f + screenwidth * 0.5f; 4976 player->crosspos[1] = player->crosspossum[1] * (1.0f - crossdamp) * screenheight * 0.5f + screenheight * 0.5f;
src/game/bondgun.c lines 4964–4976 at 46d3393 · upstream ↗ 112 // names ending in 60 mean their units are 60ths of a second 113 // names ending in 240 mean their units are 240ths of a second
src/include/types.h lines 112–113 at 46d3393 · upstream ↗ 5123 crosspos[0] = player->crosspos[0] + randfactor * scaledspread * cam_get_screen_width() ⋮ 5132 crosspos[1] = player->crosspos[1] + (randfactor * scaledspread * cam_get_screen_height()) ⋮ 5139 cam0f0b4c3c(crosspos, gundir2d, 1);
src/game/bondgun.c lines 5123–5139 at 46d3393 · upstream ↗ 633 damage = 5*(P_Random ()%3+1); 634 angle = mo->angle; ⋮ 639 P_LineAttack (mo, angle, MISSILERANGE, bulletslope, damage);
linuxdoom-1.10/p_pspr.c lines 633–639 at a77dfb9 · upstream ↗ 489 j = aim( s, AUTO_AIM_ANGLE ); 490 if(j >= 0) ⋮ 508 sa = getangle(sprite[j].x-sx,sprite[j].y-sy);
source/PLAYER.C lines 489–508 at e75bb06 · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-22.
Checked by a second AI model, 2026-10-22.
- ^ abIn solo Perfect Dark the autoaim margin (perfect-dark/autoaim) is multiplied by
g_AutoAimScale, whichlv_update_solo_handicapssets by difficulty: 1.5 on Agent, 0.75 on Special Agent, 0.2 on Perfect Agent, and 1 on the Perfect Dark difficulty; the ntsc-final build takes the non-Japanese values (g_Jpnis false). Co-op and Counter-Operative use their own values.Coderyandwyer/perfect-darkHow we know
1840 } else { 1841 if (g_Difficulty == DIFF_A) { ⋮ 1864 g_AutoAimScale = 1.5f;
src/game/lv.c lines 1840–1864 at 46d3393 · upstream ↗ 1867 } else if (g_Difficulty == DIFF_SA) { ⋮ 1877#if VERSION >= VERSION_JPN_FINAL 1878 g_AutoAimScale = 0.75f; 1879#else 1880 g_AutoAimScale = g_Jpn ? 1.1f : 0.75f; 1881#endif
src/game/lv.c lines 1867–1881 at 46d3393 · upstream ↗ 1884 } else if (g_Difficulty == DIFF_PA) { ⋮ 1897 g_AutoAimScale = g_Jpn ? 0.75f : 0.2f; 1898#endif
src/game/lv.c lines 1884–1898 at 46d3393 · upstream ↗ 1901 } else if (g_Difficulty == DIFF_PD) { ⋮ 1911 g_AutoAimScale = 1;
src/game/lv.c lines 1901–1911 at 46d3393 · upstream ↗ 53#if VERSION >= VERSION_PAL_BETA 54bool g_Jpn = VERSION == VERSION_JPN_FINAL ? true : false;
src/game/lang.c lines 53–54 at 46d3393 · upstream ↗ 66bool g_Jpn = false;src/game/lang.c line 66 at 46d3393 · upstream ↗ 2451 if (!g_Vars.normmplayerisrunning) { 2452 sp48 = sp48 * g_AutoAimScale;
src/game/prop.c lines 2451–2453 at 46d3393 · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-22.
Checked by a second AI model, 2026-10-22.
- ^Perfect Dark's standard autoaim, for a gun whose autoaim is on, scores each character on screen and takes the highest score. It skips teammates, the dead and dying, and anyone not holding a gun (unless flagged
CHRCFLAG_FORCEAUTOAIM). A candidate needs a clear line from the player, its screen center in the middle half of the screen's width, and its height reaching into the band from 17.5% to 82.5% of the screen's height; the screen's center must lie within 1.5 of its screen widths of its middle, times a solo scale (perfect-dark/autoaim-scale). The score is 1 when the screen's center x lies between the target's left and right screen edges, and otherwise falls off with the gap over that margin. The walk runs from the end of a listprops_sortorders byprop->z, largest first, which the decompilers' comment calls "furthest first"; since a score of 1 ends the walk, the nearest such target wins on that reading. Doom traces ahead and 5.625° to each side for a target, and Duke Nukem 3D'saimtakes the target within 8.4° of the facing that is nearest along it.Coderyandwyer/perfect-dark · id-Software/DOOM · 3D Realms, duke3dsource.ziprevisedHow we know
60/** 61 * Populate g_Vars.onscreenprops. This is an array of prop pointers, filtered by 62 * props that are on screen and sorted by distance descending (furthest first). ⋮ 64void props_sort(void)
src/game/prop.c lines 60–64 at 46d3393 · upstream ↗ 94 for (j = i; j < count; j++) { 95 if (depths[j] > depth) { 96 depth = depths[j]; 97 swapindex = j;
src/game/prop.c lines 94–98 at 46d3393 · upstream ↗ 2699 struct prop **ptr = g_Vars.endonscreenprops - 1; ⋮ 2702 while (ptr >= g_Vars.onscreenprops) { ⋮ 2710 if (!chr_compare_teams(g_Vars.currentplayer->prop->chr, chr, COMPARE_FRIENDS) 2711 && (chr_get_held_prop(chr, HAND_RIGHT) 2712 || chr_get_held_prop(chr, HAND_LEFT) 2713 || (chr->chrflags & CHRCFLAG_FORCEAUTOAIM) 2714 || chr->gunprop) 2715 && chr_calculate_autoaim(prop, &screenpos, xrange, yrange)) { 2716 f32 score = prop_calculate_autoaim_score(prop, &screenpos, xrange, yrange, thisaimpos, false, cangangsta, 0); ⋮ 2718 if (score > bestscore) { ⋮ 2724 if (score >= 1) { 2725 break;
src/game/prop.c lines 2699–2725 at 46d3393 · upstream ↗ 5014bool chr_calculate_autoaim(struct prop *prop, struct coord *screenpos, f32 *xrange, f32 *yrange) ⋮ 5020 && chr->actiontype != ACT_DIE ⋮ 5023 && chr->actiontype != ACT_DEAD
src/game/chr.c lines 5014–5025 at 46d3393 · upstream ↗ 2412 } else { 2413 top = cam_get_screen_top() + cam_get_screen_height() * 0.175f; 2414 bottom = cam_get_screen_top() + cam_get_screen_height() * 0.825f; 2415 left = cam_get_screen_left() + cam_get_screen_width() * 0.25f; 2416 right = cam_get_screen_left() + cam_get_screen_width() * 0.75f;
src/game/prop.c lines 2412–2417 at 46d3393 · upstream ↗ 2441 if (sp74[1] >= top && bottom >= sp7c[1]) { ⋮ 2449 sp48 = (sp84[0] - sp8c[0]) * 1.5f; ⋮ 2451 if (!g_Vars.normmplayerisrunning) { 2452 sp48 = sp48 * g_AutoAimScale; ⋮ 2455 sp4c = cam_get_screen_left() + 0.5f * cam_get_screen_width() >= (sp8c[0] + sp84[0]) * 0.5f - sp48 2456 && cam_get_screen_left() + 0.5f * cam_get_screen_width() <= (sp8c[0] + sp84[0]) * 0.5f + sp48 2457 && left <= spa0[0] 2458 && right >= spa0[0];
src/game/prop.c lines 2441–2458 at 46d3393 · upstream ↗ 2474 ok = cd_test_los_oobok(&playerprop->pos, playerprop->rooms, &prop->pos, 2475 CDTYPE_OBJS | CDTYPE_DOORS | CDTYPE_PATHBLOCKER | CDTYPE_BG, ⋮ 2479 if (ok) { ⋮ 2502 if (cam_get_screen_left() + 0.5f * cam_get_screen_width() >= sp8c[0] 2503 && cam_get_screen_left() + 0.5f * cam_get_screen_width() <= sp84[0]) { 2504 result = 1; 2505 } else if (cam_get_screen_left() + 0.5f * cam_get_screen_width() >= sp8c[0]) { 2506 result = 1 - ((cam_get_screen_left() + 0.5f * cam_get_screen_width()) - sp84[0]) / sp48; 2507 } else { 2508 result = 1 - (sp8c[0] - (cam_get_screen_left() + 0.5f * cam_get_screen_width())) / sp48;
src/game/prop.c lines 2474–2508 at 46d3393 · upstream ↗ 601void P_BulletSlope (mobj_t* mo) ⋮ 606 an = mo->angle; 607 bulletslope = P_AimLineAttack (mo, an, 16*64*FRACUNIT); ⋮ 611 an += 1<<26; 612 bulletslope = P_AimLineAttack (mo, an, 16*64*FRACUNIT); ⋮ 615 an -= 2<<26; 616 bulletslope = P_AimLineAttack (mo, an, 16*64*FRACUNIT);
linuxdoom-1.10/p_pspr.c lines 601–619 at a77dfb9 · upstream ↗ 62#define AUTO_AIM_ANGLE 48source/DUKE3D.H line 62 at e75bb06 · upstream ↗ 273 if( (dy1*xv) <= (dx1*yv) ) 274 if( ( dy2*xv ) >= (dx2*yv) ) ⋮ 276 sdist = mulscale(dx3,xv,14) + mulscale(dy3,yv,14); 277 if( sdist > 512 && sdist < smax ) ⋮ 287 if( a && cans ) ⋮ 289 smax = sdist; 290 j = i;
source/PLAYER.C lines 273–291 at e75bb06 · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-22.
Revised after a check by a second AI model, 2026-10-22.
Sources
Code
- Software, id. 2024. “DOOM: the Linux source release (id-Software/DOOM, linuxdoom-1.10).” Commit a77dfb9. id-Software/DOOM @ a77dfb9 · licence, all uses
- Realms, 3D. 2003. “Duke Nukem 3D: 3D Realms' source release (version 1.5, 2003).” Commit e75bb06. commercial-game-sources/duke_nukem_3d @ e75bb06 · licence, all uses
- Dwyer, Ryan and perfect-dark contributors. 2026. “perfect-dark: a decompilation of Perfect Dark (Ryan Dwyer).” Commit 46d3393. ryandwyer/perfect-dark @ 46d3393 · licence, all uses