
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