VScript Examples


This page contains examples of VScripts specifically for Counter-Strike: Source.
Generic examples that work on all VScript games can be found on the SDK 2013 page.
Lag compensating any entity
CS:S only lag compensates players. It may be useful to also compensate other entities, and this can be approximated by reading the player's current ping. Note that this is not perfect compared to the game's built-in lag compensation, but it's significantly better for players on high ping!
This example demonstrates how to lag compensate a custom moving prop_dynamic prop. At the high level:
- The prop itself is made non-solid so the bullets don't hit, as the logic will be replaced by our own
- The prop will add its current position to a list every tick, into a so-called "lag record"
- The
bullet_impact
game event is listened for, to detect when a fired bullet hits anything (a bullet should always hit something) - On impact, the prop's position is backtracked depending on the player's ping
- An intersection test is done along the bullet path against the historical prop position to see if it hit
This also a useful example to see how to create moving entities, do collision tests, etc.
Projectiles <- []
function ProjectileCreate()
{
local projectile = SpawnEntityFromTable("prop_dynamic_override",
{
origin = Vector(-435, 2183, -63)
model = "models/items/cs_gift.mdl"
solid = 0 // not solid so bullets don't hit it
})
projectile.SetMoveType(8, 0) // noclip
projectile.SetAbsVelocity(Vector(-300, 0, 0))
projectile.ValidateScriptScope()
projectile.GetScriptScope().m_lag_record <- [projectile.GetOrigin()]
AddThinkToEnt(projectile, "ProjectileThink")
Projectiles.append(projectile)
}
function ProjectileThink()
{
local origin = self.GetOrigin()
local velocity = self.GetAbsVelocity()
local deltatime = FrameTime()
// save position into history
m_lag_record.append(origin)
if (m_lag_record.len() > 100)
m_lag_record.remove(0)
// apply gravity
local gravity = 800.0 * deltatime
velocity.z -= gravity
// check for collision
local trace =
{
start = origin
end = origin + velocity * deltatime
ignore = self
}
TraceLineEx(trace)
if (trace.hit)
{
local speed = velocity.Norm()
// bounce off
velocity = (velocity - (trace.plane_normal * (velocity.Dot(trace.plane_normal) * 2.0))) * speed
}
self.SetAbsVelocity(velocity)
return -1
}
LastImpactUserID <- null
LastImpactFrame <- null
function OnGameEvent_bullet_impact(params)
{
// optimization: don't lag compensate after 1st penetration
local userid = params.userid
local frame = GetFrameCount()
if (LastImpactUserID == userid && frame == LastImpactFrame)
return
LastImpactUserID = userid
LastImpactFrame = frame
local player = GetPlayerFromUserID(params.userid)
local start = player.EyePosition()
local end = Vector(params.x, params.y, params.z)
local dir = end - start
local dist = dir.Norm()
DebugDrawLine(start, end, 0, 255, 255, false, 3.0)
local lag = GetPlayerLag(player)
local tick = TimeToTicks(lag)
foreach (projectile in Projectiles)
{
if (projectile.IsValid())
{
local lag_record = projectile.GetScriptScope().m_lag_record
local lag_record_tick = lag_record.len() - 1 - tick
if (lag_record_tick < 0) lag_record_tick = 0
local lag_origin = lag_record[lag_record_tick]
local mins = projectile.GetBoundingMins()
local maxs = projectile.GetBoundingMaxs()
DebugDrawBox(projectile.GetOrigin(), mins, maxs, 255, 0, 0, 20, 3.0)
DebugDrawBox(lag_origin, mins, maxs 0, 255, 0, 20, 3.0)
if (IntersectRayWithBox(start, dir, lag_origin + mins, lag_origin + maxs, 0.0, dist) > 0.0)
{
DebugDrawScreenTextLine(0.5, 0.45, 0, "HIT!", 255, 255, 255, 255, 0.25)
}
}
}
}
__CollectGameEventCallbacks(this)
PlayerMgr <- Entities.FindByClassname(null, "cs_player_manager")
function GetPlayerLag(player)
{
local latency = NetProps.GetPropIntArray(PlayerMgr, "m_iPing", player.entindex()) * 0.001
printl(latency)
if (player != GetListenServerHost())
latency += NetProps.GetPropFloat(player, "m_fLerpTime")
return latency
}
function TimeToTicks(time)
{
return (0.5 + time / FrameTime()).tointeger()
}
function IntersectRayWithBox(start, dir, mins, maxs, near, far)
{
foreach (i in ["x", "y", "z"])
{
local d = dir[i]
if (fabs(d) > 0.0001)
{
local recip_dir = 1.0 / d
local t1 = (mins[i] - start[i]) * recip_dir
local t2 = (maxs[i] - start[i]) * recip_dir
if (t1 < t2)
{
if (t1 >= near) near = t1
if (t2 <= far) far = t2
}
else
{
if (t2 >= near) near = t2
if (t1 <= far) far = t1
}
if (near > far) return -1.0
}
else if (start[i] < mins[i] || start[i] > maxs[i])
{
return -1.0
}
}
return near
}
ProjectileCreate()
Chat command that changes color of entity in crosshair

