/* =========================================================================== Wolfenstein: Enemy Territory GPL Source Code Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. This file is part of the Wolfenstein: Enemy Territory GPL Source Code (“Wolf ET Source Code”). Wolf ET 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 3 of the License, or (at your option) any later version. Wolf ET 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 Wolf ET Source Code. If not, see . In addition, the Wolf: ET Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Wolf ET Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ // g_cmds_ext.c: Extended command set handling // ------------------------------------------- // #include "g_local.h" #include "../../etmain/ui/menudef.h" int iWeap = WS_MAX; char *lock_status[2] = { "unlock", "lock" }; // // Update info: // 1. Add line to aCommandInfo w/appropriate info // 2. Add implementation for specific command (see an existing command for an example) // typedef struct { char *pszCommandName; qboolean fAnytime; qboolean fValue; void ( *pCommand )( gentity_t *ent, unsigned int dwCommand, qboolean fValue ); const char *pszHelpInfo; } cmd_reference_t; // VC optimizes for dup strings :) static const cmd_reference_t aCommandInfo[] = { { "+stats", qtrue, qtrue, NULL, ":^7 HUD overlay showing current weapon stats info" }, { "+topshots", qtrue, qtrue, NULL, ":^7 HUD overlay showing current top accuracies of all players" }, { "?", qtrue, qtrue, G_commands_cmd, ":^7 Gives a list of OSP-specific commands" }, { "autorecord", qtrue, qtrue, NULL, ":^7 Creates a demo with a consistent naming scheme" }, { "autoscreenshot", qtrue, qtrue, NULL, ":^7 Creates a screenshot with a consistent naming scheme" }, { "bottomshots", qtrue, qfalse, G_weaponRankings_cmd, ":^7 Shows WORST player for each weapon. Add ^3^7 to show all stats for a weapon" }, // { "callvote", qtrue, qfalse, Cmd_CallVote_f, " :^7 Calls a vote" }, { "callvote", qtrue, qfalse, ( void( * ) ( gentity_t *, unsigned int, qboolean ) )Cmd_CallVote_f, " :^7 Calls a vote" }, // { "captains", qtrue, qtrue, NULL, ":^7 Shows team captains" }, // { "coach", qtrue, qtrue, NULL, ":^7 Accepts coach invitation/restarts coach view" }, // { "coachdecline", qtrue, qtrue, NULL, ":^7 Declines coach invitation or resigns coach status" }, // { "coachinvite", qtrue, qtrue, NULL, " :^7 Invites a player to coach team" }, // { "coachkick", qtrue, qtrue, NULL, " :^7 Kicks active coach from team" }, { "commands", qtrue, qtrue, G_commands_cmd, ":^7 Gives a list of OSP-specific commands" }, { "currenttime", qtrue, qtrue, NULL, ":^7 Displays current local time" }, { "follow", qfalse, qtrue, Cmd_Follow_f, " :^7 Spectates a particular player or team" }, // { "invite", qtrue, qtrue, NULL, " :^7 Invites a player to join a team" }, { "lock", qtrue, qtrue, G_lock_cmd, ":^7 Locks a player's team to prevent others from joining" }, { "notready", qtrue, qfalse, G_ready_cmd, ":^7 Sets your status to ^5not ready^7 to start a match" }, { "pause", qfalse, qtrue, G_pause_cmd, ":^7 Allows a team to pause a match" }, { "players", qtrue, qtrue, G_players_cmd, ":^7 Lists all active players and their IDs/information" }, { "ready", qtrue, qtrue, G_ready_cmd, ":^7 Sets your status to ^5ready^7 to start a match" }, { "readyteam", qfalse, qtrue, G_teamready_cmd, ":^7 Sets an entire team's status to ^5ready^7 to start a match" }, { "ref", qtrue, qtrue, G_ref_cmd, " :^7 Become a referee (admin access)" }, // { "remove", qtrue, qtrue, NULL, " :^7 Removes a player from the team" }, // { "resign", qtrue, qtrue, NULL, " [player_ID]:^7 Resigns captainship. Can optionally be given to another teammate" }, { "say_teamnl", qtrue, qtrue, G_say_teamnl_cmd, ":^7 Sends a team chat without location info" }, { "scores", qtrue, qtrue, G_scores_cmd, ":^7 Displays current match stat info" }, { "specinvite", qtrue, qtrue, G_specinvite_cmd, ":^7 Invites a player to spectate a speclock'ed team" }, { "speclock", qtrue, qtrue, G_speclock_cmd, ":^7 Locks a player's team from spectators" }, // { "speconly", qtrue, qtrue, NULL, ":^7 Toggles option to stay as a spectator in 1v1" }, { "specunlock", qtrue, qfalse, G_speclock_cmd, ":^7 Unlocks a player's team from spectators" }, { "statsall", qtrue, qfalse, G_statsall_cmd, ":^7 Shows weapon accuracy stats for all players" }, { "statsdump", qtrue, qtrue, NULL, ":^7 Shows player stats + match info saved locally to a file" }, { "team", qtrue, qtrue, Cmd_Team_f, " :^7 Joins a team (b = allies, r = axis, s = spectator)" }, // { "setclass", qtrue, qtrue, Cmd_SetClass_f, " :^7 Selects a class" }, // { "setweapons", qtrue, qtrue, Cmd_SetWeapons_f, " :^7 Selects your weapon loadout" }, { "timein", qfalse, qfalse, G_pause_cmd, ":^7 Unpauses a match (if initiated by the issuing team)" }, { "timeout", qfalse, qtrue, G_pause_cmd, ":^7 Allows a team to pause a match" }, { "topshots", qtrue, qtrue, G_weaponRankings_cmd, ":^7 Shows BEST player for each weapon. Add ^3^7 to show all stats for a weapon" }, { "unlock", qtrue, qfalse, G_lock_cmd, ":^7 Unlocks a player's team, allowing others to join" }, { "unpause", qfalse, qfalse, G_pause_cmd, ":^7 Unpauses a match (if initiated by the issuing team)" }, { "unready", qtrue, qfalse, G_ready_cmd, ":^7 Sets your status to ^5not ready^7 to start a match" }, { "weaponstats", qtrue, qfalse, G_weaponStats_cmd, " [player_ID]:^7 Shows weapon accuracy stats for a player" }, // { "viewcam", qfalse, qtrue, NULL, ":^7 Switches to cinematic camera mode" }, // { "vc_follow", qfalse, qtrue, NULL, " [player_ID]:^7 Puts viewcam in follow mode. Can optionally to follow a specific player" }, // { "vc_free", qfalse, qtrue, NULL, ":^7 Toggle viewcam between manual/automatic change" }, // { "vc_view", qfalse, qtrue, NULL, ":^7 Toggle ViewCam between static/dynamic views" }, // { "viewadd", qfalse, qtrue, NULL, " :^7 Adds a player to multi-screen view" }, // { "viewall", qfalse, qtrue, NULL, ":^7 Adds all active players to a multi-screen view" }, // { "viewallies", qfalse, qtrue, NULL, ": ^7 Views entire allies/axis team" }, // { "viewaxis", qfalse, qtrue, NULL, ": ^7 Views entire allies/axis team" }, // { "viewcyclenext", qfalse, qtrue, NULL, ":^7 Cycles through players in current view" }, // { "viewfollow", qfalse, qtrue, NULL, ":^7 Follows current highlighted view" }, // { "viewnext", qfalse, qtrue, NULL, ":^7 Moves through active screen in a multi-screen display" }, // { "viewnone", qfalse, qtrue, NULL, ":^7 Disables multiview mode and goes back to spectator mode" }, // { "viewremove", qfalse, qtrue, NULL, " [player_ID]:^7 Removes current selected or specific player from multi-screen view" }, { 0, qfalse, qtrue, NULL, 0 } }; // OSP-specific Commands qboolean G_commandCheck( gentity_t *ent, char *cmd, qboolean fDoAnytime ) { unsigned int i, cCommands = sizeof( aCommandInfo ) / sizeof( aCommandInfo[0] ); const cmd_reference_t *pCR; for ( i = 0; i < cCommands; i++ ) { pCR = &aCommandInfo[i]; if ( NULL != pCR->pCommand && pCR->fAnytime == fDoAnytime && 0 == Q_stricmp( cmd, pCR->pszCommandName ) ) { if ( !G_commandHelp( ent, cmd, i ) ) { pCR->pCommand( ent, i, pCR->fValue ); } return( qtrue ); } } return( G_smvCommands( ent, cmd ) ); } // Prints specific command help info. qboolean G_commandHelp( gentity_t *ent, char *pszCommand, unsigned int dwCommand ) { char arg[MAX_TOKEN_CHARS]; if ( !ent ) { return( qfalse ); } trap_Argv( 1, arg, sizeof( arg ) ); if ( !Q_stricmp( arg, "?" ) ) { CP( va( "print \"\n^3%s%s\n\n\"", pszCommand, aCommandInfo[dwCommand].pszHelpInfo ) ); return( qtrue ); } return( qfalse ); } // Debounces cmd request as necessary. qboolean G_cmdDebounce( gentity_t *ent, const char *pszCommandName ) { if ( ent->client->pers.cmd_debounce > level.time ) { CP( va( "print \"Wait another %.1fs to issue ^3%s\n\"", 1.0 * (float)( ent->client->pers.cmd_debounce - level.time ) / 1000.0, pszCommandName ) ); return( qfalse ); } ent->client->pers.cmd_debounce = level.time + CMD_DEBOUNCE; return( qtrue ); } void G_noTeamControls( gentity_t *ent ) { CP( "cpm \"Team commands not enabled on this server.\n\"" ); } //////////////////////////////////////////////////////////////////////////// ///// ///// Match Commands ///// ///// // ************** COMMANDS / ? // // Lists server commands. void G_commands_cmd( gentity_t *ent, unsigned int dwCommand, qboolean fValue ) { int i, rows, num_cmds = sizeof( aCommandInfo ) / sizeof( aCommandInfo[0] ) - 1; rows = num_cmds / HELP_COLUMNS; if ( num_cmds % HELP_COLUMNS ) { rows++; } if ( rows < 0 ) { return; } CP( "cpm \"^5\nAvailable OSP Game-Commands:\n----------------------------\n\"" ); for ( i = 0; i < rows; i++ ) { if ( i + rows * 3 + 1 <= num_cmds ) { CP( va( "print \"^3%-17s%-17s%-17s%-17s\n\"", aCommandInfo[i].pszCommandName, aCommandInfo[i + rows].pszCommandName, aCommandInfo[i + rows * 2].pszCommandName, aCommandInfo[i + rows * 3].pszCommandName ) ); } else if ( i + rows * 2 + 1 <= num_cmds ) { CP( va( "print \"^3%-17s%-17s%-17s\n\"", aCommandInfo[i].pszCommandName, aCommandInfo[i + rows].pszCommandName, aCommandInfo[i + rows * 2].pszCommandName ) ); } else if ( i + rows + 1 <= num_cmds ) { CP( va( "print \"^3%-17s%-17s\n\"", aCommandInfo[i].pszCommandName, aCommandInfo[i + rows].pszCommandName ) ); } else { CP( va( "print \"^3%-17s\n\"", aCommandInfo[i].pszCommandName ) ); } } CP( "cpm \"\nType: ^3\\command_name ?^7 for more information\n\"" ); } // ************** LOCK / UNLOCK // // Locks/unlocks a player's team. void G_lock_cmd( gentity_t *ent, unsigned int dwCommand, qboolean fLock ) { int tteam; if ( team_nocontrols.integer ) { G_noTeamControls( ent ); return; } if ( !G_cmdDebounce( ent, aCommandInfo[dwCommand].pszCommandName ) ) { return; } tteam = G_teamID( ent ); if ( tteam == TEAM_AXIS || tteam == TEAM_ALLIES ) { if ( teamInfo[tteam].team_lock == fLock ) { CP( va( "print \"^3Your team is already %sed!\n\"", lock_status[fLock] ) ); } else { char *info = va( "\"The %s team is now %sed!\n\"", aTeams[tteam], lock_status[fLock] ); teamInfo[tteam].team_lock = fLock; AP( va( "print %s", info ) ); AP( va( "cp %s", info ) ); } } else {CP( va( "print \"Spectators can't %s a team!\n\"", lock_status[fLock] ) );} } // ************** PAUSE / UNPAUSE // // Pause/unpause a match. void G_pause_cmd( gentity_t *ent, unsigned int dwCommand, qboolean fPause ) { char *status[2] = { "^5UN", "^1" }; if ( team_nocontrols.integer ) { G_noTeamControls( ent ); return; } if ( ( PAUSE_UNPAUSING >= level.match_pause && !fPause ) || ( PAUSE_NONE != level.match_pause && fPause ) ) { CP( va( "print \"The match is already %sPAUSED^7!\n\"", status[fPause] ) ); return; } // Alias for referees if ( ent->client->sess.referee ) { G_refPause_cmd( ent, fPause ); } else { int tteam = G_teamID( ent ); if ( !G_cmdDebounce( ent, aCommandInfo[dwCommand].pszCommandName ) ) { return; } // Trigger the auto-handling of pauses if ( fPause ) { if ( 0 == teamInfo[tteam].timeouts ) { CP( "cpm \"^3Your team has no more timeouts remaining!\n\"" ); return; } else { teamInfo[tteam].timeouts--; level.match_pause = tteam + 128; G_globalSound( "sound/misc/referee.wav" ); G_spawnPrintf( DP_PAUSEINFO, level.time + 15000, NULL ); AP( va( "print \"^3Match is ^1PAUSED^3!\n^7[%s^7: - %d Timeouts Remaining]\n\"", aTeams[tteam], teamInfo[tteam].timeouts ) ); CP( va( "cp \"^3Match is ^1PAUSED^3! (%s^3)\n\"", aTeams[tteam] ) ); level.server_settings |= CV_SVS_PAUSE; trap_SetConfigstring( CS_SERVERTOGGLES, va( "%d", level.server_settings ) ); } } else if ( tteam + 128 != level.match_pause ) { CP( "cpm \"^3Your team didn't call the timeout!\n\"" ); return; } else { AP( "print \"\n^3Match is ^5UNPAUSED^3 ... resuming in 10 seconds!\n\n\"" ); level.match_pause = PAUSE_UNPAUSING; G_globalSound( "sound/osp/prepare.wav" ); G_spawnPrintf( DP_UNPAUSING, level.time + 10, NULL ); } } } // ************** PLAYERS // // Show client info void G_players_cmd( gentity_t *ent, unsigned int dwCommand, qboolean fValue ) { int i, idnum, max_rate, cnt = 0, tteam; int user_rate, user_snaps; gclient_t *cl; gentity_t *cl_ent; char n2[MAX_NETNAME], ready[16], ref[16], rate[256]; char *s, *tc, *coach, userinfo[MAX_INFO_STRING]; if ( g_gamestate.integer == GS_PLAYING ) { if ( ent ) { CP( "print \"\n^3 ID^1 : ^3Player Nudge Rate MaxPkts Snaps\n\"" ); CP( "print \"^1-----------------------------------------------------------^7\n\"" ); } else { G_Printf( " ID : Player Nudge Rate MaxPkts Snaps\n" ); G_Printf( "-----------------------------------------------------------\n" ); } } else { if ( ent ) { CP( "print \"\n^3Status^1 : ^3ID^1 : ^3Player Nudge Rate MaxPkts Snaps\n\"" ); CP( "print \"^1---------------------------------------------------------------------^7\n\"" ); } else { G_Printf( "Status : ID : Player Nudge Rate MaxPkts Snaps\n" ); G_Printf( "---------------------------------------------------------------------\n" ); } } max_rate = trap_Cvar_VariableIntegerValue( "sv_maxrate" ); for ( i = 0; i < level.numConnectedClients; i++ ) { idnum = level.sortedClients[i]; //level.sortedNames[i]; cl = &level.clients[idnum]; cl_ent = g_entities + idnum; SanitizeString( cl->pers.netname, n2, qtrue ); n2[26] = 0; ref[0] = 0; ready[0] = 0; // Rate info if ( cl_ent->r.svFlags & SVF_BOT ) { strcpy( rate, va( "%s%s%s%s", "[BOT]", " -----", " --", " --" ) ); } else if ( cl->pers.connected == CON_CONNECTING ) { strcpy( rate, va( "%s", "^3>>> CONNECTING <<<" ) ); } else { trap_GetUserinfo( idnum, userinfo, sizeof( userinfo ) ); s = Info_ValueForKey( userinfo, "rate" ); user_rate = ( max_rate > 0 && atoi( s ) > max_rate ) ? max_rate : atoi( s ); s = Info_ValueForKey( userinfo, "snaps" ); user_snaps = atoi( s ); strcpy( rate, va( "%5d%6d%9d%7d", cl->pers.clientTimeNudge, user_rate, cl->pers.clientMaxPackets, user_snaps ) ); } if ( g_gamestate.integer != GS_PLAYING ) { if ( cl->sess.sessionTeam == TEAM_SPECTATOR || cl->pers.connected == CON_CONNECTING ) { strcpy( ready, ( ( ent ) ? "^5--------^1 :" : "-------- :" ) ); } else if ( cl->pers.ready || ( g_entities[idnum].r.svFlags & SVF_BOT ) ) { strcpy( ready, ( ( ent ) ? "^3(READY)^1 :" : "(READY) :" ) ); } else { strcpy( ready, ( ( ent ) ? "NOTREADY^1 :" : "NOTREADY :" ) ); } } if ( cl->sess.referee ) { strcpy( ref, "REF" ); } if ( cl->sess.coach_team ) { tteam = cl->sess.coach_team; coach = ( ent ) ? "^3C" : "C"; } else { tteam = cl->sess.sessionTeam; coach = " "; } tc = ( ent ) ? "^7 " : " "; if ( g_gametype.integer >= GT_WOLF ) { if ( tteam == TEAM_AXIS ) { tc = ( ent ) ? "^1X^7" : "X"; } if ( tteam == TEAM_ALLIES ) { tc = ( ent ) ? "^4L^7" : "L"; } } if ( ent ) { CP( va( "print \"%s%s%2d%s^1:%s %-26s^7%s ^3%s\n\"", ready, tc, idnum, coach, ( ( ref[0] ) ? "^3" : "^7" ), n2, rate, ref ) ); } else { G_Printf( "%s%s%2d%s: %-26s%s %s\n", ready, tc, idnum, coach, n2, rate, ref );} cnt++; } if ( ent ) { CP( va( "print \"\n^3%2d^7 total players\n\n\"", cnt ) ); } else { G_Printf( "\n%2d total players\n\n", cnt );} // Team speclock info if ( g_gametype.integer >= GT_WOLF ) { for ( i = TEAM_AXIS; i <= TEAM_ALLIES; i++ ) { if ( teamInfo[i].spec_lock ) { if ( ent ) { CP( va( "print \"** %s team is speclocked.\n\"", aTeams[i] ) ); } else { G_Printf( "** %s team is speclocked.\n", aTeams[i] );} } } } } // ************** READY / NOTREADY // // Sets a player's "ready" status. void G_ready_cmd( gentity_t *ent, unsigned int dwCommand, qboolean state ) { char *status[2] = { " NOT", "" }; if ( g_gamestate.integer == GS_PLAYING || g_gamestate.integer == GS_INTERMISSION ) { CP( "cpm \"Match is already in progress!\n\"" ); return; } if ( !state && g_gamestate.integer == GS_WARMUP_COUNTDOWN ) { CP( "cpm \"Countdown started.... ^3notready^7 ignored!\n\"" ); return; } if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { CP( "cpm \"You must be in the game to be ^3ready^7!\n\"" ); return; } // Can't ready until enough players. if ( level.numPlayingClients < match_minplayers.integer ) { CP( "cpm \"Not enough players to start match!\n\"" ); return; } if ( !G_cmdDebounce( ent, aCommandInfo[dwCommand].pszCommandName ) ) { return; } // Move them to correct ready state if ( ent->client->pers.ready == state ) { CP( va( "print \"You are already%s ready!\n\"", status[state] ) ); } else { ent->client->pers.ready = state; if ( !level.intermissiontime ) { if ( state ) { G_MakeReady( ent ); } else { G_MakeUnready( ent ); } AP( va( "print \"%s^7 is%s ready!\n\"", ent->client->pers.netname, status[state] ) ); AP( va( "cp \"\n%s\n^3is%s ready!\n\"", ent->client->pers.netname, status[state] ) ); } } G_readyMatchState(); } // ************** SAY_TEAMNL // // Team chat w/no location info void G_say_teamnl_cmd( gentity_t *ent, unsigned int dwCommand, qboolean fValue ) { Cmd_Say_f( ent, SAY_TEAMNL, qfalse ); } // ************** SCORES // // Shows match stats to the requesting client. void G_scores_cmd( gentity_t *ent, unsigned int dwCommand, qboolean fValue ) { G_printMatchInfo( ent ); } // ************** SPECINVITE // // Sends an invitation to a player to spectate a team. void G_specinvite_cmd( gentity_t *ent, unsigned int dwCommand, qboolean fLock ) { int tteam, pid; gentity_t *player; char arg[MAX_TOKEN_CHARS]; if ( team_nocontrols.integer ) { G_noTeamControls( ent ); return; } if ( !G_cmdDebounce( ent, aCommandInfo[dwCommand].pszCommandName ) ) { return; } tteam = G_teamID( ent ); if ( tteam == TEAM_AXIS || tteam == TEAM_ALLIES ) { if ( !teamInfo[tteam].spec_lock ) { CP( "cpm \"Your team isn't locked from spectators!\n\"" ); return; } // Find the player to invite. trap_Argv( 1, arg, sizeof( arg ) ); if ( ( pid = ClientNumberFromString( ent, arg ) ) == -1 ) { return; } player = g_entities + pid; // Can't invite self if ( player->client == ent->client ) { CP( "cpm \"You can't specinvite yourself!\n\"" ); return; } // Can't invite an active player. if ( player->client->sess.sessionTeam != TEAM_SPECTATOR ) { CP( "cpm \"You can't specinvite a non-spectator!\n\"" ); return; } player->client->sess.spec_invite |= tteam; // Notify sender/recipient CP( va( "print \"%s^7 has been sent a spectator invitation.\n\"", player->client->pers.netname ) ); G_printFull( va( "*** You've been invited to spectate the %s team!", aTeams[tteam] ), player ); } else {CP( "cpm \"Spectators can't specinvite players!\n\"" );} } // ************** SPECLOCK / SPECUNLOCK // // Locks/unlocks a player's team from spectators. void G_speclock_cmd( gentity_t *ent, unsigned int dwCommand, qboolean fLock ) { int tteam; if ( team_nocontrols.integer ) { G_noTeamControls( ent ); return; } if ( !G_cmdDebounce( ent, aCommandInfo[dwCommand].pszCommandName ) ) { return; } tteam = G_teamID( ent ); if ( tteam == TEAM_AXIS || tteam == TEAM_ALLIES ) { if ( teamInfo[tteam].spec_lock == fLock ) { CP( va( "print \"\n^3Your team is already %sed from spectators!\n\n\"", lock_status[fLock] ) ); } else { G_printFull( va( "The %s team is now %sed from spectators", aTeams[tteam], lock_status[fLock] ), NULL ); G_updateSpecLock( tteam, fLock ); if ( fLock ) { CP( "cpm \"Use ^3specinvite^7 to invite people to spectate.\n\"" ); } } } else { CP( va( "print \"Spectators can't %s a team from spectators!\n\"", lock_status[fLock] ) ); } } // ************** WEAPONSTATS // // Shows a player's stats to the requesting client. void G_weaponStats_cmd( gentity_t *ent, unsigned int dwCommand, qboolean fDump ) { G_statsPrint( ent, 0 ); } // ************** STATSALL // // Shows all players' stats to the requesting client. void G_statsall_cmd( gentity_t *ent, unsigned int dwCommand, qboolean fDump ) { int i; gentity_t *player; for ( i = 0; i < level.numConnectedClients; i++ ) { player = &g_entities[level.sortedClients[i]]; if ( player->client->sess.sessionTeam == TEAM_SPECTATOR ) { continue; } CP( va( "ws %s\n", G_createStats( player ) ) ); } } // ************** TEAMREADY // // Sets a player's team "ready" status. void G_teamready_cmd( gentity_t *ent, unsigned int dwCommand, qboolean state ) { int i, tteam = G_teamID( ent ); gclient_t *cl; if ( g_gamestate.integer == GS_PLAYING || g_gamestate.integer == GS_INTERMISSION ) { CP( "cpm \"Match is already in progress!\n\"" ); return; } if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { CP( "cpm \"Spectators can't ready a team!\n\"" ); return; } // Can't ready until enough players. if ( level.numPlayingClients < match_minplayers.integer ) { CP( "cpm \"Not enough players to start match!\n\"" ); return; } if ( !G_cmdDebounce( ent, aCommandInfo[dwCommand].pszCommandName ) ) { return; } // Move them to correct ready state for ( i = 0; i < level.numPlayingClients; i++ ) { cl = level.clients + level.sortedClients[i]; if ( cl->sess.sessionTeam == tteam ) { cl->pers.ready = qtrue; G_MakeReady( ent ); } } G_printFull( va( "The %s team is ready!", aTeams[tteam] ), NULL ); G_readyMatchState(); } // These map to WS_* weapon indexes const int cQualifyingShots[WS_MAX] = { 10, // 0 15, // 1 15, // 2 30, // 3 30, // 4 30, // 5 30, // 6 3, // 7 100, // 8 5, // 9 5, // 10 5, // 11 3, // 12 3, // 13 5, // 14 3, // 15 3, // 16 5, // 17 10, // 18 100, // 19 30, // 20 30 // 21 }; // ************** TOPSHOTS/BOTTOMSHOTS // // Gives back overall or specific weapon rankings int QDECL SortStats( const void *a, const void *b ) { gclient_t *ca, *cb; float accA, accB; ca = &level.clients[*(int *)a]; cb = &level.clients[*(int *)b]; // then connecting clients if ( ca->pers.connected == CON_CONNECTING ) { return( 1 ); } if ( cb->pers.connected == CON_CONNECTING ) { return( -1 ); } if ( ca->sess.sessionTeam == TEAM_SPECTATOR ) { return( 1 ); } if ( cb->sess.sessionTeam == TEAM_SPECTATOR ) { return( -1 ); } if ( ( ca->sess.aWeaponStats[iWeap].atts ) < cQualifyingShots[iWeap] ) { return( 1 ); } if ( ( cb->sess.aWeaponStats[iWeap].atts ) < cQualifyingShots[iWeap] ) { return( -1 ); } accA = (float)( ca->sess.aWeaponStats[iWeap].hits * 100.0 ) / (float)( ca->sess.aWeaponStats[iWeap].atts ); accB = (float)( cb->sess.aWeaponStats[iWeap].hits * 100.0 ) / (float)( cb->sess.aWeaponStats[iWeap].atts ); // then sort by accuracy if ( accA > accB ) { return( -1 ); } return( 1 ); } // Shows the most accurate players for each weapon to the requesting client void G_weaponStatsLeaders_cmd( gentity_t* ent, qboolean doTop, qboolean doWindow ) { int i, iWeap, shots, wBestAcc, cClients, cPlaces; int aClients[MAX_CLIENTS]; float acc; char z[MAX_STRING_CHARS]; const gclient_t* cl; z[0] = 0; for ( iWeap = WS_KNIFE; iWeap < WS_MAX; iWeap++ ) { wBestAcc = ( doTop ) ? 0 : 99999; cClients = 0; cPlaces = 0; // suckfest - needs two passes, in case there are ties for ( i = 0; i < level.numConnectedClients; i++ ) { cl = &level.clients[level.sortedClients[i]]; if ( cl->sess.sessionTeam == TEAM_SPECTATOR ) { continue; } shots = cl->sess.aWeaponStats[iWeap].atts; if ( shots >= cQualifyingShots[iWeap] ) { acc = (float)( ( cl->sess.aWeaponStats[iWeap].hits ) * 100.0 ) / (float)shots; aClients[cClients++] = level.sortedClients[i]; if ( ( ( doTop ) ? acc : (float)wBestAcc ) > ( ( doTop ) ? wBestAcc : acc ) ) { wBestAcc = (int)acc; cPlaces++; } } } if ( !doTop && cPlaces < 2 ) { continue; } for ( i = 0; i < cClients; i++ ) { cl = &level.clients[ aClients[i] ]; acc = (float)( cl->sess.aWeaponStats[iWeap].hits * 100.0 ) / (float)( cl->sess.aWeaponStats[iWeap].atts ); if ( ( ( doTop ) ? acc : (float)wBestAcc + 0.999 ) >= ( ( doTop ) ? wBestAcc : acc ) ) { Q_strcat( z, sizeof( z ), va( " %d %d %d %d %d %d", iWeap + 1, aClients[i], cl->sess.aWeaponStats[iWeap].hits, cl->sess.aWeaponStats[iWeap].atts, cl->sess.aWeaponStats[iWeap].kills, cl->sess.aWeaponStats[iWeap].deaths ) ); } } } CP( va( "%sbstats%s %s 0", ( ( doWindow ) ? "w" : "" ), ( ( doTop ) ? "" : "b" ), z ) ); } // Shows best/worst accuracy for all weapons, or sorted // accuracies for a single weapon. void G_weaponRankings_cmd( gentity_t *ent, unsigned int dwCommand, qboolean state ) { gclient_t *cl; int c = 0, i, shots, wBestAcc; char z[MAX_STRING_CHARS]; if ( trap_Argc() < 2 ) { G_weaponStatsLeaders_cmd( ent, state, qfalse ); return; } wBestAcc = ( state ) ? 0 : 99999; // Find the weapon trap_Argv( 1, z, sizeof( z ) ); if ( ( iWeap = atoi( z ) ) == 0 || iWeap < WS_KNIFE || iWeap >= WS_MAX ) { for ( iWeap = WS_SYRINGE; iWeap >= WS_KNIFE; iWeap-- ) { if ( !Q_stricmp( z, aWeaponInfo[iWeap].pszCode ) ) { break; } } } if ( iWeap < WS_KNIFE ) { G_commandHelp( ent, ( state ) ? "topshots" : "bottomshots", dwCommand ); Q_strncpyz( z, "^3Available weapon codes:^7\n", sizeof( z ) ); for ( i = WS_KNIFE; i < WS_MAX; i++ ) { Q_strcat( z, sizeof( z ), va( " %s - %s\n", aWeaponInfo[i].pszCode, aWeaponInfo[i].pszName ) ); } CP( va( "print \"%s\"", z ) ); return; } memcpy( &level.sortedStats, &level.sortedClients, sizeof( level.sortedStats ) ); qsort( level.sortedStats, level.numConnectedClients, sizeof( level.sortedStats[0] ), SortStats ); z[0] = 0; for ( i = 0; i < level.numConnectedClients; i++ ) { cl = &level.clients[level.sortedStats[i]]; if ( cl->sess.sessionTeam == TEAM_SPECTATOR ) { continue; } shots = cl->sess.aWeaponStats[iWeap].atts; if ( shots >= cQualifyingShots[iWeap] ) { float acc = (float)( cl->sess.aWeaponStats[iWeap].hits * 100.0 ) / (float)shots; c++; wBestAcc = ( ( ( state ) ? acc : wBestAcc ) > ( ( state ) ? wBestAcc : acc ) ) ? (int)acc : wBestAcc; Q_strcat( z, sizeof( z ), va( " %d %d %d %d %d", level.sortedStats[i], cl->sess.aWeaponStats[iWeap].hits, shots, cl->sess.aWeaponStats[iWeap].kills, cl->sess.aWeaponStats[iWeap].deaths ) ); } } CP( va( "astats%s %d %d %d%s", ( ( state ) ? "" : "b" ), c, iWeap, wBestAcc, z ) ); }