Ghost monster

From DoomWiki.org

This article is about bugs in Doom and Doom II. For information about the Heretic monster type, see Ghosts (Heretic).

A ghost monster is a monster altered through at least one of several known bugs to have unusual ghost-like properties. The most well known cause is a bug that occurs when the monster's corpse becomes a small pool of gore by being crushed under a door or ceiling and is then resurrected by an arch-vile. This happens frequently depending on the map layout, only requiring an arch-vile and a crushed common monster. This ghost monster looks normal and still behaves like before being killed, except that it retains some of the properties of the pool of gore. These properties allow it to move through walls to sectors that are equal or lower in height than the one it occupies. As far as combat is concerned, this ghost monster can only be harmed by splash damage, monster melee attacks, arch-vile attacks, and telefragging. It is possible to harm it by hitting a specific, very narrow area at the base of its axis with a regular projectile attack, although this is very difficult to achieve.

Some PWADs have used the ghost monster phenomenon as a feature, such as Dark Side of the Mind, The Waterfront, Requiem's MAP23: Hatred, Icarus: Alien Vanguard's MAP24: The Haunting, Unholy Realms's MAP06: Sewage Entrypoint, Hell Revealed's MAP26: Afterlife, Hell to Pay's MAP14: The Habitat Deck and MAP22: Vile Temple, and Plutonia 2's MAP32: Go 4 It.

As with many Doom engine bugs, this one is fixed in many source ports, including Boom; while Doomsday retains it fully, other ports, such as ZDoom, automatically enable it for maps widely known to utilize the bug, namely the above examples, but otherwise prevents the bug from occurring. In MBF, it depends on whether the comp_vile setting is enabled.

A significantly more rare cause leads to the Boom ghost monster bug, which as the name suggests can occur while playing on Boom, though it is also a bug in the original engine. When a lost soul attacks an idle arch-vile, causing the arch-vile to wake up and look for a corpse to resurrect on the very same tic that it was hit in, that corpse, if it has its collision blocked by the lost soul or something else, can be instantly raised through the joint effort from the lost soul and arch-vile. This ghost monster also walks around and attacks and is non-solid, but it has different properties from the more traditional type; it is truly dead because it has no health, making it immune to all forms of damage including splash damage, and it has regular collision with the world.

Since a Boom ghost monster is technically still a corpse, it can be destroyed and reduced to a normal pile of gore when caught under a door. It is possible to combine this bug with the traditional ghost monster bug; crushing the monster before letting the arch-vile and lost soul raise it will remove its collision entirely, making it walk through walls and immune to crushing as it fits snugly into a zero height sector.

It is possible for a lost soul to similarly become a ghost, though the conditions required do not exist in vanilla Doom, requiring modifications such as through DeHackEd. This happens when a lost soul attacks and is instantly killed by what it hits, such as a barrel patched to instantly explode on hit. This means that ghost lost souls can inadvertently be created while making DeHackEd patches that have nothing directly to do with lost souls.

Technical[edit]

Traditional ghost monsters[edit]

When a sector in motion encounters a corpse, that corpse is crushed:

p_map.c, PIT_ChangeSector

...
// crunch bodies to giblets
if (thing->health <= 0) {
    P_SetMobjState (thing, S_GIBS);
    thing->flags &= ~MF_SOLID;
    thing->height = 0;
    thing->radius = 0;
    return true; // keep checking
}
...

Though resurrection restores the removed solid flag, it does not restore the radius, and it fails to restore the height (as it tries to do so by multiplying the normally quartered height of a corpse), making collision with the monster still fail:

p_enemy.c, A_VileChase

...
P_SetMobjState (corpsehit,info->raisestate);
corpsehit->height <<= 2;
corpsehit->flags = info->flags;
corpsehit->health = info->spawnhealth;
corpsehit->target = NULL;
...

This means that all such pools of blood are potential ghosts if the original monster has a resurrection sequence (a set of sprite pointers usually containing the frames of the death of the monster in reverse). Most monsters have one, with the ones that cannot be resurrected by the arch-vile (lost soul, cyberdemon, spiderdemon and another arch-vile) being exceptions.

Boom fixes this bug by resetting the height and radius properly:

p_enemy.c (Boom), A_VileChase

...
corpsehit->height = info->height; // fix Ghost bug
corpsehit->radius = info->radius; // fix Ghost bug
...

Boom ghost monsters[edit]

When a monster is in motion, its intended location to make sure the move is valid and not blocked is checked using the p_map.c function P_CheckPosition. In this function, the global variable tmthing is set to this moving monster to be used by other functions called as part of this collision detection routine.

When the lost soul attacks, it throws itself forward (which sets the MF_SKULLFLY flag) and uses collision detection to land a hit. This is the code that gets run for when an attacking lost soul (tmthing) hits a target (thing):

p_map.c, PIT_CheckThing

