/*
===========================================================================
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" );
}
}