Post tutorial Report RSS Class or Team Based Mod

This tutorial will show you how to make a class based mod! You can change a few words around and in an instant you have a team based mod! Change a few other words around, You have a Battle of the Sexes! Anything pretty much classish can be made with this!

Posted by on - Intermediate Client Side Coding

[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

Post comment Comments
methy
methy - - 1,221 comments

A kick arse tutorial. Well done!

Reply Good karma Bad karma+1 vote
Basket_Case
Basket_Case - - 58 comments

Very nicely done!

Can someone write the same tutorial except for Half-Life 2?

Reply Good karma Bad karma+1 vote
Jambozal
Jambozal - - 337 comments

Yeah, I'd be interested in a class system tutorial for HL2 too.

Great tut! An interesting read :)

Reply Good karma Bad karma+1 vote
Basket_Case
Basket_Case - - 58 comments

Also, how do you select what class you want to be? (unless I missed it)

Reply Good karma Bad karma+1 vote
Basket_Case
Basket_Case - - 58 comments

I don't see how you make it team based...

Reply Good karma Bad karma+1 vote
Paril Author
Paril - - 24 comments

G_cmds.c, typing a cmd. :)

Reply Good karma+1 vote
Dr.Zoidberg
Dr.Zoidberg - - 45 comments

do one for hl2!! =)

Reply Good karma Bad karma+1 vote
Paril Author
Paril - - 24 comments

I don't like HL :)

Reply Good karma+1 vote
Paril Author
Paril - - 24 comments

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

Reply Good karma+1 vote
Roarkes
Roarkes - - 213 comments

You open what file with what program, Paril ?

Reply Good karma Bad karma+1 vote
Post a comment

Your comment will be anonymous unless you join the community. Or sign in with your social account: