/* =========================================================================== Copyright (C) 1999 - 2005, Id Software, Inc. Copyright (C) 2000 - 2013, Raven Software, Inc. Copyright (C) 2001 - 2013, Activision, Inc. Copyright (C) 2005 - 2015, ioquake3 contributors Copyright (C) 2013 - 2015, OpenJK contributors This file is part of the OpenJK source code. OpenJK is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. 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. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, see . =========================================================================== */ #include "g_local.h" #include "bg_saga.h" #include "ui/menudef.h" // for the voice chats //rww - for getting bot commands... int AcceptBotCommand(char *cmd, gentity_t *pl); //end rww void WP_SetSaber( int entNum, saberInfo_t *sabers, int saberNum, const char *saberName ); void Cmd_NPC_f( gentity_t *ent ); void SetTeamQuick(gentity_t *ent, int team, qboolean doBegin); /* ================== 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; if (numSorted > MAX_CLIENT_SCORE_SEND) { numSorted = MAX_CLIENT_SCORE_SEND; } 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 > 1022) break; strcpy (string + stringlength, entry); stringlength += j; } //still want to know the total # of clients i = level.numConnectedClients; 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 ); } /* ================== 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; } /* ================== StringIsInteger ================== */ qboolean StringIsInteger( const char *s ) { int i=0, len=0; qboolean foundDigit=qfalse; for ( i=0, len=strlen( s ); i= 0 && idnum < level.maxclients ) { cl = &level.clients[idnum]; if ( cl->pers.connected == CON_CONNECTED ) return idnum; else if ( allowconnecting && cl->pers.connected == CON_CONNECTING ) return idnum; } } Q_strncpyz( cleanInput, s, sizeof(cleanInput) ); Q_StripColor( cleanInput ); for ( idnum=0,cl=level.clients; idnum < level.maxclients; idnum++,cl++ ) {// check for a name match if ( cl->pers.connected != CON_CONNECTED ) if ( !allowconnecting || cl->pers.connected < CON_CONNECTING ) continue; if ( !Q_stricmp( cl->pers.netname_nocolor, cleanInput ) ) 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 G_Give( gentity_t *ent, const char *name, const char *args, int argc ) { gitem_t *it; int i; qboolean give_all = qfalse; gentity_t *it_ent; trace_t trace; if ( !Q_stricmp( name, "all" ) ) give_all = qtrue; if ( give_all ) { for ( i=0; iclient->ps.stats[STAT_HOLDABLE_ITEMS] |= (1 << i); } if ( give_all || !Q_stricmp( name, "health") ) { if ( argc == 3 ) ent->health = Com_Clampi( 1, ent->client->ps.stats[STAT_MAX_HEALTH], atoi( args ) ); else { if ( level.gametype == GT_SIEGE && ent->client->siegeClass != -1 ) ent->health = bgSiegeClasses[ent->client->siegeClass].maxhealth; else ent->health = ent->client->ps.stats[STAT_MAX_HEALTH]; } if ( !give_all ) return; } if ( give_all || !Q_stricmp( name, "armor" ) || !Q_stricmp( name, "shield" ) ) { if ( argc == 3 ) ent->client->ps.stats[STAT_ARMOR] = Com_Clampi( 0, ent->client->ps.stats[STAT_MAX_HEALTH], atoi( args ) ); else { if ( level.gametype == GT_SIEGE && ent->client->siegeClass != -1 ) ent->client->ps.stats[STAT_ARMOR] = bgSiegeClasses[ent->client->siegeClass].maxarmor; else ent->client->ps.stats[STAT_ARMOR] = ent->client->ps.stats[STAT_MAX_HEALTH]; } if ( !give_all ) return; } if ( give_all || !Q_stricmp( name, "force" ) ) { if ( argc == 3 ) ent->client->ps.fd.forcePower = Com_Clampi( 0, ent->client->ps.fd.forcePowerMax, atoi( args ) ); else ent->client->ps.fd.forcePower = ent->client->ps.fd.forcePowerMax; if ( !give_all ) return; } if ( give_all || !Q_stricmp( name, "weapons" ) ) { ent->client->ps.stats[STAT_WEAPONS] = (1 << (LAST_USEABLE_WEAPON+1)) - ( 1 << WP_NONE ); if ( !give_all ) return; } if ( !give_all && !Q_stricmp( name, "weaponnum" ) ) { ent->client->ps.stats[STAT_WEAPONS] |= (1 << atoi( args )); return; } if ( give_all || !Q_stricmp( name, "ammo" ) ) { int num = 999; if ( argc == 3 ) num = Com_Clampi( 0, 999, atoi( args ) ); for ( i=AMMO_BLASTER; iclient->ps.ammo[i] = num; if ( !give_all ) return; } if ( !Q_stricmp( name, "excellent" ) ) { ent->client->ps.persistant[PERS_EXCELLENT_COUNT]++; return; } if ( !Q_stricmp( name, "impressive" ) ) { ent->client->ps.persistant[PERS_IMPRESSIVE_COUNT]++; return; } if ( !Q_stricmp( name, "gauntletaward" ) ) { ent->client->ps.persistant[PERS_GAUNTLET_FRAG_COUNT]++; return; } if ( !Q_stricmp( name, "defend" ) ) { ent->client->ps.persistant[PERS_DEFEND_COUNT]++; return; } if ( !Q_stricmp( name, "assist" ) ) { 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 ); if ( !it_ent || !it_ent->inuse ) return; FinishSpawningItem( it_ent ); if ( !it_ent || !it_ent->inuse ) return; memset( &trace, 0, sizeof( trace ) ); Touch_Item( it_ent, ent, &trace ); if ( it_ent->inuse ) G_FreeEntity( it_ent ); } } void Cmd_Give_f( gentity_t *ent ) { char name[MAX_TOKEN_CHARS] = {0}; trap->Argv( 1, name, sizeof( name ) ); G_Give( ent, name, ConcatArgs( 2 ), trap->Argc() ); } void Cmd_GiveOther_f( gentity_t *ent ) { char name[MAX_TOKEN_CHARS] = {0}; int i; char otherindex[MAX_TOKEN_CHARS]; gentity_t *otherEnt = NULL; if ( trap->Argc () < 3 ) { trap->SendServerCommand( ent-g_entities, "print \"Usage: giveother \n\"" ); return; } trap->Argv( 1, otherindex, sizeof( otherindex ) ); i = ClientNumberFromString( ent, otherindex, qfalse ); if ( i == -1 ) { return; } otherEnt = &g_entities[i]; if ( !otherEnt->inuse || !otherEnt->client ) { return; } if ( (otherEnt->health <= 0 || otherEnt->client->tempSpectate >= level.time || otherEnt->client->sess.sessionTeam == TEAM_SPECTATOR) ) { // Intentionally displaying for the command user trap->SendServerCommand( ent-g_entities, va( "print \"%s\n\"", G_GetStringEdString( "MP_SVGAME", "MUSTBEALIVE" ) ) ); return; } trap->Argv( 2, name, sizeof( name ) ); G_Give( otherEnt, name, ConcatArgs( 3 ), trap->Argc()-1 ); } /* ================== Cmd_God_f Sets client to godmode argv(0) god ================== */ void Cmd_God_f( gentity_t *ent ) { char *msg = NULL; ent->flags ^= FL_GODMODE; if ( !(ent->flags & FL_GODMODE) ) msg = "godmode OFF"; else msg = "godmode ON"; trap->SendServerCommand( ent-g_entities, va( "print \"%s\n\"", msg ) ); } /* ================== Cmd_Notarget_f Sets client to notarget argv(0) notarget ================== */ void Cmd_Notarget_f( gentity_t *ent ) { char *msg = NULL; ent->flags ^= FL_NOTARGET; if ( !(ent->flags & FL_NOTARGET) ) msg = "notarget OFF"; else msg = "notarget ON"; trap->SendServerCommand( ent-g_entities, va( "print \"%s\n\"", msg ) ); } /* ================== Cmd_Noclip_f argv(0) noclip ================== */ void Cmd_Noclip_f( gentity_t *ent ) { char *msg = NULL; ent->client->noclip = !ent->client->noclip; if ( !ent->client->noclip ) msg = "noclip OFF"; else msg = "noclip ON"; trap->SendServerCommand( ent-g_entities, va( "print \"%s\n\"", 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; } // doesn't work in single player if ( level.gametype == 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" ); } #if 0 /* ================== Cmd_TeamTask_f From TA. ================== */ 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); } #endif void G_Kill( gentity_t *ent ) { if ((level.gametype == GT_DUEL || level.gametype == GT_POWERDUEL) && level.numPlayingClients > 1 && !level.warmupTime) { if (!g_allowDuelSuicide.integer) { trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "ATTEMPTDUELKILL")) ); return; } } ent->flags &= ~FL_GODMODE; ent->client->ps.stats[STAT_HEALTH] = ent->health = -999; player_die (ent, ent, ent, 100000, MOD_SUICIDE); } /* ================= Cmd_Kill_f ================= */ void Cmd_Kill_f( gentity_t *ent ) { G_Kill( ent ); } void Cmd_KillOther_f( gentity_t *ent ) { int i; char otherindex[MAX_TOKEN_CHARS]; gentity_t *otherEnt = NULL; if ( trap->Argc () < 2 ) { trap->SendServerCommand( ent-g_entities, "print \"Usage: killother \n\"" ); return; } trap->Argv( 1, otherindex, sizeof( otherindex ) ); i = ClientNumberFromString( ent, otherindex, qfalse ); if ( i == -1 ) { return; } otherEnt = &g_entities[i]; if ( !otherEnt->inuse || !otherEnt->client ) { return; } if ( (otherEnt->health <= 0 || otherEnt->client->tempSpectate >= level.time || otherEnt->client->sess.sessionTeam == TEAM_SPECTATOR) ) { // Intentionally displaying for the command user trap->SendServerCommand( ent-g_entities, va( "print \"%s\n\"", G_GetStringEdString( "MP_SVGAME", "MUSTBEALIVE" ) ) ); return; } G_Kill( otherEnt ); } /* ================= BroadCastTeamChange Let everyone know about a team change ================= */ void BroadcastTeamChange( gclient_t *client, int oldTeam ) { client->ps.fd.forceDoInit = 1; //every time we change teams make sure our force powers are set right if (level.gametype == GT_SIEGE) { //don't announce these things in siege return; } if ( client->sess.sessionTeam == TEAM_RED ) { trap->SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " %s\n\"", client->pers.netname, G_GetStringEdString("MP_SVGAME", "JOINEDTHEREDTEAM")) ); } else if ( client->sess.sessionTeam == TEAM_BLUE ) { trap->SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " %s\n\"", client->pers.netname, G_GetStringEdString("MP_SVGAME", "JOINEDTHEBLUETEAM"))); } else if ( client->sess.sessionTeam == TEAM_SPECTATOR && oldTeam != TEAM_SPECTATOR ) { trap->SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " %s\n\"", client->pers.netname, G_GetStringEdString("MP_SVGAME", "JOINEDTHESPECTATORS"))); } else if ( client->sess.sessionTeam == TEAM_FREE ) { trap->SendServerCommand( -1, va("cp \"%s" S_COLOR_WHITE " %s\n\"", client->pers.netname, G_GetStringEdString("MP_SVGAME", "JOINEDTHEBATTLE"))); } G_LogPrintf( "ChangeTeam: %i [%s] (%s) \"%s^7\" %s -> %s\n", (int)(client - level.clients), client->sess.IP, client->pers.guid, client->pers.netname, TeamName( oldTeam ), TeamName( client->sess.sessionTeam ) ); } qboolean G_PowerDuelCheckFail(gentity_t *ent) { int loners = 0; int doubles = 0; if (!ent->client || ent->client->sess.duelTeam == DUELTEAM_FREE) { return qtrue; } G_PowerDuelCount(&loners, &doubles, qfalse); if (ent->client->sess.duelTeam == DUELTEAM_LONE && loners >= 1) { return qtrue; } if (ent->client->sess.duelTeam == DUELTEAM_DOUBLE && doubles >= 2) { return qtrue; } return qfalse; } /* ================= SetTeam ================= */ qboolean g_dontPenalizeTeam = qfalse; qboolean g_preventTeamBegin = qfalse; void SetTeam( gentity_t *ent, char *s ) { int team, oldTeam; gclient_t *client; int clientNum; spectatorState_t specState; int specClient; int teamLeader; // fix: this prevents rare creation of invalid players if (!ent->inuse) { return; } // // 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_FREE; // SPECTATOR_SCOREBOARD disabling this for now since it is totally broken on client side } 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 ( level.gametype >= 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 //For now, don't do this. The legalize function will set powers properly now. /* if (g_forceBasedTeams.integer) { if (ent->client->ps.fd.forceSide == FORCE_LIGHTSIDE) { team = TEAM_BLUE; } else { team = TEAM_RED; } } else { */ team = PickTeam( clientNum ); //} } if ( g_teamForceBalance.integer && !g_jediVmerc.integer ) { int counts[TEAM_NUM_TEAMS]; //JAC: Invalid clientNum was being used counts[TEAM_BLUE] = TeamCount( ent-g_entities, TEAM_BLUE ); counts[TEAM_RED] = TeamCount( ent-g_entities, TEAM_RED ); // We allow a spread of two if ( team == TEAM_RED && counts[TEAM_RED] - counts[TEAM_BLUE] > 1 ) { //For now, don't do this. The legalize function will set powers properly now. /* if (g_forceBasedTeams.integer && ent->client->ps.fd.forceSide == FORCE_DARKSIDE) { trap->SendServerCommand( ent->client->ps.clientNum, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "TOOMANYRED_SWITCH")) ); } else */ { //JAC: Invalid clientNum was being used trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "TOOMANYRED")) ); } return; // ignore the request } if ( team == TEAM_BLUE && counts[TEAM_BLUE] - counts[TEAM_RED] > 1 ) { //For now, don't do this. The legalize function will set powers properly now. /* if (g_forceBasedTeams.integer && ent->client->ps.fd.forceSide == FORCE_LIGHTSIDE) { trap->SendServerCommand( ent->client->ps.clientNum, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "TOOMANYBLUE_SWITCH")) ); } else */ { //JAC: Invalid clientNum was being used trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "TOOMANYBLUE")) ); } return; // ignore the request } // It's ok, the team we are switching to has less or same number of players } //For now, don't do this. The legalize function will set powers properly now. /* if (g_forceBasedTeams.integer) { if (team == TEAM_BLUE && ent->client->ps.fd.forceSide != FORCE_LIGHTSIDE) { trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "MUSTBELIGHT")) ); return; } if (team == TEAM_RED && ent->client->ps.fd.forceSide != FORCE_DARKSIDE) { trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "MUSTBEDARK")) ); return; } } */ } else { // force them to spectators if there aren't any spots free team = TEAM_FREE; } oldTeam = client->sess.sessionTeam; if (level.gametype == GT_SIEGE) { if (client->tempSpectate >= level.time && team == TEAM_SPECTATOR) { //sorry, can't do that. return; } if ( team == oldTeam && team != TEAM_SPECTATOR ) return; client->sess.siegeDesiredTeam = team; //oh well, just let them go. /* if (team != TEAM_SPECTATOR) { //can't switch to anything in siege unless you want to switch to being a fulltime spectator //fill them in on their objectives for this team now trap->SendServerCommand(ent-g_entities, va("sb %i", client->sess.siegeDesiredTeam)); trap->SendServerCommand( ent-g_entities, va("print \"You will be on the selected team the next time the round begins.\n\"") ); return; } */ if (client->sess.sessionTeam != TEAM_SPECTATOR && team != TEAM_SPECTATOR) { //not a spectator now, and not switching to spec, so you have to wait til you die. //trap->SendServerCommand( ent-g_entities, va("print \"You will be on the selected team the next time you respawn.\n\"") ); qboolean doBegin; if (ent->client->tempSpectate >= level.time) { doBegin = qfalse; } else { doBegin = qtrue; } if (doBegin) { // Kill them so they automatically respawn in the team they wanted. if (ent->health > 0) { ent->flags &= ~FL_GODMODE; ent->client->ps.stats[STAT_HEALTH] = ent->health = 0; player_die( ent, ent, ent, 100000, MOD_TEAM_CHANGE ); } } if (ent->client->sess.sessionTeam != ent->client->sess.siegeDesiredTeam) { SetTeamQuick(ent, ent->client->sess.siegeDesiredTeam, qfalse); } return; } } // override decision if limiting the players if ( (level.gametype == GT_DUEL) && level.numNonSpectatorClients >= 2 ) { team = TEAM_SPECTATOR; } else if ( (level.gametype == GT_POWERDUEL) && (level.numPlayingClients >= 3 || G_PowerDuelCheckFail(ent)) ) { team = TEAM_SPECTATOR; } else if ( g_maxGameClients.integer > 0 && level.numNonSpectatorClients >= g_maxGameClients.integer ) { team = TEAM_SPECTATOR; } // // decide if we will allow the change // if ( team == oldTeam && team != TEAM_SPECTATOR ) { return; } // // execute the team change // //If it's siege then show the mission briefing for the team you just joined. // if (level.gametype == GT_SIEGE && team != TEAM_SPECTATOR) // { // trap->SendServerCommand(clientNum, va("sb %i", team)); // } // if the player was dead leave the body if ( client->ps.stats[STAT_HEALTH] <= 0 && client->sess.sessionTeam != TEAM_SPECTATOR ) { MaintainBodyQueue(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; g_dontPenalizeTeam = qtrue; player_die (ent, ent, ent, 100000, MOD_SUICIDE); g_dontPenalizeTeam = qfalse; } // they go to the end of the line for tournaments if ( team == TEAM_SPECTATOR && oldTeam != team ) AddTournamentQueue( client ); // clear votes if going to spectator (specs can't vote) if ( team == TEAM_SPECTATOR ) G_ClearVote( ent ); // also clear team votes if switching red/blue or going to spec G_ClearTeamVote( ent, oldTeam ); client->sess.sessionTeam = (team_t)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 ); //make a disappearing effect where they were before teleporting them to the appropriate spawn point, //if we were not on the spec team if (oldTeam != TEAM_SPECTATOR) { gentity_t *tent = G_TempEntity( client->ps.origin, EV_PLAYER_TELEPORT_OUT ); tent->s.clientNum = clientNum; } // get and distribute relevent paramters if ( !ClientUserinfoChanged( clientNum ) ) return; if (!g_preventTeamBegin) { ClientBegin( clientNum, qfalse ); } } /* ================= StopFollowing If the client being followed leaves the game, or you just want to drop to free floating spectator mode ================= */ extern void G_LeaveVehicle( gentity_t *ent, qboolean ConCheck ); void StopFollowing( gentity_t *ent ) { int i=0; 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; ent->client->ps.weapon = WP_NONE; G_LeaveVehicle( ent, qfalse ); // clears m_iVehicleNum as well ent->client->ps.emplacedIndex = 0; //ent->client->ps.m_iVehicleNum = 0; ent->client->ps.viewangles[ROLL] = 0.0f; ent->client->ps.forceHandExtend = HANDEXTEND_NONE; ent->client->ps.forceHandExtendTime = 0; ent->client->ps.zoomMode = 0; ent->client->ps.zoomLocked = qfalse; ent->client->ps.zoomLockTime = 0; ent->client->ps.saberMove = LS_NONE; ent->client->ps.legsAnim = 0; ent->client->ps.legsTimer = 0; ent->client->ps.torsoAnim = 0; ent->client->ps.torsoTimer = 0; ent->client->ps.isJediMaster = qfalse; // major exploit if you are spectating somebody and they are JM and you reconnect ent->client->ps.cloakFuel = 100; // so that fuel goes away after stop following them ent->client->ps.jetpackFuel = 100; // so that fuel goes away after stop following them ent->health = ent->client->ps.stats[STAT_HEALTH] = 100; // so that you don't keep dead angles if you were spectating a dead person ent->client->ps.bobCycle = 0; ent->client->ps.pm_type = PM_SPECTATOR; ent->client->ps.eFlags &= ~EF_DISINTEGRATION; for ( i=0; iclient->ps.powerups[i] = 0; } /* ================= Cmd_Team_f ================= */ void Cmd_Team_f( gentity_t *ent ) { int oldTeam; char s[MAX_TOKEN_CHARS]; oldTeam = ent->client->sess.sessionTeam; if ( trap->Argc() != 2 ) { switch ( oldTeam ) { case TEAM_BLUE: trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "PRINTBLUETEAM")) ); break; case TEAM_RED: trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "PRINTREDTEAM")) ); break; case TEAM_FREE: trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "PRINTFREETEAM")) ); break; case TEAM_SPECTATOR: trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "PRINTSPECTEAM")) ); break; } return; } if ( ent->client->switchTeamTime > level.time ) { trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NOSWITCH")) ); return; } if (gEscaping) { return; } // if they are playing a tournament game, count as a loss if ( level.gametype == GT_DUEL && ent->client->sess.sessionTeam == TEAM_FREE ) {//in a tournament game //disallow changing teams trap->SendServerCommand( ent-g_entities, "print \"Cannot switch teams in Duel\n\"" ); return; //FIXME: why should this be a loss??? //ent->client->sess.losses++; } if (level.gametype == GT_POWERDUEL) { //don't let clients change teams manually at all in powerduel, it will be taken care of through automated stuff trap->SendServerCommand( ent-g_entities, "print \"Cannot switch teams in Power Duel\n\"" ); return; } trap->Argv( 1, s, sizeof( s ) ); SetTeam( ent, s ); // fix: update team switch time only if team change really happend if (oldTeam != ent->client->sess.sessionTeam) ent->client->switchTeamTime = level.time + 5000; } /* ================= Cmd_DuelTeam_f ================= */ void Cmd_DuelTeam_f(gentity_t *ent) { int oldTeam; char s[MAX_TOKEN_CHARS]; if (level.gametype != GT_POWERDUEL) { //don't bother doing anything if this is not power duel return; } /* if (ent->client->sess.sessionTeam != TEAM_SPECTATOR) { trap->SendServerCommand( ent-g_entities, va("print \"You cannot change your duel team unless you are a spectator.\n\"")); return; } */ if ( trap->Argc() != 2 ) { //No arg so tell what team we're currently on. oldTeam = ent->client->sess.duelTeam; switch ( oldTeam ) { case DUELTEAM_FREE: trap->SendServerCommand( ent-g_entities, va("print \"None\n\"") ); break; case DUELTEAM_LONE: trap->SendServerCommand( ent-g_entities, va("print \"Single\n\"") ); break; case DUELTEAM_DOUBLE: trap->SendServerCommand( ent-g_entities, va("print \"Double\n\"") ); break; default: break; } return; } if ( ent->client->switchDuelTeamTime > level.time ) { //debounce for changing trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NOSWITCH")) ); return; } trap->Argv( 1, s, sizeof( s ) ); oldTeam = ent->client->sess.duelTeam; if (!Q_stricmp(s, "free")) { ent->client->sess.duelTeam = DUELTEAM_FREE; } else if (!Q_stricmp(s, "single")) { ent->client->sess.duelTeam = DUELTEAM_LONE; } else if (!Q_stricmp(s, "double")) { ent->client->sess.duelTeam = DUELTEAM_DOUBLE; } else { trap->SendServerCommand( ent-g_entities, va("print \"'%s' not a valid duel team.\n\"", s) ); } if (oldTeam == ent->client->sess.duelTeam) { //didn't actually change, so don't care. return; } if (ent->client->sess.sessionTeam != TEAM_SPECTATOR) { //ok..die int curTeam = ent->client->sess.duelTeam; ent->client->sess.duelTeam = oldTeam; G_Damage(ent, ent, ent, NULL, ent->client->ps.origin, 99999, DAMAGE_NO_PROTECTION, MOD_SUICIDE); ent->client->sess.duelTeam = curTeam; } //reset wins and losses ent->client->sess.wins = 0; ent->client->sess.losses = 0; //get and distribute relevent paramters if ( ClientUserinfoChanged( ent->s.number ) ) return; ent->client->switchDuelTeamTime = level.time + 5000; } int G_TeamForSiegeClass(const char *clName) { int i = 0; int team = SIEGETEAM_TEAM1; siegeTeam_t *stm = BG_SiegeFindThemeForTeam(team); siegeClass_t *scl; if (!stm) { return 0; } while (team <= SIEGETEAM_TEAM2) { scl = stm->classes[i]; if (scl && scl->name[0]) { if (!Q_stricmp(clName, scl->name)) { return team; } } i++; if (i >= MAX_SIEGE_CLASSES || i >= stm->numClasses) { if (team == SIEGETEAM_TEAM2) { break; } team = SIEGETEAM_TEAM2; stm = BG_SiegeFindThemeForTeam(team); i = 0; } } return 0; } /* ================= Cmd_SiegeClass_f ================= */ void Cmd_SiegeClass_f( gentity_t *ent ) { char className[64]; int team = 0; int preScore; qboolean startedAsSpec = qfalse; if (level.gametype != GT_SIEGE) { //classes are only valid for this gametype return; } if (!ent->client) { return; } if (trap->Argc() < 1) { return; } if ( ent->client->switchClassTime > level.time ) { trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NOCLASSSWITCH")) ); return; } if (ent->client->sess.sessionTeam == TEAM_SPECTATOR) { startedAsSpec = qtrue; } trap->Argv( 1, className, sizeof( className ) ); team = G_TeamForSiegeClass(className); if (!team) { //not a valid class name return; } if (ent->client->sess.sessionTeam != team) { //try changing it then g_preventTeamBegin = qtrue; if (team == TEAM_RED) { SetTeam(ent, "red"); } else if (team == TEAM_BLUE) { SetTeam(ent, "blue"); } g_preventTeamBegin = qfalse; if (ent->client->sess.sessionTeam != team) { //failed, oh well if (ent->client->sess.sessionTeam != TEAM_SPECTATOR || ent->client->sess.siegeDesiredTeam != team) { trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NOCLASSTEAM")) ); return; } } } //preserve 'is score preScore = ent->client->ps.persistant[PERS_SCORE]; //Make sure the class is valid for the team BG_SiegeCheckClassLegality(team, className); //Set the session data strcpy(ent->client->sess.siegeClass, className); // get and distribute relevent paramters if ( !ClientUserinfoChanged( ent->s.number ) ) return; if (ent->client->tempSpectate < level.time) { // Kill him (makes sure he loses flags, etc) if (ent->health > 0 && !startedAsSpec) { ent->flags &= ~FL_GODMODE; ent->client->ps.stats[STAT_HEALTH] = ent->health = 0; player_die (ent, ent, ent, 100000, MOD_SUICIDE); } if (ent->client->sess.sessionTeam == TEAM_SPECTATOR || startedAsSpec) { //respawn them instantly. ClientBegin( ent->s.number, qfalse ); } } //set it back after we do all the stuff ent->client->ps.persistant[PERS_SCORE] = preScore; ent->client->switchClassTime = level.time + 5000; } /* ================= Cmd_ForceChanged_f ================= */ void Cmd_ForceChanged_f( gentity_t *ent ) { char fpChStr[1024]; const char *buf; // Cmd_Kill_f(ent); if (ent->client->sess.sessionTeam == TEAM_SPECTATOR) { //if it's a spec, just make the changes now //trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "FORCEAPPLIED")) ); //No longer print it, as the UI calls this a lot. WP_InitForcePowers( ent ); goto argCheck; } buf = G_GetStringEdString("MP_SVGAME", "FORCEPOWERCHANGED"); strcpy(fpChStr, buf); trap->SendServerCommand( ent-g_entities, va("print \"%s%s\n\"", S_COLOR_GREEN, fpChStr) ); ent->client->ps.fd.forceDoInit = 1; argCheck: if (level.gametype == GT_DUEL || level.gametype == GT_POWERDUEL) { //If this is duel, don't even bother changing team in relation to this. return; } if (trap->Argc() > 1) { char arg[MAX_TOKEN_CHARS]; trap->Argv( 1, arg, sizeof( arg ) ); if ( arg[0] ) { //if there's an arg, assume it's a combo team command from the UI. Cmd_Team_f(ent); } } } extern qboolean WP_SaberStyleValidForSaber( saberInfo_t *saber1, saberInfo_t *saber2, int saberHolstered, int saberAnimLevel ); extern qboolean WP_UseFirstValidSaberStyle( saberInfo_t *saber1, saberInfo_t *saber2, int saberHolstered, int *saberAnimLevel ); qboolean G_SetSaber(gentity_t *ent, int saberNum, char *saberName, qboolean siegeOverride) { char truncSaberName[MAX_QPATH] = {0}; if ( !siegeOverride && level.gametype == GT_SIEGE && ent->client->siegeClass != -1 && (bgSiegeClasses[ent->client->siegeClass].saberStance || bgSiegeClasses[ent->client->siegeClass].saber1[0] || bgSiegeClasses[ent->client->siegeClass].saber2[0]) ) { //don't let it be changed if the siege class has forced any saber-related things return qfalse; } Q_strncpyz( truncSaberName, saberName, sizeof( truncSaberName ) ); if ( saberNum == 0 && (!Q_stricmp( "none", truncSaberName ) || !Q_stricmp( "remove", truncSaberName )) ) { //can't remove saber 0 like this Q_strncpyz( truncSaberName, DEFAULT_SABER, sizeof( truncSaberName ) ); } //Set the saber with the arg given. If the arg is //not a valid sabername defaults will be used. WP_SetSaber( ent->s.number, ent->client->saber, saberNum, truncSaberName ); if ( !ent->client->saber[0].model[0] ) { assert(0); //should never happen! Q_strncpyz( ent->client->pers.saber1, DEFAULT_SABER, sizeof( ent->client->pers.saber1 ) ); } else Q_strncpyz( ent->client->pers.saber1, ent->client->saber[0].name, sizeof( ent->client->pers.saber1 ) ); if ( !ent->client->saber[1].model[0] ) Q_strncpyz( ent->client->pers.saber2, "none", sizeof( ent->client->pers.saber2 ) ); else Q_strncpyz( ent->client->pers.saber2, ent->client->saber[1].name, sizeof( ent->client->pers.saber2 ) ); if ( !WP_SaberStyleValidForSaber( &ent->client->saber[0], &ent->client->saber[1], ent->client->ps.saberHolstered, ent->client->ps.fd.saberAnimLevel ) ) { WP_UseFirstValidSaberStyle( &ent->client->saber[0], &ent->client->saber[1], ent->client->ps.saberHolstered, &ent->client->ps.fd.saberAnimLevel ); ent->client->ps.fd.saberAnimLevelBase = ent->client->saberCycleQueue = ent->client->ps.fd.saberAnimLevel; } return qtrue; } /* ================= Cmd_Follow_f ================= */ void Cmd_Follow_f( gentity_t *ent ) { int i; char arg[MAX_TOKEN_CHARS]; if ( ent->client->sess.spectatorState == SPECTATOR_NOT && ent->client->switchTeamTime > level.time ) { trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NOSWITCH")) ); return; } if ( trap->Argc() != 2 ) { if ( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) { StopFollowing( ent ); } return; } trap->Argv( 1, arg, sizeof( arg ) ); i = ClientNumberFromString( ent, arg, qfalse ); 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 ( level.clients[ i ].tempSpectate >= level.time ) { return; } // if they are playing a tournament game, count as a loss if ( (level.gametype == GT_DUEL || level.gametype == GT_POWERDUEL) && ent->client->sess.sessionTeam == TEAM_FREE ) { //WTF??? ent->client->sess.losses++; } // first set them to spectator if ( ent->client->sess.sessionTeam != TEAM_SPECTATOR ) { SetTeam( ent, "spectator" ); // fix: update team switch time only if team change really happend if (ent->client->sess.sessionTeam == TEAM_SPECTATOR) ent->client->switchTeamTime = level.time + 5000; } 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; qboolean looped = qfalse; if ( ent->client->sess.spectatorState == SPECTATOR_NOT && ent->client->switchTeamTime > level.time ) { trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NOSWITCH")) ); return; } // if they are playing a tournament game, count as a loss if ( (level.gametype == GT_DUEL || level.gametype == GT_POWERDUEL) && ent->client->sess.sessionTeam == TEAM_FREE ) {\ //WTF??? ent->client->sess.losses++; } // first set them to spectator if ( ent->client->sess.spectatorState == SPECTATOR_NOT ) { SetTeam( ent, "spectator" ); // fix: update team switch time only if team change really happend if (ent->client->sess.sessionTeam == TEAM_SPECTATOR) ent->client->switchTeamTime = level.time + 5000; } if ( dir != 1 && dir != -1 ) { trap->Error( ERR_DROP, "Cmd_FollowCycle_f: bad dir %i", dir ); } clientnum = ent->client->sess.spectatorClient; original = clientnum; do { clientnum += dir; if ( clientnum >= level.maxclients ) { // Avoid /team follow1 crash if ( looped ) { clientnum = original; break; } else { clientnum = 0; looped = qtrue; } } if ( clientnum < 0 ) { if ( looped ) { clientnum = original; break; } else { clientnum = level.maxclients - 1; looped = qtrue; } } // 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; } // can't follow another spectator if ( level.clients[ clientnum ].tempSpectate >= level.time ) { return; } // 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 } void Cmd_FollowNext_f( gentity_t *ent ) { Cmd_FollowCycle_f( ent, 1 ); } void Cmd_FollowPrev_f( gentity_t *ent ) { Cmd_FollowCycle_f( ent, -1 ); } /* ================== G_Say ================== */ static void G_SayTo( gentity_t *ent, gentity_t *other, int mode, int color, const char *name, const char *message, char *locMsg ) { 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 tournaments if ( (level.gametype == GT_DUEL || level.gametype == GT_POWERDUEL) && other->client->sess.sessionTeam == TEAM_FREE && ent->client->sess.sessionTeam != TEAM_FREE ) { //Hmm, maybe some option to do so if allowed? Or at least in developer mode... return; } */ //They've requested I take this out. if (level.gametype == GT_SIEGE && ent->client && (ent->client->tempSpectate >= level.time || ent->client->sess.sessionTeam == TEAM_SPECTATOR) && other->client->sess.sessionTeam != TEAM_SPECTATOR && other->client->tempSpectate < level.time) { //siege temp spectators should not communicate to ingame players return; } if (locMsg) { trap->SendServerCommand( other-g_entities, va("%s \"%s\" \"%s\" \"%c\" \"%s\" %i", mode == SAY_TEAM ? "ltchat" : "lchat", name, locMsg, color, message, ent->s.number)); } else { trap->SendServerCommand( other-g_entities, va("%s \"%s%c%c%s\" %i", mode == SAY_TEAM ? "tchat" : "chat", name, Q_COLOR_ESCAPE, color, message, ent->s.number)); } } 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]; char *locMsg = NULL; if ( level.gametype < GT_TEAM && mode == SAY_TEAM ) { mode = SAY_ALL; } Q_strncpyz( text, chatText, sizeof(text) ); Q_strstrip( text, "\n\r", " " ); switch ( mode ) { default: case SAY_ALL: G_LogPrintf( "say: %s: %s\n", ent->client->pers.netname, text ); 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, text ); if (Team_GetLocationMsg(ent, location, sizeof(location))) { Com_sprintf (name, sizeof(name), EC"(%s%c%c"EC")"EC": ", ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE ); locMsg = 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 && level.gametype >= 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"]"EC": ", ent->client->pers.netname, Q_COLOR_ESCAPE, COLOR_WHITE ); locMsg = 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; } if ( target ) { G_SayTo( ent, target, mode, color, name, text, locMsg ); return; } // echo the text to the console if ( dedicated.integer ) { trap->Print( "%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, locMsg ); } } /* ================== Cmd_Say_f ================== */ static void Cmd_Say_f( gentity_t *ent ) { char *p = NULL; if ( trap->Argc () < 2 ) return; p = ConcatArgs( 1 ); if ( strlen( p ) >= MAX_SAY_TEXT ) { p[MAX_SAY_TEXT-1] = '\0'; G_SecurityLogPrintf( "Cmd_Say_f from %d (%s) has been truncated: %s\n", ent->s.number, ent->client->pers.netname, p ); } G_Say( ent, NULL, SAY_ALL, p ); } /* ================== Cmd_SayTeam_f ================== */ static void Cmd_SayTeam_f( gentity_t *ent ) { char *p = NULL; if ( trap->Argc () < 2 ) return; p = ConcatArgs( 1 ); if ( strlen( p ) >= MAX_SAY_TEXT ) { p[MAX_SAY_TEXT-1] = '\0'; G_SecurityLogPrintf( "Cmd_SayTeam_f from %d (%s) has been truncated: %s\n", ent->s.number, ent->client->pers.netname, p ); } G_Say( ent, NULL, (level.gametype>=GT_TEAM) ? SAY_TEAM : SAY_ALL, 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, qfalse ); if ( targetNum == -1 ) { return; } target = &g_entities[targetNum]; if ( !target->inuse || !target->client ) { return; } p = ConcatArgs( 2 ); if ( strlen( p ) >= MAX_SAY_TEXT ) { p[MAX_SAY_TEXT-1] = '\0'; G_SecurityLogPrintf( "Cmd_Tell_f from %d (%s) has been truncated: %s\n", ent->s.number, ent->client->pers.netname, 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 ); } } //siege voice command static void Cmd_VoiceCommand_f(gentity_t *ent) { gentity_t *te; char arg[MAX_TOKEN_CHARS]; char *s; int i = 0; if (level.gametype < GT_TEAM) { return; } if (trap->Argc() < 2) { return; } if (ent->client->sess.sessionTeam == TEAM_SPECTATOR || ent->client->tempSpectate >= level.time) { trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NOVOICECHATASSPEC")) ); return; } trap->Argv(1, arg, sizeof(arg)); if (arg[0] == '*') { //hmm.. don't expect a * to be prepended already. maybe someone is trying to be sneaky. return; } s = va("*%s", arg); //now, make sure it's a valid sound to be playing like this.. so people can't go around //screaming out death sounds or whatever. while (i < MAX_CUSTOM_SIEGE_SOUNDS) { if (!bg_customSiegeSoundNames[i]) { break; } if (!Q_stricmp(bg_customSiegeSoundNames[i], s)) { //it matches this one, so it's ok break; } i++; } if (i == MAX_CUSTOM_SIEGE_SOUNDS || !bg_customSiegeSoundNames[i]) { //didn't find it in the list return; } te = G_TempEntity(vec3_origin, EV_VOICECMD_SOUND); te->s.groundEntityNum = ent->s.number; te->s.eventParm = G_SoundIndex((char *)bg_customSiegeSoundNames[i]); te->r.svFlags |= SVF_BROADCAST; } static char *gc_orders[] = { "hold your position", "hold this position", "come here", "cover me", "guard location", "search and destroy", "report" }; static size_t numgc_orders = ARRAY_LEN( gc_orders ); void Cmd_GameCommand_f( gentity_t *ent ) { int targetNum; unsigned int order; gentity_t *target; char arg[MAX_TOKEN_CHARS] = {0}; 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 >= 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, qfalse ); 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 ) { //JAC: This wasn't working for non-spectators since s.origin doesn't update for active players. if(ent->client && ent->client->sess.sessionTeam != TEAM_SPECTATOR ) {//active players use currentOrigin trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", vtos( ent->r.currentOrigin ) ) ); } else { trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", vtos( ent->s.origin ) ) ); } //trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", vtos( ent->s.origin ) ) ); } static const char *gameNames[GT_MAX_GAME_TYPE] = { "Free For All", "Holocron FFA", "Jedi Master", "Duel", "Power Duel", "Single Player", "Team FFA", "Siege", "Capture the Flag", "Capture the Ysalamiri" }; /* ================== Cmd_CallVote_f ================== */ extern void SiegeClearSwitchData(void); //g_saga.c qboolean G_VoteCapturelimit( gentity_t *ent, int numArgs, const char *arg1, const char *arg2 ) { int n = Com_Clampi( 0, 0x7FFFFFFF, atoi( arg2 ) ); Com_sprintf( level.voteString, sizeof( level.voteString ), "%s %i", arg1, n ); Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "%s", level.voteString ); Q_strncpyz( level.voteStringClean, level.voteString, sizeof( level.voteStringClean ) ); return qtrue; } qboolean G_VoteClientkick( gentity_t *ent, int numArgs, const char *arg1, const char *arg2 ) { int n = atoi ( arg2 ); if ( n < 0 || n >= level.maxclients ) { trap->SendServerCommand( ent-g_entities, va( "print \"invalid client number %d.\n\"", n ) ); return qfalse; } if ( g_entities[n].client->pers.connected == CON_DISCONNECTED ) { trap->SendServerCommand( ent-g_entities, va( "print \"there is no client with the client number %d.\n\"", n ) ); return qfalse; } Com_sprintf( level.voteString, sizeof( level.voteString ), "%s %s", arg1, arg2 ); Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "%s %s", arg1, g_entities[n].client->pers.netname ); Q_strncpyz( level.voteStringClean, level.voteString, sizeof( level.voteStringClean ) ); return qtrue; } qboolean G_VoteFraglimit( gentity_t *ent, int numArgs, const char *arg1, const char *arg2 ) { int n = Com_Clampi( 0, 0x7FFFFFFF, atoi( arg2 ) ); Com_sprintf( level.voteString, sizeof( level.voteString ), "%s %i", arg1, n ); Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "%s", level.voteString ); Q_strncpyz( level.voteStringClean, level.voteString, sizeof( level.voteStringClean ) ); return qtrue; } qboolean G_VoteGametype( gentity_t *ent, int numArgs, const char *arg1, const char *arg2 ) { int gt = atoi( arg2 ); // ffa, ctf, tdm, etc if ( arg2[0] && isalpha( arg2[0] ) ) { gt = BG_GetGametypeForString( arg2 ); if ( gt == -1 ) { trap->SendServerCommand( ent-g_entities, va( "print \"Gametype (%s) unrecognised, defaulting to FFA/Deathmatch\n\"", arg2 ) ); gt = GT_FFA; } } // numeric but out of range else if ( gt < 0 || gt >= GT_MAX_GAME_TYPE ) { trap->SendServerCommand( ent-g_entities, va( "print \"Gametype (%i) is out of range, defaulting to FFA/Deathmatch\n\"", gt ) ); gt = GT_FFA; } // logically invalid gametypes, or gametypes not fully implemented in MP if ( gt == GT_SINGLE_PLAYER ) { trap->SendServerCommand( ent-g_entities, va( "print \"This gametype is not supported (%s).\n\"", arg2 ) ); return qfalse; } level.votingGametype = qtrue; level.votingGametypeTo = gt; Com_sprintf( level.voteString, sizeof( level.voteString ), "%s %d", arg1, gt ); Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "%s %s", arg1, gameNames[gt] ); Q_strncpyz( level.voteStringClean, level.voteString, sizeof( level.voteStringClean ) ); return qtrue; } qboolean G_VoteKick( gentity_t *ent, int numArgs, const char *arg1, const char *arg2 ) { int clientid = ClientNumberFromString( ent, arg2, qtrue ); gentity_t *target = NULL; if ( clientid == -1 ) return qfalse; target = &g_entities[clientid]; if ( !target || !target->inuse || !target->client ) return qfalse; Com_sprintf( level.voteString, sizeof( level.voteString ), "clientkick %d", clientid ); Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "kick %s", target->client->pers.netname ); Q_strncpyz( level.voteStringClean, level.voteString, sizeof( level.voteStringClean ) ); return qtrue; } const char *G_GetArenaInfoByMap( const char *map ); void Cmd_MapList_f( gentity_t *ent ) { int i, toggle=0; char map[24] = "--", buf[512] = {0}; Q_strcat( buf, sizeof( buf ), "Map list:" ); for ( i=0; i= sizeof( buf ) ) { trap->SendServerCommand( ent-g_entities, va( "print \"%s\"", buf ) ); buf[0] = '\0'; } Q_strcat( buf, sizeof( buf ), tmpMsg ); } } trap->SendServerCommand( ent-g_entities, va( "print \"%s\n\"", buf ) ); } qboolean G_VoteMap( gentity_t *ent, int numArgs, const char *arg1, const char *arg2 ) { char s[MAX_CVAR_VALUE_STRING] = {0}, bspName[MAX_QPATH] = {0}, *mapName = NULL, *mapName2 = NULL; fileHandle_t fp = NULL_FILE; const char *arenaInfo; // didn't specify a map, show available maps if ( numArgs < 3 ) { Cmd_MapList_f( ent ); return qfalse; } if ( strchr( arg2, '\\' ) ) { trap->SendServerCommand( ent-g_entities, "print \"Can't have mapnames with a \\\n\"" ); return qfalse; } Com_sprintf( bspName, sizeof(bspName), "maps/%s.bsp", arg2 ); if ( trap->FS_Open( bspName, &fp, FS_READ ) <= 0 ) { trap->SendServerCommand( ent-g_entities, va( "print \"Can't find map %s on server\n\"", bspName ) ); if( fp != NULL_FILE ) trap->FS_Close( fp ); return qfalse; } trap->FS_Close( fp ); if ( !G_DoesMapSupportGametype( arg2, level.gametype ) ) { trap->SendServerCommand( ent-g_entities, va( "print \"%s\n\"", G_GetStringEdString( "MP_SVGAME", "NOVOTE_MAPNOTSUPPORTEDBYGAME" ) ) ); return qfalse; } // preserve the map rotation trap->Cvar_VariableStringBuffer( "nextmap", s, sizeof( s ) ); if ( *s ) Com_sprintf( level.voteString, sizeof( level.voteString ), "%s %s; set nextmap \"%s\"", arg1, arg2, s ); else Com_sprintf( level.voteString, sizeof( level.voteString ), "%s %s", arg1, arg2 ); arenaInfo = G_GetArenaInfoByMap(arg2); if ( arenaInfo ) { mapName = Info_ValueForKey( arenaInfo, "longname" ); mapName2 = Info_ValueForKey( arenaInfo, "map" ); } if ( !mapName || !mapName[0] ) mapName = "ERROR"; if ( !mapName2 || !mapName2[0] ) mapName2 = "ERROR"; Com_sprintf( level.voteDisplayString, sizeof( level.voteDisplayString ), "map %s (%s)", mapName, mapName2 ); Q_strncpyz( level.voteStringClean, level.voteString, sizeof( level.voteStringClean ) ); return qtrue; } qboolean G_VoteMapRestart( gentity_t *ent, int numArgs, const char *arg1, const char *arg2 ) { int n = Com_Clampi( 0, 60, atoi( arg2 ) ); if ( numArgs < 3 ) n = 5; Com_sprintf( level.voteString, sizeof( level.voteString ), "%s %i", arg1, n ); Q_strncpyz( level.voteDisplayString, level.voteString, sizeof( level.voteDisplayString ) ); Q_strncpyz( level.voteStringClean, level.voteString, sizeof( level.voteStringClean ) ); return qtrue; } qboolean G_VoteNextmap( gentity_t *ent, int numArgs, const char *arg1, const char *arg2 ) { char s[MAX_CVAR_VALUE_STRING]; trap->Cvar_VariableStringBuffer( "nextmap", s, sizeof( s ) ); if ( !*s ) { trap->SendServerCommand( ent-g_entities, "print \"nextmap not set.\n\"" ); return qfalse; } SiegeClearSwitchData(); Com_sprintf( level.voteString, sizeof( level.voteString ), "vstr nextmap"); Q_strncpyz( level.voteDisplayString, level.voteString, sizeof( level.voteDisplayString ) ); Q_strncpyz( level.voteStringClean, level.voteString, sizeof( level.voteStringClean ) ); return qtrue; } qboolean G_VoteTimelimit( gentity_t *ent, int numArgs, const char *arg1, const char *arg2 ) { float tl = Com_Clamp( 0.0f, 35790.0f, atof( arg2 ) ); if ( Q_isintegral( tl ) ) Com_sprintf( level.voteString, sizeof( level.voteString ), "%s %i", arg1, (int)tl ); else Com_sprintf( level.voteString, sizeof( level.voteString ), "%s %.3f", arg1, tl ); Q_strncpyz( level.voteDisplayString, level.voteString, sizeof( level.voteDisplayString ) ); Q_strncpyz( level.voteStringClean, level.voteString, sizeof( level.voteStringClean ) ); return qtrue; } qboolean G_VoteWarmup( gentity_t *ent, int numArgs, const char *arg1, const char *arg2 ) { int n = Com_Clampi( 0, 1, atoi( arg2 ) ); Com_sprintf( level.voteString, sizeof( level.voteString ), "%s %i", arg1, n ); Q_strncpyz( level.voteDisplayString, level.voteString, sizeof( level.voteDisplayString ) ); Q_strncpyz( level.voteStringClean, level.voteString, sizeof( level.voteStringClean ) ); return qtrue; } typedef struct voteString_s { const char *string; const char *aliases; // space delimited list of aliases, will always show the real vote string qboolean (*func)(gentity_t *ent, int numArgs, const char *arg1, const char *arg2); int numArgs; // number of REQUIRED arguments, not total/optional arguments uint32_t validGT; // bit-flag of valid gametypes qboolean voteDelay; // if true, will delay executing the vote string after it's accepted by g_voteDelay const char *shortHelp; // NULL if no arguments needed } voteString_t; static voteString_t validVoteStrings[] = { // vote string aliases # args valid gametypes exec delay short help { "capturelimit", "caps", G_VoteCapturelimit, 1, GTB_CTF|GTB_CTY, qtrue, "" }, { "clientkick", NULL, G_VoteClientkick, 1, GTB_ALL, qfalse, "" }, { "fraglimit", "frags", G_VoteFraglimit, 1, GTB_ALL & ~(GTB_SIEGE|GTB_CTF|GTB_CTY), qtrue, "" }, { "g_doWarmup", "dowarmup warmup", G_VoteWarmup, 1, GTB_ALL, qtrue, "<0-1>" }, { "g_gametype", "gametype gt mode", G_VoteGametype, 1, GTB_ALL, qtrue, "" }, { "kick", NULL, G_VoteKick, 1, GTB_ALL, qfalse, "" }, { "map", NULL, G_VoteMap, 0, GTB_ALL, qtrue, "" }, { "map_restart", "restart", G_VoteMapRestart, 0, GTB_ALL, qtrue, "" }, { "nextmap", NULL, G_VoteNextmap, 0, GTB_ALL, qtrue, NULL }, { "timelimit", "time", G_VoteTimelimit, 1, GTB_ALL &~GTB_SIEGE, qtrue, "" }, }; static const int validVoteStringsSize = ARRAY_LEN( validVoteStrings ); void Svcmd_ToggleAllowVote_f( void ) { if ( trap->Argc() == 1 ) { int i = 0; for ( i = 0; iPrint( "%2d [X] %s\n", i, validVoteStrings[i].string ); else trap->Print( "%2d [ ] %s\n", i, validVoteStrings[i].string ); } return; } else { char arg[8] = { 0 }; int index; trap->Argv( 1, arg, sizeof( arg ) ); index = atoi( arg ); if ( index < 0 || index >= validVoteStringsSize ) { Com_Printf( "ToggleAllowVote: Invalid range: %i [0, %i]\n", index, validVoteStringsSize - 1 ); return; } trap->Cvar_Set( "g_allowVote", va( "%i", (1 << index) ^ (g_allowVote.integer & ((1 << validVoteStringsSize) - 1)) ) ); trap->Cvar_Update( &g_allowVote ); Com_Printf( "%s %s^7\n", validVoteStrings[index].string, ((g_allowVote.integer & (1 << index)) ? "^2Enabled" : "^1Disabled") ); } } void Cmd_CallVote_f( gentity_t *ent ) { int i=0, numArgs=0; char arg1[MAX_CVAR_VALUE_STRING] = {0}; char arg2[MAX_CVAR_VALUE_STRING] = {0}; voteString_t *vote = NULL; // not allowed to vote at all if ( !g_allowVote.integer ) { trap->SendServerCommand( ent-g_entities, va( "print \"%s\n\"", G_GetStringEdString( "MP_SVGAME", "NOVOTE" ) ) ); return; } // vote in progress else if ( level.voteTime ) { trap->SendServerCommand( ent-g_entities, va( "print \"%s\n\"", G_GetStringEdString( "MP_SVGAME", "VOTEINPROGRESS" ) ) ); return; } // can't vote as a spectator, except in (power)duel else if ( level.gametype != GT_DUEL && level.gametype != GT_POWERDUEL && ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { trap->SendServerCommand( ent-g_entities, va( "print \"%s\n\"", G_GetStringEdString( "MP_SVGAME", "NOSPECVOTE" ) ) ); return; } // make sure it is a valid command to vote on numArgs = trap->Argc(); trap->Argv( 1, arg1, sizeof( arg1 ) ); if ( numArgs > 1 ) Q_strncpyz( arg2, ConcatArgs( 2 ), sizeof( arg2 ) ); // filter ; \n \r if ( Q_strchrs( arg1, ";\r\n" ) || Q_strchrs( arg2, ";\r\n" ) ) { trap->SendServerCommand( ent-g_entities, "print \"Invalid vote string.\n\"" ); return; } // check for invalid votes for ( i=0; iSendServerCommand( ent-g_entities, "print \"Invalid vote string.\n\"" ); trap->SendServerCommand( ent-g_entities, "print \"Allowed vote strings are: \"" ); for ( i=0; iSendServerCommand( ent-g_entities, va( "print \"%s\n\"", buf ) ); return; } validVote: vote = &validVoteStrings[i]; if ( !(vote->validGT & (1<SendServerCommand( ent-g_entities, va( "print \"%s is not applicable in this gametype.\n\"", arg1 ) ); return; } if ( numArgs < vote->numArgs+2 ) { trap->SendServerCommand( ent-g_entities, va( "print \"%s requires more arguments: %s\n\"", arg1, vote->shortHelp ) ); return; } level.votingGametype = qfalse; level.voteExecuteDelay = vote->voteDelay ? g_voteDelay.integer : 0; // there is still a vote to be executed, execute it and store the new vote if ( level.voteExecuteTime ) { level.voteExecuteTime = 0; trap->SendConsoleCommand( EXEC_APPEND, va( "%s\n", level.voteString ) ); } // pass the args onto vote-specific handlers for parsing/filtering if ( vote->func ) { if ( !vote->func( ent, numArgs, arg1, arg2 ) ) return; } // otherwise assume it's a command else { Com_sprintf( level.voteString, sizeof( level.voteString ), "%s \"%s\"", arg1, arg2 ); Q_strncpyz( level.voteDisplayString, level.voteString, sizeof( level.voteDisplayString ) ); Q_strncpyz( level.voteStringClean, level.voteString, sizeof( level.voteStringClean ) ); } Q_strstrip( level.voteStringClean, "\"\n\r", NULL ); trap->SendServerCommand( -1, va( "print \"%s^7 %s (%s)\n\"", ent->client->pers.netname, G_GetStringEdString( "MP_SVGAME", "PLCALLEDVOTE" ), level.voteStringClean ) ); // start the voting, the caller automatically votes yes level.voteTime = level.time; level.voteYes = 1; level.voteNo = 0; for ( i=0; iclient->mGameFlags |= PSG_VOTED; ent->client->pers.vote = 1; trap->SetConfigstring( CS_VOTE_TIME, va( "%i", level.voteTime ) ); trap->SetConfigstring( CS_VOTE_STRING, level.voteDisplayString ); trap->SetConfigstring( CS_VOTE_YES, va( "%i", level.voteYes ) ); trap->SetConfigstring( CS_VOTE_NO, va( "%i", level.voteNo ) ); } /* ================== Cmd_Vote_f ================== */ void Cmd_Vote_f( gentity_t *ent ) { char msg[64] = {0}; if ( !level.voteTime ) { trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NOVOTEINPROG")) ); return; } if ( ent->client->mGameFlags & PSG_VOTED ) { trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "VOTEALREADY")) ); return; } if (level.gametype != GT_DUEL && level.gametype != GT_POWERDUEL) { if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NOVOTEASSPEC")) ); return; } } trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "PLVOTECAST")) ); ent->client->mGameFlags |= PSG_VOTED; trap->Argv( 1, msg, sizeof( msg ) ); if ( tolower( msg[0] ) == 'y' || msg[0] == '1' ) { level.voteYes++; ent->client->pers.vote = 1; trap->SetConfigstring( CS_VOTE_YES, va("%i", level.voteYes ) ); } else { level.voteNo++; ent->client->pers.vote = 2; trap->SetConfigstring( CS_VOTE_NO, va("%i", level.voteNo ) ); } // a majority will be determined in CheckVote, which will also account // for players entering or leaving } qboolean G_TeamVoteLeader( gentity_t *ent, int cs_offset, team_t team, int numArgs, const char *arg1, const char *arg2 ) { int clientid = numArgs == 2 ? ent->s.number : ClientNumberFromString( ent, arg2, qfalse ); gentity_t *target = NULL; if ( clientid == -1 ) return qfalse; target = &g_entities[clientid]; if ( !target || !target->inuse || !target->client ) return qfalse; if ( target->client->sess.sessionTeam != team ) { trap->SendServerCommand( ent-g_entities, va( "print \"User %s is not on your team\n\"", arg2 ) ); return qfalse; } Com_sprintf( level.teamVoteString[cs_offset], sizeof( level.teamVoteString[cs_offset] ), "leader %d", clientid ); Q_strncpyz( level.teamVoteDisplayString[cs_offset], level.teamVoteString[cs_offset], sizeof( level.teamVoteDisplayString[cs_offset] ) ); Q_strncpyz( level.teamVoteStringClean[cs_offset], level.teamVoteString[cs_offset], sizeof( level.teamVoteStringClean[cs_offset] ) ); return qtrue; } /* ================== Cmd_CallTeamVote_f ================== */ void Cmd_CallTeamVote_f( gentity_t *ent ) { team_t team = ent->client->sess.sessionTeam; int i=0, cs_offset=0, numArgs=0; char arg1[MAX_CVAR_VALUE_STRING] = {0}; char arg2[MAX_CVAR_VALUE_STRING] = {0}; if ( team == TEAM_RED ) cs_offset = 0; else if ( team == TEAM_BLUE ) cs_offset = 1; else return; // not allowed to vote at all if ( !g_allowTeamVote.integer ) { trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NOVOTE")) ); return; } // vote in progress else if ( level.teamVoteTime[cs_offset] ) { trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "TEAMVOTEALREADY")) ); return; } // can't vote as a spectator else if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NOSPECVOTE")) ); return; } // make sure it is a valid command to vote on numArgs = trap->Argc(); trap->Argv( 1, arg1, sizeof( arg1 ) ); if ( numArgs > 1 ) Q_strncpyz( arg2, ConcatArgs( 2 ), sizeof( arg2 ) ); // filter ; \n \r if ( Q_strchrs( arg1, ";\r\n" ) || Q_strchrs( arg2, ";\r\n" ) ) { trap->SendServerCommand( ent-g_entities, "print \"Invalid team vote string.\n\"" ); return; } // pass the args onto vote-specific handlers for parsing/filtering if ( !Q_stricmp( arg1, "leader" ) ) { if ( !G_TeamVoteLeader( ent, cs_offset, team, numArgs, arg1, arg2 ) ) return; } else { trap->SendServerCommand( ent-g_entities, "print \"Invalid team vote string.\n\"" ); trap->SendServerCommand( ent-g_entities, va("print \"Allowed team vote strings are: ^%c%s %s\n\"", COLOR_GREEN, "leader", "" )); return; } Q_strstrip( level.teamVoteStringClean[cs_offset], "\"\n\r", NULL ); for ( i=0; iSendServerCommand( i, va("print \"%s^7 called a team vote (%s)\n\"", ent->client->pers.netname, level.teamVoteStringClean[cs_offset] ) ); } // start the voting, the caller autoamtically votes yes level.teamVoteTime[cs_offset] = level.time; level.teamVoteYes[cs_offset] = 1; level.teamVoteNo[cs_offset] = 0; for ( i=0; iclient->mGameFlags |= PSG_TEAMVOTED; ent->client->pers.teamvote = 1; trap->SetConfigstring( CS_TEAMVOTE_TIME + cs_offset, va("%i", level.teamVoteTime[cs_offset] ) ); trap->SetConfigstring( CS_TEAMVOTE_STRING + cs_offset, level.teamVoteDisplayString[cs_offset] ); trap->SetConfigstring( CS_TEAMVOTE_YES + cs_offset, va("%i", level.teamVoteYes[cs_offset] ) ); trap->SetConfigstring( CS_TEAMVOTE_NO + cs_offset, va("%i", level.teamVoteNo[cs_offset] ) ); } /* ================== Cmd_TeamVote_f ================== */ void Cmd_TeamVote_f( gentity_t *ent ) { team_t team = ent->client->sess.sessionTeam; int cs_offset=0; char msg[64] = {0}; if ( team == TEAM_RED ) cs_offset = 0; else if ( team == TEAM_BLUE ) cs_offset = 1; else return; if ( !level.teamVoteTime[cs_offset] ) { trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NOTEAMVOTEINPROG")) ); return; } if ( ent->client->mGameFlags & PSG_TEAMVOTED ) { trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "TEAMVOTEALREADYCAST")) ); return; } if ( ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NOVOTEASSPEC")) ); return; } trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "PLTEAMVOTECAST")) ); ent->client->mGameFlags |= PSG_TEAMVOTED; trap->Argv( 1, msg, sizeof( msg ) ); if ( tolower( msg[0] ) == 'y' || msg[0] == '1' ) { level.teamVoteYes[cs_offset]++; ent->client->pers.teamvote = 1; trap->SetConfigstring( CS_TEAMVOTE_YES + cs_offset, va("%i", level.teamVoteYes[cs_offset] ) ); } else { level.teamVoteNo[cs_offset]++; ent->client->pers.teamvote = 2; trap->SetConfigstring( CS_TEAMVOTE_NO + cs_offset, va("%i", level.teamVoteNo[cs_offset] ) ); } // a majority will be determined in TeamCheckVote, which will also account // for players entering or leaving } /* ================= Cmd_SetViewpos_f ================= */ void Cmd_SetViewpos_f( gentity_t *ent ) { vec3_t origin, angles; char buffer[MAX_TOKEN_CHARS]; int i; if ( trap->Argc() != 5 ) { trap->SendServerCommand( ent-g_entities, va("print \"usage: setviewpos x y z yaw\n\"")); return; } VectorClear( angles ); for ( i = 0 ; i < 3 ; i++ ) { trap->Argv( i + 1, buffer, sizeof( buffer ) ); origin[i] = atof( buffer ); } trap->Argv( 4, buffer, sizeof( buffer ) ); angles[YAW] = atof( buffer ); TeleportPlayer( ent, origin, angles ); } void G_LeaveVehicle( gentity_t* ent, qboolean ConCheck ) { if (ent->client->ps.m_iVehicleNum) { //tell it I'm getting off gentity_t *veh = &g_entities[ent->client->ps.m_iVehicleNum]; if (veh->inuse && veh->client && veh->m_pVehicle) { if ( ConCheck ) { // check connection clientConnected_t pCon = ent->client->pers.connected; ent->client->pers.connected = CON_DISCONNECTED; veh->m_pVehicle->m_pVehicleInfo->Eject(veh->m_pVehicle, (bgEntity_t *)ent, qtrue); ent->client->pers.connected = pCon; } else { // or not. veh->m_pVehicle->m_pVehicleInfo->Eject(veh->m_pVehicle, (bgEntity_t *)ent, qtrue); } } } ent->client->ps.m_iVehicleNum = 0; } int G_ItemUsable(playerState_t *ps, int forcedUse) { vec3_t fwd, fwdorg, dest, pos; vec3_t yawonly; vec3_t mins, maxs; vec3_t trtest; trace_t tr; // fix: dead players shouldn't use items if (ps->stats[STAT_HEALTH] <= 0) { return 0; } if (ps->m_iVehicleNum) { return 0; } if (ps->pm_flags & PMF_USE_ITEM_HELD) { //force to let go first return 0; } if (!forcedUse) { forcedUse = bg_itemlist[ps->stats[STAT_HOLDABLE_ITEM]].giTag; } if (!BG_IsItemSelectable(ps, forcedUse)) { return 0; } switch (forcedUse) { case HI_MEDPAC: case HI_MEDPAC_BIG: if (ps->stats[STAT_HEALTH] >= ps->stats[STAT_MAX_HEALTH]) { return 0; } if (ps->stats[STAT_HEALTH] <= 0) { return 0; } return 1; case HI_SEEKER: if (ps->eFlags & EF_SEEKERDRONE) { G_AddEvent(&g_entities[ps->clientNum], EV_ITEMUSEFAIL, SEEKER_ALREADYDEPLOYED); return 0; } return 1; case HI_SENTRY_GUN: if (ps->fd.sentryDeployed) { G_AddEvent(&g_entities[ps->clientNum], EV_ITEMUSEFAIL, SENTRY_ALREADYPLACED); return 0; } yawonly[ROLL] = 0; yawonly[PITCH] = 0; yawonly[YAW] = ps->viewangles[YAW]; VectorSet( mins, -8, -8, 0 ); VectorSet( maxs, 8, 8, 24 ); AngleVectors(yawonly, fwd, NULL, NULL); fwdorg[0] = ps->origin[0] + fwd[0]*64; fwdorg[1] = ps->origin[1] + fwd[1]*64; fwdorg[2] = ps->origin[2] + fwd[2]*64; trtest[0] = fwdorg[0] + fwd[0]*16; trtest[1] = fwdorg[1] + fwd[1]*16; trtest[2] = fwdorg[2] + fwd[2]*16; trap->Trace(&tr, ps->origin, mins, maxs, trtest, ps->clientNum, MASK_PLAYERSOLID, qfalse, 0, 0); if ((tr.fraction != 1 && tr.entityNum != ps->clientNum) || tr.startsolid || tr.allsolid) { G_AddEvent(&g_entities[ps->clientNum], EV_ITEMUSEFAIL, SENTRY_NOROOM); return 0; } return 1; case HI_SHIELD: mins[0] = -8; mins[1] = -8; mins[2] = 0; maxs[0] = 8; maxs[1] = 8; maxs[2] = 8; AngleVectors (ps->viewangles, fwd, NULL, NULL); fwd[2] = 0; VectorMA(ps->origin, 64, fwd, dest); trap->Trace(&tr, ps->origin, mins, maxs, dest, ps->clientNum, MASK_SHOT, qfalse, 0, 0 ); if (tr.fraction > 0.9 && !tr.startsolid && !tr.allsolid) { VectorCopy(tr.endpos, pos); VectorSet( dest, pos[0], pos[1], pos[2] - 4096 ); trap->Trace( &tr, pos, mins, maxs, dest, ps->clientNum, MASK_SOLID, qfalse, 0, 0 ); if ( !tr.startsolid && !tr.allsolid ) { return 1; } } G_AddEvent(&g_entities[ps->clientNum], EV_ITEMUSEFAIL, SHIELD_NOROOM); return 0; case HI_JETPACK: //do something? return 1; case HI_HEALTHDISP: return 1; case HI_AMMODISP: return 1; case HI_EWEB: return 1; case HI_CLOAK: return 1; default: return 1; } } void saberKnockDown(gentity_t *saberent, gentity_t *saberOwner, gentity_t *other); void Cmd_ToggleSaber_f(gentity_t *ent) { if (ent->client->ps.fd.forceGripCripple) { //if they are being gripped, don't let them unholster their saber if (ent->client->ps.saberHolstered) { return; } } if (ent->client->ps.saberInFlight) { if (ent->client->ps.saberEntityNum) { //turn it off in midair saberKnockDown(&g_entities[ent->client->ps.saberEntityNum], ent, ent); } return; } if (ent->client->ps.forceHandExtend != HANDEXTEND_NONE) { return; } if (ent->client->ps.weapon != WP_SABER) { return; } // if (ent->client->ps.duelInProgress && !ent->client->ps.saberHolstered) // { // return; // } if (ent->client->ps.duelTime >= level.time) { return; } if (ent->client->ps.saberLockTime >= level.time) { return; } if (ent->client && ent->client->ps.weaponTime < 1) { if (ent->client->ps.saberHolstered == 2) { ent->client->ps.saberHolstered = 0; if (ent->client->saber[0].soundOn) { G_Sound(ent, CHAN_AUTO, ent->client->saber[0].soundOn); } if (ent->client->saber[1].soundOn) { G_Sound(ent, CHAN_AUTO, ent->client->saber[1].soundOn); } } else { ent->client->ps.saberHolstered = 2; if (ent->client->saber[0].soundOff) { G_Sound(ent, CHAN_AUTO, ent->client->saber[0].soundOff); } if (ent->client->saber[1].soundOff && ent->client->saber[1].model[0]) { G_Sound(ent, CHAN_AUTO, ent->client->saber[1].soundOff); } //prevent anything from being done for 400ms after holster ent->client->ps.weaponTime = 400; } } } extern vmCvar_t d_saberStanceDebug; extern qboolean WP_SaberCanTurnOffSomeBlades( saberInfo_t *saber ); void Cmd_SaberAttackCycle_f(gentity_t *ent) { int selectLevel = 0; qboolean usingSiegeStyle = qfalse; if ( !ent || !ent->client ) { return; } if ( level.intermissionQueued || level.intermissiontime ) { trap->SendServerCommand( ent-g_entities, va( "print \"%s (saberAttackCycle)\n\"", G_GetStringEdString( "MP_SVGAME", "CANNOT_TASK_INTERMISSION" ) ) ); return; } if ( ent->health <= 0 || ent->client->tempSpectate >= level.time || ent->client->sess.sessionTeam == TEAM_SPECTATOR ) { trap->SendServerCommand( ent-g_entities, va( "print \"%s\n\"", G_GetStringEdString( "MP_SVGAME", "MUSTBEALIVE" ) ) ); return; } if ( ent->client->ps.weapon != WP_SABER ) { return; } /* if (ent->client->ps.weaponTime > 0) { //no switching attack level when busy return; } */ if (ent->client->saber[0].model[0] && ent->client->saber[1].model[0]) { //no cycling for akimbo if ( WP_SaberCanTurnOffSomeBlades( &ent->client->saber[1] ) ) {//can turn second saber off if ( ent->client->ps.saberHolstered == 1 ) {//have one holstered //unholster it G_Sound(ent, CHAN_AUTO, ent->client->saber[1].soundOn); ent->client->ps.saberHolstered = 0; //g_active should take care of this, but... ent->client->ps.fd.saberAnimLevel = SS_DUAL; } else if ( ent->client->ps.saberHolstered == 0 ) {//have none holstered if ( (ent->client->saber[1].saberFlags2&SFL2_NO_MANUAL_DEACTIVATE) ) {//can't turn it off manually } else if ( ent->client->saber[1].bladeStyle2Start > 0 && (ent->client->saber[1].saberFlags2&SFL2_NO_MANUAL_DEACTIVATE2) ) {//can't turn it off manually } else { //turn it off G_Sound(ent, CHAN_AUTO, ent->client->saber[1].soundOff); ent->client->ps.saberHolstered = 1; //g_active should take care of this, but... ent->client->ps.fd.saberAnimLevel = SS_FAST; } } if (d_saberStanceDebug.integer) { trap->SendServerCommand( ent-g_entities, va("print \"SABERSTANCEDEBUG: Attempted to toggle dual saber blade.\n\"") ); } return; } } else if (ent->client->saber[0].numBlades > 1 && WP_SaberCanTurnOffSomeBlades( &ent->client->saber[0] ) ) { //use staff stance then. if ( ent->client->ps.saberHolstered == 1 ) {//second blade off if ( ent->client->ps.saberInFlight ) {//can't turn second blade back on if it's in the air, you naughty boy! if (d_saberStanceDebug.integer) { trap->SendServerCommand( ent-g_entities, va("print \"SABERSTANCEDEBUG: Attempted to toggle staff blade in air.\n\"") ); } return; } //turn it on G_Sound(ent, CHAN_AUTO, ent->client->saber[0].soundOn); ent->client->ps.saberHolstered = 0; //g_active should take care of this, but... if ( ent->client->saber[0].stylesForbidden ) {//have a style we have to use WP_UseFirstValidSaberStyle( &ent->client->saber[0], &ent->client->saber[1], ent->client->ps.saberHolstered, &selectLevel ); if ( ent->client->ps.weaponTime <= 0 ) { //not busy, set it now ent->client->ps.fd.saberAnimLevel = selectLevel; } else { //can't set it now or we might cause unexpected chaining, so queue it ent->client->saberCycleQueue = selectLevel; } } } else if ( ent->client->ps.saberHolstered == 0 ) {//both blades on if ( (ent->client->saber[0].saberFlags2&SFL2_NO_MANUAL_DEACTIVATE) ) {//can't turn it off manually } else if ( ent->client->saber[0].bladeStyle2Start > 0 && (ent->client->saber[0].saberFlags2&SFL2_NO_MANUAL_DEACTIVATE2) ) {//can't turn it off manually } else { //turn second one off G_Sound(ent, CHAN_AUTO, ent->client->saber[0].soundOff); ent->client->ps.saberHolstered = 1; //g_active should take care of this, but... if ( ent->client->saber[0].singleBladeStyle != SS_NONE ) { if ( ent->client->ps.weaponTime <= 0 ) { //not busy, set it now ent->client->ps.fd.saberAnimLevel = ent->client->saber[0].singleBladeStyle; } else { //can't set it now or we might cause unexpected chaining, so queue it ent->client->saberCycleQueue = ent->client->saber[0].singleBladeStyle; } } } } if (d_saberStanceDebug.integer) { trap->SendServerCommand( ent-g_entities, va("print \"SABERSTANCEDEBUG: Attempted to toggle staff blade.\n\"") ); } return; } if (ent->client->saberCycleQueue) { //resume off of the queue if we haven't gotten a chance to update it yet selectLevel = ent->client->saberCycleQueue; } else { selectLevel = ent->client->ps.fd.saberAnimLevel; } if (level.gametype == GT_SIEGE && ent->client->siegeClass != -1 && bgSiegeClasses[ent->client->siegeClass].saberStance) { //we have a flag of useable stances so cycle through it instead int i = selectLevel+1; usingSiegeStyle = qtrue; while (i != selectLevel) { //cycle around upward til we hit the next style or end up back on this one if (i >= SS_NUM_SABER_STYLES) { //loop back around to the first valid i = SS_FAST; } if (bgSiegeClasses[ent->client->siegeClass].saberStance & (1 << i)) { //we can use this one, select it and break out. selectLevel = i; break; } i++; } if (d_saberStanceDebug.integer) { trap->SendServerCommand( ent-g_entities, va("print \"SABERSTANCEDEBUG: Attempted to cycle given class stance.\n\"") ); } } else { selectLevel++; if ( selectLevel > ent->client->ps.fd.forcePowerLevel[FP_SABER_OFFENSE] ) { selectLevel = FORCE_LEVEL_1; } if (d_saberStanceDebug.integer) { trap->SendServerCommand( ent-g_entities, va("print \"SABERSTANCEDEBUG: Attempted to cycle stance normally.\n\"") ); } } /* #ifndef FINAL_BUILD switch ( selectLevel ) { case FORCE_LEVEL_1: trap->SendServerCommand( ent-g_entities, va("print \"Lightsaber Combat Style: %sfast\n\"", S_COLOR_BLUE) ); break; case FORCE_LEVEL_2: trap->SendServerCommand( ent-g_entities, va("print \"Lightsaber Combat Style: %smedium\n\"", S_COLOR_YELLOW) ); break; case FORCE_LEVEL_3: trap->SendServerCommand( ent-g_entities, va("print \"Lightsaber Combat Style: %sstrong\n\"", S_COLOR_RED) ); break; } #endif */ if ( !usingSiegeStyle ) { //make sure it's valid, change it if not WP_UseFirstValidSaberStyle( &ent->client->saber[0], &ent->client->saber[1], ent->client->ps.saberHolstered, &selectLevel ); } if (ent->client->ps.weaponTime <= 0) { //not busy, set it now ent->client->ps.fd.saberAnimLevelBase = ent->client->ps.fd.saberAnimLevel = selectLevel; } else { //can't set it now or we might cause unexpected chaining, so queue it ent->client->ps.fd.saberAnimLevelBase = ent->client->saberCycleQueue = selectLevel; } } qboolean G_OtherPlayersDueling(void) { int i = 0; gentity_t *ent; while (i < MAX_CLIENTS) { ent = &g_entities[i]; if (ent && ent->inuse && ent->client && ent->client->ps.duelInProgress) { return qtrue; } i++; } return qfalse; } void Cmd_EngageDuel_f(gentity_t *ent) { trace_t tr; vec3_t forward, fwdOrg; if (!g_privateDuel.integer) { return; } if (level.gametype == GT_DUEL || level.gametype == GT_POWERDUEL) { //rather pointless in this mode.. trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NODUEL_GAMETYPE")) ); return; } //if (level.gametype >= GT_TEAM && level.gametype != GT_SIEGE) if (level.gametype >= GT_TEAM) { //no private dueling in team modes trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "NODUEL_GAMETYPE")) ); return; } if (ent->client->ps.duelTime >= level.time) { return; } if (ent->client->ps.weapon != WP_SABER) { return; } /* if (!ent->client->ps.saberHolstered) { //must have saber holstered at the start of the duel return; } */ //NOTE: No longer doing this.. if (ent->client->ps.saberInFlight) { return; } if (ent->client->ps.duelInProgress) { return; } //New: Don't let a player duel if he just did and hasn't waited 10 seconds yet (note: If someone challenges him, his duel timer will reset so he can accept) /*if (ent->client->ps.fd.privateDuelTime > level.time) { trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "CANTDUEL_JUSTDID")) ); return; } if (G_OtherPlayersDueling()) { trap->SendServerCommand( ent-g_entities, va("print \"%s\n\"", G_GetStringEdString("MP_SVGAME", "CANTDUEL_BUSY")) ); return; }*/ AngleVectors( ent->client->ps.viewangles, forward, NULL, NULL ); fwdOrg[0] = ent->client->ps.origin[0] + forward[0]*256; fwdOrg[1] = ent->client->ps.origin[1] + forward[1]*256; fwdOrg[2] = (ent->client->ps.origin[2]+ent->client->ps.viewheight) + forward[2]*256; trap->Trace(&tr, ent->client->ps.origin, NULL, NULL, fwdOrg, ent->s.number, MASK_PLAYERSOLID, qfalse, 0, 0); if (tr.fraction != 1 && tr.entityNum < MAX_CLIENTS) { gentity_t *challenged = &g_entities[tr.entityNum]; if (!challenged || !challenged->client || !challenged->inuse || challenged->health < 1 || challenged->client->ps.stats[STAT_HEALTH] < 1 || challenged->client->ps.weapon != WP_SABER || challenged->client->ps.duelInProgress || challenged->client->ps.saberInFlight) { return; } if (level.gametype >= GT_TEAM && OnSameTeam(ent, challenged)) { return; } if (challenged->client->ps.duelIndex == ent->s.number && challenged->client->ps.duelTime >= level.time) { trap->SendServerCommand( /*challenged-g_entities*/-1, va("print \"%s %s %s!\n\"", challenged->client->pers.netname, G_GetStringEdString("MP_SVGAME", "PLDUELACCEPT"), ent->client->pers.netname) ); ent->client->ps.duelInProgress = qtrue; challenged->client->ps.duelInProgress = qtrue; ent->client->ps.duelTime = level.time + 2000; challenged->client->ps.duelTime = level.time + 2000; G_AddEvent(ent, EV_PRIVATE_DUEL, 1); G_AddEvent(challenged, EV_PRIVATE_DUEL, 1); //Holster their sabers now, until the duel starts (then they'll get auto-turned on to look cool) if (!ent->client->ps.saberHolstered) { if (ent->client->saber[0].soundOff) { G_Sound(ent, CHAN_AUTO, ent->client->saber[0].soundOff); } if (ent->client->saber[1].soundOff && ent->client->saber[1].model[0]) { G_Sound(ent, CHAN_AUTO, ent->client->saber[1].soundOff); } ent->client->ps.weaponTime = 400; ent->client->ps.saberHolstered = 2; } if (!challenged->client->ps.saberHolstered) { if (challenged->client->saber[0].soundOff) { G_Sound(challenged, CHAN_AUTO, challenged->client->saber[0].soundOff); } if (challenged->client->saber[1].soundOff && challenged->client->saber[1].model[0]) { G_Sound(challenged, CHAN_AUTO, challenged->client->saber[1].soundOff); } challenged->client->ps.weaponTime = 400; challenged->client->ps.saberHolstered = 2; } } else { //Print the message that a player has been challenged in private, only announce the actual duel initiation in private trap->SendServerCommand( challenged-g_entities, va("cp \"%s %s\n\"", ent->client->pers.netname, G_GetStringEdString("MP_SVGAME", "PLDUELCHALLENGE")) ); trap->SendServerCommand( ent-g_entities, va("cp \"%s %s\n\"", G_GetStringEdString("MP_SVGAME", "PLDUELCHALLENGED"), challenged->client->pers.netname) ); } challenged->client->ps.fd.privateDuelTime = 0; //reset the timer in case this player just got out of a duel. He should still be able to accept the challenge. ent->client->ps.forceHandExtend = HANDEXTEND_DUELCHALLENGE; ent->client->ps.forceHandExtendTime = level.time + 1000; ent->client->ps.duelIndex = challenged->s.number; ent->client->ps.duelTime = level.time + 5000; } } #ifndef FINAL_BUILD extern stringID_table_t animTable[MAX_ANIMATIONS+1]; void Cmd_DebugSetSaberMove_f(gentity_t *self) { int argNum = trap->Argc(); char arg[MAX_STRING_CHARS]; if (argNum < 2) { return; } trap->Argv( 1, arg, sizeof( arg ) ); if (!arg[0]) { return; } self->client->ps.saberMove = atoi(arg); self->client->ps.saberBlocked = BLOCKED_BOUNCE_MOVE; if (self->client->ps.saberMove >= LS_MOVE_MAX) { self->client->ps.saberMove = LS_MOVE_MAX-1; } Com_Printf("Anim for move: %s\n", animTable[saberMoveData[self->client->ps.saberMove].animToUse].name); } void Cmd_DebugSetBodyAnim_f(gentity_t *self) { int argNum = trap->Argc(); char arg[MAX_STRING_CHARS]; int i = 0; if (argNum < 2) { return; } trap->Argv( 1, arg, sizeof( arg ) ); if (!arg[0]) { return; } while (i < MAX_ANIMATIONS) { if (!Q_stricmp(arg, animTable[i].name)) { break; } i++; } if (i == MAX_ANIMATIONS) { Com_Printf("Animation '%s' does not exist\n", arg); return; } G_SetAnim(self, NULL, SETANIM_BOTH, i, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0); Com_Printf("Set body anim to %s\n", arg); } #endif void StandardSetBodyAnim(gentity_t *self, int anim, int flags) { G_SetAnim(self, NULL, SETANIM_BOTH, anim, flags, 0); } void DismembermentTest(gentity_t *self); void Bot_SetForcedMovement(int bot, int forward, int right, int up); #ifndef FINAL_BUILD extern void DismembermentByNum(gentity_t *self, int num); extern void G_SetVehDamageFlags( gentity_t *veh, int shipSurf, int damageLevel ); #endif qboolean TryGrapple(gentity_t *ent) { if (ent->client->ps.weaponTime > 0) { //weapon busy return qfalse; } if (ent->client->ps.forceHandExtend != HANDEXTEND_NONE) { //force power or knockdown or something return qfalse; } if (ent->client->grappleState) { //already grappling? but weapontime should be > 0 then.. return qfalse; } if (ent->client->ps.weapon != WP_SABER && ent->client->ps.weapon != WP_MELEE) { return qfalse; } if (ent->client->ps.weapon == WP_SABER && !ent->client->ps.saberHolstered) { Cmd_ToggleSaber_f(ent); if (!ent->client->ps.saberHolstered) { //must have saber holstered return qfalse; } } //G_SetAnim(ent, &ent->client->pers.cmd, SETANIM_BOTH, BOTH_KYLE_PA_1, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0); G_SetAnim(ent, &ent->client->pers.cmd, SETANIM_BOTH, BOTH_KYLE_GRAB, SETANIM_FLAG_OVERRIDE|SETANIM_FLAG_HOLD, 0); if (ent->client->ps.torsoAnim == BOTH_KYLE_GRAB) { //providing the anim set succeeded.. ent->client->ps.torsoTimer += 500; //make the hand stick out a little longer than it normally would if (ent->client->ps.legsAnim == ent->client->ps.torsoAnim) { ent->client->ps.legsTimer = ent->client->ps.torsoTimer; } ent->client->ps.weaponTime = ent->client->ps.torsoTimer; ent->client->dangerTime = level.time; return qtrue; } return qfalse; } void Cmd_TargetUse_f( gentity_t *ent ) { if ( trap->Argc() > 1 ) { char sArg[MAX_STRING_CHARS] = {0}; gentity_t *targ; trap->Argv( 1, sArg, sizeof( sArg ) ); targ = G_Find( NULL, FOFS( targetname ), sArg ); while ( targ ) { if ( targ->use ) targ->use( targ, ent, ent ); targ = G_Find( targ, FOFS( targetname ), sArg ); } } } void Cmd_TheDestroyer_f( gentity_t *ent ) { if ( !ent->client->ps.saberHolstered || ent->client->ps.weapon != WP_SABER ) return; Cmd_ToggleSaber_f( ent ); } void Cmd_BotMoveForward_f( gentity_t *ent ) { int arg = 4000; int bCl = 0; char sarg[MAX_STRING_CHARS]; assert( trap->Argc() > 1 ); trap->Argv( 1, sarg, sizeof( sarg ) ); assert( sarg[0] ); bCl = atoi( sarg ); Bot_SetForcedMovement( bCl, arg, -1, -1 ); } void Cmd_BotMoveBack_f( gentity_t *ent ) { int arg = -4000; int bCl = 0; char sarg[MAX_STRING_CHARS]; assert( trap->Argc() > 1 ); trap->Argv( 1, sarg, sizeof( sarg ) ); assert( sarg[0] ); bCl = atoi( sarg ); Bot_SetForcedMovement( bCl, arg, -1, -1 ); } void Cmd_BotMoveRight_f( gentity_t *ent ) { int arg = 4000; int bCl = 0; char sarg[MAX_STRING_CHARS]; assert( trap->Argc() > 1 ); trap->Argv( 1, sarg, sizeof( sarg ) ); assert( sarg[0] ); bCl = atoi( sarg ); Bot_SetForcedMovement( bCl, -1, arg, -1 ); } void Cmd_BotMoveLeft_f( gentity_t *ent ) { int arg = -4000; int bCl = 0; char sarg[MAX_STRING_CHARS]; assert( trap->Argc() > 1 ); trap->Argv( 1, sarg, sizeof( sarg ) ); assert( sarg[0] ); bCl = atoi( sarg ); Bot_SetForcedMovement( bCl, -1, arg, -1 ); } void Cmd_BotMoveUp_f( gentity_t *ent ) { int arg = 4000; int bCl = 0; char sarg[MAX_STRING_CHARS]; assert( trap->Argc() > 1 ); trap->Argv( 1, sarg, sizeof( sarg ) ); assert( sarg[0] ); bCl = atoi( sarg ); Bot_SetForcedMovement( bCl, -1, -1, arg ); } void Cmd_AddBot_f( gentity_t *ent ) { //because addbot isn't a recognized command unless you're the server, but it is in the menus regardless trap->SendServerCommand( ent-g_entities, va( "print \"%s.\n\"", G_GetStringEdString( "MP_SVGAME", "ONLY_ADD_BOTS_AS_SERVER" ) ) ); } /* ================= ClientCommand ================= */ #define CMD_NOINTERMISSION (1<<0) #define CMD_CHEAT (1<<1) #define CMD_ALIVE (1<<2) typedef struct command_s { const char *name; void (*func)(gentity_t *ent); int flags; } command_t; int cmdcmp( const void *a, const void *b ) { return Q_stricmp( (const char *)a, ((command_t*)b)->name ); } command_t commands[] = { { "addbot", Cmd_AddBot_f, 0 }, { "callteamvote", Cmd_CallTeamVote_f, CMD_NOINTERMISSION }, { "callvote", Cmd_CallVote_f, CMD_NOINTERMISSION }, { "debugBMove_Back", Cmd_BotMoveBack_f, CMD_CHEAT|CMD_ALIVE }, { "debugBMove_Forward", Cmd_BotMoveForward_f, CMD_CHEAT|CMD_ALIVE }, { "debugBMove_Left", Cmd_BotMoveLeft_f, CMD_CHEAT|CMD_ALIVE }, { "debugBMove_Right", Cmd_BotMoveRight_f, CMD_CHEAT|CMD_ALIVE }, { "debugBMove_Up", Cmd_BotMoveUp_f, CMD_CHEAT|CMD_ALIVE }, { "duelteam", Cmd_DuelTeam_f, CMD_NOINTERMISSION }, { "follow", Cmd_Follow_f, CMD_NOINTERMISSION }, { "follownext", Cmd_FollowNext_f, CMD_NOINTERMISSION }, { "followprev", Cmd_FollowPrev_f, CMD_NOINTERMISSION }, { "forcechanged", Cmd_ForceChanged_f, 0 }, { "gc", Cmd_GameCommand_f, CMD_NOINTERMISSION }, { "give", Cmd_Give_f, CMD_CHEAT|CMD_ALIVE|CMD_NOINTERMISSION }, { "giveother", Cmd_GiveOther_f, CMD_CHEAT|CMD_NOINTERMISSION }, { "god", Cmd_God_f, CMD_CHEAT|CMD_ALIVE|CMD_NOINTERMISSION }, { "kill", Cmd_Kill_f, CMD_ALIVE|CMD_NOINTERMISSION }, { "killother", Cmd_KillOther_f, CMD_CHEAT|CMD_NOINTERMISSION }, // { "kylesmash", TryGrapple, 0 }, { "levelshot", Cmd_LevelShot_f, CMD_CHEAT|CMD_ALIVE|CMD_NOINTERMISSION }, { "maplist", Cmd_MapList_f, CMD_NOINTERMISSION }, { "noclip", Cmd_Noclip_f, CMD_CHEAT|CMD_ALIVE|CMD_NOINTERMISSION }, { "notarget", Cmd_Notarget_f, CMD_CHEAT|CMD_ALIVE|CMD_NOINTERMISSION }, { "npc", Cmd_NPC_f, CMD_CHEAT|CMD_ALIVE }, { "say", Cmd_Say_f, 0 }, { "say_team", Cmd_SayTeam_f, 0 }, { "score", Cmd_Score_f, 0 }, { "setviewpos", Cmd_SetViewpos_f, CMD_CHEAT|CMD_NOINTERMISSION }, { "siegeclass", Cmd_SiegeClass_f, CMD_NOINTERMISSION }, { "team", Cmd_Team_f, CMD_NOINTERMISSION }, // { "teamtask", Cmd_TeamTask_f, CMD_NOINTERMISSION }, { "teamvote", Cmd_TeamVote_f, CMD_NOINTERMISSION }, { "tell", Cmd_Tell_f, 0 }, { "thedestroyer", Cmd_TheDestroyer_f, CMD_CHEAT|CMD_ALIVE|CMD_NOINTERMISSION }, { "t_use", Cmd_TargetUse_f, CMD_CHEAT|CMD_ALIVE }, { "voice_cmd", Cmd_VoiceCommand_f, CMD_NOINTERMISSION }, { "vote", Cmd_Vote_f, CMD_NOINTERMISSION }, { "where", Cmd_Where_f, CMD_NOINTERMISSION }, }; static const size_t numCommands = ARRAY_LEN( commands ); void ClientCommand( int clientNum ) { gentity_t *ent = NULL; char cmd[MAX_TOKEN_CHARS] = {0}; command_t *command = NULL; ent = g_entities + clientNum; if ( !ent->client || ent->client->pers.connected != CON_CONNECTED ) { G_SecurityLogPrintf( "ClientCommand(%d) without an active connection\n", clientNum ); return; // not fully in game yet } trap->Argv( 0, cmd, sizeof( cmd ) ); //rww - redirect bot commands if ( strstr( cmd, "bot_" ) && AcceptBotCommand( cmd, ent ) ) return; //end rww command = (command_t *)Q_LinearSearch( cmd, commands, numCommands, sizeof( commands[0] ), cmdcmp ); if ( !command ) { trap->SendServerCommand( clientNum, va( "print \"Unknown command %s\n\"", cmd ) ); return; } else if ( (command->flags & CMD_NOINTERMISSION) && ( level.intermissionQueued || level.intermissiontime ) ) { trap->SendServerCommand( clientNum, va( "print \"%s (%s)\n\"", G_GetStringEdString( "MP_SVGAME", "CANNOT_TASK_INTERMISSION" ), cmd ) ); return; } else if ( (command->flags & CMD_CHEAT) && !sv_cheats.integer ) { trap->SendServerCommand( clientNum, va( "print \"%s\n\"", G_GetStringEdString( "MP_SVGAME", "NOCHEATS" ) ) ); return; } else if ( (command->flags & CMD_ALIVE) && (ent->health <= 0 || ent->client->tempSpectate >= level.time || ent->client->sess.sessionTeam == TEAM_SPECTATOR) ) { trap->SendServerCommand( clientNum, va( "print \"%s\n\"", G_GetStringEdString( "MP_SVGAME", "MUSTBEALIVE" ) ) ); return; } else command->func( ent ); }