|
from: http://planethalflife.com/fixxxer/hl_seek.htm
So, you want to customise half-life to suit your own needs? Well, you can start by downloading the source code. Go to Wavelength and get it at once!
--------------------------------------------------------------------------------
NOTICE:All the tutorials found on this page are designed to be written and compiled using MS Visual C++ 5 or greater. The learning edition is available at around £40 for students and £70 otherwise. I would recommend you get hold of a copy if you want to edit half-life since Valve's project files were done using Visual C++.
--------------------------------------------------------------------------------
Finding your way around
Now that you've read my other two tutorials, and probably browsed around the source a bit I expect your mighty confused! The Half-Life source code initially looks a lot more complicated than it is, this is because in C++ you have many objects but no idea where these objects are createrd or what they arre used for. You can start off by opening the original gmae dll source code (not the stuff from the last tutorial) in Visual C++.
Where it all begins
First of all open the 'h_export.cpp'. This is a short file, but is, in essence, where the whole DLL begins. The function DllMain is a standard entry point for DLL's (much like the main function or WinMain function). You will notice that DllMain doesn't actually do anything, why you may ask? How does the game know what to do with it all if the entry point does nothing. Well, now open the file mp.def, you will se that it includes these lines:
EXPORTS
GiveFnptrsToDll @1
This tells the compiler that a function called GiveFnptrsToDll needs to be exported, if you go back to 'h_export.cpp' you will notice the implementation of this function. This function is called by the engine to tell the DLL about certain information. There are two key blocks, the engine functions and the global variables. Def files are special files used when writing your own DLLs which allow you to export special information and functions.
The engine functions allow the DLL to access information in the game such as information about map layout and network messages, whilst hideing the complexities involved behind these functions. The global variables provide data which is shared between the engine and the dll such as level time.
But, of course, it has to be more compilcated than that, there have to be times when the engine tells the dll to do something. If you open up 'c_base.h' and scroll to line 56 you will see this code:
extern "C" EXPORT int GetEntityAPI( DLL_FUNCTIONS *pFunctionTable, int interfaceVersion );
The use of the EXPORT keyword here indicates that this function is also exported from the DLL. You will find the implementation on line 75 of 'c_base.cpp'. So what does this function do> Well, if you look at the implementation you will see that it copys the global dll variable gFunctionTable intop the parameter pFunctionTable. Why? Well, gFunctionTable contains a load of functions the DLL needs to tell the engine about, it includes pointers to these functions so that the engine can call them at the appropriate times. These functions are defined in client.cpp and are listed below:
GameDLLInit, // Called to initalise the DLL
// I'm not sure what these do (yet)
DispatchSpawn,
DispatchThink,
DispatchUse,
DispatchTouch,
DispatchBlocked,
DispatchKeyValue,
DispatchSave,
DispatchRestore,
DispatchObjectCollsionBox,
// Called to Save and Restore information (used in saveing games)
SaveWriteFields,
SaveReadFields,
// Used to save, restore and reset the level data (I think this is used between levels to backup
// the old data and clear the new, when ou return to a level it is restored)
SaveGlobalState,
RestoreGlobalState,
ResetGlobalState,
ClientConnect, // A client connects (you can reject them here)
ClientDisconnect, // A client disconnected (you clean up afteer them here)
ClientKill, // A client types the 'kill' command at the console (I think)
ClientPutInServer, // Called every time a client spawns or respawns
ClientCommand, // Called whenever a client executes a command (you can add your own commands with this)
ClientUserInfoChanged, // Called whenever a client changes their custom data (such as their name or model)
ServerActivate, // Starts a server (?)
// Called at the start of the player think cycle (does most of the work) (before StartFrame)
PlayerPreThink,
// Called at the end of the player think cycle (after StartFrame)
PlayerPostThink,
// Starts a new frame, does things such as tells each object to run it's 'think' function
StartFrame,
// I think this is called when a new level is started
ParmsNewLevel,
// If that's the case then this is when the level is changed
ParmsChangeLevel,
GetGameDescription, // Returns string describing current .dll game.
PlayerCustomization, // Notifies .dll of new customization for player.
SpectatorConnect, // Called when spectator joins server
SpectatorDisconnect, // Called when spectator leaves the server
SpectatorThink, // Called when spectator sends a command packet (usercmd_t)
So now you'vwe seen where the DLL and the Engine overlap. You should be able to see that the engine gives the DLL access to a number of it's functions and some of it's global variables and that the DLL gives the engine access to some of the DLL's functions. However, the class based system masks many of the above functions through the use of classes. In important class is CGameRules, CGameRules is the base class for game types (it is not used itself). In the standard game code you will see 3 game types, CHalfLifeMultiplay, CHalfLifeRules and CHalfLifeTeamplay which handle multi-player games, single player games and teamplay games repsectivley.
As you will have seen though classes need to be created before they can be used, these CGameRules classes are created by the function InstallGameRules on line 304 of gamerules.cpp (this function is called when the world entity is spawned, see later for information on entities). The InstallGameRules function examines the console variables (or cvars) to decide which type of game rules should be applied (notice how, for instance, the magnum does not have a laser sight , or that the crossbow does not fire explosive darts in single player) and creates the appropriate class. This is an example of a base class pointer being used to point to derived classes, the pointer is the same for each type of game (CGameRules *) but the data contained is not, each gme type can define it's own implementation of the pure virtual functions in CGameRules.
WARNING : If you are not using MS VC++ some of this may not make sense to you, basically I am describing the class listing provided by VC++, if your program has similar functionality then that's where to look. If not then finding your way around the source may be tough, but look at the file names and attemp to match them to the classes I describe.
So let's have a look what the CGameRules derivates actually do. If you expand ClassView under the 'mp classes' item and then expans the CHalfLifeMultiplay class you will see a number of functions, double click a few of them to see what their definitions look like. There are various functions for adding points to players, ammo, determining if clients should be allowed to connect etc. etc. You should be able to see that customization of these classes allows you to modify the way the game works on a glboal scale. When you want to make a change which affects every player or the whole world, such as increasing the score that gets added from 1 to 2, this is where you'd make it. You can even customize InstallGameRules to create your own custom derivative of CGameRules - allowing you to add more than one set of rules to your mod.
Objects and things
So far I've show you the entry points to the DLL, and the main classes for handleing a Half-Life game, what I haven't done is explaine how all those objects get into the game and how the game knows what they do. In this section I'm going to introduce the way Half-Life handles objects in the game DLL.
Let's start by looking at an object, The Egon. Find the CEgon class in class view and expand it. You will see a list of member functions and variables, some of them such as 'PrimaryAttack' have clear uses, but this still doesn't explain how they get used. Now open up the fiule 'egon.cpp', we are going to take a look at how the Egon is implemented as an item.
At the top of the file you will first see a number of #includes which give egon.cpp access to global variables and class definitions. If you were to add your own object you may well just copy this list of includes to allow your file to access similar global variables and objects. You will then see a number of #defines which make changing constant values for the Egon quick and simple, it is always a good idea to use defines for constant values rather then just the values themselves because this makes it easier to change EVERY instance of the value at once. You will then see an Enum called egon_e - this defines the animations for the egon, although the names are unimportant the order and values are since they are designed to match up with the order and values contianed in the model file for the egon (a model file defines the 'geometry' of an object, what it looks like, how it animates etc. etc.).
Then comes the class definition for CEgon, you will notice that it is derived from the CBasePlayerWeapon class. This is where you get your first glimpse of how the game works. From what you have seen in the derivates of CGameRules you should be able to see that weapons are probably handled in a similar way - with basic functions defined in the 'CBasePlayerWeapon' class but each weapon overriding the default behaviour to handle how they perform the actions. This can be seen with the 'PrimaryAttack' and 'SecondaryAttack' (CEgon does not have a secondary fire, but in the future one of my tutorials will show you how to add one) functions which are called from the CBasePlayerWeapon pointer to handle primary and secondary fires for the weapons. In fact the entire object heirarchy is derived from a single object, CBaseEntity:
Object Heirarchy
This shows only a small part of the entire object heirarchy
CBaseEntity
CBaseDelay
CBaseToggle
CBaseItem
CBaseMonster
CBaseCycler
CBasePlayer
CBaseGroup
CBaseAnimating
CBasePlayerItem
CBasePlayerWeapon
CEgon
As you can see, CEgon is a long way down the heirarchy, but still derived from CBaseEntity - meaning it has access to all the members and functions of an Entity. Now, as we contine through the file we come to the most important line is any object definition:
LINK_ENTITY_TO_CLASS( weapon_egon, CEgon );
The LINK_ENTITY_TO_CLASS macro tells the engine that whenever a map file defines an object called 'weapon_egon' that it should use the class 'CEgon' to handle all of that objects functions (we will look at adding items to the map editor in a future tutorial, meanwhile one may be found on Wavelength) . Because CEgon is derived from CBasePlayerWeapon the game knows that it is a player weapon and because CBasePlayerWeapon is derived from CBasePlayerItem it knows that it is a player item and may be carried.
The final piece of information I want to describe is 'Think' functions. 'Think' functions always have a definition of void ClassName::ThinkName( void ) and are called each 'frame' to handle whatever 'thinking' an object needs to do.
There are two critical values related the think functions, the 'next think time' and the 'current think function'. The following example illustrates how a Snark sets it's 'HuntThink' function.
// In CSqueakGrenade::Spawn( )
// This line sets the think function to the member function 'HuntThink'
SetThink( HuntThink );
// This line sets the next think time to 0.1 seconds from the current time (that's one frame from now, since one frame for the game DLL is 0.1 seconds)
pev->nextthink = gpGlobals->time + 0.1;
// In CSqueakGrenade::HuntThink()
// Every time the think function is called the next think is reset, in order for our think function to be called again we need to set nextthink again
pev->nextthink = gpGlobals->time + 0.1;
Of course you can set nextthink to any time you want, for example you could have a grenade blow up in three seconds by setting the think to a custom function such as 'BlowUp' and the next think time to 'gpGlobals->time + 3.0'. Any function may be used for thinking as long as it has a void return type and takes no parameters.
Well, that's it for this tutorial so far, of course as I delve further into the DLL I'll add more information here, but I hope I've given you enough information to be able to find your way around the DLL now. I'd suggest double clicking a few classes, looking at what they are derived from and what functions they share in common. Try to get a 'feel' for where certain actions are handled in the game, look for where death messages are presented and search for function names to see where they are called and used. You may want to get familiar with the CBasePlayer class which handles all the players actions - take a good look at the PreThink function because this is where much of the work is handled.
The Client DLL
You thought that was it didn't you! Well, this is the newest addition to this page, it describes how the client dll works. You can start by opening up the client dll workspace.
If your using MS Visual C++ the first thing you will notice is that it contains far fewer classes than the game DLL and that most of these classes are related to the HUD. This is because the client DLL is responsible for handling all of the HUD graphics (communication between the Client and the Server will be handled in a future tutorial). I am not really going to look at the entry points here, if you are interested then you can search for similar function names to those in the game DLL or look at files which are not related to a particular class.
The two main classes are the CHud class and the CHudBase class, I'll deal with each in turn.
The CHud class is responsible for drawing each of the HUD elements if it wants to be drawn and generally handling the game HUD and global functions. If you did look up those entry points you would probably find that the global variable gHud (a CHud) is referenced.
However, the whole HUD is not incorperated into the CHud, instead each HUD element is given it's own class and this class is added to a linked list of elements in CHud to be drawn. This is where the CHudBase class makes an enterance, since the linked list is actually a linked list of CHudBase elements, but each CHudBase element points to a derived class such as CHudScoreboard - another example of pointers to a base class being used to access their children. If you want to add your own HUD element then the simplest thing to do is look at one of the most basic elements such as CHudTrain (this displays the train controls when you are driving a train), searching for CHudTrain brings up the following list:
Searching for 'CHudTrain'...
E:Adam's Data FilesStandardSDKSourcecl_dllhud.h(175): class CHudTrain: public CHudBase
E:Adam's Data FilesStandardSDKSourcecl_dllhud.h(566): CHudTrain m_Train;
E:Adam's Data FilesStandardSDKSourcecl_dlltrain.cpp(30): int CHudTrain::Init(void)
E:Adam's Data FilesStandardSDKSourcecl_dlltrain.cpp(41): int CHudTrain::VidInit(void)
E:Adam's Data FilesStandardSDKSourcecl_dlltrain.cpp(48): int CHudTrain: raw(float fTime)
E:Adam's Data FilesStandardSDKSourcecl_dlltrain.cpp(72): int CHudTrain::MsgFunc_Train(const char *pszName, int iSize, void *pbuf)
6 occurrence(s) have been found.
You will notice that only two of these items are outside the train.cpp definition file for the CHudTrain class, and that one of these is the declaration of the CHudTrain class (class CHudTrain : public CHudBase). The other is a member variable of the CHud class, called m_Train. With this info it is a good idea to see where m_Train is used, so we do a search for m_Train:
Searching for 'm_Train'...
E:Adam's Data FilesStandardSDKSourcecl_dllhud.cpp(100): m_Train.Init();
E:Adam's Data FilesStandardSDKSourcecl_dllhud.cpp(230): m_Train.VidInit();
E:Adam's Data FilesStandardSDKSourcecl_dllhud.h(566): CHudTrain m_Train;
E:Adam's Data FilesStandardSDKSourcecl_dlltrain.cpp(27):DECLARE_MESSAGE(m_Train, Train )
4 occurrence(s) have been found.
Looking at this we see that the absolute minimum number of things we have to do to add a class is three (plus defining and implementing our new class) since DECLARE_MESSAGE is specific to the CHudTrain class (it is related to how the client and server communicate and will be dealt with is a future tutorial):
Add a member variable to the CHud class for our new element
Add a call to our classes Init( ) function in CHud::Init( )
Add a call to our classes VidInit( ) function in CHud::VidInit( )
We also have to:
Define our class in hud.h
Implement the Init( ) function in our classes implementation file
Implement the VidInit( ) function in our classes implementation file
Implement the Draw( float flTime ) function in our classes implementation file
That's it for the client DLL for now, don't forget to check back for more information on adding your own HUD componant!
|
|