Sector type 17 is disabled after loading a saved game


Sector type 17 is a lighting effect that was first introduced in Doom II. The effect causes the lighting of the sector to flicker in random grades between its initial brightness and a value that depends on the neighboring sectors. Although the effect has been meant to work constantly, it becomes disabled if the player loads a saved game. When the restoration takes place, the sectors with the type will receive the light level they had at the moment the game was saved and flickering no longer occurs on them. The bug is still present in Ultimate Doom.


The reason is simply that the id developers forgot to add this sector type to the savegame code. A more detailed explanation follows.


The explanation is simple: when setting up a level, sector types are treated by the P_SpawnSpecials() function (p_spec.c line 1241). For effects other than damaging floors, the sector type is cleared and instead a thinker is spawned that will be associated to the sector and perform the effect, be it closing a door in half a minute or modifying the light level. Sector type 17 is the only new sector type introduced by Doom II, so it was added after most of the thinker code had already been written and bug fixed.

Each of these sector types is associated to its own thinker type:

Sector type Thinker spawn function Struct Thinker type
1 P_SpawnLightFlash (sector); lightflash_t T_LightFlash
2 P_SpawnStrobeFlash(sector,FASTDARK,0); strobe_t T_StrobeFlash
3 P_SpawnStrobeFlash(sector,SLOWDARK,0); strobe_t T_StrobeFlash
4 P_SpawnStrobeFlash(sector,FASTDARK,0); sector->special = 4; strobe_t T_StrobeFlash
8 P_SpawnGlowingLight(sector); glow_t T_Glow
10 P_SpawnDoorCloseIn30 (sector); vldoor_t T_VerticalDoor
12 P_SpawnStrobeFlash (sector, SLOWDARK, 1); strobe_t T_StrobeFlash
13 P_SpawnStrobeFlash (sector, FASTDARK, 1); strobe_t T_StrobeFlash
14 P_SpawnDoorRaiseIn5Mins (sector, i); vldoor_t T_VerticalDoor
17 P_SpawnFireFlicker(sector); fireflicker_t T_FireFlicker

A typical P_SpawnFoobar(sector) function will look like this:

void P_SpawnFoobar(sector_t* sector)
    // Declaring a struct corresponding to the thinker type
    foobar_t*	foo;
    // Sector type is reset to 0.
     sector->special = 0; 
    foo = Z_Malloc ( sizeof(*foo), PU_LEVSPEC, 0);

    // A thinker is added to the struct
    P_AddThinker (&foo->thinker);

    // The thinker function is set to the thinker type
    foo->thinker.function.acp1 = (actionf_p1) T_FooBar;

    // The sector is attached to the struct so that the
    // thinker will know what to affect.
    foo->sector = sector;

    // Init rest of the struct's variables as needed.
    <more code>

You notice that in type 4, which is both strobing and damaging, the sector type is explicitly restored to 4 after calling the thinker spawn function, since P_SpawnStrobeFlash() will have reset it to 0.

The important part of this explanation is that sector type 17 uses a new thinker function.


Since these sectors do not keep their type, the savegame code has a function written to preserve their thinkers. This is P_ArchiveSpecials() in p_saveg.c, line 356. The function's description above should sum up the nature of the problem:

// Things to handle:
// T_MoveCeiling, (ceiling_t: sector_t * swizzle), - active list
// T_VerticalDoor, (vldoor_t: sector_t * swizzle),
// T_MoveFloor, (floormove_t: sector_t * swizzle),
// T_LightFlash, (lightflash_t: sector_t * swizzle),
// T_StrobeFlash, (strobe_t: sector_t *),
// T_Glow, (glow_t: sector_t *),
// T_PlatRaise, (plat_t: sector_t *), - active list

Indeed, you do not see T_FireFlicker in that list. When the new thinker was added for Doom II, the developers simply forgot to also add it to the savegame code. The bulk of the P_ArchiveSpecials() function is a switch statement that will write relevant info if the thinker type is T_MoveCeiling, T_VerticalDoor, T_MoveFloor, T_PlatRaise, T_LightFlash, T_StrobeFlash, or T_Glow. Since a T_FireFlicker is not any of those, they will not be written to the savegame.

To represent these various thinker types in archived games, P_ArchiveSpecials uses values from this enumeration:


} specials_e;	

Notice the absence of a "tc_flicker" in the list. If it had been added, between tc_glow and tc_endspecial, it would have the value 7.

And similarly, the thinker is also forgotten about by the reverse function P_UnArchiveSpecials() used to load a savegame. It handles the same thinker types as its counterpart, in the same order. If a port fixed P_ArchiveSpecials and its enum but forgot to also fix P_UnarchiveSpecials(), when handling the saved T_FireFlicker thinker, the error message "P_UnarchiveSpecials:Unknown tclass 7 in savegame".


This was fixed in Boom. For comparison, the specials_e enumeration in Boom looks like this:

enum {
  tc_flicker,     //jff 8/8/98 add missing fire flicker entry
  tc_elevator,    //jff 2/22/98 new elevator type thinker                 
  tc_scroll,      // killough 3/7/98: new scroll effect thinker
  tc_friction,    // phares 3/18/98:  new friction effect thinker
  tc_pusher,      // phares 3/22/98:  new push/pull effect thinker
} specials_e;

The problem was also fixed in a different ways in source ports where most of the code was rewritten in an object-oriented approach, such as ZDoom or Eternity Engine. There, thinker objects contain their own serialization code, which avoids the problem of having related code in two different parts of the program and therefore makes it harder to forget about savegames when adding a new thinker type.