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

Counter-Strike: Source/Scripting/VScript Examples: Difference between revisions

From Valve Developer Community
Jump to navigation Jump to search
No edit summary
No edit summary
Line 266: Line 266:
     {
     {
         local ply = GetPlayerFromUserID(d.userid)
         local ply = GetPlayerFromUserID(d.userid)
        local weapon = NetProps.GetPropEntity(ply, "m_hActiveWeapon")


         if(NetProps.GetPropEntity(ply, "m_hActiveWeapon").GetClassname() == "weapon_ak47")
         if(weapon.GetClassname() == "weapon_ak47")
         {
         {
             m_hTargetStart <- SpawnEntityFromTable("info_target", {
             m_hTargetStart <- SpawnEntityFromTable("info_target", {
Line 276: Line 277:
             })
             })


             m_hTargetStart.SetAbsOrigin(Vector(ply.EyePosition().x, ply.EyePosition().y, ply.EyePosition().z - 5))
             m_hTargetStart.SetAbsOrigin(ply.GetAttachmentOrigin(ply.LookupAttachment("muzzle_flash")))
             m_hTargetEnd.SetAbsOrigin(Vector(d.x, d.y, d.z))
             m_hTargetEnd.SetAbsOrigin(Vector(d.x, d.y, d.z))


             m_hBeam <- SpawnEntityFromTable("env_beam", {
             m_hBeam <- SpawnEntityFromTable("env_beam", {
                 rendercolor = "0 255 255"
                 rendercolor = "0 228 255"
                 LightningStart = m_hTargetStart.GetName()
                 LightningStart = m_hTargetStart.GetName()
                 LightningEnd = m_hTargetEnd.GetName()
                 LightningEnd = m_hTargetEnd.GetName()

Revision as of 06:59, 28 February 2025

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")
        {
            m_hTargetStart <- SpawnEntityFromTable("info_target", {
                targetname = UniqueString()
            })
            m_hTargetEnd <- SpawnEntityFromTable("info_target", {
                targetname = UniqueString()
            })

            m_hTargetStart.SetAbsOrigin(ply.GetAttachmentOrigin(ply.LookupAttachment("muzzle_flash")))
            m_hTargetEnd.SetAbsOrigin(Vector(d.x, d.y, d.z))

            m_hBeam <- SpawnEntityFromTable("env_beam", {
                rendercolor = "0 228 255"
                LightningStart = m_hTargetStart.GetName()
                LightningEnd = m_hTargetEnd.GetName()
                BoltWidth = 1
                texture = "sprites/laserbeam.spr"
                spawnflags = 1
            })

            EntFireByHandle(m_hBeam, "Kill", "", 0, null, null)

            EntFireByHandle(m_hTargetStart, "Kill", "", 0.01, null, null)
            EntFireByHandle(m_hTargetEnd, "Kill", "", 0.01, null, null)
        }
    }
}
__CollectGameEventCallbacks(m_Events)