/* * ============================================================================ * * 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 . * * ============================================================================ */ 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(damagecustom & ZR_KNOCKBACK_CUSTOM); bool scale = custom && view_as(damagecustom & ZR_KNOCKBACK_SCALE); bool limitforce = custom && view_as(damagecustom & ZR_KNOCKBACK_LIMITFORCE); bool limitvel = custom && view_as(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; }