[page=Introduction]
Welcome to another one of my tutorials! This tutorial will show you how to implement easy classes into your mod. There is another, harder, way of doing this, but I won't get much into that.
Here we go!
[page=Implementing the Classes]
Alright, first of all, we need to define the integer so it stays across respawns! go to your client_respawn_t struct, and change it so it looks like this:
int game_helpchanged;
int helpchanged;
int class; // What class am I?
Done! Let's go to the next part.
[page=Adding the Command]
Head down in g_cmds to ClientCommand and change the bottom part to look like this:
else if (Q_stricmp (cmd, "wave") == 0)
Cmd_Wave_f (ent);
else if (Q_stricmp (cmd, "class1") == 0) // you can change this
{
ent->client->resp.class = 1;
EndObserverMode(ent);
}
else if (Q_stricmp (cmd, "class2") == 0) // same here
{
ent->client->resp.class = 2;
EndObserverMode(ent);
}
else if (Q_stricmp (cmd, "class") == 0)
{
if (ent->client->resp.class == 1)
gi.cprintf(ent, PRINT_HIGH, "You are Class 1.\n"); // change the text if you want
else if (ent->client->resp.class == 2)
gi.cprintf(ent, PRINT_HIGH, "You are Class 2.\n"); // same here
else
gi.cprintf(ent, PRINT_HIGH, "You are an OBSERVER.\n"); // and here
}
else // anything that doesn't match a command will be a chat
Cmd_Say_f (ent, false, true);
Remember, there are things you can change to effect how the classes work, like names and such.
[page=The Weapons for each class]
Since the EndObserverMode (you'll see it next) calls to PutClientInServer, it runs through InitClientPersistant to check for the weapons. Let's change it to effect the teams so we can change the weapons around for each. Change InitClientPersistant to this:
/*
==============
InitClientPersistant
This is only called when the game first initializes in single player,
but is called after each death and level change in deathmatch
==============
*/
void InitClientPersistant (gclient_t *client)
{
if (client->resp.class == 1)
{
//Class 1
gitem_t *item;
memset (&client->pers, 0, sizeof(client->pers));
item = FindItem("Blaster");
client->pers.selected_item = ITEM_INDEX(item);
client->pers.inventory[client->pers.selected_item] = 1;
item = FindItem("Jacket Armor");
client->pers.selected_item = ITEM_INDEX(item);
client->pers.inventory[client->pers.selected_item] = 25;
item = FindItem("Rockets");
client->pers.selected_item = ITEM_INDEX(item);
client->pers.inventory[client->pers.selected_item] = 30;
item = FindItem("Rocket Launcher");
client->pers.selected_item = ITEM_INDEX(item);
client->pers.inventory[client->pers.selected_item] = 1;
client->pers.weapon = item;
}
else if (client->resp.class == 2)
{
//Class 2
gitem_t *item;
memset (&client->pers, 0, sizeof(client->pers));
item = FindItem("Blaster");
client->pers.selected_item = ITEM_INDEX(item);
client->pers.inventory[client->pers.selected_item] = 1;
item = FindItem("Combat Armor");
client->pers.selected_item = ITEM_INDEX(item);
client->pers.inventory[client->pers.selected_item] = 50;
item = FindItem("Slugs");
client->pers.selected_item = ITEM_INDEX(item);
client->pers.inventory[client->pers.selected_item] = 30;
item = FindItem("Railgun");
client->pers.selected_item = ITEM_INDEX(item);
client->pers.inventory[client->pers.selected_item] = 1;
client->pers.weapon = item;
}
else
{
//Observer mode, doesn't really matter what they have
gitem_t *item;
memset (&client->pers, 0, sizeof(client->pers));
item = FindItem("Combat Armor");
client->pers.selected_item = ITEM_INDEX(item);
client->pers.inventory[client->pers.selected_item] = 1;
client->pers.weapon = item;
}
client->pers.health = 100;
client->pers.max_health = 100;
client->pers.max_bullets = 200;
client->pers.max_shells = 100;
client->pers.max_rockets = 50;
client->pers.max_grenades = 50;
client->pers.max_cells = 200;
client->pers.max_slugs = 50;
client->pers.connected = true;
}
You can change and add new weapons as you wish to there, even add ammo if you want, but make sure: Unusuable items should be at the top of the new items, and the weapon you want him to start with at the bottom of the list.
[page=Let Me Play!]
Alright, head over to ClientBeginDeathmatch. Above it, add this in:
void EndObserverMode(edict_t* ent)
{
ent->movetype &= ~MOVETYPE_NOCLIP;
ent->solid &= ~SOLID_NOT;
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 Class 2\n", ent->client->pers.netname); // change if you want
else if (ent->client->resp.class == 2)
gi.bprintf (PRINT_HIGH, "%s is Class 1\n", ent->client->pers.netname); //same here
}
[page=A few last minute changes]
Alright, now we have some stuff to change so there are no bugs!
Go to Player_Die, after int n; add this:
self->svflags &= ~SVF_NOCLIENT;
Also, change ClientBeginDeathmatch to this:
/*
=====================
ClientBeginDeathmatch
A client has just connected to the server in
deathmatch mode, so clear everything out before starting them.
=====================
*/
void ClientBeginDeathmatch (edict_t *ent)
{
G_InitEdict (ent);
InitClientResp (ent->client);
// locate ent at a spawn point
PutClientInServer (ent);
ent->client->ps.gunindex = 0;
gi.linkentity (ent);
gi.bprintf (PRINT_HIGH, "%s entered the game\n", ent->client->pers.netname);
// make sure all view stuff is valid
ClientEndServerFrame (ent);
}
Now, on to ClientThink. Find this:
if (ent->movetype == MOVETYPE_NOCLIP)
client->ps.pmove.pm_type = PM_SPECTATOR;
else if (ent->s.modelindex != 255)
client->ps.pmove.pm_type = PM_GIB;
else if (ent->deadflag)
client->ps.pmove.pm_type = PM_DEAD;
else
client->ps.pmove.pm_type = PM_NORMAL;
client->ps.pmove.gravity = sv_gravity->value;
pm.s = client->ps.pmove;
Chage it to this:
if (ent->movetype == MOVETYPE_NOCLIP)
client->ps.pmove.pm_type = PM_SPECTATOR;
else if (ent->s.modelindex != 255)
client->ps.pmove.pm_type = PM_GIB;
else if (ent->deadflag)
client->ps.pmove.pm_type = PM_DEAD;
else
client->ps.pmove.pm_type = PM_NORMAL;
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;
}
pm.s = client->ps.pmove;
You're done now! You now have a perfect Class-Based mod. There are a few things you can do with it, so let's head onto the Modifications!
[page=Modifications]
Firstly, we don't want weapons laying around on the map if they start with their own! If you think so too, change the first part of SpawnItem to this:
/*
============
SpawnItem
Sets the clipping size and plants the object on the floor.
Items can't be immediately dropped to floor, because they might
be on an entity that hasn't spawned yet.
============
*/
void SpawnItem (edict_t *ent, gitem_t *item)
{
if (deathmatch->value)
{
if (strcmp(ent->classname, "weapon_shotgun") == 0)
{
ent->classname = "ammo_shells";
item = FindItemByClassname ("ammo_shells");
}
if (strcmp(ent->classname, "weapon_supershotgun") == 0)
{
ent->classname = "ammo_shells";
item = FindItemByClassname ("ammo_shells");
}
if (strcmp(ent->classname, "weapon_machinegun") == 0)
{
ent->classname = "ammo_bullets";
item = FindItemByClassname ("ammo_bullets");
}
if (strcmp(ent->classname, "weapon_chaingun") == 0)
{
ent->classname = "ammo_bullets";
item = FindItemByClassname ("ammo_bullets");
}
if (strcmp(ent->classname, "weapon_grenadelauncher") == 0)
{
ent->classname = "ammo_grenades";
item = FindItemByClassname ("ammo_grenades");
}
if (strcmp(ent->classname, "weapon_rocketlauncher") == 0)
{
ent->classname = "ammo_rockets";
item = FindItemByClassname ("ammo_rockets");
}
if (strcmp(ent->classname, "weapon_railgun") == 0)
{
ent->classname = "ammo_slugs";
item = FindItemByClassname ("ammo_slugs");
}
if (strcmp(ent->classname, "weapon_hyperblaster") == 0)
{
ent->classname = "ammo_cells";
item = FindItemByClassname ("ammo_cells");
}
if (strcmp(ent->classname, "weapon_bfg") == 0)
{
ent->classname = "ammo_cells";
item = FindItemByClassname ("ammo_cells");
}
}
PrecacheItem (item);
if (ent->spawnflags)
{
if (strcmp(ent->classname, "key_power_cube") != 0)
{
ent->spawnflags = 0;
gi.dprintf("%s at %s has invalid spawnflags set\n", ent->classname, vtos(ent->s.origin));
}
}
and the rest goes on.
Another thing you may want to do is stop them from losing their weapon if they die. Here's what you do. Head to PlayerDie, and remove this line:
TossClientWeapon (self);
That's all for me..
Cya later! Expect more tutorials, yet again!
-Paril Kalashnikov
A kick arse tutorial. Well done!
Very nicely done!
Can someone write the same tutorial except for Half-Life 2?
Yeah, I'd be interested in a class system tutorial for HL2 too.
Great tut! An interesting read :)
Also, how do you select what class you want to be? (unless I missed it)
I don't see how you make it team based...
G_cmds.c, typing a cmd. :)
do one for hl2!! =)
I don't like HL :)
A team-based mod would have the same idea, but I mean only for selection, not for gameplay itself.. I don't even use this way anymore, Zoid's PMenu helped me a lot..
You open what file with what program, Paril ?