sm-zombiereloaded-3/src/zr/immunityhandler.inc

1058 lines
28 KiB
SourcePawn

/*
* ============================================================================
*
* Zombie:Reloaded
*
* File: immunityhandler.inc
* Type: Core module
* Description: Manages infection immunity modes for every player.
*
* Copyright (C) 2009-2013 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 <http://www.gnu.org/licenses/>.
*
* ============================================================================
*/
/**
* Maximum delay of infection.
*/
#define IMMUNITY_MAX_DELAY 300
/**
* Maximum shield duration.
*/
#define IMMUNITY_MAX_SHIELD_TIME 300
/**
* Timers for handling timed immunity actions.
*/
new Handle:PlayerImmunityTimer[MAXPLAYERS + 1] = {INVALID_HANDLE, ...};
/**
* Remaining time of timed immunity actions.
*/
new PlayerImmunityDuration[MAXPLAYERS + 1] = {-1, ...};
/**
* Cached attacker index for delayed infections, if available.
*/
new PlayerImmunityAttacker[MAXPLAYERS + 1] = {0, ...};
/**
* Timestamp of last action. Usage depends on mode (cooldown, etc).
*/
new PlayerImmunityLastUse[MAXPLAYERS + 1] = {0, ...};
/**
* Whether the player has passed a threshold (infect mode).
*/
new bool:PlayerImmunityThresholdPassed[MAXPLAYERS + 1] = {false, ...};
/*____________________________________________________________________________*/
/**
* Console commands are being created.
*/
ImmunityOnCommandsCreate()
{
RegConsoleCmd(SAYHOOKS_KEYWORD_ZSHIELD, Command_DeployShield, "Deploy the shield, if available.");
}
/*____________________________________________________________________________*/
/**
* Client executed the deploy shield command.
*
* @param client Client index.
* @param argc Number of arguments.
*/
public Action:Command_DeployShield(client, argc)
{
// Block console.
if (ZRIsConsole(client))
{
TranslationPrintToServer("Must be player");
return Plugin_Handled;
}
// Attempt to deploy shield.
ImmunityDeployShield(client);
return Plugin_Handled;
}
/*____________________________________________________________________________*/
/**
* Handles immunity when a client is about to be infected. This function may
* delay or block infection according to the immunity mode class settings.
*
* @param client Client that's being infected.
* @param attacker Attacker client (zombie).
*
* @return True if infection will be handled by this module, false if
* infection can be applied instantly.
*/
bool:ImmunityOnClientInfect(client, attacker)
{
// Get immunity mode from client class.
new ImmunityMode:mode = ClassGetImmunityMode(client);
// Check mode.
switch(mode)
{
case Immunity_None:
{
// Instant infection.
return false;
}
case Immunity_Kill:
{
// Block infection. Damage is increased in ImmunityOnClientDamage.
return true;
}
case Immunity_Full:
{
// Full immunity, do nothing.
return true;
}
case Immunity_Infect:
{
return ImmunityInfectModeHandler(client);
}
case Immunity_Delay:
{
return ImmunityDelayModeHandler(client, attacker);
}
case Immunity_Shield:
{
return ImmunityShieldModeHandler(client);
}
}
// Current mode doesn't apply to infection.
return false;
}
/*____________________________________________________________________________*/
/**
* TraceAttack hook.
*
* Returns whether attacker damage should be blocked. If damage is blocked this
* module will handle it.
*
* @param client Client index.
* @param attacker Attacker client, if any.
* @param inflictor The entity index of the inflictor.
* @param damage Damage received by client.
*
* @return True if damage should be blocked, false otherwise.
*/
bool ImmunityOnClientTraceAttack(int client, int &attacker, int &inflictor, float &damage, int &damagetype, int &ammotype, int hitgroup)
{
// Check if there is no attacker (world damage).
if (!ZRIsClientValid(attacker))
{
// Allow damage.
return false;
}
// Get immunity mode from client class.
new ImmunityMode:mode = ClassGetImmunityMode(client);
// Check mode.
switch(mode)
{
case Immunity_Full:
{
// Block damage (implies blocking knock back on zombies).
return true;
}
case Immunity_Infect:
{
// Client must be human.
if (InfectIsClientInfected(client))
{
// Allow damage.
return false;
}
// Check if damage give HP below the infection threshold.
if (ImmunityBelowInfectThreshold(client, damage))
{
PlayerImmunityThresholdPassed[client] = true;
}
else
{
PlayerImmunityThresholdPassed[client] = false;
}
}
case Immunity_Damage:
{
// Client must be zombie.
if (!InfectIsClientInfected(client))
{
// Allow damage.
return false;
}
// Get attacker weapon.
decl String:weapon[32];
weapon[0] = 0;
if (damagetype == DMG_BLAST)
{
// Most likely a HE grenade. GetClientWeapon can't be used if
// the attacker throw grenades. The attacker may switch weapon
// before the grenade explodes.
strcopy(weapon, sizeof(weapon), "hegrenade");
}
else
{
GetClientWeapon(attacker, weapon, sizeof(weapon));
}
// Since damage is blocked, trigger knock back hurt event manually.
KnockbackOnClientHurt(client, attacker, inflictor, weapon, hitgroup, damage, damagetype, -1, 0);
// Block damage from attacker.
return true;
}
case Immunity_Shield:
{
// Client must be human.
if (InfectIsClientInfected(client))
{
// Allow damage.
return false;
}
// Check if shield is active.
if (PlayerImmunityTimer[client] != INVALID_HANDLE)
{
// Block damage for humans with shield enabled (preventing
// zombies from stabbing them to death).
return true;
}
}
}
// Allow damage.
return false;
}
/*____________________________________________________________________________*/
/**
* TakeDamage hook.
*
* Blocks or modifies damage in certain situations.
*
* @param client Client index.
* @param attacker Attacker client, if any.
* @param damage Damage received by client.
* @param weapon Weapon entity.
*
* @return Plugin_Handled if damage was blocked, Plugin_Changed if
* damage was modified, Plugin_Continue otherwise.
*/
Action:ImmunityOnClientDamage(client, attacker, &Float:damage)
{
// Check if there is no attacker (world damage).
if (!ZRIsClientValid(attacker))
{
// Allow damage.
return Plugin_Continue;
}
// Check if spawn protection is on.
if (g_bInfectImmune[client])
{
// Block damage.
return Plugin_Handled;
}
// Get immunity mode from client class.
new ImmunityMode:mode = ClassGetImmunityMode(client);
switch(mode)
{
case Immunity_Kill:
{
// Client must be human and attacker must be zombie.
if (InfectIsClientInfected(client)
|| !InfectIsClientInfected(attacker))
{
// Don't modify damage.
return Plugin_Continue;
}
// A zombie is attacking a human in kill immunity mode. Increase
// damage so human will be instantly killed. (Using a high damage
// value in case the human class has a lot of HP.)
damage = 60000.0;
// Update score and health gain.
InfectUpdateScore(attacker, client);
// Damage was changed.
return Plugin_Changed;
}
case Immunity_Infect:
{
// Prevent humans with low HP from dying when a zombie is
// attacking, and stab to death is disabled (threshold above zero).
if (ImmunityBelowInfectThreshold(client, damage))
{
// Fake hurt event because it's not triggered when the damage
// was removed (because no one is actually hurt).
InfectOnClientHurt(client, attacker, "knife");
// Block damage to prevent player from dying.
return Plugin_Handled;
}
}
case Immunity_Delay:
{
// Fake hurt event because it's not triggered when the damage
// was removed (because no one is actually hurt). This event must
// still be triggered so that subsequent attacks are registered,
// without dealing any damage.
InfectOnClientHurt(client, attacker, "knife");
// Block damage to prevent player from dying.
return Plugin_Handled;
}
}
// Allow damage.
return Plugin_Continue;
}
/*____________________________________________________________________________*/
/**
* Handles infect mode immunity.
*
* Allow humans to receive damage from zombies until HP is below a certain
* threshold. If the threshold is zero, never infect.
*
* @param client Client that's being infected.
* @param attacker Attacker client (zombie).
*
* @return True if infection will be handled by this module, false if
* infection can be applied instantly.
*/
bool:ImmunityInfectModeHandler(client)
{
// Note: ImmunityOnClientDamage and ImmunityOnClientTraceAttack hook into
// the damage module to prevent humans with low HP from dying when
// they're not supposed to.
new threshold = ClassGetImmunityAmount(client);
// Check if infection is disabled.
if (threshold == 0)
{
// Infection is handled here: blocked.
return true;
}
if (PlayerImmunityThresholdPassed[client])
{
// Client HP below threshold, allow instant infection.
return false;
}
return true;
}
/*____________________________________________________________________________*/
/**
* Handles delayed infections.
*
* @param client Client that's being infected.
*
* @return True if infection will be handled by this module, false if
* infection can be applied instantly.
*/
bool:ImmunityDelayModeHandler(client, attacker)
{
// Check if an infection is in progress
if (PlayerImmunityTimer[client] != INVALID_HANDLE)
{
// Additional attacks while a delayed infection is in progress will
// speedup the infection.
// Get reduction amount for subsequent zombie attack.
new reduction = ClassGetImmunityCooldown(client);
if (reduction > 0)
{
// Reduce duration. Add one because the timer handler itself reduce
// duration by one.
PlayerImmunityDuration[client] -= reduction + 1;
// Note: This feature can be used to trigger an instant infection
// when a human receive a second attack, by setting the
// reduction value high enough.
// Trigger timer event to reduce delay and infect faster.
ImmunityDelayTimerHandler(PlayerImmunityTimer[client], client);
}
// Block infection.
return true;
}
// Start a delayed infection. Initialize duration and attacker.
PlayerImmunityDuration[client] = ClassGetImmunityAmount(client);
PlayerImmunityAttacker[client] = attacker;
// Create repated 1-second timer for handling the countdown.
PlayerImmunityTimer[client] = CreateTimer(1.0, ImmunityDelayTimerHandler, client, TIMER_FLAG_NO_MAPCHANGE | TIMER_REPEAT);
// Block infection.
return true;
}
/*____________________________________________________________________________*/
/**
* Delayed infection timer handler. Handles countdown and infection when time
* is up.
*/
public Action:ImmunityDelayTimerHandler(Handle:timer, any:client)
{
// Verify that client is still connected and alive.
if (!IsClientInGame(client) || !IsPlayerAlive(client))
{
// Client disconnected or died. Abort immunity action.
ImmunityAbortHandler(client);
return Plugin_Stop;
}
// Reduce duration.
PlayerImmunityDuration[client] -= 1;
// Check if time is up.
if (PlayerImmunityDuration[client] <= 0)
{
// Get attacker before cleaning up.
new attacker = PlayerImmunityAttacker[client];
// Time is up. Reset data.
PlayerImmunityDuration[client] = 0;
ImmunityAbortHandler(client);
// Infect client. Give credit to the stored attacker.
InfectHumanToZombie(client, attacker);
return Plugin_Stop;
}
return Plugin_Continue;
}
/*____________________________________________________________________________*/
/**
* Handles shield mode immunity when client is about to become infected.
*
* Zombies will get a shield against knock back, while humans become immune of
* infections.
*
* @param client Client deploying shield.
*
* @return True if infection will be handled by this module, false if
* infection can be applied instantly.
*/
bool:ImmunityShieldModeHandler(client)
{
// Check if shield is active.
if (PlayerImmunityTimer[client] != INVALID_HANDLE)
{
// Block infection.
return true;
}
// Shield is not active, allow infection.
return false;
}
/*____________________________________________________________________________*/
/**
* Attempts to deploy the shield.
*
* @param client Client index.
*/
ImmunityDeployShield(client)
{
// Check if shield is available.
if (!ImmunityCanDeployShield(client))
{
// Not available.
return;
}
// Deploy the shield.
PlayerImmunityDuration[client] = ClassGetImmunityAmount(client);
PlayerImmunityLastUse[client] = GetTime();
PlayerImmunityTimer[client] = CreateTimer(1.0, ImmunityShieldTimerHandler, client, TIMER_FLAG_NO_MAPCHANGE | TIMER_REPEAT);
// Trigger initial countdown.
ImmunityShieldTimerHandler(PlayerImmunityTimer[client], client);
}
/*____________________________________________________________________________*/
/**
* Shield timer handler. Handles countdown and shield removal when time is up.
*/
public Action:ImmunityShieldTimerHandler(Handle:timer, any:client)
{
// Verify that client is still connected and alive.
if (!IsClientInGame(client) || !IsPlayerAlive(client))
{
// Client disconnected or died. Abort immunity action.
ImmunityAbortHandler(client);
return Plugin_Stop;
}
// Reduce duration.
PlayerImmunityDuration[client] -= 1;
// Print remaining shield time.
TranslationPrintCenterText(client, "Immunity Shield Time Left", PlayerImmunityDuration[client]);
// Check if time is up.
if (PlayerImmunityDuration[client] <= 0)
{
// Time is up. Reset data, but not last use timestamp.
ImmunityAbortHandler(client, false);
return Plugin_Stop;
}
return Plugin_Continue;
}
/*____________________________________________________________________________*/
/**
* Aborts any immunity mode in action (shields, delays, etc.). Resets values.
*
* @param client Client that's aborting immunity mode actions.
* @param resetLastUse Reset timestamp of last use. This will reset cooldown.
*/
ImmunityAbortHandler(client, bool:resetLastUse = true)
{
// Stop timer, if running.
ZREndTimer(PlayerImmunityTimer[client]);
// Reset data.
PlayerImmunityDuration[client] = -1;
PlayerImmunityAttacker[client] = 0;
PlayerImmunityThresholdPassed[client] = false;
if (resetLastUse)
{
PlayerImmunityLastUse[client] = 0;
}
}
/*____________________________________________________________________________*/
/**
* Aborts all immunity modes in action.
*
* @param resetLastUse Reset timestamp of last use. This will reset cooldown.
*/
ImmunityAbortAll(bool:resetLastUse = true)
{
for (new client = 0; client < MAXPLAYERS + 1; client++)
{
ImmunityAbortHandler(resetLastUse);
}
}
/*____________________________________________________________________________*/
/**
* Client is about to receive knock back.
*
* @param Client that's receiving knock back.
*
* @return True if knock back should be blocked, false otherwise.
*/
bool:ImmunityOnClientKnockBack(client)
{
// Knock back filter is currently only used in shield mode.
if (ClassGetImmunityMode(client) == Immunity_Shield)
{
// Client must be zombie. (In case a future change allow knock back
// on humans.)
if (!InfectIsClientInfected(client))
{
// Client is human, allow knock back.
return false;
}
// Block knock back if shield is deployed.
if (PlayerImmunityTimer[client] != INVALID_HANDLE)
{
// Block knock back.
return true;
}
}
// Allow knock back.
return false;
}
/*____________________________________________________________________________*/
/**
* Client was infected.
*/
ImmunityOnClientInfected(client)
{
// In case client was infected through an admin command or mother zombie
// selection, abort other actions in progress.
ImmunityAbortHandler(client);
}
/*____________________________________________________________________________*/
/**
* Client was turned back into a human.
*/
ImmunityOnClientHuman(client)
{
ImmunityAbortHandler(client);
}
/*____________________________________________________________________________*/
/**
* Client died.
*/
ImmunityOnClientDeath(client)
{
ImmunityAbortHandler(client, false);
}
/*____________________________________________________________________________*/
/**
* Client connected to the server.
*/
ImmunityClientInit(client)
{
ImmunityAbortHandler(client);
}
/*____________________________________________________________________________*/
/**
* Client spawned.
*/
ImmunityClientSpawn(client)
{
ImmunityAbortHandler(client, false);
}
/*____________________________________________________________________________*/
/**
* Client disconnected.
*/
ImmunityOnClientDisconnect(client)
{
ImmunityAbortHandler(client);
// Loop through attacker cache and remove client (set to 0).
for (new victim = 0; victim < sizeof(PlayerImmunityAttacker); victim++)
{
if (PlayerImmunityAttacker[victim] == client)
{
// The victim was attacked by this client, but the client is
// disconnecting now. Reset the attacker index to the world index.
PlayerImmunityAttacker[victim] = 0;
}
}
}
/*____________________________________________________________________________*/
/**
* Client changed team.
*/
ImmunityOnClientTeam(client)
{
ImmunityAbortHandler(client);
}
/*____________________________________________________________________________*/
/**
* Round ended.
*/
ImmunityOnRoundEnd()
{
ImmunityAbortAll();
}
/*____________________________________________________________________________*/
/**
* Map ended.
*/
ImmunityOnMapEnd()
{
ImmunityAbortAll();
}
/*____________________________________________________________________________*/
/**
* Returns whether the specified damage will take a client's HP below the
* infection threshold. Only used by "infect" immunity mode.
*
* If threshold is disabled (zero) this function will always return false.
*
* @param client Client index.
* @param damage Damage applied to client.
*
* @return True if client HP go below threshold (immunity_amount) when
* applying damage, false if above threshold or if threshold
* is disabled (zero).
*/
bool:ImmunityBelowInfectThreshold(client, Float:damage)
{
new threshold = ClassGetImmunityAmount(client);
new clientHP = GetClientHealth(client);
new dmg = RoundToNearest(damage);
// Check if the damage go below the HP threshold. Client can only go below
// threshold when threshold is enabled (above zero).
if (clientHP - dmg <= threshold && threshold > 0)
{
return true;
}
return false;
}
/*____________________________________________________________________________*/
/**
* Converts a string to an immunity mode.
*
* @param mode String to convert.
*
* @return Immunity mode or Immunity_Invalid on error.
*/
ImmunityMode:ImmunityStringToMode(const String:mode[])
{
if (strlen(mode) == 0)
{
return Immunity_Invalid;
}
if (StrEqual(mode, "none", false))
{
return Immunity_None;
}
if (StrEqual(mode, "kill", false))
{
return Immunity_Kill;
}
else if (StrEqual(mode, "full", false))
{
return Immunity_Full;
}
else if (StrEqual(mode, "infect", false))
{
return Immunity_Infect;
}
else if (StrEqual(mode, "damage", false))
{
return Immunity_Damage;
}
else if (StrEqual(mode, "delay", false))
{
return Immunity_Delay;
}
else if (StrEqual(mode, "shield", false))
{
return Immunity_Shield;
}
return Immunity_Invalid;
}
/*____________________________________________________________________________*/
/**
* Converts an immunity mode to a string.
*
* @param mode Mode to convert.
* @param buffer Destination string buffer.
* @param maxlen Size of buffer.
*
* @return Number of cells written.
*/
ImmunityModeToString(ImmunityMode:mode, String:buffer[], maxlen)
{
if (mode == Immunity_Invalid)
{
return 0;
}
switch (mode)
{
case Immunity_None:
{
return strcopy(buffer, maxlen, "none");
}
case Immunity_Kill:
{
return strcopy(buffer, maxlen, "kill");
}
case Immunity_Full:
{
return strcopy(buffer, maxlen, "full");
}
case Immunity_Infect:
{
return strcopy(buffer, maxlen, "infect");
}
case Immunity_Damage:
{
return strcopy(buffer, maxlen, "damage");
}
case Immunity_Delay:
{
return strcopy(buffer, maxlen, "delay");
}
case Immunity_Shield:
{
return strcopy(buffer, maxlen, "shield");
}
}
return 0;
}
/*____________________________________________________________________________*/
/**
* Returns whether the amount value is valid for the specified mode.
*
* @param mode Immunity mode to validate against.
* @param amount Amount value to test.
*
* @return True if valid, false otherwise.
*/
bool:ImmunityIsValidAmount(ImmunityMode:mode, amount)
{
switch (mode)
{
case Immunity_Invalid:
{
return false;
}
case Immunity_None:
{
// Immunity mode disabled, amount ignored.
return true;
}
case Immunity_Kill:
{
// Amount isn't used in this mode.
return true;
}
case Immunity_Full:
{
// Amount isn't used in this mode.
return true;
}
case Immunity_Infect:
{
// Check if HP threshold is negative.
if (amount < 0)
{
return false;
}
// There's no upper limit. If the value is too high it will
// overflow and become negative.
}
case Immunity_Damage:
{
// Amount isn't used in this mode.
return true;
}
case Immunity_Delay:
{
if (amount <= 0 || amount > IMMUNITY_MAX_DELAY)
{
return false;
}
}
case Immunity_Shield:
{
if (amount <= 0 || amount > IMMUNITY_MAX_SHIELD_TIME)
{
return false;
}
}
default:
{
// Invalid mode.
return false;
}
}
// Passed.
return true;
}
/*____________________________________________________________________________*/
/**
* Returns whether the cooldown value is valid for the specified mode.
*
* @param mode Immunity mode to validate against.
* @param cooldown Cooldown value to test.
*
* @return True if valid, false otherwise.
*/
bool:ImmunityIsValidCooldown(ImmunityMode:mode, cooldown)
{
switch (mode)
{
case Immunity_Invalid:
{
return false;
}
case Immunity_None:
{
// Immunity mode disabled, amount ignored.
return true;
}
case Immunity_Kill:
{
// Cooldown isn't used in this mode.
return true;
}
case Immunity_Full:
{
// Cooldown isn't used in this mode.
return true;
}
case Immunity_Infect:
{
// Cooldown isn't used in this mode.
return true;
}
case Immunity_Damage:
{
// Cooldown isn't used in this mode.
return true;
}
case Immunity_Delay:
{
if (cooldown < 0)
{
return false;
}
// No upper limit. It may be intentional to use a high value so that
// the section attack will remove all delay and infect instantly.
}
case Immunity_Shield:
{
if (cooldown < 0)
{
return false;
}
// No upper limit. It may be intentional to use a high value so that
// the shield can only be used once per life.
}
default:
{
// Invalid mode.
return false;
}
}
// Passed.
return true;
}
/*____________________________________________________________________________*/
/**
* Returns whether the client is allowed to deploy a shield now. Tests whether
* the client has shield immunity mode and whether cooldown is done, or a shield
* is already active.
*
* @param client Client index.
* @param printResponse Whether a response message is printed on the
* client's screen.
*
* @return True if shield can be deployed, false otherwise.
*/
bool:ImmunityCanDeployShield(client, bool:printResponse = true)
{
// Check immunity mode.
if (ClassGetImmunityMode(client) != Immunity_Shield)
{
if (printResponse)
{
TranslationPrintToChat(client, "Immunity Shield Not Available");
}
return false;
}
// Check if cooldown is still in progress.
new cooldown = ClassGetImmunityCooldown(client);
new timeLeft = PlayerImmunityLastUse[client] + cooldown - GetTime();
if (timeLeft > 0)
{
if (printResponse)
{
TranslationPrintToChat(client, "Immunity Shield Cooldown", timeLeft);
}
return false;
}
// Check if a shield is already deployed.
if (PlayerImmunityTimer[client] != INVALID_HANDLE)
{
return false;
}
// Humans cannot deploy shield before first zombie.
if (!InfectHasZombieSpawned())
{
return false;
}
return true;
}