My tut for class base mods

 

Credit for most of the class code in this goes to Willi (of quakestyle fame)

First off, This is more an example of a use for the menu tut that I have done, than a replacement of any class tuts out there. You MUST have implemented my menu tut and null weapon tut for this to work. I suppose you could strip out the menu code and add your own selection code, but if you can do that, why the hell are you looking at this tut?

I'm only putting in 2 classes, but you should be able to see how to add your own. Also there is no special abilities attributed to the classes, only different weapon and armour combo's. This shouldn't be a problem as all you need to do is add a check to the special abilities that only let them activate for a specific class

You may want to get rid of items in the level (especially weapons), theres a bit at the bottom of the item placement tut about it. If I get any emails asking how, I'll probably write a v.quick tut on how to do it. 20 lines should be all i need, including all the markup for the pictures at the top of this page


The first thing that needs to be done is for a varible to be added to the player's data structure to store which class they are. An integer is all that is really needed. Find the code below in g_local.h

        qboolean       spectator;                     // client is a spectator
} client_respawn_t;

This is part of the client data structure that doesn't change from level to level. Modify it to read

        qboolean       spectator;                     // client is a spectator
        int            class;                         // Player Class
} client_respawn_t;

putting it here means that they don't need to change class that often.


Now that we have the variable, we need some way to set it. A menu should do it perfectly. Create a new file, call it p_class.c or something similar. Add it to your project/makefile.

This is where we will put as much of the player class handling code as possible. It's neater this way and easier to maintain

Add the lines below to it. it will allow us to access the rest of the code

#include "g_local.h"
#include "p_class.h"

The next couple of functions handle the selection of class by the player add them to the file, I'll explain after them


void class_selected(edict_t *ent,int choice)
{
switch (choice){
case 1:
gi.centerprintf(ent,"Kill some people,\n and protect your comrades back,\n Mercenary\n");break;
case 2:
gi.centerprintf(ent,"One shot, One kill \n That is your creed,\n Sniper\n");break;
}
ent->client->resp.class=choice;
Class_Selected(ent);
}
 
void select_class(edict_t *ent)
{
 
// Check to see if the menu is already open
 
if (ent->client->showscores || ent->client->showinventory || ent->client->menustorage.menu_active)
        return;
 
clearmenu(ent);
 
// send the layout
addlinetomenu(ent,"Select Your Class",0);
addlinetomenu(ent,"Mercenary",1);
addlinetomenu(ent,"Sniper",2);
 
ent->client->menustorage.currentline=2;
// Setup the User Selection Handler
setmenuhandler(ent,class_selected);
showmenu(ent);
 
}

The code above should be fairly self explanatory. Class_selected handles what happens when we actually pick a class, allowing the player movement and giving them weapons.

Now for some background work, mostly stopping them from moving and preventing them picking up any weapons. as its the easiest way to code it, this will also include the weapon selection for the players once they have a class


Find InitClientPersistant in p_client.c ; It handles the allocation of equipment to players. we are going to redirect all the code to a new function in p_class.c called Equip_player.

comment out all the code below in InitClientPersistant (commenting it out makes it easier to undo)

    item = FindItem("Blaster");
    client->pers.selected_item = ITEM_INDEX(item);
    client->pers.inventory[client->pers.selected_item] = 1;
 
    client->pers.weapon = item;
 

The code above gives the player a blaster. Just below the code you just commented out add the line below

Equip_Player(client);

I'll get back to this function in a small while. Find Player_Die and add the line below just after the variable declarations

self->svflags &= ~SVF_NOCLIENT;

This prevents the player from being invisible after dying. It shouldn't happen, but we want to be sure don't we?

Remove TossClientWeapon (self); from Player_Die. Doing so means that their weapon isn't thrown for their killer to pick up

Now we should set up the special cicumstances for a player without a class. namely, invisiblity, intangibility, and immobililty(not actually been able to do this. currently they are noclipping). Find ClientThink (same file). Add the code below to it after the line client->ps.pmove.gravity = sv_gravity->value;

 
 
 
  if (ent->client->resp.class < 1)
 
{
        ent->solid = SOLID_NOT;
        ent->movetype = MOVETYPE_NOCLIP;
        ent->svflags |= SVF_NOCLIENT; 
 
}

All it does is check to see if they have a class and prevent them interacting with the world if they don't


