Dynamic Weapon Spawns

From Valve Developer Community
Jump to: navigation, search
Abstract Coding series

Levels & XPOptimizationProcedural TexturesSights & SniperriflesSpecial effectsVehiclesThreadsSave Game FilesNight VisionNon-offensive WeaponsDynamic Weapon SpawnsDynamic Weapon Spawns (Advanced)

For years, many console FPS games have been using a great feature called dynamic weapon spawn. The best example to illustrate that feature is the Perfect Dark game. Indeed, you can choose up to six different weapons at the beginning of the game, then the game will put those weapons at whatever point the weapon was assigned to. Pistols should go in all slot 1 spawns, cloak generators at another, and so on.

This kind of feature might add some longevity to your map because each game would let you select different sets of weapons. This article describes how one would go about adding this to a PC game such as Half-Life 2.

Setting it up

Creating a model entity called CWeaponSpawn seems to work.

Thus, create a new file called weaponspawns.cpp or something and place it in the \game_shared folder so the file will be shared by both the client and the server dlls. Then, add the file to both projects in the Visual C++ editor.

OK, now let's create the entity in the code, here's an empty shell of a model entity:

#include "cbase.h" 
#ifdef CLIENT_DLL
	#define CWeaponSpawner C_WeaponSpawner 
#endif
class CWeaponSpawner : public CBaseAnimating
{
	DECLARE_CLASS( CWeaponSpawner, CBaseAnimating );
	DECLARE_NETWORKCLASS(); 
	DECLARE_PREDICTABLE();
	DECLARE_DATADESC();

	int m_iWeaponSlot;

public:

	void Spawn( void )
	{
		Precache( );
		
		BaseClass::Spawn( );
		AddEffects( EF_NODRAW );
		SetMoveType( MOVETYPE_NONE );
		SetSolid( SOLID_NONE );
		SetThink( &CWeaponSpawner::Think );
		SetNextThink( gpGlobals->curtime + 3 );
	}
	void Precache( void )
	{
		PrecacheModel ("models/w_rcp120.mdl"); // set a different model, something you have
               //Precache all entities spawned at these, to prevent late caching(ingame stutters suck)
               UTIL_PrecacheOther("weapon_falcon2");
               UTIL_PrecacheOther("weapon_magsec4");
               UTIL_PrecacheOther("weapon_dy357");
               UTIL_PrecacheOther("weapon_dy357lx");
               UTIL_PrecacheOther("weapon_cmp");
               UTIL_PrecacheOther("weapon_cyclone");
               UTIL_PrecacheOther("weapon_laptopgun");
               UTIL_PrecacheOther("weapon_rcp120");
               UTIL_PrecacheOther("weapon_proximitymine");
               UTIL_PrecacheOther("weapon_remotemine");
               UTIL_PrecacheOther("weapon_timedmine");
       }

	void Think( void )
	{
	}
};

IMPLEMENT_NETWORKCLASS_ALIASED( WeaponSpawner, DT_WeaponSpawner )
BEGIN_NETWORK_TABLE( CWeaponSpawner, DT_WeaponSpawner )
END_NETWORK_TABLE()
BEGIN_PREDICTION_DATA( CWeaponSpawner )
END_PREDICTION_DATA()
BEGIN_DATADESC( CWeaponSpawner )
	DEFINE_KEYFIELD( m_iWeaponSlot, FIELD_INTEGER, "ForWeapon" ),
#ifndef CLIENT_DLL
	DEFINE_THINKFUNC( Think ),
#endif
END_DATADESC()
LINK_ENTITY_TO_CLASS(pd_weaponspawner, CWeaponSpawner);
PRECACHE_REGISTER(pd_weaponspawner);

Whenever a server host / adminstrator has to set up options for their server, the best idea is to use Console Variables ( CVARs ). For that tutorial, we will use six different CVARs to implement our dynamic weapons system. To make it tight, copy that piece of code at the very bottom of the file.

ConVar	mp_pdweapon1( "mp_pdweapon1", "1", FCVAR_NOTIFY|FCVAR_REPLICATED, "Weapon to create at weapon 1 areas" );
ConVar	mp_pdweapon2( "mp_pdweapon2", "1", FCVAR_NOTIFY|FCVAR_REPLICATED, "Weapon to create at weapon 2 areas" );
ConVar	mp_pdweapon3( "mp_pdweapon3", "1", FCVAR_NOTIFY|FCVAR_REPLICATED, "Weapon to create at weapon 3 areas" );
ConVar	mp_pdweapon4( "mp_pdweapon4", "1", FCVAR_NOTIFY|FCVAR_REPLICATED, "Weapon to create at weapon 4 areas" );
ConVar	mp_pdweapon5( "mp_pdweapon5", "1", FCVAR_NOTIFY|FCVAR_REPLICATED, "Weapon to create at weapon 5 areas" );
ConVar	mp_pdweapon6( "mp_pdweapon6", "1", FCVAR_NOTIFY|FCVAR_REPLICATED, "Weapon to create at weapon 6 areas" );

Now this is possible to let the server host set an integer value to those CVARs to set weapons. Another way to do it is to set them to strings. Thus the CVar would look like this:

ConVar	mp_pdweapon1( "mp_pdweapon1", "magsec4", FCVAR_NOTIFY|FCVAR_REPLICATED, "Weapon to create at weapon 1 areas" );

