/* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. This file is part of Quake III Arena source code. Quake III Arena source code is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Quake III Arena source code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Foobar; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // #include "g_local.h" #include "../ui/menudef.h" // for the voice chats /* ================== DeathmatchScoreboardMessage ================== */ void DeathmatchScoreboardMessage(gentity_t * ent) { char entry[1024]; char string[1400]; int stringlength; int i, j; gclient_t *cl; int numSorted, scoreFlags, accuracy, perfect; // send the latest information on all clients string[0] = 0; stringlength = 0; scoreFlags = 0; numSorted = level.numConnectedClients; for(i = 0; i < numSorted; i++) { int ping; cl = &level.clients[level.sortedClients[i]]; if(cl->pers.connected == CON_CONNECTING) { ping = -1; } else { ping = cl->ps.ping < 999 ? cl->ps.ping : 999; } if(cl->accuracy_shots) { accuracy = cl->accuracy_hits * 100 / cl->accuracy_shots; } else { accuracy = 0; } perfect = (cl->ps.persistant[PERS_RANK] == 0 && cl->ps.persistant[PERS_KILLED] == 0) ? 1 : 0; Com_sprintf(entry, sizeof(entry), " %i %i %i %i %i %i %i %i %i %i %i %i %i %i", level.sortedClients[i], cl->ps.persistant[PERS_SCORE], ping, (level.time - cl->pers.enterTime) / 60000, scoreFlags, g_entities[level.sortedClients[i]].s.powerups, accuracy, cl->ps.persistant[PERS_IMPRESSIVE_COUNT], cl->ps.persistant[PERS_EXCELLENT_COUNT], cl->ps.persistant[PERS_GAUNTLET_FRAG_COUNT], cl->ps.persistant[PERS_DEFEND_COUNT], cl->ps.persistant[PERS_ASSIST_COUNT], perfect, cl->ps.persistant[PERS_CAPTURES]); j = strlen(entry); if(stringlength + j > 1024) break; strcpy(string + stringlength, entry); stringlength += j; } trap_SendServerCommand(ent - g_entities, va("scores %i %i %i%s", i, level.teamScores[TEAM_RED], level.teamScores[TEAM_BLUE], string)); } /* ================== Cmd_Score_f Request current scoreboard information ================== */ void Cmd_Score_f(gentity_t * ent) { DeathmatchScoreboardMessage(ent); } /* ================== CheatsOk ================== */ qboolean CheatsOk(gentity_t * ent) { if(!g_cheats.integer) { trap_SendServerCommand(ent - g_entities, va("print \"Cheats are not enabled on this server.\n\"")); return qfalse; } if(ent->health <= 0) { trap_SendServerCommand(ent - g_entities, va("print \"You must be alive to use this command.\n\"")); return qfalse; } return qtrue; } /* ================== ConcatArgs ================== */ char *ConcatArgs(int start) { int i, c, tlen; static char line[MAX_STRING_CHARS]; int len; char arg[MAX_STRING_CHARS]; len = 0; c = trap_Argc(); for(i = start; i < c; i++) { trap_Argv(i, arg, sizeof(arg)); tlen = strlen(arg); if(len + tlen >= MAX_STRING_CHARS - 1) { break; } memcpy(line + len, arg, tlen); len += tlen; if(i != c - 1) { line[len] = ' '; len++; } } line[len] = 0; return line; } /* ================== SanitizeString Remove case and control characters ================== */ void SanitizeString(char *in, char *out) { while(*in) { if(*in == 27) { in += 2; // skip color code continue; } if(*in < 32) { in++; continue; } *out++ = tolower(*in++); } *out = 0; } /* ================== ClientNumberFromString Returns a player number for either a number or name string Returns -1 if invalid ================== */ int ClientNumberFromString(gentity_t * to, char *s) { gclient_t *cl; int idnum; char s2[MAX_STRING_CHARS]; char n2[MAX_STRING_CHARS]; // numeric values are just slot numbers if(s[0] >= '0' && s[0] <= '9') { idnum = atoi(s); if(idnum < 0 || idnum >= level.maxclients) { trap_SendServerCommand(to - g_entities, va("print \"Bad client slot: %i\n\"", idnum)); return -1; } cl = &level.clients[idnum]; if(cl->pers.connected != CON_CONNECTED) { trap_SendServerCommand(to - g_entities, va("print \"Client %i is not active\n\"", idnum)); return -1; } return idnum; } // check for a name match SanitizeString(s, s2); for(idnum = 0, cl = level.clients; idnum < level.maxclients; idnum++, cl++) { if(cl->pers.connected != CON_CONNECTED) { continue; } SanitizeString(cl->pers.netname, n2); if(!strcmp(n2, s2)) { return idnum; } } trap_SendServerCommand(to - g_entities, va("print \"User %s is not on the server\n\"", s)); return -1; } /* ================== Cmd_Give_f Give items to a client ================== */ void Cmd_Give_f(gentity_t * ent) { char *name; gitem_t *it; int i; qboolean give_all; gentity_t *it_ent; trace_t trace; if(!CheatsOk(ent)) { return; } name = ConcatArgs(1); if(Q_stricmp(name, "all") == 0) give_all = qtrue; else give_all = qfalse; if(give_all || Q_stricmp(name, "health") == 0) { ent->health = ent->client->ps.stats[STAT_MAX_HEALTH]; if(!give_all) return; } if(give_all || Q_stricmp(name, "weapons") == 0) { ent->client->ps.stats[STAT_WEAPONS] = (1 << WP_NUM_WEAPONS) - 1 - (1 << WP_GRAPPLING_HOOK) - (1 << WP_NONE); if(!give_all) return; } if(give_all || Q_stricmp(name, "ammo") == 0) { for(i = 0; i < MAX_WEAPONS; i++) { ent->client->ps.ammo[i] = 999; } if(!give_all) return; } if(give_all || Q_stricmp(name, "armor") == 0) { ent->client->ps.stats[STAT_ARMOR] = 200; if(!give_all) return; } if(Q_stricmp(name, "excellent") == 0) { ent->client->ps.persistant[PERS_EXCELLENT_COUNT]++; return; } if(Q_stricmp(name, "impressive") == 0) { ent->client->ps.persistant[PERS_IMPRESSIVE_COUNT]++; return; } if(Q_stricmp(name, "gauntletaward") == 0) { ent->client->ps.persistant[PERS_GAUNTLET_FRAG_COUNT]++; return; } if(Q_stricmp(name, "defend") == 0) { ent->client->ps.persistant[PERS_DEFEND_COUNT]++; return; } if(Q_stricmp(name, "assist") == 0) { ent->client->ps.persistant[PERS_ASSIST_COUNT]++; return; } // spawn a specific item right on the player if(!give_all) { it = BG_FindItem(name); if(!it) { return; } it_ent = G_Spawn(); VectorCopy(ent->r.currentOrigin, it_ent->s.origin); it_ent->classname = it->classname; G_SpawnItem(it_ent, it); FinishSpawningItem(it_ent); memset(&trace, 0, sizeof(trace)); Touch_Item(it_ent, ent, &trace); if(it_ent->inuse) { G_FreeEntity(it_ent); } } } /* ================== Cmd_God_f Sets client to godmode argv(0) god ================== */ void Cmd_God_f(gentity_t * ent) { char *msg; if(!CheatsOk(ent)) { return; } ent->flags ^= FL_GODMODE; if(!(ent->flags & FL_GODMODE)) msg = "godmode OFF\n"; else msg = "godmode ON\n"; trap_SendServerCommand(ent - g_entities, va("print \"%s\"", msg)); } /* ================== Cmd_Notarget_f Sets client to notarget argv(0) notarget ================== */ void Cmd_Notarget_f(gentity_t * ent) { char *msg; if(!CheatsOk(ent)) { return; } ent->flags ^= FL_NOTARGET; if(!(ent->flags & FL_NOTARGET)) msg = "notarget OFF\n"; else msg = "notarget ON\n"; trap_SendServerCommand(ent - g_entities, va("print \"%s\"", msg)); } /* ================== Cmd_Noclip_f argv(0) noclip ================== */ void Cmd_Noclip_f(gentity_t * ent) { char *msg; if(!CheatsOk(ent)) { return; } if(ent->client->noclip) { msg = "noclip OFF\n"; } else { msg = "noclip ON\n"; } ent->client->noclip = !ent->client->noclip; trap_SendServerCommand(ent - g_entities, va("print \"%s\"", msg)); } /* ================== Cmd_LevelShot_f This is just to help generate the level pictures for the menus. It goes to the intermission immediately and sends over a command to the client to resize the view, hide the scoreboard, and take a special screenshot ================== */ void Cmd_LevelShot_f(gentity_t * ent) { if(!CheatsOk(ent)) { return; } // doesn't work in single player if(g_gametype.integer != 0) { trap_SendServerCommand(ent - g_entities, "print \"Must be in g_gametype 0 for levelshot\n\""); return; } BeginIntermission(); trap_SendServerCommand(ent - g_entities, "clientLevelShot"); } /* ================== Cmd_LevelShot_f This is just to help generate the level pictures for the menus. It goes to the intermission immediately and sends over a command to the client to resize the view, hide the scoreboard, and take a special screenshot ================== */ void Cmd_TeamTask_f(gentity_t * ent) { char userinfo[MAX_INFO_STRING]; char arg[MAX_TOKEN_CHARS]; int task; int client = ent->client - level.clients; if(trap_Argc() != 2) { return; } trap_Argv(1, arg, sizeof(arg)); task = atoi(arg); trap_GetUserinfo(client, userinfo, sizeof(userinfo)); Info_SetValueForKey(userinfo, "teamtask", va("%d", task)); trap_SetUserinfo(client, userinfo); ClientUserinfoChanged(client); } /* ================= Cmd_Kill_f ================= */ void Cmd_Kill_f(gentity_t * ent) { if(ent->client->sess.sessionTeam == TEAM_SPECTATOR) { return; } if(ent->health <= 0) { return; } ent->flags &= ~FL_GODMODE; ent->client->ps.stats[STAT_HEALTH] = ent->health = -999; player_die(ent, ent, ent, 100000, MOD_SUICIDE); } /* ================= BroadCastTeamChange Let everyone know about a team change ================= */ void BroadcastTeamChange(gclient_t * client, int oldTeam) { if(client->sess.sessionTeam == TEAM_RED) { trap_SendServerCommand(-1, va("cp \"%s" S_COLOR_WHITE " joined the red team.\n\"", client->pers.netname)); } else if(client->sess.sessionTeam == TEAM_BLUE) { trap_SendServerCommand(-1, va("cp \"%s" S_COLOR_WHITE " joined the blue team.\n\"", client->pers.netname)); } else if(client->sess.sessionTeam == TEAM_SPECTATOR && oldTeam != TEAM_SPECTATOR) { trap_SendServerCommand(-1, va("cp \"%s" S_COLOR_WHITE " joined the spectators.\n\"", client->pers.netname)); } else if(client->sess.sessionTeam == TEAM_FREE) { trap_SendServerCommand(-1, va("cp \"%s" S_COLOR_WHITE " joined the battle.\n\"", client->pers.netname)); } } /* ================= SetTeam ================= */ void SetTeam(gentity_t * ent, char *s) { int team, oldTeam; gclient_t *client; int clientNum; spectatorState_t specState; int specClient; int teamLeader; // // see what change is requested // client = ent->client; clientNum = client - level.clients; specClient = 0; specState = SPECTATOR_NOT; if(!Q_stricmp(s, "scoreboard") || !Q_stricmp(s, "score")) { team = TEAM_SPECTATOR; specState = SPECTATOR_SCOREBOARD; } else if(!Q_stricmp(s, "follow1")) { team = TEAM_SPECTATOR; specState = SPECTATOR_FOLLOW; specClient = -1; } else if(!Q_stricmp(s, "follow2")) { team = TEAM_SPECTATOR; specState = SPECTATOR_FOLLOW; specClient = -2; } else if(!Q_stricmp(s, "spectator") || !Q_stricmp(s, "s")) { team = TEAM_SPECTATOR; specState = SPECTATOR_FREE; } else if(g_gametype.integer >= GT_TEAM) { // if running a team game, assign player to one of the teams specState = SPECTATOR_NOT; if(!Q_stricmp(s, "red") || !Q_stricmp(s, "r")) { team = TEAM_RED; } else if(!Q_stricmp(s, "blue") || !Q_stricmp(s, "b")) { team = TEAM_BLUE; } else { // pick the team with the least number of players team = PickTeam(clientNum); } if(g_teamForceBalance.integer) { int counts[TEAM_NUM_TEAMS]; counts[TEAM_BLUE] = TeamCount(ent->client->ps.clientNum, TEAM_BLUE); counts[TEAM_RED] = TeamCount(ent->client->ps.clientNum, TEAM_RED); // We allow a spread of two if(team == TEAM_RED && counts[TEAM_RED] - counts[TEAM_BLUE] > 1) { trap_SendServerCommand(ent->client->ps.clientNum, "cp \"Red team has too many players.\n\""); return; // ignore the request } if(team == TEAM_BLUE && counts[TEAM_BLUE] - counts[TEAM_RED] > 1) { trap_SendServerCommand(ent->client->ps.clientNum, "cp \"Blue team has too many players.\n\""); return; // ignore the request } // It's ok, the team we are switching to has less or same number of players } } else { // force them to spectators if there aren't any spots free team = TEAM_FREE; } // override decision if limiting the players if((g_gametype.integer == GT_TOURNAMENT) && level.numNonSpectatorClients >= 2) { team = TEAM_SPECTATOR; } else if(g_maxGameClients.integer > 0 && level.numNonSpectatorClients >= g_maxGameClients.integer) { team = TEAM_SPECTATOR; } // // decide if we will allow the change // oldTeam = client->sess.sessionTeam; if(team == oldTeam && team != TEAM_SPECTATOR) { return; } // // execute the team change // // if the player was dead leave the body if(client->ps.stats[STAT_HEALTH] <= 0) { CopyToBodyQue(ent); } // he starts at 'base' client->pers.teamState.state = TEAM_BEGIN; if(oldTeam != TEAM_SPECTATOR) { // Kill him (makes sure he loses flags, etc) ent->flags &= ~FL_GODMODE; ent->client->ps.stats[STAT_HEALTH] = ent->health = 0; player_die(ent, ent, ent, 100000, MOD_SUICIDE); } // they go to the end of the line for tournements if(team == TEAM_SPECTATOR) { client->sess.spectatorTime = level.time; } client->sess.sessionTeam = team; client->sess.spectatorState = specState; client->sess.spectatorClient = specClient; client->sess.teamLeader = qfalse; if(team == TEAM_RED || team == TEAM_BLUE) { teamLeader = TeamLeader(team); // if there is no team leader or the team leader is a bot and this client is not a bot if(teamLeader == -1 || (!(g_entities[clientNum].r.svFlags & SVF_BOT) && (g_entities[teamLeader].r.svFlags & SVF_BOT))) { SetLeader(team, clientNum); } } // make sure there is a team leader on the team the player came from if(oldTeam == TEAM_RED || oldTeam == TEAM_BLUE) { CheckTeamLeader(oldTeam); } BroadcastTeamChange(client, oldTeam); // get and distribute relevent paramters ClientUserinfoChanged(clientNum); ClientBegin(clientNum); } /* ================= StopFollowing If the client being followed leaves the game, or you just want to drop to free floating spectator mode ================= */ void StopFollowing(gentity_t * ent) { ent->client->ps.persistant[PERS_TEAM] = TEAM_SPECTATOR; ent->client->sess.sessionTeam = TEAM_SPECTATOR; ent->client->sess.spectatorState = SPECTATOR_FREE; ent->client->ps.pm_flags &= ~PMF_FOLLOW; ent->r.svFlags &= ~SVF_BOT; ent->client->ps.clientNum = ent - g_entities; } /* ================= Cmd_Team_f ================= */ void Cmd_Team_f(gentity_t * ent) { int oldTeam; char s[MAX_TOKEN_CHARS]; if(trap_Argc() != 2) { oldTeam = ent->client->sess.sessionTeam; switch (oldTeam) { case TEAM_BLUE: trap_SendServerCommand(ent - g_entities, "print \"Blue team\n\""); break; case TEAM_RED: trap_SendServerCommand(ent - g_entities, "print \"Red team\n\""); break; case TEAM_FREE: trap_SendServerCommand(ent - g_entities, "print \"Free team\n\""); break; case TEAM_SPECTATOR: trap_SendServerCommand(ent - g_entities, "print \"Spectator team\n\""); break; } return; } if(ent->client->switchTeamTime > level.time) { trap_SendServerCommand(ent - g_entities, "print \"May not switch teams more than once per 5 seconds.\n\""); return; } // if they are playing a tournement game, count as a loss if((g_gametype.integer == GT_TOURNAMENT) && ent->client->sess.sessionTeam == TEAM_FREE) { ent->client->sess.losses++; } trap_Argv(1, s, sizeof(s)); SetTeam(ent, s); ent->client->switchTeamTime = level.time + 5000; } /* ================= Cmd_Follow_f ================= */ void Cmd_Follow_f(gentity_t * ent) { int i; char arg[MAX_TOKEN_CHARS]; if(trap_Argc() != 2) { if(ent->client->sess.spectatorState == SPECTATOR_FOLLOW) { StopFollowing(ent); } return; } trap_Argv(1, arg, sizeof(arg)); i = ClientNumberFromString(ent, arg); if(i == -1) { return; } // can't follow self if(&level.clients[i] == ent->client) { return; } // can't follow another spectator if(level.clients[i].sess.sessionTeam == TEAM_SPECTATOR) { return; } // if they are playing a tournement game, count as a loss if((g_gametype.integer == GT_TOURNAMENT) && ent->client->sess.sessionTeam == TEAM_FREE) { ent->client->sess.losses++; } // first set them to spectator if(ent->client->sess.sessionTeam != TEAM_SPECTATOR) { SetTeam(ent, "spectator"); } ent->client->sess.spectatorState = SPECTATOR_FOLLOW; ent->client->sess.spectatorClient = i; } /* ================= Cmd_FollowCycle_f ================= */ void Cmd_FollowCycle_f(gentity_t * ent, int dir) { int clientnum; int original; // if they are playing a tournement game, count as a loss if((g_gametype.integer == GT_TOURNAMENT) && ent->client->sess.sessionTeam == TEAM_FREE) { ent->client->sess.losses++; } // first set them to spectator if(ent->client->sess.spectatorState == SPECTATOR_NOT) { SetTeam(ent, "spectator"); } if(dir != 1 && dir != -1) { G_Error("Cmd_FollowCycle_f: bad dir %i", dir); } clientnum = ent->client->sess.spectatorClient; original = clientnum; do { clientnum += dir; if(clientnum >= level.maxclients) { clientnum = 0; } if(clientnum < 0) { clientnum = level.maxclients - 1; } // can only follow connected clients if(level.clients[clientnum].pers.connected != CON_CONNECTED) { continue; } // can't follow another spectator if(level.clients[clientnum].sess.sessionTeam == TEAM_SPECTATOR) { continue; } // this is good, we can use it ent->client->sess.spectatorClient = clientnum; ent->client->sess.spectatorState = SPECTATOR_FOLLOW; return; } while(clientnum != original); // leave it where it was } /* ================== G_Say ================== */ static void G_SayTo(gentity_t * ent, gentity_t * other, int mode, int color, const char *name, const char *message) { if(!other) { return; } if(!other->inuse) { return; } if(!other->client) { return; } if(other->client->pers.connected != CON_CONNECTED) { return; } if(mode == SAY_TEAM && !OnSameTeam(ent, other)) { return; } // no chatting to players in tournements if((g_gametype.integer == GT_TOURNAMENT) && other->client->sess.sessionTeam == TEAM_FREE && ent->client->sess.sessionTeam != TEAM_FREE) { return; } trap_SendServerCommand(other - g_entities, va("%s \"%s%c%c%s\"", mode == SAY_TEAM ? "tchat" : "chat", name, Q_COLOR_ESCAPE, color, message)); } #define EC "\x19" void G_Say(gentity_t * ent, gentity_t * target, int mode, const char *chatText) { int j; gentity_t *other; int color; char name[64]; // don't let text be too long for malicious reasons char text[MAX_SAY_TEXT]; char location[64]; if(g_gametype.integer < GT_TEAM && mode == SAY_TEAM) { mode = SAY_ALL; } switch (mode) { default: case SAY_ALL: G_LogPrintf("say: %s: %s\n", ent->client->pers.netname, chatText); Com_sprintf(name, sizeof(name), "%s%c%c" EC ": ", ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE); color = COLOR_GREEN; break; case SAY_TEAM: G_LogPrintf("sayteam: %s: %s\n", ent->client->pers.netname, chatText); if(Team_GetLocationMsg(ent, location, sizeof(location))) Com_sprintf(name, sizeof(name), EC "(%s%c%c" EC ") (%s)" EC ": ", ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE, location); else Com_sprintf(name, sizeof(name), EC "(%s%c%c" EC ")" EC ": ", ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE); color = COLOR_CYAN; break; case SAY_TELL: if(target && g_gametype.integer >= GT_TEAM && target->client->sess.sessionTeam == ent->client->sess.sessionTeam && Team_GetLocationMsg(ent, location, sizeof(location))) Com_sprintf(name, sizeof(name), EC "[%s%c%c" EC "] (%s)" EC ": ", ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE, location); else Com_sprintf(name, sizeof(name), EC "[%s%c%c" EC "]" EC ": ", ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE); color = COLOR_MAGENTA; break; } Q_strncpyz(text, chatText, sizeof(text)); if(target) { G_SayTo(ent, target, mode, color, name, text); return; } // echo the text to the console if(g_dedicated.integer) { G_Printf("%s%s\n", name, text); } // send it to all the apropriate clients for(j = 0; j < level.maxclients; j++) { other = &g_entities[j]; G_SayTo(ent, other, mode, color, name, text); } } /* ================== Cmd_Say_f ================== */ static void Cmd_Say_f(gentity_t * ent, int mode, qboolean arg0) { char *p; if(trap_Argc() < 2 && !arg0) { return; } if(arg0) { p = ConcatArgs(0); } else { p = ConcatArgs(1); } G_Say(ent, NULL, mode, p); } /* ================== Cmd_Tell_f ================== */ static void Cmd_Tell_f(gentity_t * ent) { int targetNum; gentity_t *target; char *p; char arg[MAX_TOKEN_CHARS]; if(trap_Argc() < 2) { return; } trap_Argv(1, arg, sizeof(arg)); targetNum = atoi(arg); if(targetNum < 0 || targetNum >= level.maxclients) { return; } target = &g_entities[targetNum]; if(!target || !target->inuse || !target->client) { return; } p = ConcatArgs(2); G_LogPrintf("tell: %s to %s: %s\n", ent->client->pers.netname, target->client->pers.netname, p); G_Say(ent, target, SAY_TELL, p); // don't tell to the player self if it was already directed to this player // also don't send the chat back to a bot if(ent != target && !(ent->r.svFlags & SVF_BOT)) { G_Say(ent, ent, SAY_TELL, p); } } static void G_VoiceTo(gentity_t * ent, gentity_t * other, int mode, const char *id, qboolean voiceonly) { int color; char *cmd; if(!other) { return; } if(!other->inuse) { return; } if(!other->client) { return; } if(mode == SAY_TEAM && !OnSameTeam(ent, other)) { return; } // no chatting to players in tournements if((g_gametype.integer == GT_TOURNAMENT)) { return; } if(mode == SAY_TEAM) { color = COLOR_CYAN; cmd = "vtchat"; } else if(mode == SAY_TELL) { color = COLOR_MAGENTA; cmd = "vtell"; } else { color = COLOR_GREEN; cmd = "vchat"; } trap_SendServerCommand(other - g_entities, va("%s %d %d %d %s", cmd, voiceonly, ent->s.number, color, id)); } void G_Voice(gentity_t * ent, gentity_t * target, int mode, const char *id, qboolean voiceonly) { int j; gentity_t *other; if(g_gametype.integer < GT_TEAM && mode == SAY_TEAM) { mode = SAY_ALL; } if(target) { G_VoiceTo(ent, target, mode, id, voiceonly); return; } // echo the text to the console if(g_dedicated.integer) { G_Printf("voice: %s %s\n", ent->client->pers.netname, id); } // send it to all the apropriate clients for(j = 0; j < level.maxclients; j++) { other = &g_entities[j]; G_VoiceTo(ent, other, mode, id, voiceonly); } } /* ================== Cmd_Voice_f ================== */ static void Cmd_Voice_f(gentity_t * ent, int mode, qboolean arg0, qboolean voiceonly) { char *p; if(trap_Argc() < 2 && !arg0) { return; } if(arg0) { p = ConcatArgs(0); } else { p = ConcatArgs(1); } G_Voice(ent, NULL, mode, p, voiceonly); } /* ================== Cmd_VoiceTell_f ================== */ static void Cmd_VoiceTell_f(gentity_t * ent, qboolean voiceonly) { int targetNum; gentity_t *target; char *id; char arg[MAX_TOKEN_CHARS]; if(trap_Argc() < 2) { return; } trap_Argv(1, arg, sizeof(arg)); targetNum = atoi(arg); if(targetNum < 0 || targetNum >= level.maxclients) { return; } target = &g_entities[targetNum]; if(!target || !target->inuse || !target->client) { return; } id = ConcatArgs(2); G_LogPrintf("vtell: %s to %s: %s\n", ent->client->pers.netname, target->client->pers.netname, id); G_Voice(ent, target, SAY_TELL, id, voiceonly); // don't tell to the player self if it was already directed to this player // also don't send the chat back to a bot if(ent != target && !(ent->r.svFlags & SVF_BOT)) { G_Voice(ent, ent, SAY_TELL, id, voiceonly); } } /* ================== Cmd_VoiceTaunt_f ================== */ static void Cmd_VoiceTaunt_f(gentity_t * ent) { gentity_t *who; int i; if(!ent->client) { return; } // insult someone who just killed you if(ent->enemy && ent->enemy->client && ent->enemy->client->lastkilled_client == ent->s.number) { // i am a dead corpse if(!(ent->enemy->r.svFlags & SVF_BOT)) { G_Voice(ent, ent->enemy, SAY_TELL, VOICECHAT_DEATHINSULT, qfalse); } if(!(ent->r.svFlags & SVF_BOT)) { G_Voice(ent, ent, SAY_TELL, VOICECHAT_DEATHINSULT, qfalse); } ent->enemy = NULL; return; } // insult someone you just killed if(ent->client->lastkilled_client >= 0 && ent->client->lastkilled_client != ent->s.number) { who = g_entities + ent->client->lastkilled_client; if(who->client) { // who is the person I just killed if(who->client->lasthurt_mod == MOD_GAUNTLET) { if(!(who->r.svFlags & SVF_BOT)) { G_Voice(ent, who, SAY_TELL, VOICECHAT_KILLGAUNTLET, qfalse); // and I killed them with a gauntlet } if(!(ent->r.svFlags & SVF_BOT)) { G_Voice(ent, ent, SAY_TELL, VOICECHAT_KILLGAUNTLET, qfalse); } } else { if(!(who->r.svFlags & SVF_BOT)) { G_Voice(ent, who, SAY_TELL, VOICECHAT_KILLINSULT, qfalse); // and I killed them with something else } if(!(ent->r.svFlags & SVF_BOT)) { G_Voice(ent, ent, SAY_TELL, VOICECHAT_KILLINSULT, qfalse); } } ent->client->lastkilled_client = -1; return; } } if(g_gametype.integer >= GT_TEAM) { // praise a team mate who just got a reward for(i = 0; i < MAX_CLIENTS; i++) { who = g_entities + i; if(who->client && who != ent && who->client->sess.sessionTeam == ent->client->sess.sessionTeam) { if(who->client->rewardTime > level.time) { if(!(who->r.svFlags & SVF_BOT)) { G_Voice(ent, who, SAY_TELL, VOICECHAT_PRAISE, qfalse); } if(!(ent->r.svFlags & SVF_BOT)) { G_Voice(ent, ent, SAY_TELL, VOICECHAT_PRAISE, qfalse); } return; } } } } // just say something G_Voice(ent, NULL, SAY_ALL, VOICECHAT_TAUNT, qfalse); } static char *gc_orders[] = { "hold your position", "hold this position", "come here", "cover me", "guard location", "search and destroy", "report" }; void Cmd_GameCommand_f(gentity_t * ent) { int player; int order; char str[MAX_TOKEN_CHARS]; trap_Argv(1, str, sizeof(str)); player = atoi(str); trap_Argv(2, str, sizeof(str)); order = atoi(str); if(player < 0 || player >= MAX_CLIENTS) { return; } if(order < 0 || order > sizeof(gc_orders) / sizeof(char *)) { return; } G_Say(ent, &g_entities[player], SAY_TELL, gc_orders[order]); G_Say(ent, ent, SAY_TELL, gc_orders[order]); } /* ================== Cmd_Where_f ================== */ void Cmd_Where_f(gentity_t * ent) { trap_SendServerCommand(ent - g_entities, va("print \"%s\n\"", vtos(ent->s.origin))); } static const char *gameNames[] = { "Free For All", "Tournament", "Single Player", "Team Deathmatch", "Capture the Flag", "One Flag CTF", "Overload", "Harvester" }; /* ================== Cmd_CallVote_f ================== */ void Cmd_CallVote_f(gentity_t * ent) { int i; char arg1[MAX_STRING_TOKENS]; char arg2[MAX_STRING_TOKENS]; if(!g_allowVote.integer) { trap_SendServerCommand(ent - g_entities, "print \"Voting not allowed here.\n\""); return; } if(level.voteTime) { trap_SendServerCommand(ent - g_entities, "print \"A vote is already in progress.\n\""); return; } if(ent->client->pers.voteCount >= MAX_VOTE_COUNT) { trap_SendServerCommand(ent - g_entities, "print \"You have called the maximum number of votes.\n\""); return; } if(ent->client->sess.sessionTeam == TEAM_SPECTATOR) { trap_SendServerCommand(ent - g_entities, "print \"Not allowed to call a vote as spectator.\n\""); return; } // make sure it is a valid command to vote on trap_Argv(1, arg1, sizeof(arg1)); trap_Argv(2, arg2, sizeof(arg2)); if(strchr(arg1, ';') || strchr(arg2, ';')) { trap_SendServerCommand(ent - g_entities, "print \"Invalid vote string.\n\""); return; } if(!Q_stricmp(arg1, "map_restart")) { } else if(!Q_stricmp(arg1, "nextmap")) { } else if(!Q_stricmp(arg1, "map")) { } else if(!Q_stricmp(arg1, "g_gametype")) { } else if(!Q_stricmp(arg1, "kick")) { } else if(!Q_stricmp(arg1, "clientkick")) { } else if(!Q_stricmp(arg1, "g_doWarmup")) { } else if(!Q_stricmp(arg1, "timelimit")) { } else if(!Q_stricmp(arg1, "fraglimit")) { } else { trap_SendServerCommand(ent - g_entities, "print \"Invalid vote string.\n\""); trap_SendServerCommand(ent - g_entities, "print \"Vote commands are: map_restart, nextmap, map , g_gametype , kick , clientkick , g_doWarmup, timelimit