On this page
  1. Related
  2. Games
  3. References
  4. Sources

A hitscan attack traces a line from the shooter when it fires and damages the first thing in the way, in the same tic. In Doom, bullets and melee trace lines, 2,048 units for guns, 64 for melee1, and rockets, plasma, and BFG balls are objects that fly at a fixed speed1. The shotgun fans 7 traces at once2.

Wolfenstein 3D traces no path. Its gun shot picks the nearest visible target near the center of the screen and hits it if the line to it is clear3. An enemy’s shot is a roll against the distance, with lower odds when the player runs4.

  • Target pickWolfenstein 3D (1992). A gun shot picks the nearest visible target near the screen's center and checks a clear line to it, and an enemy's shot is a roll against the distance and the player's speed.3,4,5
  • Hitscan beside projectilesDoom (1993). Bullets and melee trace a line in the tic they fire; rockets, plasma, and BFG balls fly.1,2Quake II (1997). Bullets, pellets, and the railgun trace a line when fired, the railgun through every body in its path; the blaster, grenades, and rockets fly.6

Games

GameYearHowNote
Wolfenstein 3D1992Target picka gun shot picks the nearest visible target near the screen's center and checks a clear line to it, and an enemy's shot is a roll against the distance and the player's speed
Doom1993Hitscan beside projectilesbullets and melee trace a line in the tic they fire; rockets, plasma, and BFG balls fly
Duke Nukem 3D1996Hitscan beside projectilesthe pistol, shotgun, and chaingun trace a line in the call that fires them; the RPG fires a rocket sprite with a speed of its own
Quake II1997Hitscan beside projectilesbullets, pellets, and the railgun trace a line when fired, the railgun through every body in its path; the blaster, grenades, and rockets fly
Every claim about this mechanic, by game (8)
Wolfenstein 3D 1992A Wolfenstein 3D gun shot (GunAttack) picks a target instead of tracing a bullet's path: it takes the nearest visible shootable actor within shootdelta (a tenth of the view's width) of the screen's center column and hits it if CheckLine returns true, which its header comment describes as "a straight line between the player and ob is unobstructed"; as written, the loop's second pass looks only for actors nearer than that one, so a blocked line is a miss (computed). The damage is a roll of US_RndT (0 to 255) by the larger of the x and y tile distances: under 2 tiles the roll / 4 (0 to 63), under 4 the roll / 6 (0 to 42), and farther a miss when a first roll / 12 is below the distance, otherwise a second roll / 6.Code
An enemy's shot in Wolfenstein 3D (T_Shoot) traces no bullet either: when the shooter's area connects to the player's (areabyplayer) and CheckLine finds a clear line, it hits if a roll of US_RndT is below a chance of 256 less 16 per tile of distance, or less 8 per tile when the shooter isn't visible on the player's screen (FL_VISABLE), starting from 160 instead of 256 when the player's thrust this frame (thrustspeed) reached RUNSPEED (6,000); SS guards and bossobj bosses count two-thirds of the distance. A hit deals the roll >> 2 (0 to 63) under 2 tiles, >> 3 (0 to 31) under 4, and >> 4 (0 to 15) farther.Code
Because the player's thrust scales with the frame's tics, a Wolfenstein 3D player who only walks straight forward with the keyboard (5,250 units a tic) counts as running for T_Shoot's lower hit chance (thrustspeed at least RUNSPEED, 6,000) on any frame that spans 2 or more tics, and only a 1-tic frame leaves that walk below it; thrustspeed sums every Thrust call, so a walk that also strafes (5,250 + 5,250) passes it even in 1 tic (computed).Code
Doom 1993Doom's bullets and melee are hitscan: P_LineAttack traces a line in the same tic, out to 2,048 units for guns and 64 for melee, and damages the first shootable thing whose height spans the shot's slope; rockets, plasma, and BFG balls are objects spawned by P_SpawnPlayerMissile that move each tic at their type's speed (20, 25, and 25 units per tic).Code
Doom's shotgun turns each of its 7 pellets by (P_Random()-P_Random())<<18, up to about 5.6° either side, at one shared vertical slope; the pistol and chaingun turn their shots the same way except while player->refire is 0, which covers the pistol's first shot and the chaingun's first two in a burst of held fire, and those fly straight.Code
Duke Nukem 3D 1996Duke Nukem 3D's pistol, shotgun, and chaingun are hitscan: shoot traces each shot with the Build engine's hitscan within the same call and spawns a SHOTSPARK1 sprite at the point hit, which carries the damage; the RPG instead spawns a rocket sprite with a velocity of 644. Doom's guns likewise trace a line with P_LineAttack in the tic they fire.Code
A Duke Nukem 3D player's shotgun and chaingun turn every shot by 16-(TRAND&31), −15 to +16 of the 2,048 angle units in a turn (up to about 2.8°, computed), and add 128-(TRAND&255) to its vertical speed, with or without an autoaim target; the pistol does both only when autoaim found none, so a pistol shot at an autoaim target flies straight to it. Doom's shotgun turns each of its 7 pellets by up to about 5.6°.Code
Quake II 1997Quake II's bullets, pellets, and railgun are hitscan: fire_lead, which fire_bullet (the machine gun and chaingun) and fire_shotgun (once per pellet, for both shotguns) call, traces a line 8192 units long in the frame the gun fires, bent by a random spread (crandom × the spread, each way) for each bullet or pellet, and fire_rail traces 8192 units and keeps tracing past every monster or player it hits, damaging each. The blaster, grenades, and rockets launch entities that travel: the player's blaster bolt at 1000 units/s, the launcher's grenade at 600, and the rocket at 650.Code

