Matchmode with "ready" commands!

This tutorial is based off of Psykotik's original clan match mode tut.  I decided to change it around a bit, so instead of using an .ini file structure to define the pre-match time (beginning once the first player enters the game), I modified it to use a "ready" command that each player will enter.  Once all the players have specified that they are ready, the match begins 20 seconds later.

In Game Explanation:  Players will enter the game in observer mode where they are invisible and can fly around.  Once everyone types, "cmd ready", in the console, the countdown will begin and the match will start 20 seconds later with a full respawn of all the players on the server.   Similarly, there will be a countdown when the timelimit approaches.

Let's get started!

Add in the blue code and take out any pink code.

First open up g_local.h.  Add these defines at the very bottom:

 

//match mode
qboolean timer;
qboolean started;
int        predmtime;
int        last_time;
int        playersready;
int        numberofplayers;
void centerprint_all (char *msg);
#define for_each_player(TOKILL,INDEX)\
for(INDEX=1;INDEX<=maxclients->value;INDEX++)\
if ((TOKILL=&g_edicts[i]) && TOKILL->inuse)

 

Next find the respawn section and add this like so:

 

typedef struct
{
    client_persistant_t    coop_respawn;     // what to set client->pers to on a respawn
    int             enterframe;             // level.framenum the client entered the game
    int             score;                 // frags, etc
    vec3_t        cmd_angles;             // angles sent over in the last command

    qboolean    spectator;             // client is a spectator
   
    //match mode
    int             observer;
} client_respawn_t;

 

Now open up g_cmds.c and add this new command to the clientcommand function:

 

    else if (Q_stricmp(cmd, "playerlist") == 0)
        Cmd_PlayerList_f(ent);
    //match mode
    else if (Q_stricmp(cmd, "ready") == 0)
    {
        if (started)
            return;
        if (ent->client->resp.isready)
            return;
        gi.bprintf (PRINT_HIGH, "%s is ready\n", ent->client->pers.netname);
        playersready++;
        ent->client->resp.isready = true;
        if (playersready == numberofplayers)
        {
            level.time = 0;
            level.framenum = 0;
            timer = true;
        }
    }

Open up g_utils.c and add this new function to the very bottom of the file:

 

/*
===================
Centerprint_All //match mode
===================
*/
void centerprint_all (char *msg)
{
int i;
edict_t *tokill;

for_each_player(tokill,i)
{
gi.centerprintf (tokill, msg);
}
}

Next open up g_main.c and add the indicated lines to the checkdmrules function:

 

