
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 layoutaddlinetomenu(ent,"Select Your Class",0);addlinetomenu(ent,"Mercenary",1);addlinetomenu(ent,"Sniper",2); ent->client->menustorage.currentline=2;// Setup the User Selection Handlersetmenuhandler(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.