L4D2 Vscript Helpers

From Valve Developer Community
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

Sometimes an idea for a function is not immediately available in the List of L4D2 Script Functions

This function library was built to help you get more functions for your code.

/*
Eyal282's helper scripts:
*/

/**
 * Helper function that gets you up from every animation except for tank rock ( couldn't get it to work )
 * Uses a weird hack that is setting the player's model to his current model.
 */
function GetUp(player)
{
	player.SetModel(player.GetModelName());
}

/**
 * Helper function that unpins you.
 * Uses a weird hack that is setting the player's model to his current model to unstagger, and the unpin is done via Stagger.
 * @param player        Survivor player to unpin
 * @param noAnimation   true to make the get-up animation of the survivor instantaneous, false otherwise.
 */
function UnpinPlayer(player, noAnimation)
{
  if(!player.IsDominatedBySpecialInfected())
    return;
  
  local pinner = player.GetSpecialInfectedDominatingMe()
  
  pinner.Stagger(Vector(0, 0, 0))
  pinner.SetModel(pinner.GetModelName())
  
  if(noAnimation)
  {
    player.SetModel(player.GetModelName())
  }
}
/**
 * Helper function that removes boomer bile from target. A bug exists where the player cannot be biled until the original bile removal, but you probably want this for bile immunity.
 */
function RemoveBoomerBile(player)
{
	NetProps.SetPropFloatArray(player, "m_itTimer", 0, 1)

	// To enable being immediately rebiled, uncomment this:
	//NetProps.SetPropFloat(player, m_vomitFadeStart, Time());
}

/**
 * Helper function that finds a random spot in either the start or end safe room.
 * This is useful for things like removing RNG from competitive gamemodes, or spawning a witch in the end safe room
 * Before you first call this function, set "g_iDistanceDoor = 256.0" to account for distance to door, to avoid being stuck in door.
 */
function FindRandomSpotInSafeRoom(bStartSafeRoom)
{

	local table = {}
	NavMesh.GetAllAreas(table);

	local properSafeRoomTable = {}

	local totalEntries = 0

	foreach(key, area in table)
	{
		if(area.IsDegenerate() || !area.IsFlat())
			continue;

		// Some stupid maps like Blood Harvest finale and The Passing finale have CHECKPOINT inside a FINALE marked area.
		if(area.HasSpawnAttributes(64))
		{
			continue;
		}
		// https://developer.valvesoftware.com/wiki/List_of_L4D_Series_Nav_Mesh_Attributes
		else if(!area.HasSpawnAttributes(2048))
			continue;
		
		if((bStartSafeRoom && GetFlowPercentForPosition(area.GetCenter(), false) <= 50.0) || (!bStartSafeRoom && GetFlowPercentForPosition(area.GetCenter(), false) >= 50.0))
		{
			// Direct correlation between size of nav area and how likely it is to win the raffle to ensure a random spot is given for the average of the safe room.
			local entries = area.GetSizeX() * area.GetSizeY();
			totalEntries += entries;

			properSafeRoomTable[key] <- {area=area, entries=entries};

			continue;
		}
	}

	local luckyNumber = RandomFloat(0.0, totalEntries);
	local luckyArea = null;
	
	local relativeTotalEntries = 0;
	
	foreach(key, value in properSafeRoomTable)
	{
		local area = value.area;
		local entries = value.entries;

		if(luckyNumber <= relativeTotalEntries + entries)
		{
			luckyArea = value.area;
			
			break;
		}	
		
		relativeTotalEntries += entries;
	}

	local spot = luckyArea.FindRandomSpot();

	local door = null;

	while((door = Entities.FindByClassname(door, "prop_door_rotating_checkpoint")))
	{
		if(GetVectorDistance(door.GetOrigin(), spot) < g_iDistanceDoor)
		{
			// Slowly care less about the distance until it's impossible to ignore.
			g_iDistanceDoor--;
			return FindRandomSpotInSafeRoom(bStartSafeRoom);
		}
	}

	door = null;
	
	// No Mercy first chapter.
	while((door = Entities.FindByClassname(door, "prop_door_rotating")))
	{
		if(GetVectorDistance(door.GetOrigin(), spot) < g_iDistanceDoor)
		{
			g_iDistanceDoor--;
			// Slowly care less about the distance until it's impossible to ignore.
			return FindRandomSpotInSafeRoom(bStartSafeRoom);
		}
	}
	return spot;
}

/**
 * Helper function that demonstrates how to prevent water drown damage, useful in very few applications, as usually the water is meant to instantly kill you.
 * In general you start with taking 3 drown damage, followed by 5. This means each time we set it to -100 it gives you drown damage immunity for 10 seconds.
 * Because it starts with 3 and ends with 5, it is really hard to determine how much seconds of drown immunity this gives, but you can always make estimate calculations
 */

function Update()
{
	local player = null

	while((player = Entities.FindByClassname(player, "player")))
	{

		NetProps.SetPropInt(player , "m_nDrownDmgRate", -1000)
	}
}

