Index: src/game/g_spawn.c =================================================================== --- src/game/g_spawn.c (revision 944) +++ src/game/g_spawn.c (working copy) @@ -669,7 +669,7 @@ trap_Cvar_Set( "g_restarted", "0" ); level.warmupTime = 0; } - else if( g_doWarmup.integer ) + else if( g_doWarmup.integer && !g_cheats.integer ) { // Turn it on level.warmupTime = -1; Index: src/game/g_local.h =================================================================== --- src/game/g_local.h (revision 944) +++ src/game/g_local.h (working copy) @@ -294,6 +294,7 @@ typedef struct { team_t sessionTeam; + pTeam_t warmupTeam; // team selected during warmup int spectatorTime; // for determining next-in-line to play spectatorState_t spectatorState; int spectatorClient; // for chasecam and follow mode @@ -378,6 +379,7 @@ clientSession_t sess; qboolean readyToExit; // wishes to leave the intermission + qboolean warmupReady; // ready to start the match qboolean noclip; @@ -721,6 +723,7 @@ IBE_NORMAL, IBE_NOROOM, IBE_PERMISSION, + IBE_WARMUP, IBE_MAXERRORS } itemBuildError_t; Index: src/game/g_combat.c =================================================================== --- src/game/g_combat.c (revision 944) +++ src/game/g_combat.c (working copy) @@ -951,6 +951,10 @@ return; } + // cannot destroy buildables during warmup + if( level.warmupTime && targ->s.eType == ET_BUILDABLE ) + return; + client = targ->client; if( client ) Index: src/game/g_session.c =================================================================== --- src/game/g_session.c (revision 944) +++ src/game/g_session.c (working copy) @@ -46,8 +46,9 @@ const char *s; const char *var; - s = va( "%i %i %i %i %i %i %i %s", + s = va( "%i %i %i %i %i %i %i %i %s", client->sess.sessionTeam, + client->sess.warmupTeam, client->sess.spectatorTime, client->sess.spectatorState, client->sess.spectatorClient, @@ -78,14 +79,16 @@ int teamLeader; int spectatorState; int sessionTeam; + int warmupTeam; var = va( "session%i", client - level.clients ); trap_Cvar_VariableStringBuffer( var, s, sizeof(s) ); // FIXME: should be using BG_ClientListParse() for ignoreList, but // bg_lib.c's sscanf() currently lacks %s - sscanf( s, "%i %i %i %i %i %i %i %x%x", + sscanf( s, "%i %i %i %i %i %i %i %i %x%x", &sessionTeam, + &warmupTeam, &client->sess.spectatorTime, &spectatorState, &client->sess.spectatorClient, @@ -97,6 +100,7 @@ ); // bk001205 - format issues client->sess.sessionTeam = (team_t)sessionTeam; + client->sess.warmupTeam = (pTeam_t)warmupTeam; client->sess.spectatorState = (spectatorState_t)spectatorState; client->sess.teamLeader = (qboolean)teamLeader; } @@ -132,6 +136,7 @@ sess->sessionTeam = TEAM_FREE; } + sess->warmupTeam = PTE_NONE; sess->spectatorState = SPECTATOR_FREE; sess->spectatorTime = level.time; sess->spectatorClient = -1; Index: src/game/g_buildable.c =================================================================== --- src/game/g_buildable.c (revision 944) +++ src/game/g_buildable.c (working copy) @@ -2761,6 +2761,10 @@ playerState_t *ps = &ent->client->ps; int buildPoints; + // cannot build during warmup time + if( level.warmupTime ) + return IBE_WARMUP; + BG_FindBBoxForBuildable( buildable, mins, maxs ); BG_PositionBuildableRelativeToPlayer( ps, mins, maxs, trap_Trace, entity_origin, angles, &tr1 ); Index: src/game/g_main.c =================================================================== --- src/game/g_main.c (revision 944) +++ src/game/g_main.c (working copy) @@ -167,6 +167,7 @@ { &g_warmup, "g_warmup", "20", CVAR_ARCHIVE, 0, qtrue }, { &g_doWarmup, "g_doWarmup", "0", 0, 0, qtrue }, + { &g_logFile, "g_logFile", "games.log", CVAR_ARCHIVE, 0, qfalse }, { &g_logFileSync, "g_logFileSync", "0", CVAR_ARCHIVE, 0, qfalse }, @@ -1939,6 +1940,89 @@ /* +============= +CheckTournament + +Once a frame, check for changes in tournement player state +============= +*/ +void CheckTournament( void ) { + if( level.numPlayingClients == 0 ) + return; + + if( g_cheats.integer ) + return; + + if( level.warmupTime != 0 ) { + if( level.numAlienClients < 1 || level.numHumanClients < 1 ) { + if( level.warmupTime != -1 ) { + G_LogPrintf( "Warmup:\n" ); + level.warmupTime = -1; + trap_SetConfigstring( CS_WARMUP, va("%i", level.warmupTime) ); + } + return; // still waiting for team members + } + + // if the warmup is changed at the console, restart it + if( g_warmup.modificationCount != level.warmupModificationCount ) { + level.warmupModificationCount = g_warmup.modificationCount; + level.warmupTime = -1; + } + + // require all players to ready up + if( g_doWarmup.integer == 2 && level.warmupTime == -1 ) { + int i, ready = 0, readyMask = 0; + gclient_t *cl; + + for( i = 0; i < g_maxclients.integer; i++ ) + { + cl = level.clients + i; + + if( cl->pers.connected != CON_CONNECTED || + cl->pers.teamSelection == PTE_NONE ) + continue; + + if( cl->warmupReady ) + { + ready++; + if( i < 16 ) + readyMask |= 1 << i; + } + } + trap_SetConfigstring( CS_CLIENTS_READY, va( "%d", readyMask ) ); + trap_SetConfigstring( CS_WARMUP, va( "%d", -(level.numPlayingClients - + ready) - 1 ) ); + if( ready < level.numPlayingClients ) + return; + } + + // if all players have arrived, start the countdown + if( level.warmupTime < 0 ) { + level.warmupTime = level.time + g_warmup.integer * 1000; + trap_SetConfigstring( CS_WARMUP, va("%i", level.warmupTime) ); + return; + } + + // if the warmup time has counted down, restart + if( level.time > level.warmupTime ) { + level.warmupTime += 10000; + trap_Cvar_Set( "g_restarted", "1" ); + trap_Cvar_Set( "g_layouts", level.layout ); + trap_SendConsoleCommand( EXEC_APPEND, "map_restart 0\n" ); + level.restarted = qtrue; + return; + } + + // if there is a vote, the countdown does not progress + if( level.voteTime && level.warmupTime > 0 ) { + level.warmupTime += level.time - level.previousTime; + trap_SetConfigstring( CS_WARMUP, va("%i", level.warmupTime) ); + } + } +} + + +/* ================== CheckVote ================== @@ -2275,6 +2359,9 @@ G_CalculateAvgPlayers( ); G_UpdateZaps( msec ); + // see if it is time to do a tournament restart + CheckTournament(); + // see if it is time to end the level CheckExitRules( ); Index: src/game/g_admin.c =================================================================== --- src/game/g_admin.c (revision 944) +++ src/game/g_admin.c (working copy) @@ -49,7 +49,7 @@ }, {"allready", G_admin_allready, "y", - "makes everyone ready in intermission", + "makes everyone ready in intermission or warmup", "" }, @@ -2618,9 +2618,27 @@ int i = 0; gclient_t *cl; + if( level.warmupTime == -1 ) + { + if( level.numAlienClients < 1 || level.numHumanClients < 1 ) + { + ADMP( "^3!allready: ^7not enough players to start the match\n" ); + return qfalse; + } + level.warmupTime = level.time + g_warmup.integer * 1000; + trap_SetConfigstring( CS_WARMUP, va("%i", level.warmupTime) ); + AP( va( "print \"^3!allready:^7 %s^7 says everyone is READY now\n\"", + ( ent ) ? ent->client->pers.netname : "console" ) ); + return qtrue; + } + if( !level.intermissiontime ) { - ADMP( "^3!allready: ^7this command is only valid during intermission\n" ); + if( level.warmupTime > 0 ) + ADMP( "^3!allready: ^7the match is already starting\n" ); + else + ADMP( "^3!allready: ^7this command is only valid during intermission " + "or warmup\n" ); return qfalse; } Index: src/game/g_client.c =================================================================== --- src/game/g_client.c (revision 944) +++ src/game/g_client.c (working copy) @@ -1262,6 +1262,13 @@ // count current clients and rank for scoreboard CalculateRanks( ); G_admin_namelog_update( client, qfalse ); + + // if the match is starting, join the selected team + if ( !level.warmupTime && client->sess.warmupTeam != PTE_NONE ) { + G_ChangeTeam( ent, client->sess.warmupTeam ); + client->sess.warmupTeam = PTE_NONE; + } + return NULL; } Index: src/game/g_cmds.c =================================================================== --- src/game/g_cmds.c (revision 944) +++ src/game/g_cmds.c (working copy) @@ -649,6 +649,11 @@ G_LeaveTeam( ent ); ent->client->pers.teamSelection = newTeam; + // if a team is selected during warmup, we need to preserve the team + // across the next restart + if ( level.warmupTime ) + ent->client->sess.warmupTeam = newTeam; + // under certain circumstances, clients can keep their kills and credits // when switching teams if( G_admin_permission( ent, ADMF_TEAMCHANGEFREE ) || @@ -1545,6 +1550,38 @@ /* +================== +Cmd_Ready_f +================== +*/ +void Cmd_Ready_f( gentity_t *ent, qboolean ready ) +{ + if( ent->client->pers.teamSelection == PTE_NONE ) { + trap_SendServerCommand( ent-g_entities, "print \"Join a team first\n\"" ); + return; + } + + if( level.warmupTime >= 0 ) + { + trap_SendServerCommand( ent-g_entities, + "print \"Match already started\n\"" ); + return; + } + + if( ready == ent->client->warmupReady ) + return; + + if( ready ) + trap_SendServerCommand( -1, va( "print \"%s^7 is ready\n\"", + ent->client->pers.netname ) ); + else + trap_SendServerCommand( -1, va( "print \"%s^7 is NOT ready\n\"", + ent->client->pers.netname ) ); + ent->client->warmupReady = ready; +} + + +/* ================= Cmd_SetViewpos_f ================= @@ -1845,6 +1882,10 @@ trace_t tr; gentity_t *traceEnt; + // cannot deconstruct during warmup + if( level.warmupTime ) + return; + if( ent->client->pers.denyBuild ) { trap_SendServerCommand( ent-g_entities, @@ -3029,6 +3070,10 @@ Cmd_CallVote_f( ent ); else if( Q_stricmp( cmd, "vote" ) == 0 ) Cmd_Vote_f( ent ); + else if( Q_stricmp( cmd, "ready" ) == 0 ) + Cmd_Ready_f( ent, qtrue ); + else if( Q_stricmp( cmd, "notready" ) == 0 ) + Cmd_Ready_f( ent, qfalse ); else if( Q_stricmp( cmd, "callteamvote" ) == 0 ) Cmd_CallTeamVote_f( ent ); else if( Q_stricmp( cmd, "follow" ) == 0 ) Index: src/cgame/cg_draw.c =================================================================== --- src/cgame/cg_draw.c (revision 944) +++ src/cgame/cg_draw.c (working copy) @@ -3039,6 +3039,48 @@ //============================================================================== +/* +================= +CG_DrawWarmup +================= +*/ +static void CG_DrawWarmup( void ) +{ + char readyKey[ 32 ], *s; + int x, y, w, h; + vec4_t white = { 1.0f, 1.0f, 1.0f, 1.0f }; + + if ( !cg.warmup || cg.snap->ps.stats[ STAT_PTEAM ] == PTE_NONE ) + return; + + if ( cg.warmup == -1 ) + s = "Waiting for players to join"; + else if ( cg.warmup < -1 ) { + int readyClients = atoi( CG_ConfigString( CS_CLIENTS_READY ) ); + + if( !( readyClients & ( 1 << cg.snap->ps.clientNum ) ) ) { + Q_strncpyz( readyKey, CG_KeyBinding( "ready" ), sizeof( readyKey ) ); + s = va("%s to ready up", readyKey); + } else { + int notReady = -cg.warmup - 1; + + s = va("Waiting for %d player%s to ready up", notReady, + notReady > 1 ? "s" : "" ); + } + } else { + int timeLeft = ( cg.warmup - cg.time ) / 1000 + 1; + + s = va( "Match starts in %d second%s", timeLeft, + timeLeft != 1 ? "s" : "" ); + } + + w = CG_Text_Width( s, 0.5, 0 ); + h = CG_Text_Height( s, 0.5, 0 ); + x = ( SCREEN_WIDTH - w ) / 2; + y = ( SCREEN_HEIGHT ) * 3/ 4 - h / 2; + CG_Text_Paint( x, y + h, 0.5, white, s, 0, 0, ITEM_TEXTSTYLE_SHADOWEDMORE ); +} + //FIXME: both vote notes are hardcoded, change to ownerdrawn? /* @@ -3322,9 +3364,11 @@ // don't draw center string if scoreboard is up cg.scoreBoardShowing = CG_DrawScoreboard( ); - if( !cg.scoreBoardShowing ) + if( !cg.scoreBoardShowing ) { + CG_DrawWarmup( ); CG_DrawCenterString( ); } +} /* =============== @@ -3520,5 +3564,3 @@ CG_Draw2D( ); } - - Index: src/cgame/cg_consolecmds.c =================================================================== --- src/cgame/cg_consolecmds.c (revision 944) +++ src/cgame/cg_consolecmds.c (working copy) @@ -303,6 +303,8 @@ trap_AddCommand( "vote" ); trap_AddCommand( "callteamvote" ); trap_AddCommand( "teamvote" ); + trap_AddCommand( "ready" ); + trap_AddCommand( "notready" ); trap_AddCommand( "stats" ); trap_AddCommand( "teamtask" ); trap_AddCommand( "class" ); Index: src/cgame/cg_main.c =================================================================== --- src/cgame/cg_main.c (revision 944) +++ src/cgame/cg_main.c (working copy) @@ -1472,7 +1472,7 @@ sp = &cg.scores[ scoreIndex ]; if( ( atoi( CG_ConfigString( CS_CLIENTS_READY ) ) & ( 1 << sp->client ) ) && - cg.intermissionStarted ) + ( cg.intermissionStarted || cg.warmup < -1 ) ) showIcons = qfalse; else if( cg.snap->ps.pm_type == PM_SPECTATOR || cg.snap->ps.pm_flags & PMF_FOLLOW || team == cg.snap->ps.stats[ STAT_PTEAM ] || cg.intermissionStarted ) @@ -1515,7 +1515,7 @@ case 2: if( ( atoi( CG_ConfigString( CS_CLIENTS_READY ) ) & ( 1 << sp->client ) ) && - cg.intermissionStarted ) + ( cg.intermissionStarted || cg.warmup < -1 ) ) return "Ready"; break;