Game Development Community

dev|Pro Game Development Curriculum

TGE spawn points that spawn anything

by Stephen Lujan · 04/10/2007 (9:09 am) · 2 comments

Add this to the bottom of your engine/game/missionmarker.cc file
//------------------------------------------------------------------------------
// Class: AISpawnSphere
//------------------------------------------------------------------------------
IMPLEMENT_CO_NETOBJECT_V1(AISpawnSphere);

Sphere AISpawnSphere::smSphere(Sphere::Octahedron);

AISpawnSphere::AISpawnSphere()
{
   mRadius = 100.f;
   mSphereWeight = 100.f;
   mIndoorWeight = 100.f;
   mOutdoorWeight = 100.f;
   mSpawnGroupName = StringTable->insert("Default Group");
}

bool AISpawnSphere::onAdd()
{
   if(!Parent::onAdd())
      return(false);

   if(!isClientObject())
      setMaskBits(UpdateSphereMask);

   return true;
}

void AISpawnSphere::inspectPostApply()
{
   Parent::inspectPostApply();
   setMaskBits(UpdateSphereMask);
}

U32 AISpawnSphere::packUpdate(NetConnection * con, U32 mask, BitStream * stream)
{
   U32 retMask = Parent::packUpdate(con, mask, stream);

   //
   if(stream->writeFlag(mask & UpdateSphereMask))
   {
      stream->write(mRadius);
	  //stream->writeString(mSpawnGroupName); //client doesn't need this, don't pack?
      stream->write(mSphereWeight);
      stream->write(mIndoorWeight);
      stream->write(mOutdoorWeight);
   }
   return(retMask);
}

void AISpawnSphere::unpackUpdate(NetConnection * con, BitStream * stream)
{
   Parent::unpackUpdate(con, stream);
   if(stream->readFlag())
   {
      stream->read(&mRadius);
	  //mmSpawnGroupName = stream->readSTString(true);
      stream->read(&mSphereWeight);
      stream->read(&mIndoorWeight);
      stream->read(&mOutdoorWeight);
   }
}

void AISpawnSphere::initPersistFields()
{
   Parent::initPersistFields();

   addGroup("Dimensions");	
   addField("radius", TypeF32, Offset(mRadius, AISpawnSphere));
   endGroup("Dimensions");	

   addGroup("Spawns");
   addField("SpawnGroupName", TypeString, Offset(mSpawnGroupName, AISpawnSphere));
   endGroup("Spawns");

   addGroup("Weight");	
   addField("sphereWeight", TypeF32, Offset(mSphereWeight, AISpawnSphere));
   addField("indoorWeight", TypeF32, Offset(mIndoorWeight, AISpawnSphere));
   addField("outdoorWeight", TypeF32, Offset(mOutdoorWeight, AISpawnSphere));
   endGroup("Weight");	
}

Add this to the bottom of your engine/game/missionmarker.h file BUT before that last #endif
//------------------------------------------------------------------------------
// Class: AISpawnSphere
//------------------------------------------------------------------------------

class AISpawnSphere : public MissionMarker
{
   private:
      typedef MissionMarker         Parent;
      static Sphere                 smSphere;

   public:
      AISpawnSphere();

      // SimObject
      bool onAdd();
      void inspectPostApply();

      // NetObject
      enum SpawnSphereMasks {
         UpdateSphereMask = Parent::NextFreeMask,
         NextFreeMask = Parent::NextFreeMask << 1
      };

      U32  packUpdate  (NetConnection *conn, U32 mask, BitStream *stream);
      void unpackUpdate(NetConnection *conn,           BitStream *stream);

      // field data
      F32				mRadius;
      F32				mSphereWeight;
      F32				mIndoorWeight;
      F32				mOutdoorWeight;
	  StringTableEntry	mSpawnGroupName;

      static void initPersistFields();

      DECLARE_CONOBJECT(AISpawnSphere);
};

modify your server/scripts/markers.cs file to include the bolded parts
datablock MissionMarkerData(SpawnSphereMarker)
{
   category = "Misc";
   shapeFile = "~/data/shapes/markers/octahedron.dts";
};
[b]
//new spawn sphere for new spawn code
datablock MissionMarkerData(AISpawnSphere)
{
   category = "AI Spawners";
   shapeFile = "~/data/shapes/markers/octahedron.dts";
};