CollectEventsInScope
function from the game events exampleCollectEventsInScope
({
function OnGameEvent_player_say(d)
{
if (d.text[0] != '.') return
local ply = GetPlayerFromUserID(d.userid)
local msg = split(d.text, " ")
if (msg[0] == ".color" && msg.len() == 4)
{
local tr =
{
start = ply.EyePosition()
end = ply.EyePosition() + ply.EyeAngles().Forward() * 1024
ignore = ply
}
TraceLineEx(tr)
DebugDrawLine(tr.start, tr.end, 255, 0, 0, false, 64.0)
if (tr.hit && tr.enthit.GetClassname() != "worldspawn")
{
try
{
SetColor(tr.enthit, msg[1].tointeger(), msg[2].tointeger(), msg[3].tointeger())
}
catch (e)
{
ClientPrint(ply, 3, "\x07FF0000[INFO] \x01Command parameter must be a number")
return
}
ClientPrint(ply, 3, "\x07FF0000[INFO] \x01Changed the color of entity " + tr.enthit.GetClassname())
}
else
ClientPrint(ply, 3, "\x07FF0000[INFO] \x01Aim to an entity to change color")
}
}
})
function SetColor(entity, r, g, b)
{
local clr = (r) | (g << 8) | (b << 16)
NetProps.SetPropInt(entity, "m_clrRender", clr)
}
L4D2-like muzzleflashes
This creates a fancy projected light when firing weapons, like Left 4 Dead 2. Requires the effects/muzzleflash_light
texture from L4D2, or just use a plain white one and recolor it.
MuzzleflashEvents <-
{
OnGameEvent_weapon_fire = function(params)
{
if (params.weapon == "knife")
return
local player = GetPlayerFromUserID(params.userid)
local host = player == GetListenServerHost()
local light = SpawnEntityFromTable("env_projectedtexture",
{
origin = player.EyePosition() + player.EyeAngles().Forward() * -16.0
angles = player.EyeAngles()
lightfov = 110
lightcolor = "255 255 255 750"
enableshadows = host
farz = host ? 800.0 : 300.0
})
NetProps.SetPropBool(light, "m_bForcePurgeFixedupStrings", true)
light.AcceptInput("SpotlightTexture", "effects/muzzleflash_light", null, null)
EntFireByHandle(light, "Kill", "", 0.01, null, null)
}
}
__CollectGameEventCallbacks(MuzzleflashEvents)
Bullet Tracers for AK47
Bullet tracers created with env_beam
if("m_Events" in this) m_Events.clear()
m_Events <-
{
function OnGameEvent_bullet_impact(d)
{
local ply = GetPlayerFromUserID(d.userid)
local weapon = NetProps.GetPropEntity(ply, "m_hActiveWeapon")
if(weapon.GetClassname() == "weapon_ak47")
{
local targetStart = SpawnEntityFromTable("info_target", {
targetname = UniqueString()
origin = ply.GetAttachmentOrigin(ply.LookupAttachment("muzzle_flash"))
})
local targetEnd = SpawnEntityFromTable("info_target", {
targetname = UniqueString()
origin = Vector(d.x, d.y, d.z)
})
local beam = SpawnEntityFromTable("env_beam", {
rendercolor = "0 255 255"
LightningStart = targetStart.GetName()
LightningEnd = targetEnd.GetName()
BoltWidth = 1
texture = "sprites/laserbeam.spr"
spawnflags = 1
})
// when entity is created new string are placed into game string table which has a limit, if it is exceeded, the game crashes
// in our case, where we create an entity, every time the bullet_impact event is fired, this table is filled with new strings
// m_bForcePurgeFixedupStrings netprop will help you avoid this
NetProps.SetPropBool(targetStart, "m_bForcePurgeFixedupStrings", true)
NetProps.SetPropBool(targetEnd, "m_bForcePurgeFixedupStrings", true)
NetProps.SetPropBool(beam, "m_bForcePurgeFixedupStrings", true)
EntFireByHandle(beam, "Kill", "", 0, null, null)
EntFireByHandle(targetStart, "Kill", "", 0.01, null, null)
EntFireByHandle(targetEnd, "Kill", "", 0.01, null, null)
}
}
}
__CollectGameEventCallbacks(m_Events)