Monsters can see through walls

From DoomWiki.org

Due to buggy line of sight calculations, monsters in vanilla Heretic and Hexen may sometimes erroneously regard a player on the other side of any arbitrary number of walls as visible, causing them to wake and go into chase mode before it makes sense for them to do so. One name given to this bug is the "Anywhere Moo bug", named as such by James "Quasar" Haley who noted the bug was most readily observed with boss monsters such as the cyberdemon and maulotaur, due to their wake sounds not decreasing in volume with distance.[1]

This bug was inherited from early versions of the Doom codebase just prior to version v1.2, and persisted into Heretic, Hexen, and was notably later adapted accidentally into the ZDoom source port. It was fixed by id Software via swapping the original BLOCKMAP-based line-of-sight (LOS) checking algorithm with one based on use of the BSP tree.

Designer workaround[edit]

The most egregious cases of the bug, where monsters activate from sectors that could not possibly see the player, can be worked around by giving the map a proper REJECT table. Therefore, it is advisable for map designers who aim for vanilla compatibility to ensure that a REJECT table is built during the nodes building process.

Any remaining problems after that may, with some concession of design, be alleviated by adjusting the angle the offending monsters are facing.

Algorithmic fix[edit]

The corrected code, from ZDoom, is below:

for (count = 0 ; count < 100 ; count++)
  {
    if (flags & PT_ADDLINES)
    {
      AddLineIntercepts(mapx, mapy);
    }
    
    if (flags & PT_ADDTHINGS)
    {
      AddThingIntercepts(mapx, mapy, btit);
    }
        
    if (mapx == xt2 && mapy == yt2)
    {
      break;
    }

    // [RH] Handle corner cases properly instead of pretending they don't exist.
    switch ((((yintercept >> FRACBITS) == mapy) << 1) | ((xintercept >> FRACBITS) == mapx))
    {
    case 0:   // neither xintercept nor yintercept match!
      count = 100;  // Stop traversing, because somebody screwed up.
      break;

    case 1:   // xintercept matches
      xintercept += xstep;
      mapy += mapystep;
      break;

    case 2:   // yintercept matches
      yintercept += ystep;
      mapx += mapxstep;
      break;

    case 3:   // xintercept and yintercept both match
      // The trace is exiting a block through its corner. Not only does the block
      // being entered need to be checked (which will happen when this loop
      // continues), but the other two blocks adjacent to the corner also need to
      // be checked.
      if (flags & PT_ADDLINES)
      {
        AddLineIntercepts(mapx + mapxstep, mapy);
        AddLineIntercepts(mapx, mapy + mapystep);
      }
      
      if (flags & PT_ADDTHINGS)
      {
        AddThingIntercepts(mapx + mapxstep, mapy, btit);
        AddThingIntercepts(mapx, mapy + mapystep, btit);
      }
      xintercept += xstep;
      yintercept += ystep;
      mapx += mapxstep;
      mapy += mapystep;
      break;
    }
  }

In the original code, "case 3" of the switch statement was missing. Quoting Christoph Oelckers (Graf Zahl): "When case 3 is not there the trace will get lost with no chance of ever getting back on track which would cause an endless loop. That's where the 'for' loop came in. Apparently id had problems with the code hanging but never found the real cause so they just put a safety net around their broken algorithm so that it can terminate."[2]

References[edit]

  1. Haley, James (12 October 2009). "Is it possible to... (Heretic source)." Doomworld Forums. Retrieved 15 October 2021.
  2. Oelckers, Christoph (12 October 2009). "Is it possible to... (Heretic source)." Doomworld Forums. Retrieved 15 October 2021.