diff --git a/src/game/g_active.c b/src/game/g_active.c index 5760d7b..7314c11 100644 --- a/src/game/g_active.c +++ b/src/game/g_active.c @@ -433,7 +433,8 @@ void SpectatorThink( gentity_t *ent, usercmd_t *ucmd ) G_TouchTriggers( ent ); trap_UnlinkEntity( ent ); - if( ( client->buttons & BUTTON_ATTACK ) && !( client->oldbuttons & BUTTON_ATTACK ) ) + if( ( client->buttons & BUTTON_ATTACK ) && !( client->oldbuttons & BUTTON_ATTACK ) && + !level.mapRotationVoteTime ) { //if waiting in a queue remove from the queue if( client->ps.pm_flags & PMF_QUEUED ) @@ -475,6 +476,14 @@ void SpectatorThink( gentity_t *ent, usercmd_t *ucmd ) if( ( client->buttons & BUTTON_USE_HOLDABLE ) && !( client->oldbuttons & BUTTON_USE_HOLDABLE ) ) G_ToggleFollow( ent ); + + if( level.mapRotationVoteTime ) + { + if ( ( client->buttons & BUTTON_ATTACK ) && !( client->oldbuttons & BUTTON_ATTACK ) ) + G_IntermissionMapVoteCommand( ent, qtrue, qfalse ); + if ( ( client->buttons & BUTTON_ATTACK2 ) && !( client->oldbuttons & BUTTON_ATTACK2 ) ) + G_IntermissionMapVoteCommand( ent, qfalse, qfalse ); + } } @@ -1534,6 +1543,12 @@ void ClientThink_real( gentity_t *ent ) // if( level.intermissiontime ) { + if( level.mapRotationVoteTime ) + { + SpectatorThink( ent, ucmd ); + return; + } + ClientIntermissionThink( client ); return; } diff --git a/src/game/g_buildable.c b/src/game/g_buildable.c index ab22ddb..3192688 100644 --- a/src/game/g_buildable.c +++ b/src/game/g_buildable.c @@ -3125,6 +3125,10 @@ itemBuildError_t G_CanBuild( gentity_t *ent, buildable_t buildable, int distance } level.markedBuildables[ level.numBuildablesForRemoval++ ] = tent; } + if( tent->r.contents != MASK_PLAYERSOLID && tent->s.eType != ET_BUILDABLE && ( normal[ 2 ] >= minNormal || ( invert && normal[ 2 ] <= -minNormal ) ) ) + { + reason = IBE_NONE; + } } if( reason != IBE_NONE ) { diff --git a/src/game/g_cmds.c b/src/game/g_cmds.c index 651eb4d..7987848 100644 --- a/src/game/g_cmds.c +++ b/src/game/g_cmds.c @@ -1957,6 +1957,27 @@ void Cmd_CallVote_f( gentity_t *ent ) sizeof( level.voteDisplayString ), "Change to layout '%s'", arg2 ); level.votePercentToPass = g_layoutVotePercent.integer; } + else if( !Q_stricmp( arg1, "nextmap" ) ) + { + if( G_MapExists( g_nextMap.string ) ) + { + trap_SendServerCommand( ent - g_entities, va( "print \"callvote: " + "the next map is already set to '%s^7'\n\"", g_nextMap.string ) ); + return; + } + + if( !trap_FS_FOpenFile( va( "maps/%s.bsp", arg2 ), NULL, FS_READ ) ) + { + trap_SendServerCommand( ent - g_entities, va( "print \"callvote: " + "'maps/%s^7.bsp' could not be found on the server\n\"", arg2 ) ); + return; + } + Com_sprintf( level.voteString, sizeof( level.voteString ), + "set g_nextMap %s", arg2 ); + Com_sprintf( level.voteDisplayString, + sizeof( level.voteDisplayString ), "Next map '%s^7'", arg2 ); + level.votePercentToPass = g_mapVotesPercent.integer; + } else if( !Q_stricmp( arg1, "draw" ) ) { Com_sprintf( level.voteString, sizeof( level.voteString ), "evacuation" ); @@ -2030,7 +2051,7 @@ void Cmd_CallVote_f( gentity_t *ent ) { trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string\n\"" ); trap_SendServerCommand( ent-g_entities, "print \"Valid vote commands are: " - "map, map_restart, layout, draw, extend, kick, mute, unmute, poll, and sudden_death\n" ); + "map, map_restart, nextmap, layout, draw, extend, kick, mute, unmute, poll, and sudden_death\n" ); return; } @@ -2080,6 +2101,18 @@ void Cmd_Vote_f( gentity_t *ent ) { char msg[ 64 ]; + if ( level.intermissiontime || level.paused ) + { + if( level.mapRotationVoteTime ) + { + trap_Argv( 1, msg, sizeof( msg ) ); + if( msg[ 0 ] == 'y' || msg[ 1 ] == 'Y' || msg[ 1 ] == '1' ) + G_IntermissionMapVoteCommand( ent, qfalse, qtrue ); + } + + return; + } + if( !level.voteTime ) { if( ent->client->pers.teamSelection != PTE_NONE ) @@ -4409,6 +4442,12 @@ G_ToggleFollow */ void G_ToggleFollow( gentity_t *ent ) { + if( level.mapRotationVoteTime ) + { + G_IntermissionMapVoteCommand( ent, qfalse, qtrue ); + return; + } + if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) G_StopFollowing( ent ); else if( ent->client->sess.spectatorState == SPECTATOR_FREE ) @@ -5014,7 +5053,7 @@ static void Cmd_Ignore_f( gentity_t *ent ) commands_t cmds[ ] = { // normal commands { "team", 0, Cmd_Team_f }, - { "vote", 0, Cmd_Vote_f }, + { "vote", CMD_INTERMISSION, Cmd_Vote_f }, { "ignore", 0, Cmd_Ignore_f }, { "unignore", 0, Cmd_Ignore_f }, diff --git a/src/game/g_local.h b/src/game/g_local.h index 2c73ea3..13d3072 100644 --- a/src/game/g_local.h +++ b/src/game/g_local.h @@ -759,6 +759,7 @@ typedef struct char layout[ MAX_QPATH ]; int extend_vote_count; + int mapRotationVoteTime; int lastMsgTime; @@ -1150,7 +1151,7 @@ void G_WriteSessionData( void ); #define MAX_MAP_ROTATIONS 16 #define MAX_MAP_ROTATION_MAPS 64 #define MAX_MAP_COMMANDS 16 -#define MAX_MAP_ROTATION_CONDS 4 +#define MAX_MAP_ROTATION_CONDS 8 #define NOT_ROTATING -1 @@ -1159,7 +1160,8 @@ typedef enum MCV_ERR, MCV_RANDOM, MCV_NUMCLIENTS, - MCV_LASTWIN + MCV_LASTWIN, + MCV_VOTE } mapConditionVariable_t; typedef enum @@ -1216,6 +1218,7 @@ typedef struct mapRotations_s int numRotations; } mapRotations_t; +qboolean G_MapExists( char *name ); void G_PrintRotations( void ); qboolean G_AdvanceMapRotation( void ); qboolean G_StartMapRotation( char *name, qboolean changeMap ); @@ -1224,6 +1227,12 @@ qboolean G_MapRotationActive( void ); void G_InitMapRotations( void ); qboolean G_GetRandomMap(char *name, int size); +qboolean G_CheckMapRotationVote( void ); +qboolean G_IntermissionMapVoteWinner( void ); +void G_IntermissionMapVoteMessage( gentity_t *ent ); +void G_IntermissionMapVoteMessageAll( void ); +void G_IntermissionMapVoteCommand( gentity_t *ent, qboolean next, qboolean choose ); + // // g_ptr.c // @@ -1301,6 +1310,7 @@ extern vmCvar_t g_extendVotesPercent; extern vmCvar_t g_extendVotesTime; extern vmCvar_t g_extendVotesCount; extern vmCvar_t g_mapVotesPercent; +extern vmCvar_t g_mapRotationVote; extern vmCvar_t g_layoutVotePercent; extern vmCvar_t g_designateVotes; extern vmCvar_t g_admitDefeatVotePercent; @@ -1345,6 +1355,7 @@ extern vmCvar_t g_deconDead; extern vmCvar_t g_debugMapRotation; extern vmCvar_t g_currentMapRotation; extern vmCvar_t g_currentMap; +extern vmCvar_t g_nextMap; extern vmCvar_t g_initialMapRotation; extern vmCvar_t g_chatTeamPrefix; extern vmCvar_t g_floodMaxDemerits; diff --git a/src/game/g_main.c b/src/game/g_main.c index 9344bfc..a15e95e 100644 --- a/src/game/g_main.c +++ b/src/game/g_main.c @@ -91,6 +91,7 @@ vmCvar_t g_extendVotesPercent; vmCvar_t g_extendVotesTime; vmCvar_t g_extendVotesCount; vmCvar_t g_mapVotesPercent; +vmCvar_t g_mapRotationVote; vmCvar_t g_layoutVotePercent; vmCvar_t g_designateVotes; vmCvar_t g_admitDefeatVotePercent; @@ -138,6 +139,7 @@ vmCvar_t g_deconDead; vmCvar_t g_debugMapRotation; vmCvar_t g_currentMapRotation; vmCvar_t g_currentMap; +vmCvar_t g_nextMap; vmCvar_t g_initialMapRotation; vmCvar_t g_shove; @@ -362,6 +364,7 @@ static cvarTable_t gameCvarTable[ ] = { &g_extendVotesTime, "g_extendVotesTime", "10", CVAR_ARCHIVE, 0, qfalse }, { &g_extendVotesCount, "g_extendVotesCount", "3", CVAR_ARCHIVE, 0, qfalse }, { &g_mapVotesPercent, "g_mapVotesPercent", "50", CVAR_ARCHIVE, 0, qfalse }, + { &g_mapRotationVote, "g_mapRotationVote", "15", CVAR_ARCHIVE, 0, qfalse }, { &g_layoutVotePercent, "g_layoutVotePercent", "50", CVAR_ARCHIVE, 0, qfalse }, { &g_designateVotes, "g_designateVotes", "0", CVAR_ARCHIVE, 0, qfalse }, { &g_admitDefeatVotePercent, "g_admitDefeatVotePercent", "50", CVAR_ARCHIVE, 0, qfalse }, @@ -412,6 +415,7 @@ static cvarTable_t gameCvarTable[ ] = { &g_debugMapRotation, "g_debugMapRotation", "0", 0, 0, qfalse }, { &g_currentMapRotation, "g_currentMapRotation", "-1", 0, 0, qfalse }, // -1 = NOT_ROTATING { &g_currentMap, "g_currentMap", "0", 0, 0, qfalse }, + { &g_nextMap, "g_nextMap", "", CVAR_ARCHIVE | CVAR_SERVERINFO, 0, qtrue }, { &g_initialMapRotation, "g_initialMapRotation", "", CVAR_ARCHIVE, 0, qfalse }, { &g_shove, "g_shove", "15", CVAR_ARCHIVE, 0, qfalse }, { &g_mapConfigs, "g_mapConfigs", "", CVAR_ARCHIVE, 0, qfalse }, @@ -1703,7 +1707,7 @@ void CalculateRanks( void ) CheckExitRules( ); // if we are at the intermission, send the new info to everyone - if( level.intermissiontime ) + if( level.intermissiontime && !level.mapRotationVoteTime ) SendScoreboardMessageToAllClients( ); } @@ -1843,6 +1847,32 @@ void BeginIntermission( void ) SendScoreboardMessageToAllClients( ); } +void BeginMapRotationVote( void ) +{ + gentity_t *ent; + int length; + int i; + + if( level.mapRotationVoteTime ) + return; + + length = g_mapRotationVote.integer; + if( length > 60 ) + length = 60; + level.mapRotationVoteTime = level.time + ( length * 1000 ); + + for( i = 0; i < level.maxclients; i++ ) + { + ent = g_entities + i; + + if( !ent->inuse ) + continue; + + ent->client->ps.pm_type = PM_SPECTATOR; + ent->client->sess.sessionTeam = TEAM_SPECTATOR; + ent->client->sess.spectatorState = SPECTATOR_LOCKED; + } +} /* ============= @@ -1859,6 +1889,19 @@ void ExitLevel( void ) buildHistory_t *tmp, *mark; + if( level.mapRotationVoteTime ) + { + if( level.time < level.mapRotationVoteTime && + !G_IntermissionMapVoteWinner( ) ) + return; + } + else if( g_mapRotationVote.integer > 0 && + G_CheckMapRotationVote() ) + { + BeginMapRotationVote( ); + return; + } + while( ( tmp = level.buildHistory ) ) { level.buildHistory = level.buildHistory->next; @@ -1870,11 +1913,15 @@ void ExitLevel( void ) } - if( G_MapRotationActive( ) ) + if ( G_MapExists( g_nextMap.string ) ) + trap_SendConsoleCommand( EXEC_APPEND, va("!map %s\n", g_nextMap.string ) ); + else if( G_MapRotationActive( ) ) G_AdvanceMapRotation( ); else trap_SendConsoleCommand( EXEC_APPEND, "map_restart\n" ); + trap_Cvar_Set( "g_nextMap", "" ); + level.restarted = qtrue; level.changemap = NULL; level.intermissiontime = 0; @@ -2573,6 +2620,12 @@ void CheckMsgTimer( void ) LastTime = level.time; + if( level.mapRotationVoteTime ) + { + G_IntermissionMapVoteMessageAll( ); + return; + } + if( g_welcomeMsgTime.integer && g_welcomeMsg.string[ 0 ] ) { char buffer[ MAX_STRING_CHARS ]; diff --git a/src/game/g_maprotation.c b/src/game/g_maprotation.c index 8cfd04b..fc6fad7 100644 --- a/src/game/g_maprotation.c +++ b/src/game/g_maprotation.c @@ -27,6 +27,21 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA static mapRotations_t mapRotations; + +static qboolean G_GetVotedMap( char *name, int size, int rotation, int map ); + +/* + * =============== + * G_MapExists + * + * Check if a map exists + * =============== + * */ +qboolean G_MapExists( char *name ) +{ + return trap_FS_FOpenFile( va( "maps/%s.bsp", name ), NULL, FS_READ ); +} + /* =============== G_ParseCommandSection @@ -237,6 +252,68 @@ static qboolean G_ParseMapRotation( mapRotation_t *mr, char **text_p ) continue; } + else if( !Q_stricmp( token, "*VOTE*" ) ) + { + if( mr->numMaps == MAX_MAP_ROTATION_MAPS ) + { + G_Printf( S_COLOR_RED "ERROR: maximum number of maps in one rotation (%d) reached\n", + MAX_MAP_ROTATION_MAPS ); + return qfalse; + } + mre = &mr->maps[ mr->numMaps ]; + Q_strncpyz( mre->name, token, sizeof( mre->name ) ); + + token = COM_Parse( text_p ); + + if( !Q_stricmp( token, "{" ) ) + { + while( 1 ) + { + token = COM_Parse( text_p ); + + if( !token ) + break; + + if( !Q_stricmp( token, "}" ) ) + { + break; + } + else + { + if( mre->numConditions < MAX_MAP_ROTATION_CONDS ) + { + mrc = &mre->conditions[ mre->numConditions ]; + mrc->lhs = MCV_VOTE; + mrc->unconditional = qfalse; + Q_strncpyz( mrc->dest, token, sizeof( mrc->dest ) ); + + mre->numConditions++; + } + else + { + G_Printf( S_COLOR_YELLOW "ERROR: maximum number of maps for one vote (%d) reached\n", + MAX_MAP_ROTATION_CONDS ); + } + } + } + if( !mre->numConditions ) + { + G_Printf( S_COLOR_YELLOW "WARNING: no maps in *VOTE* section\n" ); + } + else + { + mr->numMaps++; + mnSet = qtrue; + } + } + else + { + G_Printf( S_COLOR_RED "ERROR: *VOTE* with no section\n" ); + return qfalse; + } + + continue; + } else if( !Q_stricmp( token, "}" ) ) return qtrue; //reached the end of this map rotation @@ -494,6 +571,9 @@ static void G_IssueMapChange( int rotation ) if (!Q_stricmp(mapName, "*RANDOM*")) G_GetRandomMap(mapName, sizeof(mapName)); + if (!Q_stricmp(mapName, "*VOTE*")) + G_GetVotedMap(mapName, sizeof(mapName), rotation, map ); + if( trap_FS_FOpenFile( va("maps/%s.bsp", mapName), &f, FS_READ ) > 0 ) trap_FS_FCloseFile( f ); else @@ -596,6 +676,10 @@ static qboolean G_EvaluateMapCondition( mapRotationCondition_t *mrc ) return level.lastWin == mrc->lastWin; break; + case MCV_VOTE: + // ignore vote for conditions; + break; + default: case MCV_ERR: G_Printf( S_COLOR_RED "ERROR: malformed map switch condition\n" ); @@ -853,3 +937,258 @@ qboolean G_GetRandomMap(char *name, int size) } return qfalse; } + +static char rotationVoteList[ MAX_MAP_ROTATION_CONDS ][ MAX_QPATH ]; +static int rotationVoteLen = 0; + +static int rotationVoteClientPosition[ MAX_CLIENTS ]; +static int rotationVoteClientSelection[ MAX_CLIENTS ]; + +/* +=============== +G_CheckMapRotationVote +=============== +*/ +qboolean G_CheckMapRotationVote( void ) +{ + mapRotation_t *mr; + mapRotationEntry_t *mre; + mapRotationCondition_t *mrc; + int currentRotation, currentMap, nextMap; + int i; + + rotationVoteLen = 0; + + if( g_mapRotationVote.integer < 1 ) + return qfalse; + + if( ( currentRotation = g_currentMapRotation.integer ) == NOT_ROTATING ) + return qfalse; + + currentMap = G_GetCurrentMap( currentRotation ); + + mr = &mapRotations.rotations[ currentRotation ]; + nextMap = ( currentMap + 1 ) % mr->numMaps; + mre = &mr->maps[ nextMap ]; + + for( i = 0; i < mre->numConditions; i++ ) + { + mrc = &mre->conditions[ i ]; + + if( mrc->lhs == MCV_VOTE ) + { + Q_strncpyz( rotationVoteList[ rotationVoteLen ], mrc->dest, + sizeof( rotationVoteList[ rotationVoteLen ] ) ); + rotationVoteLen++; + if( rotationVoteLen >= MAX_MAP_ROTATION_CONDS ) + break; + } + } + + if( !rotationVoteLen ) + return qfalse; + + for( i = 0; i < MAX_CLIENTS; i++ ) + { + rotationVoteClientPosition[ i ] = 0; + rotationVoteClientSelection[ i ] = -1; + } + + return qtrue; +} + +static int G_GetMapVoteWinner( int *winvotes, int *totalvotes ) +{ + int results[ MAX_MAP_ROTATION_CONDS ]; + int winner = 0; + int wv = 0; + int tv = 0; + int i, n; + + memset( results, 0, sizeof( results )); + for( i = 0; i < MAX_CLIENTS; i++ ) + { + n = rotationVoteClientSelection[ i ]; + if( n >=0 && n < MAX_MAP_ROTATION_CONDS ) + { + results[ n ] += 1; + tv++; + } + } + + for( i = 0; i < MAX_MAP_ROTATION_CONDS; i++ ) + { + if( results[ i ] > winner ) + { + winner = i; + wv = results[ i ]; + } + } + + if( winvotes != NULL ) + *winvotes = wv; + if( totalvotes != NULL ) + *totalvotes = tv; + + return winner; +} + +qboolean G_IntermissionMapVoteWinner( void ) +{ + int winner, winvotes, totalvotes; + int nonvotes; + + winner = G_GetMapVoteWinner( &winvotes, &totalvotes ); + if( winvotes * 2 > level.numConnectedClients ) + return qtrue; + nonvotes = level.numConnectedClients - totalvotes; + if( nonvotes < 0 ) + nonvotes = 0; + if( winvotes > nonvotes + ( totalvotes - winvotes ) ) + return qtrue; + + return qfalse; +} + +static qboolean G_GetVotedMap( char *name, int size, int rotation, int map ) +{ + mapRotation_t *mr; + mapRotationEntry_t *mre; + mapRotationCondition_t *mrc; + int i, n; + int winner; + qboolean found = qfalse; + + if( !rotationVoteLen ) + return qfalse; + + winner = G_GetMapVoteWinner( NULL, NULL ); + + mr = &mapRotations.rotations[ rotation ]; + mre = &mr->maps[ map ]; + + n = 0; + for( i = 0; i < mre->numConditions && n < rotationVoteLen; i++ ) + { + mrc = &mre->conditions[ i ]; + + if( mrc->lhs == MCV_VOTE ) + { + if( n == winner ) + { + Q_strncpyz( name, mrc->dest, size ); + found = qtrue; + break; + } + n++; + } + } + + rotationVoteLen = 0; + + return found; +} + +static void G_IntermissionMapVoteMessageReal( gentity_t *ent, int winner, int winvotes, int totalvotes ) +{ + int clientNum; + char string[ MAX_STRING_CHARS ]; + char entry[ MAX_STRING_CHARS ]; + int len = 0; + int index, selection; + int i; + char *color; + + clientNum = ent-g_entities; + + index = rotationVoteClientSelection[ clientNum ]; + selection = rotationVoteClientPosition[ clientNum ]; + + if( winner < 0 || winner >= MAX_MAP_ROTATION_CONDS ) + winner = G_GetMapVoteWinner( &winvotes, &totalvotes ); + + Q_strncpyz( string, "^7Attack = down ^0/^7 Repair = up ^0/^7 F1 = vote\n\n" + "^2Map Vote Menu\n" + "^7+------------------+\n", sizeof( string ) ); + for( i = 0; i < rotationVoteLen; i++ ) + { + if( i == selection ) + color = "^5"; + else if( i == index ) + color = "^1"; + else + color = "^7"; + + Com_sprintf( entry, sizeof( entry ), "^7%s%s%s %s %s^7%s\n", + ( i == index ) ? "^1>>>" : "", + ( i == selection ) ? "^7(" : " ", + color, + rotationVoteList[ i ], + ( i == selection ) ? "^7)" : " ", + ( i == index ) ? "^1<<<" : "" ); + + Q_strcat( string, sizeof( string ), entry ); + len += strlen( entry ); + } + + Com_sprintf( entry, sizeof( entry ), + "\n^7+----------------+\nleader: ^3%s^7 with %d vote%s\nvoters: %d\ntime left: %d", + rotationVoteList[ winner ], + winvotes, + ( winvotes == 1 ) ? "" : "s", + totalvotes, + ( level.mapRotationVoteTime - level.time ) / 1000 ); + Q_strcat( string, sizeof( string ), entry ); + + trap_SendServerCommand( ent-g_entities, va( "cp \"%s\"\n", string ) ); +} + +void G_IntermissionMapVoteMessageAll( void ) +{ + int winner; + int winvotes, totalvotes; + int i; + + winner = G_GetMapVoteWinner( &winvotes, &totalvotes ); + for( i = 0; i < level.maxclients; i++ ) + { + if( level.clients[ i ].pers.connected == CON_CONNECTED ) + G_IntermissionMapVoteMessageReal( g_entities + i, winner, winvotes, totalvotes ); + } +} + +void G_IntermissionMapVoteMessage( gentity_t *ent ) +{ + G_IntermissionMapVoteMessageReal( ent, -1, 0, 0 ); +} + +void G_IntermissionMapVoteCommand( gentity_t *ent, qboolean next, qboolean choose ) +{ + int clientNum; + int n; + + clientNum = ent-g_entities; + + if( choose ) + { + rotationVoteClientSelection[ clientNum ] = rotationVoteClientPosition[ clientNum ]; + } + else + { + n = rotationVoteClientPosition[ clientNum ]; + if( next ) + n++; + else + n--; + + if( n >= rotationVoteLen ) + n = rotationVoteLen - 1; + if( n < 0 ) + n = 0; + + rotationVoteClientPosition[ clientNum ] = n; + } + + G_IntermissionMapVoteMessage( ent ); +} +