/* * ============================================================================ * * Zombie:Reloaded * * File: roundend.inc * Description: (Core) Handles round end actions. * * ============================================================================ */ /** * @section All round end reasons. */ #define ROUNDEND_TARGET_BOMBED 1 // Target Successfully Bombed! #define ROUNDEND_VIP_ESCAPED 2 // The VIP has escaped! #define ROUNDEND_VIP_ASSASSINATED 3 // VIP has been assassinated! #define ROUNDEND_TERRORISTS_ESCAPED 4 // The terrorists have escaped! #define ROUNDEND_CTS_PREVENTESCAPE 5 // The CT's have prevented most of the terrorists from escaping! #define ROUNDEND_ESCAPING_TERRORISTS_NEUTRALIZED 6 // Escaping terrorists have all been neutralized! #define ROUNDEND_BOMB_DEFUSED 7 // The bomb has been defused! #define ROUNDEND_CTS_WIN 8 // Counter-Terrorists Win! #define ROUNDEND_TERRORISTS_WIN 9 // Terrorists Win! #define ROUNDEND_ROUND_DRAW 10 // Round Draw! #define ROUNDEND_ALL_HOSTAGES_RESCUED 11 // All Hostages have been rescued! #define ROUNDEND_TARGET_SAVED 12 // Target has been saved! #define ROUNDEND_HOSTAGES_NOT_RESCUED 13 // Hostages have not been rescued! #define ROUNDEND_TERRORISTS_NOT_ESCAPED 14 // Terrorists have not escaped! #define ROUNDEND_VIP_NOT_ESCAPED 15 // VIP has not escaped! #define ROUNDEND_GAME_COMMENCING 16 // Game Commencing! /** * @endsection */ /** * Delay between round ending and new round starting. (Normal) */ #define ROUNDEND_DELAY_NORMAL 5.0 /** * Delay between round ending and new round starting. (Short, for ROUNDEND_GAME_COMMENCING) */ #define ROUNDEND_DELAY_SHORT 3.0 /** * Possible round end outcomes. */ enum RoundEndOutcome { HumansWin, /** Humans have killed all zombies. */ ZombiesWin, /** Zombies have infected all humans. */ Draw, /** Round has ended in unexpected way. */ } /** * Global variable to store round win timer handle. */ new Handle:tRoundEnd = INVALID_HANDLE; /** * Global variable to store a convar query cookie */ new QueryCookie:mat_dxlevel; /** * Map is starting. */ RoundEndOnMapStart() { // Reset timer handle. tRoundEnd = INVALID_HANDLE; } /** * Client is joining the server. * * @param client The client index. */ RoundEndClientInit(client) { // Get client's DX level. RoundEndGetClientDXLevel(client); } /** * Client has been killed. */ RoundEndOnClientDeath() { // Terminate the round if the last player was killed. new RoundEndOutcome:outcome; if (RoundEndGetRoundStatus(outcome)) { RoundEndTerminateRound(outcome); } } /** * Client has been infected. */ RoundEndOnClientInfected() { // Terminate the round if the last player was infected. new RoundEndOutcome:outcome; if (RoundEndGetRoundStatus(outcome)) { RoundEndTerminateRound(outcome); } } /** * The round is starting. */ RoundEndOnRoundStart() { // If round end timer is running, then kill it. if (tRoundEnd != INVALID_HANDLE) { // Kill timer. KillTimer(tRoundEnd); // Reset timer handle. tRoundEnd = INVALID_HANDLE; } } /** * The freeze time is ending. */ RoundEndOnRoundFreezeEnd() { // Calculate round length, in seconds. // Get mp_roundtime. (in minutes) new Float:roundtime = GetConVarFloat(FindConVar("mp_roundtime")); // Convert to seconds. roundtime *= 60.0; // Start timer. tRoundEnd = CreateTimer(roundtime, RoundEndTimer, _, TIMER_FLAG_NO_MAPCHANGE); } /** * The round is ending. * * @param reason Reason the round has ended. */ RoundEndOnRoundEnd(reason) { // If round end timer is running, then kill it. if (tRoundEnd != INVALID_HANDLE) { // Kill timer. KillTimer(tRoundEnd); // Reset timer handle. tRoundEnd = INVALID_HANDLE; } // Tell plugin no zombies have been spawned. g_bZombieSpawned = false; // Get outcome of the round. new RoundEndOutcome:outcome = RoundEndReasonToOutcome(reason); // Display the overlay to all clients. RoundEndOverlayStart(ROUNDEND_DELAY_NORMAL, outcome); // Balance teams. RoundEndBalanceTeams(); } /** * Finds DX level of a client. * * @param client The client index. */ RoundEndGetClientDXLevel(client) { // If client is fake (or bot), then stop. if (IsFakeClient(client)) { return; } // Query mat_dxlevel on client. mat_dxlevel = QueryClientConVar(client, "mat_dxlevel", RoundEndQueryClientDXLevel); } /** * Query callback function. * * @param cookie Unique cookie of the query. * @param client The client index. * @param result The result of the query (see console.inc enum ConVarQueryResult) * @param cvarName Name of the cvar. * @param cvarValue Value of the cvar. */ public RoundEndQueryClientDXLevel(QueryCookie:cookie, client, ConVarQueryResult:result, const String:cvarName[], const String:cvarValue[]) { // If query cookie does not match cookie given by mat_dxlevel query, then stop, this isn't our query. if (cookie != mat_dxlevel) { return; } // Reset dxLevel. dxLevel[client] = 0; // If result is any other than ConVarQuery_Okay, then stop. if (result != ConVarQuery_Okay) { return; } // Copy cvar value to dxLevel array. dxLevel[client] = StringToInt(cvarValue); } /** * Convert a round_end reason, to a round winner, or draw. * * @param reason The round_end reason. * @return The winner of the round. (see enum RoundEndOutcome) */ RoundEndOutcome:RoundEndReasonToOutcome(reason) { switch(reason) { // CTs won the round. case ROUNDEND_CTS_WIN: { return HumansWin; } // Ts won the round. case ROUNDEND_TERRORISTS_WIN: { return ZombiesWin; } // Unexpected case. default: { return Draw; } } // Return draw to satisfy compiler. (code will never reach this point.) return Draw; } /** * Timer callback, called when round time reaches 0. * * @param timer The timer handle. */ public Action:RoundEndTimer(Handle:timer) { // Terminate the round with a normal delay, and CTs as the winner. TerminateRound(ROUNDEND_DELAY_NORMAL, ROUNDEND_CTS_WIN); } /** * Checks if the round is over. * * @param outcome Set to the outcome of the round, if round is over. * @return True if the round is over, false otherwise. */ bool:RoundEndGetRoundStatus(&RoundEndOutcome:outcome) { // If zombie hasn't spawned, then stop. if (!g_bZombieSpawned) { // Round isn't over. return false; } // Initialize count variables new zombiecount; new humancount; // Count valid clients. (true to only allow living clients) ZRCountValidClients(zombiecount, humancount); // If there are no clients on either teams, then stop. if (!zombiecount && !humancount) { // Round isn't active. return false; } // If there are clients on both teams, then stop. if (zombiecount && humancount) { // Round isn't over. return false; } // We know here, that either zombiecount or humancount is 0. (not both) // If there are zombies, then zombies won the round. if (zombiecount) { outcome = ZombiesWin; } // If there are no zombies, that means there must be humans, they win the round. else { outcome = HumansWin; } // Round is over. return true; } /** * Ends the round with the given outcome. * * @param outcome The outcome of the round. */ RoundEndTerminateRound(RoundEndOutcome:outcome) { switch(outcome) { // Zombies won. case ZombiesWin: { // Terminate the round with a normal delay, and Ts as the winner. TerminateRound(ROUNDEND_DELAY_NORMAL, ROUNDEND_TERRORISTS_WIN); } // Humans won. case HumansWin: { // Terminate the round with a normal delay, and CTs as the winner. TerminateRound(ROUNDEND_DELAY_NORMAL, ROUNDEND_CTS_WIN); } } } /** * Restarts the game. * * @param delay How long to wait between round end and new round. */ RoundEndRestart(Float:delay) { // Terminate the round. TerminateRound(delay, ROUNDEND_GAME_COMMENCING); } /** * Balances teams * * @param spawn If true, it will respawn player after switching their team. */ RoundEndBalanceTeams() { // Create eligible player list. new Handle:arrayEligibleClients = INVALID_HANDLE; new eligibleclients = ZRCreateEligibleClientList(arrayEligibleClients, true); // If there are no eligible client's then stop. if (!eligibleclients) { return; } new client; // Move all clients to T // x = array index. // client = client index. for (new x = 0; x < eligibleclients; x++) { // Get client stored in array index. client = GetArrayCell(arrayEligibleClients, x); // Switch client to T CS_SwitchTeam(client, CS_TEAM_T); } // Move every other client back to CT // x = array index // client = client index. for (new x = 0; x < eligibleclients; x += 2) { // Get client stored in array index. client = GetArrayCell(arrayEligibleClients, x); // Switch client to CT CS_SwitchTeam(client, CS_TEAM_CT); } // Destroy handle. CloseHandle(arrayEligibleClients); } /** * Displays overlay to client, or prints unsupported message on client's screen. * * @param client The client index. * @param overlay The overlay path. */ RoundEndDisplayClientOverlay(client, const String:overlay[]) { // If dxLevel is 0, then query on client failed, so try again, then stop. if (!dxLevel[client]) { // Query dxlevel cvar. RoundEndGetClientDXLevel(client); return; } // If dxLevel is above or equal to minimum requirement, then display overlay. if (dxLevel[client] >= GENERAL_DXLEVEL_MIN) { ClientCommand(client, "r_screenoverlay \"%s\"", overlay); } // If client doesn't meet minimum requirement, then print unsupported text. else { ZR_PrintCenterText(client, "DX90 not supported", dxLevel[client], GENERAL_DXLEVEL_MIN); } } /** * Displays overlays to clients, depending on the outcome. * * @param time Time to display overlays. * @param outcome The outcome of the round. */ RoundEndOverlayStart(Float:time, RoundEndOutcome:outcome) { // If round end overlays are disabled, then stop. new bool:overlay = GetConVarBool(g_hCvarsList[CVAR_ROUNDEND_OVERLAY]); if (!overlay) { return; } decl String:overlaypath[PLATFORM_MAX_PATH]; switch(outcome) { // Show "zombies win" overlay. case ZombiesWin: { GetConVarString(g_hCvarsList[CVAR_ROUNDEND_OVERLAY_ZOMBIE], overlaypath, sizeof(overlaypath)); } // Show "humans win" overlay. case HumansWin: { GetConVarString(g_hCvarsList[CVAR_ROUNDEND_OVERLAY_HUMAN], overlaypath, sizeof(overlaypath)); } // Show no overlay. default: { strcopy(overlaypath, sizeof(overlaypath), ""); } } // x = client index. for (new x = 1; x <= MaxClients; x++) { // If client isn't in-game, then stop. if (!IsClientInGame(x)) { continue; } RoundEndDisplayClientOverlay(x, overlaypath); } CreateTimer(time, RoundEndOverlayTimer, _, TIMER_FLAG_NO_MAPCHANGE); } RoundEndOverlayStop() { // x = client index. for (new x = 1; x <= MaxClients; x++) { // If client isn't in-game, then stop. if (!IsClientInGame(x)) { continue; } // Removes overlay from client's screen. ClientCommand(x, "r_screenoverlay \"\""); } } /** * Timer callback, stops overlays on all clients. * * @param timer The timer handle. */ public Action:RoundEndOverlayTimer(Handle:timer) { // Stop all overlays. RoundEndOverlayStop(); }