
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.