References

  1. ^ abcDoom's bullets and melee are hitscan: P_LineAttack traces a line in the same tic, out to 2,048 units for guns and 64 for melee, and damages the first shootable thing whose height spans the shot's slope; rockets, plasma, and BFG balls are objects spawned by P_SpawnPlayerMissile that move each tic at their type's speed (20, 25, and 25 units per tic).Codeid-Software/DOOMrevised
    How we know
    483    slope = P_AimLineAttack (player->mo, angle, MELEERANGE);
    484    P_LineAttack (player->mo, angle, MELEERANGE, slope, damage);
    
    linuxdoom-1.10/p_pspr.c lines 483–484 at a77dfb9 · upstream ↗
    57#define MELEERANGE		(64*FRACUNIT)
    58#define MISSILERANGE	(32*64*FRACUNIT)
    
    linuxdoom-1.10/p_local.h lines 57–58 at a77dfb9 · upstream ↗
    1063P_LineAttack
    ⋮
    1082    P_PathTraverse ( t1->x, t1->y,
    1083		     x2, y2,
    1084		     PT_ADDLINES|PT_ADDTHINGS,
    1085		     PTR_ShootTraverse );
    
    linuxdoom-1.10/p_map.c lines 1063–1085 at a77dfb9 · upstream ↗
    984    thingtopslope = FixedDiv (th->z+th->height - shootz , dist);
    ⋮
    986    if (thingtopslope < aimslope)
    987	return true;		// shot over the thing
    ⋮
    1010    if (la_damage)
    1011	P_DamageMobj (th, shootthing, shootthing, la_damage);
    ⋮
    1013    // don't go any farther
    1014    return false;
    
    linuxdoom-1.10/p_map.c lines 984–1014 at a77dfb9 · upstream ↗
    556    P_SpawnPlayerMissile (player->mo, MT_ROCKET);
    
    linuxdoom-1.10/p_pspr.c line 556 at a77dfb9 · upstream ↗
    973    th = P_SpawnMobj (x,y,z, type);
    ⋮
    980    th->momx = FixedMul( th->info->speed,
    981			 finecosine[an>>ANGLETOFINESHIFT]);
    
    linuxdoom-1.10/p_mobj.c lines 973–984 at a77dfb9 · upstream ↗
    1966    {		// MT_ROCKET
    ⋮
    1982	20*FRACUNIT,		// speed
    
    linuxdoom-1.10/info.c lines 1966–1982 at a77dfb9 · upstream ↗
    1992    {		// MT_PLASMA
    ⋮
    2008	25*FRACUNIT,		// speed
    
    linuxdoom-1.10/info.c lines 1992–2008 at a77dfb9 · upstream ↗
    2018    {		// MT_BFG
    ⋮
    2034	25*FRACUNIT,		// speed
    
    linuxdoom-1.10/info.c lines 2018–2034 at a77dfb9 · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-21.

    Revised after a check by a second AI model, 2026-10-21.

    In the claim ledger

  2. ^ abDoom's shotgun turns each of its 7 pellets by (P_Random()-P_Random())<<18, up to about 5.6° either side, at one shared vertical slope; the pistol and chaingun turn their shots the same way except while player->refire is 0, which covers the pistol's first shot and the chaingun's first two in a burst of held fire, and those fly straight.Codeid-Software/DOOM
    How we know
    627( mobj_t*	mo,
    628  boolean	accurate )
    ⋮
    636    if (!accurate)
    637	angle += (P_Random()-P_Random())<<18;
    ⋮
    639    P_LineAttack (mo, angle, MISSILERANGE, bulletslope, damage);
    
    linuxdoom-1.10/p_pspr.c lines 626–639 at a77dfb9 · upstream ↗
    684    P_BulletSlope (player->mo);
    ⋮
    686    for (i=0 ; i<7 ; i++)
    687	P_GunShot (player->mo, false);
    
    linuxdoom-1.10/p_pspr.c lines 684–687 at a77dfb9 · upstream ↗
    661    P_GunShot (player->mo, !player->refire);
    
    linuxdoom-1.10/p_pspr.c line 661 at a77dfb9 · upstream ↗
    753    P_GunShot (player->mo, !player->refire);
    
    linuxdoom-1.10/p_pspr.c line 753 at a77dfb9 · upstream ↗
    350    if ( (player->cmd.buttons & BT_ATTACK) 
    ⋮
    354	player->refire++;
    355	P_FireWeapon (player);
    ⋮
    359	player->refire = 0;
    
    linuxdoom-1.10/p_pspr.c lines 350–359 at a77dfb9 · upstream ↗
    149    {SPR_PISG,0,4,{NULL},S_PISTOL2,0,0},	// S_PISTOL1
    150    {SPR_PISG,1,6,{A_FirePistol},S_PISTOL3,0,0},// S_PISTOL2
    151    {SPR_PISG,2,4,{NULL},S_PISTOL4,0,0},	// S_PISTOL3
    152    {SPR_PISG,1,5,{A_ReFire},S_PISTOL,0,0},	// S_PISTOL4
    
    linuxdoom-1.10/info.c lines 149–152 at a77dfb9 · upstream ↗
    188    {SPR_CHGG,0,4,{A_FireCGun},S_CHAIN2,0,0},	// S_CHAIN1
    189    {SPR_CHGG,1,4,{A_FireCGun},S_CHAIN3,0,0},	// S_CHAIN2
    190    {SPR_CHGG,1,0,{A_ReFire},S_CHAIN,0,0},	// S_CHAIN3
    
    linuxdoom-1.10/info.c lines 188–190 at a77dfb9 · upstream ↗
    69#define ANG90			0x40000000
    
    linuxdoom-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.

    In the claim ledger

  3. ^ abA Wolfenstein 3D gun shot (GunAttack) picks a target instead of tracing a bullet's path: it takes the nearest visible shootable actor within shootdelta (a tenth of the view's width) of the screen's center column and hits it if CheckLine returns true, which its header comment describes as "a straight line between the player and ob is unobstructed"; as written, the loop's second pass looks only for actors nearer than that one, so a blocked line is a miss (computed). The damage is a roll of US_RndT (0 to 255) by the larger of the x and y tile distances: under 2 tiles the roll / 4 (0 to 63), under 4 the roll / 6 (0 to 42), and farther a miss when a first roll / 12 is below the distance, otherwise a second roll / 6.Codeid-Software/wolf3drevised
    How we know
    1196	while (1)
    ⋮
    1198		oldclosest = closest;
    ⋮
    1200		for (check=ob->next ; check ; check=check->next)
    1201			if ( (check->flags & FL_SHOOTABLE)
    1202			&& (check->flags & FL_VISABLE)
    1203			&& abs (check->viewx-centerx) < shootdelta
    ⋮
    1206				if (check->transx < viewdist)
    ⋮
    1208					viewdist = check->transx;
    1209					closest = check;
    ⋮
    1213		if (closest == oldclosest)
    1214			return;						// no more targets, all missed
    ⋮
    1217	// trace a line from player to enemey
    ⋮
    
    WOLFSRC/WL_AGENT.C lines 1196–1242 at 0516778 · upstream ↗
    1283	shootdelta = viewwidth/10;
    
    WOLFSRC/WL_MAIN.C line 1283 at 0516778 · upstream ↗
    1030= CheckLine
    ⋮
    1032= Returns true if a straight line between the player and ob is unobstructed
    
    WOLFSRC/WL_STATE.C lines 1030–1032 at 0516778 · upstream ↗
    80; int US_RndT (void)
    81; Return a random # between 0-255
    ⋮
    94	mov	al,[es:rndtable+BX]
    95	xor	ah,ah
    
    WOLFSRC/ID_US_A.ASM lines 80–95 at 0516778 · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-25.

    Revised after a check by a second AI model, 2026-10-22.

    Checked by a second AI model, 2026-10-25.

    In the claim ledger

  4. ^ abAn enemy's shot in Wolfenstein 3D (T_Shoot) traces no bullet either: when the shooter's area connects to the player's (areabyplayer) and CheckLine finds a clear line, it hits if a roll of US_RndT is below a chance of 256 less 16 per tile of distance, or less 8 per tile when the shooter isn't visible on the player's screen (FL_VISABLE), starting from 160 instead of 256 when the player's thrust this frame (thrustspeed) reached RUNSPEED (6,000); SS guards and bossobj bosses count two-thirds of the distance. A hit deals the roll >> 2 (0 to 63) under 2 tiles, >> 3 (0 to 31) under 4, and >> 4 (0 to 15) farther.Codeid-Software/wolf3d
    How we know
    3449	hitchance = 128;
    ⋮
    3451	if (!areabyplayer[ob->areanumber])
    3452		return;
    ⋮
    3454	if (!CheckLine (ob))			// player is behind a wall
    3455	  return;
    ⋮
    3457	dx = abs(ob->tilex - player->tilex);
    3458	dy = abs(ob->tiley - player->tiley);
    3459	dist = dx>dy ? dx:dy;
    ⋮
    3461	if (ob->obclass == ssobj || ob->obclass == bossobj)
    3462		dist = dist*2/3;					// ss are better shots
    ⋮
    3464	if (thrustspeed >= RUNSPEED)
    ⋮
    3466		if (ob->flags&FL_VISABLE)
    ⋮
    
    WOLFSRC/WL_ACT2.C lines 3449–3490 at 0516778 · upstream ↗
    75#define RUNSPEED   		6000
    
    WOLFSRC/WL_DEF.H line 75 at 0516778 · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-22.

    Checked by a second AI model, 2026-10-22.

    In the claim ledger

  5. ^Because the player's thrust scales with the frame's tics, a Wolfenstein 3D player who only walks straight forward with the keyboard (5,250 units a tic) counts as running for T_Shoot's lower hit chance (thrustspeed at least RUNSPEED, 6,000) on any frame that spans 2 or more tics, and only a 1-tic frame leaves that walk below it; thrustspeed sums every Thrust call, so a walk that also strafes (5,250 + 5,250) passes it even in 1 tic (computed).Codeid-Software/wolf3drevised
    How we know
    354	else
    ⋮
    356		if (Keyboard[dirscan[di_north]])
    357			controly -= BASEMOVE*tics;
    
    WOLFSRC/WL_PLAY.C lines 354–357 at 0516778 · upstream ↗
    205	if (controly < 0)
    ⋮
    207		Thrust (ob->angle,-controly*MOVESCALE);	// move forwards
    
    WOLFSRC/WL_AGENT.C lines 205–207 at 0516778 · upstream ↗
    156	thrustspeed = 0;
    
    WOLFSRC/WL_AGENT.C line 156 at 0516778 · upstream ↗
    164	if (buttonstate[bt_strafe])
    ⋮
    167	// strafing
    ⋮
    170		if (controlx > 0)
    ⋮
    172			angle = ob->angle - ANGLES/4;
    173			if (angle < 0)
    174				angle += ANGLES;
    175			Thrust (angle,controlx*MOVESCALE);	// move to left
    
    WOLFSRC/WL_AGENT.C lines 164–175 at 0516778 · upstream ↗
    943	thrustspeed += speed;
    
    WOLFSRC/WL_AGENT.C line 943 at 0516778 · upstream ↗
    3464	if (thrustspeed >= RUNSPEED)
    
    WOLFSRC/WL_ACT2.C line 3464 at 0516778 · upstream ↗
    75#define RUNSPEED   		6000
    
    WOLFSRC/WL_DEF.H line 75 at 0516778 · 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.

    In the claim ledger

  6. ^Quake II's bullets, pellets, and railgun are hitscan: fire_lead, which fire_bullet (the machine gun and chaingun) and fire_shotgun (once per pellet, for both shotguns) call, traces a line 8192 units long in the frame the gun fires, bent by a random spread (crandom × the spread, each way) for each bullet or pellet, and fire_rail traces 8192 units and keeps tracing past every monster or player it hits, damaging each. The blaster, grenades, and rockets launch entities that travel: the player's blaster bolt at 1000 units/s, the launcher's grenade at 600, and the rocket at 650.Codeid-Software/Quake-2
    How we know
    152		r = crandom()*hspread;
    153		u = crandom()*vspread;
    154		VectorMA (start, 8192, forward, end);
    155		VectorMA (end, r, right, end);
    156		VectorMA (end, u, up, end);
    ⋮
    165		tr = gi.trace (start, NULL, NULL, end, self, content_mask);
    
    game/g_weapon.c lines 152–165 at 372afde · upstream ↗
    667	VectorMA (start, 8192, aimdir, end);
    ⋮
    672	while (ignore)
    ⋮
    674		tr = gi.trace (from, NULL, NULL, end, ignore, mask);
    ⋮
    681		else
    ⋮
    683			if ((tr.ent->svflags & SVF_MONSTER) || (tr.ent->client))
    684				ignore = tr.ent;
    685			else
    686				ignore = NULL;
    ⋮
    688			if ((tr.ent != self) && (tr.ent->takedamage))
    689				T_Damage (tr.ent, self, self, aimdir, tr.endpos, tr.plane.normal, damage, kick, 0, MOD_RAILGUN);
    
    game/g_weapon.c lines 667–689 at 372afde · upstream ↗
    277void fire_bullet (edict_t *self, vec3_t start, vec3_t aimdir, int damage, int kick, int hspread, int vspread, int mod)
    ⋮
    279	fire_lead (self, start, aimdir, damage, kick, TE_GUNSHOT, hspread, vspread, mod);
    ⋮
    287Shoots shotgun pellets.  Used by shotgun and super shotgun.
    ⋮
    294	for (i = 0; i < count; i++)
    295		fire_lead (self, start, aimdir, damage, kick, TE_SHOTGUN, hspread, vspread, mod);
    
    game/g_weapon.c lines 277–295 at 372afde · upstream ↗
    1014	fire_bullet (ent, start, forward, damage, kick, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MOD_MACHINEGUN);
    
    game/p_weapon.c line 1014 at 372afde · upstream ↗
    1151		fire_bullet (ent, start, forward, damage, kick, DEFAULT_BULLET_HSPREAD, DEFAULT_BULLET_VSPREAD, MOD_CHAINGUN);
    
    game/p_weapon.c line 1151 at 372afde · upstream ↗
    1213		fire_shotgun (ent, start, forward, damage, kick, 500, 500, DEFAULT_DEATHMATCH_SHOTGUN_COUNT, MOD_SHOTGUN);
    
    game/p_weapon.c line 1213 at 372afde · upstream ↗
    1266	fire_shotgun (ent, start, forward, damage, kick, DEFAULT_SHOTGUN_HSPREAD, DEFAULT_SHOTGUN_VSPREAD, DEFAULT_SSHOTGUN_COUNT/2, MOD_SSHOTGUN);
    
    game/p_weapon.c line 1266 at 372afde · upstream ↗
    728	fire_grenade (ent, start, forward, damage, 600, 2.5, radius);
    
    game/p_weapon.c line 728 at 372afde · upstream ↗
    620void fire_rocket (edict_t *self, vec3_t start, vec3_t dir, int damage, int speed, float damage_radius, int radius_damage)
    ⋮
    628	VectorScale (dir, speed, rocket->velocity);
    629	rocket->movetype = MOVETYPE_FLYMISSILE;
    
    game/g_weapon.c lines 620–629 at 372afde · upstream ↗
    783	fire_rocket (ent, start, forward, damage, 650, damage_radius, radius_damage);
    
    game/p_weapon.c line 783 at 372afde · upstream ↗
    832	fire_blaster (ent, start, forward, damage, 1000, effect, hyper);
    
    game/p_weapon.c line 832 at 372afde · upstream ↗

    Checked against the evidence by its author, an AI model, 2026-10-22.

    Checked by a second AI model, 2026-10-22.

    In the claim ledger

Sources

Code