// check for skulls slamming into things
if (tmthing->flags & MF_SKULLFLY) {
    damage = ((P_Random()%8)+1)*tmthing->info->damage;
    
    P_DamageMobj (thing, tmthing, tmthing, damage);
    
    tmthing->flags &= ~MF_SKULLFLY;
    tmthing->momx = tmthing->momy = tmthing->momz = 0;
    
    P_SetMobjState (tmthing, tmthing->info->spawnstate);
    
    return false; // stop moving
}

The call to P_DamageMobj seeks to deal damage to the monster hit by the lost soul, and perform whichever routines arise as a result of getting hit. This causes the following code to run:

p_inter.c, P_DamageMobj

...
if (
    (!target->threshold || target->type == MT_VILE) &&
    source && source != target && source->type != MT_VILE
) {
    // if not intent on another player,
    // chase after this one
    target->target = source;
    target->threshold = BASETHRESHOLD;
    if (target->state == &states[target->info->spawnstate] && target->info->seestate != S_NULL) {
        P_SetMobjState (target, target->info->seestate);
    }
}

When the monster is damaged in its default frame of its idle animation, it wakes up by having its state changed to its "see state", the same state that would get activated when it sees a player. When a new state is set, the routine associated with this state (for the "see state", the routine to chase the player) is immediately executed. For the arch-vile, this routine is A_VileChase.

The arch-vile uses this routine to check for nearby corpses to resurrect by calling PIT_VileCheck on all nearby things. When it finds a corpse, it checks that corpse's position to make sure there is enough clearance for its resurrection. If not, it is skipped.

p_enemy.c, PIT_VileCheck

...
corpsehit->momx = corpsehit->momy = 0;
corpsehit->height <<= 2;
check = P_CheckPosition (corpsehit, corpsehit->x, corpsehit->y);
corpsehit->height >>= 2;

if (!check) return true; // doesn't fit here
return false; // got one, so stop checking

Since there is a call to P_CheckPosition here, the global variable tmthing originally pointing to the lost soul gets overwritten with the corpse.

If the corpse is blocked, tmthing remains overwritten, and the arch-vile does not resurrect it. This deep sequence of calls returns back; PIT_VileCheck to A_VileChase to P_SetMobjState to P_DamageMobj, eventually reaching back to the lost soul collision code.

p_map.c, PIT_CheckThing

// check for skulls slamming into things
...
P_DamageMobj (thing, tmthing, tmthing, damage);

tmthing->flags &= ~MF_SKULLFLY;
tmthing->momx = tmthing->momy = tmthing->momz = 0;

P_SetMobjState (tmthing, tmthing->info->spawnstate);

Since tmthing has now been altered from being the lost soul that it is supposed to be, to be the corpse that the arch-vile failed to revive, it has its state set to the "spawn state", which is the default idle state, from which it can see the player and attack them. The monster now looks alive and walks and attacks as though it were alive, but it otherwise still has all the properties of a dead monster.

Ghost lost souls[edit]

Similarly to the Boom ghost monsters, this bug involves the lost soul attack code:

p_map.c, PIT_CheckThing

// check for skulls slamming into things
if (tmthing->flags & MF_SKULLFLY) {
    damage = ((P_Random()%8)+1)*tmthing->info->damage;
    
    P_DamageMobj (thing, tmthing, tmthing, damage);
    
    tmthing->flags &= ~MF_SKULLFLY;
    tmthing->momx = tmthing->momy = tmthing->momz = 0;
    
    P_SetMobjState (tmthing, tmthing->info->spawnstate);
    
    return false; // stop moving
}

The following focuses on a concrete example involving barrels modified with DeHackEd to instantly explode when destroyed (meaning its death state is set to the same state that executes explosion code, as opposed to the normal behaviour of having a slight delay before exploding), however this bug can be triggered using anything that can instantly deal damage after being hit. No such things exist in vanilla Doom, so this bug only happens when modifications to the game are made.

If a lost soul attacks a barrel, it will deal damage with the call to P_DamageMobj, and if the damage is sufficient to destroy it, the barrel will change to its death state and execute the associated code. This results in A_Explode being executed and damage applied to nearby things, such as the lost soul.

Once this is done, the call to P_DamageMobj is returned out of, leading back into the lost soul attack code, where the remaining lines are executed. The call to P_SetMobjState now gives the lost soul its spawn state. Supposing the lost soul took sufficient damage from the barrel explosion to die and have its death state set, that death state has now been overwritten by the spawn state. This results in a lost soul that died and immediately had its state set back to an alive state, retaining some of its death properties, such as not being shootable.

The following is the contents of a DeHackEd file that modifies barrels to instantly explode when destroyed. While using this modification to play Doom, if a lost soul attacks a barrel and takes sufficient damage from the resulting explosion, it will become a ghost monster.

BARREL.DEH

Patch File for DeHackEd v2.3

Doom version = 19
Patch format = 5


Thing 31 (Barrel)
Death frame = 811

Notes[edit]

  • The pain elemental specifically has a resurrection sequence, yet normally leaves no corpse to resurrect. If one is crushed to death or while dying, however, a pool of gore is generated. This means pain elementals can only be resurrected as ghosts.

Demo[edit]

See also[edit]

External links[edit]