//won't render like the old spawn sphere without this
function AISpawnSphere::onEditorRender(%this, %editor, %selected, %expanded)
{
   if(%selected $= "true")
   {
      %editor.consoleFrameColor = "255 0 0";
      %editor.consoleFillColor = "0 15 0 15";
      %editor.renderSphere(%this.getWorldBoxCenter(), %this.radius, 1);
   }
}
[/b]

//------------------------------------------------------------------------------
// - serveral marker types may share MissionMarker datablock type
function MissionMarkerData::create(%block)
{
	echo("MissionMarkerData::create(",%block,")");
   switch$(%block)
   {
		case "WayPointMarker":
			 %obj = new WayPoint() {
				dataBlock = %block;
			 };
			 return(%obj);
		case "SpawnSphereMarker":
			 %obj = new SpawnSphere() {
				datablock = %block;
			 };
			 return(%obj);[b]
		case "AISpawnSphere":
			 echo("creating AISpawnSphere");
			 //new ones should automatically be placed in the appropriate group
			 //do not put anything but AISpawnSpheres in this new group
			 %groupName = "MissionGroup/AISpawnPoints";
			 %group = nameToID(%groupName);
			 if (%group == -1)
			 {
				%group = new SimGroup(AISpawnPoints);
				MissionGroup.add(%group);
			 }

			 %obj = new AISpawnSphere() {
				datablock = %block;  };

			 %group.add(%obj);
			 return(%obj);[/b]
   }
   return(-1);
}

add this new file: server/scripts/ai/aispawn.cs

Edit this file to quickly add a new spawn group. Then in the mission editor, set each spawn group as needed for each of the AISpawnSpheres you place.
//checks all AISpawnSpheres and runs their corresponding spawn code
function AIManager::runAISpawners(%this)
{

	//for fun lets add two additional bots before we run the AISpawnSpheres' codes
	//bots will be added at random AISpawnSpheres
	%this.spawnSome(2);

   %groupName = "MissionGroup/AISpawnPoints";
   %group = nameToID(%groupName);

   if (%group != -1) {
      %count = %group.getCount();
      if (%count != 0) {
		for(%i = 0; %i < %count; %i++)
		{
			%spawner = %group.getObject(%i);
			%spawner.spawn(%i+3);
		}
      }
      else
         error("No spawn points found in " @ %groupName);
   }
   else
      error("Missing spawn points group " @ %groupName);
	
   return 1;
}

//spawns whatever corresponds to the SpawnGroupName of an AISpawnSphere
function AISpawnSphere::spawn(%this, %number)
{
	switch$(%this.SpawnGroupName)
	{
		//spawn whatever you want, however you want
		//just come up with a name for each spawning package
		
		//spawn 2 bots at the spawn point by default
		case "Default Group":
			
			//put bot on the ground below this AISpawnSphere
			%pos = AIManager::getTerrainLevel(%this.position,%this.radius,4.5);
			if (%pos)
				//create new object here at %pos	
				AIGuard::spawn2("Bot "@ %number, %pos);
			else
				//and create new object here at %this.position
				AIGuard::spawn2("Bot "@ %number, %this.position);

			%pos = AIManager::getTerrainLevel(%this.position,%this.radius,4.5);
			if (%pos)
				AIGuard::spawn2("Bot "@ %number+0.5, %pos);
			else
				AIGuard::spawn2("Bot "@ %number+0.5, %this.position);

			return 1; //endcase

		//using this example spawn code an AISpawnSphere will spawn only one bot
		case "Only One":
			%pos = AIManager::getTerrainLevel(%this.position,%this.radius,4.5);
			if (%pos)
			{
				//create new object here at %pos	
				AIGuard::spawn2("Bot "@ %number, %pos);
			}
			else
			{
				//and create new object here at %this.position
				AIGuard::spawn2("Bot "@ %number, %this.position);
			}

			return 1; //endcase
	}
	error(%this.objId,".SpawnGroupName is not a valid choice");
	return (-1);
}

//recursive function adds %number of bots to random AISpawnSpheres
function AIManager::spawnSome(%this, %number)
{
	if (%number > 0)
	{
		AIGuard::spawn2("Bot "@ %number, %this.pickAISpawnPoint());
		%number--;
		%this.schedule(600, "spawnSome",%number);
	}
}

