/* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. Copyright (C) 2002-2021 Q3Rally Team (Per Thormann - q3rally@gmail.com) This file is part of q3rally source code. q3rally 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. q3rally 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 q3rally; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // #include "g_local.h" #ifdef MISSIONPACK #include "../../ui/menudef.h" // for the voice chats #endif /* ================== DeathmatchScoreboardMessage ================== */ void DeathmatchScoreboardMessage( gentity_t *ent ) { char entry[1024]; char string[1000]; int stringlength; int i, j; gclient_t *cl; int numSorted, scoreFlags, accuracy, perfect; // don't send scores to bots, they don't parse it if ( ent->r.svFlags & SVF_BOT ) { return; } // 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; // STONELANCE int time; // END cl = &level.clients[level.sortedClients[i]]; // STONELANCE /* if (cl->sess.sessionTeam == TEAM_SPECTATOR && !cl->finishRaceTime) time = cl->sess.spectatorTime; else time = level.startRaceTime; */ if ( isRallyRace() || g_gametype.integer == GT_DERBY ) time = level.startRaceTime; else time = cl->switchTeamTime; // END 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), // STONELANCE // " %i %i %i %i %i %i %i %i %i %i %i %i %i %i", level.sortedClients[i], " %i %i %i %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, cl->ps.persistant[PERS_SCORE], ping, time, // END scoreFlags, g_entities[level.sortedClients[i]].s.powerups, accuracy, cl->ps.persistant[PERS_IMPRESSIVE_COUNT], cl->ps.persistant[PERS_IMPRESSIVETELEFRAG_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, // STONELANCE // cl->ps.persistant[PERS_CAPTURES]); cl->ps.persistant[PERS_CAPTURES], cl->ps.stats[STAT_DAMAGE_DEALT], cl->ps.stats[STAT_DAMAGE_TAKEN], cl->ps.stats[STAT_POSITION] ); // END j = strlen(entry); if (stringlength + j >= sizeof(string)) break; strcpy (string + stringlength, entry); stringlength += j; } // STONELANCE // trap_SendServerCommand( ent-g_entities, va("scores %i %i %i%s", i, // level.teamScores[TEAM_RED], level.teamScores[TEAM_BLUE], // string ) ); trap_SendServerCommand( ent-g_entities, va("scores %i %i %i %i %i%s", i, level.teamScores[TEAM_RED], level.teamScores[TEAM_BLUE], level.teamScores[TEAM_GREEN], level.teamScores[TEAM_YELLOW], string ) ); // END } /* ================== 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, "print \"Cheats are not enabled on this server.\n\""); return qfalse; } if ( ent->health <= 0 ) { trap_SendServerCommand( ent-g_entities, "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; } /* ================== StringIsInteger ================== */ qboolean StringIsInteger( const char * s ) { int i; int len; qboolean foundDigit; len = strlen( s ); foundDigit = qfalse; for ( i=0 ; i < len ; i++ ) { if ( !isdigit( s[i] ) ) { return qfalse; } foundDigit = qtrue; } return foundDigit; } /* ================== ClientNumberFromString Returns a player number for either a number or name string Returns -1 if invalid ================== */ int ClientNumberFromString( gentity_t *to, char *s, qboolean checkNums, qboolean checkNames ) { gclient_t *cl; int idnum; char s2[MAX_STRING_CHARS]; char n2[MAX_STRING_CHARS]; if ( checkNums ) { // numeric values could be slot numbers if ( StringIsInteger( s ) ) { idnum = atoi( s ); if ( idnum >= 0 && idnum < level.maxclients ) { cl = &level.clients[idnum]; if ( cl->pers.connected == CON_CONNECTED ) { return idnum; } } } } if ( checkNames ) { // 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; } // STONELANCE // if (give_all || Q_stricmp(name, "weapons") == 0) if ((give_all || Q_stricmp(name, "weapons") == 0) && (g_gametype.integer != GT_RACING && g_gametype.integer != GT_TEAM_RACING)) // END { // STONELANCE ent->client->ps.stats[STAT_WEAPONS] = (1 << RWP_SMOKE) - 1 - - ( 1 << WP_NONE ); // ent->client->ps.stats[STAT_WEAPONS] = (1 << WP_NUM_WEAPONS) - 1 - // ( 1 << WP_GRAPPLING_HOOK ) - ( 1 << WP_NONE ); // END if (!give_all) return; } if (give_all || Q_stricmp(name, "ammo") == 0) { // STONELANCE for ( i = 0 ; i < RWP_SMOKE ; i++ ) { // for ( i = 0 ; i < MAX_WEAPONS ; i++ ) { // END ent->client->ps.ammo[i] = 999; } // STONELANCE for ( ; i < MAX_WEAPONS ; i++ ) { ent->client->ps.ammo[i] = 99; } // END 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, "impressive telefrag") == 0) { ent->client->ps.persistant[PERS_IMPRESSIVETELEFRAG_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(!ent->client->pers.localClient) { trap_SendServerCommand(ent-g_entities, "print \"The levelshot command must be executed by a local client\n\""); return; } if(!CheatsOk(ent)) return; // doesn't work in single player if(g_gametype.integer == GT_SINGLE_PLAYER) { trap_SendServerCommand(ent-g_entities, "print \"Must not be in singleplayer mode for levelshot\n\"" ); return; } BeginIntermission(); trap_SendServerCommand(ent-g_entities, "clientLevelShot"); } /* ================== Cmd_TeamTask_f ================== */ 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)); } // STONELANCE else if ( client->sess.sessionTeam == TEAM_GREEN ) { trap_SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " joined the green team.\n\"", client->pers.netname)); } else if ( client->sess.sessionTeam == TEAM_YELLOW ) { trap_SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " joined the yellow team.\n\"", client->pers.netname)); } // END 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, const char *s ) { int team, oldTeam; gclient_t *client; int clientNum; spectatorState_t specState; int specClient; // STONELANCE qboolean specWilling; // END int teamLeader; // // see what change is requested // client = ent->client; clientNum = client - level.clients; specClient = 0; specState = SPECTATOR_NOT; // STONELANCE specWilling = qtrue; // END 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; } // STONELANCE else if ( !Q_stricmp( s, "racerSpectator" ) ) { team = TEAM_SPECTATOR; specState = SPECTATOR_FREE; specWilling = qfalse; } else if ((isRallyRace() || g_gametype.integer == GT_DERBY) && level.startRaceTime){ trap_SendServerCommand( clientNum, "cp \"Cannot change teams\nduring a race.\n\""); return; // dont allow any change except to spectator during a race. } // END 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; } // STONELANCE else if ( !Q_stricmp( s, "green" ) || !Q_stricmp( s, "g" ) ) { if (g_gametype.integer == GT_CTF) return; // no green in ctf team = TEAM_GREEN; } else if ( !Q_stricmp( s, "yellow" ) || !Q_stricmp( s, "y" ) ) { if (g_gametype.integer == GT_CTF) return; // no yellow in ctf team = TEAM_YELLOW; } // END else { // pick the team with the least number of players team = PickTeam( clientNum ); } if ( g_teamForceBalance.integer && !client->pers.localClient && !( ent->r.svFlags & SVF_BOT ) ) { int counts[TEAM_NUM_TEAMS]; counts[TEAM_BLUE] = TeamCount( clientNum, TEAM_BLUE ); counts[TEAM_RED] = TeamCount( clientNum, TEAM_RED ); // STONELANCE counts[TEAM_GREEN] = TeamCount( clientNum, TEAM_GREEN ); counts[TEAM_YELLOW] = TeamCount( clientNum, TEAM_YELLOW ); // We allow a spread of two /* if ( team == TEAM_RED && counts[TEAM_RED] - counts[TEAM_BLUE] > 1 ) { trap_SendServerCommand( 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( clientNum, "cp \"Blue team has too many players.\n\"" ); return; // ignore the request } */ if ( team == TEAM_RED && counts[TEAM_RED] - counts[TEAM_BLUE] > 1 && counts[TEAM_RED] - counts[TEAM_GREEN] > 1 && counts[TEAM_RED] - counts[TEAM_YELLOW] > 1) { trap_SendServerCommand( clientNum, "cp \"Red team has too many players.\n\"" ); return; // ignore the request } if ( team == TEAM_BLUE && counts[TEAM_BLUE] - counts[TEAM_RED] > 1 && counts[TEAM_BLUE] - counts[TEAM_GREEN] > 1 && counts[TEAM_YELLOW] - counts[TEAM_YELLOW] > 1) { trap_SendServerCommand( clientNum, "cp \"Blue team has too many players.\n\"" ); return; // ignore the request } if ( team == TEAM_GREEN && counts[TEAM_GREEN] - counts[TEAM_RED] > 1 && counts[TEAM_GREEN] - counts[TEAM_BLUE] > 1 && counts[TEAM_GREEN] - counts[TEAM_YELLOW] > 1) { trap_SendServerCommand( clientNum, "cp \"Green team has too many players.\n\"" ); return; // ignore the request } if ( team == TEAM_YELLOW && counts[TEAM_YELLOW] - counts[TEAM_RED] > 1 && counts[TEAM_YELLOW] - counts[TEAM_BLUE] > 1 && counts[TEAM_YELLOW] - counts[TEAM_GREEN] > 1) { trap_SendServerCommand( clientNum, "cp \"Yellow team has too many players.\n\"" ); return; // ignore the request } // END // 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 // STONELANCE /* if ( (g_gametype.integer == GT_TOURNAMENT) && level.numNonSpectatorClients >= 2 ) { team = TEAM_SPECTATOR; } else */ // END if ( g_maxGameClients.integer > 0 && level.numNonSpectatorClients >= g_maxGameClients.integer ) { team = TEAM_SPECTATOR; // STONELANCE specWilling = qfalse; // END } // // 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, but only if they're actually in game if ( client->ps.stats[STAT_HEALTH] <= 0 && client->pers.connected == CON_CONNECTED ) { CopyToBodyQue(ent); } // he starts at 'base' client->pers.teamState.state = TEAM_BEGIN; if ( oldTeam != TEAM_SPECTATOR // STONELANCE && !client->finishRaceTime // END ) { // 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 && oldTeam != team) AddTournamentQueue(client); client->sess.sessionTeam = team; client->sess.spectatorState = specState; client->sess.spectatorClient = specClient; // STONELANCE client->sess.spectatorWilling = specWilling; // END client->sess.teamLeader = qfalse; // STONELANCE // if ( team == TEAM_RED || team == TEAM_BLUE ) { if ( team == TEAM_RED || team == TEAM_BLUE || team == TEAM_GREEN || team == TEAM_YELLOW ) { // END 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 // STONELANCE // if ( oldTeam == TEAM_RED || oldTeam == TEAM_BLUE ) { if ( oldTeam == TEAM_RED || oldTeam == TEAM_BLUE || oldTeam == TEAM_GREEN || oldTeam == TEAM_YELLOW ) { // END CheckTeamLeader( oldTeam ); } // get and distribute relevant parameters ClientUserinfoChanged( clientNum ); // client hasn't spawned yet, they sent an early team command, teampref userinfo, or g_teamAutoJoin is enabled if ( client->pers.connected != CON_CONNECTED ) { return; } BroadcastTeamChange( client, oldTeam ); 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 ) { // STONELANCE // ent->client->ps.persistant[ PERS_TEAM ] = TEAM_SPECTATOR; // ent->client->sess.sessionTeam = TEAM_SPECTATOR; // END 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; SetClientViewAngle( ent, ent->client->ps.viewangles ); // don't use dead view angles if ( ent->client->ps.stats[STAT_HEALTH] <= 0 ) { ent->client->ps.stats[STAT_HEALTH] = 1; } } /* ================= 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; // STONELANCE case TEAM_GREEN: trap_SendServerCommand( ent-g_entities, "print \"Green team\n\"" ); break; case TEAM_YELLOW: trap_SendServerCommand( ent-g_entities, "print \"Yellow team\n\"" ); break; // END 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 // STONELANCE - removed gametype /* if ( (g_gametype.integer == GT_TOURNAMENT ) && ent->client->sess.sessionTeam == TEAM_FREE ) { ent->client->sess.losses++; } */ // END 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, qtrue, qtrue ); 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 // STONELANCE - removed gametype /* if ( (g_gametype.integer == GT_TOURNAMENT ) && ent->client->sess.sessionTeam == TEAM_FREE ) { ent->client->sess.losses++; } */ // END // 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 // STONELANCE - removed gametype /* if ( (g_gametype.integer == GT_TOURNAMENT ) && ent->client->sess.sessionTeam == TEAM_FREE ) { ent->client->sess.losses++; } */ // END // 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 ); } // if dedicated follow client, just switch between the two auto clients if (ent->client->sess.spectatorClient < 0) { if (ent->client->sess.spectatorClient == -1) { ent->client->sess.spectatorClient = -2; } else if (ent->client->sess.spectatorClient == -2) { ent->client->sess.spectatorClient = -1; } return; } 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; } // STONELANCE // can't follow another spectator if ( isRaceObserver( clientnum ) ) { continue; } // END // this is good, we can use it ent->client->sess.spectatorClient = clientnum; // STONELANCE if( ent->client->sess.spectatorState == SPECTATOR_OBSERVE ) { vec3_t origin, angles; if ( ent->client->sess.spectatorClient >= 0 && FindBestObserverSpot(ent, &g_entities[ent->client->sess.spectatorClient], origin, angles) ) { G_SetOrigin(ent, origin); VectorCopy(origin, ent->client->ps.origin); VectorCopy(angles, ent->client->ps.viewangles); } else ent->client->sess.spectatorState = SPECTATOR_FOLLOW; } if( ent->client->sess.spectatorState != SPECTATOR_FOLLOW && ent->client->sess.spectatorState != SPECTATOR_OBSERVE ) ent->client->sess.spectatorState = SPECTATOR_OBSERVE; // ent->client->sess.spectatorState = SPECTATOR_FOLLOW; // END 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 // STONELANCE - removed gametype /* if ( (g_gametype.integer == GT_TOURNAMENT ) && other->client->sess.sessionTeam == TEAM_FREE && ent->client->sess.sessionTeam != TEAM_FREE ) { return; } */ // END 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 && target->inuse && target->client && 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 appropriate clients for (j = 0; j < level.maxclients; j++) { other = &g_entities[j]; G_SayTo( ent, other, mode, color, name, text ); } } static void SanitizeChatText( char *text ) { int i; for ( i = 0; text[i]; i++ ) { if ( text[i] == '\n' || text[i] == '\r' ) { text[i] = ' '; } } } /* ================== 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 ); } SanitizeChatText( p ); 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 () < 3 ) { trap_SendServerCommand( ent-g_entities, "print \"Usage: tell \n\"" ); return; } trap_Argv( 1, arg, sizeof( arg ) ); targetNum = ClientNumberFromString( ent, arg, qtrue, qtrue ); if ( targetNum == -1 ) { return; } target = &g_entities[targetNum]; if ( !target->inuse || !target->client ) { return; } p = ConcatArgs( 2 ); SanitizeChatText( p ); 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 ); } } #ifdef MISSIONPACK 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 // STONELANCE - removed gametype /* if ( g_gametype.integer == GT_TOURNAMENT ) { return; } */ // END 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 appropriate 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 ); } SanitizeChatText( p ); 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 () < 3 ) { trap_SendServerCommand( ent-g_entities, va( "print \"Usage: %s \n\"", voiceonly ? "votell" : "vtell" ) ); return; } trap_Argv( 1, arg, sizeof( arg ) ); targetNum = ClientNumberFromString( ent, arg, qtrue, qtrue ); if ( targetNum == -1 ) { return; } target = &g_entities[targetNum]; if ( !target->inuse || !target->client ) { return; } id = ConcatArgs( 2 ); SanitizeChatText( id ); 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 ); } #endif static char *gc_orders[] = { "hold your position", "hold this position", "come here", "cover me", "guard location", "search and destroy", "report" }; static const int numgc_orders = ARRAY_LEN( gc_orders ); void Cmd_GameCommand_f( gentity_t *ent ) { int targetNum; gentity_t *target; int order; char arg[MAX_TOKEN_CHARS]; if ( trap_Argc() != 3 ) { trap_SendServerCommand( ent-g_entities, va( "print \"Usage: gc \n\"", numgc_orders - 1 ) ); return; } trap_Argv( 2, arg, sizeof( arg ) ); order = atoi( arg ); if ( order < 0 || order >= numgc_orders ) { trap_SendServerCommand( ent-g_entities, va("print \"Bad order: %i\n\"", order)); return; } trap_Argv( 1, arg, sizeof( arg ) ); targetNum = ClientNumberFromString( ent, arg, qtrue, qtrue ); if ( targetNum == -1 ) { return; } target = &g_entities[targetNum]; if ( !target->inuse || !target->client ) { return; } G_LogPrintf( "tell: %s to %s: %s\n", ent->client->pers.netname, target->client->pers.netname, gc_orders[order] ); G_Say( ent, target, SAY_TELL, gc_orders[order] ); // 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, gc_orders[order] ); } } /* ================== Cmd_Where_f ================== */ void Cmd_Where_f( gentity_t *ent ) { trap_SendServerCommand( ent-g_entities, va("print \"%s\n\"", vtos(ent->r.currentOrigin) ) ); } 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 ) { char* c; 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; } // Q3Rally Code Start - fixed callvote error if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { trap_SendServerCommand( ent-g_entities, "print \"Not allowed to call a vote as spectator.\n\"" ); return; } // END // make sure it is a valid command to vote on trap_Argv( 1, arg1, sizeof( arg1 ) ); trap_Argv( 2, arg2, sizeof( arg2 ) ); // check for command separators in arg2 for( c = arg2; *c; ++c) { switch(*c) { case '\n': case '\r': case ';': trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string.\n\"" ); return; break; } } // STONELANCE if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR && ( !Q_stricmp( arg1, "kick" ) || !Q_stricmp( arg1, "clientkick" ) ) ) { trap_SendServerCommand( ent-g_entities, "print \"Not allowed to call a vote as spectator.\n\"" ); return; } // END 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