void CheckDMRules (void)
{
    int             i;
    gclient_t    *cl;

    if (level.intermissiontime)
        return;

    if (!deathmatch->value)
        return;

    //match mode
    if ((level.time == ((timelimit->value - 1) * 60)) && last_time != 60)
{
        last_time = 60;
        centerprint_all("The match ends in 1 minute.\n");
}
else if ((level.time == ((timelimit->value * 60) - 20)) && last_time != 20)
{
last_time = 20;
        centerprint_all("The match ends in 20 seconds.\n");
}
else if ((level.time == ((timelimit->value * 60) - 10)) && last_time != 10)
{
last_time = 10;
        centerprint_all("[ 10 ]\n");
}
else if ((level.time == ((timelimit->value * 60) - 9)) && last_time != 9)
{
last_time = 9;
        centerprint_all("[ 9 ]\n");
}
else if ((level.time == ((timelimit->value * 60) - 8)) && last_time != 8)
{
last_time = 8;
        centerprint_all("[ 8 ]\n");
}
else if ((level.time == ((timelimit->value * 60) - 7)) && last_time != 7)
{
last_time = 7;
        centerprint_all("[ 7 ]\n");
}
else if ((level.time == ((timelimit->value * 60) - 6)) && last_time != 6)
{
last_time = 6;
        centerprint_all("[ 6 ]\n");
}
else if ((level.time == ((timelimit->value * 60) - 5)) && last_time != 5)
{
last_time = 5;
        centerprint_all("[ 5 ]\n");
}
else if ((level.time == ((timelimit->value * 60) - 4)) && last_time != 4)
{
last_time = 4;
        centerprint_all("[ 4 ]\n");
}
else if ((level.time == ((timelimit->value * 60) - 3)) && last_time != 3)
{
last_time = 3;
        centerprint_all("[ 3 ]\n");
}
else if ((level.time == ((timelimit->value * 60) - 2)) && last_time != 2)
{
last_time = 2;
        centerprint_all("[ 2 ]\n");
}
else if ((level.time == ((timelimit->value * 60) - 1)) && last_time != 1)
{
last_time = 1;
        centerprint_all("[ 1 ]\n");
}

    if (timelimit->value)
    {
        if (level.time >= timelimit->value*60)
        {
            gi.bprintf (PRINT_HIGH, "Timelimit hit.\n");
            EndDMLevel ();
            return;
        }
    }

 

Lastly, open up p_client.c and follow along, there's quite a few additions.

Go to the initclientpersistent function and add this:

 

    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;

    //match mode
    if (client->resp.observer == 0)
    {
        item = FindItem("Jacket Armor");
        client->pers.selected_item = ITEM_INDEX(item);
        client->pers.inventory[client->pers.selected_item] = 1;
    }

    client->pers.weapon = item;

 

Go to the putclientinserver function and add this:

 

    // clear entity values
    ent->groundentity = NULL;
    ent->client = &game.clients[index];
    ent->takedamage = DAMAGE_AIM;
    ent->movetype = MOVETYPE_WALK;
    ent->viewheight = 22;
    ent->inuse = true;
    ent->classname = "player";
    ent->mass = 200;
    ent->solid = SOLID_BBOX;
    ent->deadflag = DEAD_NO;
    ent->air_finished = level.time + 12;
    ent->clipmask = MASK_PLAYERSOLID;
    ent->model = "players/male/tris.md2";
    ent->pain = player_pain;
    ent->die = player_die;
    ent->waterlevel = 0;
    ent->watertype = 0;
    ent->flags &= ~FL_NO_KNOCKBACK;
    ent->svflags &= ~SVF_DEADMONSTER;

    //match mode
    ent->movetype &= ~MOVETYPE_NOCLIP;
    ent->solid &= ~SOLID_NOT;
    ent->svflags &= ~SVF_NOCLIENT;

 

Go to the clientbegindeathmatch function and add this:

 

void ClientBeginDeathmatch (edict_t *ent)
{
    G_InitEdict (ent);

    InitClientResp (ent->client);

    // locate ent at a spawn point
    PutClientInServer (ent);

    //match mode
    ent->client->ps.gunindex = 0;
    numberofplayers++;

    if (level.intermissiontime)
    {
        MoveClientToIntermission (ent);
    }

 

Go to the clientdisconnect function and add this:

 

void ClientDisconnect (edict_t *ent)
{
    int        playernum;

    if (!ent->client)
        return;

    gi.bprintf (PRINT_HIGH, "%s disconnected\n", ent->client->pers.netname);

    //match mode
    numberofplayers--;

    // send effect
    gi.WriteByte (svc_muzzleflash);
    gi.WriteShort (ent-g_edicts);
    gi.WriteByte (MZ_LOGOUT);
    gi.multicast (ent->s.origin, MULTICAST_PVS);

 

Go to the clientthink function and add this:

 

void ClientThink (edict_t *ent, usercmd_t *ucmd)
{
    gclient_t    *client;
    edict_t    *other;
    int        i, j;
    pmove_t    pm;

    level.current_entity = ent;
    client = ent->client;

    //match mode
    if (timer)
{
int time_left = (30 - level.time);
static int last_time_left;

if (time_left > 0)
{
if (time_left == 20 && last_time_left != 20)
{
last_time_left = 20;
                gi.centerprintf(ent, "The Match Will Begin In 20 Seconds");
}
else if (time_left == 10 && last_time_left != 10)
{
last_time_left = 10;
                gi.centerprintf(ent, "[ 10 ]");
}
else if (time_left == 9 && last_time_left != 9)
{
last_time_left = 9;
                gi.centerprintf(ent, "[ 9 ]");
}
else if (time_left == 8 && last_time_left != 8)
{
last_time_left = 8;
                gi.centerprintf(ent, "[ 8 ]");
}
else if (time_left == 7 && last_time_left != 7)
{
last_time_left = 7;
                gi.centerprintf(ent, "[ 7 ]");
}
else if (time_left == 6 && last_time_left != 6)
{
last_time_left = 6;
                gi.centerprintf(ent, "[ 6 ]");
}
else if (time_left == 5 && last_time_left != 5)
{
last_time_left = 5;
                gi.centerprintf(ent, "[ 5 ]");
}
else if (time_left == 4 && last_time_left != 4)
{
last_time_left = 4;
                gi.centerprintf(ent, "[ 4 ]");
}
else if (time_left == 3 && last_time_left != 3)
{
last_time_left = 3;
                gi.centerprintf(ent, "[ 3 ]");
}
else if (time_left == 2 && last_time_left != 2 && last_time_left != 5)
{
last_time_left = 2;
                gi.centerprintf(ent, "[ 2 ]");
}
else if (time_left == 1 && last_time_left != 1)
{
last_time_left = 1;
                gi.centerprintf(ent, "[ 1 ]");
}
}
else // timer done
{
edict_t *tokill;
int i;
level.framenum = 0;
centerprint_all("*** Let The Fragging Begin! ***\n");
for_each_player(tokill, i)
{
                tokill->client->resp.observer = 1;
PutClientInServer (tokill);
gi.WriteByte (svc_muzzleflash);
gi.WriteShort (tokill-g_edicts);
gi.WriteByte (MZ_LOGIN);
gi.multicast (tokill->s.origin, MULTICAST_PVS);
tokill->svflags &= ~SVF_NOCLIENT;
}

timer = false;
            started = true;
}
}

    if (level.intermissiontime)
    {
        client->ps.pmove.pm_type = PM_FREEZE;
        // can exit intermission after five seconds
        if (level.time > level.intermissiontime + 5.0
            && (ucmd->buttons & BUTTON_ANY) )
            level.exitintermission = true;
        return;
    }

And still in the clientthink function, but further down, add this:

 

        ent->viewheight = pm.viewheight;
        ent->waterlevel = pm.waterlevel;
        ent->watertype = pm.watertype;
        ent->groundentity = pm.groundentity;
        if (pm.groundentity)
            ent->groundentity_linkcount = pm.groundentity->linkcount;

        //match mode
        if (ent->client->resp.observer == 0)
        {
            ent->solid = SOLID_NOT;
            ent->movetype = MOVETYPE_NOCLIP;
            ent->svflags |= SVF_NOCLIENT;
        }

        if (ent->deadflag)
        {
            client->ps.viewangles[ROLL] = 40;
            client->ps.viewangles[PITCH] = -15;
            client->ps.viewangles[YAW] = client->killer_yaw;
        }

 

Code Explanation:  The defines in g_local.h just prototype certain variables and functions needed for this add-on.  In g_cmds.c we added a new command that will pronounce a player as being ready, and checks to see if the number of players that are ready equals the number of players on the server.  If they are equal, it turns on the timer to begin the match.  In p_client.c we added many new things.  The initclientpersistent function was changed so players that are observers are given an item so they can't shoot while observing.  The putclientinserver function had some calls that removed the effects of being an observer.  The clientbegindeathmatch and clientdisconnect functions had small changes that increased or decreased the integer counting the number of players on the server.  This is used to compare to the number of "ready" players on the server.  In the clientthink function we added a large section which does the countdown and final respawning of all the players.  Lastly, we added a similar piece of code to checkdmrules in g_main.c so it does a countdown when the match is almost over.

Remember the command is "cmd ready".

Enjoy!

Tutorial by Willi