Jump to content

Home

How to add classes tutorial


Commodus

Recommended Posts

Okiedokie folks, here it is, short and sweet: how to add classes

to your mod. It isn't mine (it actually came from

this thread at Quake3World) so if you decide to use it, it is

only fair to credit Tim0tholt and the others in the thread who

helped with various things.

 

Razor_Ace said that the community should try to strengthen itself

and share knowledge. This is exactly what I'm doing.

 

Now, while there was nothing wrong with the tutorial @

Code3Arena, it changed your class according to model. I didn't

like that for various reasons (what happens if you add new

models?) so with the help of the thread, I was able to make a

system which will change your class via a console command.

 

Technically speaking it uses similar declarations as the class

tutorial on Code3Arena but it works slightly different.

 

The files that you will need to change are: cg_consolecmds.c,

g_local.h, bg_public.h, g_cmds.c and g_client.c .

 

First bg_public.h:

 

Go to some logical spot (I went to line 660, after the declaration

of the team_t enum) and stick in an enum declaration like this

one:

 

typedef enum {
PCLASS_SCOUT,
PCLASS_SNIPER,
PCLASS_SOLDIER,
PCLASS_DEMOMAN,
PCLASS_MEDIC,
PCLASS_HWGUY,
PCLASS_PYRO,
PCLASS_SPY,
PCLASS_ENGINEER,

PCLASS_NUM_CLASSES
} pclass_t; {

 

Do not forget the comma after the last enum (PCLASS_ENGINEER)

and do not forget PCLASS_NUM_CLASSES, otherwise it will cause

problems.

 

Second, g_local.h:

 

Now we are going to declare two variables in the

clientPersistant_t structure to store the current class of the

player and the next class of the player.

 

Go to line 333 in g_local.h and add

 

pclass_t	playerclass;
pclass_t	nextplayerclass;

 

underneath "qboolean teamInfo;"

 

What we are going to do is when the player changes his class,

we are going to stick the new class value (the player's class after

death) into the variable of type pclass_t called nextplayerclass.

Then, when the function in g_client.c ClientSpawn() is called,

nextplayerclass is stuck into playerclass, and depending on which

class the guy is, the person will get different weapons and other

goodness.

 

Third, cg_consolecmds.c:

 

Open it up and go to line 555 and add

trap_AddCommand ("changeclass");

under "trap_AddCommand ("loaddefered");".

 

After some looking around, I noticed trap_AddCommand() just

adds the string argument (i.e. for trap_AddCommand

("changeclass") the argument is "changeclass") to a tab-

completion list.... so if you forget how to change classes and you

type in change and you press TAB it will complete it for you. Or

that's what I think it does, I'm not exactly sure... I'm sure it does

more than that (I think it allows the client to send that command

to the server or something like that).

 

Stick it there and don't test it... it's better to be safe than sorry.

 

Fourth, g_cmds.c:

 

Open it up and on line 2717 add

	else if (Q_stricmp (cmd, "changeclass") == 0)
	Cmd_ChangeClass_f( ent );

 

It doesn't make a difference where you put the two lines in the if-

then-else tree but it has to be there. What it does is it checks if

the command you typed into the console is changeclass by using

stricmp (stricmp compares two strings and returns 0 if they are

equal, and something else if they're not.... very useful). If the

command is changeclass, then it runs the function

Cmd_ChangeClass_f().

 

Now, you have to define the function Cmd_ChangeClass_f()

somewhere. It doesn't matter where but it must be before the

function ClientCommand, which contains the if-then-else tree we

edited just now. If you put it after it, the compiler is going to think

that it doesn't exist (because it hasn't been defined yet).

 

I stuck mine on line 1722, after Cmd_Vote_f(). Copy and paste

this code but try to understand it as well.

 

void Cmd_ChangeClass_f (gentity_t *ent)
{
char *name;
int i;

// Get second parameter
name = ConcatArgs( 1 ); //I'm not really sure about this one - I think it gets the value of the first argument. if you passed 0 as an argument it would return the command itself i think...

Com_Printf("2nd parameter (%i chars) is %s\n", strlen(name), name); //this is just for debugging. remove it later when you feel it works without any glitches...

// the next part is a massive if-then-else tree. like i said b4, if two strings are equal, then the function Q_stricmp() should return 0. here, the argument of the changeclass command is checked against various names like scout, sniper, spy, etc. 
//if any of the expressions evaluates to true, then the corresponding class is assigned to the player. Otherwise it continues checking until it finds a matching class.
// if it finds that the class you want to change to doesn't exist, then it will give you a message "Invalid class <classname>" where <classname> is the class you gave.
if (Q_stricmp(name, "scout") == 0) {
	ent->client->pers.nextplayerclass = PCLASS_SCOUT;
}
else if (Q_stricmp(name, "sniper") == 0) {
	ent->client->pers.nextplayerclass = PCLASS_SNIPER;
}
else if (Q_stricmp(name, "soldier") == 0) {
	ent->client->pers.nextplayerclass = PCLASS_SOLDIER;
}
else if (Q_stricmp(name, "demoman") == 0) {
	ent->client->pers.nextplayerclass = PCLASS_DEMOMAN;
}
else if (Q_stricmp(name, "medic") == 0) {
	ent->client->pers.nextplayerclass = PCLASS_MEDIC;
}
else if (Q_stricmp(name, "hwguy") == 0) {
	ent->client->pers.nextplayerclass = PCLASS_HWGUY;
}
else if (Q_stricmp(name, "pyro") == 0) {
	ent->client->pers.nextplayerclass = PCLASS_PYRO;
}
else if (Q_stricmp(name, "spy") == 0) {
	ent->client->pers.nextplayerclass = PCLASS_SPY;
}
else if (Q_stricmp(name, "engineer") == 0) {
	ent->client->pers.nextplayerclass = PCLASS_ENGINEER;
}
else {
	Com_Printf("Invalid class %s \n", name);
	return;
}

Com_Printf("Your next player class is %s \n", name);
return;
} 

 

Now I can guarantee you that some coder who is more

knowledgeable in C will suggest a better way to make this thing

work. For the moment, it works pretty well. Another thing I might

get smacked for is Com_Printf - last time I heard it printed the

message to the server console (or everybody's console, not sure)

which might be problematic (i.e. everybody knows that you're a

spy or you don't know whether the changeclass command

actually worked or mucked up somewhere).

 

Fifth, g_client.c:

Now, open up g_client.c . This is the source file which is perhaps

the easiest to understand (for me at least)...

 

Go to the definition of the function ClientSpawn() (line 1585) and

add at the very beginning (after the

index = ent - g_entities;
client = ent->client;

bit)

 

 client->pers.playerclass = client->pers.nextplayerclass;

 

Now, scroll down to about line 1945, and add the following code

(this is to do with weapon changing according to class).

 

if (g_gametype.integer >= GT_TEAM) {
	client->ps.stats[sTAT_WEAPONS] &= ~((1 << WP_SABER) | (1 << WP_STUN_BATON));
	client->ps.stats[sTAT_WEAPONS] |= (1 << WP_BRYAR_PISTOL);

	// coders are probably thinking "WTF" because what I just did was take out
	// everybodies lightsabers and stun batons, and have given everybody a bryar pistol.
	// a player's weapons are stored in a 16-bit variable with every bit position
	//corresponding to a weapon - so if the very first bit is switched on, a person has the weapon corresponding to that bit (stunbaton)
	//the WP_ enumerators store the bit position of the weapon. They are declared in bg_weapons.h .
	//by doing ... &= ~((.... we are switching off the saber and stun baton bits so that nobody has them... yet. You need to have a good understanding of bitwise operators to understand what's going on.

	switch (client->pers.playerclass) 
	{
	case PCLASS_SCOUT:
		client->ps.stats[sTAT_WEAPONS] |= (1 << WP_BLASTER);
		client->ps.ammo[AMMO_BLASTER] = 100;
		break;
	case PCLASS_SNIPER:
		client->ps.stats[sTAT_WEAPONS] |= ((1 << WP_DISRUPTOR) | (1 << WP_SABER));
		client->ps.ammo[AMMO_POWERCELL] = 100;
		break;
	case PCLASS_ENGINEER:
		client->ps.stats[sTAT_WEAPONS] |= (1 << WP_BOWCASTER);
		client->ps.ammo[AMMO_POWERCELL] = 100;
		break;
	case PCLASS_HWGUY:
		client->ps.stats[sTAT_WEAPONS] |= (1 << WP_REPEATER);
		client->ps.ammo[AMMO_METAL_BOLTS] = 100;
		break;
	case PCLASS_SPY:
		client->ps.stats[sTAT_WEAPONS] |= (1 << WP_SABER);
			break;
	default:
		client->ps.stats[sTAT_WEAPONS] |= (1 << WP_SABER);
		break;
	}
}

 

Congratulations - you are finished! Do not be afraid to

experiment with the classes and give them different things. If you

want the model to change according to class, look in

ClientUserinfoChanged() in g_client.c and play around with the

things there to get the desired model (use Q_strncpyz to copy

model names into the variable containing the model name - I

*think* it's called model but I can't be sure).

 

Again, if you use it, credit the forumers who posted in the thread

mentioned at the beginning.

Link to comment
Share on other sites

Mind if I write that up into a tutorial and post it to my site? I don't have much of anything else yet (just a few other little write-ups), but I've been thinking about it.

 

Tutorials I plan to write up would be based on things that I've modded from other tutorials, or things that I've learned, or descriptions of main functions. I'd have to get permission from some of the original authors (such as the weather code from Q3f, Eternal's Jetpack code, the JediMod things, et cetera.)

 

I think one of the stipulations of JediMod's source should have been "if you use it, you must release your source to the public as well" - that would have been pretty spiffy, because I can't think of a mod that doesn't at least need Tchouky's sabers ... hmmm ...

 

Anyway, that's besides the point.

Link to comment
Share on other sites

Oh yeah sure. Your site was actually a help, I only found out at the last minute that the WP_ enums were in bg_weapons.h through your Doxygen'd source :D

 

Originally posted by Wudan

doesn't at least need Tchouky's sabers

 

That is quite ironic - the mod I'm making doesn't need Tchouky's saber stuff, nor does it need the dual saber stuff.... the only thing we really need is the hilt stuff, because we're going to have different sword-like weapons.

 

BTW, have you tried compiling the cgame code of JediMod? I tried the other day and it just wouldn't budge....

Link to comment
Share on other sites

Archived

This topic is now archived and is closed to further replies.

×
×
  • Create New...