This article relates to the game "Counter-Strike: Source". Click here for more information.

VScript Examples

From Valve Developer Community
Jump to navigation Jump to search
English (en)Translate (Translate)
VScript

Counter-Strike: Source 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

Note.pngNote:Requires CollectEventsInScope function from the game events example
CollectEventsInScope
({
    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)