L4D2 Vscript Helpers
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);
}