Knowledge Base - Thing Count

Thing Count


Pesky Sergeants!
Figure 1: Those Pesky Sergeants!

Figure 1 shows what happens when you pick up the shotgun in the example map thingcnt.wad: four sergeants are spawned into the room. Not being totally sadistic, it would be nice to reward the player with some health after s/he dispatches those pesky sergeants. This can be done via the thingcount function.

WadAuthor Map Layout
Figure 2: WadAuthor Map Layout

Figure 2 illustrates the layout of the map. The shotgun will trigger the spawn of the sergeants. Figure 3 illustrates the properties for the shotgun.

Shotgun will Execute Script
Figure 3: Shotgun will Execute Script

When the shotgun is picked up, script 1 will be executed. Script 1 will spawn the four sergeants. Figure 4 shows the properties for the sergeant map spot spawn points.

Shotgun will Execute Script
Figure 4: Map Spot Properties

Each map spot must have an associated tag. To make things easier I numbered the map spot tags sequentially, 1 to 4. This makes the script writing a tad easier.

The meat of all this is the scripting.:

#include "zcommon.acs"

/* This is a map level variable which means it has
   scope for all functions in the current map. */

int cdone;


/* The scripts will spawn the sergeants and kick 
off the code to watch the thing count. This has 
some fancy programming just to illustrate that ACS 
is a full fledged programming language. */
 
script 1 (void)
{
    int i; // Script level variable. Only has scope within this script.
     
    /* Below Translated: start i at 1 and do Thing_Spawn at map spot 
       that has tag i. Increment i by i (i++) and continue 
       until i reaches 5. Then quit. */

    i=1;
    while (i < 5)
    {
        Thing_Spawn(i, T_SHOTGUY, 192);
        i++;
    }

    // Now kick off the thing count watch script, script 2.

    ACS_Execute(2, 0, 0, 0, 0);	
}

//This script will run continuously.

script 2 (void)
{
    
    int cc;

    // If cdone is not zero, then we are done here.

    if (cdone) Terminate;

    /* Translated: Count sergeants and give me a count
       and ignore their tags. */

    cc = ThingCount(T_SHOTGUY, 0);

    // If all the sergeants are dead, spawn health.
    if (cc==0)
    {
        Thing_Spawn(5, T_MEDKIT, 0);	
        cdone=1;
    }
    
    /* Want to loop this so put in a delay so we don't get a
       runaway script error. */
    Delay (70); // 2 seconds.

    // Play it again Sam :) until cc = 0.
    Restart;
}

The first thing we need to do is to spawn the sergeants in script 1. The following code will iterate through the map spot tags and spawn the sergeants at each respective map spot:

    i=1;
	while (i < 5) {
		Thing_Spawn(i, T_SHOTGUY, 192);
		i++;
	}

Here, is is clear why the map spots were numbered consecutively. Since we are using the #include "zcommon.acs" statement at the top of the script, we can use the human-readable name for the sergeant: T_SHOTGUY. To recall an earlier tutorial, the Thing_Spawn parameters are the map spot id, the thing to spawn and the facing direction. Here the sergeants will spawn facing the player, of course.

In this code, i starts at 1. The Thing_Spawn function will spawn a sergeant at map spot tagged 1. The code i++; increments the variable i by 1 and the whole thing starts over. How does the engine know what to repeat? The repeating part of the while loop is enclosed between >{ and }. While i is less than five, all the statements between { and } will be executed. When i equals 5, the program drops out of the while loop.

The next thing script 1 does is fire off script 2 with:

    ACS_Execute(2, 0, 0, 0, 0);

Script 1 has now done its job and it done for the day. :)

The point of script 2 is really quite simple: keep checking to see if all the sergeants have been killed. When all of the sergeants are dead, then spawn a health at map spot 5 for the player. This script will run continuously, checking every couple of seconds to see if there are any shotgun guys still around. It actually looks more complicated than it is in reality.

    // If cdone is not zero, then we are done here.

    if (cdone) Terminate;

This code checks to see if the script had already spawned a health pack. It is simply a flag that is set when the health pack gets spawned. If this check wasn't here, then the script would spawn a health pack every two seconds after all the sergeants are killed.

    cc = ThingCount(T_SHOTGUY, 0);

This code returns the count of sergeants currently on the map.

ThingCount(Type, Tag)

  • Type: What to count. Uses the same ids used in Thing_Spawn.
  • Tag: Count items tagged as #.

Either type or tag can be zero. If the parameter is zero, then the engine ignores the count qualifier. For example, to count all the sergeants, the tag is zero which means that all sergeants on the maps will be counted. If the type is zero, but a tag is entered, then all the items of tag n will be counted, regardless of what kind of thing.

    if (cc==0)
    {
        Thing_Spawn(5, T_MEDKIT, 0);	
        cdone=1;
    }

When the count of sergeants reaches zero, then the above code will be executed. The script will spawn a health pack at map spot 5. The direction doesn't matter in this case, so it is zero.

Once the medkit is spawned, this script is done, so the flag is set. Remember at the beginning of the script, a check is made to see if cdone is non-zero. As soon as cdone becomes 1, the script will terminate.

    Delay (70); // 2 seconds.
    Restart;

If the count of sergeants is greater than zero however, we want to continue to count. This code simply delays a couple of seconds, then restarts the script. The Delay is important here. Without a delay, a runaway script error will be generated and the engine will terminate the script. The delay allows other scripts to get CPU time (like a DoEvents in VB).


Marisa sent me the following simplified version of my script:

script 1 (void)
{
   // Thing_Spawn will operate on all of the map spots with tid 1
   Thing_Spawn (1, T_SHOTGUY, 192);
   while (ThingCount (T_SHOTGUY, 0))
   {
      delay (70); 
   }
   Thing_Spawn (5, T_MEDKIT, 0);
}

As you can see, this does it all in one shot. Also, note here that the map spots all have the same tag of 1, so you only need one spawn command. He also put the delay inside the while loop; while the thingcount returns a value greater than 0, the while loop will execute. As soon as the thingcount returns 0, the while loop is exited and the medkit is spawned. Very neat. Thanks, Marisa.

Sources

3D Game Alchemy by Steve Benner, et al. Copyright © 1996 By Sams Publishing

Back