/**
 * Helper function that prevents the game from giving a pistol to an incapped player.
 */
function OnGameEvent_item_pickup( params )
{
	local ent = null
	
	if("userid" in params && params.userid == 0){
		return;
	}
	
	ent = GetPlayerFromUserID(params["userid"])

	if(ent.IsIncapacitated())
	{
		local classname = "weapon_" + params["item"];

		RemovePlayerWeapon(ent, classname);
	}
}


/**
 * Helper function to prevent witches from instakilling survivors. This is done by blocking the damage via AllowTakeDamage and manually setting the health to 0.
 * It also caps the damage to 100 to prevent dealing 400 damage when incapped. Note that she still shreds through that 400 health in under a second.
 */
function AllowTakeDamage(damageTable)
{
	local attacker = damageTable.Attacker;
	local victim = damageTable.Victim;

	if (attacker != null && victim != null && attacker.GetClassname() == "witch")
	{
		if(victim != null && victim.GetClassname() == "player" && victim.IsSurvivor())
		{
			if(damageTable.DamageDone > victim.GetMaxHealth())
				damageTable.DamageDone = victim.GetMaxHealth(); // Caps incap damage at 100.
				
			if(damageTable.DamageDone >= victim.GetHealth())
			{
				DoEntFire( "!self", "SetHealth", "0", 0, victim, victim );
			}
			else
			{
				victim.SetHealth(victim.GetHealth() - damageTable.DamageDone);
			}

			damageTable.DamageDone = 0;
			return false;
		}
	}
}


/**
 * Helper function to equip adrenaline and pipe bomb found in safe room to avoid wasting time, happens on grabbing any item, or when pressing E on safe room door.
 * If someone has a tactic to make this run when the player spawns, please alter it.
 */
function OnGameEvent_player_use( params )
{
	local autoEquipItems =
	{
		weapon_adrenaline = "Adrenaline"
		weapon_pipe_bomb = "Pipe Bomb"
	}

	if(!player.IsDead() && !player.IsDying() && player.IsSurvivor() && IsEntityInStartSafeRoom(player))
	{
		foreach(key, val in autoEquipItems)
		{
			if(DoesPlayerHaveWeapon(player, key))
			{
				continue;
			}
			else
			{
				local weapon = null;
				while ( weapon = Entities.FindByClassname( weapon, key + "_spawn" ) )
				{
					if ( weapon.IsValid() )
					{
						// If a weapon is nodraw, it's deleted for all intents and purposes.
						if( IsEntityInStartSafeRoom(weapon) && !(NetProps.GetPropInt(weapon, "m_fEffects") & 32))
						{
							DoEntFire( "!self", "Use", "", 0, player, weapon );
							
							ClientPrint(player, 5, "Equipped " + val + " from safe room.");
							
							break;
						}
					}
				}
			}
		}
	}
}

/**
 * Checks whether or not an entity is in the starting safe room
 * 
 * @param entity     Entity to check
 * @return           true if entity is in start safe room, false otherwise.
 * @notes			 This works for the safe area on the first chapter of a campaign.
 */
function IsEntityInStartSafeRoom(entity)
{
	local origin = entity.GetOrigin();
	
	local navArea = NavMesh.GetNearestNavArea(origin, 2048, true, true);

	if(navArea != null)
	{
		// Some stupid maps like Blood Harvest finale and The Passing finale have CHECKPOINT inside a FINALE marked area.
		if(navArea.HasSpawnAttributes(64))
		{
			return false;
		}
		// https://developer.valvesoftware.com/wiki/List_of_L4D_Series_Nav_Mesh_Attributes
		else if(navArea.HasSpawnAttributes(2048) && GetFlowPercentForPosition(origin, false) <= 50.0)
		{
			return true;
		}

	}

	return false;
}



/**
 * Checks whether or not an entity is in the ending safe room
 * 
 * @param entity     Entity to check
 * @return           true if entity is in end safe room, false otherwise.
 * @notes			 It is unknown whether or not this works for rescue vehicles.
 */
function IsEntityInEndSafeRoom(entity)
{
	local origin = entity.GetOrigin();
	
	local navArea = NavMesh.GetNearestNavArea(origin, 2048, true, true);

	if(navArea != null)
	{
		// Some stupid maps like Blood Harvest finale and The Passing finale have CHECKPOINT inside a FINALE marked area.
		if(navArea.HasSpawnAttributes(64))
		{
			return false;
		}
		// https://developer.valvesoftware.com/wiki/List_of_L4D_Series_Nav_Mesh_Attributes
		else if(navArea.HasSpawnAttributes(2048) && GetFlowPercentForPosition(origin, false) >= 50.0)
		{
			return true;
		}

	}

	return false;
}



/**
 * Checks whether or not an player owns a weapon by its classname
 * 
 * @param player     player to check
 * @param classname  classname string to check
 * @return           true if player has the weapon in inventory, false otherwise
 */
