Passive Hazards and Prizes

The passive hazards and passive prizes (together called passive actors) are game elements that the player can encounter and interact with, but which do not have any movement code. Passive hazards simply “exist”; the player will be hurt if they walk into one, but the hazard does not actively seek out or attack the player. Passive prizes can be picked up by the player for points or other benefits, but have no idle animation effects.

Appearance of all passive hazards and prizes in the game.

Passive actors can utilize the common actor movement functions, permitting hazards to be destroyed by explosions and for weighted prizes to fall down due to gravity. All passive actors have an implementation inside InteractPlayer() that specifies what should happen when the player and the actor touch each other.

Every passive actor uses ActFootSwitch() as its tick function. ActFootSwitch() begins with a test to differentiate passive actors from genuine Foot Switches; passive actors are treated as no-ops.

Data Fields

The data1data5 fields for each of these actors is initialized to zero and, in almost all cases, nothing reads or writes the data values at any time. The exception is data1 on the E1M11 Exit Monster. For this and only this actor, the value increments by one during each game tick in which the player’s sprite is touching the actor.

Initial Values

To keep the tables a manageable size, the actor tables have been split into “weighted” and “unweighted” (i.e., floating) groups.

Weighted

Actor Type
ACT_POD (137)
ACT_HORN (140)
ACT_ROOT (168)
Sprite Type
SPR_POD (137)
SPR_HORN (140)
SPR_ROOT (168)
X Shift00000000000000000000
Y Shift00000000000000000000
Force Activeyesyesyesyesyesyesyesyesyesyesyesyesyesyesyesyesyesyesyesyes
Stay Activenononononononononononononononononononono
Weightedyesyesyesyesyesyesyesyesyesyesyesyesyesyesyesyesyesyesyesyes
Acrophilenononononononononononononononononononono
Data 1not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)
Data 2not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)
Data 3not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)
Data 4not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)
Data 5not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)

Unweighted

Actor Type
Sprite Type
X Shift0000000-300000000000
Y Shift0001020022000010000
Force Activenononononononononoyesnonononononononono
Stay Activenonononononononononononononononononono
Weightednonononononononononononononononononono
Acrophilenonononononononononononononononononono
Data 1not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)
Data 2not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)
Data 3not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)
Data 4not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)
Data 5not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)not used (0)frame count (0)

Note: The differences in the “force active” flag do not matter since unweighted passive prizes do not change their behavior based on visibility.

Player Interaction

Inside the InteractPlayer() function, one of the following cases occurs every time a passive actor is touching the player’s sprite.

Floor Spikes (stationary) / Wall Spikes (pointing east, stationary) / Wall Spikes (pointing west, stationary)

Harmfulyes
Vulnerable (Pounces)no
Vulnerable (Explosions)yes
Explosions Required1
Explosion Points250
Name OriginCanonical; appears in the hint sheet text.
    case SPR_SPIKES_FLOOR:
    case SPR_SPIKES_FLOOR_RECIP: /* non-passive actor; discussed elsewhere */
    case SPR_SPIKES_E:
    case SPR_SPIKES_E_RECIP:     /* non-passive actor; discussed elsewhere */
    case SPR_SPIKES_W:
        if (act->frame > 1) return true;

        HurtPlayer();

        return false;

This responds to actors having the SPR_SPIKES_FLOOR, SPR_SPIKES_E, or SPR_SPIKES_W sprite types.

This case also handles the reciprocating variants of the floor-mounted and east-facing spikes, which could have a nonzero frame value while in the retracted state. This never occurs for the passive spikes, so the return true is not relevant to them.

Since passive spikes never retract, the player is always hurt when touching these actors. HurtPlayer() causes this injury, and false is returned. Per InteractPlayer()’s conventions, a false return value indicates that the actor should be drawn normally for this frame.

Green Tomato (on floor) / Green Tomato (floating) / Red Tomato (on floor) / Red Tomato (floating) / Yellow/Teal Striped Pear (on floor) / Yellow/Teal Striped Pear (floating) / Teal Onion

