/* * ============================================================================ * * Zombie:Reloaded * * File: infect.inc * Type: Core * Description: Client infection functions. * * Copyright (C) 2009 Greyscale, Richard Helgeby * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * * ============================================================================ */ /** * @section Explosion flags. */ #define EXP_NODAMAGE 1 #define EXP_REPEATABLE 2 #define EXP_NOFIREBALL 4 #define EXP_NOSMOKE 8 #define EXP_NODECAL 16 #define EXP_NOSPARKS 32 #define EXP_NOSOUND 64 #define EXP_RANDOMORIENTATION 128 #define EXP_NOFIREBALLSMOKE 256 #define EXP_NOPARTICLES 512 #define EXP_NODLIGHTS 1024 #define EXP_NOCLAMPMIN 2048 #define EXP_NOCLAMPMAX 4096 /** * @endsection */ /** * Global variable to store infect timer handle. */ new Handle:tInfect = INVALID_HANDLE; /** * Array for flagging client as zombie. */ new bool:bZombie[MAXPLAYERS + 1]; /** * @section bInfectImmune indexes */ #define INFECT_TYPE_MOTHER 0 #define INFECT_TYPE_NORMAL 1 /** * @endsection */ /** * Array for flagging client to be protected. (See defines above) */ new bool:bInfectImmune[MAXPLAYERS + 1][2]; /** * Map is starting. */ InfectOnMapStart() { // Reset timer handle. tInfect = INVALID_HANDLE; } /** * Loads downloadable content data for infect module. */ InfectLoad() { // Get infection sound. decl String:sound[PLATFORM_MAX_PATH]; GetConVarString(g_hCvarsList[CVAR_INFECT_SOUND], sound, sizeof(sound)); // If infect sound cvar is empty, then stop. if (!sound[0]) { return; } // Prepend sound/ to the path. Format(sound, sizeof(sound), "sound/%s", sound); // Add sound file to downloads table. AddFileToDownloadsTable(sound); } /** * Create commands specific to infect here. */ InfectOnCommandsCreate() { RegAdminCmd("zr_infect", InfectInfectCommand, ADMFLAG_GENERIC, "Infect a client. Usage: zr_infect [respawn - 1/0]"); RegAdminCmd("zr_human", InfectHumanCommand, ADMFLAG_GENERIC, "Turn a client into a human. Usage: zr_human [respawn - 1/0]"); } /** * Client is joining the server. * * @param client The client index. */ InfectClientInit(client) { // Reset infect immunity flags. bInfectImmune[client][INFECT_TYPE_MOTHER] = false; bInfectImmune[client][INFECT_TYPE_NORMAL] = false; } /** * Client is leaving the server. * * @param client The client index. */ InfectOnClientDisconnect(client) { // If client is still connecting, then stop. if (!IsClientInGame(client)) { return; } // If zombie hasn't spawned, then stop. if (!g_bZombieSpawned) { return; } // If client is dead, then stop. if (!IsPlayerAlive(client)) { return; } // Initialize count variables new zombiecount; new humancount; // Count valid clients. ZRCountValidClients(zombiecount, humancount); // If client is a human. if (InfectIsClientHuman(client)) { // If there are other humans (ignoring this human), then stop. if (humancount > 1) { return; } // If there are no more clients in the server, then stop. if (!ZRTeamHasClients(CS_TEAM_T)) { return; } // Manually terminate round. RoundEndTerminateRound(ROUNDEND_DELAY, ZombiesWin); return; } // We know here that player is a zombie. // If there is 1 or less humans, then stop. if (humancount <= 1) { return; } // If there are other zombies (ignoring this zombie), then stop. if (zombiecount - 1) { return; } // Create eligible player list. new Handle:arrayEligibleClients = INVALID_HANDLE; // Create eligible client list, with no mother infect immunities new eligibleclients = ZRCreateEligibleClientList(arrayEligibleClients, true, true, true); // If there are no eligible client's then stop. if (!eligibleclients) { return; } // Get a random valid array index. new randindex = GetRandomInt(0, eligibleclients - 1); // Get the client stored in the random array index. new randclient = GetArrayCell(arrayEligibleClients, randindex); // Infect player. InfectHumanToZombie(randclient); // Tell client they have been randomly been chosen to replace disconnecting zombie. TranslationPrintToChat(randclient, "Infect disconnect"); // Destroy handle. CloseHandle(arrayEligibleClients); } /** * Client is joining a team. * * @param client The client index. * @param team The team index. */ InfectOnClientTeam(client, team) { // If client isn't joining spec, then stop. if (team != CS_TEAM_SPECTATOR) { return; } // Disable zombie flag on client. bZombie[client] = false; } /** * Client is spawning into the game. * * @param client The client index. */ InfectOnClientSpawn(client) { // Disable zombie flag on client. bZombie[client] = false; // Check if client is spawning on the terrorist team. if (ZRIsClientOnTeam(client, CS_TEAM_T)) { if (g_bZombieSpawned) { CS_SwitchTeam(client, CS_TEAM_CT); CS_RespawnPlayer(client); } } } /** * Client has been killed. * * @param client The client index. * @param attacker The attacker index. */ InfectOnClientDeath(client, attacker) { // If attacker isn't valid, then stop. if (!ZRIsClientValid(attacker)) { return; } // If attacker isn't a human, then stop. if (!InfectIsClientHuman(attacker)) { return; } // If client isn't a zombie, then stop. if (!InfectIsClientInfected(client)) { return; } // Add kill bonus to attacker's score. new bonus = ClassGetKillBonus(client); new score = ToolsClientScore(attacker, true, false); ToolsClientScore(attacker, true, true, score + bonus); } /** Client has been hurt. * * @param client The client index. * @param attacker The attacker index. * @param weapon The weapon used. */ InfectOnClientHurt(client, attacker, const String:weapon[]) { // If attacker isn't valid, then stop. if (!ZRIsClientValid(attacker)) { return; } // If client isn't a human, then stop. if (!InfectIsClientHuman(client)) { return; } // Attacker isn't a zombie, then stop. if (!InfectIsClientInfected(attacker)) { return; } // If client has infect immunity, then stop. if (bInfectImmune[client][INFECT_TYPE_NORMAL]) { return; } // If weapon isn't a knife, then stop. if (!StrEqual(weapon, "knife")) { return; } // Infect client. InfectHumanToZombie(client, attacker); } /** * The round is starting. */ InfectOnRoundStart() { // If infect timer is running, then kill it. if (tInfect != INVALID_HANDLE) { // Kill timer. KillTimer(tInfect); // Reset timer handle. tInfect = INVALID_HANDLE; } } /** * The freeze time is ending. */ InfectOnRoundFreezeEnd() { // If infect timer is running, then kill it. if (tInfect != INVALID_HANDLE) { // Kill timer. KillTimer(tInfect); } // Get min and max times. new Float:infectspawntimemin = GetConVarFloat(g_hCvarsList[CVAR_INFECT_SPAWNTIME_MIN]); new Float:infectspawntimemax = GetConVarFloat(g_hCvarsList[CVAR_INFECT_SPAWNTIME_MAX]); // Pick random time between min and max. new Float:randomtime = GetRandomFloat(infectspawntimemin, infectspawntimemax); tInfect = CreateTimer(randomtime, InfectMotherZombie, _, TIMER_FLAG_NO_MAPCHANGE); } /** * The round is ending. */ InfectOnRoundEnd() { // If infect timer is running, then kill it. if (tInfect != INVALID_HANDLE) { // Kill timer. KillTimer(tInfect); // Reset timer handle. tInfect = INVALID_HANDLE; } // x = client index. for (new x = 1; x <= MaxClients; x++) { // If client isn't in-game, then stop. if (!IsClientInGame(x)) { continue; } // Disable zombie flag on client. bZombie[x] = false; } } /** * Timer callback, chooses mother zombies. * * @param timer The timer handle. */ public Action:InfectMotherZombie(Handle:timer) { // Reset timer handle. tInfect = INVALID_HANDLE; // Create eligible player list. new Handle:arrayEligibleClients = INVALID_HANDLE; new eligibleclients = ZRCreateEligibleClientList(arrayEligibleClients, true, true, true); // If there are no eligible client's then stop. if (!eligibleclients) { 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++) { // Stop pruning if there is only 1 player left. if (eligibleclients <= 1) { break; } // Get client stored in array index. client = GetArrayCell(arrayEligibleClients, x); // If client is immune from being a mother zombie, then stop. if (bInfectImmune[client][INFECT_TYPE_MOTHER]) { // Take away immunity. bInfectImmune[client][INFECT_TYPE_MOTHER] = false; // Remove client from array. RemoveFromArray(arrayEligibleClients, x); // Subtract one from count. eligibleclients--; // Backtrack one index, because we deleted it out from under the loop. x--; } } // 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); } /** * Infects a client. Execute events, sets attributes and flags that indicate * that the client is a zombie. * * @param client The client to infect. * @param attacker (Optional) The attacker who did the infect. * @param motherinfect (Optional) Indicates a mother zombie infect. * @param respawnoverride (Optional) Set to true to override respawn cvar. * @param respawn (Optional) Value to override with. */ InfectHumanToZombie(client, attacker = -1, bool:motherinfect = false, bool:respawnoverride = false, bool:respawn = false) { // Mark player as zombie. bZombie[client] = true; // Get a list of all client's weapon indexes. new weapons[WeaponsSlot]; WeaponsGetClientWeapons(client, weapons); // Check if weapons drop is enabled. new bool:weaponsdrop = GetConVarBool(g_hCvarsList[CVAR_INFECT_WEAPONS_DROP]); // Loop through array slots and force drop. // x = weapon slot. for (new x = 0; x < WEAPONS_SLOTS_MAX; x++) { // If weapon is invalid, then stop. if (weapons[x] == -1) { continue; } if (weaponsdrop) { // If this is the knife slot, then stop. if (WeaponsSlot:x == Slot_Melee) { continue; } // Force client to drop weapon. WeaponsForceClientDrop(client, weapons[x]); } else { // Strip weapon. RemovePlayerItem(client, weapons[x]); } } // If client has no knife, give them one. if (GetPlayerWeaponSlot(client, _:Slot_Melee) == -1) { GivePlayerItem(client, "weapon_knife"); } // Check if consecutive infection protection is enabled. new bool:infectconsecutiveblock = GetConVarBool(g_hCvarsList[CVAR_INFECT_CONSECUTIVE_BLOCK]); // Flag player to be immune from being mother zombie twice, if consecutive infect protection is enabled. bInfectImmune[client][INFECT_TYPE_MOTHER] = infectconsecutiveblock ? motherinfect : false; // Apply effects. InfectFireEffects(client); // If attacker is valid, then continue. if (ZRIsClientValid(attacker)) { // Create and send custom player_death event. new Handle:event = CreateEvent("player_death"); if (event != INVALID_HANDLE) { SetEventInt(event, "userid", GetClientUserId(client)); SetEventInt(event, "attacker", GetClientUserId(attacker)); SetEventString(event, "weapon", "zombie_claws_of_death"); FireEvent(event, false); } // Give client's infector a point. new score = ToolsClientScore(attacker, true, false); ToolsClientScore(attacker, true, true, ++score); // Add a death to the zombie's score. new deaths = ToolsClientScore(client, false, false); ToolsClientScore(client, false, true, ++deaths); // Apply infect HP gain. new healthgain = ClassGetHealthInfectGain(attacker); new health = GetClientHealth(attacker); // Set attacker's new health. SetEntityHealth(attacker, health + healthgain); // Forward event to modules. ZHPOnHealthInfectGain(attacker); } // Switch the player to terrorists. // TODO: A solution to stop confusing bots? Respawn and teleport? CS_SwitchTeam(client, CS_TEAM_T); // If respawn is enabled, then teleport mother zombie back to spawnpoint. if (motherinfect) { new bool:zombierespawn = GetConVarBool(g_hCvarsList[CVAR_INFECT_MZOMBIE_RESPAWN]); if(zombierespawn) { ZTeleTeleportClient(client); } } // Check override. else { if (respawnoverride && respawn) { ZTeleTeleportClient(client); } } // Set client as translation target. SetGlobalTransTarget(client); // Print message to client. TranslationPrintToChat(client, "Infect infected"); // Forward event to modules. ClassOnClientInfected(client, motherinfect); RoundEndOnClientInfected(); SEffectsOnClientInfected(client); ZTeleOnClientInfected(client); ZHPOnClientInfected(client); } /** * Turns a zombie back into a human. Execute events, sets attributes and flags that indicate * that the client is a human. * * @param client The client to make human. * @param respawn Teleport client back to spawn if true. * @param protect Start spawn protection on new human. */ InfectZombieToHuman(client, bool:respawn = false, bool:protect = false) { // Mark player as human. bZombie[client] = false; // Switch the player to counter-terrorists. CS_SwitchTeam(client, CS_TEAM_CT); // Set client as translation target. SetGlobalTransTarget(client); // Print message to client. TranslationPrintToChat(client, "Infect human"); // Forward event to modules. ClassReloadPlayer(client); RoundEndOnClientInfected(); ZTeleOnClientInfected(client); // Check if we should respawn the client. if (respawn) { ZTeleTeleportClient(client); } // Check if we should spawn protect the client. if (protect) { SpawnProtectStart(client); } } /** * Creates effects on a newly infected client. * * @param client The client index. */ InfectFireEffects(client) { // Initialize vector variables. new Float:clientloc[3]; new Float:direction[3] = {0.0, 0.0, 0.0}; // Get client's position. GetClientAbsOrigin(client, clientloc); clientloc[2] += 30; // If cvar contains path, then continue. decl String:sound[PLATFORM_MAX_PATH]; GetConVarString(g_hCvarsList[CVAR_INFECT_SOUND], sound, sizeof(sound)); if (sound[0]) { // Emit infect sound from infected client. SEffectsEmitSoundFromClient(client, sound, SNDLEVEL_SCREAMING); } // If energy splash effect is enabled, then continue. new bool:esplash = GetConVarBool(g_hCvarsList[CVAR_INFECT_ESPLASH]); if (esplash) { // Create energy splash effect. VEffectsCreateEnergySplash(clientloc, direction, true); } // Initialize explosion flags variable. new flags; // Set "nofireball" flag if fireball is disabled. new bool:fireball = GetConVarBool(g_hCvarsList[CVAR_INFECT_FIREBALL]); if (!fireball) { flags = flags | EXP_NOFIREBALL; } // Set "nosmoke" flag if smoke is disabled. new bool:smoke = GetConVarBool(g_hCvarsList[CVAR_INFECT_SMOKE]); if (!smoke) { flags = flags | EXP_NOSMOKE; } // Set "nosparks" flag if sparks are disabled. new bool:sparks = GetConVarBool(g_hCvarsList[CVAR_INFECT_SPARKS]); if (!sparks) { flags = flags | EXP_NOSPARKS; } // Create explosion at client's origin. VEffectsCreateExplosion(clientloc, flags); // If shake effect is enabled, then continue. new bool:shake = GetConVarBool(g_hCvarsList[CVAR_INFECT_SHAKE]); if (shake) { // Get shake info. new Float:shakeamp = GetConVarFloat(g_hCvarsList[CVAR_INFECT_SHAKE_AMP]); new Float:shakefrequency = GetConVarFloat(g_hCvarsList[CVAR_INFECT_SHAKE_FREQUENCY]); new Float:shakeduration = GetConVarFloat(g_hCvarsList[CVAR_INFECT_SHAKE_DURATION]); // Shake client's screen. VEffectsShakeClientScreen(client, shakeamp, shakefrequency, shakeduration); } } /** * Returns if a client is infected. * * @param client The client index. * @return True if the client has been infected, false otherwise. */ bool:InfectIsClientInfected(client) { // If client is invalid, then stop. if (!ZRIsClientValid(client)) { return false; } // Return client's zombie flag. return bZombie[client]; } /** * Returns if a client is a human. * * @param client The client index. * @return True if the client is a human, false otherwise. */ bool:InfectIsClientHuman(client) { // If client is invalid, then stop. if (!ZRIsClientValid(client)) { return true; } // Return opposite of client's zombie flag. return !bZombie[client]; } /** * Command callback (zr_infect) * Infects a client. * * @param client The client index. * @param argc Argument count. */ public Action:InfectInfectCommand(client, argc) { // If not enough arguments given, then stop. if (argc < 1) { TranslationReplyToCommand(client, "Infect command infect syntax"); return Plugin_Handled; } decl String:target[MAX_NAME_LENGTH], String:targetname[MAX_NAME_LENGTH]; new targets[MAXPLAYERS], bool:tn_is_ml, result; // Get targetname. GetCmdArg(1, target, sizeof(target)); // Find a target. result = ProcessTargetString(target, client, targets, sizeof(targets), COMMAND_FILTER_ALIVE , targetname, sizeof(targetname), tn_is_ml); // Check if there was a problem finding a client. if (result <= 0) { ZRReplyToTargetError(client, result); return Plugin_Handled; } new bool:zombiespawned = g_bZombieSpawned; // If zombie hasn't spawned, then make targetted player(s) mother zombies. if (!zombiespawned) { // Stop mother infect timer. if (tInfect != INVALID_HANDLE) { KillTimer(tInfect); tInfect = INVALID_HANDLE; } // 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); } // Tell the plugin a mother zombie has spawned. g_bZombieSpawned = true; } // x = Client index. for (new x = 0; x < result; x++) { // Check if client is a human before turning into zombie. if (!InfectIsClientHuman(targets[x])) { // If there was only 1 player targetted, then let admin know the command was unsuccessful. if (result == 1) { // Tell admin command was unsuccessful. TranslationReplyToCommand(client, "Infect command infect unsuccessful", targetname); } continue; } new bool:respawnoverride, bool:respawn; decl String:strRespawn[8]; // Get respawn parameter. GetCmdArg(2, strRespawn, sizeof(strRespawn)); // If parameter exists then cast it into a bool and feed it to infect function. if (strRespawn[0]) { respawnoverride = true; respawn = bool:StringToInt(strRespawn); } // If zombie hasn't spawned, then make targetted player(s) mother zombies. if (!zombiespawned) { // Turn client into a mother zombie. InfectHumanToZombie(targets[x], _, true, respawnoverride, respawn); // If there was only 1 player targetted, then let admin know the outcome of the command. if (result == 1) { TranslationReplyToCommand(client, "Infect command infect mother successful", targetname); } continue; } // Turn client into a zombie. InfectHumanToZombie(targets[x], _, false, respawnoverride, respawn); // If there was only 1 player targetted, then let admin know the outcome of the command. if (result == 1) { TranslationReplyToCommand(client, "Infect command infect successful", targetname); } } return Plugin_Handled; } /** * Command callback (zr_human) * Turns a client into a human. * * @param client The client index. * @param argc Argument count. */ public Action:InfectHumanCommand(client, argc) { // If not enough arguments given, then stop. if (argc < 1) { TranslationReplyToCommand(client, "Infect command human syntax"); return Plugin_Handled; } decl String:target[MAX_NAME_LENGTH], String:targetname[MAX_NAME_LENGTH]; new targets[MAXPLAYERS], bool:tn_is_ml, result; // Get targetname. GetCmdArg(1, target, sizeof(target)); // Find a target. result = ProcessTargetString(target, client, targets, sizeof(targets), COMMAND_FILTER_ALIVE , targetname, sizeof(targetname), tn_is_ml); // Check if there was a problem finding a client. if (result <= 0) { ZRReplyToTargetError(client, result); return Plugin_Handled; } // x = Client index. for (new x = 0; x < result; x++) { // Check if client is a human before turning into zombie. if (InfectIsClientInfected(targets[x])) { new bool:respawn, bool:protect; decl String:strRespawn[8], String:strProtect[8]; // Get respawn&protect parameters GetCmdArg(2, strRespawn, sizeof(strRespawn)); GetCmdArg(3, strProtect, sizeof(strProtect)); // If parameter exists then cast it into a bool and feed it to "humanize" function. respawn = (strRespawn[0]) ? (bool:StringToInt(strRespawn)) : false; protect = (strProtect[0]) ? (bool:StringToInt(strProtect)) : false; // Turn client into a zombie. InfectZombieToHuman(targets[x], respawn, protect); // If there was only 1 player targetted, then let admin know the outcome of the command. if (result == 1) { // Tell admin command was successful. TranslationReplyToCommand(client, "Infect command human successful", targetname); } } else { // If there was only 1 player targetted, then let admin know the command was unsuccessful. if (result == 1) { // Tell admin command was unsuccessful. TranslationReplyToCommand(client, "Infect command human unsuccessful", targetname); } } } return Plugin_Handled; }