Varlock GameRules Pack Tutorial 2 of 4

 

GRPack (GameRules Pack) Tutorial 2 of 4: Adding Assault to Your Mod

This is the second tutorial in a four-part series which will help you implement Two-Team Deathmatch, Assault, CTF, and Domination gamemodes into your Half-Life mod. These tutorials were designed as a quick and easy way to add the four gamemodes listed above to a fresh SDK, so you don't have to keep re-inventing the wheel every time you want to use one of these gamemodes. As a result the source code is included; first, because there is so much of it a tutorial would be difficult to follow, and second because it was originally meant to be used as a bare bones addition to the Half-Life SDK. Once you've added the code listed here, you will likely want to modify some (if not all) of the code to fit your mod's needs.

You MUST have the Two-Team Deathmatch (Tutorial 1) source already installed for this tutorial to work properly. The >>gras.zip<< file included with this tutorial contains two zip files, server.zip and client.zip.

This Assault code contains mostly serverside code, but there is one small modification to the client dll. The source code for this tutorial is contained in the gras.zip file which you should already have. To install, simply unzip the serverside source code (server.zip) on top of the >>grtdm.zip<< code from Tutorial 1 (/Source/dlls). Make sure you add assault.h and assault.cpp to the project before you compile or they won't be included in the build. To add files to your project, open them with 'File | Open' and when the code appears, right-click anywhere on the code and choose 'Insert File into Project'.

The Assault gamerules set is basically Two-Team Deathmatch with two additions: capture points and a timer. The capture point entity makes up most of the new code in this tutorial. Bring up the server code, open assault.h, and take a look around. The first section of code here is the CInfoRulesAssault. This entity is responsible for holding information about the Assault gamerules which the mapper has set, such as total round time. It is derived from CBaseEntity and the only function we've chosen to override is the KeyValue function, so we can extract information from the map.

Next, there's CFuncCapturePoint. This entity is also derived from CBaseEntity and is a lot more complicated than the info entity above. You should be able to figure out what the functions and variables do from their names and the comments. The guts of this class can be found in CPThink.

The last thing in assault.h is the actual gamerules. It's derived from the CRulesTeamDM you installed in Tutorial 1. Why is it derived from CRulesTeamDM? There's a lot of power in class inheritance, and you can see it in action here. We don't have to reprogram any of the functions we created in CRulesTeamDM for our Assault gamerules. They already exist and are passed down to CRulesAssault through inheritance. Compared to CRulesTeamDM there's not a whole lot of functions here; only eight, and five of them are new! This means we've only had to reprogram three functions instead of the entire set!

Take a look at line 51. The string returned by GetGameDescription is the text you see in the server browser while you are looking for a game. You can change the text returned by this function to match your mod.

Here's the .fgd definitions for the new entities (you probably want to add these to your own .fgd file),

@PointClass base(Target) = info_rules_assault: "Assault Info" 
[
	attacker(choices) : "Attacking Team" : 1 =
	[
		1: "Red"
		2: "Blue"
	]
	roundtime(integer) : "Round Time (Minutes)" : 5
]

@SolidClass base(Target) = func_capture_point : "Capture Point"
[
	captime(integer) : "Capture Time (Seconds)" : 1
	name(string) : "Name" : "Point Name"
	reqpercent(integer) : "Required % of Team" : 50
]
Now open assault.cpp; the code in this file appears in the same order as in assault.h. The CInfoRulesAssault :: KeyValue function appears here. This function grabs the attacker and roundtime values from the map and sets the appropriate variables in CRulesAssault for future reference. The only time this entity is accessed is at the beginning of each map when Half-Life calls the KeyValue function for every entity. NOTE: Half-Life will crash if the info_rules_assault entity is present in a non-Assault map! Also, if there is more than one info_rules_assault in the map, the last one called by Half-Life will be the one the mod uses.

The next section of code is for CFuncCapturePoint. You should see the CPThink function first. This function contains the guts for the capture point. Let's take a look at it in detail. At the beginning of the function it sets up the variables, retrieves a pointer to the Assault gamerules, and performs a few important checks. After that, it checks to see how many players are inside the capture point with a simple loop through every player. Next, if an attacker is inside the capture point it checks to see if there are enough attackers present based on the capture point's 'reqpercent' value. Finally if there are enough people there it starts a timer to make sure they stay there for the required time. Once it's been captured, it fires the target entity, sets a few variables and tells the gamerules to check if all the CP's are captured.

Whew! Skip down to line 167 and the PrintToPlayers function. This function performs a ClientPrint with the passed message on any players inside the capture point. Next, the spawn function sets the capture point's origin and size based on the mapper's choice, sets it invisible and starts the CPThink cycle.

You may want to modify the logic of CPThink to fit your mod's needs. If you don't plan on having a timer or a player requirement, just delete the corresponding code.

Finally, there's the CRulesAssault gamerules. Most of these functions should be self-explanatory, but take a look at ClientCommand. We've added one more command, "updatetimer". When the server receives this command it updates the client's version of the round timer. For example, if you joined an Assault game half-way through a round, you would have no idea how much time is left. If you send an "updatetimer" message, your clock will be updated with the server's version and you'll have a correct timer! If for any reason your timer gets out of sync with the server, you can also send an "updatetimer" message.

You'll likely want to change the ResetRound function on line 309. This function is called with a single BOOL parameter. If it's TRUE, the capture points were captured, if it's FALSE, the round time ran out instead. There are several places in this function where you might want to award players more points, set them into some sort of spectator mode, or change their teams (ie. make the attackers now the defenders and vice versa). If you DO decided to switch the player's teams here, you will probably want to remove the 'attacker' variable from CInfoRulesAssault and modify the .fgd as well.

We're just about done the server now. Open up player.cpp and take a look at line 188. We've defined a new message, gmsgRoundTime, and on line 232 we register it. Now check out gamerules.cpp. On line 29 you'll need to uncomment the preprocessor call to include "assault.h" if it's not already done. Also, you'll need to uncomment lines 324 and 325 or else the Assault gamerules will never be created,

else if ( strncmp( szMapName, "as_", 3 ) == 0 )
	return new CRulesAssault( );
That's it for the server. Compile the new code and open up the client project. Unzip the client.zip source included with this tutorial over /Source/cl_dll. You'll need to add the roundtimer.cpp file to the project, just like you did assault.cpp and assault.h for the server. The changes here are very simple. A new HUD item has been added which receives the gmsgRoundTime message and displays the time left as a text string on the screen. You might want to change the position of the timer or make it use the number sprites instead of a text string, but I'll leave that up to you.