diff --git a/csgo/cfg/sourcemod/zombiereloaded/zombiereloaded.cfg b/csgo/cfg/sourcemod/zombiereloaded/zombiereloaded.cfg index 6daebf4..508ae1c 100644 --- a/csgo/cfg/sourcemod/zombiereloaded/zombiereloaded.cfg +++ b/csgo/cfg/sourcemod/zombiereloaded/zombiereloaded.cfg @@ -325,6 +325,34 @@ zr_infect_mzombie_countdown "0" // Default: "0" zr_infect_mzombie_respawn "0" +// Exclude fake clients (bots, SourceTV) from being picked as mother zombie. Excluded players are still used as a last resort when no one else can be picked. [0 = Include | 1 = Exclude] +// Default: "1" +zr_infect_mzombie_exclude_fakeclients "1" + +// Exclude AFK players (AFKManager idle time of at least zr_infect_mzombie_exclude_afk_time) from being picked as mother zombie. Requires the AFKManager plugin. Excluded players are still used as a last resort when no one else can be picked. [0 = Include | 1 = Exclude] +// Default: "1" +zr_infect_mzombie_exclude_afk "1" + +// Seconds a player has to be idle (according to AFKManager) to count as AFK for mother zombie selection. +// Default: "180" +zr_infect_mzombie_exclude_afk_time "180" + +// Exclude autism bots (IsClientAutismBot from the BotTargeting plugin) from being picked as mother zombie. Requires the BotTargeting plugin. Excluded players are still used as a last resort when no one else can be picked. [0 = Include | 1 = Exclude] +// Default: "1" +zr_infect_mzombie_exclude_autismbots "1" + +// The zr_infect_mzombie_exclude_* cvars only apply while at least this many REAL players are present. +// A REAL player is a player who is: +// - NOT a fake client +// - NOT AFK (AFKManager idle time >= zr_infect_mzombie_exclude_afk_time) +// - NOT an autism bot (BotTargeting) +// - NOT in spectate/unassigned +// - NOT dead +// Example: 5 = while there are fewer than 5 real players, fake clients, AFKs and autism bots can be mother zombie too. +// [0 = Always apply the exclusions] +// Default: "0" +zr_infect_mzombie_exclude_min_real "0" + // Maximum time from the start of the round until picking the mother zombie(s). // Default: "50.0" zr_infect_spawntime_max "50.0" diff --git a/cstrike/cfg/sourcemod/zombiereloaded/zombiereloaded.cfg b/cstrike/cfg/sourcemod/zombiereloaded/zombiereloaded.cfg index 42cd41e..92004be 100644 --- a/cstrike/cfg/sourcemod/zombiereloaded/zombiereloaded.cfg +++ b/cstrike/cfg/sourcemod/zombiereloaded/zombiereloaded.cfg @@ -325,6 +325,34 @@ zr_infect_mzombie_countdown "0" // Default: "0" zr_infect_mzombie_respawn "0" +// Exclude fake clients (bots, SourceTV) from being picked as mother zombie. Excluded players are still used as a last resort when no one else can be picked. [0 = Include | 1 = Exclude] +// Default: "1" +zr_infect_mzombie_exclude_fakeclients "1" + +// Exclude AFK players (AFKManager idle time of at least zr_infect_mzombie_exclude_afk_time) from being picked as mother zombie. Requires the AFKManager plugin. Excluded players are still used as a last resort when no one else can be picked. [0 = Include | 1 = Exclude] +// Default: "1" +zr_infect_mzombie_exclude_afk "1" + +// Seconds a player has to be idle (according to AFKManager) to count as AFK for mother zombie selection. +// Default: "180" +zr_infect_mzombie_exclude_afk_time "180" + +// Exclude autism bots (IsClientAutismBot from the BotTargeting plugin) from being picked as mother zombie. Requires the BotTargeting plugin. Excluded players are still used as a last resort when no one else can be picked. [0 = Include | 1 = Exclude] +// Default: "1" +zr_infect_mzombie_exclude_autismbots "1" + +// The zr_infect_mzombie_exclude_* cvars only apply while at least this many REAL players are present. +// A REAL player is a player who is: +// - NOT a fake client +// - NOT AFK (AFKManager idle time >= zr_infect_mzombie_exclude_afk_time) +// - NOT an autism bot (BotTargeting) +// - NOT in spectate/unassigned +// - NOT dead +// Example: 5 = while there are fewer than 5 real players, fake clients, AFKs and autism bots can be mother zombie too. +// [0 = Always apply the exclusions] +// Default: "0" +zr_infect_mzombie_exclude_min_real "0" + // Maximum time from the start of the round until picking the mother zombie(s). // Default: "50.0" zr_infect_spawntime_max "50.0" diff --git a/env/include/BotTargeting.inc b/env/include/BotTargeting.inc new file mode 100644 index 0000000..924b294 --- /dev/null +++ b/env/include/BotTargeting.inc @@ -0,0 +1,30 @@ +#if defined _BotTargeting_Included + #endinput +#endif +#define _BotTargeting_Included + +/** + * Returns whether the client is one of the BotTargeting "autism bot" SteamIDs. + * + * @param client Client index. Must be in-game and authorized, otherwise a native error is thrown. + * @return True if the client is an autism bot, false otherwise. + */ +native bool IsClientAutismBot(int client); + +public SharedPlugin __pl_BotTargeting = +{ + name = "BotTargeting", + file = "BotTargeting.smx", +#if defined REQUIRE_PLUGIN + required = 1, +#else + required = 0, +#endif +}; + +#if !defined REQUIRE_PLUGIN +public __pl_BotTargeting_SetNTVOptional() +{ + MarkNativeAsOptional("IsClientAutismBot"); +} +#endif diff --git a/src/zr/cvars.inc b/src/zr/cvars.inc index 3b34a4e..bbc4efe 100644 --- a/src/zr/cvars.inc +++ b/src/zr/cvars.inc @@ -116,6 +116,11 @@ enum CvarsList Handle:CVAR_INFECT_MZOMBIE_MAX, Handle:CVAR_INFECT_MZOMBIE_COUNTDOWN, Handle:CVAR_INFECT_MZOMBIE_RESPAWN, + Handle:CVAR_INFECT_MZOMBIE_EXCLUDE_FAKECLIENTS, + Handle:CVAR_INFECT_MZOMBIE_EXCLUDE_AFK, + Handle:CVAR_INFECT_MZOMBIE_EXCLUDE_AFK_TIME, + Handle:CVAR_INFECT_MZOMBIE_EXCLUDE_AUTISMBOTS, + Handle:CVAR_INFECT_MZOMBIE_EXCLUDE_MIN_REAL, Handle:CVAR_INFECT_EXPLOSION, Handle:CVAR_INFECT_FIREBALL, Handle:CVAR_INFECT_SMOKE, @@ -338,6 +343,11 @@ CvarsCreate() 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_MZOMBIE_EXCLUDE_FAKECLIENTS] = CreateConVar("zr_infect_mzombie_exclude_fakeclients", "1", "Exclude fake clients (bots, SourceTV) from being picked as mother zombie. Excluded players are still used as a last resort when no one else can be picked. [0 = Include | 1 = Exclude]"); + g_hCvarsList[CVAR_INFECT_MZOMBIE_EXCLUDE_AFK] = CreateConVar("zr_infect_mzombie_exclude_afk", "1", "Exclude AFK players (AFKManager idle time of at least zr_infect_mzombie_exclude_afk_time) from being picked as mother zombie. Requires the AFKManager plugin. Excluded players are still used as a last resort when no one else can be picked. [0 = Include | 1 = Exclude]"); + g_hCvarsList[CVAR_INFECT_MZOMBIE_EXCLUDE_AFK_TIME] = CreateConVar("zr_infect_mzombie_exclude_afk_time", "180", "Seconds a player has to be idle (according to AFKManager) to count as AFK for mother zombie selection.", _, true, 1.0); + g_hCvarsList[CVAR_INFECT_MZOMBIE_EXCLUDE_AUTISMBOTS] = CreateConVar("zr_infect_mzombie_exclude_autismbots", "1", "Exclude autism bots (IsClientAutismBot from the BotTargeting plugin) from being picked as mother zombie. Requires the BotTargeting plugin. Excluded players are still used as a last resort when no one else can be picked. [0 = Include | 1 = Exclude]"); + g_hCvarsList[CVAR_INFECT_MZOMBIE_EXCLUDE_MIN_REAL] = CreateConVar("zr_infect_mzombie_exclude_min_real", "0", "The zr_infect_mzombie_exclude_* cvars only apply while at least this many REAL players are present. A REAL player is a player who is NOT a fake client, NOT AFK (AFKManager idle time >= zr_infect_mzombie_exclude_afk_time), NOT an autism bot (BotTargeting), NOT in spectate/unassigned and NOT dead. Example: 5 = while there are fewer than 5 real players, fake clients, AFKs and autism bots can be mother zombie too. [0 = Always apply the exclusions]", _, true, 0.0); 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)."); g_hCvarsList[CVAR_INFECT_SPAWNTIME_MAX] = CreateConVar("zr_infect_spawntime_max", "50.0", "Maximum time from the start of the round until picking the mother zombie(s)."); g_hCvarsList[CVAR_INFECT_CONSECUTIVE_BLOCK] = CreateConVar("zr_infect_consecutive_block", "1", "Prevent a player from being chosen as mother zombie two rounds in a row."); diff --git a/src/zr/infect.inc b/src/zr/infect.inc index 4a6721c..9d9cb2a 100644 --- a/src/zr/infect.inc +++ b/src/zr/infect.inc @@ -32,6 +32,7 @@ #tryinclude "AFKManager.inc" #tryinclude "TeamManager.inc" +#include /* Restore old REQUIRE_PLUGIN value if necessary */ #if defined TEMP_REQUIRE_PLUGIN @@ -41,6 +42,7 @@ new bool:g_AFKManagerLoaded = false; new bool:g_TeamManagerLoaded = false; +new bool:g_BotTargetingLoaded = false; /** * @section Explosion flags. @@ -122,6 +124,9 @@ InfectOnAllPluginsLoaded() g_TeamManagerLoaded = LibraryExists("TeamManager"); LogMessage("TeamManager: %s", (g_TeamManagerLoaded ? "loaded" : "not loaded")); #endif + + g_BotTargetingLoaded = LibraryExists("BotTargeting"); + LogMessage("BotTargeting: %s", (g_BotTargetingLoaded ? "loaded" : "not loaded")); } /** @@ -139,6 +144,11 @@ InfectOnLibraryAdded(const String:name[]) // TeamManager loaded. g_TeamManagerLoaded = true; } + else if (StrEqual(name, "BotTargeting")) + { + // BotTargeting loaded. + g_BotTargetingLoaded = true; + } } /** @@ -156,6 +166,200 @@ InfectOnLibraryRemoved(const String:name[]) // TeamManager unloaded. g_TeamManagerLoaded = false; } + else if (StrEqual(name, "BotTargeting")) + { + // BotTargeting unloaded. + g_BotTargetingLoaded = false; + } +} + +/** + * Checks if a (non-fake) client is AFK according to AFKManager. + * + * @param client The client index. Must be in-game and not a fake client. + * @return True if AFKManager is loaded and the client has been idle for + * at least zr_infect_mzombie_exclude_afk_time seconds. + */ +bool:InfectIsClientAFK(client) +{ + #if defined _AFKManager_Included + if (g_AFKManagerLoaded && GetClientIdleTime(client) >= GetConVarInt(g_hCvarsList[CVAR_INFECT_MZOMBIE_EXCLUDE_AFK_TIME])) + { + return true; + } + #endif + + return false; +} + +/** + * Checks if a (non-fake) client is an autism bot according to BotTargeting. + * + * @param client The client index. Must be in-game and not a fake client. + * @return True if BotTargeting is loaded and says the client is an autism bot. + */ +bool:InfectIsClientAutismBot(client) +{ + // The native throws on unauthorized clients, so check that first. + return (g_BotTargetingLoaded && IsClientAuthorized(client) && IsClientAutismBot(client)); +} + +/** + * Checks if a client falls in one of the categories excluded from mother zombie + * by the zr_infect_mzombie_exclude_fakeclients/afk/autismbots cvars. + * + * This only looks at the category cvars, not at zr_infect_mzombie_exclude_min_real. + * + * @param client The client index. Must be in-game. + * @return True if the client is excluded, false otherwise. + */ +bool:InfectIsClientMotherExcluded(client) +{ + // Bots / SourceTV. + if (IsFakeClient(client)) + { + return GetConVarBool(g_hCvarsList[CVAR_INFECT_MZOMBIE_EXCLUDE_FAKECLIENTS]); + } + + // AFK players. + if (GetConVarBool(g_hCvarsList[CVAR_INFECT_MZOMBIE_EXCLUDE_AFK]) && InfectIsClientAFK(client)) + { + return true; + } + + // Autism bots. + if (GetConVarBool(g_hCvarsList[CVAR_INFECT_MZOMBIE_EXCLUDE_AUTISMBOTS]) && InfectIsClientAutismBot(client)) + { + return true; + } + + return false; +} + +/** + * Checks if a client is a "real" player, as used by zr_infect_mzombie_exclude_min_real. + * + * A real player is in-game, on a team (not spectate/unassigned), alive, and is + * not a fake client, not AFK and not an autism bot. This definition is fixed and + * does not depend on the zr_infect_mzombie_exclude_* category cvars. + * + * @param client The client index. + * @return True if the client is a real player, false otherwise. + */ +bool:InfectIsClientRealPlayer(client) +{ + if (!IsClientInGame(client) || !ZRIsClientOnTeam(client) || !IsPlayerAlive(client)) + { + return false; + } + + if (IsFakeClient(client) || InfectIsClientAFK(client) || InfectIsClientAutismBot(client)) + { + return false; + } + + return true; +} + +/** + * Checks if the mother zombie exclusions currently apply, based on + * zr_infect_mzombie_exclude_min_real. + * + * @return True if there are at least zr_infect_mzombie_exclude_min_real real + * players (or the cvar is 0), false otherwise. + */ +bool:InfectMotherExclusionsActive() +{ + new minreal = GetConVarInt(g_hCvarsList[CVAR_INFECT_MZOMBIE_EXCLUDE_MIN_REAL]); + if (minreal <= 0) + { + return true; + } + + new realplayers = 0; + for (new x = 1; x <= MaxClients; x++) + { + if (InfectIsClientRealPlayer(x)) + { + realplayers++; + } + } + + return (realplayers >= minreal); +} + +/** + * Builds the mother zombie candidate lists. + * + * All alive humans on a team that the API allows are split into two lists: + * - arrayEligibleClients: regular candidates. Only these go through the + * consecutive block / round-robin cycle logic. + * - arrayExcludedClients: fake clients, AFKs and autism bots excluded by the + * zr_infect_mzombie_exclude_* cvars. Only used as a last resort when the + * regular candidates can't fill the mother zombie count. + * + * When zr_infect_mzombie_exclude_min_real isn't reached, nobody is excluded. + * + * @param arrayEligibleClients Array to push regular candidates into. + * @param arrayExcludedClients Array to push excluded candidates into. + * @return Number of regular candidates. + */ +InfectCreateMotherCandidateLists(Handle:arrayEligibleClients, Handle:arrayExcludedClients) +{ + new Handle:arrayAllClients = CreateArray(); + new allclients = ZRCreateEligibleClientList(arrayAllClients, true, true, true, true); + + new bool:exclude = InfectMotherExclusionsActive(); + new eligibleclients = 0; + + for (new n = 0; n < allclients; n++) + { + new client = GetArrayCell(arrayAllClients, n); + + if (exclude && InfectIsClientMotherExcluded(client)) + { + PushArrayCell(arrayExcludedClients, client); + } + else + { + PushArrayCell(arrayEligibleClients, client); + eligibleclients++; + } + } + + CloseHandle(arrayAllClients); + return eligibleclients; +} + +/** + * Picks a random client from the list that is still human. + * + * @param arrayClients List of client indexes. + * @return Client index, or -1 if nobody in the list is human. + */ +InfectGetRandomHumanFromList(Handle:arrayClients) +{ + new Handle:arrayHumans = CreateArray(); + new size = GetArraySize(arrayClients); + + for (new n = 0; n < size; n++) + { + new client = GetArrayCell(arrayClients, n); + if (IsClientInGame(client) && InfectIsClientHuman(client)) + { + PushArrayCell(arrayHumans, client); + } + } + + new client = -1; + new humans = GetArraySize(arrayHumans); + if (humans) + { + client = GetArrayCell(arrayHumans, Math_GetRandomInt(0, humans - 1)); + } + + CloseHandle(arrayHumans); + return client; } /** @@ -234,15 +438,10 @@ InfectClientInit(client) if(!IsClientInGame(x)) continue; - /* - #if defined _AFKManager_Included - if(g_AFKManagerLoaded) - { - if(GetClientIdleTime(x) > 3 * 60) - continue; - } - #endif - */ + // Clients that can never be mother zombie are never in the cycle, + // don't let them water down the ratio. + if(InfectIsClientMotherExcluded(x)) + continue; players++; @@ -580,15 +779,18 @@ public Action:InfectMotherZombie(Handle:timer) } #endif - // Create eligible player list. + // Create candidate lists: regular candidates and excluded (fake clients, AFKs, autism bots). new Handle:arrayEligibleClients = CreateArray(); - new eligibleclients = ZRCreateEligibleClientList(arrayEligibleClients, true, true, true, true); + new Handle:arrayExcludedClients = CreateArray(); + new eligibleclients = InfectCreateMotherCandidateLists(arrayEligibleClients, arrayExcludedClients); + new excludedclients = GetArraySize(arrayExcludedClients); - // If there are no eligible client's then stop. - if (!eligibleclients) + // If nobody is alive on a team (everyone dead or in spectate), then stop. + if (!eligibleclients && !excludedclients) { - // Destroy handle. + // Destroy handles. CloseHandle(arrayEligibleClients); + CloseHandle(arrayExcludedClients); return; } @@ -690,6 +892,15 @@ public Action:InfectMotherZombie(Handle:timer) } } + // Split excluded clients too, so the last resort prefers those who weren't mother zombie last round. + new Handle:aExcludedMain = CreateArray(); + new Handle:aExcludedAlt = CreateArray(); + for (new n = 0; n < excludedclients; n++) + { + new client = GetArrayCell(arrayExcludedClients, n); + PushArrayCell(g_bInfectMotherLast[client] ? aExcludedAlt : aExcludedMain, client); + } + // Remove mother zombie last flag from all players. for (int client = 0; client <= MAXPLAYERS; client++) { @@ -701,7 +912,11 @@ public Action:InfectMotherZombie(Handle:timer) // Infect players. new infected = 0; new infected_human_to_zombie_failed = 0; - while (infected < mothercount) + + // Only run the regular selection (and its cycle reset) if there are regular candidates. + // If there are none, all alive players are excluded ones and the cycle of the regular + // players must not be touched. + while (eligibleclients && infected < mothercount) { // Infect one of the main candidates. if (candidatesMain) @@ -751,40 +966,70 @@ public Action:InfectMotherZombie(Handle:timer) } else // We have no candidates at all. time to fuck over random people! { - // Infect player. - if (eligibleclients) + // Pick a random regular candidate that is still human (ignores the consecutive block). + new client = InfectGetRandomHumanFromList(arrayEligibleClients); + if (client == -1) { - new randindex = Math_GetRandomInt(0, eligibleclients - 1); - new client = GetArrayCell(arrayEligibleClients, randindex); - if (InfectHumanToZombie(client, _, true)) - { - infected++; - } - else - { - infected_human_to_zombie_failed++; - if (infected_human_to_zombie_failed > 100) - { - break; - //doing testround on dev server would cause script time execution, hence this was added. - } - } + break; // All regular candidates are zombies already, fall through to the excluded clients. + } + + // Infect player. + if (InfectHumanToZombie(client, _, true)) + { + infected++; } else { - break; //prevent infinite loop. should be very unlikely we end up here. + infected_human_to_zombie_failed++; + if (infected_human_to_zombie_failed > 100) + { + break; + //doing testround on dev server would cause script time execution, hence this was added. + } } } } } + // Last resort: not enough regular candidates, so infect excluded clients (fake clients, AFKs, + // autism bots). A round must not start without mother zombies. + // Prefer those who weren't mother zombie last round. + while (infected < mothercount) + { + new Handle:aExcluded = INVALID_HANDLE; + if (GetArraySize(aExcludedMain)) + { + aExcluded = aExcludedMain; + } + else if (GetArraySize(aExcludedAlt)) + { + aExcluded = aExcludedAlt; + } + else + { + break; // Nobody left. + } + + new i = Math_GetRandomInt(0, GetArraySize(aExcluded) - 1); + new client = GetArrayCell(aExcluded, i); + RemoveFromArray(aExcluded, i); + + if (IsClientInGame(client) && InfectIsClientHuman(client) && InfectHumanToZombie(client, _, true)) + { + infected++; + } + } + // Mother zombies have been infected. g_bZombieSpawned = true; // Destroy client list. CloseHandle(arrayEligibleClients); + CloseHandle(arrayExcludedClients); CloseHandle(aCandidatesMain); CloseHandle(aCandidatesAlt); + CloseHandle(aExcludedMain); + CloseHandle(aExcludedAlt); } /**