View Single Post
Author Message
Morell
AlliedModders Donor
Join Date: Jun 2010
Location: World
Old 07-22-2014 , 11:31   CS:GO Z:R fixes and workarounds
Reply With Quote #1

After dropping our CS:GO server months ago, we decided to move back to it. We have been trying to find a fix/workaround that lets us have a decent knockback for CS:GO. The workaround isn't equal to what you'd be expecting if you had only played CS:S' zombie mods, but it is enough to be enjoyable. It is based (we cooperated with mappers to get it) on setting the basevelocity field when hit, which lets you have a subtle knockback/push while on the ground (it lets a player hold a zombie by himself alone, which is enough for us). As seen on CS:GO, you'll get a normal knockback while on the air.

knockback.inc
Code:
/**
 * 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 has been hurt.
 *
 * @param client        The client index. (zombie)
 * @param attacker      The attacker index. (human)
 * @param weapon        The weapon used.
 * @param hitgroup      Hitgroup attacker has damaged. 
 * @param dmg_health    Damage done.
 */
KnockbackOnClientHurt(client, attacker, const String:weapon[], hitgroup, dmg_health)
{
    // If attacker is invalid, then stop.
    if (!ZRIsClientValid(attacker))
    {
        return;
    }
    
    // Client is a human, then stop.
    if (InfectIsClientHuman(client))
    {
        return;
    }
    
    // If attacker is a zombie, then stop.
    if (InfectIsClientInfected(attacker))
    {
        return;
    }
    
    // Block knock back if an immunity mode is handling this.
    if (ImmunityOnClientKnockBack(client))
    {
        return;
    }
    
    // Get zombie knockback value.
    new Float:knockback = ClassGetKnockback(client);
    
    new Float:clientloc[3];
    new Float:attackerloc[3];
    
    GetClientAbsOrigin(client, clientloc);
    
    // Check if a grenade was thrown.
    if (StrEqual(weapon, "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(weapon);
        if (weaponindex != -1)
        {
            // Apply weapon knockback multiplier.
            knockback *= WeaponsGetKnockback(weaponindex);
        }
    }
    
    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 *= float(dmg_health);
	
	knockback *= 7.5;
    
    // Apply knockback.
    KnockbackSetVelocity(client, attackerloc, clientloc, knockback);
}

/**
 * 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)
{
    // Create vector from the given starting and ending points.
    new Float:vector[3];
    MakeVectorFromPoints(startpoint, endpoint, vector);
 
    // Normalize the vector
    new Float:normal = GetVectorLength(vector);
    vector[0] /= normal;
    vector[1] /= normal;
    vector[2] /= normal;
 
    // Only apply knockback if the vector has a reasonable length (this avoids null vectors)
    if(normal>0.001)
    {
        // If we are touching the ground (with a threshold) we get a weaker Z-knockback
        // Perhaps this could be removed to suit your needs...
        if((GetClientDistanceToGround(client) > -1.0) && (GetClientDistanceToGround(client) < 1.0))
        {
                // This reduces the obnoxiousness of knockback on zombies that try to jump a crate
                // I'd recommend each server owner to tune to their specific needs.
                // It converges pretty fast, so it shouldn't be troublesome at all.
                while(vector[2]*magnitude > 64)
                {
                        magnitude/=2;
                }
        }
        // Otherwise we apply knockback with a cap
        // (256 is a good value because it's the breaking point for basevelocity to have an effect vertically, so it will push but not too much)
        else
        {
                if(magnitude > 256.0)
                {
                        magnitude = 256.0;
                }
        }
        // Apply the magnitude by scaling the vector (multiplying each of its components).
        ScaleVector(vector, magnitude);
                 
        // Add the given vector to the client's current velocity.
        // We use the basevelocity (velocity for movement in vehicles/given by other forces) for that
        new g_iBaseVelocityOffset = FindSendPropOffs("CBasePlayer","m_vecBaseVelocity");
        SetEntDataVector(client, g_iBaseVelocityOffset, vector, true);
    }
}

/**
 * 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;
}

stock Float:GetClientDistanceToGround(client)
{
	new Float:distance = 0.0;
    // Player is already standing on the ground?
    if(GetEntPropEnt(client, Prop_Send, "m_hGroundEntity") == 0)
	{
		distance = 0.0;
		return distance;
	}
	
    new Float:fOrigin[3], Float:fGround[3];
    GetClientAbsOrigin(client, fOrigin);
    
    fOrigin[2] += 10.0;
    
    TR_TraceRayFilter(fOrigin, Float:{90.0,0.0,0.0}, MASK_PLAYERSOLID, RayType_Infinite, TraceRayNoPlayers, client);
    if (TR_DidHit())
    {
        TR_GetEndPosition(fGround);
        fOrigin[2] -= 10.0;
		
	distance = GetVectorDistance(fOrigin, fGround);
        return distance;
    }
    return distance;
}

public bool:TraceRayNoPlayers(entity, mask, any:data)
{
    if(entity == data || (entity >= 1 && entity <= MaxClients))
    {
        return false;
    }
    return true;
}
To handle this and make it more enjoyable, we figured out we could depend on subtle ground knockback plus extra vertical knockback (to produce 'jumps') provided by some weapons. We are publishing our vertical knockback for grenades too in case anyone wants a similar system. On the server, we have extended it for a few other weapons (knives, for example, and the awp). We thought about forcing a vertical knockback if on the ground (as you can see on the code), but we found that to be annoying (despite other communities using it).

Code:
#include <sourcemod>
#include <sdktools>
#include <sdkhooks>
#pragma semicolon 1

new Handle:g_Cvar_MaxZKnock = INVALID_HANDLE;
new Handle:g_Cvar_HeKnock = INVALID_HANDLE;

public Plugin:myinfo ={
	name = "Z:R Grenade Boost",
	author = "Marica Stevens",
	description = "Pushes zombies upwards when hit by a grenade.",
	version = "1.0.0",
	url = "http://maricascripters.com"
};

public OnPluginStart()
{
	g_Cvar_MaxZKnock = CreateConVar("sm_max_zknock", "32.0", "Knock in Z");
	g_Cvar_HeKnock = CreateConVar("sm_he_knock", "320.0", "Max Knock in Z");
	HookEvent("player_hurt", Event_HandleNadeDamage); 
}

public OnPluginEnd()
{ 	
	UnhookEvent("player_hurt", Event_HandleNadeDamage); 
}

public Event_HandleNadeDamage(Handle:event, const String:name[], bool:dontBroadcast)
{ 
	new clientid = GetEventInt(event, "userid");
	new client = GetClientOfUserId(clientid);
	new damage = GetEventInt(event,"dmg_health");
	decl String:weapon[32];
	GetEventString(event, "weapon", weapon, sizeof(weapon));
	if(GetClientTeam(client)==2 && StrEqual("hegrenade", weapon))
	{
		new Float:vector[3];
		vector[0] = 0.0;
		vector[1] = 0.0;
		vector[2] = GetConVarFloat(g_Cvar_HeKnock)*damage;
		
		if(vector[2]> GetConVarFloat(g_Cvar_MaxZKnock))
		{
			vector[2] = GetConVarFloat(g_Cvar_MaxZKnock);
		}
		new g_iBaseVelocityOffset = FindSendPropOffs("CBasePlayer","m_vecBaseVelocity");
		SetEntDataVector(client, g_iBaseVelocityOffset, vector, true);
	}
}
Finally, to address the problem with zombies spawning as classes, we've tried to perfect the 'fix' (which didn't feel like enough to us) that Franc1sco made. We're sure that he probably updated his own, but seeing how it wasn't updated we're publishing ours.

Code:
#include <sourcemod>
#include <sdktools>
#undef REQUIRE_PLUGIN
#include <zombiereloaded>

new zombie1, zombie2, zombie3;

public Plugin:myinfo =
{
	name = "ZR Class Fix",
	author = "Mapeadores",
	description = "Class Fix (Franug plugin modification)",
	version = "1.5",
	url = "http://Mapeadores.com/"
};

public OnPluginStart()
{
	//Edit this with your zombie classes
	zombie1 = ZR_GetClassByName("Clasico");
	zombie2 = ZR_GetClassByName("Zombie Rapido");
	zombie3 = ZR_GetClassByName("Zombie Resistente");
}

public ZR_OnClientInfected(client, attacker, bool:motherInfect, bool:respawnOverride, bool:respawn)
{
	new vida = GetClientHealth(client);
	if(vida < 300)
	{
		CreateTimer(0.5, TimerAsegurarClase, client, TIMER_FLAG_NO_MAPCHANGE);
	}
}

public Action:TimerAsegurarClase(Handle:timer, any:client)
{
	AsegurarClaseDefault(client);
}

public AsegurarClaseDefault(client)
{
	SetEntityHealth(client, 5000);
	new randomnum = GetRandomInt(0, 2);
	switch(randomnum)
	{
		case 0:
		{
			SetEntityModel(client, "models/player/mapeadores/morell/zh/zh3fix.mdl");
			ZR_SelectClientClass(client, zombie3, true, true);
		}
		case 1:
		{
			SetEntityModel(client, "models/player/mapeadores/kaem/zh/zh1fix.mdl");
			ZR_SelectClientClass(client, zombie1, true, true);
		}
		case 2:
		{
			SetEntityModel(client, "models/player/mapeadores/kaem/zh/zh2fix.mdl");
			ZR_SelectClientClass(client, zombie2, true, true);
		}
	}
}
This plugin is a sample from our server. If you have seen Franc1sco's plugin you'll notice that it hasn't changed much, the only difference is that the plugin selects a valid class for a player so that this round (if possible) or next he gets a real zombie class. You'd need to adjust it to your settings by adding more classes following the style (we thought of making a nicer plugin but ended up slacking, sorry about that).

Those plugins, fixes and workarounds aren't optimal, probably, but seeing how the community doesn't seem to help, we thought that by adding our little grain of sand perhaps others will eventually join us and help the mod develop as a whole with interests aside.

On a different matter, some mappers have been porting maps with us being the test subjects. The mappers will eventually publish their ports in some sort of way, as they're following a set of porting rules to ensure that the ports have a minimal filesize while adapting the looks of the maps to the new engine. We have seen that some communities (shockingly, those that seem to rarely contribute or, rather, try to mock others whilst saying they've addressed issues) have 'stolen' those maps while they are still in the process of being perfected (and some even said they ported the maps themselves, which is rather bothersome). We'd value that, for some time, you stepped out and didn't use '_p' map ports until we have a big enough batch for everybody to use.

ATTACHED FIX WITH JARGON ZR FIXES: https://forums.alliedmods.net/showthread.php?t=200527
Attached Files
File Type: zip ZR 3.1 [KNOCKBACK FIX].zip (608.7 KB, 727 views)
File Type: sp Get Plugin or Get Source (zr_classfix.sp - 437 views - 1.4 KB)
File Type: sp Get Plugin or Get Source (zr_grenadeboost.sp - 937 views - 1.5 KB)
__________________

Last edited by Morell; 07-23-2014 at 02:57.
Morell is offline