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

A pain chance sets how often a hit makes an enemy flinch. Doom rolls once for each hit a monster survives, against a chance set for its type, so a zombieman flinches far more often than a Cyberdemon1. The flinch also lets the monster’s next ranged-attack check pass at once if it can see its target1. In the four Quake II monsters read, a hit plays a pain animation only once a 3-second cooldown has run out, and the tank adds a roll for light hits2.

  • Per-type rollDoom (1993). Each hit a monster survives flinches it with a chance out of 256, from the zombieman's 200 to the Cyberdemon's 20.1
  • CooldownQuake II (1997). 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.2

Games

GameYearHowNote
Doom1993Per-type rolleach hit a monster survives flinches it with a chance out of 256, from the zombieman's 200 to the Cyberdemon's 20
Quake II1997Cooldownin 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
Every claim about this mechanic, by game (2)

References

  1. ^ abcA 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 sets MF_JUSTHIT, so the monster's next missile-range check passes at once if it can see its target.Codeid-Software/DOOMrevised
    How 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 claim ledger

  2. ^ abIn 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_Damage resets 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 when random() is at most 0.2; on hard and Nightmare it doesn't flinch during two of its attacks.Codeid-Software/Quake-2revised
    How 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.

    In the claim ledger

Sources

Code