Harmfulno
Vulnerable (Pounces)no
Vulnerable (Explosions)no
Prize Pick-Up Points200
Name OriginInvented by the Cosmore/Cosmodoc projects.
    case SPR_GRN_TOMATO:
    case SPR_RED_TOMATO:
    case SPR_YEL_PEAR:
    case SPR_ONION:
        act->dead = true;

        AddScore(200);
        NewActor(ACT_SCORE_EFFECT_200, x, y);

        NewDecoration(SPR_SPARKLE_SHORT, 4, act->x, act->y, DIR8_NONE, 3);
        StartSound(SND_PRIZE);

        return true;

This responds to actors having the SPR_GRN_TOMATO, SPR_RED_TOMATO, SPR_YEL_PEAR, or SPR_ONION sprite types. These are all prize actors which are removed from the map when the player sprite touches them. This removal is achieved by setting the actor’s dead flag to true.

The player earns 200 points via AddScore() for picking up one of these actors. This is accompanied by a Floating Score (200) created by NewActor() with an actor type of ACT_SCORE_EFFECT_200. The score effect is inserted at the prize’s last known x/y position.

At the same location (although now using the act->x and act->y values for unknown reasons) a “Decoration: Sparkles after prize is picked up” (SPR_SPARKLE_SHORT) animation is started with NewDecoration(). This animation is four frames long and plays three times without moving from its start position (DIR8_NONE). Along with this visual, the SND_PRIZE sound effect is queued for playback with StartSound().

Because the prize actor has become dead here, the value true is returned to the caller. Per InteractPlayer()’s conventions, a true return value indicates that the actor should not be drawn during this frame.

Exit Sign

Harmfulno
Vulnerable (Pounces)no
Vulnerable (Explosions)no
Name OriginInfluenced by sprite appearance.
    case SPR_EXIT_SIGN:
        winLevel = true;

        return false;

This responds to actors having the SPR_EXIT_SIGN sprite type. Whenever the player touches one of these actors, the winLevel flag is unconditionally set, informing the game loop that the current level has been completed.

Per InteractPlayer()’s conventions, a false return value indicates that the actor should be drawn normally for this frame.

Spear (stationary) / Pyramid Spike (on ceiling, stationary)

Harmfulyes
Vulnerable (Pounces)no
Vulnerable (Explosions)yes
Explosions Required1
Explosion Points1,600
Name OriginInvented by the Cosmore/Cosmodoc projects.
NotesOnly the Spear (stationary) can be exploded.
    case SPR_ARROW_PISTON_W:     /* non-passive actor; discussed elsewhere */
    case SPR_ARROW_PISTON_E:     /* non-passive actor; discussed elsewhere */
    case SPR_FIREBALL:           /* non-passive actor; discussed elsewhere */
    case SPR_SAW_BLADE:          /* non-passive actor; discussed elsewhere */
    case SPR_SPEAR:
    case SPR_FLYING_WISP:        /* non-passive actor; discussed elsewhere */
    case SPR_TWO_TONS_CRUSHER:   /* non-passive actor; discussed elsewhere */
    case SPR_JUMPING_BULLET:     /* non-passive actor; discussed elsewhere */
    case SPR_STONE_HEAD_CRUSHER: /* non-passive actor; discussed elsewhere */
    case SPR_PYRAMID:
    case SPR_PROJECTILE:         /* non-passive actor; discussed elsewhere */
    case SPR_SHARP_ROBOT_FLOOR:  /* non-passive actor; discussed elsewhere */
    case SPR_SHARP_ROBOT_CEIL:   /* non-passive actor; discussed elsewhere */
    case SPR_SPARK:              /* non-passive actor; discussed elsewhere */
    case SPR_SMALL_FLAME:        /* non-passive actor; discussed elsewhere */
        HurtPlayer();

        if (act->sprite == SPR_PROJECTILE) {
            act->dead = true;
        }

        return false;

This responds to actors having the SPR_SPEAR or SPR_PYRAMID sprite types along with a bit more than a dozen other unrelated types that are not described here. One of those unrelated types is SPR_PROJECTILE which governs the execution of an if body that is of no concern to passive actors.

