Quake II
id Software's 1997 first-person shooter, read through id's GPL release of the 3.19 source, against Quake's and Quake III's rules.
The atlas reads Quake II through the source id Software released under the GPL in 2001, which names itself version 3.191,1, so every claim here describes 3.19 rather than the 1997 retail build. Player movement is engine code that the server and the client’s prediction both run; weapons, damage, and monsters live in the game library.
#Movement
On the ground an acceleration of 10 works against a friction of 6, the numbers Quake III Arena would keep, where Quake’s friction is 4, so a run settles at exactly the 300 units/s cap (computed)2. That cap is on the wish velocity’s length, and with the view level a run already wishes more than 300, so a strafe added to a run gains nothing on the ground; only a walk does3.
In the air the push is a tenth of the ground’s, but it stops only when the speed along the wish direction reaches the full wish speed, not 30 as in Quake. Quake III kept this rule and called its acceleration code “q2 style”. A deathmatch server can switch Quake’s air rule back on, though only as an on-off switch4. The jump adds 270 units/s5,5 to the vertical velocity, as Quake’s does and Quake III’s doesn’t6.
The jump is checked before friction, so a bunny hop on the first ground step loses nothing to the floor, as in QuakeWorld. A landing faster than 200 units/s downward can set a timer that blocks the next jump for 18 units, 144 ms at 8 ms commands. By the atlas’s reading of the code, a landing step that reaches the floor would not set it; how often a hop’s landing does has not been measured7.
#Weapons and damage
The machine gun, chaingun, shotguns, and railgun are hitscan: each bullet or pellet traces its own randomly spread line, and the rail goes on through every monster or player in its path. The blaster, grenades, and rockets are projectiles8. Damage is mostly fixed. The rocket’s direct hit rolls 100 to 119, and its splash falls off by half a unit of damage per unit of distance9. A player who hits a monster that hasn’t noticed anyone does double damage, splash excepted10.
A player’s own splash does half damage, but the push from it is more than three times as strong, a “rocket jump hack” in the code’s comment. A rocket under a standing player’s feet adds about 424 units/s upward (computed)11. Weapon pickups add to an inventory that keeps everything taken12.
#Monsters and skill
Hurt monsters turn on each other under narrower rules than Quake’s: only a different class that walks, flies, or swims as they do, and never the tanks or the bosses; failing that, a monster joins in on whoever its attacker was after13. In the four monsters read (soldier, infantry, gunner, tank), a flinch is mostly a cooldown, at most one pain animation every 3 seconds, rather than a roll like Doom’s pain chance14. Only the tank rolls, for light hits, and a soldier knocked into the air can switch flinches within the cooldown. Nightmare drops the animations and stretches the cooldown to 5 seconds15. Outside deathmatch, Easy halves the damage a player takes, and each skill level removes the entities a map flags against it16.
Mechanics
| Mechanic | Kind | How |
|---|---|---|
| Running | Momentum | acceleration 10 against a friction of 6 settles a run at exactly the 300 cap, the numbers Quake III kept |
| Strafe-running | Angled air strafe | on the ground the 300 cap takes away a strafe's gain at a run; in the air the push is weak but capped only at the full wish speed, unless a deathmatch server switches on Quake's rule |
| Bunny hopping | Frictionless | the jump is checked before friction, so a hop on the first ground step keeps its speed, but a hard landing can lock the jump for 18 or 25 timer units |
| Jump | One speed | adds 270 units/s upward, at least 270 in all, and only after the key has been released since the last jump |
| Air control | Momentum | the move keys push against the speed carried at a tenth of the ground acceleration, up to the full wish speed along the wish direction |
| Hitscan | Hitscan beside projectiles | bullets, pellets, and the railgun trace a line when fired, the railgun through every body in its path; the blaster, grenades, and rockets fly |
| Damage formula | Base plus a roll | fixed amounts, except the rocket's 100 plus a roll of 0 to 19; a player's non-splash hit on a monster that hasn't noticed anyone does double |
| Rocket jumping | Boosted self-push | a player's own splash does half damage but pushes at 1600 over mass, not 500, commented as the rocket jump hack |
| Difficulty level | Damage halved | outside deathmatch, Easy halves the damage a player takes and each skill removes the entities flagged against it |
| Pain chance | Cooldown | in the four monsters read, a 3-second cooldown rather than a roll, 5 seconds with no pain animations on Nightmare; the tank adds a roll for light hits |
| Monster infighting | Same kind | a hurt monster turns on another of a different class that moves the same way, except the tanks and bosses, and otherwise joins its attacker's target |
| Weapon pickup | Inventory | every weapon taken stays in the inventory with its ammo; the player switches only to a new one, and in deathmatch only from the blaster |
Every claim about this game, by concept (14)
References
- ^ abid Software's GPL source release of Quake II calls itself "the complete source code for Quake 2, version 3.19", and
qcommon.hsetsVERSIONto 3.19, so the code read here is the 3.19 point release rather than the 1997 retail build.Codeid-Software/Quake-2How we know
2This is the complete source code for Quake 2, version 3.19, buildable withreadme.txt line 2 at 372afde · upstream ↗ 26#define VERSION 3.19qcommon/qcommon.h line 26 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.
- ^On the ground, Quake II's
PmoverunsPM_Friction, which lowers the speed by step time × 6 (pm_friction) × the larger of the speed and 100 (pm_stopspeed) on any ground that isn't slick, and thenPM_Acceleratewithpm_accelerate10: if the speed along the wish direction is under the wish speed, it adds 10 × step time × wish speed, no more than the shortfall. The wish velocity's length is capped at 300 (pm_maxspeed), or 100 ducked. These are the numbers Quake III keeps (friction 6, acceleration 10), where Quake's friction is 4. Computed, not measured: since 10 outpaces 6, a run settles at exactly 300 units/s. The movement constants are C globals, not cvars.Codeid-Software/Quake-2 · id-Software/Quake-III-Arena · id-Software/QuakeHow we know
53float pm_stopspeed = 100; 54float pm_maxspeed = 300; 55float pm_duckspeed = 100; 56float pm_accelerate = 10; ⋮ 59float pm_friction = 6;
qcommon/pmove.c lines 53–59 at 372afde · upstream ↗ 1335 PM_CheckJump (); ⋮ 1337 PM_Friction ();
qcommon/pmove.c lines 1335–1337 at 372afde · upstream ↗ 364// apply ground friction 365 if ((pm->groundentity && pml.groundsurface && !(pml.groundsurface->flags & SURF_SLICK) ) || (pml.ladder) ) ⋮ 367 friction = pm_friction; 368 control = speed < pm_stopspeed ? pm_stopspeed : speed; 369 drop += control*friction*pml.frametime;
qcommon/pmove.c lines 364–370 at 372afde · upstream ↗ 397void PM_Accelerate (vec3_t wishdir, float wishspeed, float accel) ⋮ 402 currentspeed = DotProduct (pml.velocity, wishdir); 403 addspeed = wishspeed - currentspeed; 404 if (addspeed <= 0) 405 return; 406 accelspeed = accel*pml.frametime*wishspeed; 407 if (accelspeed > addspeed) 408 accelspeed = addspeed;
qcommon/pmove.c lines 397–411 at 372afde · upstream ↗ 607 maxspeed = (pm->s.pm_flags & PMF_DUCKED) ? pm_duckspeed : pm_maxspeed; ⋮ 609 if (wishspeed > maxspeed) ⋮ 611 VectorScale (wishvel, maxspeed/wishspeed, wishvel); 612 wishspeed = maxspeed;
qcommon/pmove.c lines 607–613 at 372afde · upstream ↗ 635 else if ( pm->groundentity ) 636 { // walking on ground 637 pml.velocity[2] = 0; //!!! this is before the accel 638 PM_Accelerate (wishdir, wishspeed, pm_accelerate);
qcommon/pmove.c lines 635–638 at 372afde · upstream ↗ 39float pm_accelerate = 10.0f;code/game/bg_pmove.c line 39 at dbe4ddb · upstream ↗ 44float pm_friction = 6.0f;code/game/bg_pmove.c line 44 at dbe4ddb · upstream ↗ 42cvar_t sv_friction = {"sv_friction","4",false,true};WinQuake/sv_phys.c line 42 at bf4ac42 · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-22.
Checked by a second AI model, 2026-10-22.
- ^Quake II's keyboard moves are
cl_forwardspeed,cl_sidespeed, andcl_upspeed200, all doubled while running, which the speed key toggles: withcl_run0 holding the key runs, and withcl_run1 holding it walks. The movement code builds the wish velocity from the view's forward and right vectors, with the pitch first divided by 3, then zeroes its vertical part and caps its length at 300. Computed from these, with the view level (pitch 0): walking forward wishes 200 and walking forward with a strafe about 283, but a run wishes 400 forward and about 566 on the diagonal, both cut to 300, so a strafe adds nothing to a run on the ground. Looking up or down shortens the forward part before the cap.Codeid-Software/Quake-2revisedHow we know
1441 cl_upspeed = Cvar_Get ("cl_upspeed", "200", 0); 1442 cl_forwardspeed = Cvar_Get ("cl_forwardspeed", "200", 0); 1443 cl_sidespeed = Cvar_Get ("cl_sidespeed", "200", 0); ⋮ 1448 cl_run = Cvar_Get ("cl_run", "0", CVAR_ARCHIVE);
client/cl_main.c lines 1441–1448 at 372afde · upstream ↗ 289 cmd->sidemove += cl_sidespeed->value * CL_KeyState (&in_moveright); ⋮ 297 cmd->forwardmove += cl_forwardspeed->value * CL_KeyState (&in_forward); ⋮ 304 if ( (in_speed.state & 1) ^ (int)(cl_run->value) ) ⋮ 306 cmd->forwardmove *= 2; 307 cmd->sidemove *= 2; 308 cmd->upmove *= 2;
client/cl_input.c lines 289–309 at 372afde · upstream ↗ 1344 VectorCopy(pm->viewangles, angles); 1345 if (angles[PITCH] > 180) 1346 angles[PITCH] = angles[PITCH] - 360; 1347 angles[PITCH] /= 3; ⋮ 1349 AngleVectors (angles, pml.forward, pml.right, pml.up);
qcommon/pmove.c lines 1344–1349 at 372afde · upstream ↗ 596 wishvel[i] = pml.forward[i]*fmove + pml.right[i]*smove; 597 wishvel[2] = 0; ⋮ 602 wishspeed = VectorNormalize(wishdir); ⋮ 607 maxspeed = (pm->s.pm_flags & PMF_DUCKED) ? pm_duckspeed : pm_maxspeed; ⋮ 609 if (wishspeed > maxspeed)
qcommon/pmove.c lines 595–613 at 372afde · 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 air, Quake II's
PM_AirMovecalls the ground'sPM_Acceleratewith an acceleration of 1 whilepm_airaccelerateis 0: the push is 1 × step time × wish speed (up to 300), and it stops only when the velocity's component along the wish direction reaches the full wish speed. Quake III keeps this rule, withpm_airaccelerate1 and aPM_Acceleratecommented "q2 style". In deathmatch the server copies its latched cvarsv_airaccelerate(default 0) intopm_airaccelerate, and any nonzero value switches toPM_AirAccelerate, which is Quake's air rule: the wish speed capped at 30 for the shortfall, the push from the uncapped wish speed ×pm_accelerate(10) × step time. The server's movement code uses the value only as a nonzero test, so it acts as an on-off switch; outside deathmatch it is forced to 0. The server also sends the value to clients as theCS_AIRACCELconfig string, for their prediction, in code not read here.Codeid-Software/Quake-2 · id-Software/Quake-III-Arena · id-Software/QuakerevisedHow we know
57float pm_airaccelerate = 0;qcommon/pmove.c line 57 at 372afde · upstream ↗ 652 else 653 { // not on ground, so little effect on velocity 654 if (pm_airaccelerate) 655 PM_AirAccelerate (wishdir, wishspeed, pm_accelerate); 656 else 657 PM_Accelerate (wishdir, wishspeed, 1);
qcommon/pmove.c lines 652–657 at 372afde · upstream ↗ 414void PM_AirAccelerate (vec3_t wishdir, float wishspeed, float accel) ⋮ 419 if (wishspd > 30) 420 wishspd = 30; 421 currentspeed = DotProduct (pml.velocity, wishdir); 422 addspeed = wishspd - currentspeed; 423 if (addspeed <= 0) 424 return; 425 accelspeed = accel * wishspeed * pml.frametime;
qcommon/pmove.c lines 414–427 at 372afde · upstream ↗ 974 sv_airaccelerate = Cvar_Get("sv_airaccelerate", "0", CVAR_LATCH);server/sv_main.c line 974 at 372afde · upstream ↗ 196 if (Cvar_VariableValue ("deathmatch")) ⋮ 198 sprintf(sv.configstrings[CS_AIRACCEL], "%g", sv_airaccelerate->value); 199 pm_airaccelerate = sv_airaccelerate->value; ⋮ 201 else ⋮ 203 strcpy(sv.configstrings[CS_AIRACCEL], "0"); 204 pm_airaccelerate = 0;
server/sv_init.c lines 196–205 at 372afde · upstream ↗ 40float pm_airaccelerate = 1.0f;code/game/bg_pmove.c line 40 at dbe4ddb · upstream ↗ 240static void PM_Accelerate( vec3_t wishdir, float wishspeed, float accel ) { 241#if 1 242 // q2 style
code/game/bg_pmove.c lines 240–242 at dbe4ddb · upstream ↗ 637 PM_Accelerate (wishdir, wishspeed, pm_airaccelerate);code/game/bg_pmove.c line 637 at dbe4ddb · upstream ↗ 212 wishspd = VectorNormalize (wishveloc); 213 if (wishspd > 30) 214 wishspd = 30; 215 currentspeed = DotProduct (velocity, wishveloc); 216 addspeed = wishspd - currentspeed; ⋮ 220 accelspeed = sv_accelerate.value*wishspeed * host_frametime;
WinQuake/sv_user.c lines 212–220 at bf4ac42 · 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.
- ^ abQuake II's
PM_CheckJump, whichPmoveruns before friction, does nothing while the landing timer flag is set; otherwise it needs an up move of at least 10 and the jump-held flag clear, which only a command with the key released clears, so holding the key never jumps twice. On ground it adds 270 to the vertical velocity, raising it to at least 270, and clears the ground entity; in water at level 2 or more it sets an upward swim speed instead (100 in water, 80 in slime, 50 in lava). Quake's QuakeC jump and QuakeWorld's movement code also add 270. Gravity issv_gravity, 800.Codeid-Software/Quake-2 · id-Software/Quake-Tools · id-Software/QuakeHow we know
778void PM_CheckJump (void) ⋮ 780 if (pm->s.pm_flags & PMF_TIME_LAND) 781 { // hasn't been long enough since landing to jump again 782 return; ⋮ 785 if (pm->cmd.upmove < 10) 786 { // not holding jump 787 pm->s.pm_flags &= ~PMF_JUMP_HELD; 788 return; ⋮ 791 // must wait for jump to be released 792 if (pm->s.pm_flags & PMF_JUMP_HELD) 793 return; ⋮ 796 return; ⋮
qcommon/pmove.c lines 778–822 at 372afde · upstream ↗ 164 sv_gravity = gi.cvar ("sv_gravity", "800", 0);game/g_save.c line 164 at 372afde · upstream ↗ 811 self.velocity_z = self.velocity_z + 270;qcc/v101qc/client.qc line 811 at c0d1b91 · upstream ↗ 681 pmove.velocity[2] += 270;QW/client/pmove.c line 681 at bf4ac42 · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-22.
Checked by a second AI model, 2026-10-22.
- ^Quake III's jump is in the shared movement code:
PM_CheckJump, called fromPM_WalkMove(so only on walkable ground), needs an up move of at least 10 and the jump-held flag clear, whichPmoveSingleclears only on a step whose up move is under 10, so holding the key never jumps twice; it then sets the vertical velocity toJUMP_VELOCITY(270), where Quake's QuakeC adds 270 to it (quake/jump). Gravity isg_gravity, 800, as in Quake. A landing from a fall faster than 200 units/s sets a land timer of 250 ms that a comment says is to "not allow another jump for a little while", butPM_CheckJumpnever tests it.Codeid-Software/Quake-III-Arena · id-Software/Quake-ToolsHow we know
29#define JUMP_VELOCITY 270code/game/bg_local.h line 29 at dbe4ddb · upstream ↗ 359static qboolean PM_CheckJump( void ) { 360 if ( pm->ps->pm_flags & PMF_RESPAWNED ) { 361 return qfalse; // don't allow jump until all buttons are up ⋮ 364 if ( pm->cmd.upmove < 10 ) { 365 // not holding jump 366 return qfalse; ⋮ 370 if ( pm->ps->pm_flags & PMF_JUMP_HELD ) { ⋮ 373 return qfalse; ⋮ 376 pml.groundPlane = qfalse; // jumping away 377 pml.walking = qfalse; 378 pm->ps->pm_flags |= PMF_JUMP_HELD; ⋮ 380 pm->ps->groundEntityNum = ENTITYNUM_NONE; ⋮
code/game/bg_pmove.c lines 359–393 at dbe4ddb · upstream ↗ 1916 if ( pm->cmd.upmove < 10 ) { 1917 // not holding jump 1918 pm->ps->pm_flags &= ~PMF_JUMP_HELD;
code/game/bg_pmove.c lines 1916–1918 at dbe4ddb · upstream ↗ 1988 } else if ( pml.walking ) { 1989 // walking on ground 1990 PM_WalkMove();
code/game/bg_pmove.c lines 1988–1990 at dbe4ddb · upstream ↗ 710 if ( PM_CheckJump () ) {code/game/bg_pmove.c line 710 at dbe4ddb · upstream ↗ 1183 // don't do landing time if we were just going down a slope 1184 if ( pml.previous_velocity[2] < -200 ) { 1185 // don't allow another jump for a little while 1186 pm->ps->pm_flags |= PMF_TIME_LAND; 1187 pm->ps->pm_time = 250;
code/game/bg_pmove.c lines 1183–1187 at dbe4ddb · upstream ↗ 143 { &g_gravity, "g_gravity", "800", 0, 0, qtrue },code/game/g_main.c line 143 at dbe4ddb · upstream ↗ 840 client->ps.gravity = g_gravity.value;code/game/g_active.c line 840 at dbe4ddb · upstream ↗ 811 self.velocity_z = self.velocity_z + 270;qcc/v101qc/client.qc line 811 at c0d1b91 · upstream ↗ Checked against the evidence by its author, an AI model, 2026-10-22.
Checked by a second AI model, 2026-10-22.
- ^Quake II checks the jump before friction:
PmovecallsPM_CheckJump, which clears the ground entity, and thenPM_Friction, whose ground friction needs a ground entity, so a hop on the first command that starts on the ground loses no speed to friction. QuakeWorld'sPlayerMovehas the same order:JumpButtonclearsongroundbeforePM_Friction, whose ground friction needs it. Two conditions apply. The jump key must be released and pressed again (the jump claim). And when the position check first finds ground with the vertical speed below −200, it sets a landing timer of 18 units, or 25 below −400, that blocks the jump until it runs out; each command lowers it by its length in ms divided by 8, rounded down and at least 1, so 18 units last 144 ms at 8 or 16 ms commands and 180 ms at 10 ms (computed). By the atlas's reading the slide move clips the velocity along a floor it runs into, and the stepping move keeps that clipped vertical speed, so a landing step that reaches the floor would not set the timer; how often a hop's landing sets it has not been measured.Codeid-Software/Quake-2 · id-Software/QuakerevisedHow we know
1333 else ⋮ 1335 PM_CheckJump (); ⋮ 1337 PM_Friction ();
qcommon/pmove.c lines 1333–1337 at 372afde · upstream ↗ 819 pm->groundentity = NULL;qcommon/pmove.c line 819 at 372afde · upstream ↗ 365 if ((pm->groundentity && pml.groundsurface && !(pml.groundsurface->flags & SURF_SLICK) ) || (pml.ladder) )qcommon/pmove.c line 365 at 372afde · upstream ↗ 714 if (! (pm->s.pm_flags & PMF_ON_GROUND) ) 715 { // just hit the ground 716 pm->s.pm_flags |= PMF_ON_GROUND; 717 // don't do landing time if we were just going down a slope 718 if (pml.velocity[2] < -200) ⋮ 720 pm->s.pm_flags |= PMF_TIME_LAND; 721 // don't allow another jump for a little while 722 if (pml.velocity[2] < -400) 723 pm->s.pm_time = 25; 724 else 725 pm->s.pm_time = 18;
qcommon/pmove.c lines 714–726 at 372afde · upstream ↗ 1302 // drop timing counter 1303 if (pm->s.pm_time) ⋮ 1305 int msec; ⋮ 1307 msec = pm->cmd.msec >> 3; 1308 if (!msec) 1309 msec = 1; 1310 if ( msec >= pm->s.pm_time) ⋮ 1312 pm->s.pm_flags &= ~(PMF_TIME_WATERJUMP | PMF_TIME_LAND | PMF_TIME_TELEPORT); 1313 pm->s.pm_time = 0; ⋮ 1315 else 1316 pm->s.pm_time -= msec;
qcommon/pmove.c lines 1302–1316 at 372afde · upstream ↗ 685 point[2] = pml.origin[2] - 0.25; ⋮ 693 trace = pm->trace (pml.origin, pm->mins, pm->maxs, point);
qcommon/pmove.c lines 683–693 at 372afde · upstream ↗ 219 for (i=0 ; i<numplanes ; i++) ⋮ 221 PM_ClipVelocity (pml.velocity, planes[i], pml.velocity, 1.01);
qcommon/pmove.c lines 219–221 at 372afde · upstream ↗ 332 //!! Special case 333 // if we were walking along a plane, then we need to copy the Z over 334 pml.velocity[2] = down_v[2];
qcommon/pmove.c lines 332–334 at 372afde · upstream ↗ 889 if (pmove.cmd.buttons & BUTTON_JUMP) 890 JumpButton (); 891 else 892 pmove.oldbuttons &= ~BUTTON_JUMP; ⋮ 894 PM_Friction ();
QW/client/pmove.c lines 889–894 at bf4ac42 · upstream ↗ 680 onground = -1;QW/client/pmove.c line 680 at bf4ac42 · upstream ↗ 366 else if (onground != -1) // apply ground friction ⋮ 368 control = speed < movevars.stopspeed ? movevars.stopspeed : speed; 369 drop += control*friction*frametime;
QW/client/pmove.c lines 366–369 at bf4ac42 · 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.
- ^Quake II's bullets, pellets, and railgun are hitscan:
fire_lead, whichfire_bullet(the machine gun and chaingun) andfire_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, andfire_railtraces 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-2How 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.
- ^A Quake II rocket's direct hit does 100 +
(int)(random() * 20.0)damage, 100 to 119, the roll made when it is fired; the explosion then does radius damage to everything else within 120 units, the rocket's 120 less half the distance from the explosion to the center of the target's box, and the target hit directly takes no splash. A quad damage multiplies both by 4. The railgun's damage is a constant, 150 in single player and 100 in deathmatch.Codeid-Software/Quake-2How we know
767 damage = 100 + (int)(random() * 20.0); 768 radius_damage = 120; 769 damage_radius = 120; 770 if (is_quad) ⋮ 772 damage *= 4; 773 radius_damage *= 4;
game/p_weapon.c lines 767–774 at 372afde · upstream ↗ 589 if (other->takedamage) ⋮ 591 T_Damage (other, ent, ent->owner, ent->velocity, ent->s.origin, plane->normal, ent->dmg, 0, 0, MOD_ROCKET); ⋮ 607 T_RadiusDamage(ent, ent->owner, ent->radius_dmg, other, ent->dmg_radius, MOD_R_SPLASH);
game/g_weapon.c lines 589–607 at 372afde · upstream ↗ 554 while ((ent = findradius(ent, inflictor->s.origin, radius)) != NULL) ⋮ 556 if (ent == ignore) 557 continue; ⋮ 559 continue; ⋮ 561 VectorAdd (ent->mins, ent->maxs, v); 562 VectorMA (ent->s.origin, 0.5, v, v); 563 VectorSubtract (inflictor->s.origin, v, v); 564 points = damage - 0.5 * VectorLength (v); ⋮ 572 T_Damage (ent, inflictor, attacker, dir, inflictor->s.origin, vec3_origin, (int)points, (int)points, DAMAGE_RADIUS, mod);
game/g_combat.c lines 554–572 at 372afde · upstream ↗ 94 eorg[j] = org[j] - (from->s.origin[j] + (from->mins[j] + from->maxs[j])*0.5); 95 if (VectorLength(eorg) > rad)
game/g_utils.c lines 94–95 at 372afde · upstream ↗ 1310 if (deathmatch->value) 1311 { // normal damage is too extreme in dm 1312 damage = 100; 1313 kick = 200; ⋮ 1315 else ⋮ 1317 damage = 150;
game/p_weapon.c lines 1310–1318 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.
- ^Quake II's
T_Damagedoubles the damage a player does to a living monster that has no enemy yet, commented "bonus damage for suprising a monster", unless the damage is radius damage; the doubling comes after Easy's halving, which applies only to players.Codeid-Software/Quake-2How we know
421// bonus damage for suprising a monster 422 if (!(dflags & DAMAGE_RADIUS) && (targ->svflags & SVF_MONSTER) && (attacker->client) && (!targ->enemy) && (targ->health > 0)) 423 damage *= 2;
game/g_combat.c lines 421–423 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.
- ^Quake II's radius damage halves the points for the entity that caused it, so a player takes half splash from their own rocket or grenade, and
T_Damagepushes a player who hurt themselves with 1600 × knockback / mass instead of 500 ×, commented "the rocket jump hack...". The player's mass is 200, so a self-inflicted push is 8 units/s per point against 2.5 from anyone else, and the radius damage passes its points as the knockback. Computed: a rocket exploding on the floor directly under a standing player, 28 units below the center of the player's box, does 120 − 14 = 106, halved to 53, which adds 424 units/s upward, against a jump's 270.Codeid-Software/Quake-2How we know
564 points = damage - 0.5 * VectorLength (v); 565 if (ent == attacker) 566 points = points * 0.5; ⋮ 572 T_Damage (ent, inflictor, attacker, dir, inflictor->s.origin, vec3_origin, (int)points, (int)points, DAMAGE_RADIUS, mod);
game/g_combat.c lines 564–572 at 372afde · upstream ↗ 436 if (targ->mass < 50) 437 mass = 50; 438 else 439 mass = targ->mass; ⋮ 441 if (targ->client && attacker == targ) 442 VectorScale (dir, 1600.0 * (float)knockback / mass, kvel); // the rocket jump hack... 443 else 444 VectorScale (dir, 500.0 * (float)knockback / mass, kvel); ⋮ 446 VectorAdd (targ->velocity, kvel, targ->velocity);
game/g_combat.c lines 436–446 at 372afde · upstream ↗ 1099 vec3_t mins = {-16, -16, -24}; 1100 vec3_t maxs = {16, 16, 32};
game/p_client.c lines 1099–1100 at 372afde · upstream ↗ 1170 ent->mass = 200;game/p_client.c line 1170 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.
- ^A Quake II weapon pickup adds the weapon to the player's inventory, which keeps every weapon taken, and gives that weapon's ammo with it unless the item carries the
DROPPED_ITEMflag. The player switches to it only when it is the first of its kind carried, and in deathmatch only while the blaster is in hand. In coop, or in deathmatch with weapons-stay set, a player who already has the weapon can't take a placed one. A placed weapon picked up in deathmatch stays where it is with weapons-stay set and otherwise respawns after 30 seconds, and in coop it stays; a weapon flaggedDROPPED_PLAYER_ITEM, which a dying player's weapon is, gives its ammo but neither stays nor respawns.Codeid-Software/Quake-2revisedHow we know
125 if ( ( ((int)(dmflags->value) & DF_WEAPONS_STAY) || coop->value) 126 && other->client->pers.inventory[index]) ⋮ 128 if (!(ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM) ) ) 129 return false; // leave the weapon for others to pickup ⋮ 132 other->client->pers.inventory[index]++; ⋮ 134 if (!(ent->spawnflags & DROPPED_ITEM) ) ⋮ 136 // give them some ammo with it 137 ammo = FindItem (ent->item->ammo); ⋮ 140 else 141 Add_Ammo (other, ammo, ammo->quantity); ⋮ 143 if (! (ent->spawnflags & DROPPED_PLAYER_ITEM) ) ⋮ 145 if (deathmatch->value) ⋮
game/p_weapon.c lines 125–160 at 372afde · upstream ↗ 814 if (!((coop->value) && (ent->item->flags & IT_STAY_COOP)) || (ent->spawnflags & (DROPPED_ITEM | DROPPED_PLAYER_ITEM))) ⋮ 816 if (ent->flags & FL_RESPAWN) 817 ent->flags &= ~FL_RESPAWN; 818 else 819 G_FreeEdict (ent);
game/g_items.c lines 814–819 at 372afde · upstream ↗ 439 drop = Drop_Item (self, item); ⋮ 441 drop->spawnflags = DROPPED_PLAYER_ITEM;
game/p_client.c lines 439–441 at 372afde · 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.
- ^When a monster survives damage from another monster, Quake II's
M_ReactToDamagemakes the attacker its enemy if both move the same way (walking, flying, or swimming), their class names differ, and the attacker isn't a tank, super tank, Makron, or Jorg; failing that, it fights back only if the attacker was targeting it, and otherwise takes on the attacker's enemy, unless that enemy is itself. Nothing happens when it hurt itself or the attacker is already its enemy. A player it was chasing is kept as the old enemy. Quake's rule, in the version 1.01 QuakeC of id's Quake-Tools release, is simpler: a monster turns on any attacker of a different class, and soldiers turn on each other too.Codeid-Software/Quake-2 · id-Software/Quake-ToolsrevisedHow we know
295void M_ReactToDamage (edict_t *targ, edict_t *attacker) ⋮ 297 if (!(attacker->client) && !(attacker->svflags & SVF_MONSTER)) 298 return; ⋮ 300 if (attacker == targ || attacker == targ->enemy) 301 return;
game/g_combat.c lines 295–301 at 372afde · upstream ↗ 335 // it's the same base (walk/swim/fly) type and a different classname and it's not a tank 336 // (they spray too much), get mad at them 337 if (((targ->flags & (FL_FLY|FL_SWIM)) == (attacker->flags & (FL_FLY|FL_SWIM))) && 338 (strcmp (targ->classname, attacker->classname) != 0) && 339 (strcmp(attacker->classname, "monster_tank") != 0) && 340 (strcmp(attacker->classname, "monster_supertank") != 0) && 341 (strcmp(attacker->classname, "monster_makron") != 0) && 342 (strcmp(attacker->classname, "monster_jorg") != 0) ) ⋮ 344 if (targ->enemy && targ->enemy->client) 345 targ->oldenemy = targ->enemy; 346 targ->enemy = attacker; ⋮ 350 // if they *meant* to shoot us, then shoot back ⋮
game/g_combat.c lines 335–367 at 372afde · upstream ↗ 506 if (targ->svflags & SVF_MONSTER) ⋮ 508 M_ReactToDamage (targ, attacker);
game/g_combat.c lines 506–508 at 372afde · upstream ↗ 198 if ( (self.flags & FL_MONSTER) && attacker != world) ⋮ 200 // get mad unless of the same class (except for soldiers) 201 if (self != attacker && attacker != self.enemy) ⋮ 203 if ( (self.classname != attacker.classname) 204 || (self.classname == "monster_army" ) ) ⋮ 206 if (self.enemy.classname == "player") 207 self.oldenemy = self.enemy; 208 self.enemy = attacker;
qcc/v101qc/combat.qc lines 198–210 at c0d1b91 · 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.
- ^A Doom monster that survives a hit enters its pain state when
P_Random()is below its pain chance, out of 256, unless it is a lost soul in mid-charge; the table gives the zombieman and imp 200, the demon 180, the shotgun guy 170, the cacodemon 128, the Baron of Hell 50, the Spider Mastermind 40, and the Cyberdemon 20, and the lost soul's 256 always succeeds. The pain setsMF_JUSTHIT, so the monster's next missile-range check passes at once if it can see its target.Codeid-Software/DOOMrevisedHow we know
887 target->health -= damage; 888 if (target->health <= 0) ⋮ 894 if ( (P_Random () < target->info->painchance) 895 && !(target->flags&MF_SKULLFLY) ) ⋮ 897 target->flags |= MF_JUSTHIT; // fight back! ⋮ 899 P_SetMobjState (target, target->info->painstate);
linuxdoom-1.10/p_inter.c lines 887–900 at a77dfb9 · upstream ↗ 197boolean P_CheckMissileRange (mobj_t* actor) ⋮ 201 if (! P_CheckSight (actor, actor->target) ) 202 return false; ⋮ 204 if ( actor->flags & MF_JUSTHIT ) ⋮ 206 // the target just hit the enemy, 207 // so fight back! 208 actor->flags &= ~MF_JUSTHIT; 209 return true;
linuxdoom-1.10/p_enemy.c lines 197–209 at a77dfb9 · upstream ↗ 1134 { // MT_POSSESSED ⋮ 1143 200, // painchance
linuxdoom-1.10/info.c lines 1134–1143 at a77dfb9 · upstream ↗ 1160 { // MT_SHOTGUY ⋮ 1169 170, // painchance
linuxdoom-1.10/info.c lines 1160–1169 at a77dfb9 · upstream ↗ 1394 { // MT_TROOP ⋮ 1403 200, // painchance
linuxdoom-1.10/info.c lines 1394–1403 at a77dfb9 · upstream ↗ 1420 { // MT_SERGEANT ⋮ 1429 180, // painchance
linuxdoom-1.10/info.c lines 1420–1429 at a77dfb9 · upstream ↗ 1472 { // MT_HEAD ⋮ 1481 128, // painchance
linuxdoom-1.10/info.c lines 1472–1481 at a77dfb9 · upstream ↗ 1498 { // MT_BRUISER ⋮ 1507 50, // painchance
linuxdoom-1.10/info.c lines 1498–1507 at a77dfb9 · upstream ↗ 1576 { // MT_SKULL ⋮ 1585 256, // painchance
linuxdoom-1.10/info.c lines 1576–1585 at a77dfb9 · upstream ↗ 1602 { // MT_SPIDER ⋮ 1611 40, // painchance
linuxdoom-1.10/info.c lines 1602–1611 at a77dfb9 · upstream ↗ 1654 { // MT_CYBORG ⋮ 1663 20, // painchance
linuxdoom-1.10/info.c lines 1654–1663 at a77dfb9 · upstream ↗ 341 {CC_ZOMBIE, MT_POSSESSED}, 342 {CC_SHOTGUN, MT_SHOTGUY}, ⋮ 344 {CC_IMP, MT_TROOP}, 345 {CC_DEMON, MT_SERGEANT}, 346 {CC_LOST, MT_SKULL}, 347 {CC_CACO, MT_HEAD}, ⋮ 349 {CC_BARON, MT_BRUISER}, ⋮ 355 {CC_SPIDER, MT_SPIDER}, 356 {CC_CYBER, MT_CYBORG},
linuxdoom-1.10/f_finale.c lines 341–356 at a77dfb9 · upstream ↗ 677#define CC_ZOMBIE "ZOMBIEMAN" 678#define CC_SHOTGUN "SHOTGUN GUY" ⋮ 680#define CC_IMP "IMP" 681#define CC_DEMON "DEMON" 682#define CC_LOST "LOST SOUL" 683#define CC_CACO "CACODEMON" ⋮ 685#define CC_BARON "BARON OF HELL" ⋮ 691#define CC_SPIDER "THE SPIDER MASTERMIND" 692#define CC_CYBER "THE CYBERDEMON"
linuxdoom-1.10/d_englsh.h lines 677–692 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 four Quake II monsters read, a flinch is a cooldown rather than a roll: the soldier's, infantry's, and gunner's pain functions play a pain animation unless one started within the last 3 seconds (
pain_debounce_time), choosing which at random (soldier, infantry) or by the damage (gunner), and on Nightmare (skill 3) they return before the animation. The soldier has an exception both ways: knocked upward faster than 100, it switches to a fourth pain animation even within the cooldown (if already in one of the other three) and even on Nightmare. On Nightmare the cooldown is 5 seconds, not 3:T_Damageresets it to 5 after each hit that calls the pain function. The tank adds a roll: it ignores hits of 10 or less, and flinches from 11 to 30 only whenrandom()is at most 0.2; on hard and Nightmare it doesn't flinch during two of its attacks.Codeid-Software/Quake-2revisedHow we know
417 if (level.time < self->pain_debounce_time) ⋮ 419 if ((self->velocity[2] > 100) && ( (self->monsterinfo.currentmove == &soldier_move_pain1) || (self->monsterinfo.currentmove == &soldier_move_pain2) || (self->monsterinfo.currentmove == &soldier_move_pain3))) 420 self->monsterinfo.currentmove = &soldier_move_pain4; 421 return; ⋮ 424 self->pain_debounce_time = level.time + 3; ⋮ 434 if (self->velocity[2] > 100) ⋮ 436 self->monsterinfo.currentmove = &soldier_move_pain4; 437 return; ⋮ 440 if (skill->value == 3) 441 return; // no pain anims in nightmare ⋮ 443 r = random(); ⋮ 445 if (r < 0.33) ⋮
game/m_soldier.c lines 417–450 at 372afde · upstream ↗ 222 if (level.time < self->pain_debounce_time) 223 return; ⋮ 225 self->pain_debounce_time = level.time + 3; ⋮ 227 if (skill->value == 3) 228 return; // no pain anims in nightmare ⋮ 230 n = rand() % 2;
game/m_infantry.c lines 222–230 at 372afde · upstream ↗ 288 if (level.time < self->pain_debounce_time) 289 return; ⋮ 291 self->pain_debounce_time = level.time + 3; ⋮ 298 if (skill->value == 3) 299 return; // no pain anims in nightmare ⋮ 301 if (damage <= 10) 302 self->monsterinfo.currentmove = &gunner_move_pain3;
game/m_gunner.c lines 288–302 at 372afde · upstream ↗ 293 if (damage <= 10) 294 return; ⋮ 296 if (level.time < self->pain_debounce_time) 297 return; ⋮ 299 if (damage <= 30) 300 if (random() > 0.2) 301 return; ⋮ 303 // If hard or nightmare, don't go into pain while attacking 304 if ( skill->value >= 2) ⋮ 307 return; ⋮ 309 return; ⋮ 312 self->pain_debounce_time = level.time + 3;
game/m_tank.c lines 293–312 at 372afde · upstream ↗ 506 if (targ->svflags & SVF_MONSTER) ⋮ 508 M_ReactToDamage (targ, attacker); 509 if (!(targ->monsterinfo.aiflags & AI_DUCKED) && (take)) ⋮ 511 targ->pain (targ, attacker, knockback, take); 512 // nightmare mode monsters don't go into pain frames often 513 if (skill->value == 3) 514 targ->pain_debounce_time = level.time + 5;
game/g_combat.c lines 506–514 at 372afde · 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.
- ^On Easy (skill 0) outside deathmatch, Quake II's
T_Damagehalves the damage a player takes, to no less than 1; the knockback is worked out separately and isn't halved. Levels also pick their entities by skill: an entity flagged not-easy, not-medium, or not-hard is removed at load on that skill, with Nightmare (3) counted as hard.Codeid-Software/Quake-2How we know
404 // easy mode takes half damage 405 if (skill->value == 0 && deathmatch->value == 0 && targ->client) ⋮ 407 damage *= 0.5; 408 if (!damage) 409 damage = 1;
game/g_combat.c lines 404–410 at 372afde · upstream ↗ 428// figure momentum add ⋮ 444 VectorScale (dir, 500.0 * (float)knockback / mass, kvel);
game/g_combat.c lines 428–446 at 372afde · upstream ↗ 585 else ⋮ 587 if ( /* ((coop->value) && (ent->spawnflags & SPAWNFLAG_NOT_COOP)) || */ 588 ((skill->value == 0) && (ent->spawnflags & SPAWNFLAG_NOT_EASY)) || 589 ((skill->value == 1) && (ent->spawnflags & SPAWNFLAG_NOT_MEDIUM)) || 590 (((skill->value == 2) || (skill->value == 3)) && (ent->spawnflags & SPAWNFLAG_NOT_HARD)) ⋮ 593 G_FreeEdict (ent);
game/g_spawn.c lines 585–593 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.
Sources
Code
- Software, id. 2012. “Quake II: the GPL source release (id-Software/Quake-2, 3.19).” Commit 372afde. id-Software/Quake-2 @ 372afde · licence, all uses
- Software, id. 2012. “Quake III Arena: the GPL source release (id-Software/Quake-III-Arena, 1.32b).” Commit dbe4ddb. id-Software/Quake-III-Arena @ dbe4ddb · licence, all uses
- Software, id. 2012. “Quake: the engine source release (id-Software/Quake: WinQuake and QuakeWorld).” Commit bf4ac42. id-Software/Quake @ bf4ac42 · licence, all uses
- Software, id. 2012. “Quake: the QuakeC source and compiler (id-Software/Quake-Tools, qcc/v101qc).” Commit c0d1b91. id-Software/Quake-Tools @ c0d1b91 · licence, all uses
- Software, id. 2024. “DOOM: the Linux source release (id-Software/DOOM, linuxdoom-1.10).” Commit a77dfb9. id-Software/DOOM @ a77dfb9 · licence, all uses