AiBaseTeamBrain *AiBaseTeamBrain::InstantiateTeamBrain( int team, const char *gametype ) { // Delegate construction to AiSquadBasedTeamBrain if( GS_TeamBasedGametype() && !GS_InvidualGameType() ) { return AiSquadBasedTeamBrain::InstantiateTeamBrain( team, gametype ); } void *mem = G_Malloc( sizeof( AiBaseTeamBrain ) ); return new(mem)AiBaseTeamBrain( team ); }
void G_Teams_Coach( edict_t *ent ) { if( GS_TeamBasedGametype() && !GS_InvidualGameType() && ent->s.team != TEAM_SPECTATOR ) { if( !teamlist[ent->s.team].has_coach ) { if( GS_MatchState() > MATCH_STATE_WARMUP && !GS_MatchPaused() ) { G_PrintMsg( ent, "Can't set coach mode with the match in progress\n" ); } else { // move to coach mode ent->r.client->teamstate.is_coach = true; G_GhostClient( ent ); ent->health = ent->max_health; ent->deadflag = DEAD_NO; G_ChasePlayer( ent, NULL, true, 0 ); //clear up his scores G_Match_Ready( ent ); // set ready and check readys memset( &ent->r.client->level.stats, 0, sizeof( ent->r.client->level.stats ) ); teamlist[ent->s.team].has_coach = true; G_PrintMsg( NULL, "%s%s is now team %s coach \n", ent->r.client->netname, S_COLOR_WHITE, GS_TeamName( ent->s.team ) ); } } else if( ent->r.client->teamstate.is_coach ) { // if you are this team coach, resign ent->r.client->teamstate.is_coach = false; G_PrintMsg( NULL, "%s%s is no longer team %s coach \n", ent->r.client->netname, S_COLOR_WHITE, GS_TeamName( ent->s.team ) ); G_Teams_SetTeam( ent, ent->s.team ); } else G_PrintMsg( ent, "Your team already has a coach.\n" ); } else G_PrintMsg( ent, "Coaching only valid while on a team in Team based Gametypes.\n" ); }
void G_Gametype_GENERIC_PlayerKilled( edict_t *targ, edict_t *attacker, edict_t *inflictor ) { if( !attacker || GS_MatchState() != MATCH_STATE_PLAYTIME || ( targ->r.svflags & SVF_CORPSE ) ) return; if( !attacker->r.client || attacker == targ || attacker == world ) teamlist[targ->s.team].stats.score--; else { if( GS_InvidualGameType() ) teamlist[attacker->s.team].stats.score = attacker->r.client->level.stats.score; if( GS_IsTeamDamage( &targ->s, &attacker->s ) ) teamlist[attacker->s.team].stats.score--; else teamlist[attacker->s.team].stats.score++; } // drop items if( targ->r.client && !( G_PointContents( targ->s.origin ) & CONTENTS_NODROP ) ) { // drop the weapon if ( targ->r.client->ps.stats[STAT_WEAPON] > WEAP_GUNBLADE ) { gsitem_t *weaponItem = GS_FindItemByTag( targ->r.client->ps.stats[STAT_WEAPON] ); if( weaponItem ) { edict_t *drop = Drop_Item( targ, weaponItem ); if( drop ) { drop->count = targ->r.client->ps.inventory[ weaponItem->weakammo_tag ]; targ->r.client->ps.inventory[ weaponItem->weakammo_tag ] = 0; } } } // drop ammo pack (won't drop anything if player doesn't have any strong ammo) Drop_Item( targ, GS_FindItemByTag( AMMO_PACK ) ); } }
/* * CG_FlashGameWindow * * Flashes game window in case of important events (match state changes, etc) for user to notice */ static void CG_FlashGameWindow( void ) { static int oldState = -1; int newState; bool flash = false; static int oldAlphaScore, oldBetaScore; static bool scoresSet = false; // notify player of important match states newState = GS_MatchState(); if( oldState != newState ) { switch( newState ) { case MATCH_STATE_COUNTDOWN: case MATCH_STATE_PLAYTIME: case MATCH_STATE_POSTMATCH: flash = true; break; default: break; } oldState = newState; } // notify player of teams scoring in team-based gametypes if( !scoresSet || ( oldAlphaScore != cg.predictedPlayerState.stats[STAT_TEAM_ALPHA_SCORE] || oldBetaScore != cg.predictedPlayerState.stats[STAT_TEAM_BETA_SCORE] ) ) { oldAlphaScore = cg.predictedPlayerState.stats[STAT_TEAM_ALPHA_SCORE]; oldBetaScore = cg.predictedPlayerState.stats[STAT_TEAM_BETA_SCORE]; flash = scoresSet && GS_TeamBasedGametype() && !GS_InvidualGameType(); scoresSet = true; } if( flash ) trap_VID_FlashWindow( cg_flashWindowCount->integer ); }
/* * G_Match_ScoreAnnouncement */ static void G_Match_ScoreAnnouncement( void ) { int i; edict_t *e, *chased; int num_leaders, team; if( !level.gametype.scoreAnnouncementEnabled ) return; num_leaders = 0; memset( leaders, 0, sizeof( leaders ) ); if( GS_TeamBasedGametype() ) { int score_max = -999999999; for( team = TEAM_ALPHA; team < GS_MAX_TEAMS; team++ ) { if( !teamlist[team].numplayers ) continue; if( teamlist[team].stats.score > score_max ) { score_max = teamlist[team].stats.score; leaders[0] = team; num_leaders = 1; } else if( teamlist[team].stats.score == score_max ) { leaders[num_leaders++] = team; } } leaders[num_leaders] = 0; } else { int score_max = -999999999; for( i = 0; i < MAX_CLIENTS && i < teamlist[TEAM_PLAYERS].numplayers; i++ ) { if( game.clients[teamlist[TEAM_PLAYERS].playerIndices[i]-1].level.stats.score > score_max ) { score_max = game.clients[teamlist[TEAM_PLAYERS].playerIndices[i]-1].level.stats.score; leaders[0] = teamlist[TEAM_PLAYERS].playerIndices[i]; num_leaders = 1; } else if( game.clients[teamlist[TEAM_PLAYERS].playerIndices[i]-1].level.stats.score == score_max ) { leaders[num_leaders++] = teamlist[TEAM_PLAYERS].playerIndices[i]; } } leaders[num_leaders] = 0; } if( !score_announcement_init ) { // copy over to last_leaders memcpy( last_leaders, leaders, sizeof( leaders ) ); score_announcement_init = true; return; } for( e = game.edicts + 1; PLAYERNUM( e ) < gs.maxclients; e++ ) { if( !e->r.client || trap_GetClientState( PLAYERNUM( e ) ) < CS_SPAWNED ) continue; if( e->r.client->resp.chase.active ) chased = &game.edicts[e->r.client->resp.chase.target]; else chased = e; // floating spectator if( chased->s.team == TEAM_SPECTATOR ) { if( !GS_TeamBasedGametype() ) continue; if( last_leaders[1] == 0 && leaders[1] != 0 ) { G_AnnouncerSound( e, trap_SoundIndex( va( S_ANNOUNCER_SCORE_TEAM_TIED_LEAD_1_to_2, ( rand()&1 )+1 ) ), GS_MAX_TEAMS, true, NULL ); } else if( leaders[1] == 0 && ( last_leaders[0] != leaders[0] || last_leaders[1] != 0 ) ) { //G_AnnouncerSound( e, trap_SoundIndex( va( S_ANNOUNCER_SCORE_TEAM_1_to_4_TAKEN_LEAD_1_to_2, // leaders[0]-1, ( rand()&1 )+1 ) ), GS_MAX_TEAMS, true, NULL ); } continue; } // in the game or chasing someone who is if( G_WasLeading( chased ) && !G_IsLeading( chased ) ) { if( GS_TeamBasedGametype() && !GS_InvidualGameType() ) G_AnnouncerSound( e, trap_SoundIndex( va( S_ANNOUNCER_SCORE_TEAM_LOST_LEAD_1_to_2, ( rand()&1 )+1 ) ), GS_MAX_TEAMS, true, NULL ); else G_AnnouncerSound( e, trap_SoundIndex( va( S_ANNOUNCER_SCORE_LOST_LEAD_1_to_2, ( rand()&1 )+1 ) ), GS_MAX_TEAMS, true, NULL ); } else if( ( !G_WasLeading( chased ) || ( last_leaders[1] != 0 ) ) && G_IsLeading( chased ) && ( leaders[1] == 0 ) ) { if( GS_TeamBasedGametype() && !GS_InvidualGameType() ) G_AnnouncerSound( e, trap_SoundIndex( va( S_ANNOUNCER_SCORE_TEAM_TAKEN_LEAD_1_to_2, ( rand()&1 )+1 ) ), GS_MAX_TEAMS, true, NULL ); else G_AnnouncerSound( e, trap_SoundIndex( va( S_ANNOUNCER_SCORE_TAKEN_LEAD_1_to_2, ( rand()&1 )+1 ) ), GS_MAX_TEAMS, true, NULL ); } else if( ( !G_WasLeading( chased ) || ( last_leaders[1] == 0 ) ) && G_IsLeading( chased ) && ( leaders[1] != 0 ) ) { if( GS_TeamBasedGametype() && !GS_InvidualGameType() ) G_AnnouncerSound( e, trap_SoundIndex( va( S_ANNOUNCER_SCORE_TEAM_TIED_LEAD_1_to_2, ( rand()&1 )+1 ) ), GS_MAX_TEAMS, true, NULL ); else G_AnnouncerSound( e, trap_SoundIndex( va( S_ANNOUNCER_SCORE_TIED_LEAD_1_to_2, ( rand()&1 )+1 ) ), GS_MAX_TEAMS, true, NULL ); } } // copy over to last_leaders memcpy( last_leaders, leaders, sizeof( leaders ) ); }
/* * G_Match_Autorecord_Start */ void G_Match_Autorecord_Start( void ) { int team, i, playerCount; G_Match_SetAutorecordState( "start" ); // do not start autorecording if all playing clients are bots for( playerCount = 0, team = TEAM_PLAYERS; team < GS_MAX_TEAMS; team++ ) { // add our team info to the string for( i = 0; i < teamlist[team].numplayers; i++ ) { if( game.edicts[ teamlist[team].playerIndices[i] ].r.svflags & SVF_FAKECLIENT ) continue; playerCount++; break; // we only need one for this check } } if( playerCount && g_autorecord->integer ) { char datetime[17], players[MAX_STRING_CHARS]; time_t long_time; struct tm *newtime; // date & time time( &long_time ); newtime = localtime( &long_time ); Q_snprintfz( datetime, sizeof( datetime ), "%04d-%02d-%02d_%02d-%02d", newtime->tm_year + 1900, newtime->tm_mon+1, newtime->tm_mday, newtime->tm_hour, newtime->tm_min ); // list of players Q_strncpyz( players, trap_GetConfigString( CS_MATCHNAME ), sizeof( players ) ); if( players[0] == '\0' ) { if( GS_InvidualGameType() ) { const char *netname; int team; edict_t *ent; for( team = TEAM_ALPHA; team < GS_MAX_TEAMS; team++ ) { if( !teamlist[team].numplayers ) continue; ent = game.edicts + teamlist[team].playerIndices[0]; netname = ent->r.client->netname; Q_strncatz( players, netname, sizeof( players ) ); if( team != GS_MAX_TEAMS - 1 ) Q_strncatz( players, " vs ", sizeof( players ) ); } } } if( players[0] != '\0' ) { char *t = strstr( players, " vs " ); if( t ) memcpy( t, "_vs_", strlen( "_vs_" ) ); Q_strncpyz( players, COM_RemoveJunkChars( COM_RemoveColorTokens( players ) ), sizeof( players ) ); } // combine Q_snprintfz( level.autorecord_name, sizeof( level.autorecord_name ), "%s_%s_%s%s%s_auto%04i", datetime, gs.gametypeName, level.mapname, players[0] == '\0' ? "" : "_", players, (int)brandom( 1, 9999 ) ); trap_Cmd_ExecuteText( EXEC_APPEND, va( "serverrecord %s\n", level.autorecord_name ) ); } }
/* * G_vsay_f */ static void G_vsay_f( edict_t *ent, bool team ) { edict_t *event = NULL; g_vsays_t *vsay; const char *text = NULL; char *msg = trap_Cmd_Argv( 1 ); if( ent->r.client && ent->r.client->muted & 2 ) return; if( ( !GS_TeamBasedGametype() || GS_InvidualGameType() ) && ent->s.team != TEAM_SPECTATOR ) team = false; if( !( ent->r.svflags & SVF_FAKECLIENT ) ) { // ignore flood checks on bots if( ent->r.client->level.last_vsay > game.realtime - 500 ) return; // ignore silently vsays in that come in rapid succession ent->r.client->level.last_vsay = game.realtime; if( CheckFlood( ent, false ) ) return; } for( vsay = g_vsays; vsay->name; vsay++ ) { if( !Q_stricmp( msg, vsay->name ) ) { event = G_SpawnEvent( EV_VSAY, vsay->id, NULL ); text = vsay->message; break; } } if( event && text ) { char saystring[256]; event->r.svflags |= SVF_BROADCAST; // force sending even when not in PVS event->s.ownerNum = ent->s.number; if( team ) { event->s.team = ent->s.team; event->r.svflags |= SVF_ONLYTEAM; // send only to clients with the same ->s.team value } if( trap_Cmd_Argc() > 2 ) { int i; saystring[0] = 0; for( i = 2; i < trap_Cmd_Argc(); i++ ) { Q_strncatz( saystring, trap_Cmd_Argv( i ), sizeof( saystring ) ); Q_strncatz( saystring, " ", sizeof( saystring ) ); } text = saystring; } if( team ) G_Say_Team( ent, va( "(v) %s", text ), false ); else G_ChatMsg( NULL, ent, false, "(v) %s", text ); return; } // unknown token, print help { char string[MAX_STRING_CHARS]; // print information string[0] = 0; if( msg && msg[0] != '\0' ) Q_strncatz( string, va( "%sUnknown vsay token%s \"%s\"\n", S_COLOR_YELLOW, S_COLOR_WHITE, msg ), sizeof( string ) ); Q_strncatz( string, va( "%svsays:%s\n", S_COLOR_YELLOW, S_COLOR_WHITE ), sizeof( string ) ); for( vsay = g_vsays; vsay->name; vsay++ ) { if( strlen( vsay->name ) + strlen( string ) < sizeof( string ) - 6 ) { Q_strncatz( string, va( "%s ", vsay->name ), sizeof( string ) ); } } Q_strncatz( string, "\n", sizeof( string ) ); G_PrintMsg( ent, string ); } }
void G_Say_Team( edict_t *who, char *msg, bool checkflood ) { char outmsg[256]; char buf[256]; char *p; char current_color[3]; if( who->s.team != TEAM_SPECTATOR && ( !GS_TeamBasedGametype() || GS_InvidualGameType() ) ) { Cmd_Say_f( who, false, true ); return; } if( checkflood ) { if( CheckFlood( who, true ) ) return; } if( *msg == '\"' ) { msg[strlen( msg ) - 1] = 0; msg++; } if( who->s.team == TEAM_SPECTATOR ) { // if speccing, also check for non-team flood if( checkflood ) { if( CheckFlood( who, false ) ) return; } G_ChatMsg( NULL, who, true, "%s", msg ); return; } #ifdef AUTHED_SAY if( sv_mm_enable->integer && who->r.client && who->r.client->mm_session <= 0 ) { // unauthed players are only allowed to chat to public at non play-time // they are allowed to team-chat at any time if( GS_MatchState() == MATCH_STATE_PLAYTIME ) { G_PrintMsg( who, "%s", S_COLOR_YELLOW "You must authenticate to be able to chat with other players during the match.\n"); return; } } #endif Q_strncpyz( current_color, S_COLOR_WHITE, sizeof( current_color ) ); memset( outmsg, 0, sizeof( outmsg ) ); UpdatePoint( who ); for( p = outmsg; *msg && (size_t)( p - outmsg ) < sizeof( outmsg ) - 3; msg++ ) { if( *msg == '%' ) { buf[0] = 0; switch( *++msg ) { case 'l': Say_Team_Location( who, buf, sizeof( buf ), current_color ); break; case 'a': Say_Team_Armor( who, buf, sizeof( buf ), current_color ); break; case 'h': Say_Team_Health( who, buf, sizeof( buf ), current_color ); break; case 'b': Say_Team_Best_Weapons( who, buf, sizeof( buf ), current_color ); break; case 'w': Say_Team_Current_Weapon( who, buf, sizeof( buf ), current_color ); break; case 'x': Say_Team_Point( who, buf, sizeof( buf ), current_color ); break; case 'y': Say_Team_Point_Location( who, buf, sizeof( buf ), current_color ); break; case 'X': Say_Team_Pickup( who, buf, sizeof( buf ), current_color ); break; case 'Y': Say_Team_Pickup_Location( who, buf, sizeof( buf ), current_color ); break; case 'd': Say_Team_Drop( who, buf, sizeof( buf ), current_color ); break; case 'D': Say_Team_Drop_Location( who, buf, sizeof( buf ), current_color ); break; case '%': *p++ = *msg; break; default: // Maybe add a warning here? *p++ = '%'; *p++ = *msg; break; } if( strlen( buf ) + ( p - outmsg ) < sizeof( outmsg ) - 3 ) { Q_strncatz( outmsg, buf, sizeof( outmsg ) ); p += strlen( buf ); } } else if( *msg == '^' ) { *p++ = *msg++; *p++ = *msg; Q_strncpyz( current_color, p-2, sizeof( current_color ) ); } else { *p++ = *msg; } } *p = 0; G_ChatMsg( NULL, who, true, "%s", outmsg ); }
//========================================== // BOT_DMclass_VSAYmessages //========================================== static void BOT_DMclass_VSAYmessages( edict_t *self ) { if( GS_MatchState() != MATCH_STATE_PLAYTIME ) return; if( level.gametype.dummyBots || bot_dummy->integer ) return; if( self->snap.damageteam_given > 25 ) { if( rand() & 1 ) { if( rand() & 1 ) { G_BOTvsay_f( self, "oops", true ); } else { G_BOTvsay_f( self, "sorry", true ); } } return; } if( self->ai->vsay_timeout > level.time ) return; if( GS_MatchDuration() && game.serverTime + 4000 > GS_MatchEndTime() ) { self->ai->vsay_timeout = game.serverTime + ( 1000 + (GS_MatchEndTime() - game.serverTime) ); if( rand() & 1 ) G_BOTvsay_f( self, "goodgame", false ); return; } self->ai->vsay_timeout = level.time + ( ( 8+random()*12 ) * 1000 ); // the more bots, the less vsays to play if( random() > 0.1 + 1.0f / game.numBots ) return; if( GS_TeamBasedGametype() && !GS_InvidualGameType() ) { if( self->health < 20 && random() > 0.3 ) { G_BOTvsay_f( self, "needhealth", true ); return; } if( ( self->s.weapon == 0 || self->s.weapon == 1 ) && random() > 0.7 ) { G_BOTvsay_f( self, "needweapon", true ); return; } if( self->r.client->resp.armor < 10 && random() > 0.8 ) { G_BOTvsay_f( self, "needarmor", true ); return; } } // NOT team based here if( random() > 0.2 ) return; switch( (int)brandom( 1, 8 ) ) { default: break; case 1: G_BOTvsay_f( self, "roger", false ); break; case 2: G_BOTvsay_f( self, "noproblem", false ); break; case 3: G_BOTvsay_f( self, "yeehaa", false ); break; case 4: G_BOTvsay_f( self, "yes", false ); break; case 5: G_BOTvsay_f( self, "no", false ); break; case 6: G_BOTvsay_f( self, "booo", false ); break; case 7: G_BOTvsay_f( self, "attack", false ); break; case 8: G_BOTvsay_f( self, "ok", false ); break; } }
/* * G_SetClientStats */ void G_SetClientStats( edict_t *ent ) { gclient_t *client = ent->r.client; int team, i; if( ent->r.client->resp.chase.active ) // in chasecam it copies the other player stats return; // // layouts // client->ps.stats[STAT_LAYOUTS] = 0; // don't force scoreboard when dead during timeout if( ent->r.client->level.showscores || GS_MatchState() >= MATCH_STATE_POSTMATCH ) client->ps.stats[STAT_LAYOUTS] |= STAT_LAYOUT_SCOREBOARD; if( GS_TeamBasedGametype() && !GS_InvidualGameType() ) client->ps.stats[STAT_LAYOUTS] |= STAT_LAYOUT_TEAMTAB; if( GS_HasChallengers() && ent->r.client->queueTimeStamp ) client->ps.stats[STAT_LAYOUTS] |= STAT_LAYOUT_CHALLENGER; if( GS_MatchState() <= MATCH_STATE_WARMUP && level.ready[PLAYERNUM( ent )] ) client->ps.stats[STAT_LAYOUTS] |= STAT_LAYOUT_READY; if( G_SpawnQueue_GetSystem( ent->s.team ) == SPAWNSYSTEM_INSTANT ) client->ps.stats[STAT_LAYOUTS] |= STAT_LAYOUT_INSTANTRESPAWN; // // team // client->ps.stats[STAT_TEAM] = client->ps.stats[STAT_REALTEAM] = ent->s.team; // // health // if( ent->s.team == TEAM_SPECTATOR ) client->ps.stats[STAT_HEALTH] = STAT_NOTSET; // no health for spectator else client->ps.stats[STAT_HEALTH] = HEALTH_TO_INT( ent->health ); client->r.frags = client->ps.stats[STAT_SCORE]; // // armor // if( GS_Instagib() ) { if( g_instashield->integer ) client->ps.stats[STAT_ARMOR] = ARMOR_TO_INT( 100.0f * ( client->resp.instashieldCharge / INSTA_SHIELD_MAX ) ); else client->ps.stats[STAT_ARMOR] = 0; } else client->ps.stats[STAT_ARMOR] = ARMOR_TO_INT( client->resp.armor ); // // pickup message // if( level.time > client->resp.pickup_msg_time ) { client->ps.stats[STAT_PICKUP_ITEM] = 0; } // // frags // if( ent->s.team == TEAM_SPECTATOR ) { client->ps.stats[STAT_SCORE] = STAT_NOTSET; // no frags for spectators } else { client->ps.stats[STAT_SCORE] = ent->r.client->level.stats.score; } // // Team scores // if( GS_TeamBasedGametype() ) { // team based i = 0; for( team = TEAM_ALPHA; team < GS_MAX_TEAMS; team++ ) { client->ps.stats[STAT_TEAM_ALPHA_SCORE+i] = teamlist[team].stats.score; i++; } // mark the rest as not set for(; team < GS_MAX_TEAMS; team++ ) { client->ps.stats[STAT_TEAM_ALPHA_SCORE+i] = STAT_NOTSET; i++; } } else { // not team based i = 0; for( team = TEAM_ALPHA; team < GS_MAX_TEAMS; team++ ) { client->ps.stats[STAT_TEAM_ALPHA_SCORE+i] = STAT_NOTSET; i++; } } // spawn system client->ps.stats[STAT_NEXT_RESPAWN] = ceil( G_SpawnQueue_NextRespawnTime( client->team ) * 0.001f ); // pointed player client->ps.stats[STAT_POINTED_TEAMPLAYER] = 0; client->ps.stats[STAT_POINTED_PLAYER] = G_FindPointedPlayer( ent ); if( client->ps.stats[STAT_POINTED_PLAYER] && GS_TeamBasedGametype() ) { edict_t *e = &game.edicts[client->ps.stats[STAT_POINTED_PLAYER]]; if( e->s.team == ent->s.team ) { int pointedhealth = HEALTH_TO_INT( e->health ); int pointedarmor = 0; int available_bits = 0; bool mega = false; if( pointedhealth < 0 ) pointedhealth = 0; if( pointedhealth > 100 ) { pointedhealth -= 100; mega = true; if( pointedhealth > 100 ) pointedhealth = 100; } pointedhealth /= 3.2; if( GS_Armor_TagForCount( e->r.client->resp.armor ) ) { pointedarmor = ARMOR_TO_INT( e->r.client->resp.armor ); } if( pointedarmor > 150 ) { pointedarmor = 150; } pointedarmor /= 5; client->ps.stats[STAT_POINTED_TEAMPLAYER] = ( ( pointedhealth &0x1F )|( pointedarmor&0x3F )<<6|( available_bits&0xF )<<12 ); if( mega ) { client->ps.stats[STAT_POINTED_TEAMPLAYER] |= 0x20; } } } // last killer. ignore world and team kills if( client->teamstate.last_killer ) { edict_t *targ = ent, *attacker = client->teamstate.last_killer; client->ps.stats[STAT_LAST_KILLER] = (attacker->r.client && !GS_IsTeamDamage( &targ->s, &attacker->s ) ? ENTNUM( attacker ) : 0); } else { client->ps.stats[STAT_LAST_KILLER] = 0; } }
void G_Say_Team( edict_t *who, char *msg, bool checkflood ) { char outmsg[256]; char buf[256]; char *p; char current_color[3]; if( who->s.team != TEAM_SPECTATOR && ( !GS_TeamBasedGametype() || GS_InvidualGameType() ) ) { Cmd_Say_f( who, false, true ); return; } if( checkflood ) { if( CheckFlood( who, true ) ) return; } if( *msg == '\"' ) { msg[strlen( msg ) - 1] = 0; msg++; } if( who->s.team == TEAM_SPECTATOR ) { // if speccing, also check for non-team flood if( checkflood ) { if( CheckFlood( who, false ) ) return; } G_ChatMsg( NULL, who, true, "%s", msg ); return; } Q_strncpyz( current_color, S_COLOR_WHITE, sizeof( current_color ) ); memset( outmsg, 0, sizeof( outmsg ) ); UpdatePoint( who ); for( p = outmsg; *msg && (size_t)( p - outmsg ) < sizeof( outmsg ) - 3; msg++ ) { if( *msg == '%' ) { buf[0] = 0; switch( *++msg ) { case 'l': Say_Team_Location( who, buf, sizeof( buf ), current_color ); break; case 'a': Say_Team_Armor( who, buf, sizeof( buf ), current_color ); break; case 'h': Say_Team_Health( who, buf, sizeof( buf ), current_color ); break; case 'b': Say_Team_Best_Weapons( who, buf, sizeof( buf ), current_color ); break; case 'w': Say_Team_Current_Weapon( who, buf, sizeof( buf ), current_color ); break; case 'x': Say_Team_Point( who, buf, sizeof( buf ), current_color ); break; case 'y': Say_Team_Point_Location( who, buf, sizeof( buf ), current_color ); break; case 'X': Say_Team_Pickup( who, buf, sizeof( buf ), current_color ); break; case 'Y': Say_Team_Pickup_Location( who, buf, sizeof( buf ), current_color ); break; case 'd': Say_Team_Drop( who, buf, sizeof( buf ), current_color ); break; case 'D': Say_Team_Drop_Location( who, buf, sizeof( buf ), current_color ); break; case '%': *p++ = *msg; break; default: // Maybe add a warning here? *p++ = '%'; *p++ = *msg; break; } if( strlen( buf ) + ( p - outmsg ) < sizeof( outmsg ) - 3 ) { Q_strncatz( outmsg, buf, sizeof( outmsg ) ); p += strlen( buf ); } } else if( *msg == '^' ) { *p++ = *msg++; *p++ = *msg; Q_strncpyz( current_color, p-2, sizeof( current_color ) ); } else { *p++ = *msg; } } *p = 0; G_ChatMsg( NULL, who, true, "%s", outmsg ); }