diff --git a/cstrike/cfg/sourcemod/zombiereloaded/zombiereloaded.cfg b/cstrike/cfg/sourcemod/zombiereloaded/zombiereloaded.cfg index 1ec9af0..a083f5d 100644 --- a/cstrike/cfg/sourcemod/zombiereloaded/zombiereloaded.cfg +++ b/cstrike/cfg/sourcemod/zombiereloaded/zombiereloaded.cfg @@ -289,9 +289,21 @@ zr_hitgroups "1" // General -// Number of mother zombies to infect (when infect timer is up) in proportion to number of humans on the server. -// Default: "5" -zr_infect_mzombie_ratio "5" +// Mother zombie infection mode. ['dynamic' = every n-th zombie (ratio) | 'absolute' = n zombies (ratio) | 'range' = min/max] +// Default: "dynamic" +zr_infect_mzombie_mode "dynamic" + +// Dynamic mode: Infection ratio. Every n-th player is infected. | Absolute mode: Number of zombies to infect (positive ratio), or number of humans to keep (negative ratio). +// Default: "7" +zr_infect_mzombie_ratio "7" + +// Minimum number of mother zombies. Range mode only, cannot be zero. +// Default: "1" +zr_infect_mzombie_min "1" + +// Maximum number of mother zombies. Range mode only, cannot be zero. +// Default: "3" +zr_infect_mzombie_max "3" // Counts down to the first infection of the round. The counter is displayed in the middle of the screen. // Default: "0" diff --git a/src/zr/cvars.inc b/src/zr/cvars.inc index 2005892..5c59824 100644 --- a/src/zr/cvars.inc +++ b/src/zr/cvars.inc @@ -101,7 +101,10 @@ enum CvarsList Handle:CVAR_INFECT_SPAWNTIME_MAX, Handle:CVAR_INFECT_CONSECUTIVE_BLOCK, Handle:CVAR_INFECT_WEAPONS_DROP, + Handle:CVAR_INFECT_MZOMBIE_MODE, Handle:CVAR_INFECT_MZOMBIE_RATIO, + Handle:CVAR_INFECT_MZOMBIE_MIN, + Handle:CVAR_INFECT_MZOMBIE_MAX, Handle:CVAR_INFECT_MZOMBIE_COUNTDOWN, Handle:CVAR_INFECT_MZOMBIE_RESPAWN, Handle:CVAR_INFECT_EXPLOSION, @@ -319,7 +322,10 @@ CvarsCreate() // =========================== // General - g_hCvarsList[CVAR_INFECT_MZOMBIE_RATIO] = CreateConVar("zr_infect_mzombie_ratio", "5", "Number of mother zombies to infect (when infect timer is up) in proportion to number of humans on the server."); + g_hCvarsList[CVAR_INFECT_MZOMBIE_MODE] = CreateConVar("zr_infect_mzombie_mode", "dynamic", "Mother zombie infection mode. ['dynamic' = every n-th zombie (ratio) | 'absolute' = n zombies (ratio) | 'range' = min/max]"); + g_hCvarsList[CVAR_INFECT_MZOMBIE_RATIO] = CreateConVar("zr_infect_mzombie_ratio", "7", "Dynamic mode: Infection ratio. Every n-th player is infected. | Absolute mode: Number of zombies to infect (positive ratio), or number of humans to keep (negative ratio)."); + g_hCvarsList[CVAR_INFECT_MZOMBIE_MIN] = CreateConVar("zr_infect_mzombie_min", "1", "Minimum number of mother zombies. Range mode only, cannot be zero."); + g_hCvarsList[CVAR_INFECT_MZOMBIE_MAX] = CreateConVar("zr_infect_mzombie_max", "3", "Maximum number of mother zombies. Range mode only, cannot be zero."); g_hCvarsList[CVAR_INFECT_MZOMBIE_COUNTDOWN] = CreateConVar("zr_infect_mzombie_countdown", "0", "Counts down to the first infection of the round. Countdown is printed in the middle of the client's screen."); g_hCvarsList[CVAR_INFECT_MZOMBIE_RESPAWN] = CreateConVar("zr_infect_mzombie_respawn", "0", "Teleport mother zombies back to spawn on infect."); g_hCvarsList[CVAR_INFECT_SPAWNTIME_MIN] = CreateConVar("zr_infect_spawntime_min", "30.0", "Minimum time from the start of the round until picking the mother zombie(s)."); diff --git a/src/zr/infect.inc b/src/zr/infect.inc index 64b2b20..f7957f6 100644 --- a/src/zr/infect.inc +++ b/src/zr/infect.inc @@ -78,6 +78,17 @@ new bool:bZombie[MAXPLAYERS + 1]; */ new bool:bInfectImmune[MAXPLAYERS + 1][2]; +/** + * Available mother zombie infection modes. + */ +enum InfectMode +{ + InfectMode_Invalid = -1, /** Invalid mode, used by validators. */ + InfectMode_Dynamic, /** Every n-th player is infected. */ + InfectMode_Absolute, /** Keep n humans (negative n) or infect n zombies. */ + InfectMode_Range /** An absolute number of zombies infected (min to max). */ +} + /** * Map is ending. */ @@ -454,126 +465,178 @@ public Action:InfectMotherZombie(Handle:timer) return; } - // Variable to store client stored in random array index. - new client; - // Prune list of immune clients. - // x = Array index. - // client = client index. - for (new x = 0; x < eligibleclients; x++) + eligibleclients = InfectRemoveImmuneClients(arrayEligibleClients); + + // Move all clients to CT. + InfectMoveAllToCT(); + + new mothercount; + new ratio = GetConVarInt(g_hCvarsList[CVAR_INFECT_MZOMBIE_RATIO]); + new min = GetConVarInt(g_hCvarsList[CVAR_INFECT_MZOMBIE_MIN]); + new max = GetConVarInt(g_hCvarsList[CVAR_INFECT_MZOMBIE_MAX]); + + // Count valid human clients. + new humancount; + ZRCountValidClients(_, humancount, _, true); + + // Get and validate infection mode. This will also log a warning on error. + new InfectMode:mode = InfectGetModeOrFail(); + + // Apply infection mode. + switch (mode) { - // Stop pruning if there is only 1 player left. - if (eligibleclients <= 1) + case InfectMode_Invalid: + { + // Validation failed. Fall back to one mother zombie. + mothercount = 1; + } + case InfectMode_Dynamic: + { + // Dynamic mode. Every n-th player is infected. + + // A ratio of 0 will infect one zombie (to keep backwards compatibility). + if (ratio == 0) + { + mothercount = 1; + } + + // Calculate number of zombies to infect. + mothercount = RoundToNearest(float(humancount) / ratio); + + // Require at least one mother zombie. + if (mothercount == 0) + { + mothercount = 1; + } + } + case InfectMode_Absolute: + { + if (ratio > 0) + { + // Infect n humans. + mothercount = ratio; + } + else + { + // Infect all but n humans. Since ratio already is negative + // just add the numbers. (Zero ratio is catched by validator.) + mothercount = humancount + ratio; + + // Force at least one mother zombie. + if (mothercount == 0) + { + mothercount = 1; + } + } + } + case InfectMode_Range: + { + // Get a random number between the range. + mothercount = Math_GetRandomInt(min, max); + } + } + + // Infect players. + for (new n = 0; n < mothercount; n++) + { + // Recount eligible clients. + eligibleclients = GetArraySize(arrayEligibleClients); + + // Stop if there are no more eligible clients. + if (eligibleclients <= 0) { break; } - // Get client stored in array index. - client = GetArrayCell(arrayEligibleClients, x); + // Get a random array index. + new i = Math_GetRandomInt(0, eligibleclients - 1); - // If client is immune from being a mother zombie, then stop. + // Get the client stored in the random array index. + new client = GetArrayCell(arrayEligibleClients, i); + + // Infect player. + InfectHumanToZombie(client, _, true); + + // Remove player from eligible client list. + RemoveFromArray(arrayEligibleClients, i); + } + + // Mother zombies have been infected. + g_bZombieSpawned = true; + + // Destroy client list. + CloseHandle(arrayEligibleClients); +} + +/** + * Moves all alive clients to the CT team. + */ +InfectMoveAllToCT() +{ + // Move all clients to CT + for (new client = 1; client <= MaxClients; client++) + { + // If client isn't in-game, then stop. + if (!IsClientInGame(client)) + { + continue; + } + + // If client is dead, then stop. + if (!IsPlayerAlive(client)) + { + continue; + } + + // Switch client to CT team. + CS_SwitchTeam(client, CS_TEAM_CT); + } +} + +/** + * Removes immune clients from a client list. If a client is removed, their + * immunity is also removed. + * + * @param clientList List of clients. + * @param keepLastPlayer Don't remove if there's only one player left. + * + * @return Number of clients remaining. + */ +InfectRemoveImmuneClients(Handle:clientList, bool:keepLastPlayer = true) +{ + new len = GetArraySize(clientList); + + // Loop though client list. + for (new i = 0; i < len; i++) + { + // Stop pruning if there is only one player left. + if (keepLastPlayer && len <= 1) + { + break; + } + + // Get client. + new client = GetArrayCell(clientList, i); + + // Check if client is immune from mother zombie infection. if (bInfectImmune[client][INFECT_TYPE_MOTHER]) { // Take away immunity. bInfectImmune[client][INFECT_TYPE_MOTHER] = false; // Remove client from array. - RemoveFromArray(arrayEligibleClients, x); + RemoveFromArray(clientList, i); - // Subtract one from count. - eligibleclients--; + // Update list size. + len--; // Backtrack one index, because we deleted it out from under the loop. - x--; + i--; } } - // Move all clients to CT - for (new x = 1; x <= MaxClients; x++) - { - // If client isn't in-game, then stop. - if (!IsClientInGame(x)) - { - continue; - } - - // If client is dead, then stop. - if (!IsPlayerAlive(x)) - { - continue; - } - - // Switch client to CT team. - CS_SwitchTeam(x, CS_TEAM_CT); - } - - // Variable to store randomly chosen array index. - new randindex; - - // Ratio of mother zombies to humans. - new ratio = GetConVarInt(g_hCvarsList[CVAR_INFECT_MZOMBIE_RATIO]); - - // If ratio is 0 or lower, then pick 1 zombie. - if (ratio <= 0) - { - // Get a random valid array index. - randindex = GetRandomInt(0, eligibleclients - 1); - - // Get the client stored in the random array index. - client = GetArrayCell(arrayEligibleClients, randindex); - - // Infect player. - InfectHumanToZombie(client, _, true); - } - else - { - // Initialize count variables - new zombiecount; - new humancount; - - // Count valid human clients - ZRCountValidClients(zombiecount, humancount, _, true); - - // Calculate mother zombie count. - new mothercount = RoundToNearest(float(humancount) / ratio); - - // If mothercount is 0, then set to 1. - if (!mothercount) - { - mothercount = 1; - } - - // x = current mother zombie count. - for (new x = 0; x < mothercount; x++) - { - // Recount eligible clients. - eligibleclients = GetArraySize(arrayEligibleClients); - - // If there are no more eligible clients, then break loop. - if (!eligibleclients) - { - break; - } - - // Get a random valid array index. - randindex = GetRandomInt(0, eligibleclients - 1); - - // Get the client stored in the random array index. - client = GetArrayCell(arrayEligibleClients, randindex); - - // Infect player. - InfectHumanToZombie(client, _, true); - - // Remove player from eligible zombie list. - RemoveFromArray(arrayEligibleClients, randindex); - } - } - - // Mother zombies have been infected. - g_bZombieSpawned = true; - - // Destroy handle. - CloseHandle(arrayEligibleClients); + return len; } /** @@ -1289,3 +1352,100 @@ public Action:InfectHumanCommand(client, argc) return Plugin_Handled; } + +/** + * Converts a string to an infection mode. + * + * @param mode Mode string to convert. + * + * @return Infection mode or InfectMode_Invalid on error. + */ +InfectMode:InfectStringToMode(const String:mode[]) +{ + if (strlen(mode) == 0) + { + return InfectMode_Invalid; + } + + if (StrEqual(mode, "dynamic", false)) + { + return InfectMode_Dynamic; + } + else if (StrEqual(mode, "absolute", false)) + { + return InfectMode_Absolute; + } + else if (StrEqual(mode, "range", false)) + { + return InfectMode_Range; + } + + return InfectMode_Invalid; +} + +/** + * Gets and validates the infection mode. On error it will log a warning. + * + * @return Infection mode or InfectMode_Invalid on error. + */ +InfectMode:InfectGetModeOrFail() +{ + new String:modeName[16]; + GetConVarString(g_hCvarsList[CVAR_INFECT_MZOMBIE_MODE], modeName, sizeof(modeName)); + + new InfectMode:mode = InfectStringToMode(modeName); + new ratio = GetConVarInt(g_hCvarsList[CVAR_INFECT_MZOMBIE_RATIO]); + new min = GetConVarInt(g_hCvarsList[CVAR_INFECT_MZOMBIE_MIN]); + new max = GetConVarInt(g_hCvarsList[CVAR_INFECT_MZOMBIE_MAX]); + + // Validate. + switch (mode) + { + case InfectMode_Invalid: + { + LogEvent(false, LogType_Error, LOG_CORE_EVENTS, LogModule_Infect, "Config Validation", "Warning: Invalid infection mode (\"%s\"). Falling back to one mother zombie.", modeName); + } + case InfectMode_Dynamic: + { + if (ratio < 0) + { + LogEvent(false, LogType_Error, LOG_CORE_EVENTS, LogModule_Infect, "Config Validation", "Warning: Invalid infection ratio (\"%d\"). Must be zero or positive in dynamic mode. Falling back to one mother zombie.", ratio); + return InfectMode_Invalid; + } + } + case InfectMode_Absolute: + { + if (ratio == 0) + { + LogEvent(false, LogType_Error, LOG_CORE_EVENTS, LogModule_Infect, "Config Validation", "Warning: Invalid infection ratio (\"%d\"). Must be nonzero in absolute mode. Falling back to one mother zombie.", ratio); + return InfectMode_Invalid; + } + } + case InfectMode_Range: + { + new bool:failed = false; + if (min <= 0) + { + LogEvent(false, LogType_Error, LOG_CORE_EVENTS, LogModule_Infect, "Config Validation", "Warning: Invalid infection range (\"%d\"). Cvar zr_infect_mzombie_min must be nonzero and positive. Falling back to one mother zombie.", min); + failed = true; + } + if (max <= 0) + { + LogEvent(false, LogType_Error, LOG_CORE_EVENTS, LogModule_Infect, "Config Validation", "Warning: Invalid infection range (\"%d\"). Cvar zr_infect_mzombie_max must be nonzero and positive. Falling back to one mother zombie.", max); + failed = true; + } + if (min > max || max < min) + { + LogEvent(false, LogType_Error, LOG_CORE_EVENTS, LogModule_Infect, "Config Validation", "Warning: Infection range values are overlapping or reversed. Check zr_infect_mzombie_min and zr_infect_mzombie_min. Falling back to one mother zombie."); + failed = true; + } + + if (failed) + { + return InfectMode_Invalid; + } + } + } + + return mode; +}