Now to the real meat of the tut. Weapon allocation and the additon to the active world

Add the code below to p_class.c

void Class_Selected(edict_t *ent)
{
    ent->movetype = MOVETYPE_WALK; 
    ent->solid = SOLID_BBOX; 
    ent->svflags &= ~SVF_NOCLIENT; 
    PutClientInServer (ent);
 
    if (level.intermissiontime)
    {
        MoveClientToIntermission (ent);
    }
    else
    {
        // send effect
        gi.WriteByte (svc_muzzleflash);
        gi.WriteShort (ent-g_edicts);
        gi.WriteByte (MZ_LOGIN);
        gi.multicast (ent->s.origin, MULTICAST_PVS);
    }
 
    if (ent->client->resp.class == 1)
        gi.bprintf (PRINT_HIGH, "%s is a Mercenary\n", ent->client->pers.netname); 
    else if (ent->client->resp.class == 2)
        gi.bprintf (PRINT_HIGH, "%s is Sniper\n", ent->client->pers.netname);  
} 
 
void Equip_Player(gclient_t *client)
{
gitem_t         *item;
 
if (client->resp.class == 1) 
{
        item = FindItem("Blaster");
        client->pers.inventory[ITEM_INDEX(item)] = 1;
        item = FindItem("Bullets");
        client->pers.inventory[ITEM_INDEX(item)] = 200;
        item = FindItem("Machinegun");
        client->pers.selected_item = ITEM_INDEX(item);
        client->pers.weapon = item;
        client->pers.inventory[ITEM_INDEX(item)] = 1;
        item = FindItem("Combat Armor");
}
else if (client->resp.class == 2)
{
        item = FindItem("Blaster");
        client->pers.inventory[ITEM_INDEX(item)] = 1;
        item = FindItem("Body Armor");
        client->pers.inventory[ITEM_INDEX(item)] = 30;
        item = FindItem("Slugs");
        client->pers.inventory[ITEM_INDEX(item)] = 50;
        item = FindItem("Railgun");
        client->pers.weapon = item;
        client->pers.inventory[ITEM_INDEX(item)] = 1;
        client->pers.selected_item = ITEM_INDEX(item);
}
else 
{
item = FindItem("No Weapon");
client->pers.selected_item = ITEM_INDEX(item);
client->pers.inventory[client->pers.selected_item] = 1;
client->pers.weapon = item;
}
}

Thats us finished with p_class.c; There's only a few things left to do.


Go back to p_client.c and find the function ClientBeginDeathmatch. comment out the code below

 
 
 
    if (level.intermissiontime)
    {
        MoveClientToIntermission (ent);
    }
    else
    {
        // send effect
        gi.WriteByte (svc_muzzleflash);
        gi.WriteShort (ent-g_edicts);
        gi.WriteByte (MZ_LOGIN);
        gi.multicast (ent->s.origin, MULTICAST_PVS);
    }
 
    gi.bprintf (PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname);
 
    

The code above is handled by Class_Selected

below the code you just commented out, add the code below

        ent->client->ps.gunindex = 0; 
        gi.linkentity (ent); 

This connects the player to the game.

Now what you need to do is give the players a way to access the menu to select the class they want to be. Open g_cmds.c and scroll down to

        else if (Q_stricmp(cmd, "playerlist") == 0)
               Cmd_PlayerList_f(ent);
        else    // anything that doesn't match a command will be a chat
               Cmd_Say_f (ent, false, true);

It should be at the end of the file. modify it to read

        else if (Q_stricmp(cmd, "playerlist") == 0)
               Cmd_PlayerList_f(ent);
        else if (Q_stricmp(cmd, "select_class") == 0)
               select_class(ent);
        else    // anything that doesn't match a command will be a chat
               Cmd_Say_f (ent, false, true);

That means a player can now get a class, or change it. you may wish to modify the class selection functions to remove a frag from the player if they change class when they already have one.


The last thing to do is to create a header file and include it in all the files that will need it. copy the code below into a new file and call it p_class.h

void select_class(edict_t *ent);
void Equip_Player(gclient_t *client);
void Class_Selected(edict_t *ent);
 

Add the line below to p_client.c and g_cmds.c just after the line #include "g_local.h"

#include "p_class.h"

And you are done. Enjoy.


Resources


Skunkworks Tutorial