The only relevant bit for passive actors is the call to HurtPlayer(), which imparts the actual injury.

Per InteractPlayer()’s conventions, a false return value indicates that the actor should be drawn normally for this frame.

Bent Floor Spikes

Harmfulyes
Vulnerable (Pounces)no
Vulnerable (Explosions)no
Name OriginCanonical; appears in the hint sheet text.
    case SPR_SPIKES_FLOOR_BENT:
    ...
        HurtPlayer();

        return false;

This responds to actors having the SPR_SPIKES_FLOOR_BENT sprite type along with a handful of other unrelated types that have been omitted.

These actors simply call HurtPlayer() and return false to the caller. Per InteractPlayer()’s conventions, a false return value indicates that the actor should be drawn normally for this frame.

Hamburger

Harmfulno
Vulnerable (Pounces)no
Vulnerable (Explosions)no
Prize Pick-Up Points12,800
Name OriginCanonical; appears in the hint sheet text.
    case SPR_HAMBURGER:
        act->dead = true;

        AddScore(12800);
        NewActor(ACT_SCORE_EFFECT_12800, x, y);

        NewDecoration(SPR_SPARKLE_SHORT, 4, act->x, act->y, DIR8_NONE, 3);
        StartSound(SND_PRIZE);

        if (playerHealthCells < 5) playerHealthCells++;

        if (!sawHamburgerBubble) {
            NewActor(ACT_SPEECH_WHOA, playerX - 1, playerY - 5);
            sawHamburgerBubble = true;
        }

        UpdateHealth();

        return true;

This responds to actors having the SPR_HAMBURGER sprite type. These Hamburgers have a unique ability: Each one adds an additional health cell to the player. This health cell begins unfilled, and requires a subsequent Power-Up Module to increase the player’s health to the new maximum.

This prize actor is removed from the map when the player sprite touches it, which is achieved by setting the actor’s dead flag to true.

The player earns 12,800 points via AddScore() for picking up one of these actors. This is accompanied by a Floating Score (12,800) created by NewActor() with an actor type of SPR_SCORE_EFFECT_12800. The score effect is inserted at the prize’s last known x/y position.

At the same location (although now using the act->x and act->y values for unknown reasons) a “Decoration: Sparkles after prize is picked up” (SPR_SPARKLE_SHORT) animation is started with NewDecoration(). This animation is four frames long and plays three times without moving from its start position (DIR8_NONE). Along with this visual, the SND_PRIZE sound effect is queued for playback with StartSound().

The real meat of this actor, so to speak, is the increment of the playerHealthCells variable. The increment is conditional on the current value being less than five, which limits the maximum number of cells that can be held.

If the player has not encountered one of these since the current level started, sawHamburgerBubble will be false and the if body will run. NewActor() creates a “Speech Bubble: Whoa!” (ACT_SPEECH_WHOA) centered above the player’s current playerX/playerY position. To prevent this from recurring again if another Hamburger is encountered on this level, the sawHamburgerBubble flag is set to true.

… Make that two triple cheeseburgers…

There are no maps in the retail game with two or more Hamburgers, so this if body always runs.

Although the newly-incremented playerHealthCells variable takes care of everything that the game logic cares about, the status bar at the bottom of the screen does not redraw unless explicitly asked to. The call to UpdateHealth() redraws the “HEALTH” area in the status bar with the fresh state.

Because the prize actor has become dead here, the value true is returned to the caller. Per InteractPlayer()’s conventions, a true return value indicates that the actor should not be drawn during this frame.

Purple Grapes / Orange Bottled Drink / Gourd (green with red spots) / Blue Stacked Spheres / Green Pod / Green Pea Pile / Three-Fingered Yellow/Cyan Fruit / Yellow Horn / Blue Vine/Red Berries / Green Vine/Yellow Fruit / Headdress / Red/Pink Root / Red Berries/Green Vine / Gourd (red with yellow spots) / Three Small Bananas / Red Leafy Vegetable (floating) / Red Leafy Vegetable (on floor) / Brown Pear (floating) / Brown Pear (on floor) / Candy Corn (floating) / Candy Corn (on floor)

