Articles:
OpenGL:
GL : Mouse Position to 3d Space

JA/JO:
JA : Getting Started
All : Qboolean
All : Bit Flags
All : Vector Math
JKA : Saber Cycle BugFix
JK2 : Classes
JK2 : Ladders
All : FontDat File Info
JK2 : Breakdown
JK2 : PM_ Anim

Dox:
JK2 : Source
JKA : Source

Related:
LF : JA Coding
LF : OJP Forum
LF : General Editing
Code3Arena
Stanford CS Library

Ancient
3.2004
2.2004
1.2004
12.2003

You are visitor #140034.
(since 11/01/2005).
You are unique visitor #32745.
(since 11/01/2005).
How to Add Classes - Commodus

Okiedokie folks, here it is: 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.

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 around 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 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 properly.

// 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 " where 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 - add:
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 this in your mod, credit the posters in the Q3W thread (i__morpheus__i , TimOtholt, Tyro) for this.... Inform me if anything doesn't work and I'll do my best to help you out.
Comments and Suggestions Follow: