/* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. Copyright (C) 2000-2006 Tim Angus This file is part of Tremulous. Tremulous 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. Tremulous 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 Tremulous; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ // this file holds commands that can be executed by the server console, but not remote clients #include "g_local.h" /* =================== Svcmd_EntityList_f =================== */ void Svcmd_EntityList_f( void ) { int e; gentity_t *check; check = g_entities; for( e = 0; e < level.num_entities; e++, check++ ) { if( !check->inuse ) continue; G_Printf( "%3i:", e ); switch( check->s.eType ) { case ET_GENERAL: G_Printf( "ET_GENERAL " ); break; case ET_PLAYER: G_Printf( "ET_PLAYER " ); break; case ET_ITEM: G_Printf( "ET_ITEM " ); break; case ET_BUILDABLE: G_Printf( "ET_BUILDABLE " ); break; case ET_MISSILE: G_Printf( "ET_MISSILE " ); break; case ET_MOVER: G_Printf( "ET_MOVER " ); break; case ET_BEAM: G_Printf( "ET_BEAM " ); break; case ET_PORTAL: G_Printf( "ET_PORTAL " ); break; case ET_SPEAKER: G_Printf( "ET_SPEAKER " ); break; case ET_PUSH_TRIGGER: G_Printf( "ET_PUSH_TRIGGER " ); break; case ET_TELEPORT_TRIGGER: G_Printf( "ET_TELEPORT_TRIGGER " ); break; case ET_INVISIBLE: G_Printf( "ET_INVISIBLE " ); break; case ET_GRAPPLE: G_Printf( "ET_GRAPPLE " ); break; case ET_CORPSE: G_Printf( "ET_CORPSE " ); break; case ET_PARTICLE_SYSTEM: G_Printf( "ET_PARTICLE_SYSTEM " ); break; case ET_ANIMMAPOBJ: G_Printf( "ET_ANIMMAPOBJ " ); break; case ET_MODELDOOR: G_Printf( "ET_MODELDOOR " ); break; case ET_LIGHTFLARE: G_Printf( "ET_LIGHTFLARE " ); break; case ET_LEV2_ZAP_CHAIN: G_Printf( "ET_LEV2_ZAP_CHAIN " ); break; default: G_Printf( "%-3i ", check->s.eType ); break; } if( check->classname ) G_Printf( "%s", check->classname ); G_Printf( "\n" ); } } static gclient_t *ClientForString( char *s ) { int idnum, count; int pids[ MAX_CLIENTS ]; if( ( count = G_ClientNumbersFromString( s, pids, MAX_CLIENTS ) ) != 1 ) { idnum = G_ClientNumberFromString( s ); if( idnum == -1 ) { char err[ MAX_STRING_CHARS ]; G_MatchOnePlayer( pids, count, err, sizeof( err ) ); G_Printf( "%s\n", err ); return NULL; } } else idnum = pids[ 0 ]; return &level.clients[ idnum ]; } static void Svcmd_Status_f( void ) { int i; gclient_t *cl; char userinfo[ MAX_INFO_STRING ]; G_Printf( "slot score ping address rate name\n" ); G_Printf( "---- ----- ---- ------- ---- ----\n" ); for( i = 0, cl = level.clients; i < level.maxclients; i++, cl++ ) { if( cl->pers.connected == CON_DISCONNECTED ) continue; G_Printf( "%-4d ", i ); G_Printf( "%-5d ", cl->ps.persistant[ PERS_SCORE ] ); if( cl->pers.connected == CON_CONNECTING ) G_Printf( "CNCT " ); else G_Printf( "%-4d ", cl->ps.ping ); trap_GetUserinfo( i, userinfo, sizeof( userinfo ) ); G_Printf( "%-21s ", Info_ValueForKey( userinfo, "ip" ) ); G_Printf( "%-8d ", Info_ValueForKey( userinfo, "rate" ) ); G_Printf( "%s\n", cl->pers.netname ); // Info_ValueForKey( userinfo, "name" ) } } /* =================== Svcmd_ForceTeam_f forceteam =================== */ static void Svcmd_ForceTeam_f( void ) { gclient_t *cl; char str[ MAX_TOKEN_CHARS ]; team_t team; if( trap_Argc( ) != 3 ) { G_Printf( "usage: forceteam \n" ); return; } trap_Argv( 1, str, sizeof( str ) ); cl = ClientForString( str ); if( !cl ) return; trap_Argv( 2, str, sizeof( str ) ); team = G_TeamFromString( str ); if( team == NUM_TEAMS ) { G_Printf( "forceteam: invalid team \"%s\"\n", str ); return; } G_ChangeTeam( &g_entities[ cl - level.clients ], team ); } /* =================== Svcmd_LayoutSave_f layoutsave =================== */ static void Svcmd_LayoutSave_f( void ) { char str[ MAX_QPATH ]; char str2[ MAX_QPATH - 4 ]; char *s; int i = 0; if( trap_Argc( ) != 2 ) { G_Printf( "usage: layoutsave \n" ); return; } trap_Argv( 1, str, sizeof( str ) ); // sanitize name s = &str[ 0 ]; while( *s && i < sizeof( str2 ) - 1 ) { if( isalnum( *s ) || *s == '-' || *s == '_' ) { str2[ i++ ] = *s; str2[ i ] = '\0'; } s++; } if( !str2[ 0 ] ) { G_Printf( "layoutsave: invalid name \"%s\"\n", str ); return; } G_LayoutSave( str2 ); } char *ConcatArgs( int start ); /* =================== Svcmd_LayoutLoad_f layoutload [ [ [ ...\n" ); return; } s = ConcatArgs( 1 ); Q_strncpyz( layouts, s, sizeof( layouts ) ); trap_Cvar_Set( "g_layouts", layouts ); trap_SendConsoleCommand( EXEC_APPEND, "map_restart\n" ); level.restarted = qtrue; } static void Svcmd_AdmitDefeat_f( void ) { int team; char teamNum[ 2 ]; if( trap_Argc( ) != 2 ) { G_Printf("admitdefeat: must provide a team\n"); return; } trap_Argv( 1, teamNum, sizeof( teamNum ) ); team = G_TeamFromString( teamNum ); if( team == TEAM_ALIENS ) { G_TeamCommand( TEAM_ALIENS, "cp \"Hivemind Link Broken\" 1"); trap_SendServerCommand( -1, "print \"Alien team has admitted defeat\n\"" ); } else if( team == TEAM_HUMANS ) { G_TeamCommand( TEAM_HUMANS, "cp \"Life Support Terminated\" 1"); trap_SendServerCommand( -1, "print \"Human team has admitted defeat\n\"" ); } else { G_Printf("admitdefeat: invalid team\n"); return; } level.surrenderTeam = team; G_BaseSelfDestruct( team ); } static void Svcmd_TeamWin_f( void ) { // this is largely made redundant by admitdefeat char cmd[ 6 ]; trap_Argv( 0, cmd, sizeof( cmd ) ); switch( G_TeamFromString( cmd ) ) { case TEAM_ALIENS: G_BaseSelfDestruct( TEAM_HUMANS ); break; case TEAM_HUMANS: G_BaseSelfDestruct( TEAM_ALIENS ); break; default: return; } } static void Svcmd_Evacuation_f( void ) { trap_SendServerCommand( -1, "print \"Evacuation ordered\n\"" ); level.lastWin = TEAM_NONE; trap_SetConfigstring( CS_WINNER, "Evacuation" ); LogExit( "Evacuation." ); } static void Svcmd_MapRotation_f( void ) { char rotationName[ MAX_QPATH ]; if( trap_Argc( ) != 2 ) { G_Printf( "usage: maprotation \n" ); return; } G_ClearRotationStack( ); trap_Argv( 1, rotationName, sizeof( rotationName ) ); if( !G_StartMapRotation( rotationName, qfalse, qtrue ) ) G_Printf( "maprotation: invalid map rotation \"%s\"\n", rotationName ); } static void Svcmd_TeamMessage_f( void ) { char teamNum[ 2 ]; team_t team; if( trap_Argc( ) < 3 ) { G_Printf( "usage: say_team \n" ); return; } trap_Argv( 1, teamNum, sizeof( teamNum ) ); team = G_TeamFromString( teamNum ); if( team == NUM_TEAMS ) { G_Printf( "say_team: invalid team \"%s\"\n", teamNum ); return; } G_TeamCommand( team, va( "tchat -1 \"%s\"", ConcatArgs( 2 ) ) ); G_LogPrintf( "SayTeam: -1 \"console\": %s\n", ConcatArgs( 2 ) ); } static void Svcmd_CenterPrint_f( void ) { if( trap_Argc( ) < 2 ) { G_Printf( "usage: cp \n" ); return; } trap_SendServerCommand( -1, va( "cp \"%s\"", ConcatArgs( 1 ) ) ); } static void Svcmd_EjectClient_f( void ) { char *reason, name[ MAX_STRING_CHARS ]; if( trap_Argc( ) < 2 ) { G_Printf( "usage: eject \n" ); return; } trap_Argv( 1, name, sizeof( name ) ); reason = ConcatArgs( 2 ); if( atoi( name ) == -1 ) { int i; for( i = 0; i < level.maxclients; i++ ) { if( level.clients[ i ].pers.connected == CON_DISCONNECTED ) continue; if( level.clients[ i ].pers.localClient ) continue; trap_DropClient( i, reason ); } } else { gclient_t *cl = ClientForString( name ); if( !cl ) return; if( cl->pers.localClient ) { G_Printf( "eject: cannot eject local clients\n" ); return; } trap_DropClient( cl-level.clients, reason ); } } static void Svcmd_DumpUser_f( void ) { char name[ MAX_STRING_CHARS ], userinfo[ MAX_INFO_STRING ]; char key[ BIG_INFO_KEY ], value[ BIG_INFO_VALUE ]; const char *info; gclient_t *cl; if( trap_Argc( ) != 2 ) { G_Printf( "usage: dumpuser \n" ); return; } trap_Argv( 1, name, sizeof( name ) ); cl = ClientForString( name ); if( !cl ) return; trap_GetUserinfo( cl-level.clients, userinfo, sizeof( userinfo ) ); info = &userinfo[ 0 ]; G_Printf( "userinfo\n--------\n" ); //Info_Print( userinfo ); while( 1 ) { Info_NextPair( &info, key, value ); if( !*info ) return; G_Printf( "%-20s%s\n", key, value ); } } static void Svcmd_PrintQueue_f( void ) { char team[ MAX_STRING_CHARS ]; if( trap_Argc() != 2 ) { G_Printf( "usage: printqueue \n" ); return; } trap_Argv( 1, team, sizeof( team ) ); switch( G_TeamFromString( team ) ) { case TEAM_ALIENS: G_PrintSpawnQueue( &level.alienSpawnQueue ); break; case TEAM_HUMANS: G_PrintSpawnQueue( &level.humanSpawnQueue ); break; default: G_Printf( "unknown team\n" ); } } static void Svcmd_Chat_f( void ) { char *s = ConcatArgs( 1 ); trap_SendServerCommand( -1, va( "chat -1 \"%s\"", s ) ); G_LogPrintf("chat: %s\n", s ); } // dumb wrapper for "a" and "m" and "say" static void Svcmd_MessageWrapper( void ) { char cmd[ 5 ]; trap_Argv( 0, cmd, sizeof( cmd ) ); if( !Q_stricmp( cmd, "a" ) ) Cmd_AdminMessage_f( NULL ); else if( !Q_stricmp( cmd, "m" ) ) Cmd_PrivateMessage_f( NULL ); else if( !Q_stricmp( cmd, "say" ) ) G_Say( NULL, SAY_ALL, ConcatArgs( 1 ) ); } static void Svcmd_SuddenDeath_f( void ) { char secs[ 5 ]; int offset; trap_Argv( 1, secs, sizeof( secs ) ); offset = atoi( secs ); level.suddenDeathBeginTime = level.time - level.startTime + offset * 1000; trap_SendServerCommand( -1, va( "cp \"Sudden Death will begin in %d second%s\"", offset, offset == 1 ? "" : "s" ) ); } struct { char *cmd; qboolean dedicated; void ( *function )( void ); } svcmds[ ] = { { "entityList", qfalse, Svcmd_EntityList_f }, { "status", qfalse, Svcmd_Status_f }, { "forceTeam", qfalse, Svcmd_ForceTeam_f }, { "mapRotation", qfalse, Svcmd_MapRotation_f }, { "stopMapRotation", qfalse, G_StopMapRotation }, { "advanceMapRotation", qfalse, G_AdvanceMapRotation }, { "alienWin", qfalse, Svcmd_TeamWin_f }, { "humanWin", qfalse, Svcmd_TeamWin_f }, { "layoutSave", qfalse, Svcmd_LayoutSave_f }, { "layoutLoad", qfalse, Svcmd_LayoutLoad_f }, { "eject", qfalse, Svcmd_EjectClient_f }, { "dumpuser", qfalse, Svcmd_DumpUser_f }, { "admitDefeat", qfalse, Svcmd_AdmitDefeat_f }, { "evacuation", qfalse, Svcmd_Evacuation_f }, { "printqueue", qfalse, Svcmd_PrintQueue_f }, // don't handle communication commands unless dedicated { "cp", qtrue, Svcmd_CenterPrint_f }, { "say_team", qtrue, Svcmd_TeamMessage_f }, { "say", qtrue, Svcmd_MessageWrapper }, { "chat", qtrue, Svcmd_Chat_f }, { "m", qtrue, Svcmd_MessageWrapper }, { "a", qtrue, Svcmd_MessageWrapper }, { "suddendeath", qfalse, Svcmd_SuddenDeath_f } }; /* ================= ConsoleCommand ================= */ qboolean ConsoleCommand( void ) { char cmd[ MAX_TOKEN_CHARS ]; int i; trap_Argv( 0, cmd, sizeof( cmd ) ); for( i = 0; i < sizeof( svcmds ) / sizeof( svcmds[ 0 ] ); i++ ) { if( !Q_stricmp( cmd, svcmds[ i ].cmd ) ) { if( svcmds[ i ].dedicated && !g_dedicated.integer ) return qfalse; svcmds[ i ].function( ); return qtrue; } } // see if this is an admin command if( G_admin_cmd_check( NULL, qfalse ) ) return qtrue; if( g_dedicated.integer ) G_Printf( "unknown command: %s\n", cmd ); return qfalse; }