Monster Madness

I wanted to add Unreal 1 monsters to the UT world. This tutorial learns you how it can be done, and how to manage your monsters to make sure they respawn, and that the scores are right. As you might have guessed, it is based on my Monster Madness mutator.

Originally written on 2000-03-01

Assumptions

I assume you have downloaded & examined the Monster Madness mutator, and that you have a little insight in map making.

The tutorial

Monster Madness started by spawning monsters at random spots in the level. Immediately a problem shows its ugly face: at which coordinates to you spawn them? If you have ever made a map that supported bots, you know what navigation points are. If not: they are points where bots and other computer-controlled creatures can walk. Together they form a tight network describing the routes bots can walk on to pick up items and find other players. Here is the code that I wrote for it:

function ScriptedPawn SpawnTheMonster(string MonsterClassString) {
  local ScriptedPawn NewMonster;
  local vector StartingLocation;
  local class<ScriptedPawn> MonsterClass;

  if(MonsterClassString~="") {
    Log("SpawnTheMonster called without MonsterClassString",'MonsterList');
    return None;
  }

  StartingLocation=GetStartingLocation();

  // DynamicLoadObject converts a string into a class. It returns
  // None if the class can't be found.</span>
  MonsterClass=class<ScriptedPawn>(DynamicLoadObject(MonsterClassString, class'Class'));
  if(MonsterClass==None) {
    Log("Unable to DynamicLoadObject"@MonsterClassString,'MonsterList');
    return None;
  }

  // Spawn a new monster at the right location
  NewMonster=Spawn(MonsterClass,,,StartingLocation);
  if(NewMonster==None)
    // Apparently, the monster couldn't be spawned at this place.
    // This function returns None, so the calling function will know
    // the spawning failed.</span>
    return None;

  // Set some properties of the freshly spawned monster.
  // GetRandomPlayer() makes sure that the new monster is angry at someone ;-)</span>
  NewMonster.Enemy=GetRandomPlayer();
  NewMonster.bHunting=true;
  NewMonster.bHateWhenTriggered=true;
  NewMonster.Intelligence=BRAINS_Human;
  NewMonster.GotoState('Attacking');

  // Return the new monster so that the calling function
  // knows everything went right.</span>
  return NewMonster;
}

function vector GetStartingLocation() {
  local NavigationPoint N;
  local NavigationPoint Candidate[16];
  local int num;

  // The navigation points are stored in a list. It starts at
  // Level.NavigationPointList, and every navigation point refers to the next one
  // with the NextNavigationPoint property.</span>
  for (N=Level.NavigationPointList; N!=None; N=N.NextNavigationPoint) {
    if(N!=None && !N.Region.Zone.bWaterZone) {
      // The three lines below are from Epic's code to find
      // a new PlayerStart when a player respawns.</span>
      if (num<16) Candidate[num] = N;
      else if (Rand(num) < 16) Candidate[Rand(16)] = N;
      num++;
    }
  }
  // Return the location of a random navigation point
  return Candidate[Rand(Min(16,num))].Location;
}

So, a creature are spawned on a random navigation point now. At least, most of the times. The maps made for UT are not made with large creatures in mind, like Titans. So there are probalby a lot of navigation points in spaces too small to spawn a Titan in. To solve this, I’ve created a Delayed Spawner. It waits for a given amount of time, and then tries to spawn a monster on a random navigation point. If that fails, it waits again, and tries to spawn again. This is tried ten times, after which the Delayed Spawner gives up and destroys itself. Here is the code:

class DelayedSpawner expands Actor;

var string MonsterType;
// I've created a list of monsters. I'll get to it later. What
// you have to know for now, is that the SpawnTheMonster() function is in
// this list, and MonsterItem.Monster is the monster.</span>
var MonsterList MonsterItem;
var float RetriggerTime;
var int TimesRetriggered;

// This function has to be called for the DelayedSpawner
// to start spawning. I didn't put it in the Spawned() or PostBeginPlay()
// function, because I wanted to set its properties before the spawning
// starts.</span>
function Go(float TimeOut) {
  RetriggerTime=TimeOut;
  SetTimer(TimeOut,false);
}

event Timer() {
  local ScriptedPawn NewMonster;

  // Try to spawn a new monster
  NewMonster=MonsterItem.SpawnTheMonster(MonsterType);
  if(NewMonster==None) {
    // Apparently, the spawn didn't work. If tried less than
    // 10 times, retry. Otherwise log an error message, and destroy.</span>
    TimesRetriggered++;
    if(TimesRetriggered<10) SetTimer(RetriggerTime, false);
    else {
      Log("Tried to respawn a monster 10 times. Giving up.");
      Destroy();
    }
    return;
  }

  // Set the new monster in the list, and destroy the
  // delayed spawner, since it is no longer needed.</span>
  MonsterItem.Monster=NewMonster;
  Destroy();
}

I hope the inline comments are enough to explain what is going on.

Now the monsters are there, and they know who they should go after. The next step is to make them respawn when they are shot! The right function for this is ScoreKill(). It is called every time someone kills someone. This could be a monster killing a player, or a player committing suicide, etc.