Harmfulno
Vulnerable (Pounces)no
Vulnerable (Explosions)no
Prize Pick-Up Points400
Name OriginInvented by the Cosmore/Cosmodoc projects.
NotesA few of these are worth 800 points instead.
    case SPR_GRAPES:
    case SPR_DANCING_MUSHROOM:  /* non-passive actor; discussed elsewhere */
    case SPR_BOTTLE_DRINK:
    case SPR_GRN_GOURD:
    case SPR_BLU_SPHERES:
    case SPR_POD:
    case SPR_PEA_PILE:
    case SPR_LUMPY_FRUIT:
    case SPR_HORN:
    case SPR_RED_BERRIES:
    case SPR_YEL_FRUIT_VINE:
    case SPR_HEADDRESS:
    case SPR_ROOT:
    case SPR_REDGRN_BERRIES:
    case SPR_RED_GOURD:
    case SPR_BANANAS:
    case SPR_RED_LEAFY:
    case SPR_BRN_PEAR:
    case SPR_CANDY_CORN:
        act->dead = true;

        if (
            sprite_type == SPR_YEL_FRUIT_VINE || sprite_type == SPR_BANANAS
            || sprite_type == SPR_GRAPES || sprite_type == SPR_RED_BERRIES
        ) {
            AddScore(800);
            NewActor(ACT_SCORE_EFFECT_800, x, y);
        } else {
            AddScore(400);
            NewActor(ACT_SCORE_EFFECT_400, x, y);
        }

        NewDecoration(SPR_SPARKLE_SHORT, 4, act->x, act->y, DIR8_NONE, 3);
        StartSound(SND_PRIZE);

        return true;

This responds to actors having the SPR_DANCING_MUSHROOM, SPR_BOTTLE_DRINK, SPR_GRN_GOURD, SPR_BLU_SPHERES, SPR_POD, SPR_PEA_PILE, SPR_LUMPY_FRUIT, SPR_HORN, SPR_HEADDRESS, SPR_ROOT, SPR_REDGRN_BERRIES, SPR_RED_GOURD, SPR_RED_LEAFY, SPR_BRN_PEAR, or SPR_CANDY_CORN sprite types, all of which are prize actors that give 400 points. This also responds to SPR_GRAPES, SPR_RED_BERRIES, SPR_YEL_FRUIT_VINE, or SPR_BANANAS, which all give 800 points instead. These actors are removed from the map when the player sprite touches them. This removal is achieved by setting the actor’s dead flag to true.

Depending on the sprite_type, the player earns 800 or 400 points via AddScore() for picking up one of these actors. This is accompanied by either a Floating Score (800) or a Floating Score (400) created by NewActor() with an actor type of ACT_SCORE_EFFECT_800 or ACT_SCORE_EFFECT_400. The score effect is inserted at the prize’s last known x/y position.

At the same location (although now using the act->x and act->y values for unknown reasons) a “Decoration: Sparkles after prize is picked up” (SPR_SPARKLE_SHORT) animation is started with NewDecoration(). This animation is four frames long and plays three times without moving from its start position (DIR8_NONE). Along with this visual, the SND_PRIZE sound effect is queued for playback with StartSound().

Because the prize actor has become dead here, the value true is returned to the caller. Per InteractPlayer()’s conventions, a true return value indicates that the actor should not be drawn during this frame.

Headphones (floating) / Headphones (on floor)

Harmfulno
Vulnerable (Pounces)no
Vulnerable (Explosions)no
Prize Pick-Up Points800
Name OriginInvented by the Cosmore/Cosmodoc projects.
    case SPR_CYA_DIAMOND:     /* non-passive actor; discussed elsewhere */
    case SPR_RED_DIAMOND:     /* non-passive actor; discussed elsewhere */
    case SPR_GRY_OCTAHEDRON:  /* non-passive actor; discussed elsewhere */
    case SPR_BLU_EMERALD:     /* non-passive actor; discussed elsewhere */
    case SPR_HEADPHONES:
        act->dead = true;

        NewDecoration(SPR_SPARKLE_SHORT, 4, act->x, act->y, DIR8_NONE, 3);

        AddScore(800);
        NewActor(ACT_SCORE_EFFECT_800, x, y);

        StartSound(SND_PRIZE);

        return true;