function DoesPlayerHaveWeapon(player, classname)
{
	local invTable = {};

	GetInvTable(player, invTable);
	
	foreach(slot, weapon in invTable)
	{
		if(weapon.GetClassname() == classname)
		{
			return true;
		}
	}

	return false;
}



/**
 * Removes a weapon from a client's inventory by classname
 * 
 * @param player     player to remove
 * @param classname  classname string to remove.
 * @noreturn
 */
function RemovePlayerWeapon(player, classname)
{
	local invTable = {};

	GetInvTable(player, invTable);
	
	foreach(slot, weapon in invTable)
	{
		if(weapon.GetClassname() == classname)
		{
			weapon.Kill();
		}
	}

	return false;
}



/**
 * Checks whether or an entity is on the ground.
 * 
 * @param entity    Entity to check
 * @return			true if entity is on the ground, false otherwise ( in the air )
 * @error           Entity cannot have m_hGroundEntity, likely only for entities without physics.
 * @notes 			It is unknown what happens for surfing entities on slopes.
 * @notes 			Not working for some entities, check this instead: https://github.com/L4D2Scripters/vslib/blob/master/VSLib/Entity.nut#L4531
 */
function IsEntityOnGround(entity)
{
	local groundEntity = NetProps.GetPropEntity(entity, "m_hGroundEntity");

	// Cannot find a ground entity = air.
	if(groundEntity == null)
		return false;
	
	return true;
}



/**
 * Checks whether or a player is throwing a tank rock. This can work on non-tanks by looping all rocks and finding their thrower
 * 
 * @param player		Player to check
 * @return			true if player has an active rock, false otherwise.
 * @notes 			Due to checking every rock's owner, this won't error on non-tanks.
 * @notes			This is not exclusive to the tank rock animation. This is true for as long as the tank has a rock active.
 */
function IsPlayerThrowingRock(player)
{
	local rock = null;
	
	while((rock = Entities.FindByClassname(rock, "tank_rock")))
	{
		if(NetProps.GetPropEntity(rock, "m_hThrower") == player)
			return true;
	}

	return false;
}



/**
 * Checks how many seconds a burning tank has to live from fire alone.
 * 
 * @param player		Player to check
 * @return			returns a float of the seconds left for the player.
 * @notes			This works for every player, even if not a tank, and works if the player is not burning. It's just a formula.
 */
function GetTankBurnSecondsLeft(player)
{
	local gamemode = Convars.GetStr("mp_gamemode")
	local difficulty = Convars.GetStr("z_difficulty")
	local burnDuration = 0.0;

	switch(gamemode)
	{
		case "versus" : case "survival" :
			burnDuration = Convars.GetFloat("tank_burn_duration");
		break;

		default:
			switch(difficulty)
			{
				case "Impossible":
					burnDuration = Convars.GetFloat("tank_burn_duration_expert");
				break;

				case "Hard":
					burnDuration = Convars.GetFloat("tank_burn_duration_hard");
				break;

				default :
					burnDuration = Convars.GetFloat("tank_burn_duration");
				break;
			}
		break;
	}

	return (player.GetHealth().tofloat() / player.GetMaxHealth().tofloat()) * burnDuration;
}




/*
Anonymous's helper scripts:
*/

function IsMissionFinale()
{
	if(IsMissionFinalMap())
	{
		return true;
	}

	return false;

}

// Double helper functions to skip the never ending intro.
// According to https://forums.alliedmods.net/showthread.php?p=2686527 it's a round_start of first chapters.
function OnGameEvent_gameinstructor_nodraw( params )
{
	if(!Director.IsFirstMapInScenario())
		return;

	SkipIntro();
}

function SkipIntro()
{
    local info_director = Entities.FindByClassname(null, "info_director");
    if (!info_director || !info_director.IsValid())
    {
        printl("info_director not found!");
        return;
    }
    
    DoEntFire("!self", "ReleaseSurvivorPositions", "", 0, null, info_director);
    DoEntFire("!self", "FinishIntro", "", 0, null, info_director);
    
    local ent = null;
    while (ent = Entities.FindByClassname(ent, "point_viewcontrol_survivor"))
    {
        if (ent.IsValid())
            DoEntFire("!self", "StartMovement", "", 0, null, ent);
    }
    
    ent = null;
    while (ent = Entities.FindByClassname(ent, "point_viewcontrol_multiplayer"))
    {
        if (ent.IsValid())
            DoEntFire("!self", "StartMovement", "", 0, null, ent);
    }
    
    local tbl = { origin = Vector(0, 0, 0), angles = QAngle(0, 0, 0), spawnflags = 1, rendercolor = "0 0 0", renderamt = 255, holdtime = 1, duration = 1 };
    ent = SpawnEntityFromTable("env_fade", tbl);
    if (!ent)
    {
        printl("Could not create env_fade!");
        return;
    }
    ent.ValidateScriptScope();
    
    DoEntFire("!self", "Fade", "", 0, null, ent);
    DoEntFire("!self", "Kill", "", 2.5, null, ent);
}