//randomly picks a good spawn location at one of the AISpawnSpheres
//tries to pick a clear space on the ground
function AIManager::pickAISpawnPoint(%this) 
{
	%groupName = "MissionGroup/AISpawnPoints";
	%group = nameToID(%groupName);

	if (%group != -1)
	{
		%count = %group.getCount();
		if (%count != 0)
		{
			%index = getRandom(%count-1);
			%spawn = %group.getObject(%index);

			%pos = %this.getTerrainLevel(%spawn.position,%spawn.radius,4.5);

			if (%pos)
			{
				return %pos;
			}
			//else place within 30 horizontal units of origin
			else
			{
				error("Couldn't find clear space at spawn point. Spawning in center of spawn point.");
				return %spawn.getTransform();
			}
		}
		else
			error("No spawn points found in " @ %groupName);
	}
	else
		error("Missing spawn points group " @ %groupName);

	// Could be no spawn points, in which case we'll stick the
	// player at the center of the world.
	%pos = %this.getTerrainLevel("0 0 600 1 0 0 0",30,4.5);
	if (%pos)
	{
		return %pos;
	}
	//give up and drop 400 units directly above origin
	else return "0 0 400 1 0 0 0";
}

//finds a space on the ground below an AISpawnSphere
//this code is borrowed from someone else resource
function AIManager::getTerrainLevel(%pos,%rad,%height)
{
	while(%retries < 150)
	{
		%x = getWord(%pos, 0) + mFloor(getRandom(%rad * 2) - %rad);
		%y = getWord(%pos, 1) + mFloor(getRandom(%rad * 2) - %rad);
		%z = getWord(%pos, 2) + mFloor(getRandom(%rad * 2) - %rad);
		
		%start 		= %x @ " " @ %y @ " 5000";
		%end 		= %x @ " " @ %y @ " -1000";
		%ground 	= containerRayCast(%start, %end, $TypeMasks::TerrainObjectType, 0);
		%z 		= getWord(%ground, 3);

		error ("Spawn Position   : " @ %x SPC %y);
		error ("Ground Level     : " @ %z);

		%z += %height;
		
		%position = %x @ " " @ %y @ " " @ %z;
		
		%mask = ($TypeMasks::VehicleObjectType |
			$TypeMasks::MoveableObjectType |
			$TypeMasks::StaticShapeObjectType |
			$TypeMasks::ForceFieldObjectType |
			$TypeMasks::InteriorObjectType |
			$TypeMasks::ItemObjectType);

		if (ContainerBoxEmpty(%mask,%position,3.5))
		{
			error ("Spawn Position Is Good");
			return %position;
		}
		else
			%retries++;
	}
	return false;
}  

//original pick spawn point function
// does not try to spawn object at the ground
function pickAISpawnPointOriginal()
{
   %groupName = "MissionGroup/AISpawnPoints";
   %group = nameToID(%groupName);

   if (%group != -1) {
      %count = %group.getCount();
      if (%count != 0) {
         %index = getRandom(%count-1);
         %spawn = %group.getObject(%index);
         return %spawn.getTransform();
      }
      else
         error("No spawn points found in " @ %groupName);
   }
   else
      error("Missing spawn points group " @ %groupName);

   // Could be no spawn points, in which case we'll stick the
   // player at the center of the world.
   return "0 0 300 1 0 0 0";
}

add the bolded parts to server/scripts/game.cs in the startGame() function
// Start the AIManager
   new ScriptObject(AIManager) {};
   MissionCleanup.add(AIManager);
   AIManager.think();
	[b]	
   //Make aiGuards
   AIManager.runAISpawners();[/b]
}

also add this line to server/scripts/game.cs in the function onServerCreated()
exec("./ai/aiSpawn.cs");


That's all! Now you can have a place all the AISpawnSpheres you want in the map editor. Just edit the "spawn group name" field to pick out which spawning script you want each one to run when the mission starts. Then add all of your custom scripts to the function AISpawnSphere::spawn in the file server/scripts/ai/aispawn.cs using the template already provided.

Enjoy.

By the way my getterrainlevel algorithm was adapted from this http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=2347
My thanks to the author Emo1313

#1
04/18/2007 (8:27 am)
Thanks I have been waiting for something like this and well put it to use.
#2
05/01/2007 (5:02 pm)
Thanks this will be a big help. Good job! Will this work with TGEA 1.0?