This responds to actors having the SPR_HEADPHONES sprite type along with a handful of non-passive types which have idle animations and are not discussed in this section. These actors are removed from the map when the player sprite touches them. This removal is achieved by setting the actor’s dead flag to true.

At the prize’s last known act->x/act->y position a “Decoration: Sparkles after prize is picked up” (SPR_SPARKLE_SHORT) animation is started with NewDecoration(). This animation is four frames long and plays three times without moving from its start position (DIR8_NONE).

The player earns 800 points via AddScore() for picking up one of these actors. This is accompanied by a Floating Score (800) created by NewActor() with an actor type of ACT_SCORE_EFFECT_800. The score effect is inserted at the same location (although now using the local x and y values for unknown reasons). Along with these visuals, the SND_PRIZE sound effect is queued for playback with StartSound().

Because the prize actor has become dead here, the value true is returned to the caller. Per InteractPlayer()’s conventions, a true return value indicates that the actor should not be drawn during this frame.

E1M11 Exit Monster (facing north)

Harmfulno
Vulnerable (Pounces)no
Vulnerable (Explosions)no
Name OriginInvented by the Cosmore/Cosmodoc projects.
#ifdef HAS_ACT_EXIT_MONSTER_N
    case SPR_EXIT_MONSTER_N:
        blockActionCmds = true;
        blockMovementCmds = true;

        act->data1++;

This code is conditionally compiled into the executable based on the presence of the HAS_ACT_EXIT_MONSTER_N define, and is only found in the second episode of the retail game. (Both the first and second episodes feature a level that ends with the player encountering this actor, but the first episode ends with an Episode 1 End Trigger Line before the player falls far enough to make contact with the monster.)

This case handles actors having the SPR_EXIT_MONSTER_N sprite type. These Exit Monsters are unusual passive actors because they do react to the player’s presence, but do not have a tick function that controls their behavior. All interactivity from this actor occurs here as a side effect of the player sprite touching it.

On every tick of gameplay where the player sprite is touching this actor, the player’s blockActionCmds and blockMovementCmds flags are both set to true. These make the player invisible, invulnerable, and unable to respond to any keyboard input. The actor’s data1 variable is also incremented to serve as a timer. The remainder of this code performs a short timed animation and the conditions occur in bottom-to-top order.

Where’d you go?

It’s not all that easy to see because of the number of objects on the screen combined with the vertical scrolling motion, but the player disappears from view several frames before the monster’s jaws actually snap shut.

        if (act->frame != 0) {
            winLevel = true;
        } else if (act->data1 == 3) {
            act->frame++;
        }

        if (act->data1 > 1) {
            playerY = act->y;
            playerY = act->y;
            isPlayerFalling = false;
        }

        return false;
#endif

On a future tick of gameplay after the player first contacts the sprite, act->data1 will be greater than one and the lowest if body will run. This overrides the player’s vertical position by setting playerY equal to the actor’s y position twice – the repetition does not have any effect beyond what a single assignment would’ve done. Additionally the isPlayerFalling flag is set to false. None of these state changes have any observable effect. Because the blockActionCmds flag is set to true, none of the player movement code in MovePlayer runs, so the player remains frozen in place regardless of what is changed here.

Once data1 reaches three, the actor’s frame value is incremented from zero to one. This changes the actor’s appearance from open-mouthed to closed.

On a subsequent tick, when this code executes with a nonzero frame, the sequence is completed and winLevel is set to true to signal the game loop that the current level has been completed.

Regardless of the path taken through the code, the value false is always returned to the caller. Per InteractPlayer()’s conventions, a false return value indicates that the actor should be drawn normally for this frame.