Using that you could check for what weapon goes with magsec4 and spawn it.

This seemed to be an annoyance when it was done for HL1, and how to check what weapon to spawn, a way to do this would check constants, though this would mean if you had to remove or add an weapon in the list you would have to change most of the values. So it's best to avoid this with an enum.

typedef enum
{
	SPAWN_OFF = 0,
	SPAWN_FALCON2,
	SPAWN_DY357,
	SPAWN_SHOTGUN,
	SPAWN_AR34,
	SPAWN_CALLISTO,
	SPAWN_RCP120,
} WeaponsToSpawn;


Getting it to work

Now everything is set up, it is possible to get it to work. The key resides in the entity Think() function. It's necessary to check what slot in the set this ent caters for, and what weapon is assigned to that slot. To do that this can be done.

int m_iWeaponSlot;

and

DEFINE_KEYFIELD( m_iWeaponSlot, FIELD_INTEGER, "ForWeapon" ),

It is an key value for a level designer to set, more on that later. We can now check for the weapon like so.

int BoundWeapon = 0;
switch (m_iWeaponSlot)
{
	case 1: 
		BoundWeapon = mp_pdweapon1.GetInt();
		break;
	case 2: 
		BoundWeapon = mp_pdweapon2.GetInt();
		break;
	case 3: 
		BoundWeapon = mp_pdweapon3.GetInt();
		break;
	case 4: 
		BoundWeapon = mp_pdweapon4.GetInt();
		break;
	case 5: 
		BoundWeapon = mp_pdweapon5.GetInt();
		break;
	case 6: 
		BoundWeapon = mp_pdweapon6.GetInt();
		break;
}

Now it comes clear what is supposed to be created. To create an entity this is called.

CBaseEntity::Create( "weapon_falcon2", GetLocalOrigin(), GetLocalAngles() );

So BoundWeapon is checked and spawns the appropriate item.

CBaseEntity * pEntity = NULL;
switch (BoundWeapon)
{
	case SPAWN_FALCON2:
		pEntity = CBaseEntity::Create( "weapon_falcon2", GetLocalOrigin(), GetLocalAngles() );
		break;
	case SPAWN_DY357:
		pEntity = CBaseEntity::Create( "weapon_dy357", GetLocalOrigin(), GetLocalAngles() );
		break;
	case SPAWN_SHOTGUN:
		pEntity = CBaseEntity::Create( "weapon_shotgun", GetLocalOrigin(), GetLocalAngles() );
		break;
	case SPAWN_AR34:
		pEntity = CBaseEntity::Create( "weapon_ar34", GetLocalOrigin(), GetLocalAngles() );
		break;
	case SPAWN_CALLISTO:
		pEntity = CBaseEntity::Create( "weapon_callisto", GetLocalOrigin(), GetLocalAngles() );
		break;	
	case SPAWN_RCP120:
		pEntity = CBaseEntity::Create( "weapon_rcp120", GetLocalOrigin(), GetLocalAngles() );
		break;
}

Also, if you are using the blank MP SDK, you will notice that weapons don't respawn if 'dropped' this seems to include creating an entity like this, if so call this afterwards.

pEntity->RemoveSpawnFlags( SF_NORESPAWN );

Getting it in-game

Ok, so this shiney new entity has been created, how is it going to work in game?

If you don't have your own mod FGD yet it might be time to make one. Create an ordinary text file with the .fgd extension and add this line to it.

@include "hl2mp.fgd"

If you don't use the hl2mp FGD, replace that with whatever fgd you use in your mod. Time to declare the new model ent.

@PointClass base(Angles) studio("models/w_rcp120.mdl") = pd_weaponspawner : "An Perfect Dark weapon spawn point"
[
	ForWeapon(integer)		: "For what weapon slot?" : 1   : "What weapon slot can use this point?(1-6)"
]
Note:Set a different model or icon, something you have

Now you can place one in hammer. Set the forweapon value to something between 1 and 6 and compile the map. Place one for each slot so you can see it all working.

Further

This is all well and good, but what if your admin doesn't know about this? What if he or she is a bumbling fool? You should keep in mind the old 1:10 ratio, assume anyone who uses or edits your program is 10x more stupid than you are. To help listen server users work this out you can add it to the server options menu when you create game. Open the settings_default.scr file in a text editor (it's not a windows screensaver) and add the following.

"mp_pdweapon1"
{
	"Weapon 1"
	{
		LIST
		"Disabled"	"0"
		"Falcon 2"	"1"
		"DY357"		"2"
		"Shotgun"	"3"
		"AR34"		"4"
		"Callisto NTG"	"5"
		"RCP-120"	"6"
	}
	{ "6" }
}

Then you should be a sport and add them to the server.cfg file when you distribute the mod, so any admin who goes to edit them might guess what they mean.

//Weapon Spawn settings:
mp_pdweapon1 1 //falcon 2
mp_pdweapon2 2 //magnum
mp_pdweapon3 3 //shotteh
mp_pdweapon4 4 //assault rifle
mp_pdweapon5 5 //alien wafflebat gun
mp_pdweapon6 0 //disabled

Reference

Since copy-pasting this whole entity might leave you with a bit of a tangle, here is the whole file.

If you have any problems you can leave a message in the original author's talk page or you can attempt to contact him on IRC at #ausbg on GameSurge.

See also