L4D2 Vscript Helpers

From Valve Developer Community
Jump to navigation Jump to search

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);
}