Hitscan attacks hit invisible barriers in large open areas

From DoomWiki.org

Spider Mastermind's attacks are blocked by an invisible barrier

Sometimes hitscans may hit an invisible barrier. This is caused by the fact that Doom has some difficulty handling the collision of bullets against very long walls. Projectiles will not be blocked by this invisible barrier, but it does block autoaim, potentially making projectile shots miss. This bug happens more frequently on levels where there are walls that are more than 2048 units long.

Technical explanation of the hitscan collision error[edit]

When a wall is very long, the engine may incorrectly evaluate the position of the wall.

The technical explanation as how it occurs is yet unknown, but a particular case is known that mappers should avoid: linedef #0 should always be shorter than 2048 units long. This greatly reduces the occurrence of the bug, because essentially all blockmap generating utilities apart from ZokumBSP will always include linedef #0 in each block of the blockmap. Although this is the most common cause of this bug, it may also occur when a hitscan crosses a block that contains another long linedef.

Technical explanation of the most common case[edit]

To understand this case, the structure of the blockmap must be first understood. Each block of the blockmap contains a list (blocklist) of linedefs that touches or may touch the block. The blocklist always starts with the value 0 and ends with -1. Most utilities will include the number 0 at the start of the list although it's not a technical necessity and the Doom engine will not skip it.

This is the essential part of the function P_BlockLinesIterator from p_maputl.c:

   offset = y*bmapwidth+x;
   
   offset = *(blockmap+offset);
   
   for ( list = blockmaplump+offset ; *list != -1 ; list++)
   {
       ld = &lines[*list];
       
       if (ld->validcount == validcount)
           continue; 	// line has already been checked
       
       ld->validcount = validcount;
       
       if ( !func(ld) )
           return false;
   }
   return true;	// everything was checked

The loop will iterate over the list and it starts at the beginning of the blocklist which has the value 0 until it reaches the end. Each element on the list is the number of a linedef, but since the iteration starts on the first element which is often, but not always 0, then linedef #0 is constantly checked each time the function is called. Since it's done whenever the player fires, the bug is more inclined to occur on a level where this linedef is too long.

A simple but inflexible fix would be to add +1 to blockmaplump+offset, but hitscan errors may still occur against other walls which are too long. This was the most common approach in all Doom source ports until the introduction of ZokumBSP, which allows omission of the redundant zero entry in each block list as a means of further optimization and blockmap compression; however, it added this capability with knowledge that existing ports would not support it without being modified to use heuristics to specially identify such maps against the background of existing maps which included the zero entry in every blocklist (BTSX E2 MAP20 is an example of a map without leading zero entries in its block lists, which is necessary to make the map work within the limitations of vanilla Doom). Several source ports have since been adjusted to contain this heuristic.

A good habit for map developers is to break long walls into linedefs smaller than 1024 units long, which will also reduce the visibility of the long wall error at the same time.

Demo files[edit]