// Copyright (C) 1999-2000 Id Software, Inc. // /* * Portions Copyright (C) 2000-2001 Tim Angus * * This program is free software; you can redistribute it and/or modify it * under the terms of the OSML - Open Source Modification License v1.0 as * described in the file COPYING which is distributed with this source * code. * * This program 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. */ #include "g_local.h" /* ================== G_SanitiseName Remove case and control characters from a player name ================== */ void G_SanitiseName( 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; } /* ================== G_ClientNumberFromString Returns a player number for either a number or name string Returns -1 if invalid ================== */ int G_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 G_SanitiseName( s, s2 ); for( idnum = 0, cl = level.clients; idnum < level.maxclients; idnum++, cl++ ) { if( cl->pers.connected != CON_CONNECTED ) continue; G_SanitiseName( 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; } /* ================== ScoreboardMessage ================== */ void ScoreboardMessage( gentity_t *ent ) { char entry[ 1024 ]; char string[ 1400 ]; int stringlength; int i, j; gclient_t *cl; int numSorted; // send the latest information on all clients string[ 0 ] = 0; stringlength = 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; Com_sprintf( entry, sizeof( entry ), " %i %i %i %i", level.sortedClients[ i ], cl->ps.persistant[ PERS_SCORE ], ping, ( level.time - cl->pers.enterTime ) / 60000 ); 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.alienKills, level.humanKills, string ) ); } /* ================== Cmd_Score_f Request current scoreboard information ================== */ void Cmd_Score_f( gentity_t *ent ) { ScoreboardMessage( 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; } /* ================== Cmd_Give_f Give items to a client ================== */ void Cmd_Give_f( gentity_t *ent ) { char *name; 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 ) { BG_packWeapon( ( 1 << WP_NUM_WEAPONS ) - 1 - ( 1 << WP_NONE ), ent->client->ps.stats ); if( !give_all ) return; } if( give_all || Q_stricmp( name, "ammo" ) == 0 ) { for( i = 0; i < MAX_WEAPONS; i++ ) BG_packAmmoArray( i, ent->client->ps.ammo, ent->client->ps.powerups, 999, 0, 0 ); if( !give_all ) return; } if( give_all || Q_stricmpn( name, "funds", 5 ) == 0 ) { int credits = atoi( name + 6 ); if( !credits ) ent->client->ps.persistant[ PERS_CREDIT ]++; else ent->client->ps.persistant[ PERS_CREDIT ] += credits; if( !give_all ) return; } } /* ================== 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; BeginIntermission( ); trap_SendServerCommand( ent - g_entities, "clientLevelShot" ); } /* ================= Cmd_Kill_f ================= */ void Cmd_Kill_f( gentity_t *ent ) { if( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) return; if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_NONE ) return; if( ent->client->ps.stats[ STAT_STATE ] & SS_INFESTING ) return; if( ent->client->ps.stats[ STAT_STATE ] & SS_HOVELING ) { trap_SendServerCommand( ent-g_entities, "print \"Leave the hovel first (use your destroy key)\n\"" ); return; } if( ent->health <= 0 ) return; if( g_cheats.integer ) { ent->flags &= ~FL_GODMODE; ent->client->ps.stats[ STAT_HEALTH ] = ent->health = 0; player_die( ent, ent, ent, 100000, MOD_SUICIDE ); } else { if( ent->suicideTime == 0 ) { trap_SendServerCommand( ent-g_entities, "print \"You will suicide in 10 seconds.\n\"" ); ent->suicideTime = level.time + 10000; } else if( ent->suicideTime > level.time ) { trap_SendServerCommand( ent-g_entities, "print \"Suicide cancelled.\n\"" ); ent->suicideTime = 0; } } } /* ================= G_ChangeTeam ================= */ void G_ChangeTeam( gentity_t *ent, pTeam_t newTeam ) { pTeam_t oldTeam = ent->client->pers.teamSelection; ent->client->pers.teamSelection = newTeam; if( oldTeam != newTeam ) { //if the client is in a queue make sure they are removed from it before changing if( oldTeam == PTE_ALIENS ) G_RemoveFromSpawnQueue( &level.alienSpawnQueue, ent->client->ps.clientNum ); else if( oldTeam == PTE_HUMANS ) G_RemoveFromSpawnQueue( &level.humanSpawnQueue, ent->client->ps.clientNum ); level.bankCredits[ ent->client->ps.clientNum ] = 0; ent->client->ps.persistant[ PERS_CREDIT ] = 0; ent->client->pers.classSelection = PCL_NONE; ClientSpawn( ent, NULL, NULL, NULL ); } //update ClientInfo ClientUserinfoChanged( ent->client->ps.clientNum ); } /* ================= Cmd_Team_f ================= */ void Cmd_Team_f( gentity_t *ent ) { pTeam_t team; char s[ MAX_TOKEN_CHARS ]; trap_Argv( 1, s, sizeof( s ) ); if( !strlen( s ) ) { trap_SendServerCommand( ent-g_entities, va("print \"team: %i\n\"", ent->client->pers.teamSelection ) ); return; } if( !Q_stricmp( s, "spectate" ) ) team = PTE_NONE; else if( !Q_stricmp( s, "aliens" ) ) { if( g_teamForceBalance.integer && level.numAlienClients > level.numHumanClients ) { G_TriggerMenu( ent->client->ps.clientNum, MN_A_TEAMFULL ); return; } team = PTE_ALIENS; } else if( !Q_stricmp( s, "humans" ) ) { if( g_teamForceBalance.integer && level.numHumanClients > level.numAlienClients ) { G_TriggerMenu( ent->client->ps.clientNum, MN_H_TEAMFULL ); return; } team = PTE_HUMANS; } else if( !Q_stricmp( s, "auto" ) ) { if( level.numHumanClients > level.numAlienClients ) team = PTE_ALIENS; else if( level.numHumanClients < level.numAlienClients ) team = PTE_HUMANS; else team = PTE_ALIENS + ( rand( ) % 2 ); } else { trap_SendServerCommand( ent-g_entities, va( "print \"Unknown team: %s\n\"", s ) ); return; } G_ChangeTeam( ent, team ); if( team == PTE_ALIENS ) trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE " joined the aliens.\n\"", ent->client->pers.netname ) ); else if( team == PTE_HUMANS ) trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE " joined the humans.\n\"", ent->client->pers.netname ) ); } /* ================== 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; 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 ]; 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->client->ps.stats[ STAT_PTEAM ] == ent->client->ps.stats[ STAT_PTEAM ] && 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 ); } /* ================== Cmd_Where_f ================== */ void Cmd_Where_f( gentity_t *ent ) { trap_SendServerCommand( ent-g_entities, va( "print \"%s\n\"", vtos( ent->s.origin ) ) ); } /* ================== 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->ps.stats[ STAT_PTEAM ] == PTE_NONE ) { 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, "kick" ) ) { } else if( !Q_stricmp( arg1, "clientkick" ) ) { } else if( !Q_stricmp( arg1, "timelimit" ) ) { } else { trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string.\n\"" ); trap_SendServerCommand( ent-g_entities, "print \"Vote commands are: map_restart, nextmap, map , " "kick , clientkick , " "timelimit