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

379 lines
11 KiB
SourcePawn

/*
* ============================================================================
*
* Zombie:Reloaded
*
* File: knockback.inc
* Type: Module
* Description: Handles knockback on clients.
*
* 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/>.
*
* ============================================================================
*/
float g_fKnockbackScale[MAXPLAYERS + 1];
float g_fKnockbackForceLimit[MAXPLAYERS + 1];
bool g_bKnockbackFrame[MAXPLAYERS + 1];
bool g_bKnockbackFrameLimitVel[MAXPLAYERS + 1];
float g_fKnockbackVectors[MAXPLAYERS + 1][3];
float g_fKnockbackVelLimit[MAXPLAYERS + 1] = {-1.0, ...};
/**
* Minimum upwards boost that is required to push zombies off the ground.
*/
#define CSGO_KNOCKBACK_BOOST 251.0
#define CSGO_KNOCKBACK_BOOST_MAX 350.0
/**
* Client is joining the server.
*
* @param client The client index.
*/
void KnockbackClientInit(int client)
{
g_fKnockbackScale[client] = 1.0;
g_fKnockbackForceLimit[client] = 0.0;
g_bKnockbackFrame[client] = false;
g_bKnockbackFrameLimitVel[client] = false;
g_fKnockbackVectors[client][0] = 0.0;
g_fKnockbackVectors[client][1] = 0.0;
g_fKnockbackVectors[client][2] = 0.0;
g_fKnockbackVelLimit[client] = -1.0;
}
/**
* Client is leaving the server.
*
* @param client The client index.
*/
void KnockbackOnClientDisconnect(int client)
{
KnockbackClientInit(client);
}
/**
* Client died.
*/
void KnockbackOnClientDeath(int client)
{
KnockbackClientInit(client);
}
void KnockbackSetClientScale(int client, float fScale)
{
g_fKnockbackScale[client] = fScale;
}
void KnockbackSetClientMaxForce(int client, float fForce)
{
if(g_fKnockbackForceLimit[client] >= 0.0)
g_fKnockbackForceLimit[client] = fForce;
}
void KnockbackSetClientMaxVelocity(int client, float fVelocity)
{
g_fKnockbackForceLimit[client] = fVelocity;
}
/**
* Called before every server frame. Note that you should avoid
* doing expensive computations or declaring large local arrays.
*/
void KnockbackOnGameFrame()
{
for(int client = 1; client <= MaxClients; client++)
{
if (g_bKnockbackFrame[client] && g_fKnockbackForceLimit[client] > 0.0)
{
// Get the knockback force
float magnitude = GetVectorLength(g_fKnockbackVectors[client]);
// ... and limit it if it's too high.
if(magnitude > g_fKnockbackForceLimit[client])
{
ScaleVector(g_fKnockbackVectors[client], g_fKnockbackForceLimit[client] / magnitude);
}
// Apply the knockback.
KnockbackApplyVector(client, g_fKnockbackVectors[client], g_bKnockbackFrameLimitVel[client]);
g_bKnockbackFrame[client] = false;
g_bKnockbackFrameLimitVel[client] = false;
g_fKnockbackVectors[client][0] = 0.0;
g_fKnockbackVectors[client][1] = 0.0;
g_fKnockbackVectors[client][2] = 0.0;
}
}
}
/**
* Hook: KnockbackOnClientHurt
* Called after client has been hurt.
*
* @param victim The client index.
* @param attacker The client index of the attacker.
* @param inflictor The entity index of the inflictor.
* @param weaponname The weapon used.
* @param hitgroup Hitgroup attacker has damaged.
* @param damage The amount of damage inflicted.
*/
public void KnockbackOnClientHurt(int victim, int attacker, int inflictor, const char[] weaponname, int hitgroup, float damage, int damagetype, int weapon, int damagecustom)
{
// Client is a human, then stop.
if (InfectIsClientHuman(victim))
{
return;
}
// If attacker is a zombie, then stop.
if (InfectIsClientInfected(attacker))
{
return;
}
// Block knock back if an immunity mode is handling this.
if (ImmunityOnClientKnockBack(victim))
{
return;
}
// Get zombie knockback value.
new Float:knockback = ClassGetKnockback(victim);
new Float:clientloc[3];
new Float:attackerloc[3];
GetClientAbsOrigin(victim, clientloc);
// Check if a grenade was thrown.
if (StrEqual(weaponname, "hegrenade"))
{
// Get the location of the grenade.
if (KnockbackFindExplodingGrenade(attackerloc) == -1)
{
// If the grenade wasn't found, then stop.
return;
}
}
else
{
// Get attackers eye position.
GetClientEyePosition(attacker, attackerloc);
// Get attackers eye angles.
new Float:attackerang[3];
GetClientEyeAngles(attacker, attackerang);
// Calculate knockback end-vector.
TR_TraceRayFilter(attackerloc, attackerang, MASK_ALL, RayType_Infinite, KnockbackTRFilter);
TR_GetEndPosition(clientloc);
}
new bool:weapons = GetConVarBool(g_hCvarsList[CVAR_WEAPONS]);
if (weapons)
{
new weaponindex = WeaponsNameToIndex(weaponname);
if (weaponindex != -1)
{
// Apply weapon knockback multiplier.
knockback *= WeaponsGetKnockback(weaponindex);
}
}
bool custom = view_as<bool>(damagecustom & ZR_KNOCKBACK_CUSTOM);
bool scale = custom && view_as<bool>(damagecustom & ZR_KNOCKBACK_SCALE);
bool limitforce = custom && view_as<bool>(damagecustom & ZR_KNOCKBACK_LIMITFORCE);
bool limitvel = custom && view_as<bool>(damagecustom & ZR_KNOCKBACK_LIMITVEL);
if (custom)
ToolsSetClientLastHitGroup(victim, HITGROUP_GENERIC);
new bool:hitgroups = GetConVarBool(g_hCvarsList[CVAR_HITGROUPS]);
if (hitgroups)
{
new hitgroupindex = HitgroupToIndex(hitgroup);
if (hitgroupindex != -1)
{
// Apply hitgroup knockback multiplier.
knockback *= HitgroupsGetKnockback(hitgroupindex);
}
}
// Apply damage knockback multiplier.
knockback *= damage;
// Custom knockback scale.
if (scale)
knockback *= g_fKnockbackScale[victim];
// Apply knockback.
if (knockback)
KnockbackSetVelocity(victim, attackerloc, clientloc, knockback, limitforce, limitvel);
}
/**
* Sets velocity on a player.
*
* @param client The client index.
* @param startpoint The starting coordinate to push from.
* @param endpoint The ending coordinate to push towards.
* @param magnitude Magnitude of the push.
*/
KnockbackSetVelocity(client, const Float:startpoint[3], const Float:endpoint[3], Float:magnitude, bool limitforce, bool limitvel)
{
// Create vector from the given starting and ending points.
new Float:vector[3];
MakeVectorFromPoints(startpoint, endpoint, vector);
// Normalize the vector (equal magnitude at varying distances).
NormalizeVector(vector, vector);
// Apply the magnitude by scaling the vector (multiplying each of its components).
ScaleVector(vector, magnitude);
if (limitforce && g_fKnockbackForceLimit[client])
{
AddVectors(g_fKnockbackVectors[client], vector, g_fKnockbackVectors[client]);
g_bKnockbackFrame[client] = true;
g_bKnockbackFrameLimitVel[client] |= limitvel;
return;
}
KnockbackApplyVector(client, vector, limitvel);
}
void KnockbackApplyVector(int client, float vector[3], bool limitvel)
{
// CS: GO workaround. Apply knock back boost if enabled.
if (g_Game == Game_CSGO && GetConVarBool(g_hCvarsList[CVAR_CLASSES_CSGO_KNOCKBACK_BOOST]))
{
new flags = GetEntityFlags(client);
new Float:velocity[3];
ToolsGetClientVelocity(client, velocity);
// Remove boost if current velocity is too high.
if (velocity[2] > CSGO_KNOCKBACK_BOOST_MAX)
{
// Don't add extra boost.
vector[2] = 0.0;
}
else if (flags & FL_ONGROUND && vector[2] < CSGO_KNOCKBACK_BOOST)
{
// Apply minimum boost required to push player off the ground.
vector[2] = CSGO_KNOCKBACK_BOOST;
}
}
// Get their current velocity.
float velocity[3];
ToolsGetClientVelocity(client, velocity);
// Calculate their speed.
float magnitude_pre = GetVectorLength(velocity);
// Add the knockback to their velocity.
AddVectors(vector, velocity, velocity);
// Should we limit their knockback velocity?
float maxvel = GetConVarFloat(g_hCvarsList[CVAR_KNOCKBACK_MAXVEL]);
if (limitvel && g_fKnockbackVelLimit[client] >= 0.0 || maxvel > 0.0)
{
if(g_fKnockbackVelLimit[client] >= 0.0 && g_fKnockbackVelLimit[client] < maxvel)
maxvel = g_fKnockbackVelLimit[client];
// Calculate their new speed.
float magnitude_post = GetVectorLength(velocity);
// Did we actually push them back or just slow them down?
if (magnitude_post > magnitude_pre)
{
// Would their knockback velocity be higher than wanted?
if(magnitude_post > maxvel)
{
// ... then scale it down.
ScaleVector(velocity, maxvel / magnitude_post);
}
}
}
// Set the new client's velocity.
ToolsSetClientVelocity(client, velocity);
}
/**
* Trace Ray forward, used as a filter to continue tracing if told so. (See sdktools_trace.inc)
*
* @param entity The entity index.
* @param contentsMask The contents mask.
* @return True to allow hit, false to continue tracing.
*/
public bool:KnockbackTRFilter(entity, contentsMask)
{
// If entity is a player, continue tracing.
if (entity > 0 && entity < MAXPLAYERS)
{
return false;
}
// Allow hit.
return true;
}
/**
* Find the location of an exploding grenade (currently inflicting damage in player_hurt).
*
* @param heLoc The location of the exploding grenade.
* @return The entity index of the grenade.
*/
KnockbackFindExplodingGrenade(Float:heLoc[3])
{
decl String:classname[64];
// Find max entities and loop through all of them.
new maxentities = GetMaxEntities();
for (new x = MaxClients; x <= maxentities; x++)
{
// If entity is invalid, then stop.
if (!IsValidEdict(x))
{
continue;
}
// If entity isn't a grenade, then stop.
GetEdictClassname(x, classname, sizeof(classname));
if (!StrEqual(classname, "hegrenade_projectile", false))
{
continue;
}
// If m_takedamage is set to 0, we found our grenade.
new takedamage = GetEntProp(x, Prop_Data, "m_takedamage");
if (takedamage == 0)
{
// Return its location.
GetEntPropVector(x, Prop_Send, "m_vecOrigin", heLoc);
// Return its entity index.
return x;
}
}
// Didn't find the grenade.
return -1;
}