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
(Created page with "{{LanguageBar}} {{CSS topicon|docs}} {{css}} This page contains examples of vscripts specifically for {{css|1}}. Generic examples that work on all VScript games can be found on the SDK 2013 page. == Lag compensating any entity == {{css}} 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 no...")
 
No edit summary
Line 170: Line 170:


ProjectileCreate()
ProjectileCreate()
</source>
== Add a chat command, that changes color of entity in crosshair ==
<source lang=js>
local uniqid = UniqueString()
getroottable()[uniqid] <-
{
    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")
        }
    }
}
local evtable = getroottable()[uniqid]
foreach( k, v in evtable) evtable[k] = v.bindenv(this)
__CollectGameEventCallbacks(evtable)
function SetColor( e, r, g, b )
{
    local clr = (r) | (g << 8) | (b << 16)
    NetProps.SetPropInt(e, "m_clrRender", clr)
}
</source>
</source>

Revision as of 08:12, 22 February 2025

English (en)Translate (Translate)

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

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

Add a chat command, that changes color of entity in crosshair

local uniqid = UniqueString()
getroottable()[uniqid] <-
{
    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")
        }
    }
}
local evtable = getroottable()[uniqid]
foreach( k, v in evtable) evtable[k] = v.bindenv(this)
__CollectGameEventCallbacks(evtable)

function SetColor( e, r, g, b )
{
    local clr = (r) | (g << 8) | (b << 16)
    NetProps.SetPropInt(e, "m_clrRender", clr)
}