Lesson 3: Short Essay about most common functions in C++ for HL Coders |
|
|
A) Adding Player Constants/Functions
B) Define a variable
C) enums
D) If-then-else
E) Switch
F) Find player right before you
G) Find player by entity
H) Other conversions
I) Loops
J) Messages between server/client
K) Includes
L) Remarks
a) PLAYER.H
in class CBasePlayer
: public CBaseMonster
==> here you can add all you want without a problem. (Recompile if you do).
This is mostly usefull for common variables like, let me think .... how much
money you have? [Or in my mod -> what sort of skullcrushing thing/weapon
do you have?].
b)
#define <name_of_field> <field_value>
==> this let you define a value to a variable, most usefull when this var has
to be tweaked, so you can use in your code <name_of_field> in case of
<field_value>. The C++ compiler you use will AUTOMATICALLY convert the
name into the value upon converting. An example:
#define WEAPON_STONE
( 1 << 1 )
#define WEAPON_HAMMER ( 1 << 2 )
#define WEAPON_ANVIL ( 1 << 3 )
......................
Now in the code you can put the following:
CBaseEntity * Target;
if ( pPlayer->BeatedWith (Target, WEAPON_STONE) )
...........
-> what to do if the current player beated anything (CBaseEntity = something
in the world) with a stone.
else if ( pPlayer->BeatedWith (Target, WEAPON_HAMMER) )
.....
and so on
c) enum
<enum_name>
{
<name_of_first_item>
= 0,
<name_of_second_item>,
...
};
==> this is most usefull when have, let me think, well something that repeats
itself SO much & it are just vars that follow each other. An example: when using
skins, I got the followin enum:
enum teams_e
{
TEAM_NONE = 0,
TEAM_TOMS,
TEAM_JERRIES,
TEAM_OBSERVER,
TEAM_AUTO
};
it's MOSTLY the same as the following lines:
#define TEAM_NONE 0
#define TEAM_TOMS 1
#define TEAM_JERRIES 2
#define TEAM_OBSERVER 3
#define TEAM_AUTO 4
Well, at you the choice which form to use (I prefer enums when working with
more then 3 vars who are almost the same & the value it's given is not applicable).
Mostly this is used by efficient coders who wants that other ppl can read their
code.
Decide yourself: (first without defines/enum)
In CHalfLifeTeamplay
:: ClientCommand (whatever)
{
...
slot = atoi (...)
switch (slot)
{
case 1: ...
case 2: ...
case 3: ...
default: ...
}
...
Or would you rather prefer this version? (with enums/defines)
Again in
CHalfLifeTeamplay
:: ClientCommand (whatever)
{
...
slot = atoi (...)
switch (slot)
{
case TEAM_TOMS: ...
case TEAM_JERRIES: ...
case TEAM_OBSERVER: ...
default: ...
}
...
Mayhaps it's readable by you the first version, but not by me (i just have to
look through the whole code to understand that 1 = team_tommies etc etc.)
One last thing about enums => if the enum-vars are used all over the code,
define it at player.h, otherwise define it locally (i mean, only define it where
it should be used ==> you can make your computer happy doing that (aka minor
garbage-code)). If you define your enums in "player.h", and for example
you are working in "jumpbeatkick.cpp", and there's no include of "player.h"
[I'll come to that (including files & the meaning of that) later on], you're
getting a bunch errors (all saying, the variables you used (enum-vars) are recognised
within this file). Well, A way to solve this is just go to the top of the file,
and put there "extern
enum <enum_name>;". This says to the compiler to look for
your enum-vars somewhere else (you don't have to define where).
d) if
... then ... else
the if statement is like this:
if ( <condition> )
{
<true-part>;
}
else
{
<false-part>;
}
where <condition> is the condition (i'll come to that later on)
<true-part> is the code which will be executed when <condition>
is true (easy huh?)
<false-part>, yeah, just guess ;-p
Now about this condition ... how do we build it?
a condition is most likely to look like this:
<param1> <comparision> <param2>
pPlayer->teamID
== TEAM_TOMS : this just say if the player is actually in the team of
the tom's, the true part will be executed, else the false one will
<comparision> can be: == : strick equal
(2 =!!!)
>= : greater than or equal
<= : whatever ;-)
!= : NOT equal
REMARK!!!!!
this is for comparing NUMBERS only!! [remember the enum? that's a number too
;-] ]
If you just wonna see if a guy calls "DumbA$$" you have to do it like this:
if ( stricmp( pPlayer->pev->netname,
"DumbA$$") )
This will perform a STRING-comparision (stricmp means a case insensitive string
compare). If you just said
if ( pPlayer->pev->netname
== "DumbA$$" )
I think (be not sure, I'll should have to look up it first before given a proper
answer), when executing this code you'll just compare the first letter from
the player's name, with the first letter of "DumbA$$", and that's not what ya
wonna get huh?
ADVANCED CODERS: if you wonna know how to use == in the if-statement EVEN for
strings ... contact me! [email below]
Another form of the If ... Then ... Else is the following:
( pPlayer->teamID
== TEAMS_TOMS ) ? PutBeforeName ( pPlayer, "kitty_" ) : PutBeforeName
( pPlayer, "mouse_" );
That is EXACTLY the same as the following:
if ( pPlayer->teamID
== TEAMS_TOMS )
{
PutBeforeName ( pPlayer, "kitty_" );
}
else
{
PutBeforeName ( pPlayer, "mouse_" );
}
You can use the "<condition> ?
<truepart> : <falsepart>"
when there are only 2 possibilities. Of course you can use it to compare 12
possibilities, but then I recommend you using the if-statement, or even better:
the switch-statement. (see next)
e) in the previous part I had used
a commando called switch. It's just a handy command to make life easier.
I used:
switch (slot)
{
case TEAM_TOMS: ...
case
TEAM_JERRIES: ...
case
TEAM_OBSERVER: ...
default:
...
}
This is the same as:
if ( slot
== TEAM_TOMS )
{
<true-part>;
}
else
{
if ( slot == TEAM_JERRIES )
{
<true-part>;
}
else
{
if ( slot == TEAM_OBSERVER )
{
<true-part>;
}
else
{
<false-part>; -->
this is when you are in none of the teams
}
}
}
Well, it's ALWAYS your choice to choose ;-) But I would rather prefer the first
one, don't ya?
Well, we know now that switch is better, but when? I love to use it a lot ;)
it just looks cool. Hehe. But when exactly? Never/Ever/Sometimes. It's all up
to you, but efficient coders use it when more than 2 possibilities are being
compared with a single value. (like the slot with the teams).
The switch statement is as follows:
switch (<var_to_be_compared_with>)
{
case <value1>: <true-part>; break;
case <value2>: <true-part>; break;
...
default: <if_nothing_of_the_previous_values_matches-part>; break;
}
As you can see, before each new 'case'-statement, I use "break;". This tells
the code on execution that when it reach a "break;" it will stop the execution
of the switch statement (most likely to do because there could happen strange
things when not doing this). Consider this piece of code:
switch (slot)
{
case TEAM_TOMS: SetSkinModel (pPlayer, "cat"); break;
case TEAM_JERRIES: SetSkinModel (pPlayer, "mouse"); break;
case TEAM_OBSERVER:
default: SetSkinModel (pPlayer, "");
}
As you can see, if someone has joined the tom's, he gets the skin of a little
cute kitty ;-) If he's joining my mod as Jerry, then he gets the mouse skin
;-). Else if he decides to just look around a bit (join as spectator or invalid
choice), the model is randomly generated as a hammer, shoe, ... whatever. (It's
all be done in SetSkinModel.
REMARK!!!!!
DON'T ASK STUPID QUESTIONS ABOUT MY MOD!!! ;-pppp I'm just a Computer Wacko,
so?
f) How to find a player who's standing
right before you?
TraceResult
tr;
UTIL_MakeVectors( pev->v_angle + pev->punchangle );
Vector vecSrc = EyePosition();
Vector vecEnd = vecSrc + (gpGlobals->v_forward * RANGE_TO_LOOK_AT);
UTIL_TraceLine( vecSrc, vecEnd, dont_ignore_monsters, edict(), &tr);
if (tr.flFraction != 1.0)
{
if
( !FNullEnt( tr.pHit ) )
{
CBaseEntity
*pEntity = CBaseEntity::Instance( tr.pHit );
.......
This is the beginning of the code where you can find a player.
This creates a line (invisible) from your current eyeposition to your eyeposition,
but then RANGE_TO_LOOK_AT space farther 'forward' you. (remember the #define
!!)
In the UTIL_TraceLine, we provide this begin & endvector, we say that it may
NOT ignore monsters (even players are monsters, yes). We provide it with an
edict(), I'll explain as soon as I've an easy to understand summary. Last parameter
from UTIL_TraceLine is &tr, this is the place where our TraceResult will be
stored (attention for the '&'!!, this is essential, because otherwise, you won't
get a result back).
After that ... I'll do some checks if it hit someone ( tr.flFraction
!= 1.0 ) [if tr.flFraction = 1 then you haven't hit anything].
Again after that I see if what we hit is an entity ( a player, a box, ..., something
in the world ). Then you can begin to code ... just check what you hit ( here->
create a CBaseEntity which is the point you hit ), then in my code, i do a check
if his name is "Dumba$$" ... if so, he'll get killed at a random time
;-))
[THIS PART IS WRITTEN BY ME - BUT THE CODE & IDEA IS BY ANOTHER TUT ON http://www.valveworld.com]
g) HELP! I got an entity, how do
I get the player?
one possibility (most easy one) is to use this code =>
CBasePlayer *pPlayer
= (CBasePlayer *)pEntity
just check if it isn't NULL after this, 'coz this entity can be a crate or sth,
and after a cast (thats how we call the "(CBasePlayer *)"), it could be, coz
a crate isn't a player after all ;-).
h) other usefull conversions: [if
you don't understand ... just don't mind ... these are used by me a LOT, any
questions? Just mail me -> see below for email-adress]
--> edict_t
*pPlayerEdict = INDEXENT( playerIndex );
where pPlayerEdict is a pointer [the * means a pointer to ...] to edict_t and
playerIndex is just a number of a player [i think this is assigned when a player
joins the server, i'm not sure].
--> ENT(<edict_s>
or <entvars_s> or <int>)
takes any of the possibilities above & convert it to a CBaseEntity ...
--> int EntityIndex(
<entity> or <entvars_t *> or <edict_t> )
you get the number of the player out this function after providing it with one
of the three argument-types.
--> edict_t
*EntityFromIndex (entityIndex)
if you have the number of the player (entityindex), you can get the edict_t
pointer out of this function.
--> CBaseEntity
*FindEntityByClassname ( <startentity>, "name" )
--> CBaseEntity
*FindEntityByTargetname( <startentity>, "name" )
these 2 function are nearly equal, the only difference is that the first one
looks for a ClassName, the second for a TargetName. This is some food for mapmakers.
They can explain ya in detail what these 2 thingies do. I can explain it too,
but it isn't coding any more then (it's mapmaking ;-) ).
The <startentity> should be NULL,
or an entity (so you can loop throughout the map & look for all the same entities.
Some code for ya:
pSpot = FIND_ENTITY_BY_CLASSNAME
( NULL, naam );
if ( !FNullEnt( pSpot ) ) // it really exists 1 time
{
while
( !FNullEnt(pSpot) ) // loop through all the available spots
{
UTIL_TraceLine(
pSpot->v.origin, pSpot->v.origin + gpGlobals->v_up * 2, dont_ignore_monsters,
ENT(this->pev), &trace );
//
trace a line (seen before), and check if we starts in a solid
if
( trace.fStartSolid )
{
// if so -> BAD spot!
pNewSpot
= FIND_ENTITY_BY_CLASSNAME( pSpot, naam ); // find us a new one!
pSpot
= pNewSpot;
}
else
break;
// if it doesn't start in a solid, break out of the while!
...
rest of the code
just read the comments, it should be self-explaining.
Oh by the way, FIND_ENTITY_BY_CLASSNAME
and FindEntityByClassName
is the same!
i) the do-while & the for-loop
REMARK! DANGEROUS THINGIES!
as you've seen in (h) there was a while loop, it's format is as follows:
while (<if_this_is_true_continue_while_else_break_out>)
{
... code ...
}
if you wonna iterate through a bunch of numbers (like the amount of players
actually on your server), you're probably going to use the for-loop, it's WAYS
safer than the while (I've experience enough with that right now ;-) -> had
to restart my PC around 20 times, before I even saw that I had an infinite while-loop
;-) )
the format of the for-loop:
for (<do_this_before_the_for_loop_starts>;
<check_if_this_is_true_else_break_out>; <do_this_at_every_loop_at_the_end>)
{
... code ...
}
the for-loop is equal to
<do_this_before_the_for_loop_starts>;
while (<check_if_this_is_true_else_break_out>)
{
...
code ...;
<do_this_at_every_loop_at_the_end>;
}
I find by myself it's much easier (and safer ;-) ) to use the for-loop, this
'coz you won't forget to check or so.
Anyway ... this is too theoritical (good word?? dunno, don't car ;))
Code: [Player.cpp - function CBasePlayer::AddPointsToTeam]
for ( int i = 1;
i <= gpGlobals->maxClients; i++ )
{
CBaseEntity *pPlayer = UTIL_PlayerByIndex( i );
if ( pPlayer && i != index )
{
if ( g_pGameRules->PlayerRelationship(
this, pPlayer ) == GR_TEAMMATE )
{
pPlayer->AddPoints(
score, bAllowNegativeScore );
}
}
}
this for-loop sets at the beginning i = 1. At the end of each for loop,
1 is added to i (i++). Each loop you check if you haven't already reached the
maxClients (all the present players).
If you convert this for to a while, you get this:
int i = 1;
while ( i <= gpGlobals->maxClients )
{
CBaseEntity
*pPlayer = UTIL_PlayerByIndex( i );
if
( pPlayer && i != index )
{
if
( g_pGameRules->PlayerRelationship( this, pPlayer ) == GR_TEAMMATE )
{
pPlayer->AddPoints(
score, bAllowNegativeScore );
}
}
//
remember that i++ at the end of the for loop?
i++;
//
understand now how hard it's to find these faults?
}
j) Messages: how to send/declare
them?
This is meant to be a newbie-tutorial, so I don't gonna tell ya... perhaps later
I write another one, but for now ... just look at http://hlpp.valveworld.com
for one (I don't exactly now WHERE, but I'm pretty sure there will be some).
k) I mentioned above something
about includes and their purpose. Well, I hope you have already looked at the
Half-Life SDK? Well, you should have seen that it consists of many, many files.
To connect this files with each other, there is the include. Another way to
connect it with each other is to put ALL the files in 1 (it will run, i assure
;-) ). But then you have a text-file of around (in my case:) 3Meg.
Another reason why mod-programmers use different files is to keep different
things away from each other. An example: BigGuy had written a spectator-tutorial.
He could have put it just in player.cpp, but he hasn't. He made a new file called
observer.cpp, and put there all the relevant code for spectators.
Also it's easier to distinguis different classes from each other (like CBaseEntity
& CBasePlayer -> the 2 most important ones).
You've probably already remarked that there are 2 or 3 kind of files. This ending
on ".H" and this on ".CPP" or ".C". The first
are called header files, here comes mostly the definitions (#define, enum, ...)
and the classes into. In the .C & .CPP files comes the working-out of the
functions, declared in the .H-files. But! When you look at the top of the .CPP-file
you see probably #include
"<NAME_OF_HEADER_FILE>.H". This is an include ;-) [duh!].
I dunno what to tell more about these ... anyway ... JUST DON'T TOUCH the standard
includes UNLESS ya know for sure what you're doing! Expect many errors else.
l) Important Remarks:
--> ALWAYS put ";" at the end of a statement,
this can be more than 1 line, but when ending ... put a ";"
after it!
example:
CBaseEntity *pPlayer
= UTIL_PlayerByIndex( i );
is the same as:
CBaseEntity
*
pPlayer
=
UTIL_PlayerByIndex(
i
)
;
the C++ compiler clears all spaces/newlines/tabs/... and receives 2 times the
same statement : "CBaseEntity*pPlayer=UTIL_PlayerByIndex(i);"
--> "//"
is used to provide comments into your code ... all text after 2 slashes are
ignored ON THE SAME LINE!!
example:
if ( pPlayer &&
i != index ) // this is a test comment
{
--> "/*"
and "*/"
is comment field. ... All text between these is ignored
example:
/*
*
* This is a test comment ( *'s are not neccessary (only the beginning & ending
are), just nice ;-) )
*
*/
--> If you don't find any functions/files/any other stuff I used in this
tutorial, it's just normal ;-) I used my own code to explain a little.
If there's anything I can help ya with ... just mail me:
Name: Karel
Email: Half_life_fool@bigfoot.com
ICQ: 35217584
Homepage: http://www.computerwackos.com
Thx for reading until this point ;-) Have fun coding (it should be put on the
list of prohibited goods as a drug ;-))
Extra: Sorry 4 my bad English, I'm a Dutch-speaking person & don't know how
to talk/right [:-p] good in English
Any questions or suggestions
should be sent to me: KaReL