/* =========================================================================== Wolfenstein: Enemy Territory GPL Source Code Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company. This file is part of the Wolfenstein: Enemy Territory GPL Source Code (“Wolf ET Source Code”). Wolf ET Source Code is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Wolf ET Source Code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Wolf ET Source Code. If not, see . In addition, the Wolf: ET Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Wolf ET Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. =========================================================================== */ // g_match.c: Match handling // ------------------------- // #include "g_local.h" #include "../../etmain/ui/menudef.h" void G_initMatch( void ) { int i; for ( i = TEAM_AXIS; i <= TEAM_ALLIES; i++ ) { G_teamReset( i, qfalse ); } } // Setting initialization void G_loadMatchGame( void ) { unsigned int i, dwBlueOffset, dwRedOffset; unsigned int aRandomValues[MAX_REINFSEEDS]; char strReinfSeeds[MAX_STRING_CHARS]; if ( server_autoconfig.integer > 0 && ( !( z_serverflags.integer & ZSF_COMP ) || level.newSession ) ) { G_configSet( g_gametype.integer, ( server_autoconfig.integer == 1 ) ); trap_Cvar_Set( "z_serverflags", va( "%d", z_serverflags.integer | ZSF_COMP ) ); } G_Printf( "Setting MOTD...\n" ); trap_SetConfigstring( CS_CUSTMOTD + 0, server_motd0.string ); trap_SetConfigstring( CS_CUSTMOTD + 1, server_motd1.string ); trap_SetConfigstring( CS_CUSTMOTD + 2, server_motd2.string ); trap_SetConfigstring( CS_CUSTMOTD + 3, server_motd3.string ); trap_SetConfigstring( CS_CUSTMOTD + 4, server_motd4.string ); trap_SetConfigstring( CS_CUSTMOTD + 5, server_motd5.string ); // Voting flags G_voteFlags(); // Set version info for demoplayback compatibility // trap_SetConfigstring(CS_OSPVERSION, va("%s", G_BASEVERSION)); // Add more tokens as needed // Set up the random reinforcement seeds for both teams and send to clients dwBlueOffset = rand() % MAX_REINFSEEDS; dwRedOffset = rand() % MAX_REINFSEEDS; strcpy( strReinfSeeds, va( "%d %d", ( dwBlueOffset << REINF_BLUEDELT ) + ( rand() % ( 1 << REINF_BLUEDELT ) ), ( dwRedOffset << REINF_REDDELT ) + ( rand() % ( 1 << REINF_REDDELT ) ) ) ); for ( i = 0; i < MAX_REINFSEEDS; i++ ) { aRandomValues[i] = ( rand() % REINF_RANGE ) * aReinfSeeds[i]; strcat( strReinfSeeds, va( " %d", aRandomValues[i] ) ); } level.dwBlueReinfOffset = 1000 * aRandomValues[dwBlueOffset] / aReinfSeeds[dwBlueOffset]; level.dwRedReinfOffset = 1000 * aRandomValues[dwRedOffset] / aReinfSeeds[dwRedOffset]; trap_SetConfigstring( CS_REINFSEEDS, strReinfSeeds ); } // Simple alias for sure-fire print :) void G_printFull( char *str, gentity_t *ent ) { if ( ent != NULL ) { CP( va( "print \"%s\n\"", str ) ); CP( va( "cp \"%s\n\"", str ) ); } else { AP( va( "print \"%s\n\"", str ) ); AP( va( "cp \"%s\n\"", str ) ); } } // Plays specified sound globally. void G_globalSound( char *sound ) { gentity_t *te = G_TempEntity( level.intermission_origin, EV_GLOBAL_SOUND ); te->s.eventParm = G_SoundIndex( sound ); te->r.svFlags |= SVF_BROADCAST; } void G_delayPrint( gentity_t *dpent ) { int think_next = 0; qboolean fFree = qtrue; switch ( dpent->spawnflags ) { case DP_PAUSEINFO: { if ( level.match_pause > PAUSE_UNPAUSING ) { int cSeconds = match_timeoutlength.integer * 1000 - ( level.time - dpent->timestamp ); if ( cSeconds > 1000 ) { AP( va( "cp \"^3Match resuming in ^1%d^3 seconds!\n\"", cSeconds / 1000 ) ); think_next = level.time + 15000; fFree = qfalse; } else { level.match_pause = PAUSE_UNPAUSING; AP( "print \"^3Match resuming in 10 seconds!\n\"" ); G_globalSound( "sound/osp/prepare.wav" ); G_spawnPrintf( DP_UNPAUSING, level.time + 10, NULL ); } } break; } case DP_UNPAUSING: { if ( level.match_pause == PAUSE_UNPAUSING ) { int cSeconds = 11 * 1000 - ( level.time - dpent->timestamp ); if ( cSeconds > 1000 ) { AP( va( "cp \"^3Match resuming in ^1%d^3 seconds!\n\"", cSeconds / 1000 ) ); think_next = level.time + 1000; fFree = qfalse; } else { level.match_pause = PAUSE_NONE; G_globalSound( "sound/osp/fight.wav" ); G_printFull( "^1FIGHT!", NULL ); trap_SetConfigstring( CS_LEVEL_START_TIME, va( "%i", level.startTime + level.timeDelta ) ); level.server_settings &= ~CV_SVS_PAUSE; trap_SetConfigstring( CS_SERVERTOGGLES, va( "%d", level.server_settings ) ); } } break; } case DP_MVSPAWN: { int i; gentity_t *ent; for ( i = 0; i < level.numConnectedClients; i++ ) { ent = g_entities + level.sortedClients[i]; if ( ent->client->pers.mvReferenceList == 0 ) { continue; } if ( ent->client->sess.sessionTeam != TEAM_SPECTATOR ) { continue; } G_smvRegenerateClients( ent, ent->client->pers.mvReferenceList ); } break; } default: break; } dpent->nextthink = think_next; if ( fFree ) { dpent->think = 0; G_FreeEntity( dpent ); } } static char *pszDPInfo[] = { "DPRINTF_PAUSEINFO", "DPRINTF_UNPAUSING", "DPRINTF_CONNECTINFO", "DPRINTF_MVSPAWN", "DPRINTF_UNK1", "DPRINTF_UNK2", "DPRINTF_UNK3", "DPRINTF_UNK4", "DPRINTF_UNK5" }; void G_spawnPrintf( int print_type, int print_time, gentity_t *owner ) { gentity_t *ent = G_Spawn(); ent->classname = pszDPInfo[print_type]; ent->clipmask = 0; ent->parent = owner; ent->r.svFlags |= SVF_NOCLIENT; ent->s.eFlags |= EF_NODRAW; ent->s.eType = ET_ITEM; ent->spawnflags = print_type; // Tunnel in DP enum ent->timestamp = level.time; // Time entity was created ent->nextthink = print_time; ent->think = G_delayPrint; } // Records accuracy, damage, and kill/death stats. void G_addStats( gentity_t *targ, gentity_t *attacker, int dmg_ref, int mod ) { int dmg, ref; // Keep track of only active player-to-player interactions in a real game if ( !targ || !targ->client || #ifndef DEBUG_STATS g_gamestate.integer != GS_PLAYING || #endif mod == MOD_SWITCHTEAM || ( g_gametype.integer >= GT_WOLF && ( targ->client->ps.pm_flags & PMF_LIMBO ) ) || ( g_gametype.integer < GT_WOLF && ( targ->s.eFlags == EF_DEAD || targ->client->ps.pm_type == PM_DEAD ) ) ) { return; } // Special hack for intentional gibbage if ( targ->health <= 0 && targ->client->ps.pm_type == PM_DEAD ) { if ( mod < MOD_CROSS && attacker && attacker->client ) { int x = attacker->client->sess.aWeaponStats[G_weapStatIndex_MOD( mod )].atts--; if ( x < 1 ) { attacker->client->sess.aWeaponStats[G_weapStatIndex_MOD( mod )].atts = 1; } } return; } // G_Printf("mod: %d, Index: %d, dmg: %d\n", mod, G_weapStatIndex_MOD(mod), dmg_ref); // Suicides only affect the player specifically if ( targ == attacker || !attacker || !attacker->client || mod == MOD_SUICIDE ) { if ( targ->health <= 0 ) { targ->client->sess.suicides++; } #ifdef DEBUG_STATS if ( !attacker || !attacker->client ) #endif return; } // Telefrags only add 100 points.. not 100k!! if ( mod == MOD_TELEFRAG ) { dmg = 100; } else { dmg = dmg_ref;} // Player team stats if ( g_gametype.integer >= GT_WOLF && targ->client->sess.sessionTeam == attacker->client->sess.sessionTeam ) { attacker->client->sess.team_damage += dmg; if ( targ->health <= 0 ) { attacker->client->sess.team_kills++; } #ifndef DEBUG_STATS return; #endif } // General player stats if ( mod != MOD_SYRINGE ) { attacker->client->sess.damage_given += dmg; targ->client->sess.damage_received += dmg; if ( targ->health <= 0 ) { attacker->client->sess.kills++; targ->client->sess.deaths++; } } // Player weapon stats ref = G_weapStatIndex_MOD( mod ); if ( dmg > 0 ) { attacker->client->sess.aWeaponStats[ref].hits++; } if ( targ->health <= 0 ) { attacker->client->sess.aWeaponStats[ref].kills++; targ->client->sess.aWeaponStats[ref].deaths++; } } // Records weapon headshots void G_addStatsHeadShot( gentity_t *attacker, int mod ) { #ifndef DEBUG_STATS if ( g_gamestate.integer != GS_PLAYING ) { return; } #endif if ( !attacker || !attacker->client ) { return; } attacker->client->sess.aWeaponStats[G_weapStatIndex_MOD( mod )].headshots++; } // Ugh, converting enums is my day-time job :) // --> MOD_* to WS_* conversion // // WS_MAX = no equivalent/not used // // FIXME: Remove everything that maps to WS_MAX to save space // static const weap_ws_convert_t aWeapMOD[MOD_NUM_MODS] = { { MOD_UNKNOWN, WS_MAX }, { MOD_MACHINEGUN, WS_MG42 }, { MOD_GRENADE, WS_GRENADE }, { MOD_ROCKET, WS_PANZERFAUST }, { MOD_KNIFE, WS_KNIFE }, { MOD_LUGER, WS_LUGER }, { MOD_COLT, WS_COLT }, { MOD_MP40, WS_MP40 }, { MOD_THOMPSON, WS_THOMPSON }, { MOD_STEN, WS_STEN }, { MOD_GARAND, WS_GARAND }, { MOD_SILENCER, WS_LUGER }, { MOD_FG42, WS_FG42 }, { MOD_FG42SCOPE, WS_FG42 }, { MOD_PANZERFAUST, WS_PANZERFAUST }, { MOD_GRENADE_LAUNCHER, WS_GRENADE }, { MOD_FLAMETHROWER, WS_FLAMETHROWER }, { MOD_GRENADE_PINEAPPLE, WS_GRENADE }, { MOD_CROSS, WS_MAX }, { MOD_AKIMBO_COLT, WS_COLT }, { MOD_AKIMBO_LUGER, WS_LUGER }, { MOD_AKIMBO_SILENCEDCOLT, WS_COLT }, { MOD_AKIMBO_SILENCEDLUGER, WS_LUGER }, { MOD_MAPMORTAR, WS_MORTAR }, { MOD_MAPMORTAR_SPLASH, WS_MORTAR }, { MOD_KICKED, WS_MAX }, { MOD_GRABBER, WS_MAX }, { MOD_DYNAMITE, WS_DYNAMITE }, { MOD_AIRSTRIKE, WS_AIRSTRIKE }, { MOD_SYRINGE, WS_SYRINGE }, { MOD_AMMO, WS_MAX }, { MOD_ARTY, WS_ARTILLERY }, { MOD_WATER, WS_MAX }, { MOD_SLIME, WS_MAX }, { MOD_LAVA, WS_MAX }, { MOD_CRUSH, WS_MAX }, { MOD_TELEFRAG, WS_MAX }, { MOD_FALLING, WS_MAX }, { MOD_SUICIDE, WS_MAX }, { MOD_TARGET_LASER, WS_MAX }, { MOD_TRIGGER_HURT, WS_MAX }, { MOD_EXPLOSIVE, WS_MAX }, { MOD_CARBINE, WS_GARAND }, { MOD_KAR98, WS_K43 }, { MOD_GPG40, WS_GRENADELAUNCHER }, { MOD_M7, WS_GRENADELAUNCHER }, { MOD_LANDMINE, WS_LANDMINE }, { MOD_SATCHEL, WS_SATCHEL }, { MOD_TRIPMINE, WS_LANDMINE }, { MOD_SMOKEBOMB, WS_SMOKE }, // ?? { MOD_SMOKEGRENADE, WS_AIRSTRIKE }, // rain - airstrike tag { MOD_MOBILE_MG42, WS_MG42}, { MOD_SILENCED_COLT, WS_COLT }, // where is silencer? // Gordon: up top^ { MOD_GARAND_SCOPE, WS_GARAND }, { MOD_CRUSH_CONSTRUCTION, WS_MAX }, { MOD_CRUSH_CONSTRUCTIONDEATH, WS_MAX }, { MOD_K43, WS_K43 }, { MOD_K43_SCOPE, WS_K43 }, { MOD_MORTAR, WS_MORTAR }, { MOD_SWAP_PLACES, WS_MAX }, { MOD_SWITCHTEAM, WS_MAX } }; // Get right stats index based on weapon mod unsigned int G_weapStatIndex_MOD( unsigned int iWeaponMOD ) { unsigned int i; for ( i = 0; i < MOD_NUM_MODS; i++ ) if ( iWeaponMOD == aWeapMOD[i].iWeapon ) { return( aWeapMOD[i].iWS ); } return( WS_MAX ); } // Generates weapon stat info for given ent char *G_createStats( gentity_t *refEnt ) { unsigned int i, dwWeaponMask = 0, dwSkillPointMask = 0; char strWeapInfo[MAX_STRING_CHARS] = {0}; char strSkillInfo[MAX_STRING_CHARS] = {0}; if ( !refEnt ) { return( NULL ); } // Add weapon stats as necessary for ( i = WS_KNIFE; i < WS_MAX; i++ ) { if ( refEnt->client->sess.aWeaponStats[i].atts || refEnt->client->sess.aWeaponStats[i].hits || refEnt->client->sess.aWeaponStats[i].deaths ) { dwWeaponMask |= ( 1 << i ); Q_strcat( strWeapInfo, sizeof( strWeapInfo ), va( " %d %d %d %d %d", refEnt->client->sess.aWeaponStats[i].hits, refEnt->client->sess.aWeaponStats[i].atts, refEnt->client->sess.aWeaponStats[i].kills, refEnt->client->sess.aWeaponStats[i].deaths, refEnt->client->sess.aWeaponStats[i].headshots ) ); } } // Additional info Q_strcat( strWeapInfo, sizeof( strWeapInfo ), va( " %d %d %d", refEnt->client->sess.damage_given, refEnt->client->sess.damage_received, refEnt->client->sess.team_damage ) ); // Add skillpoints as necessary for ( i = SK_BATTLE_SENSE; i < SK_NUM_SKILLS; i++ ) { if ( refEnt->client->sess.skillpoints[i] > 0 ) { dwSkillPointMask |= ( 1 << i ); Q_strcat( strSkillInfo, sizeof( strSkillInfo ), va( " %d", (int)refEnt->client->sess.skillpoints[i] ) ); } } return( va( "%d %d %d%s %d%s", refEnt - g_entities, refEnt->client->sess.rounds, dwWeaponMask, strWeapInfo, dwSkillPointMask, strSkillInfo ) ); } // Resets player's current stats void G_deleteStats( int nClient ) { gclient_t *cl = &level.clients[nClient]; cl->sess.damage_given = 0; cl->sess.damage_received = 0; cl->sess.deaths = 0; cl->sess.game_points = 0; cl->sess.rounds = 0; cl->sess.kills = 0; cl->sess.suicides = 0; cl->sess.team_damage = 0; cl->sess.team_kills = 0; memset( &cl->sess.aWeaponStats, 0, sizeof( cl->sess.aWeaponStats ) ); trap_Cvar_Set( va( "wstats%i", nClient ), va( "%d", nClient ) ); } // Parses weapon stat info for given ent // ---> The given string must be space delimited and contain only integers void G_parseStats( char *pszStatsInfo ) { gclient_t *cl; const char *tmp = pszStatsInfo; unsigned int i, dwWeaponMask, dwClientID = atoi( pszStatsInfo ); if ( dwClientID < 0 || dwClientID > MAX_CLIENTS ) { return; } cl = &level.clients[dwClientID]; #define GETVAL( x ) if ( ( tmp = strchr( tmp, ' ' ) ) == NULL ) {return;} x = atoi( ++tmp ); GETVAL( cl->sess.rounds ); GETVAL( dwWeaponMask ); for ( i = WS_KNIFE; i < WS_MAX; i++ ) { if ( dwWeaponMask & ( 1 << i ) ) { GETVAL( cl->sess.aWeaponStats[i].hits ); GETVAL( cl->sess.aWeaponStats[i].atts ); GETVAL( cl->sess.aWeaponStats[i].kills ); GETVAL( cl->sess.aWeaponStats[i].deaths ); GETVAL( cl->sess.aWeaponStats[i].headshots ); } } GETVAL( cl->sess.damage_given ); GETVAL( cl->sess.damage_received ); GETVAL( cl->sess.team_damage ); } // Prints current player match info. // --> FIXME: put the pretty print on the client void G_printMatchInfo( gentity_t *ent ) { int i, j, cnt, eff; int tot_kills, tot_deaths, tot_gp, tot_sui, tot_tk, tot_dg, tot_dr, tot_td; gclient_t *cl; char *ref; char n2[MAX_STRING_CHARS]; cnt = 0; for ( i = TEAM_AXIS; i <= TEAM_ALLIES; i++ ) { if ( !TeamCount( -1, i ) ) { continue; } tot_kills = 0; tot_deaths = 0; tot_sui = 0; tot_tk = 0; tot_dg = 0; tot_dr = 0; tot_td = 0; tot_gp = 0; CP( "sc \"\n^7TEAM Player Kll Dth Sui TK Eff ^3GP^7 ^2DG ^1DR ^6TD ^3Score\n" "^7---------------------------------------------------------------------\n\"" ); for ( j = 0; j < level.numPlayingClients; j++ ) { cl = level.clients + level.sortedClients[j]; if ( cl->pers.connected != CON_CONNECTED || cl->sess.sessionTeam != i ) { continue; } SanitizeString( cl->pers.netname, n2, qfalse ); n2[15] = 0; ref = "^7"; tot_kills += cl->sess.kills; tot_deaths += cl->sess.deaths; tot_sui += cl->sess.suicides; tot_tk += cl->sess.team_kills; tot_dg += cl->sess.damage_given; tot_dr += cl->sess.damage_received; tot_td += cl->sess.team_damage; tot_gp += cl->sess.game_points; eff = ( cl->sess.deaths + cl->sess.kills == 0 ) ? 0 : 100 * cl->sess.kills / ( cl->sess.deaths + cl->sess.kills ); if ( eff < 0 ) { eff = 0; } if ( ent->client == cl || ( ent->client->sess.sessionTeam == TEAM_SPECTATOR && ent->client->sess.spectatorState == SPECTATOR_FOLLOW && ent->client->sess.spectatorClient == level.sortedClients[j] ) ) { ref = "^3"; } cnt++; CP( va( "sc \"%-10s %s%-15s^3%4d%4d%4d%3d%s%4d^3%4d^2%6d^1%6d^6%5d^3%7d\n\"", aTeams[i], ref, n2, cl->sess.kills, cl->sess.deaths, cl->sess.suicides, cl->sess.team_kills, ref, eff, cl->sess.game_points - ( cl->sess.kills * WOLF_FRAG_BONUS ), cl->sess.damage_given, cl->sess.damage_received, cl->sess.team_damage, cl->ps.persistant[PERS_SCORE] ) ); } eff = ( tot_kills + tot_deaths == 0 ) ? 0 : 100 * tot_kills / ( tot_kills + tot_deaths ); if ( eff < 0 ) { eff = 0; } CP( va( "sc \"^7---------------------------------------------------------------------\n" "%-10s ^5%-15s%4d%4d%4d%3d^5%4d^3%4d^2%6d^1%6d^6%5d^3%7d\n\"", aTeams[i], "Totals", tot_kills, tot_deaths, tot_sui, tot_tk, eff, tot_gp - ( tot_kills * WOLF_FRAG_BONUS ), tot_dg, tot_dr, tot_td, tot_gp ) ); } CP( va( "sc \"%s\n\n\" 0", ( ( !cnt ) ? "^3\nNo scores to report." : "" ) ) ); } // Dumps end-of-match info void G_matchInfoDump( unsigned int dwDumpType ) { int i, ref; gentity_t *ent; gclient_t *cl; for ( i = 0; i < level.numConnectedClients; i++ ) { ref = level.sortedClients[i]; ent = &g_entities[ref]; cl = ent->client; if ( cl->pers.connected != CON_CONNECTED ) { continue; } if ( dwDumpType == EOM_WEAPONSTATS ) { // If client wants to write stats to a file, don't auto send this stuff if ( !( cl->pers.clientFlags & CGF_STATSDUMP ) ) { if ( ( cl->pers.autoaction & AA_STATSALL ) || cl->pers.mvCount > 0 ) { G_statsall_cmd( ent, 0, qfalse ); } else if ( cl->sess.sessionTeam != TEAM_SPECTATOR ) { if ( cl->pers.autoaction & AA_STATSTEAM ) { G_statsall_cmd( ent, cl->sess.sessionTeam, qfalse ); // Currently broken.. need to support the overloading of dwCommandID } else { CP( va( "ws %s\n", G_createStats( ent ) ) );} } else if ( cl->sess.spectatorState != SPECTATOR_FREE ) { int pid = cl->sess.spectatorClient; if ( ( cl->pers.autoaction & AA_STATSTEAM ) ) { G_statsall_cmd( ent, level.clients[pid].sess.sessionTeam, qfalse ); // Currently broken.. need to support the overloading of dwCommandID } else { CP( va( "ws %s\n", G_createStats( g_entities + pid ) ) );} } } // Log it if ( cl->sess.sessionTeam != TEAM_SPECTATOR ) { G_LogPrintf( "WeaponStats: %s\n", G_createStats( ent ) ); } } else if ( dwDumpType == EOM_MATCHINFO ) { if ( !( cl->pers.clientFlags & CGF_STATSDUMP ) ) { G_printMatchInfo( ent ); } if ( g_gametype.integer == GT_WOLF_STOPWATCH ) { if ( g_currentRound.integer == 1 ) { // We've already missed the switch CP( va( "print \">>> ^3Clock set to: %d:%02d\n\n\n\"", g_nextTimeLimit.integer, (int)( 60.0 * (float)( g_nextTimeLimit.value - g_nextTimeLimit.integer ) ) ) ); } else { float val = (float)( ( level.timeCurrent - ( level.startTime + level.time - level.intermissiontime ) ) / 60000.0 ); if ( val < g_timelimit.value ) { CP( va( "print \">>> ^3Objective reached at %d:%02d (original: %d:%02d)\n\n\n\"", (int)val, (int)( 60.0 * ( val - (int)val ) ), g_timelimit.integer, (int)( 60.0 * (float)( g_timelimit.value - g_timelimit.integer ) ) ) ); } else { CP( va( "print \">>> ^3Objective NOT reached in time (%d:%02d)\n\n\n\"", g_timelimit.integer, (int)( 60.0 * (float)( g_timelimit.value - g_timelimit.integer ) ) ) ); } } } } } } // Update configstring for vote info int G_checkServerToggle( vmCvar_t *cv ) { int nFlag; if ( cv == &match_mutespecs ) { nFlag = CV_SVS_MUTESPECS; } else if ( cv == &g_friendlyFire ) { nFlag = CV_SVS_FRIENDLYFIRE; } else if ( cv == &g_antilag ) { nFlag = CV_SVS_ANTILAG; } else if ( cv == &g_balancedteams ) { nFlag = CV_SVS_BALANCEDTEAMS; } // special case for 2 bits else if ( cv == &match_warmupDamage ) { if ( cv->integer > 0 ) { level.server_settings &= ~CV_SVS_WARMUPDMG; nFlag = ( cv->integer > 2 ) ? 2 : cv->integer; nFlag = nFlag << 2; } else { nFlag = CV_SVS_WARMUPDMG; } } else if ( cv == &g_nextmap && g_gametype.integer != GT_WOLF_CAMPAIGN ) { if ( *cv->string ) { level.server_settings |= CV_SVS_NEXTMAP; } else { level.server_settings &= ~CV_SVS_NEXTMAP; } return( qtrue ); } else if ( cv == &g_nextcampaign && g_gametype.integer == GT_WOLF_CAMPAIGN ) { if ( *cv->string ) { level.server_settings |= CV_SVS_NEXTMAP; } else { level.server_settings &= ~CV_SVS_NEXTMAP; } return( qtrue ); } else {return( qfalse );} if ( cv->integer > 0 ) { level.server_settings |= nFlag; } else { level.server_settings &= ~nFlag; } return( qtrue ); } // Sends a player's stats to the requesting client. void G_statsPrint( gentity_t *ent, int nType ) { int pid; char *cmd = ( nType == 0 ) ? "ws" : ( ( nType == 1 ) ? "wws" : "gstats" ); // Yes, not the cleanest char arg[MAX_TOKEN_CHARS]; if ( !ent || ( ent->r.svFlags & SVF_BOT ) ) { return; } // If requesting stats for self, its easy. if ( trap_Argc() < 2 ) { if ( ent->client->sess.sessionTeam != TEAM_SPECTATOR ) { CP( va( "%s %s\n", cmd, G_createStats( ent ) ) ); // Specs default to players they are chasing } else if ( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) { CP( va( "%s %s\n", cmd, G_createStats( g_entities + ent->client->sess.spectatorClient ) ) ); } else { CP( "cpm \"Type ^3\\stats ^7 to see stats on an active player.\n\"" ); return; } } else { // Find the player to poll stats. trap_Argv( 1, arg, sizeof( arg ) ); if ( ( pid = ClientNumberFromString( ent, arg ) ) == -1 ) { return; } CP( va( "%s %s\n", cmd, G_createStats( g_entities + pid ) ) ); } } /* // See if the player is allowed to have a panzer qboolean G_allowPanzer(gentity_t *ent) { int i, cPanzers = 0; gclient_t *cl; if(team_maxPanzers.integer < 0) return(qtrue); if(ent->client->sess.latchPlayerType != PC_SOLDIER || ent->client->sess.latchPlayerWeapon != 8) { ent->client->pers.panzerSelectTime = 0; return(qtrue); } if(team_maxPanzers.integer == 0) { if(ent->client->pers.cmd_debounce < level.time) { ent->client->pers.cmd_debounce = level.time + 3000; G_printFull("[lof]^3*** [lon]Panzers are disabled on this server[lof].", ent); } return(qfalse); } for(i=0; iclient) continue; if(cl->sess.sessionTeam != ent->client->sess.sessionTeam) continue; if(cl->sess.latchPlayerType != PC_SOLDIER) continue; // ACTIVE panzers take precedence. Limbo players will fight amongst themselves if(COM_BitCheck(cl->ps.weapons, WP_PANZERFAUST) || cl->pers.panzerDropTime > level.time) { cPanzers++; continue; } // Deal with waiting-to-spawn clients where there is contention on who gets a panzer if((cl->ps.pm_flags & PMF_LIMBO) && ((cl->pers.panzerSelectTime != 0 && ent->client->pers.panzerSelectTime == 0) || (cl->pers.panzerSelectTime > 0 && cl->pers.panzerSelectTime < ent->client->pers.panzerSelectTime))) { cPanzers++; continue; } } if(cPanzers < team_maxPanzers.integer) return(qtrue); if(ent->client->pers.cmd_debounce < level.time) { ent->client->pers.cmd_debounce = level.time + 3000; G_printFull(va("[lof]^3*** [lon]Already[lof %d [lon]panzers in the game[lof].", team_maxPanzers.integer), ent); } return(qfalse); } */ void G_resetRoundState( void ) { if ( g_gametype.integer == GT_WOLF_STOPWATCH ) { trap_Cvar_Set( "g_currentRound", "0" ); } else if ( g_gametype.integer == GT_WOLF_LMS ) { trap_Cvar_Set( "g_currentRound", "0" ); trap_Cvar_Set( "g_lms_currentMatch", "0" ); } } void G_resetModeState( void ) { if ( g_gametype.integer == GT_WOLF_STOPWATCH ) { trap_Cvar_Set( "g_nextTimeLimit", "0" ); } else if ( g_gametype.integer == GT_WOLF_LMS ) { trap_Cvar_Set( "g_axiswins", "0" ); trap_Cvar_Set( "g_alliedwins", "0" ); } }